package events import ( "context" "testing" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" ) type tsCacheAPIFailOnStorageCall struct { t *testing.T } func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSetAfterHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { tc.t.Fatal("storage call") return &types.TipSet{}, nil } func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSetByHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { tc.t.Fatal("storage call") return &types.TipSet{}, nil } func (tc *tsCacheAPIFailOnStorageCall) ChainHead(ctx context.Context) (*types.TipSet, error) { tc.t.Fatal("storage call") return &types.TipSet{}, nil } func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { tc.t.Fatal("storage call") return &types.TipSet{}, nil } type cacheHarness struct { t *testing.T miner address.Address tsc *tipSetCache height abi.ChainEpoch } func newCacheharness(t *testing.T) *cacheHarness { a, err := address.NewFromString("t00") require.NoError(t, err) h := &cacheHarness{ t: t, tsc: newTSCache(&tsCacheAPIFailOnStorageCall{t: t}, 50), height: 75, miner: a, } h.addWithParents(nil) return h } func (h *cacheHarness) addWithParents(parents []cid.Cid) { ts, err := types.NewTipSet([]*types.BlockHeader{{ Miner: h.miner, Height: h.height, ParentStateRoot: dummyCid, Messages: dummyCid, ParentMessageReceipts: dummyCid, BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, Parents: parents, }}) require.NoError(h.t, err) require.NoError(h.t, h.tsc.add(ts)) h.height++ } func (h *cacheHarness) add() { last, err := h.tsc.ChainHead(context.Background()) require.NoError(h.t, err) h.addWithParents(last.Cids()) } func (h *cacheHarness) revert() { best, err := h.tsc.ChainHead(context.Background()) require.NoError(h.t, err) err = h.tsc.revert(best) require.NoError(h.t, err) h.height-- } func (h *cacheHarness) skip(n abi.ChainEpoch) { h.height += n } func TestTsCache(t *testing.T) { h := newCacheharness(t) for i := 0; i < 9000; i++ { if i%90 > 60 { h.revert() } else { h.add() } } } func TestTsCacheNulls(t *testing.T) { ctx := context.Background() h := newCacheharness(t) h.add() h.add() h.add() h.skip(5) h.add() h.add() best, err := h.tsc.ChainHead(ctx) require.NoError(t, err) require.Equal(t, h.height-1, best.Height()) ts, err := h.tsc.ChainGetTipSetByHeight(ctx, h.height-1, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-1, ts.Height()) ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-2, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-2, ts.Height()) // Should skip the nulls and walk back to the last tipset. ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-3, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-8, ts.Height()) ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-8, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-8, ts.Height()) best, err = h.tsc.ChainHead(ctx) require.NoError(t, err) require.NoError(t, h.tsc.revert(best)) best, err = h.tsc.ChainHead(ctx) require.NoError(t, err) require.NoError(t, h.tsc.revert(best)) best, err = h.tsc.ChainHead(ctx) require.NoError(t, err) require.Equal(t, h.height-8, best.Height()) h.skip(50) h.add() ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-1, types.EmptyTSK) require.NoError(t, err) require.Equal(t, h.height-1, ts.Height()) } type tsCacheAPIStorageCallCounter struct { t *testing.T chainGetTipSetByHeight int chainGetTipSetAfterHeight int chainGetTipSet int chainHead int } func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSetByHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { tc.chainGetTipSetByHeight++ return &types.TipSet{}, nil } func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSetAfterHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { tc.chainGetTipSetAfterHeight++ return &types.TipSet{}, nil } func (tc *tsCacheAPIStorageCallCounter) ChainHead(ctx context.Context) (*types.TipSet, error) { tc.chainHead++ return &types.TipSet{}, nil } func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { tc.chainGetTipSet++ return &types.TipSet{}, nil } func TestTsCacheEmpty(t *testing.T) { // Calling best on an empty cache should just call out to the chain API callCounter := &tsCacheAPIStorageCallCounter{t: t} tsc := newTSCache(callCounter, 50) _, err := tsc.ChainHead(context.Background()) require.NoError(t, err) require.Equal(t, 1, callCounter.chainHead) } func TestTsCacheSkip(t *testing.T) { h := newCacheharness(t) ts, err := types.NewTipSet([]*types.BlockHeader{{ Miner: h.miner, Height: h.height, ParentStateRoot: dummyCid, Messages: dummyCid, ParentMessageReceipts: dummyCid, BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, // With parents that don't match the last block. Parents: nil, }}) require.NoError(h.t, err) err = h.tsc.add(ts) require.Error(t, err) }