diff --git a/actors/builtin/market/cbor_gen.go b/actors/builtin/market/cbor_gen.go index aaa72050a..3db1992be 100644 --- a/actors/builtin/market/cbor_gen.go +++ b/actors/builtin/market/cbor_gen.go @@ -255,6 +255,103 @@ func (t *State) UnmarshalCBOR(r io.Reader) error { return nil } +var lengthBufPublishStorageDealsReturn = []byte{130} + +func (t *PublishStorageDealsReturn) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufPublishStorageDealsReturn); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.IDs ([]abi.DealID) (slice) + if len(t.IDs) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.IDs was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.IDs))); err != nil { + return err + } + for _, v := range t.IDs { + if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + + // t.ValidDeals (bitfield.BitField) (struct) + if err := t.ValidDeals.MarshalCBOR(w); err != nil { + return err + } + return nil +} + +func (t *PublishStorageDealsReturn) UnmarshalCBOR(r io.Reader) error { + *t = PublishStorageDealsReturn{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 2 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.IDs ([]abi.DealID) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.IDs: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.IDs = make([]abi.DealID, extra) + } + + for i := 0; i < int(extra); i++ { + + maj, val, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.IDs slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.IDs was not a uint, instead got %d", maj) + } + + t.IDs[i] = abi.DealID(val) + } + + // t.ValidDeals (bitfield.BitField) (struct) + + { + + if err := t.ValidDeals.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.ValidDeals: %w", err) + } + + } + return nil +} + var lengthBufVerifyDealsForActivationParams = []byte{129} func (t *VerifyDealsForActivationParams) MarshalCBOR(w io.Writer) error { diff --git a/actors/builtin/market/deal.go b/actors/builtin/market/deal.go index c8e6d1d8c..6a7d41519 100644 --- a/actors/builtin/market/deal.go +++ b/actors/builtin/market/deal.go @@ -45,8 +45,8 @@ var PieceCIDPrefix = market0.PieceCIDPrefix type DealProposal = market0.DealProposal // ClientDealProposal is a DealProposal signed by a client -//type ClientDealProposal struct { -// Proposal DealProposal -// ClientSignature crypto.Signature -//} +// type ClientDealProposal struct { +// Proposal DealProposal +// ClientSignature crypto.Signature +// } type ClientDealProposal = market0.ClientDealProposal diff --git a/actors/builtin/market/market_actor.go b/actors/builtin/market/market_actor.go index bc3522dee..7567f7b67 100644 --- a/actors/builtin/market/market_actor.go +++ b/actors/builtin/market/market_actor.go @@ -4,6 +4,7 @@ import ( "sort" addr "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/cbor" @@ -136,19 +137,18 @@ func (a Actor) AddBalance(rt Runtime, providerOrClientAddress *addr.Address) *ab return nil } -//type PublishStorageDealsParams struct { -// Deals []ClientDealProposal -//} +// type PublishStorageDealsParams struct { +// Deals []ClientDealProposal +// } type PublishStorageDealsParams = market0.PublishStorageDealsParams -//type PublishStorageDealsReturn struct { -// IDs []abi.DealID -//} -type PublishStorageDealsReturn = market0.PublishStorageDealsReturn +type PublishStorageDealsReturn struct { + IDs []abi.DealID + ValidDeals bitfield.BitField +} // Publish a new set of storage deals (not yet included in a sector). func (a Actor) PublishStorageDeals(rt Runtime, params *PublishStorageDealsParams) *PublishStorageDealsReturn { - // Deal message must have a From field identical to the provider of all the deals. // This allows us to retain and verify only the client's signature in each deal proposal itself. rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) @@ -181,13 +181,121 @@ func (a Actor) PublishStorageDeals(rt Runtime, params *PublishStorageDealsParams if !callerOk { rt.Abortf(exitcode.ErrForbidden, "caller %v is not worker or control address of provider %v", caller, provider) } - resolvedAddrs := make(map[addr.Address]addr.Address, len(params.Deals)) baselinePower := requestCurrentBaselinePower(rt) networkRawPower, networkQAPower := requestCurrentNetworkPower(rt) - var newDealIds []abi.DealID + // Drop invalid deals var st State + proposalCidLookup := make(map[cid.Cid]struct{}) + validProposalCids := make([]cid.Cid, 0) + validDeals := make([]ClientDealProposal, 0, len(params.Deals)) + totalClientLockup := make(map[addr.Address]abi.TokenAmount) + totalProviderLockup := abi.NewTokenAmount(0) + + validInputBf := bitfield.New() + rt.StateReadonly(&st) + msm, err := st.mutator(adt.AsStore(rt)).withPendingProposals(ReadOnlyPermission). + withEscrowTable(ReadOnlyPermission).withLockedTable(ReadOnlyPermission).build() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load state") + for di, deal := range params.Deals { + /* + drop malformed deals + */ + if err := validateDeal(rt, deal, networkRawPower, networkQAPower, baselinePower); err != nil { + rt.Log(rtt.INFO, "invalid deal %d: %s", di, err) + continue + } + if deal.Proposal.Provider != provider && deal.Proposal.Provider != providerRaw { + rt.Log(rtt.INFO, "invalid deal %d: cannot publish deals from multiple providers in one batch", di) + continue + } + client, ok := rt.ResolveAddress(deal.Proposal.Client) + if !ok { + rt.Log(rtt.INFO, "invalid deal %d: failed to resolve proposal.Client address %v for deal ", di, deal.Proposal.Client) + continue + } + + /* + drop deals with insufficient lock up to cover costs + */ + if _, ok := totalClientLockup[client]; !ok { + totalClientLockup[client] = abi.NewTokenAmount(0) + } + totalClientLockup[client] = big.Sum(totalClientLockup[client], deal.Proposal.ClientBalanceRequirement()) + clientBalanceOk, err := msm.balanceCovered(client, totalClientLockup[client]) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check client balance coverage") + if !clientBalanceOk { + rt.Log(rtt.INFO, "invalid deal: %d: insufficient client funds to cover proposal cost", di) + continue + } + totalProviderLockup = big.Sum(totalProviderLockup, deal.Proposal.ProviderCollateral) + providerBalanceOk, err := msm.balanceCovered(provider, totalProviderLockup) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check provider balance coverage") + if !providerBalanceOk { + rt.Log(rtt.INFO, "invalid deal: %d: insufficient provider funds to cover proposal cost", di) + continue + } + + /* + drop duplicate deals + */ + // Normalise provider and client addresses in the proposal stored on chain. + // Must happen after signature verification and before taking cid. + deal.Proposal.Provider = provider + resolvedAddrs[deal.Proposal.Client] = client + deal.Proposal.Client = client + + pcid, err := deal.Proposal.Cid() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalArgument, "failed to take cid of proposal %d", di) + + // check proposalCids for duplication within message batch + // check state PendingProposals for duplication across messages + duplicateInState, err := msm.pendingDeals.Has(abi.CidKey(pcid)) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check for existence of deal proposal") + _, duplicateInMessage := proposalCidLookup[pcid] + if duplicateInState || duplicateInMessage { + rt.Log(rtt.INFO, "invalid deal %d: cannot publish duplicate deal proposal %s", di) + continue + } + + /* + check VerifiedClient allowed cap and deduct PieceSize from cap + drop deals with a DealSize that cannot be fully covered by VerifiedClient's available DataCap + */ + if deal.Proposal.VerifiedDeal { + code := rt.Send( + builtin.VerifiedRegistryActorAddr, + builtin.MethodsVerifiedRegistry.UseBytes, + &verifreg.UseBytesParams{ + Address: client, + DealSize: big.NewIntUnsigned(uint64(deal.Proposal.PieceSize)), + }, + abi.NewTokenAmount(0), + &builtin.Discard{}, + ) + if code.IsError() { + rt.Log(rtt.INFO, "invalid deal %d: failed to acquire datacap exitcode: %d", di, code) + continue + } + } + + // update valid deal state + proposalCidLookup[pcid] = struct{}{} + validProposalCids = append(validProposalCids, pcid) + validDeals = append(validDeals, deal) + validInputBf.Set(uint64(di)) + } + + validDealCount, err := validInputBf.Count() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to count valid deals in bitfield") + builtin.RequirePredicate(rt, len(validDeals) == len(validProposalCids), exitcode.ErrIllegalState, + "%d valid deals but %d valid proposal cids", len(validDeals), len(validProposalCids)) + builtin.RequirePredicate(rt, uint64(len(validDeals)) == validDealCount, exitcode.ErrIllegalState, + "%d valid deals but validDealCount=%d", len(validDeals), validDealCount) + builtin.RequireParam(rt, validDealCount > 0, "All deal proposals invalid") + + var newDealIds []abi.DealID rt.StateTransaction(&st, func() { msm, err := st.mutator(adt.AsStore(rt)).withPendingProposals(WritePermission). withDealProposals(WritePermission).withDealsByEpoch(WritePermission).withEscrowTable(WritePermission). @@ -195,45 +303,23 @@ func (a Actor) PublishStorageDeals(rt Runtime, params *PublishStorageDealsParams builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load state") // All storage dealProposals will be added in an atomic transaction; this operation will be unrolled if any of them fails. - for di, deal := range params.Deals { - validateDeal(rt, deal, networkRawPower, networkQAPower, baselinePower) - - if deal.Proposal.Provider != provider && deal.Proposal.Provider != providerRaw { - rt.Abortf(exitcode.ErrIllegalArgument, "cannot publish deals from different providers at the same time") - } - - client, ok := rt.ResolveAddress(deal.Proposal.Client) - if !ok { - rt.Abortf(exitcode.ErrNotFound, "failed to resolve client address %v", deal.Proposal.Client) - } - // Normalise provider and client addresses in the proposal stored on chain (after signature verification). - deal.Proposal.Provider = provider - resolvedAddrs[deal.Proposal.Client] = client - deal.Proposal.Client = client - - err := msm.lockClientAndProviderBalances(&deal.Proposal) + // This should only fail on programmer error because all expected invalid conditions should be filtered in the first set of checks. + for vdi, validDeal := range validDeals { + err := msm.lockClientAndProviderBalances(&validDeal.Proposal) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to lock balance") id := msm.generateStorageDealID() - pcid, err := deal.Proposal.Cid() - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalArgument, "failed to take cid of proposal %d", di) - - has, err := msm.pendingDeals.Has(abi.CidKey(pcid)) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check for existence of deal proposal") - if has { - rt.Abortf(exitcode.ErrIllegalArgument, "cannot publish duplicate deals") - } - + pcid := validProposalCids[vdi] err = msm.pendingDeals.Put(abi.CidKey(pcid)) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set pending deal") - err = msm.dealProposals.Set(id, &deal.Proposal) + err = msm.dealProposals.Set(id, &validDeal.Proposal) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set deal") - // We should randomize the first epoch for when the deal will be processed so an attacker isn't able to + // We randomize the first epoch for when the deal will be processed so an attacker isn't able to // schedule too many deals for the same tick. - processEpoch := GenRandNextEpoch(deal.Proposal.StartEpoch, id) + processEpoch := GenRandNextEpoch(validDeal.Proposal.StartEpoch, id) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to generate random process epoch") err = msm.dealsByEpoch.Put(processEpoch, id) @@ -241,34 +327,14 @@ func (a Actor) PublishStorageDeals(rt Runtime, params *PublishStorageDealsParams newDealIds = append(newDealIds, id) } - err = msm.commitState() builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush state") }) - for _, deal := range params.Deals { - // Check VerifiedClient allowed cap and deduct PieceSize from cap. - // Either the DealSize is within the available DataCap of the VerifiedClient - // or this message will fail. We do not allow a deal that is partially verified. - if deal.Proposal.VerifiedDeal { - resolvedClient, ok := resolvedAddrs[deal.Proposal.Client] - builtin.RequireParam(rt, ok, "could not get resolvedClient client address") - - code := rt.Send( - builtin.VerifiedRegistryActorAddr, - builtin.MethodsVerifiedRegistry.UseBytes, - &verifreg.UseBytesParams{ - Address: resolvedClient, - DealSize: big.NewIntUnsigned(uint64(deal.Proposal.PieceSize)), - }, - abi.NewTokenAmount(0), - &builtin.Discard{}, - ) - builtin.RequireSuccess(rt, code, "failed to add verified deal for client: %v", deal.Proposal.Client) - } + return &PublishStorageDealsReturn{ + IDs: newDealIds, + ValidDeals: validInputBf, } - - return &PublishStorageDealsReturn{IDs: newDealIds} } // Changed since v2: @@ -717,57 +783,58 @@ func validateDealCanActivate(proposal *DealProposal, minerAddr addr.Address, sec return nil } -func validateDeal(rt Runtime, deal ClientDealProposal, networkRawPower, networkQAPower, baselinePower abi.StoragePower) { +func validateDeal(rt Runtime, deal ClientDealProposal, networkRawPower, networkQAPower, baselinePower abi.StoragePower) error { if err := dealProposalIsInternallyValid(rt, deal); err != nil { - rt.Abortf(exitcode.ErrIllegalArgument, "Invalid deal proposal: %s", err) + return xerrors.Errorf("Invalid deal proposal %w", err) } proposal := deal.Proposal if len(proposal.Label) > DealMaxLabelSize { - rt.Abortf(exitcode.ErrIllegalArgument, "deal label can be at most %d bytes, is %d", DealMaxLabelSize, len(proposal.Label)) + return xerrors.Errorf("deal label can be at most %d bytes, is %d", DealMaxLabelSize, len(proposal.Label)) } if err := proposal.PieceSize.Validate(); err != nil { - rt.Abortf(exitcode.ErrIllegalArgument, "proposal piece size is invalid: %v", err) + return xerrors.Errorf("proposal piece size is invalid: %w", err) } if !proposal.PieceCID.Defined() { - rt.Abortf(exitcode.ErrIllegalArgument, "proposal PieceCID undefined") + return xerrors.Errorf("proposal PieceCid undefined") } if proposal.PieceCID.Prefix() != PieceCIDPrefix { - rt.Abortf(exitcode.ErrIllegalArgument, "proposal PieceCID had wrong prefix") + return xerrors.Errorf("proposal PieceCID had wrong prefix") } if proposal.EndEpoch <= proposal.StartEpoch { - rt.Abortf(exitcode.ErrIllegalArgument, "proposal end before proposal start") + return xerrors.Errorf("proposal end before proposal start") } if rt.CurrEpoch() > proposal.StartEpoch { - rt.Abortf(exitcode.ErrIllegalArgument, "Deal start epoch has already elapsed.") + return xerrors.Errorf("Deal start epoch has already elapsed") } minDuration, maxDuration := DealDurationBounds(proposal.PieceSize) if proposal.Duration() < minDuration || proposal.Duration() > maxDuration { - rt.Abortf(exitcode.ErrIllegalArgument, "Deal duration out of bounds.") + return xerrors.Errorf("Deal duration out of bounds") } minPrice, maxPrice := DealPricePerEpochBounds(proposal.PieceSize, proposal.Duration()) if proposal.StoragePricePerEpoch.LessThan(minPrice) || proposal.StoragePricePerEpoch.GreaterThan(maxPrice) { - rt.Abortf(exitcode.ErrIllegalArgument, "Storage price out of bounds.") + return xerrors.Errorf("Storage price out of bounds") } minProviderCollateral, maxProviderCollateral := DealProviderCollateralBounds(proposal.PieceSize, proposal.VerifiedDeal, networkRawPower, networkQAPower, baselinePower, rt.TotalFilCircSupply()) if proposal.ProviderCollateral.LessThan(minProviderCollateral) || proposal.ProviderCollateral.GreaterThan(maxProviderCollateral) { - rt.Abortf(exitcode.ErrIllegalArgument, "Provider collateral out of bounds.") + return xerrors.Errorf("Provider collateral out of bounds") } minClientCollateral, maxClientCollateral := DealClientCollateralBounds(proposal.PieceSize, proposal.Duration()) if proposal.ClientCollateral.LessThan(minClientCollateral) || proposal.ClientCollateral.GreaterThan(maxClientCollateral) { - rt.Abortf(exitcode.ErrIllegalArgument, "Client collateral out of bounds.") + return xerrors.Errorf("Client collateral out of bounds") } + return nil } // diff --git a/actors/builtin/market/market_balances.go b/actors/builtin/market/market_balances.go index cceece219..c13748a1c 100644 --- a/actors/builtin/market/market_balances.go +++ b/actors/builtin/market/market_balances.go @@ -98,3 +98,16 @@ func (m *marketStateMutation) maybeLockBalance(addr addr.Address, amount abi.Tok } return nil } + +// Return true when the funds in escrow for the input address can cover an additional lockup of amountToLock +func (m *marketStateMutation) balanceCovered(addr addr.Address, amountToLock abi.TokenAmount) (bool, error) { + prevLocked, err := m.lockedTable.Get(addr) + if err != nil { + return false, xerrors.Errorf("failed to get locked balance: %w", err) + } + escrowBalance, err := m.escrowTable.Get(addr) + if err != nil { + return false, xerrors.Errorf("failed to get escrow balance: %w", err) + } + return big.Add(prevLocked, amountToLock).LessThanEqual(escrowBalance), nil +} diff --git a/actors/builtin/market/market_test.go b/actors/builtin/market/market_test.go index 6dc2ed85a..9dd5a4744 100644 --- a/actors/builtin/market/market_test.go +++ b/actors/builtin/market/market_test.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "math" "strings" "testing" @@ -833,20 +834,20 @@ func TestPublishStorageDealsFailures(t *testing.T) { a.addParticipantFunds(rt, client, big.Sub(d.ClientBalanceRequirement(), big.NewInt(1))) a.addProviderFunds(rt, d.ProviderCollateral, mAddrs) }, - exitCode: exitcode.ErrInsufficientFunds, + exitCode: exitcode.ErrIllegalArgument, }, "provider does not have enough balance for collateral": { setup: func(rt *mock.Runtime, a *marketActorTestHarness, d *market.DealProposal) { a.addParticipantFunds(rt, client, d.ClientBalanceRequirement()) a.addProviderFunds(rt, big.Sub(d.ProviderCollateral, big.NewInt(1)), mAddrs) }, - exitCode: exitcode.ErrInsufficientFunds, + exitCode: exitcode.ErrIllegalArgument, }, "unable to resolve client address": { setup: func(_ *mock.Runtime, a *marketActorTestHarness, d *market.DealProposal) { d.Client = tutil.NewBLSAddr(t, 1) }, - exitCode: exitcode.ErrNotFound, + exitCode: exitcode.ErrIllegalArgument, }, "signature is invalid": { setup: func(_ *mock.Runtime, a *marketActorTestHarness, d *market.DealProposal) { @@ -859,13 +860,13 @@ func TestPublishStorageDealsFailures(t *testing.T) { setup: func(rt *mock.Runtime, a *marketActorTestHarness, d *market.DealProposal) { a.addProviderFunds(rt, d.ProviderCollateral, mAddrs) }, - exitCode: exitcode.ErrInsufficientFunds, + exitCode: exitcode.ErrIllegalArgument, }, "no entry for provider in locked balance table": { setup: func(rt *mock.Runtime, a *marketActorTestHarness, d *market.DealProposal) { a.addParticipantFunds(rt, client, d.ClientBalanceRequirement()) }, - exitCode: exitcode.ErrInsufficientFunds, + exitCode: exitcode.ErrIllegalArgument, }, "bad piece CID": { setup: func(_ *mock.Runtime, _ *marketActorTestHarness, d *market.DealProposal) { @@ -934,7 +935,7 @@ func TestPublishStorageDealsFailures(t *testing.T) { expectQueryNetworkInfo(rt, actor) rt.SetCaller(worker, builtin.AccountActorCodeID) rt.ExpectVerifySignature(crypto.Signature{}, deal1.Client, mustCbor(&deal1), nil) - rt.ExpectAbort(exitcode.ErrInsufficientFunds, func() { + rt.ExpectAbort(exitcode.ErrIllegalArgument, func() { rt.Call(actor.PublishStorageDeals, params) }) @@ -956,7 +957,7 @@ func TestPublishStorageDealsFailures(t *testing.T) { expectQueryNetworkInfo(rt, actor) rt.SetCaller(worker, builtin.AccountActorCodeID) rt.ExpectVerifySignature(crypto.Signature{}, deal1.Client, mustCbor(&deal1), nil) - rt.ExpectAbort(exitcode.ErrInsufficientFunds, func() { + rt.ExpectAbort(exitcode.ErrIllegalArgument, func() { rt.Call(actor.PublishStorageDeals, params) }) @@ -983,9 +984,11 @@ func TestPublishStorageDealsFailures(t *testing.T) { rt.ExpectVerifySignature(crypto.Signature{}, deal1.Client, mustCbor(&deal1), nil) rt.ExpectVerifySignature(crypto.Signature{}, deal2.Client, mustCbor(&deal2), nil) - rt.ExpectAbort(exitcode.ErrIllegalArgument, func() { - rt.Call(actor.PublishStorageDeals, params) - }) + ret := rt.Call(actor.PublishStorageDeals, params) + psdRet := ret.(*market.PublishStorageDealsReturn) + valid, err := psdRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0}, valid) rt.Verify() actor.checkState(rt) diff --git a/actors/builtin/shared.go b/actors/builtin/shared.go index 597600635..70bc0da1f 100644 --- a/actors/builtin/shared.go +++ b/actors/builtin/shared.go @@ -53,8 +53,13 @@ func RequireState(rt runtime.Runtime, predicate bool, msg string, args ...interf // Aborts with an ErrIllegalArgument if predicate is not true. func RequireParam(rt runtime.Runtime, predicate bool, msg string, args ...interface{}) { + RequirePredicate(rt, predicate, exitcode.ErrIllegalArgument, msg, args...) +} + +// Aborts with `code` if predicate is not true. +func RequirePredicate(rt runtime.Runtime, predicate bool, code exitcode.ExitCode, msg string, args ...interface{}) { if !predicate { - rt.Abortf(exitcode.ErrIllegalArgument, msg, args...) + rt.Abortf(code, msg, args...) } } diff --git a/actors/test/common_test.go b/actors/test/common_test.go index 706cf4dbf..cead90aa6 100644 --- a/actors/test/common_test.go +++ b/actors/test/common_test.go @@ -83,6 +83,69 @@ func publishDeal(t *testing.T, v *vm.VM, provider, dealClient, minerID addr.Addr return result.Ret.(*market.PublishStorageDealsReturn) } +type dealBatcher struct { + deals []market.DealProposal + v *vm.VM +} + +func newDealBatcher(v *vm.VM) *dealBatcher { + return &dealBatcher{ + deals: make([]market.DealProposal, 0), + v: v, + } +} + +func (db *dealBatcher) stage(t *testing.T, dealClient, dealProvider addr.Address, dealLabel string, pieceSize abi.PaddedPieceSize, verifiedDeal bool, dealStart, + dealLifetime abi.ChainEpoch, pricePerEpoch, providerCollateral, clientCollateral abi.TokenAmount) { + deal := market.DealProposal{ + PieceCID: tutil.MakeCID(dealLabel, &market.PieceCIDPrefix), + PieceSize: pieceSize, + VerifiedDeal: verifiedDeal, + Client: dealClient, + Provider: dealProvider, + Label: dealLabel, + StartEpoch: dealStart, + EndEpoch: dealStart + dealLifetime, + StoragePricePerEpoch: pricePerEpoch, + ProviderCollateral: providerCollateral, + ClientCollateral: clientCollateral, + } + + db.deals = append(db.deals, deal) +} + +func (db *dealBatcher) publishOK(t *testing.T, sender addr.Address) *market.PublishStorageDealsReturn { + publishDealParams := market.PublishStorageDealsParams{} + for _, deal := range db.deals { + publishDealParams.Deals = append(publishDealParams.Deals, market.ClientDealProposal{ + Proposal: deal, + ClientSignature: crypto.Signature{ + Type: crypto.SigTypeBLS, + }, + }) + } + + result := vm.RequireApplyMessage(t, db.v, sender, builtin.StorageMarketActorAddr, big.Zero(), builtin.MethodsMarket.PublishStorageDeals, &publishDealParams, t.Name()) + require.Equal(t, exitcode.Ok, result.Code) + + return result.Ret.(*market.PublishStorageDealsReturn) +} + +func (db *dealBatcher) publishFail(t *testing.T, sender addr.Address) { + publishDealParams := market.PublishStorageDealsParams{} + for _, deal := range db.deals { + publishDealParams.Deals = append(publishDealParams.Deals, market.ClientDealProposal{ + Proposal: deal, + ClientSignature: crypto.Signature{ + Type: crypto.SigTypeBLS, + }, + }) + } + + result := vm.RequireApplyMessage(t, db.v, sender, builtin.StorageMarketActorAddr, big.Zero(), builtin.MethodsMarket.PublishStorageDeals, &publishDealParams, t.Name()) + require.Equal(t, exitcode.ErrIllegalArgument, result.Code) // because we can't return multiple codes for batch failures we return 16 in all cases +} + func requireActor(t *testing.T, v *vm.VM, addr address.Address) *states.Actor { a, found, err := v.GetActor(addr) require.NoError(t, err) diff --git a/actors/test/publish-deals_test.go b/actors/test/publish-deals_test.go new file mode 100644 index 000000000..9c7ac58e2 --- /dev/null +++ b/actors/test/publish-deals_test.go @@ -0,0 +1,395 @@ +package test + +import ( + "context" + "math" + "testing" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/v6/actors/builtin" + "github.com/filecoin-project/specs-actors/v6/actors/builtin/miner" + "github.com/filecoin-project/specs-actors/v6/actors/builtin/power" + "github.com/filecoin-project/specs-actors/v6/actors/builtin/verifreg" + "github.com/filecoin-project/specs-actors/v6/support/ipld" + tutil "github.com/filecoin-project/specs-actors/v6/support/testing" + "github.com/filecoin-project/specs-actors/v6/support/vm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var defaultPricePerEpoch = abi.NewTokenAmount(1 << 20) +var defaultProviderCollateral = big.Mul(big.NewInt(2), vm.FIL) +var defaultClientCollateral = big.Mul(big.NewInt(1), vm.FIL) +var dealLifeTime = abi.ChainEpoch(181 * builtin.EpochsInDay) + +func TestPublishStorageDealsFailure(t *testing.T) { + ctx := context.Background() + v := vm.NewVMWithSingletons(ctx, t, ipld.NewBlockStoreInMemory()) + addrs := vm.CreateAccounts(ctx, t, v, 5, big.Mul(big.NewInt(10_000), vm.FIL), 93837778) + worker, client1, client2, notMiner, cheapClient := addrs[0], addrs[1], addrs[2], addrs[3], addrs[4] + sealProof := abi.RegisteredSealProof_StackedDrg32GiBV1_1 + + // create miner + params := power.CreateMinerParams{ + Owner: worker, + Worker: worker, + WindowPoStProofType: abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, + Peer: abi.PeerID("not really a peer id"), + } + ret := vm.ApplyOk(t, v, worker, builtin.StoragePowerActorAddr, big.Mul(big.NewInt(100), vm.FIL), builtin.MethodsPower.CreateMiner, ¶ms) + + minerAddrs, ok := ret.(*power.CreateMinerReturn) + require.True(t, ok) + + // add market collateral for clients and miner + clientCollateral := big.Mul(big.NewInt(100), vm.FIL) + vm.ApplyOk(t, v, client1, builtin.StorageMarketActorAddr, clientCollateral, builtin.MethodsMarket.AddBalance, &client1) + vm.ApplyOk(t, v, client2, builtin.StorageMarketActorAddr, clientCollateral, builtin.MethodsMarket.AddBalance, &client2) + minerCollateral := big.Mul(big.NewInt(100), vm.FIL) + vm.ApplyOk(t, v, worker, builtin.StorageMarketActorAddr, minerCollateral, builtin.MethodsMarket.AddBalance, &minerAddrs.IDAddress) + + // + // publish one bad deal + // + t.Run("mismatched provider", func(t *testing.T) { + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run0-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal, provider doesn't match worker + batcher.stage(t, client1, notMiner, "run0-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run0-deal2", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0, 2}, goodInputs) + assert.Equal(t, 2, len(dealRet.IDs)) + }) + + t.Run("invalid deal: bad piece size", func(t *testing.T) { + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // bad deal piece size too small + batcher.stage(t, client1, minerAddrs.IDAddress, "run1-deal0", 0, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run1-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{1}, goodInputs) + assert.Equal(t, 1, len(dealRet.IDs)) + }) + + t.Run("invalid deal: start time in the past", func(t *testing.T) { + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + badStart := v.GetEpoch() - 1 + batcher := newDealBatcher(v) + // bad deal, start time in the past + batcher.stage(t, client1, minerAddrs.IDAddress, "run2-deal0", 1<<30, false, badStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run2-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{1}, goodInputs) + assert.Equal(t, 1, len(dealRet.IDs)) + }) + + t.Run("client address cannot be resolved", func(t *testing.T) { + badClient := tutil.NewIDAddr(t, 5_000_000) + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run3-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- client addr is not a resolvable account + batcher.stage(t, badClient, minerAddrs.IDAddress, "run3-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0}, goodInputs) + assert.Equal(t, 1, len(dealRet.IDs)) + }) + + t.Run("no client lockup", func(t *testing.T) { + /* added no market collateral for cheapClient */ + + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // bad deal client can't pay + batcher.stage(t, cheapClient, minerAddrs.IDAddress, "run4-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run4-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{1}, goodInputs) + assert.Equal(t, 1, len(dealRet.IDs)) + }) + + t.Run("insufficient client lockup when considering the whole batch", func(t *testing.T) { + oneLifeTimeCost := big.Sum(defaultClientCollateral, big.Mul(big.NewInt(int64(dealLifeTime)), defaultPricePerEpoch)) + // add only one lifetime cost to cheapClient's collateral but attempt to make 3 deals + vm.ApplyOk(t, v, cheapClient, builtin.StorageMarketActorAddr, oneLifeTimeCost, builtin.MethodsMarket.AddBalance, &cheapClient) + + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // good deal + batcher.stage(t, cheapClient, minerAddrs.IDAddress, "run5-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- insufficient funds + batcher.stage(t, cheapClient, minerAddrs.IDAddress, "run5-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- insufficient funds + batcher.stage(t, cheapClient, minerAddrs.IDAddress, "run5-deal2", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0}, goodInputs) + assert.Equal(t, 1, len(dealRet.IDs)) + }) + + t.Run("insufficient provider lockup when considering the whole batch", func(t *testing.T) { + // Note: its important to use a different seed here because same seed will generate same key + // and overwrite init actor mapping + cheapWorker := vm.CreateAccounts(ctx, t, v, 1, big.Mul(big.NewInt(10_000), vm.FIL), 444)[0] + // create miner + params := power.CreateMinerParams{ + Owner: cheapWorker, + Worker: cheapWorker, + WindowPoStProofType: abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, + Peer: abi.PeerID("not really a peer id"), + } + ret := vm.ApplyOk(t, v, worker, builtin.StoragePowerActorAddr, big.Mul(big.NewInt(100), vm.FIL), builtin.MethodsPower.CreateMiner, ¶ms) + + cheapMinerAddrs, ok := ret.(*power.CreateMinerReturn) + require.True(t, ok) + + minerCollateral := defaultProviderCollateral + vm.ApplyOk(t, v, worker, builtin.StorageMarketActorAddr, minerCollateral, builtin.MethodsMarket.AddBalance, &minerAddrs.IDAddress) + + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + + vm.ApplyOk(t, v, worker, builtin.StorageMarketActorAddr, minerCollateral, builtin.MethodsMarket.AddBalance, &cheapMinerAddrs.IDAddress) + // good deal + batcher.stage(t, client1, cheapMinerAddrs.IDAddress, "run6-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- insufficient provider funds + batcher.stage(t, client2, cheapMinerAddrs.IDAddress, "run6-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, cheapWorker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0}, goodInputs) + assert.Equal(t, 1, len(dealRet.IDs)) + }) + + t.Run("duplicate deal in batch", func(t *testing.T) { + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run7-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run7-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal duplicate + batcher.stage(t, client1, minerAddrs.IDAddress, "run7-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal duplicate + batcher.stage(t, client1, minerAddrs.IDAddress, "run7-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // good deal + batcher.stage(t, client2, minerAddrs.IDAddress, "run7-deal2", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal duplicate + batcher.stage(t, client1, minerAddrs.IDAddress, "run7-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0, 1, 4}, goodInputs) + assert.Equal(t, 3, len(dealRet.IDs)) + }) + + t.Run("duplicate deal in state", func(t *testing.T) { + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // good deal + batcher.stage(t, client2, minerAddrs.IDAddress, "run8-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0}, goodInputs) + + batcher = newDealBatcher(v) + // good deal + batcher.stage(t, client2, minerAddrs.IDAddress, "run8-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal duplicate in batch + batcher.stage(t, client2, minerAddrs.IDAddress, "run8-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal duplicate in state + batcher.stage(t, client2, minerAddrs.IDAddress, "run8-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet = batcher.publishOK(t, worker) + goodInputs, err = dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0}, goodInputs) + assert.Equal(t, 1, len(dealRet.IDs)) + }) + + t.Run("verified deal fails to acquire datacap", func(t *testing.T) { + vAddrs := vm.CreateAccounts(ctx, t, v, 2, big.Mul(big.NewInt(10_000), vm.FIL), 555) + verifier, verifiedClient := vAddrs[0], vAddrs[1] + + addVerifierParams := verifreg.AddVerifierParams{ + Address: verifier, + Allowance: abi.NewStoragePower(32 << 40), + } + vm.ApplyOk(t, v, vm.VerifregRoot, builtin.VerifiedRegistryActorAddr, big.Zero(), builtin.MethodsVerifiedRegistry.AddVerifier, &addVerifierParams) + addClientParams := verifreg.AddVerifiedClientParams{ + Address: verifiedClient, + Allowance: abi.NewStoragePower(1 << 32), + } + vm.ApplyOk(t, v, verifier, builtin.VerifiedRegistryActorAddr, big.Zero(), builtin.MethodsVerifiedRegistry.AddVerifiedClient, &addClientParams) + vm.ApplyOk(t, v, verifiedClient, builtin.StorageMarketActorAddr, big.Mul(big.NewInt(100), vm.FIL), builtin.MethodsMarket.AddBalance, &verifiedClient) + + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // good deal + batcher.stage(t, verifiedClient, minerAddrs.IDAddress, "run9-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + + // good verified deal, uses up all datacap + batcher.stage(t, verifiedClient, minerAddrs.IDAddress, "run9-deal1", 1<<32, true, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad verified deal, no data cap left + batcher.stage(t, verifiedClient, minerAddrs.IDAddress, "run9-deal2", 1<<32, true, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0, 1}, goodInputs) + assert.Equal(t, 2, len(dealRet.IDs)) + }) + + t.Run("random assortment of different failures", func(t *testing.T) { + xtraAddrs := vm.CreateAccounts(ctx, t, v, 4, big.Mul(big.NewInt(10_000), vm.FIL), 555) + verifier, verifiedClient, cheapClient, brokeClient := xtraAddrs[0], xtraAddrs[1], xtraAddrs[2], xtraAddrs[3] + + addVerifierParams := verifreg.AddVerifierParams{ + Address: verifier, + Allowance: abi.NewStoragePower(32 << 40), + } + vm.ApplyOk(t, v, vm.VerifregRoot, builtin.VerifiedRegistryActorAddr, big.Zero(), builtin.MethodsVerifiedRegistry.AddVerifier, &addVerifierParams) + addClientParams := verifreg.AddVerifiedClientParams{ + Address: verifiedClient, + Allowance: abi.NewStoragePower(1 << 32), + } + vm.ApplyOk(t, v, verifier, builtin.VerifiedRegistryActorAddr, big.Zero(), builtin.MethodsVerifiedRegistry.AddVerifiedClient, &addClientParams) + vm.ApplyOk(t, v, verifiedClient, builtin.StorageMarketActorAddr, big.Mul(big.NewInt(100), vm.FIL), builtin.MethodsMarket.AddBalance, &verifiedClient) + oneLifeTimeCost := big.Sum(defaultClientCollateral, big.Mul(big.NewInt(int64(dealLifeTime)), defaultPricePerEpoch)) + + vm.ApplyOk(t, v, cheapClient, builtin.StorageMarketActorAddr, oneLifeTimeCost, builtin.MethodsMarket.AddBalance, &cheapClient) + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + + // good deal + batcher.stage(t, verifiedClient, minerAddrs.IDAddress, "run10-deal1", 1<<32, true, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- duplicate + batcher.stage(t, verifiedClient, minerAddrs.IDAddress, "run10-deal1", 1<<32, true, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // good deal + batcher.stage(t, cheapClient, minerAddrs.IDAddress, "run10-deal2", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- no client funds + batcher.stage(t, brokeClient, minerAddrs.IDAddress, "run10-deal3", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- provider address does not match + batcher.stage(t, client1, client2, "run10-deal4", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- insufficient data cap + batcher.stage(t, verifiedClient, minerAddrs.IDAddress, "run10-deal5", 1<<32, true, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- cheap client out of funds + batcher.stage(t, cheapClient, minerAddrs.IDAddress, "run10-deal6", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- provider collateral too low + batcher.stage(t, cheapClient, minerAddrs.IDAddress, "run10-deal7", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, big.Zero(), defaultClientCollateral) + // good deal + batcher.stage(t, client1, minerAddrs.IDAddress, "run10-deal8", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0, 2, 8}, goodInputs) + assert.Equal(t, 3, len(dealRet.IDs)) + }) + + t.Run("all deals are bad", func(t *testing.T) { + + batcher := newDealBatcher(v) + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + badClient := tutil.NewIDAddr(t, 1_000) + + // bad deal -- provider collateral too low + batcher.stage(t, client1, minerAddrs.IDAddress, "run11-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, big.Zero(), defaultClientCollateral) + // bad deal -- provider address does not match + batcher.stage(t, client1, client2, "run11-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- verified deal from non-verified client + batcher.stage(t, client1, client2, "run11-deal2", 1<<30, true, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal -- client addr is not a resolvable account + batcher.stage(t, badClient, minerAddrs.IDAddress, "run11-deal3", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + // bad deal piece size too small + batcher.stage(t, client1, minerAddrs.IDAddress, "run11-deal4", 0, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + + batcher.publishFail(t, worker) + }) + + t.Run("all deals are good", func(t *testing.T) { + dealStart := v.GetEpoch() + miner.MaxProveCommitDuration[sealProof] + batcher := newDealBatcher(v) + // good deals + batcher.stage(t, client1, minerAddrs.IDAddress, "run12-deal0", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + batcher.stage(t, client1, minerAddrs.IDAddress, "run12-deal1", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + batcher.stage(t, client1, minerAddrs.IDAddress, "run12-deal2", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + batcher.stage(t, client1, minerAddrs.IDAddress, "run12-deal3", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + batcher.stage(t, client1, minerAddrs.IDAddress, "run12-deal4", 1<<30, false, dealStart, dealLifeTime, + defaultPricePerEpoch, defaultProviderCollateral, defaultClientCollateral) + + dealRet := batcher.publishOK(t, worker) + goodInputs, err := dealRet.ValidDeals.All(math.MaxUint64) + require.NoError(t, err) + assert.Equal(t, []uint64{0, 1, 2, 3, 4}, goodInputs) + assert.Equal(t, 5, len(dealRet.IDs)) + }) + +} diff --git a/gen/gen.go b/gen/gen.go index 505cfbc68..629234b52 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -148,8 +148,8 @@ func main() { market.State{}, // method params and returns //market.WithdrawBalanceParams{}, // Aliased from v0 - //market.PublishStorageDealsParams{}, // Aliased from v0 - //market.PublishStorageDealsReturn{}, // Aliased from v0 + // market.PublishStorageDealsParams{}, // Aliased from v0 + market.PublishStorageDealsReturn{}, //market.ActivateDealsParams{}, // Aliased from v0 market.VerifyDealsForActivationParams{}, market.VerifyDealsForActivationReturn{}, @@ -159,7 +159,7 @@ func main() { //market.OnMinerSectorsTerminateParams{}, // Aliased from v0 // other types //market.DealProposal{}, // Aliased from v0 - //market.ClientDealProposal{}, // Aliased from v0 + //market.ClientDealProposal{}, market.SectorDeals{}, market.SectorWeights{}, market.DealState{}, diff --git a/support/agent/miner_agent.go b/support/agent/miner_agent.go index 961569d63..3be761b65 100644 --- a/support/agent/miner_agent.go +++ b/support/agent/miner_agent.go @@ -18,6 +18,7 @@ import ( "github.com/filecoin-project/specs-actors/v6/actors/builtin" "github.com/filecoin-project/specs-actors/v6/actors/builtin/market" + "github.com/filecoin-project/specs-actors/v6/actors/builtin/miner" "github.com/filecoin-project/specs-actors/v6/actors/runtime/proof" "github.com/filecoin-project/specs-actors/v6/support/vm" diff --git a/test-vectors/determinism-check b/test-vectors/determinism-check index 0c524fc36..79fab1bc6 100644 --- a/test-vectors/determinism-check +++ b/test-vectors/determinism-check @@ -1 +1 @@ -- de328620513afeb9b1baaf0e1c02d7b268955a62ec152f603d158ff908608d7e +- 29c61c851c63fe07934a89303f2a82a1d9d7ad32bff159099e5e6e66b415dc36