-
Notifications
You must be signed in to change notification settings - Fork 59
/
testnodes.go
429 lines (363 loc) · 15.1 KB
/
testnodes.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
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// Package testnodes contains stubbed implementations of the StorageProviderNode
// and StorageClientNode interface to simulate communications with a filecoin node
package testnodes
import (
"context"
"errors"
"fmt"
"io/ioutil"
"sync"
"testing"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v8/verifreg"
"github.com/filecoin-project/go-state-types/builtin/v9/market"
"github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/go-fil-markets/commp"
"github.com/filecoin-project/go-fil-markets/shared"
"github.com/filecoin-project/go-fil-markets/shared_testutil"
"github.com/filecoin-project/go-fil-markets/storagemarket"
)
// Below fake node implementations
// StorageMarketState represents a state for the storage market that can be inspected
// - methods on the provider nodes will affect this state
type StorageMarketState struct {
TipSetToken shared.TipSetToken
Epoch abi.ChainEpoch
DealID abi.DealID
Balances map[address.Address]abi.TokenAmount
Providers map[address.Address]*storagemarket.StorageProviderInfo
}
// NewStorageMarketState returns a new empty state for the storage market
func NewStorageMarketState() *StorageMarketState {
return &StorageMarketState{
Epoch: 0,
DealID: 0,
Balances: map[address.Address]abi.TokenAmount{},
Providers: map[address.Address]*storagemarket.StorageProviderInfo{},
}
}
// AddFunds adds funds for a given address in the storage market
func (sma *StorageMarketState) AddFunds(addr address.Address, amount abi.TokenAmount) {
if existing, ok := sma.Balances[addr]; ok {
sma.Balances[addr] = big.Add(existing, amount)
} else {
sma.Balances[addr] = amount
}
}
// Balance returns the balance of a given address in the market
func (sma *StorageMarketState) Balance(addr address.Address) storagemarket.Balance {
if existing, ok := sma.Balances[addr]; ok {
return storagemarket.Balance{Locked: big.NewInt(0), Available: existing}
}
return storagemarket.Balance{Locked: big.NewInt(0), Available: big.NewInt(0)}
}
// StateKey returns a state key with the storage market states set Epoch
func (sma *StorageMarketState) StateKey() (shared.TipSetToken, abi.ChainEpoch) {
return sma.TipSetToken, sma.Epoch
}
// FakeCommonNode implements common methods for the storage & client node adapters
// where responses are stubbed
type FakeCommonNode struct {
SMState *StorageMarketState
DealFunds *shared_testutil.TestDealFunds
AddFundsCid cid.Cid
ReserveFundsError error
VerifySignatureFails bool
GetBalanceError error
GetChainHeadError error
SignBytesError error
PreCommittedSectorNumber abi.SectorNumber
PreCommittedIsActive bool
DealPreCommittedSyncError error
DealPreCommittedAsyncError error
DealCommittedSyncError error
DealCommittedAsyncError error
WaitForDealCompletionError error
OnDealExpiredError error
OnDealSlashedError error
OnDealSlashedEpoch abi.ChainEpoch
WaitForMessageBlocks bool
WaitForMessageError error
WaitForMessageExitCode exitcode.ExitCode
WaitForMessageRetBytes []byte
WaitForMessageFinalCid cid.Cid
WaitForMessageNodeError error
WaitForMessageCalls []cid.Cid
DelayFakeCommonNode DelayFakeCommonNode
}
// DelayFakeCommonNode allows configuring delay in the FakeCommonNode functions
type DelayFakeCommonNode struct {
OnDealSectorPreCommitted bool
OnDealSectorPreCommittedChan chan struct{}
OnDealSectorCommitted bool
OnDealSectorCommittedChan chan struct{}
OnDealExpiredOrSlashed bool
OnDealExpiredOrSlashedChan chan struct{}
ValidatePublishedDeal bool
ValidatePublishedDealChan chan struct{}
}
// GetChainHead returns the state id in the storage market state
func (n *FakeCommonNode) GetChainHead(ctx context.Context) (shared.TipSetToken, abi.ChainEpoch, error) {
if n.GetChainHeadError == nil {
key, epoch := n.SMState.StateKey()
return key, epoch, nil
}
return []byte{}, 0, n.GetChainHeadError
}
// AddFunds adds funds to the given actor in the storage market state
func (n *FakeCommonNode) AddFunds(ctx context.Context, addr address.Address, amount abi.TokenAmount) (cid.Cid, error) {
n.SMState.AddFunds(addr, amount)
return n.AddFundsCid, nil
}
// ReserveFunds reserves funds required for a deal with the storage market actor
func (n *FakeCommonNode) ReserveFunds(ctx context.Context, wallet, addr address.Address, amt abi.TokenAmount) (cid.Cid, error) {
if n.ReserveFundsError == nil {
_, _ = n.DealFunds.Reserve(amt)
balance := n.SMState.Balance(addr)
if balance.Available.LessThan(amt) {
return n.AddFunds(ctx, addr, big.Sub(amt, balance.Available))
}
}
return cid.Undef, n.ReserveFundsError
}
// ReleaseFunds releases funds reserved with ReserveFunds
func (n *FakeCommonNode) ReleaseFunds(ctx context.Context, addr address.Address, amt abi.TokenAmount) error {
n.DealFunds.Release(amt)
return nil
}
// WaitForMessage simulates waiting for a message to appear on chain
func (n *FakeCommonNode) WaitForMessage(ctx context.Context, mcid cid.Cid, onCompletion func(exitcode.ExitCode, []byte, cid.Cid, error) error) error {
n.WaitForMessageCalls = append(n.WaitForMessageCalls, mcid)
if n.WaitForMessageError != nil {
return n.WaitForMessageError
}
if n.WaitForMessageBlocks {
// just leave the test node in this state to simulate a long operation
return nil
}
finalCid := n.WaitForMessageFinalCid
if finalCid.Equals(cid.Undef) {
finalCid = mcid
}
return onCompletion(n.WaitForMessageExitCode, n.WaitForMessageRetBytes, finalCid, n.WaitForMessageNodeError)
}
// GetBalance returns the funds in the storage market state
func (n *FakeCommonNode) GetBalance(ctx context.Context, addr address.Address, tok shared.TipSetToken) (storagemarket.Balance, error) {
if n.GetBalanceError == nil {
return n.SMState.Balance(addr), nil
}
return storagemarket.Balance{}, n.GetBalanceError
}
// VerifySignature just always returns true, for now
func (n *FakeCommonNode) VerifySignature(ctx context.Context, signature crypto.Signature, addr address.Address, data []byte, tok shared.TipSetToken) (bool, error) {
return !n.VerifySignatureFails, nil
}
// SignBytes simulates signing data by returning a test signature
func (n *FakeCommonNode) SignBytes(ctx context.Context, signer address.Address, b []byte) (*crypto.Signature, error) {
if n.SignBytesError == nil {
return shared_testutil.MakeTestSignature(), nil
}
return nil, n.SignBytesError
}
func (n *FakeCommonNode) DealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, isVerified bool) (abi.TokenAmount, abi.TokenAmount, error) {
return abi.NewTokenAmount(5000), builtin.TotalFilecoin, nil
}
// OnDealSectorPreCommitted returns immediately, and returns stubbed errors
func (n *FakeCommonNode) OnDealSectorPreCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, proposal market.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorPreCommittedCallback) error {
if n.DelayFakeCommonNode.OnDealSectorPreCommitted {
select {
case <-ctx.Done():
return ctx.Err()
case <-n.DelayFakeCommonNode.OnDealSectorPreCommittedChan:
}
}
if n.DealPreCommittedSyncError == nil {
cb(n.PreCommittedSectorNumber, n.PreCommittedIsActive, n.DealPreCommittedAsyncError)
}
return n.DealPreCommittedSyncError
}
// OnDealSectorCommitted returns immediately, and returns stubbed errors
func (n *FakeCommonNode) OnDealSectorCommitted(ctx context.Context, provider address.Address, dealID abi.DealID, sectorNumber abi.SectorNumber, proposal market.DealProposal, publishCid *cid.Cid, cb storagemarket.DealSectorCommittedCallback) error {
if n.DelayFakeCommonNode.OnDealSectorCommitted {
select {
case <-ctx.Done():
return ctx.Err()
case <-n.DelayFakeCommonNode.OnDealSectorCommittedChan:
}
}
if n.DealCommittedSyncError == nil {
cb(n.DealCommittedAsyncError)
}
return n.DealCommittedSyncError
}
// OnDealExpiredOrSlashed simulates waiting for a deal to be expired or slashed, but provides stubbed behavior
func (n *FakeCommonNode) OnDealExpiredOrSlashed(ctx context.Context, dealID abi.DealID, onDealExpired storagemarket.DealExpiredCallback, onDealSlashed storagemarket.DealSlashedCallback) error {
if n.DelayFakeCommonNode.OnDealExpiredOrSlashed {
select {
case <-ctx.Done():
return ctx.Err()
case <-n.DelayFakeCommonNode.OnDealExpiredOrSlashedChan:
}
}
if n.WaitForDealCompletionError != nil {
return n.WaitForDealCompletionError
}
if n.OnDealSlashedError != nil {
onDealSlashed(abi.ChainEpoch(0), n.OnDealSlashedError)
return nil
}
if n.OnDealExpiredError != nil {
onDealExpired(n.OnDealExpiredError)
return nil
}
if n.OnDealSlashedEpoch == 0 {
onDealExpired(nil)
return nil
}
onDealSlashed(n.OnDealSlashedEpoch, nil)
return nil
}
var _ storagemarket.StorageCommon = (*FakeCommonNode)(nil)
// FakeClientNode is a node adapter for a storage client whose responses
// are stubbed
type FakeClientNode struct {
FakeCommonNode
ClientAddr address.Address
MinerAddr address.Address
WorkerAddr address.Address
ValidationError error
ValidatePublishedDealID abi.DealID
ValidatePublishedError error
ExpectedMinerInfos []address.Address
receivedMinerInfos []address.Address
}
// ListStorageProviders lists the providers in the storage market state
func (n *FakeClientNode) ListStorageProviders(ctx context.Context, tok shared.TipSetToken) ([]*storagemarket.StorageProviderInfo, error) {
providers := make([]*storagemarket.StorageProviderInfo, 0, len(n.SMState.Providers))
for _, provider := range n.SMState.Providers {
providers = append(providers, provider)
}
return providers, nil
}
// ValidatePublishedDeal always succeeds
func (n *FakeClientNode) ValidatePublishedDeal(ctx context.Context, deal storagemarket.ClientDeal) (abi.DealID, error) {
if n.DelayFakeCommonNode.ValidatePublishedDeal {
select {
case <-ctx.Done():
return 0, ctx.Err()
case <-n.DelayFakeCommonNode.ValidatePublishedDealChan:
}
}
return n.ValidatePublishedDealID, n.ValidatePublishedError
}
// SignProposal signs a deal with a dummy signature
func (n *FakeClientNode) SignProposal(ctx context.Context, signer address.Address, proposal market.DealProposal) (*market.ClientDealProposal, error) {
return &market.ClientDealProposal{
Proposal: proposal,
ClientSignature: *shared_testutil.MakeTestSignature(),
}, nil
}
// GetDefaultWalletAddress returns a stubbed ClientAddr
func (n *FakeClientNode) GetDefaultWalletAddress(ctx context.Context) (address.Address, error) {
return n.ClientAddr, nil
}
// GetMinerInfo returns stubbed information for the first miner in storage market state
func (n *FakeClientNode) GetMinerInfo(ctx context.Context, maddr address.Address, tok shared.TipSetToken) (*storagemarket.StorageProviderInfo, error) {
n.receivedMinerInfos = append(n.receivedMinerInfos, maddr)
info, ok := n.SMState.Providers[maddr]
if !ok {
return nil, errors.New("Provider not found")
}
return info, nil
}
func (n *FakeClientNode) VerifyExpectations(t *testing.T) {
require.Equal(t, n.ExpectedMinerInfos, n.receivedMinerInfos)
}
var _ storagemarket.StorageClientNode = (*FakeClientNode)(nil)
// FakeProviderNode implements functions specific to the StorageProviderNode
type FakeProviderNode struct {
FakeCommonNode
MinerAddr address.Address
MinerWorkerError error
PieceLength uint64
PieceSectorID uint64
PublishDealID abi.DealID
PublishDealsError error
WaitForPublishDealsError error
OnDealCompleteError error
OnDealCompleteSkipCommP bool
LastOnDealCompleteBytes []byte
OnDealCompleteCalls []storagemarket.MinerDeal
LocatePieceForDealWithinSectorError error
DataCap *verifreg.DataCap
GetDataCapErr error
lk sync.Mutex
Sealed map[abi.SectorNumber]bool
}
// PublishDeals simulates publishing a deal by adding it to the storage market state
func (n *FakeProviderNode) PublishDeals(ctx context.Context, deal storagemarket.MinerDeal) (cid.Cid, error) {
if n.PublishDealsError == nil {
return shared_testutil.GenerateCids(1)[0], nil
}
return cid.Undef, n.PublishDealsError
}
// WaitForPublishDeals simulates waiting for the deal to be published and
// calling the callback with the results
func (n *FakeProviderNode) WaitForPublishDeals(ctx context.Context, mcid cid.Cid, proposal market.DealProposal) (*storagemarket.PublishDealsWaitResult, error) {
if n.WaitForPublishDealsError != nil {
return nil, n.WaitForPublishDealsError
}
finalCid := n.WaitForMessageFinalCid
if finalCid.Equals(cid.Undef) {
finalCid = mcid
}
return &storagemarket.PublishDealsWaitResult{
DealID: n.PublishDealID,
FinalCid: finalCid,
}, nil
}
// OnDealComplete simulates passing of the deal to the storage miner, and does nothing
func (n *FakeProviderNode) OnDealComplete(ctx context.Context, deal storagemarket.MinerDeal, pieceSize abi.UnpaddedPieceSize, pieceReader shared.ReadSeekStarter) (*storagemarket.PackingResult, error) {
n.OnDealCompleteCalls = append(n.OnDealCompleteCalls, deal)
n.LastOnDealCompleteBytes, _ = ioutil.ReadAll(pieceReader)
if n.OnDealCompleteError != nil || n.OnDealCompleteSkipCommP {
return &storagemarket.PackingResult{}, n.OnDealCompleteError
}
// We read in all the bytes from the reader above, so seek back to the start
err := pieceReader.SeekStart()
if err != nil {
return nil, fmt.Errorf("on deal complete: seeking to start of piece data: %w", err)
}
// Generate commP
pieceCID, err := commp.GenerateCommp(pieceReader, uint64(pieceSize), uint64(pieceSize))
if err != nil {
return nil, fmt.Errorf("on deal complete: generating commp: %w", err)
}
// Check that commP of the data matches the proposal piece CID
if pieceCID != deal.Proposal.PieceCID {
return nil, fmt.Errorf("on deal complete: proposal piece CID %s does not match calculated commP %s", deal.Proposal.PieceCID, pieceCID)
}
return &storagemarket.PackingResult{}, n.OnDealCompleteError
}
// GetMinerWorkerAddress returns the address specified by MinerAddr
func (n *FakeProviderNode) GetMinerWorkerAddress(ctx context.Context, miner address.Address, tok shared.TipSetToken) (address.Address, error) {
if n.MinerWorkerError == nil {
return n.MinerAddr, nil
}
return address.Undef, n.MinerWorkerError
}
// GetDataCap gets the current data cap for addr
func (n *FakeProviderNode) GetDataCap(ctx context.Context, addr address.Address, tok shared.TipSetToken) (*verifreg.DataCap, error) {
return n.DataCap, n.GetDataCapErr
}
// GetProofType returns the miner's proof type.
func (n *FakeProviderNode) GetProofType(ctx context.Context, addr address.Address, tok shared.TipSetToken) (abi.RegisteredSealProof, error) {
return abi.RegisteredSealProof_StackedDrg2KiBV1, nil
}
var _ storagemarket.StorageProviderNode = (*FakeProviderNode)(nil)