From bf6b76a4fb02ef13e8c2630b2071e990393c6ade Mon Sep 17 00:00:00 2001 From: frrist Date: Tue, 7 Jul 2020 16:39:32 -0700 Subject: [PATCH] feat: add market deal state & proposal predicates - detects changes in the market deal proposal and market deal state amts. --- chain/events/state/predicates.go | 190 ++++++++++++++-- chain/events/state/predicates_test.go | 302 ++++++++++++++++++-------- 2 files changed, 392 insertions(+), 100 deletions(-) diff --git a/chain/events/state/predicates.go b/chain/events/state/predicates.go index 793a00806..16abc4928 100644 --- a/chain/events/state/predicates.go +++ b/chain/events/state/predicates.go @@ -4,13 +4,11 @@ import ( "bytes" "context" + "github.com/filecoin-project/go-address" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" typegen "github.com/whyrusleeping/cbor-gen" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-amt-ipld/v2" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/builtin/market" @@ -87,20 +85,25 @@ func (sp *StatePredicates) OnStorageMarketActorChanged(diffStorageMarketState Di }) } -type DiffDealStatesFunc func(ctx context.Context, oldDealStateRoot *amt.Root, newDealStateRoot *amt.Root) (changed bool, user UserData, err error) +type DiffAdtArraysFunc func(ctx context.Context, oldDealStateRoot, newDealStateRoot *adt.Array) (changed bool, user UserData, err error) -// OnDealStateChanged calls diffDealStates when the market state changes -func (sp *StatePredicates) OnDealStateChanged(diffDealStates DiffDealStatesFunc) DiffStorageMarketStateFunc { +// OnDealStateChanged calls diffDealStates when the market deal state changes +func (sp *StatePredicates) OnDealStateChanged(diffDealStates DiffAdtArraysFunc) DiffStorageMarketStateFunc { return func(ctx context.Context, oldState *market.State, newState *market.State) (changed bool, user UserData, err error) { if oldState.States.Equals(newState.States) { return false, nil, nil } - oldRoot, err := amt.LoadAMT(ctx, sp.cst, oldState.States) + ctxStore := &contextStore{ + ctx: ctx, + cst: sp.cst, + } + + oldRoot, err := adt.AsArray(ctxStore, oldState.States) if err != nil { return false, nil, err } - newRoot, err := amt.LoadAMT(ctx, sp.cst, newState.States) + newRoot, err := adt.AsArray(ctxStore, newState.States) if err != nil { return false, nil, err } @@ -109,31 +112,188 @@ func (sp *StatePredicates) OnDealStateChanged(diffDealStates DiffDealStatesFunc) } } +// OnDealProposalChanged calls diffDealProps when the market proposal state changes +func (sp *StatePredicates) OnDealProposalChanged(diffDealProps DiffAdtArraysFunc) DiffStorageMarketStateFunc { + return func(ctx context.Context, oldState *market.State, newState *market.State) (changed bool, user UserData, err error) { + if oldState.Proposals.Equals(newState.Proposals) { + return false, nil, nil + } + + ctxStore := &contextStore{ + ctx: ctx, + cst: sp.cst, + } + + oldRoot, err := adt.AsArray(ctxStore, oldState.Proposals) + if err != nil { + return false, nil, err + } + newRoot, err := adt.AsArray(ctxStore, newState.Proposals) + if err != nil { + return false, nil, err + } + + return diffDealProps(ctx, oldRoot, newRoot) + } +} + +var _ AdtArrayDiff = &MarketDealProposalChanges{} + +type MarketDealProposalChanges struct { + Added []ProposalIDState + Removed []ProposalIDState +} + +type ProposalIDState struct { + ID abi.DealID + Proposal market.DealProposal +} + +func (m *MarketDealProposalChanges) Add(key uint64, val *typegen.Deferred) error { + dp := new(market.DealProposal) + err := dp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + m.Added = append(m.Added, ProposalIDState{abi.DealID(key), *dp}) + return nil +} + +func (m *MarketDealProposalChanges) Modify(key uint64, from, to *typegen.Deferred) error { + // short circuit, DealProposals are static + return nil +} + +func (m *MarketDealProposalChanges) Remove(key uint64, val *typegen.Deferred) error { + dp := new(market.DealProposal) + err := dp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + m.Removed = append(m.Removed, ProposalIDState{abi.DealID(key), *dp}) + return nil +} + +// OnDealProposalAmtChanged detects changes in the deal proposal AMT for all deal proposals and returns a MarketProposalsChanges structure containing: +// - Added Proposals +// - Modified Proposals +// - Removed Proposals +func (sp *StatePredicates) OnDealProposalAmtChanged() DiffAdtArraysFunc { + return func(ctx context.Context, oldDealProps, newDealProps *adt.Array) (changed bool, user UserData, err error) { + proposalChanges := new(MarketDealProposalChanges) + if err := DiffAdtArray(oldDealProps, newDealProps, proposalChanges); err != nil { + return false, nil, err + } + + if len(proposalChanges.Added)+len(proposalChanges.Removed) == 0 { + return false, nil, nil + } + + return true, proposalChanges, nil + } +} + +var _ AdtArrayDiff = &MarketDealStateChanges{} + +type MarketDealStateChanges struct { + Added []DealIDState + Modified []DealStateChange + Removed []DealIDState +} + +type DealIDState struct { + ID abi.DealID + Deal market.DealState +} + +func (m *MarketDealStateChanges) Add(key uint64, val *typegen.Deferred) error { + ds := new(market.DealState) + err := ds.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + m.Added = append(m.Added, DealIDState{abi.DealID(key), *ds}) + return nil +} + +func (m *MarketDealStateChanges) Modify(key uint64, from, to *typegen.Deferred) error { + dsFrom := new(market.DealState) + if err := dsFrom.UnmarshalCBOR(bytes.NewReader(from.Raw)); err != nil { + return err + } + + dsTo := new(market.DealState) + if err := dsTo.UnmarshalCBOR(bytes.NewReader(to.Raw)); err != nil { + return err + } + + if *dsFrom != *dsTo { + m.Modified = append(m.Modified, DealStateChange{abi.DealID(key), dsFrom, dsTo}) + } + return nil +} + +func (m *MarketDealStateChanges) Remove(key uint64, val *typegen.Deferred) error { + ds := new(market.DealState) + err := ds.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + m.Removed = append(m.Removed, DealIDState{abi.DealID(key), *ds}) + return nil +} + +// OnDealStateAmtChanged detects changes in the deal state AMT for all deal states and returns a MarketDealStateChanges structure containing: +// - Added Deals +// - Modified Deals +// - Removed Deals +func (sp *StatePredicates) OnDealStateAmtChanged() DiffAdtArraysFunc { + return func(ctx context.Context, oldDealStates, newDealStates *adt.Array) (changed bool, user UserData, err error) { + dealStateChanges := new(MarketDealStateChanges) + if err := DiffAdtArray(oldDealStates, newDealStates, dealStateChanges); err != nil { + return false, nil, err + } + + if len(dealStateChanges.Added)+len(dealStateChanges.Modified)+len(dealStateChanges.Removed) == 0 { + return false, nil, nil + } + + return true, dealStateChanges, nil + } +} + // ChangedDeals is a set of changes to deal state type ChangedDeals map[abi.DealID]DealStateChange // DealStateChange is a change in deal state from -> to type DealStateChange struct { - From market.DealState - To market.DealState + ID abi.DealID + From *market.DealState + To *market.DealState } // DealStateChangedForIDs detects changes in the deal state AMT for the given deal IDs -func (sp *StatePredicates) DealStateChangedForIDs(dealIds []abi.DealID) DiffDealStatesFunc { - return func(ctx context.Context, oldDealStateRoot *amt.Root, newDealStateRoot *amt.Root) (changed bool, user UserData, err error) { +func (sp *StatePredicates) DealStateChangedForIDs(dealIds []abi.DealID) DiffAdtArraysFunc { + return func(ctx context.Context, oldDealStateArray, newDealStateArray *adt.Array) (changed bool, user UserData, err error) { changedDeals := make(ChangedDeals) for _, dealID := range dealIds { + var oldDealPtr, newDealPtr *market.DealState var oldDeal, newDeal market.DealState - err := oldDealStateRoot.Get(ctx, uint64(dealID), &oldDeal) + + _, err := oldDealStateArray.Get(uint64(dealID), &oldDeal) if err != nil { return false, nil, err } - err = newDealStateRoot.Get(ctx, uint64(dealID), &newDeal) + oldDealPtr = &oldDeal + + _, err = newDealStateArray.Get(uint64(dealID), &newDeal) if err != nil { return false, nil, err } + newDealPtr = &newDeal + if oldDeal != newDeal { - changedDeals[dealID] = DealStateChange{oldDeal, newDeal} + changedDeals[dealID] = DealStateChange{dealID, oldDealPtr, newDealPtr} } } if len(changedDeals) > 0 { diff --git a/chain/events/state/predicates_test.go b/chain/events/state/predicates_test.go index 7ab3dd074..072b2946e 100644 --- a/chain/events/state/predicates_test.go +++ b/chain/events/state/predicates_test.go @@ -21,6 +21,7 @@ import ( "github.com/filecoin-project/specs-actors/actors/builtin/market" "github.com/filecoin-project/specs-actors/actors/builtin/miner" "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/specs-actors/actors/util/adt" tutils "github.com/filecoin-project/specs-actors/support/testing" "github.com/filecoin-project/lotus/chain/types" @@ -65,99 +66,216 @@ func (m mockAPI) setActor(tsk types.TipSetKey, act *types.Actor) { m.ts[tsk] = act } -func TestPredicates(t *testing.T) { +func TestMarketPredicates(t *testing.T) { ctx := context.Background() bs := bstore.NewBlockstore(ds_sync.MutexWrap(ds.NewMapDatastore())) store := cbornode.NewCborStore(bs) + oldDeal1 := &market.DealState{ + SectorStartEpoch: 1, + LastUpdatedEpoch: 2, + SlashEpoch: 0, + } + oldDeal2 := &market.DealState{ + SectorStartEpoch: 4, + LastUpdatedEpoch: 5, + SlashEpoch: 0, + } oldDeals := map[abi.DealID]*market.DealState{ - abi.DealID(1): { - SectorStartEpoch: 1, - LastUpdatedEpoch: 2, - SlashEpoch: 0, - }, - abi.DealID(2): { - SectorStartEpoch: 4, - LastUpdatedEpoch: 5, - SlashEpoch: 0, - }, + abi.DealID(1): oldDeal1, + abi.DealID(2): oldDeal2, } - oldStateC := createMarketState(ctx, t, store, oldDeals) + oldProp1 := &market.DealProposal{ + PieceCID: dummyCid, + PieceSize: 0, + VerifiedDeal: false, + Client: tutils.NewIDAddr(t, 1), + Provider: tutils.NewIDAddr(t, 1), + StartEpoch: 1, + EndEpoch: 2, + StoragePricePerEpoch: big.Zero(), + ProviderCollateral: big.Zero(), + ClientCollateral: big.Zero(), + } + oldProp2 := &market.DealProposal{ + PieceCID: dummyCid, + PieceSize: 0, + VerifiedDeal: false, + Client: tutils.NewIDAddr(t, 1), + Provider: tutils.NewIDAddr(t, 1), + StartEpoch: 2, + EndEpoch: 3, + StoragePricePerEpoch: big.Zero(), + ProviderCollateral: big.Zero(), + ClientCollateral: big.Zero(), + } + oldProps := map[abi.DealID]*market.DealProposal{ + abi.DealID(1): oldProp1, + abi.DealID(2): oldProp2, + } + + oldStateC := createMarketState(ctx, t, store, oldDeals, oldProps) + + newDeal1 := &market.DealState{ + SectorStartEpoch: 1, + LastUpdatedEpoch: 3, + SlashEpoch: 0, + } + newDeal2 := &market.DealState{ + SectorStartEpoch: 4, + LastUpdatedEpoch: 6, + SlashEpoch: 6, + } + // added + newDeal3 := &market.DealState{ + SectorStartEpoch: 1, + LastUpdatedEpoch: 2, + SlashEpoch: 3, + } newDeals := map[abi.DealID]*market.DealState{ - abi.DealID(1): { - SectorStartEpoch: 1, - LastUpdatedEpoch: 3, - SlashEpoch: 0, - }, - abi.DealID(2): { - SectorStartEpoch: 4, - LastUpdatedEpoch: 6, - SlashEpoch: 6, - }, + abi.DealID(1): newDeal1, + abi.DealID(2): newDeal2, + abi.DealID(3): newDeal3, } - newStateC := createMarketState(ctx, t, store, newDeals) - miner, err := address.NewFromString("t00") + // added + newProp3 := &market.DealProposal{ + PieceCID: dummyCid, + PieceSize: 0, + VerifiedDeal: false, + Client: tutils.NewIDAddr(t, 1), + Provider: tutils.NewIDAddr(t, 1), + StartEpoch: 4, + EndEpoch: 4, + StoragePricePerEpoch: big.Zero(), + ProviderCollateral: big.Zero(), + ClientCollateral: big.Zero(), + } + newProps := map[abi.DealID]*market.DealProposal{ + abi.DealID(1): oldProp1, // 1 was persisted + // prop 2 was removed + abi.DealID(3): newProp3, // new + // NB: DealProposals cannot be modified, so don't test that case. + } + newStateC := createMarketState(ctx, t, store, newDeals, newProps) + + minerAddr, err := address.NewFromString("t00") require.NoError(t, err) - oldState, err := mockTipset(miner, 1) + oldState, err := mockTipset(minerAddr, 1) require.NoError(t, err) - newState, err := mockTipset(miner, 2) + newState, err := mockTipset(minerAddr, 2) require.NoError(t, err) api := newMockAPI(bs) api.setActor(oldState.Key(), &types.Actor{Head: oldStateC}) api.setActor(newState.Key(), &types.Actor{Head: newStateC}) - preds := NewStatePredicates(api) + t.Run("deal ID predicate", func(t *testing.T) { + preds := NewStatePredicates(api) - dealIds := []abi.DealID{abi.DealID(1), abi.DealID(2)} - diffFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.DealStateChangedForIDs(dealIds))) + dealIds := []abi.DealID{abi.DealID(1), abi.DealID(2)} + diffIDFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.DealStateChangedForIDs(dealIds))) - // Diff a state against itself: expect no change - changed, _, err := diffFn(ctx, oldState.Key(), oldState.Key()) - require.NoError(t, err) - require.False(t, changed) + // Diff a state against itself: expect no change + changed, _, err := diffIDFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) - // Diff old state against new state - changed, val, err := diffFn(ctx, oldState.Key(), newState.Key()) - require.NoError(t, err) - require.True(t, changed) + // Diff old state against new state + changed, valIDs, err := diffIDFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, changed) - changedDeals, ok := val.(ChangedDeals) - require.True(t, ok) - require.Len(t, changedDeals, 2) - require.Contains(t, changedDeals, abi.DealID(1)) - require.Contains(t, changedDeals, abi.DealID(2)) - deal1 := changedDeals[abi.DealID(1)] - if deal1.From.LastUpdatedEpoch != 2 || deal1.To.LastUpdatedEpoch != 3 { - t.Fatal("Unexpected change to LastUpdatedEpoch") - } - deal2 := changedDeals[abi.DealID(2)] - if deal2.From.SlashEpoch != 0 || deal2.To.SlashEpoch != 6 { - t.Fatal("Unexpected change to SlashEpoch") - } + changedDealIDs, ok := valIDs.(ChangedDeals) + require.True(t, ok) + require.Len(t, changedDealIDs, 2) + require.Contains(t, changedDealIDs, abi.DealID(1)) + require.Contains(t, changedDealIDs, abi.DealID(2)) + deal1 := changedDealIDs[abi.DealID(1)] + if deal1.From.LastUpdatedEpoch != 2 || deal1.To.LastUpdatedEpoch != 3 { + t.Fatal("Unexpected change to LastUpdatedEpoch") + } + deal2 := changedDealIDs[abi.DealID(2)] + if deal2.From.SlashEpoch != 0 || deal2.To.SlashEpoch != 6 { + t.Fatal("Unexpected change to SlashEpoch") + } - // Test that OnActorStateChanged does not call the callback if the state has not changed - mockAddr, err := address.NewFromString("t01") - require.NoError(t, err) - actorDiffFn := preds.OnActorStateChanged(mockAddr, func(context.Context, cid.Cid, cid.Cid) (bool, UserData, error) { - t.Fatal("No state change so this should not be called") - return false, nil, nil + // Test that OnActorStateChanged does not call the callback if the state has not changed + mockAddr, err := address.NewFromString("t01") + require.NoError(t, err) + actorDiffFn := preds.OnActorStateChanged(mockAddr, func(context.Context, cid.Cid, cid.Cid) (bool, UserData, error) { + t.Fatal("No state change so this should not be called") + return false, nil, nil + }) + changed, _, err = actorDiffFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + // Test that OnDealStateChanged does not call the callback if the state has not changed + diffDealStateFn := preds.OnDealStateChanged(func(context.Context, *adt.Array, *adt.Array) (bool, UserData, error) { + t.Fatal("No state change so this should not be called") + return false, nil, nil + }) + marketState := createEmptyMarketState(t, store) + changed, _, err = diffDealStateFn(ctx, marketState, marketState) + require.NoError(t, err) + require.False(t, changed) }) - changed, _, err = actorDiffFn(ctx, oldState.Key(), oldState.Key()) - require.NoError(t, err) - require.False(t, changed) - // Test that OnDealStateChanged does not call the callback if the state has not changed - diffDealStateFn := preds.OnDealStateChanged(func(context.Context, *amt.Root, *amt.Root) (bool, UserData, error) { - t.Fatal("No state change so this should not be called") - return false, nil, nil + t.Run("deal state array predicate", func(t *testing.T) { + preds := NewStatePredicates(api) + diffArrFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.OnDealStateAmtChanged())) + + changed, _, err := diffArrFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + changed, valArr, err := diffArrFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, changed) + + changedDeals, ok := valArr.(*MarketDealStateChanges) + require.True(t, ok) + require.Len(t, changedDeals.Added, 1) + require.Equal(t, abi.DealID(3), changedDeals.Added[0].ID) + require.Equal(t, *newDeal3, changedDeals.Added[0].Deal) + + require.Len(t, changedDeals.Removed, 0) + + require.Len(t, changedDeals.Modified, 2) + require.Equal(t, abi.DealID(1), changedDeals.Modified[0].ID) + require.Equal(t, newDeal1, changedDeals.Modified[0].To) + require.Equal(t, oldDeal1, changedDeals.Modified[0].From) + + require.Equal(t, abi.DealID(2), changedDeals.Modified[1].ID) + require.Equal(t, oldDeal2, changedDeals.Modified[1].From) + require.Equal(t, newDeal2, changedDeals.Modified[1].To) + }) + + t.Run("deal proposal array predicate", func(t *testing.T) { + preds := NewStatePredicates(api) + diffArrFn := preds.OnStorageMarketActorChanged(preds.OnDealProposalChanged(preds.OnDealProposalAmtChanged())) + changed, _, err := diffArrFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + changed, valArr, err := diffArrFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, changed) + + changedProps, ok := valArr.(*MarketDealProposalChanges) + require.True(t, ok) + require.Len(t, changedProps.Added, 1) + require.Equal(t, abi.DealID(3), changedProps.Added[0].ID) + require.Equal(t, *newProp3, changedProps.Added[0].Proposal) + + // proposals cannot be modified -- no modified testing + + require.Len(t, changedProps.Removed, 1) + require.Equal(t, abi.DealID(2), changedProps.Removed[0].ID) + require.Equal(t, *oldProp2, changedProps.Removed[0].Proposal) }) - marketState := createEmptyMarketState(t, store) - changed, _, err = diffDealStateFn(ctx, marketState, marketState) - require.NoError(t, err) - require.False(t, changed) } func TestMinerSectorChange(t *testing.T) { @@ -208,14 +326,15 @@ func TestMinerSectorChange(t *testing.T) { require.True(t, ok) require.Equal(t, len(sectorChanges.Added), 1) - require.Equal(t, sectorChanges.Added[0], si3) + require.Equal(t, 1, len(sectorChanges.Added)) + require.Equal(t, si3, sectorChanges.Added[0]) - require.Equal(t, len(sectorChanges.Removed), 1) - require.Equal(t, sectorChanges.Removed[0], si0) + require.Equal(t, 1, len(sectorChanges.Removed)) + require.Equal(t, si0, sectorChanges.Removed[0]) - require.Equal(t, len(sectorChanges.Extended), 1) - require.Equal(t, sectorChanges.Extended[0].From, si1) - require.Equal(t, sectorChanges.Extended[0].To, si1Ext) + require.Equal(t, 1, len(sectorChanges.Extended)) + require.Equal(t, si1, sectorChanges.Extended[0].From) + require.Equal(t, si1Ext, sectorChanges.Extended[0].To) change, val, err = minerDiffFn(ctx, oldState.Key(), oldState.Key()) require.NoError(t, err) @@ -230,20 +349,20 @@ func TestMinerSectorChange(t *testing.T) { sectorChanges, ok = val.(*MinerSectorChanges) require.True(t, ok) - require.Equal(t, len(sectorChanges.Added), 1) - require.Equal(t, sectorChanges.Added[0], si0) + require.Equal(t, 1, len(sectorChanges.Added)) + require.Equal(t, si0, sectorChanges.Added[0]) - require.Equal(t, len(sectorChanges.Removed), 1) - require.Equal(t, sectorChanges.Removed[0], si3) + require.Equal(t, 1, len(sectorChanges.Removed)) + require.Equal(t, si3, sectorChanges.Removed[0]) - require.Equal(t, len(sectorChanges.Extended), 1) - require.Equal(t, sectorChanges.Extended[0].To, si1) - require.Equal(t, sectorChanges.Extended[0].From, si1Ext) + require.Equal(t, 1, len(sectorChanges.Extended)) + require.Equal(t, si1, sectorChanges.Extended[0].To) + require.Equal(t, si1Ext, sectorChanges.Extended[0].From) } -func mockTipset(miner address.Address, timestamp uint64) (*types.TipSet, error) { +func mockTipset(minerAddr address.Address, timestamp uint64) (*types.TipSet, error) { return types.NewTipSet([]*types.BlockHeader{{ - Miner: miner, + Miner: minerAddr, Height: 5, ParentStateRoot: dummyCid, Messages: dummyCid, @@ -254,11 +373,13 @@ func mockTipset(miner address.Address, timestamp uint64) (*types.TipSet, error) }}) } -func createMarketState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState) cid.Cid { - rootCid := createDealAMT(ctx, t, store, deals) +func createMarketState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState, props map[abi.DealID]*market.DealProposal) cid.Cid { + dealRootCid := createDealAMT(ctx, t, store, deals) + propRootCid := createProposalAMT(ctx, t, store, props) state := createEmptyMarketState(t, store) - state.States = rootCid + state.States = dealRootCid + state.Proposals = propRootCid stateC, err := store.Put(ctx, state) require.NoError(t, err) @@ -284,6 +405,17 @@ func createDealAMT(ctx context.Context, t *testing.T, store *cbornode.BasicIpldS return rootCid } +func createProposalAMT(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, props map[abi.DealID]*market.DealProposal) cid.Cid { + root := amt.NewAMT(store) + for dealID, prop := range props { + err := root.Set(ctx, uint64(dealID), prop) + require.NoError(t, err) + } + rootCid, err := root.Flush(ctx) + require.NoError(t, err) + return rootCid +} + func createMinerState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, owner, worker address.Address, sectors []miner.SectorOnChainInfo) cid.Cid { rootCid := createSectorsAMT(ctx, t, store, sectors)