forked from NebulousLabs/Sia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
allowance.go
140 lines (128 loc) · 4.03 KB
/
allowance.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package contractor
import (
"errors"
"reflect"
"github.com/NebulousLabs/Sia/modules"
)
var (
errAllowanceNoHosts = errors.New("hosts must be non-zero")
errAllowanceNotSynced = errors.New("you must be synced to set an allowance")
errAllowanceWindowSize = errors.New("renew window must be less than period")
errAllowanceZeroPeriod = errors.New("period must be non-zero")
// ErrAllowanceZeroWindow is returned when the caller requests a
// zero-length renewal window. This will happen if the caller sets the
// period to 1 block, since RenewWindow := period / 2.
ErrAllowanceZeroWindow = errors.New("renew window must be non-zero")
)
// SetAllowance sets the amount of money the Contractor is allowed to spend on
// contracts over a given time period, divided among the number of hosts
// specified. Note that Contractor can start forming contracts as soon as
// SetAllowance is called; that is, it may block.
//
// In most cases, SetAllowance will renew existing contracts instead of
// forming new ones. This preserves the data on those hosts. When this occurs,
// the renewed contracts will atomically replace their previous versions. If
// SetAllowance is interrupted, renewed contracts may be lost, though the
// allocated funds will eventually be returned.
//
// If a is the empty allowance, SetAllowance will archive the current contract
// set. The contracts cannot be used to create Editors or Downloads, and will
// not be renewed.
//
// TODO: can an Editor or Downloader be used across renewals?
// TODO: will hosts allow renewing the same contract twice?
//
// NOTE: At this time, transaction fees are not counted towards the allowance.
// This means the contractor may spend more than allowance.Funds.
func (c *Contractor) SetAllowance(a modules.Allowance) error {
if reflect.DeepEqual(a, modules.Allowance{}) {
return c.managedCancelAllowance()
}
if reflect.DeepEqual(a, c.allowance) {
return nil
}
// sanity checks
if a.Hosts == 0 {
return errAllowanceNoHosts
} else if a.Period == 0 {
return errAllowanceZeroPeriod
} else if a.RenewWindow == 0 {
return ErrAllowanceZeroWindow
} else if a.RenewWindow >= a.Period {
return errAllowanceWindowSize
} else if !c.cs.Synced() {
return errAllowanceNotSynced
}
c.log.Println("INFO: setting allowance to", a)
c.mu.Lock()
// set the current period to the blockheight if the existing allowance is
// empty
if reflect.DeepEqual(c.allowance, modules.Allowance{}) {
c.currentPeriod = c.blockHeight
}
c.allowance = a
err := c.saveSync()
c.mu.Unlock()
if err != nil {
c.log.Println("Unable to save contractor after setting allowance:", err)
}
// Interrupt any existing maintenance and launch a new round of
// maintenance.
c.managedInterruptContractMaintenance()
go c.threadedContractMaintenance()
return nil
}
// managedCancelAllowance handles the special case where the allowance is empty.
func (c *Contractor) managedCancelAllowance() error {
c.log.Println("INFO: canceling allowance")
// first need to invalidate any active editors/downloaders
// NOTE: this code is the same as in managedRenewContracts
c.mu.Lock()
ids := c.contracts.IDs()
for _, id := range ids {
// we aren't renewing, but we don't want new editors or downloaders to
// be created
c.renewing[id] = true
}
c.mu.Unlock()
defer func() {
c.mu.Lock()
for _, id := range ids {
delete(c.renewing, id)
}
c.mu.Unlock()
}()
for _, id := range ids {
c.mu.RLock()
e, eok := c.editors[id]
d, dok := c.downloaders[id]
c.mu.RUnlock()
if eok {
e.invalidate()
}
if dok {
d.invalidate()
}
}
// Clear out the allowance and save.
c.mu.Lock()
c.allowance = modules.Allowance{}
c.currentPeriod = 0
err := c.saveSync()
c.mu.Unlock()
if err != nil {
return err
}
// Issue an interrupt to any in-progress contract maintenance thread.
c.managedInterruptContractMaintenance()
// Cycle through all contracts and delete them.
ids = c.contracts.IDs()
for _, id := range ids {
contract, exists := c.contracts.Acquire(id)
if !exists {
continue
}
c.contracts.Delete(contract)
}
return nil
}