/
allowance.go
160 lines (147 loc) · 4.73 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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package contractor
import (
"errors"
"reflect"
"github.com/HyperspaceApp/Hyperspace/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.
//
// 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. the current period is set in the past by the renew window to make sure
// the first period aligns with the first period contracts in the same way
// that future periods align with contracts
if reflect.DeepEqual(c.allowance, modules.Allowance{}) {
c.currentPeriod = c.blockHeight - a.RenewWindow
}
c.allowance = a
err := c.saveSync()
c.mu.Unlock()
if err != nil {
c.log.Println("Unable to save contractor after setting allowance:", err)
}
// Cycle through all contracts and unlock them again since they might have
// been locked by managedCancelAllowance previously.
ids := c.staticContracts.IDs()
for _, id := range ids {
contract, exists := c.staticContracts.Acquire(id)
if !exists {
continue
}
utility := contract.Utility()
utility.Locked = false
err := contract.UpdateUtility(utility)
c.staticContracts.Return(contract)
if err != nil {
return 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
// NOTE: this code is the same as in managedRenewContracts
ids := c.staticContracts.IDs()
c.mu.Lock()
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]
c.mu.RUnlock()
if eok {
e.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 mark them as !goodForRenew and !goodForUpload
ids = c.staticContracts.IDs()
for _, id := range ids {
contract, exists := c.staticContracts.Acquire(id)
if !exists {
continue
}
utility := contract.Utility()
utility.GoodForRenew = false
utility.GoodForUpload = false
utility.Locked = true
err := contract.UpdateUtility(utility)
c.staticContracts.Return(contract)
if err != nil {
return err
}
}
return nil
}