Files
lotus/chain/index/read_test.go

304 lines
7.9 KiB
Go

package index
import (
"context"
"errors"
pseudo "math/rand"
"sync"
"testing"
"time"
"github.com/ipfs/go-cid"
mh "github.com/multiformats/go-multihash"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/lotus/chain/actors/adt"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)
func TestGetCidFromHash(t *testing.T) {
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
ctx := context.Background()
s, _, _ := setupWithHeadIndexed(t, 10, rng)
ethTxHash := ethtypes.EthHash([32]byte{1})
msgCid := randomCid(t, rng)
// read from empty db -> ErrNotFound
c, err := s.GetCidFromHash(ctx, ethTxHash)
require.Error(t, err)
require.True(t, errors.Is(err, ErrNotFound))
require.EqualValues(t, cid.Undef, c)
// insert and read
insertEthTxHash(t, s, ethTxHash, msgCid)
c, err = s.GetCidFromHash(ctx, ethTxHash)
require.NoError(t, err)
require.EqualValues(t, msgCid, c)
// look up some other hash -> fails
c, err = s.GetCidFromHash(ctx, ethtypes.EthHash([32]byte{2}))
require.Error(t, err)
require.True(t, errors.Is(err, ErrNotFound))
require.EqualValues(t, cid.Undef, c)
}
func TestGetMsgInfo(t *testing.T) {
ctx := context.Background()
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
s, _, _ := setupWithHeadIndexed(t, 10, rng)
t.Run("message exists", func(t *testing.T) {
msgCid := randomCid(t, rng)
msgCidBytes := msgCid.Bytes()
tsKeyCid := randomCid(t, rng)
insertTipsetMessage(t, s, tipsetMessage{
tipsetKeyCid: tsKeyCid.Bytes(),
height: uint64(1),
reverted: false,
messageCid: msgCidBytes,
messageIndex: 1,
})
mi, err := s.GetMsgInfo(ctx, msgCid)
require.NoError(t, err)
require.Equal(t, msgCid, mi.Message)
require.Equal(t, tsKeyCid, mi.TipSet)
require.Equal(t, abi.ChainEpoch(1), mi.Epoch)
})
t.Run("message not found", func(t *testing.T) {
nonExistentMsgCid := randomCid(t, rng)
mi, err := s.GetMsgInfo(ctx, nonExistentMsgCid)
require.Error(t, err)
require.ErrorIs(t, err, ErrNotFound)
require.Nil(t, mi)
})
}
func setupWithHeadIndexed(t *testing.T, headHeight abi.ChainEpoch, rng *pseudo.Rand) (*SqliteIndexer, *types.TipSet, *dummyChainStore) {
head := fakeTipSet(t, rng, headHeight, []cid.Cid{})
d := newDummyChainStore()
d.SetHeaviestTipSet(head)
s, err := NewSqliteIndexer(":memory:", d, 0, false, 0)
require.NoError(t, err)
insertHead(t, s, head, headHeight)
return s, head, d
}
func insertHead(t *testing.T, s *SqliteIndexer, head *types.TipSet, height abi.ChainEpoch) {
headKeyBytes, err := toTipsetKeyCidBytes(head)
require.NoError(t, err)
insertTipsetMessage(t, s, tipsetMessage{
tipsetKeyCid: headKeyBytes,
height: uint64(height),
reverted: false,
messageCid: nil,
messageIndex: -1,
})
}
func insertEthTxHash(t *testing.T, s *SqliteIndexer, ethTxHash ethtypes.EthHash, messageCid cid.Cid) {
msgCidBytes := messageCid.Bytes()
res, err := s.stmts.insertEthTxHashStmt.Exec(ethTxHash.String(), msgCidBytes)
require.NoError(t, err)
rowsAffected, err := res.RowsAffected()
require.NoError(t, err)
require.Equal(t, int64(1), rowsAffected)
}
type dummyChainStore struct {
mu sync.RWMutex
heightToTipSet map[abi.ChainEpoch]*types.TipSet
messagesForTipset map[*types.TipSet][]types.ChainMsg
keyToTipSet map[types.TipSetKey]*types.TipSet
tipsetCidToTipset map[cid.Cid]*types.TipSet
heaviestTipSet *types.TipSet
messagesForBlock func(ctx context.Context, b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error)
actorStore func(ctx context.Context) adt.Store
}
func newDummyChainStore() *dummyChainStore {
return &dummyChainStore{
heightToTipSet: make(map[abi.ChainEpoch]*types.TipSet),
messagesForTipset: make(map[*types.TipSet][]types.ChainMsg),
keyToTipSet: make(map[types.TipSetKey]*types.TipSet),
tipsetCidToTipset: make(map[cid.Cid]*types.TipSet),
}
}
func (d *dummyChainStore) MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error) {
d.mu.RLock()
defer d.mu.RUnlock()
msgs, ok := d.messagesForTipset[ts]
if !ok {
return nil, nil
}
return msgs, nil
}
func (d *dummyChainStore) GetHeaviestTipSet() *types.TipSet {
d.mu.RLock()
defer d.mu.RUnlock()
return d.heaviestTipSet
}
func (d *dummyChainStore) GetTipSetByCid(_ context.Context, tsKeyCid cid.Cid) (*types.TipSet, error) {
d.mu.RLock()
defer d.mu.RUnlock()
if _, ok := d.tipsetCidToTipset[tsKeyCid]; !ok {
return nil, errors.New("not found")
}
return d.tipsetCidToTipset[tsKeyCid], nil
}
func (d *dummyChainStore) SetTipSetByCid(t *testing.T, ts *types.TipSet) {
d.mu.Lock()
defer d.mu.Unlock()
tsKeyCid, err := ts.Key().Cid()
require.NoError(t, err)
d.tipsetCidToTipset[tsKeyCid] = ts
}
func (d *dummyChainStore) GetTipSetFromKey(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) {
d.mu.RLock()
defer d.mu.RUnlock()
return d.keyToTipSet[tsk], nil
}
func (d *dummyChainStore) MessagesForBlock(ctx context.Context, b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) {
d.mu.RLock()
defer d.mu.RUnlock()
if d.messagesForBlock != nil {
return d.messagesForBlock(ctx, b)
}
return nil, nil, nil
}
func (d *dummyChainStore) ActorStore(ctx context.Context) adt.Store {
d.mu.RLock()
defer d.mu.RUnlock()
if d.actorStore != nil {
return d.actorStore(ctx)
}
return nil
}
func (d *dummyChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, _ *types.TipSet, prev bool) (*types.TipSet, error) {
d.mu.RLock()
defer d.mu.RUnlock()
ts, ok := d.heightToTipSet[h]
if !ok {
return nil, errors.New("tipset not found")
}
return ts, nil
}
func (d *dummyChainStore) IsStoringEvents() bool {
return true
}
// Setter methods to configure the mock
func (d *dummyChainStore) SetMessagesForTipset(ts *types.TipSet, msgs []types.ChainMsg) {
d.mu.Lock()
defer d.mu.Unlock()
d.messagesForTipset[ts] = msgs
}
func (d *dummyChainStore) SetHeaviestTipSet(ts *types.TipSet) {
d.mu.Lock()
defer d.mu.Unlock()
d.heaviestTipSet = ts
}
func (d *dummyChainStore) SetTipsetByHeightAndKey(h abi.ChainEpoch, tsk types.TipSetKey, ts *types.TipSet) {
d.mu.Lock()
defer d.mu.Unlock()
d.heightToTipSet[h] = ts
d.keyToTipSet[tsk] = ts
}
func randomIDAddr(tb testing.TB, rng *pseudo.Rand) address.Address {
tb.Helper()
addr, err := address.NewIDAddress(uint64(rng.Int63()))
require.NoError(tb, err)
return addr
}
func randomCid(tb testing.TB, rng *pseudo.Rand) cid.Cid {
tb.Helper()
cb := cid.V1Builder{Codec: cid.Raw, MhType: mh.IDENTITY}
c, err := cb.Sum(randomBytes(10, rng))
require.NoError(tb, err)
return c
}
func randomBytes(n int, rng *pseudo.Rand) []byte {
buf := make([]byte, n)
rng.Read(buf)
return buf
}
func fakeTipSet(tb testing.TB, rng *pseudo.Rand, h abi.ChainEpoch, parents []cid.Cid) *types.TipSet {
tb.Helper()
ts, err := types.NewTipSet([]*types.BlockHeader{
{
Height: h,
Miner: randomIDAddr(tb, rng),
Parents: parents,
Ticket: &types.Ticket{VRFProof: []byte{byte(h % 2)}},
ParentStateRoot: randomCid(tb, rng),
Messages: randomCid(tb, rng),
ParentMessageReceipts: randomCid(tb, rng),
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS},
BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS},
},
{
Height: h,
Miner: randomIDAddr(tb, rng),
Parents: parents,
Ticket: &types.Ticket{VRFProof: []byte{byte((h + 1) % 2)}},
ParentStateRoot: randomCid(tb, rng),
Messages: randomCid(tb, rng),
ParentMessageReceipts: randomCid(tb, rng),
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS},
BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS},
},
})
require.NoError(tb, err)
return ts
}