mirror of
https://github.com/filecoin-project/lotus.git
synced 2025-08-24 01:08:42 +08:00

* 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>
523 lines
16 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|