Files
lotus/chain/index/api_test.go
Aarsh Shah dcc903c65d feat: a new ChainIndexer to index tipsets, messages and events (#12421)
* chain index complete for msgs and txns

* dont need observer changes for now

* changes

* fix tests

* fix tests

* use th right context

* index empty tipsets correctly

* implement automated backfilling

* add event indexing and remove all old indices

* fix test

* revert deployment test changes

* revert test changes and better error handling for eth tx index lookups

* fix sql statments naming convention

* address review for Index GC

* more changes as per review

* changes as per review

* fix config

* mark events as reverted during reconciliation

* better reconciliation; pens down and code complete; also reconcile events

* fix tests

* improve config and docs

* improve docs and error handling

* improve read logic

* improve docs

* better logging and handle ennable event storage

* improve logs and index init proc

* better logging

* fix bugs based on calibnet testing

* create sqliite Indices

* gc should be based on epochs

* fix event query

* foreign keys should be enabled on the DB

* reverted tipsets should be removed as part of GC

* release read lock

* make it easy to backfill an empty index using reconciliation

* better docs for reconciliation

* fix conflicts with master

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* fix go mod

* fix formatting

* revert config changes

* address changes in observer

* remove top level chainindex package

* changes as per review

* changes as per review

* changes as per review

* handle index with reverted tipsets during reconciliation

* changes as per review

* fix type of max reconcile epoch

* changes to reconciliation as per review

* log ipld error

* better logging of progress

* disable chain indexer hydrate from snapshot based on config

* always populate index

* make config easy to reason about

* fix config

* fix messaging

* revert config changes

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* changes as per review

* make error messages homogenous

* fix indentation

* changes as per review

* feat: recompute tipset to generate missing events if event indexing is enabled (#12463)

* auto repair events

* make jen

* fix leaky abstraction

* better docs for gc retention epoch

* imrpove DB handling (#12485)

* fix conflict

* fix lite node config for indexer

* exclude reverted events from eth get logs if client queries by epoch

* Simply addressing for event lookups in the index.

simply addressing for event lookups

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* fix tests

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* feat: migration("re-indexing"), backfilling and diasgnostics tooling for the `ChainIndexer` (#12450)

* fix conflicts with chain indexer

* feat: chain indexer todos [skip changelog] (#12462)

* feat: finish todos of validation api

* feat: add indexed data verification with chain store

* feat: address comments and finish TODO

* fix: build issue

* address comments

* fix: ci issue

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* changes to Index Validation API based on Rodds first review

* build chain indexer API

* improve error handling

* feat: lotus-shed tooling for chain indexer (#12474)

* feat: add lotus-shed command for backfilling chain indexer

* feat: add lotus-shed command for inspecting the chain indexer

* feat: use single lotus-shed command to inspect and backfill

* fix: remove the unused queries

* small changes

* add change log

* backfilling improvements and fixes

* finish chain index validation and backfill tooling

* user documentation for the

* validate from epoch

* Apply suggestions from code review

Suggestions from Steve's read of the user doc.

Co-authored-by: Steve Loeppky <biglep@filoz.org>

* changes to user doc as per review

* Apply suggestions from code review

Co-authored-by: Steve Loeppky <biglep@filoz.org>

* changes to user doc as per review

* Apply suggestions from code review

Co-authored-by: Steve Loeppky <biglep@filoz.org>

* changes as per review

* feat: add event entries count in validation API (#12506)

* feat: add event entry count in validation API

* address comments

* use sqllite defaults (#12504)

* Apply suggestions from code review

Co-authored-by: Steve Loeppky <biglep@filoz.org>

* write chain index to a different dir

* Apply suggestions from code review

Co-authored-by: Steve Loeppky <biglep@filoz.org>

* fix conflicts

* UX improvements to backfilling

* feat: tests for the chain indexer (#12521)

* ddl tests

* tests for the chain indexer

* finish unit tests for chain indexer

* fix formatting

* cleanup reverted tipsets to avoid db bloat

* fix logging

* test for filter by address

* test gc cascade delete

* fix db locked error during backfilling

* fix var name

* increase db locked timeout

* fix db locked issue

* reduce db lock timeout

* no lock in gc

* reconcile does not need lock

* improved error handling

* Update chain-indexing-overview-for-rpc-providers.md

Doc updates based on @jennijuju feedack.

* Update chain-indexing-overview-for-rpc-providers.MD

Fixes after reviewing 33c1ca1831

* better metrics for backfilling

* Update chain/index/chain-indexing-overview-for-rpc-providers.MD

Co-authored-by: Rod Vagg <rod@vagg.org>

* Update chain/index/chain-indexing-overview-for-rpc-providers.MD

Co-authored-by: Rod Vagg <rod@vagg.org>

* Update chain/index/chain-indexing-overview-for-rpc-providers.MD

Co-authored-by: Rod Vagg <rod@vagg.org>

* Update chain/index/chain-indexing-overview-for-rpc-providers.MD

Co-authored-by: Rod Vagg <rod@vagg.org>

* Update chain/index/chain-indexing-overview-for-rpc-providers.MD

Co-authored-by: Rod Vagg <rod@vagg.org>

* Update chain/index/chain-indexing-overview-for-rpc-providers.MD

Co-authored-by: Rod Vagg <rod@vagg.org>

* Update chain/index/chain-indexing-overview-for-rpc-providers.MD

Co-authored-by: Rod Vagg <rod@vagg.org>

* tests for changes to event addressing

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* changes as per review -> round 1

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* log tipset key cid

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* fix docs

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* fix tests

* fix tests

* make jen

* fix conflicts

---------

Co-authored-by: Aryan Tikarya <aryan.tikarya@dojima.network>
Co-authored-by: Rod Vagg <rod@vagg.org>
Co-authored-by: Steve Loeppky <biglep@filoz.org>

* fix lint

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* remove reverted flag from RPC

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* fix testing of events and dummy chain store

* remove lotus shed commands for old Indices

* change type of event counts to uint64

* only recompute events if theyre not found

* short-circuit empty events path for older tipsets

* chain indexer must be enabled if ETH RPC is enabled

* change name of message_id column to id in tipset_message table

* only expose SetRecomputeTipSetStateFunc

* dont block on head indexing for reading messages

* document why we're only checking for missing events for a single tipset

* document when we query for reverted events

* simplify event collection

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* fix test

* change event_id to id in the event table

* change head indexed timeout

* remove deprecated config options

* fail ETH RPC calls if ChainIndexer is disabled

* fix docs

* remove the tipset key cid func from lotus shed

* address review comments

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* chore(events): remove unnecessary DisableRealTimeFilterAPI (#12610)

* feat(cli): add --quiet to chainindex validate-backfill + cleanups (#12611)

* fix tests

* Apply suggestions from code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* error type for disabled chainindexer

* fix(chainindex): recompute tipset when we find no receipts

* fix(chainindexer): backfilling should halt when chain state data is missing and not backfill parents (#12619)

* fix backfilling UX

* Update chain/index/api.go

Co-authored-by: Rod Vagg <rod@vagg.org>

* address review

---------

Co-authored-by: Rod Vagg <rod@vagg.org>

* reduce log noise

* make jen

* make jen

* docs: finishing chain-indexer-overview-for-operators.md (#12600)

* Followup to PR #12450 for doc updates

This is being used to resolve the unresolved items in https://github.com/filecoin-project/lotus/pull/12450 since that PR is unwieldly at this point.

* Incorporated some items and added TODOs based on unresolved items from https://github.com/filecoin-project/lotus/pull/12450

* Incorporating more feedback

* Pointing to issue to learn about benefits

* Formatting fixes

* Apply most of the suggestions from @rvagg code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* Incorporating feedback from https://github.com/filecoin-project/lotus/pull/12600#discussion_r1802519453

* Addressing https://github.com/filecoin-project/lotus/pull/12600#discussion_r1802540042 and more

* Moved chain-indexer docs to documentation
Renamed
Added ToC

We can move to lotus-docs later

* Update documentation/en/chain-indexer-overview-for-operators.md

Co-authored-by: Rod Vagg <rod@vagg.org>

* Update documentation/en/chain-indexer-overview-for-operators.md

Co-authored-by: Rod Vagg <rod@vagg.org>

* Added upgrade path when importing chain state from a snapshot.

* Typo fixes

* Update documentation/en/chain-indexer-overview-for-operators.md

Co-authored-by: Rod Vagg <rod@vagg.org>

* chore(doc): "regular checks" section for chainindexer docs (#12612)

* Apply suggestions from @rvagg code review

Co-authored-by: Rod Vagg <rod@vagg.org>

* Incorporating @aarshkshah1992 feedback

* Update documentation/en/chain-indexer-overview-for-operators.md

Co-authored-by: Rod Vagg <rod@vagg.org>

---------

Co-authored-by: Rod Vagg <rod@vagg.org>
Co-authored-by: Aarsh Shah <aarshkshah1992@gmail.com>

* remove go mod replace

* remove unnecessary changes from CHANGELOG

* fix test

* compare events AMT root (#12632)

* fix(chainindex): retry transaction if database connection is lost (#12657)

* retry database lost connection

* log context cancellation

* address review

* fix gateway itest: no chainindexer for lite nodes

* fix changelog

---------

Co-authored-by: Rod Vagg <rod@vagg.org>
Co-authored-by: Aryan Tikarya <aryan.tikarya@dojima.network>
Co-authored-by: Steve Loeppky <biglep@filoz.org>
2024-10-31 09:58:19 +00:00

523 lines
16 KiB
Go

package index
import (
"context"
pseudo "math/rand"
"testing"
"time"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/require"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/types"
)
func TestValidateIsNullRoundSimple(t *testing.T) {
ctx := context.Background()
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
headHeight := abi.ChainEpoch(100)
tests := []struct {
name string
epoch abi.ChainEpoch
setupFunc func(*SqliteIndexer)
expectedResult bool
expectError bool
errorContains string
}{
{
name: "happy path - null round",
epoch: 50,
expectedResult: true,
},
{
name: "failure - non-null round",
epoch: 50,
setupFunc: func(si *SqliteIndexer) {
insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: randomCid(t, rng).Bytes(),
height: 50,
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
},
expectError: true,
errorContains: "index corruption",
},
{
name: "edge case - epoch 0",
epoch: 0,
expectedResult: true,
},
{
name: "edge case - epoch above head",
epoch: headHeight + 1,
expectedResult: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
si, _, _ := setupWithHeadIndexed(t, headHeight, rng)
t.Cleanup(func() { _ = si.Close() })
if tt.setupFunc != nil {
tt.setupFunc(si)
}
res, err := si.validateIsNullRound(ctx, tt.epoch)
if tt.expectError {
require.Error(t, err)
if tt.errorContains != "" {
require.ErrorContains(t, err, tt.errorContains)
}
} else {
require.NoError(t, err)
require.NotNil(t, res)
require.Equal(t, tt.expectedResult, res.IsNullRound)
require.Equal(t, tt.epoch, res.Height)
}
})
}
}
func TestFailureHeadHeight(t *testing.T) {
ctx := context.Background()
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
headHeight := abi.ChainEpoch(100)
si, head, _ := setupWithHeadIndexed(t, headHeight, rng)
t.Cleanup(func() { _ = si.Close() })
si.Start()
_, err := si.ChainValidateIndex(ctx, head.Height(), false)
require.Error(t, err)
require.Contains(t, err.Error(), "cannot validate index at epoch")
}
func TestBackfillNullRound(t *testing.T) {
ctx := context.Background()
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
headHeight := abi.ChainEpoch(100)
si, _, cs := setupWithHeadIndexed(t, headHeight, rng)
t.Cleanup(func() { _ = si.Close() })
si.Start()
nullRoundEpoch := abi.ChainEpoch(50)
nonNullRoundEpoch := abi.ChainEpoch(51)
// Create a tipset with a height different from the requested epoch
nonNullTs := fakeTipSet(t, rng, nonNullRoundEpoch, []cid.Cid{})
// Set up the chainstore to return the non-null tipset for the null round epoch
cs.SetTipsetByHeightAndKey(nullRoundEpoch, nonNullTs.Key(), nonNullTs)
// Attempt to validate the null round epoch
result, err := si.ChainValidateIndex(ctx, nullRoundEpoch, true)
require.NoError(t, err)
require.NotNil(t, result)
require.False(t, result.Backfilled)
require.True(t, result.IsNullRound)
}
func TestBackfillReturnsError(t *testing.T) {
ctx := context.Background()
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
headHeight := abi.ChainEpoch(100)
si, _, cs := setupWithHeadIndexed(t, headHeight, rng)
t.Cleanup(func() { _ = si.Close() })
si.Start()
missingEpoch := abi.ChainEpoch(50)
// Create a tipset for the missing epoch, but don't index it
missingTs := fakeTipSet(t, rng, missingEpoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(missingEpoch, missingTs.Key(), missingTs)
// Attempt to validate the missing epoch with backfill flag set to false
_, err := si.ChainValidateIndex(ctx, missingEpoch, false)
require.Error(t, err)
require.ErrorContains(t, err, "missing tipset at height 50 in the chain index")
}
func TestBackfillMissingEpoch(t *testing.T) {
ctx := context.Background()
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
headHeight := abi.ChainEpoch(100)
si, _, cs := setupWithHeadIndexed(t, headHeight, rng)
t.Cleanup(func() { _ = si.Close() })
si.Start()
// Initialize address resolver
si.SetActorToDelegatedAddresFunc(func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) {
idAddr, err := address.NewIDAddress(uint64(emitter))
if err != nil {
return address.Undef, false
}
return idAddr, true
})
missingEpoch := abi.ChainEpoch(50)
parentTs := fakeTipSet(t, rng, missingEpoch-1, []cid.Cid{})
cs.SetTipsetByHeightAndKey(missingEpoch-1, parentTs.Key(), parentTs)
missingTs := fakeTipSet(t, rng, missingEpoch, parentTs.Cids())
cs.SetTipsetByHeightAndKey(missingEpoch, missingTs.Key(), missingTs)
executionTs := fakeTipSet(t, rng, missingEpoch+1, missingTs.Key().Cids())
cs.SetTipsetByHeightAndKey(missingEpoch+1, executionTs.Key(), executionTs)
// Create fake messages and events
fakeMsg := fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng))
fakeEvent := fakeEvent(1, []kv{{k: "test", v: []byte("value")}, {k: "test2", v: []byte("value2")}}, nil)
ec := randomCid(t, rng)
executedMsg := executedMessage{
msg: fakeMsg,
evs: []types.Event{*fakeEvent},
rct: types.MessageReceipt{
EventsRoot: &ec,
},
}
cs.SetMessagesForTipset(missingTs, []types.ChainMsg{fakeMsg})
si.setExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) {
if msgTs.Height() == missingTs.Height() {
return []executedMessage{executedMsg}, nil
}
return nil, nil
})
// Attempt to validate and backfill the missing epoch
result, err := si.ChainValidateIndex(ctx, missingEpoch, true)
require.NoError(t, err)
require.NotNil(t, result)
require.True(t, result.Backfilled)
require.EqualValues(t, missingEpoch, result.Height)
require.Equal(t, uint64(1), result.IndexedMessagesCount)
require.Equal(t, uint64(1), result.IndexedEventsCount)
require.Equal(t, uint64(2), result.IndexedEventEntriesCount)
// Verify that the epoch is now indexed
// fails as the events root dont match
verificationResult, err := si.ChainValidateIndex(ctx, missingEpoch, false)
require.ErrorContains(t, err, "events AMT root mismatch")
require.Nil(t, verificationResult)
tsKeyCid, err := missingTs.Key().Cid()
require.NoError(t, err)
root, b, err := si.amtRootForEvents(ctx, tsKeyCid, fakeMsg.Cid())
require.NoError(t, err)
require.True(t, b)
executedMsg.rct.EventsRoot = &root
si.setExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) {
if msgTs.Height() == missingTs.Height() {
return []executedMessage{executedMsg}, nil
}
return nil, nil
})
verificationResult, err = si.ChainValidateIndex(ctx, missingEpoch, false)
require.NoError(t, err)
require.NotNil(t, verificationResult)
require.False(t, verificationResult.Backfilled)
require.Equal(t, result.IndexedMessagesCount, verificationResult.IndexedMessagesCount)
require.Equal(t, result.IndexedEventsCount, verificationResult.IndexedEventsCount)
require.Equal(t, result.IndexedEventEntriesCount, verificationResult.IndexedEventEntriesCount)
}
func TestIndexCorruption(t *testing.T) {
ctx := context.Background()
seed := time.Now().UnixNano()
t.Logf("seed: %d", seed)
rng := pseudo.New(pseudo.NewSource(seed))
headHeight := abi.ChainEpoch(100)
tests := []struct {
name string
setupFunc func(*testing.T, *SqliteIndexer, *dummyChainStore)
epoch abi.ChainEpoch
errorContains string
}{
{
name: "only reverted tipsets",
setupFunc: func(t *testing.T, si *SqliteIndexer, cs *dummyChainStore) {
epoch := abi.ChainEpoch(50)
ts := fakeTipSet(t, rng, epoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts)
keyBz, err := ts.Key().Cid()
require.NoError(t, err)
insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: keyBz.Bytes(),
height: uint64(epoch),
reverted: true,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
},
epoch: 50,
errorContains: "index corruption: height 50 only has reverted tipsets",
},
{
name: "multiple non-reverted tipsets",
setupFunc: func(t *testing.T, si *SqliteIndexer, cs *dummyChainStore) {
epoch := abi.ChainEpoch(50)
ts1 := fakeTipSet(t, rng, epoch, []cid.Cid{})
ts2 := fakeTipSet(t, rng, epoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(epoch, ts1.Key(), ts1)
t1Bz, err := toTipsetKeyCidBytes(ts1)
require.NoError(t, err)
t2Bz, err := toTipsetKeyCidBytes(ts2)
require.NoError(t, err)
insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: t1Bz,
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: t2Bz,
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
},
epoch: 50,
errorContains: "index corruption: height 50 has multiple non-reverted tipsets",
},
{
name: "tipset key mismatch",
setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) {
epoch := abi.ChainEpoch(50)
ts1 := fakeTipSet(t, rng, epoch, []cid.Cid{})
ts2 := fakeTipSet(t, rng, epoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(epoch, ts1.Key(), ts1)
insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: ts2.Key().Cids()[0].Bytes(),
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
},
epoch: 50,
errorContains: "index corruption: indexed tipset at height 50 has key",
},
{
name: "reverted events for executed tipset",
setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) {
epoch := abi.ChainEpoch(50)
ts := fakeTipSet(t, rng, epoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts)
keyBz, err := ts.Key().Cid()
require.NoError(t, err)
messageID := insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: keyBz.Bytes(),
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
insertEvent(t, si, event{
messageID: messageID,
eventIndex: 0,
emitterId: 1,
emitterAddr: randomIDAddr(t, rng).Bytes(),
reverted: true,
})
cs.SetTipsetByHeightAndKey(epoch+1, fakeTipSet(t, rng, epoch+1, ts.Key().Cids()).Key(), fakeTipSet(t, rng, epoch+1, ts.Key().Cids()))
},
epoch: 50,
errorContains: "index corruption: reverted events found for an executed tipset",
},
{
name: "message count mismatch",
setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) {
epoch := abi.ChainEpoch(50)
ts := fakeTipSet(t, rng, epoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts)
keyBz, err := ts.Key().Cid()
require.NoError(t, err)
// Insert two messages in the index
insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: keyBz.Bytes(),
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: keyBz.Bytes(),
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 1,
})
// Setup dummy event loader
si.setExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) {
return []executedMessage{{msg: fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng))}}, nil
})
// Set up the next tipset for event execution
nextTs := fakeTipSet(t, rng, epoch+1, ts.Key().Cids())
cs.SetTipsetByHeightAndKey(epoch+1, nextTs.Key(), nextTs)
},
epoch: 50,
errorContains: "failed to verify indexed data at height 50: message count mismatch for height 50: chainstore has 1, index has 2",
},
{
name: "event count mismatch",
setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) {
epoch := abi.ChainEpoch(50)
ts := fakeTipSet(t, rng, epoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts)
keyBz, err := ts.Key().Cid()
require.NoError(t, err)
// Insert one message in the index
messageID := insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: keyBz.Bytes(),
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
// Insert two events for the message
insertEvent(t, si, event{
messageID: messageID,
eventIndex: 0,
emitterId: 2,
emitterAddr: randomIDAddr(t, rng).Bytes(),
reverted: false,
})
insertEvent(t, si, event{
messageID: messageID,
eventIndex: 1,
emitterId: 3,
emitterAddr: randomIDAddr(t, rng).Bytes(),
reverted: false,
})
// Setup dummy event loader to return only one event
si.setExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) {
return []executedMessage{
{
msg: fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng)),
evs: []types.Event{*fakeEvent(1, []kv{{k: "test", v: []byte("value")}}, nil)},
},
}, nil
})
// Set up the next tipset for event execution
nextTs := fakeTipSet(t, rng, epoch+1, ts.Key().Cids())
cs.SetTipsetByHeightAndKey(epoch+1, nextTs.Key(), nextTs)
},
epoch: 50,
errorContains: "failed to verify indexed data at height 50: event count mismatch for height 50: chainstore has 1, index has 2",
},
{
name: "event entries count mismatch",
setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) {
epoch := abi.ChainEpoch(50)
ts := fakeTipSet(t, rng, epoch, []cid.Cid{})
cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts)
keyBz, err := ts.Key().Cid()
require.NoError(t, err)
// Insert one message in the index
messageID := insertTipsetMessage(t, si, tipsetMessage{
tipsetKeyCid: keyBz.Bytes(),
height: uint64(epoch),
reverted: false,
messageCid: randomCid(t, rng).Bytes(),
messageIndex: 0,
})
// Insert one event with two entries for the message
eventID := insertEvent(t, si, event{
messageID: messageID,
eventIndex: 0,
emitterId: 4,
emitterAddr: randomIDAddr(t, rng).Bytes(),
reverted: false,
})
insertEventEntry(t, si, eventEntry{
eventID: eventID,
indexed: true,
flags: []byte{0x01},
key: "key1",
codec: 1,
value: []byte("value1"),
})
insertEventEntry(t, si, eventEntry{
eventID: eventID,
indexed: true,
flags: []byte{0x00},
key: "key2",
codec: 2,
value: []byte("value2"),
})
// Setup dummy event loader to return one event with only one entry
si.setExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) {
return []executedMessage{
{
msg: fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng)),
evs: []types.Event{*fakeEvent(1, []kv{{k: "key1", v: []byte("value1")}}, nil)},
},
}, nil
})
// Set up the next tipset for event execution
nextTs := fakeTipSet(t, rng, epoch+1, ts.Key().Cids())
cs.SetTipsetByHeightAndKey(epoch+1, nextTs.Key(), nextTs)
},
epoch: 50,
errorContains: "failed to verify indexed data at height 50: event entries count mismatch for height 50: chainstore has 1, index has 2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
si, _, cs := setupWithHeadIndexed(t, headHeight, rng)
t.Cleanup(func() { _ = si.Close() })
si.Start()
tt.setupFunc(t, si, cs)
_, err := si.ChainValidateIndex(ctx, tt.epoch, false)
require.Error(t, err)
require.Contains(t, err.Error(), tt.errorContains)
})
}
}