forked from NebulousLabs/Sia
/
editor.go
193 lines (168 loc) · 5.67 KB
/
editor.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package contractor
import (
"errors"
"sync"
"github.com/NebulousLabs/Sia/crypto"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/modules/renter/proto"
"github.com/NebulousLabs/Sia/types"
)
var errInvalidEditor = errors.New("editor has been invalidated because its contract is being renewed")
// the contractor will cap host's MaxCollateral setting to this value
var maxUploadCollateral = types.SiacoinPrecision.Mul64(1e3).Div(modules.BlockBytesPerMonthTerabyte) // 1k SC / TB / Month
// An Editor modifies a Contract by communicating with a host. It uses the
// contract revision protocol to send modification requests to the host.
// Editors are the means by which the renter uploads data to hosts.
type Editor interface {
// Upload revises the underlying contract to store the new data. It
// returns the Merkle root of the data.
Upload(data []byte) (root crypto.Hash, err error)
// Address returns the address of the host.
Address() modules.NetAddress
// ContractID returns the FileContractID of the contract.
ContractID() types.FileContractID
// EndHeight returns the height at which the contract ends.
EndHeight() types.BlockHeight
// Close terminates the connection to the host.
Close() error
}
// A hostEditor modifies a Contract by calling the revise RPC on a host. It
// implements the Editor interface. hostEditors are safe for use by
// multiple goroutines.
type hostEditor struct {
clients int // safe to Close when 0
contractor *Contractor
editor *proto.Editor
endHeight types.BlockHeight
id types.FileContractID
invalid bool // true if invalidate has been called
netAddress modules.NetAddress
mu sync.Mutex
}
// invalidate sets the invalid flag and closes the underlying proto.Editor.
// Once invalidate returns, the hostEditor is guaranteed to not further revise
// its contract. This is used during contract renewal to prevent an Editor
// from revising a contract mid-renewal.
func (he *hostEditor) invalidate() {
he.mu.Lock()
defer he.mu.Unlock()
if !he.invalid {
he.editor.Close()
he.invalid = true
}
he.contractor.mu.Lock()
delete(he.contractor.editors, he.id)
delete(he.contractor.revising, he.id)
he.contractor.mu.Unlock()
}
// Address returns the NetAddress of the host.
func (he *hostEditor) Address() modules.NetAddress { return he.netAddress }
// ContractID returns the ID of the contract being revised.
func (he *hostEditor) ContractID() types.FileContractID { return he.id }
// EndHeight returns the height at which the host is no longer obligated to
// store the file.
func (he *hostEditor) EndHeight() types.BlockHeight { return he.endHeight }
// Close cleanly terminates the revision loop with the host and closes the
// connection.
func (he *hostEditor) Close() error {
he.mu.Lock()
defer he.mu.Unlock()
he.clients--
// Close is a no-op if invalidate has been called, or if there are other
// clients still using the hostEditor.
if he.invalid || he.clients > 0 {
return nil
}
he.invalid = true
he.contractor.mu.Lock()
delete(he.contractor.editors, he.id)
delete(he.contractor.revising, he.id)
he.contractor.mu.Unlock()
return he.editor.Close()
}
// Upload negotiates a revision that adds a sector to a file contract.
func (he *hostEditor) Upload(data []byte) (_ crypto.Hash, err error) {
he.mu.Lock()
defer he.mu.Unlock()
if he.invalid {
return crypto.Hash{}, errInvalidEditor
}
// Perform the upload.
_, sectorRoot, err := he.editor.Upload(data)
if err != nil {
return crypto.Hash{}, err
}
return sectorRoot, nil
}
// Editor returns a Editor object that can be used to upload, modify, and
// delete sectors on a host.
func (c *Contractor) Editor(id types.FileContractID, cancel <-chan struct{}) (_ Editor, err error) {
id = c.ResolveID(id)
c.mu.RLock()
cachedEditor, haveEditor := c.editors[id]
height := c.blockHeight
renewing := c.renewing[id]
c.mu.RUnlock()
if renewing {
// Cannot use the editor if the contract is being renewed.
return nil, errors.New("currently renewing that contract")
} else if haveEditor {
// This editor already exists. Mark that there are now two routines
// using the editor, and then return the editor that already exists.
cachedEditor.mu.Lock()
cachedEditor.clients++
cachedEditor.mu.Unlock()
return cachedEditor, nil
}
// Check that the contract and host are both available, and run some brief
// sanity checks to see that the host is not swindling us.
contract, haveContract := c.staticContracts.View(id)
if !haveContract {
return nil, errors.New("no record of that contract")
}
host, haveHost := c.hdb.Host(contract.HostPublicKey)
if height > contract.EndHeight {
return nil, errors.New("contract has already ended")
} else if !haveHost {
return nil, errors.New("no record of that host")
} else if host.StoragePrice.Cmp(maxStoragePrice) > 0 {
return nil, errTooExpensive
} else if host.UploadBandwidthPrice.Cmp(maxUploadPrice) > 0 {
return nil, errTooExpensive
}
// Acquire the revising lock.
c.mu.Lock()
alreadyRevising := c.revising[contract.ID]
if alreadyRevising {
c.mu.Unlock()
return nil, errors.New("already revising that contract")
}
c.revising[contract.ID] = true
c.mu.Unlock()
// Release the revising lock early in the event of an error.
defer func() {
if err != nil {
c.mu.Lock()
delete(c.revising, contract.ID)
c.mu.Unlock()
}
}()
// Create the editor.
e, err := c.staticContracts.NewEditor(host, contract.ID, height, c.hdb, cancel)
if err != nil {
return nil, err
}
// cache editor
he := &hostEditor{
clients: 1,
contractor: c,
editor: e,
endHeight: contract.EndHeight,
id: contract.ID,
netAddress: host.NetAddress,
}
c.mu.Lock()
c.editors[contract.ID] = he
c.mu.Unlock()
return he, nil
}