package storageadapter import ( "bytes" "errors" "math/rand" "testing" "time" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/market" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" test "github.com/filecoin-project/lotus/chain/events/state/mock" "github.com/filecoin-project/lotus/chain/types" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "golang.org/x/net/context" "golang.org/x/xerrors" ) var errNotFound = errors.New("Could not find") func TestGetCurrentDealInfo(t *testing.T) { ctx := context.Background() dummyCid, _ := cid.Parse("bafkqaaa") startDealID := abi.DealID(rand.Uint64()) newDealID := abi.DealID(rand.Uint64()) twoValuesReturn := makePublishDealsReturnBytes(t, []abi.DealID{abi.DealID(rand.Uint64()), abi.DealID(rand.Uint64())}) sameValueReturn := makePublishDealsReturnBytes(t, []abi.DealID{startDealID}) newValueReturn := makePublishDealsReturnBytes(t, []abi.DealID{newDealID}) proposal := market.DealProposal{ PieceCID: dummyCid, PieceSize: abi.PaddedPieceSize(rand.Uint64()), Label: "success", } otherProposal := market.DealProposal{ PieceCID: dummyCid, PieceSize: abi.PaddedPieceSize(rand.Uint64()), Label: "other", } successDeal := &api.MarketDeal{ Proposal: proposal, State: market.DealState{ SectorStartEpoch: 1, LastUpdatedEpoch: 2, }, } otherDeal := &api.MarketDeal{ Proposal: otherProposal, State: market.DealState{ SectorStartEpoch: 1, LastUpdatedEpoch: 2, }, } testCases := map[string]struct { searchMessageLookup *api.MsgLookup searchMessageErr error marketDeals map[abi.DealID]*api.MarketDeal publishCid *cid.Cid expectedDealID abi.DealID expectedMarketDeal *api.MarketDeal expectedError error }{ "deal lookup succeeds": { marketDeals: map[abi.DealID]*api.MarketDeal{ startDealID: successDeal, }, expectedDealID: startDealID, expectedMarketDeal: successDeal, }, "publish CID = nil": { expectedDealID: startDealID, expectedError: errNotFound, }, "publish CID = nil, other deal on lookup": { marketDeals: map[abi.DealID]*api.MarketDeal{ startDealID: otherDeal, }, expectedDealID: startDealID, expectedError: xerrors.Errorf("Deal proposals did not match"), }, "search message fails": { publishCid: &dummyCid, searchMessageErr: errors.New("something went wrong"), expectedDealID: startDealID, expectedError: errors.New("something went wrong"), }, "return code not ok": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.ErrIllegalState, }, }, expectedDealID: startDealID, expectedError: xerrors.Errorf("looking for publish deal message %s: non-ok exit code: %s", dummyCid, exitcode.ErrIllegalState), }, "unable to unmarshal params": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.Ok, Return: []byte("applesauce"), }, }, expectedDealID: startDealID, expectedError: xerrors.Errorf("looking for publish deal message: unmarshaling message return: cbor input should be of type array"), }, "more than one returned id": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.Ok, Return: twoValuesReturn, }, }, expectedDealID: startDealID, expectedError: xerrors.Errorf("can't recover dealIDs from publish deal message with more than 1 deal"), }, "deal ids still match": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.Ok, Return: sameValueReturn, }, }, expectedDealID: startDealID, expectedError: errNotFound, }, "new deal id success": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.Ok, Return: newValueReturn, }, }, marketDeals: map[abi.DealID]*api.MarketDeal{ newDealID: successDeal, }, expectedDealID: newDealID, expectedMarketDeal: successDeal, }, "new deal id after other deal found": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.Ok, Return: newValueReturn, }, }, marketDeals: map[abi.DealID]*api.MarketDeal{ startDealID: otherDeal, newDealID: successDeal, }, expectedDealID: newDealID, expectedMarketDeal: successDeal, }, "new deal id failure": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.Ok, Return: newValueReturn, }, }, expectedDealID: newDealID, expectedError: errNotFound, }, "new deal id, failure due to other deal present": { publishCid: &dummyCid, searchMessageLookup: &api.MsgLookup{ Receipt: types.MessageReceipt{ ExitCode: exitcode.Ok, Return: newValueReturn, }, }, marketDeals: map[abi.DealID]*api.MarketDeal{ newDealID: otherDeal, }, expectedDealID: newDealID, expectedError: xerrors.Errorf("Deal proposals did not match"), }, } runTestCase := func(testCase string, data struct { searchMessageLookup *api.MsgLookup searchMessageErr error marketDeals map[abi.DealID]*api.MarketDeal publishCid *cid.Cid expectedDealID abi.DealID expectedMarketDeal *api.MarketDeal expectedError error }) { t.Run(testCase, func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() ts, err := test.MockTipset(address.TestAddress, rand.Uint64()) require.NoError(t, err) marketDeals := make(map[marketDealKey]*api.MarketDeal) for dealID, deal := range data.marketDeals { marketDeals[marketDealKey{dealID, ts.Key()}] = deal } api := &mockGetCurrentDealInfoAPI{ SearchMessageLookup: data.searchMessageLookup, SearchMessageErr: data.searchMessageErr, MarketDeals: marketDeals, } dealID, marketDeal, _, err := GetCurrentDealInfo(ctx, ts, api, startDealID, proposal, data.publishCid) require.Equal(t, data.expectedDealID, dealID) require.Equal(t, data.expectedMarketDeal, marketDeal) if data.expectedError == nil { require.NoError(t, err) } else { require.EqualError(t, err, data.expectedError.Error()) } }) } for testCase, data := range testCases { runTestCase(testCase, data) } } type marketDealKey struct { abi.DealID types.TipSetKey } type mockGetCurrentDealInfoAPI struct { SearchMessageLookup *api.MsgLookup SearchMessageErr error MarketDeals map[marketDealKey]*api.MarketDeal } func (mapi *mockGetCurrentDealInfoAPI) diffPreCommits(ctx context.Context, actor address.Address, pre, cur types.TipSetKey) (*miner.PreCommitChanges, error) { return &miner.PreCommitChanges{}, nil } func (mapi *mockGetCurrentDealInfoAPI) StateMarketStorageDeal(ctx context.Context, dealID abi.DealID, ts types.TipSetKey) (*api.MarketDeal, error) { deal, ok := mapi.MarketDeals[marketDealKey{dealID, ts}] if !ok { return nil, errNotFound } return deal, nil } func (mapi *mockGetCurrentDealInfoAPI) StateSearchMsg(context.Context, cid.Cid) (*api.MsgLookup, error) { return mapi.SearchMessageLookup, mapi.SearchMessageErr } func (mapi *mockGetCurrentDealInfoAPI) StateLookupID(ctx context.Context, addr address.Address, ts types.TipSetKey) (address.Address, error) { return addr, nil } func makePublishDealsReturnBytes(t *testing.T, dealIDs []abi.DealID) []byte { buf := new(bytes.Buffer) dealsReturn := market.PublishStorageDealsReturn{ IDs: dealIDs, } err := dealsReturn.MarshalCBOR(buf) require.NoError(t, err) return buf.Bytes() }