158 lines
4.7 KiB
Go
158 lines
4.7 KiB
Go
package storageadapter
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/filecoin-project/lotus/chain/events"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
cbornode "github.com/ipfs/go-ipld-cbor"
|
|
|
|
adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt"
|
|
"github.com/ipfs/go-cid"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
test "github.com/filecoin-project/lotus/chain/events/state/mock"
|
|
bstore "github.com/filecoin-project/lotus/lib/blockstore"
|
|
builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin"
|
|
|
|
market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/filecoin-project/lotus/chain/events/state"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
)
|
|
|
|
func TestDealStateMatcher(t *testing.T) {
|
|
ctx := context.Background()
|
|
bs := bstore.NewTemporarySync()
|
|
store := adt2.WrapStore(ctx, cbornode.NewCborStore(bs))
|
|
|
|
deal1 := &market2.DealState{
|
|
SectorStartEpoch: 1,
|
|
LastUpdatedEpoch: 2,
|
|
}
|
|
deal2 := &market2.DealState{
|
|
SectorStartEpoch: 4,
|
|
LastUpdatedEpoch: 5,
|
|
}
|
|
deal3 := &market2.DealState{
|
|
SectorStartEpoch: 7,
|
|
LastUpdatedEpoch: 8,
|
|
}
|
|
deals1 := map[abi.DealID]*market2.DealState{
|
|
abi.DealID(1): deal1,
|
|
}
|
|
deals2 := map[abi.DealID]*market2.DealState{
|
|
abi.DealID(1): deal2,
|
|
}
|
|
deals3 := map[abi.DealID]*market2.DealState{
|
|
abi.DealID(1): deal3,
|
|
}
|
|
|
|
deal1StateC := createMarketState(ctx, t, store, deals1)
|
|
deal2StateC := createMarketState(ctx, t, store, deals2)
|
|
deal3StateC := createMarketState(ctx, t, store, deals3)
|
|
|
|
minerAddr, err := address.NewFromString("t00")
|
|
require.NoError(t, err)
|
|
ts1, err := test.MockTipset(minerAddr, 1)
|
|
require.NoError(t, err)
|
|
ts2, err := test.MockTipset(minerAddr, 2)
|
|
require.NoError(t, err)
|
|
ts3, err := test.MockTipset(minerAddr, 3)
|
|
require.NoError(t, err)
|
|
|
|
api := test.NewMockAPI(bs)
|
|
api.SetActor(ts1.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: deal1StateC})
|
|
api.SetActor(ts2.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: deal2StateC})
|
|
api.SetActor(ts3.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: deal3StateC})
|
|
|
|
t.Run("caching", func(t *testing.T) {
|
|
dsm := newDealStateMatcher(state.NewStatePredicates(api))
|
|
matcher := dsm.matcher(ctx, abi.DealID(1))
|
|
|
|
// Call matcher with tipsets that have the same state
|
|
ok, stateChange, err := matcher(ts1, ts1)
|
|
require.NoError(t, err)
|
|
require.False(t, ok)
|
|
require.Nil(t, stateChange)
|
|
// Should call StateGetActor once for each tipset
|
|
require.Equal(t, 2, api.StateGetActorCallCount())
|
|
|
|
// Call matcher with tipsets that have different state
|
|
api.ResetCallCounts()
|
|
ok, stateChange, err = matcher(ts1, ts2)
|
|
require.NoError(t, err)
|
|
require.True(t, ok)
|
|
require.NotNil(t, stateChange)
|
|
// Should call StateGetActor once for each tipset
|
|
require.Equal(t, 2, api.StateGetActorCallCount())
|
|
|
|
// Call matcher again with the same tipsets as above, should be cached
|
|
api.ResetCallCounts()
|
|
ok, stateChange, err = matcher(ts1, ts2)
|
|
require.NoError(t, err)
|
|
require.True(t, ok)
|
|
require.NotNil(t, stateChange)
|
|
// Should not call StateGetActor (because it should hit the cache)
|
|
require.Equal(t, 0, api.StateGetActorCallCount())
|
|
|
|
// Call matcher with different tipsets, should not be cached
|
|
api.ResetCallCounts()
|
|
ok, stateChange, err = matcher(ts2, ts3)
|
|
require.NoError(t, err)
|
|
require.True(t, ok)
|
|
require.NotNil(t, stateChange)
|
|
// Should call StateGetActor once for each tipset
|
|
require.Equal(t, 2, api.StateGetActorCallCount())
|
|
})
|
|
|
|
t.Run("parallel", func(t *testing.T) {
|
|
api.ResetCallCounts()
|
|
dsm := newDealStateMatcher(state.NewStatePredicates(api))
|
|
matcher := dsm.matcher(ctx, abi.DealID(1))
|
|
|
|
// Call matcher with lots of go-routines in parallel
|
|
var eg errgroup.Group
|
|
res := make([]struct {
|
|
ok bool
|
|
stateChange events.StateChange
|
|
}, 20)
|
|
for i := 0; i < len(res); i++ {
|
|
i := i
|
|
eg.Go(func() error {
|
|
ok, stateChange, err := matcher(ts1, ts2)
|
|
res[i].ok = ok
|
|
res[i].stateChange = stateChange
|
|
return err
|
|
})
|
|
}
|
|
err := eg.Wait()
|
|
require.NoError(t, err)
|
|
|
|
// All go-routines should have got the same (cached) result
|
|
for i := 1; i < len(res); i++ {
|
|
require.Equal(t, res[i].ok, res[i-1].ok)
|
|
require.Equal(t, res[i].stateChange, res[i-1].stateChange)
|
|
}
|
|
|
|
// Only one go-routine should have called StateGetActor
|
|
// (once for each tipset)
|
|
require.Equal(t, 2, api.StateGetActorCallCount())
|
|
})
|
|
}
|
|
|
|
func createMarketState(ctx context.Context, t *testing.T, store adt2.Store, deals map[abi.DealID]*market2.DealState) cid.Cid {
|
|
dealRootCid := test.CreateDealAMT(ctx, t, store, deals)
|
|
state := test.CreateEmptyMarketState(t, store)
|
|
state.States = dealRootCid
|
|
|
|
stateC, err := store.Put(ctx, state)
|
|
require.NoError(t, err)
|
|
return stateC
|
|
}
|