-
Notifications
You must be signed in to change notification settings - Fork 12
/
contracts.go
345 lines (304 loc) · 11.3 KB
/
contracts.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package contracts
import (
"errors"
"fmt"
"strings"
"sync"
"time"
lru "github.com/hashicorp/golang-lru/v2"
rhp2 "go.sia.tech/core/rhp/v2"
"go.sia.tech/core/types"
"go.uber.org/zap"
)
// A SectorAction denotes the type of action to be performed on a
// contract sector.
const (
SectorActionAppend SectorAction = "append"
SectorActionUpdate SectorAction = "update"
SectorActionSwap SectorAction = "swap"
SectorActionTrim SectorAction = "trim"
)
// ContractStatus is an enum that indicates the current status of a contract.
const (
// ContractStatusPending indicates that the contract has been formed but
// has not yet been confirmed on the blockchain. The contract is still
// usable, but there is a risk that the contract will never be confirmed.
ContractStatusPending ContractStatus = iota
// ContractStatusRejected indicates that the contract formation transaction
// was never confirmed on the blockchain
ContractStatusRejected
// ContractStatusActive indicates that the contract has been confirmed on
// the blockchain and is currently active.
ContractStatusActive
// ContractStatusSuccessful indicates that a storage proof has been
// confirmed or the contract expired without requiring the host to burn
// Siacoin (e.g. renewal, unused contracts).
ContractStatusSuccessful
// ContractStatusFailed indicates that the contract ended without a storage proof
// and the host was required to burn Siacoin.
ContractStatusFailed
)
// fields that the contracts can be sorted by.
const (
ContractSortStatus = "status"
ContractSortNegotiationHeight = "negotiationHeight"
ContractSortExpirationHeight = "expirationHeight"
)
type (
// A SectorAction denotes the type of action to be performed on a
// contract sector.
SectorAction string
// ContractStatus is an enum that indicates the current status of a contract.
ContractStatus uint8
// A SignedRevision pairs a contract revision with the signatures of the host
// and renter needed to broadcast the revision.
SignedRevision struct {
Revision types.FileContractRevision `json:"revision"`
HostSignature types.Signature `json:"hostSignature"`
RenterSignature types.Signature `json:"renterSignature"`
}
// Usage tracks the usage of a contract's funds.
Usage struct {
RPCRevenue types.Currency `json:"rpc"`
StorageRevenue types.Currency `json:"storage"`
EgressRevenue types.Currency `json:"egress"`
IngressRevenue types.Currency `json:"ingress"`
RegistryRead types.Currency `json:"registryRead"`
RegistryWrite types.Currency `json:"registryWrite"`
AccountFunding types.Currency `json:"accountFunding"`
RiskedCollateral types.Currency `json:"riskedCollateral"`
}
// A Contract contains metadata on the current state of a file contract.
Contract struct {
SignedRevision
Status ContractStatus `json:"status"`
LockedCollateral types.Currency `json:"lockedCollateral"`
Usage Usage `json:"usage"`
// NegotiationHeight is the height the contract was negotiated at.
NegotiationHeight uint64 `json:"negotiationHeight"`
// FormationConfirmed is true if the contract formation transaction
// has been confirmed on the blockchain.
FormationConfirmed bool `json:"formationConfirmed"`
// RevisionConfirmed is true if the contract revision transaction has
// been confirmed on the blockchain.
RevisionConfirmed bool `json:"revisionConfirmed"`
// ResolutionHeight is the height the storage proof was confirmed
// at. If the contract has not been resolved, the field is the zero
// value.
ResolutionHeight uint64 `json:"resolutionHeight"`
// RenewedTo is the ID of the contract that renewed this contract. If
// this contract was not renewed, this field is the zero value.
RenewedTo types.FileContractID `json:"renewedTo"`
// RenewedFrom is the ID of the contract that this contract renewed. If
// this contract is not a renewal, the field is the zero value.
RenewedFrom types.FileContractID `json:"renewedFrom"`
}
// ContractFilter defines the filter criteria for a contract query.
ContractFilter struct {
// filters
Statuses []ContractStatus `json:"statuses"`
ContractIDs []types.FileContractID `json:"contractIDs"`
RenewedFrom []types.FileContractID `json:"renewedFrom"`
RenewedTo []types.FileContractID `json:"renewedTo"`
RenterKey []types.PublicKey `json:"renterKey"`
MinNegotiationHeight uint64 `json:"minNegotiationHeight"`
MaxNegotiationHeight uint64 `json:"maxNegotiationHeight"`
MinExpirationHeight uint64 `json:"minExpirationHeight"`
MaxExpirationHeight uint64 `json:"maxExpirationHeight"`
// pagination
Limit int `json:"limit"`
Offset int `json:"offset"`
// sorting
SortField string `json:"sortField"`
SortDesc bool `json:"sortDesc"`
}
// A SectorChange defines an action to be performed on a contract's sectors.
SectorChange struct {
Action SectorAction
Root types.Hash256
A, B uint64
}
// A ContractUpdater is used to atomically update a contract's sectors
// and metadata.
ContractUpdater struct {
store ContractStore
log *zap.Logger
rootsCache *lru.TwoQueueCache[types.FileContractID, []types.Hash256] // reference to the cache in the contract manager
once sync.Once
done func() // done is called when the updater is closed.
contractID types.FileContractID
sectorActions []SectorChange
sectorRoots []types.Hash256
oldRoots []types.Hash256
}
)
var (
// ErrNotFound is returned by the contract store when a contract is not
// found.
ErrNotFound = errors.New("contract not found")
// ErrContractExists is returned by the contract store during formation when
// the contract already exists.
ErrContractExists = errors.New("contract already exists")
)
// Add returns the sum of two usages.
func (u Usage) Add(b Usage) (c Usage) {
return Usage{
RPCRevenue: u.RPCRevenue.Add(b.RPCRevenue),
StorageRevenue: u.StorageRevenue.Add(b.StorageRevenue),
EgressRevenue: u.EgressRevenue.Add(b.EgressRevenue),
IngressRevenue: u.IngressRevenue.Add(b.IngressRevenue),
AccountFunding: u.AccountFunding.Add(b.AccountFunding),
RiskedCollateral: u.RiskedCollateral.Add(b.RiskedCollateral),
RegistryRead: u.RegistryRead.Add(b.RegistryRead),
RegistryWrite: u.RegistryWrite.Add(b.RegistryWrite),
}
}
// String returns the string representation of a ContractStatus.
func (c ContractStatus) String() string {
switch c {
case ContractStatusPending:
return "pending"
case ContractStatusRejected:
return "rejected"
case ContractStatusActive:
return "active"
case ContractStatusSuccessful:
return "successful"
case ContractStatusFailed:
return "failed"
default:
panic("unrecognized contract status") // developer error
}
}
// MarshalJSON implements the json.Marshaler interface.
func (c ContractStatus) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`%q`, c.String())), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (c *ContractStatus) UnmarshalJSON(b []byte) error {
status := strings.Trim(string(b), `"`)
switch status {
case "pending":
*c = ContractStatusPending
case "rejected":
*c = ContractStatusRejected
case "active":
*c = ContractStatusActive
case "successful":
*c = ContractStatusSuccessful
case "failed":
*c = ContractStatusFailed
default:
return fmt.Errorf("unrecognized contract status: %v", status)
}
return nil
}
// RenterKey returns the renter's public key.
func (sr SignedRevision) RenterKey() types.PublicKey {
return *(*types.PublicKey)(sr.Revision.UnlockConditions.PublicKeys[0].Key)
}
// Signatures returns the host and renter transaction signatures for the
// contract revision.
func (sr SignedRevision) Signatures() []types.TransactionSignature {
return []types.TransactionSignature{
{
ParentID: types.Hash256(sr.Revision.ParentID),
Signature: sr.RenterSignature[:],
CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}},
},
{
ParentID: types.Hash256(sr.Revision.ParentID),
Signature: sr.HostSignature[:],
CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}},
PublicKeyIndex: 1,
},
}
}
// AppendSector appends a sector to the contract.
func (cu *ContractUpdater) AppendSector(root types.Hash256) {
cu.sectorActions = append(cu.sectorActions, SectorChange{
Root: root,
Action: SectorActionAppend,
})
cu.sectorRoots = append(cu.sectorRoots, root)
}
// SwapSectors swaps the sectors at the given indices.
func (cu *ContractUpdater) SwapSectors(a, b uint64) error {
if a >= uint64(len(cu.sectorRoots)) || b >= uint64(len(cu.sectorRoots)) {
return fmt.Errorf("invalid sector indices %v, %v", a, b)
}
cu.sectorActions = append(cu.sectorActions, SectorChange{
A: a,
B: b,
Action: SectorActionSwap,
})
cu.sectorRoots[a], cu.sectorRoots[b] = cu.sectorRoots[b], cu.sectorRoots[a]
return nil
}
// TrimSectors removes the last n sectors from the contract.
func (cu *ContractUpdater) TrimSectors(n uint64) error {
if n > uint64(len(cu.sectorRoots)) {
return fmt.Errorf("invalid sector count %v", n)
}
cu.sectorActions = append(cu.sectorActions, SectorChange{
A: n,
Action: SectorActionTrim,
})
cu.sectorRoots = cu.sectorRoots[:uint64(len(cu.sectorRoots))-n]
return nil
}
// UpdateSector updates the Merkle root of the sector at the given index.
func (cu *ContractUpdater) UpdateSector(root types.Hash256, i uint64) error {
if i >= uint64(len(cu.sectorRoots)) {
return fmt.Errorf("invalid sector index %v", i)
}
cu.sectorActions = append(cu.sectorActions, SectorChange{
Root: root,
A: i,
Action: SectorActionUpdate,
})
cu.sectorRoots[i] = root
return nil
}
// SectorCount returns the number of sectors in the contract.
func (cu *ContractUpdater) SectorCount() uint64 {
return uint64(len(cu.sectorRoots))
}
// SectorRoot returns the Merkle root of the sector at the given index.
func (cu *ContractUpdater) SectorRoot(i uint64) (types.Hash256, error) {
if i >= uint64(len(cu.sectorRoots)) {
return types.Hash256{}, fmt.Errorf("invalid sector index %v", i)
}
return cu.sectorRoots[i], nil
}
// MerkleRoot returns the merkle root of the contract's sector roots.
func (cu *ContractUpdater) MerkleRoot() types.Hash256 {
return rhp2.MetaRoot(cu.sectorRoots)
}
// SectorRoots returns a copy of the current state of the contract's sector roots.
func (cu *ContractUpdater) SectorRoots() []types.Hash256 {
return append([]types.Hash256(nil), cu.sectorRoots...)
}
// Close must be called when the contract updater is no longer needed.
func (cu *ContractUpdater) Close() error {
cu.once.Do(cu.done)
return nil
}
// Commit atomically applies all changes to the contract store.
func (cu *ContractUpdater) Commit(revision SignedRevision, usage Usage) error {
if revision.Revision.ParentID != cu.contractID {
panic("contract updater used with wrong contract")
}
start := time.Now()
// revise the contract
err := cu.store.ReviseContract(revision, cu.oldRoots, usage, cu.sectorActions)
if err != nil {
return err
}
// clear the committed sector actions
cu.sectorActions = cu.sectorActions[:0]
// update the roots cache
cu.rootsCache.Add(revision.Revision.ParentID, append([]types.Hash256(nil), cu.sectorRoots...))
cu.log.Debug("contract update committed", zap.String("contractID", revision.Revision.ParentID.String()), zap.Uint64("revision", revision.Revision.RevisionNumber), zap.Duration("elapsed", time.Since(start)))
return nil
}