From 8f60cab197ba3b7c004d8ccf683eadf612bb204d Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 14 Jan 2026 15:07:00 -0500 Subject: [PATCH 01/17] new receiptDB receipt-specific interface --- app/app.go | 5 +- app/test_helpers.go | 15 ++--- evmrpc/filter.go | 99 +++++++------------------------- evmrpc/filter_test.go | 29 ++-------- evmrpc/setup_test.go | 8 +-- evmrpc/simulate_test.go | 8 +-- evmrpc/state_test.go | 4 +- evmrpc/tests/utils.go | 4 +- evmrpc/watermark_manager.go | 7 ++- evmrpc/watermark_manager_test.go | 39 ++++++++++++- go.work.sum | 93 +++++++++++++++++++++++++++++- x/evm/keeper/keeper.go | 9 ++- x/evm/keeper/receipt.go | 82 ++++---------------------- 13 files changed, 197 insertions(+), 205 deletions(-) diff --git a/app/app.go b/app/app.go index dab67151b0..c8802e260a 100644 --- a/app/app.go +++ b/app/app.go @@ -378,7 +378,7 @@ type App struct { genesisImportConfig genesistypes.GenesisImportConfig stateStore seidb.StateStore - receiptStore seidb.StateStore + receiptStore evmkeeper.ReceiptStore forkInitializer func(sdk.Context) @@ -620,10 +620,11 @@ func New( ssConfig.DBDirectory = receiptStorePath ssConfig.KeepLastVersion = false if app.receiptStore == nil { - app.receiptStore, err = ss.NewStateStore(logger, receiptStorePath, ssConfig) + receiptStateStore, err := ss.NewStateStore(logger, receiptStorePath, ssConfig) if err != nil { panic(fmt.Sprintf("error while creating receipt store: %s", err)) } + app.receiptStore = evmkeeper.NewReceiptStore(receiptStateStore, keys[evmtypes.StoreKey]) } app.EvmKeeper = *evmkeeper.NewKeeper(keys[evmtypes.StoreKey], tkeys[evmtypes.TransientStoreKey], app.GetSubspace(evmtypes.ModuleName), app.receiptStore, app.BankKeeper, diff --git a/app/test_helpers.go b/app/test_helpers.go index 4cf7d6119b..0a140e6c2c 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -15,9 +15,10 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" ssconfig "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/state_db/ss" - seidbtypes "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm" wasmkeeper "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm/keeper" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/suite" "github.com/tendermint/tendermint/config" @@ -247,7 +248,7 @@ func (s *TestWrapper) EndBlock() { legacyabci.EndBlock(s.Ctx, s.Ctx.BlockHeight(), 0, s.App.EndBlockKeepers) } -func setupReceiptStore() (seidbtypes.StateStore, error) { +func setupReceiptStore(storeKey sdk.StoreKey) (evmkeeper.ReceiptStore, error) { // Create a unique temporary directory per test process to avoid Pebble DB lock conflicts baseDir := filepath.Join(os.TempDir(), "sei-testing") if err := os.MkdirAll(baseDir, 0o750); err != nil { @@ -262,11 +263,11 @@ func setupReceiptStore() (seidbtypes.StateStore, error) { ssConfig.KeepRecent = 0 // No min retain blocks in test ssConfig.DBDirectory = tempDir ssConfig.KeepLastVersion = false - receiptStore, err := ss.NewStateStore(log.NewNopLogger(), tempDir, ssConfig) + receiptStateStore, err := ss.NewStateStore(log.NewNopLogger(), tempDir, ssConfig) if err != nil { return nil, err } - return receiptStore, nil + return evmkeeper.NewReceiptStore(receiptStateStore, storeKey), nil } func SetupWithDefaultHome(isCheckTx bool, enableEVMCustomPrecompiles bool, overrideWasmGasMultiplier bool, baseAppOptions ...func(*bam.BaseApp)) (res *App) { @@ -275,7 +276,7 @@ func SetupWithDefaultHome(isCheckTx bool, enableEVMCustomPrecompiles bool, overr options := []AppOption{ func(app *App) { - receiptStore, err := setupReceiptStore() + receiptStore, err := setupReceiptStore(app.keys[evmtypes.StoreKey]) if err != nil { panic(fmt.Sprintf("error while creating receipt store: %s", err)) } @@ -345,7 +346,7 @@ func SetupWithDB(tb testing.TB, db dbm.DB, isCheckTx bool, enableEVMCustomPrecom options := []AppOption{ func(app *App) { - receiptStore, err := setupReceiptStore() + receiptStore, err := setupReceiptStore(app.keys[evmtypes.StoreKey]) if err != nil { panic(fmt.Sprintf("error while creating receipt store: %s", err)) } @@ -411,7 +412,7 @@ func SetupWithSc(t *testing.T, isCheckTx bool, enableEVMCustomPrecompiles bool, options := []AppOption{ func(app *App) { - receiptStore, err := setupReceiptStore() + receiptStore, err := setupReceiptStore(app.keys[evmtypes.StoreKey]) if err != nil { panic(fmt.Sprintf("error while creating receipt store: %s", err)) } diff --git a/evmrpc/filter.go b/evmrpc/filter.go index 9daa35a2a2..d89075da28 100644 --- a/evmrpc/filter.go +++ b/evmrpc/filter.go @@ -783,7 +783,7 @@ func (f *LogFetcher) GetLogsByFilters(ctx context.Context, crit filters.FilterCr localLogs := f.globalLogSlicePool.Get() for _, block := range batch { - f.GetLogsForBlockPooled(block, crit, bloomIndexes, &localLogs) + f.GetLogsForBlockPooled(block, crit, &localLogs) } // Sort the local batch @@ -907,68 +907,32 @@ func (f *LogFetcher) earliestHeight(ctx context.Context) (int64, error) { } // Pooled version that reuses slice allocation -func (f *LogFetcher) GetLogsForBlockPooled(block *coretypes.ResultBlock, crit filters.FilterCriteria, filters [][]bloomIndexes, result *[]*ethtypes.Log) { +func (f *LogFetcher) GetLogsForBlockPooled(block *coretypes.ResultBlock, crit filters.FilterCriteria, result *[]*ethtypes.Log) { collector := &pooledCollector{logs: result} - f.collectLogs(block, crit, filters, collector, true) // Apply exact matching -} - -func (f *LogFetcher) IsLogExactMatch(log *ethtypes.Log, crit filters.FilterCriteria) bool { - addrMatch := len(crit.Addresses) == 0 - for _, addrFilter := range crit.Addresses { - if log.Address == addrFilter { - addrMatch = true - break - } - } - return addrMatch && matchTopics(crit.Topics, log.Topics) + f.collectLogs(block, crit, collector, true) // Apply exact matching } // Unified log collection logic -func (f *LogFetcher) collectLogs(block *coretypes.ResultBlock, crit filters.FilterCriteria, filters [][]bloomIndexes, collector logCollector, applyExactMatch bool) { +func (f *LogFetcher) collectLogs(block *coretypes.ResultBlock, crit filters.FilterCriteria, collector logCollector, applyExactMatch bool) { ctx := f.ctxProvider(block.Block.Height) - totalLogs := uint(0) - evmTxIndex := 0 - - for _, hash := range getTxHashesFromBlock(f.ctxProvider, f.txConfigProvider, f.k, block, f.includeSyntheticReceipts, f.cacheCreationMutex, f.globalBlockCache) { - receipt, found := getOrSetCachedReceipt(f.cacheCreationMutex, f.globalBlockCache, ctx, f.k, block, hash.hash) - if !found { - ctx.Logger().Error(fmt.Sprintf("collectLogs: unable to find receipt for hash %s", hash.hash.Hex())) - continue - } - - txLogs := keeper.GetLogsForTx(receipt, totalLogs) + store := f.k.ReceiptStore() + if store == nil { + return + } - if len(crit.Addresses) != 0 || len(crit.Topics) != 0 { - if len(receipt.LogsBloom) == 0 || MatchFilters(ethtypes.Bloom(receipt.LogsBloom), filters) { - if applyExactMatch { - for _, log := range txLogs { - log.TxIndex = uint(evmTxIndex) //nolint:gosec - log.BlockNumber = uint64(block.Block.Height) //nolint:gosec - log.BlockHash = common.BytesToHash(block.BlockID.Hash) - if f.IsLogExactMatch(log, crit) { - collector.Append(log) - } - } - } else { - for _, log := range txLogs { - log.TxIndex = uint(evmTxIndex) //nolint:gosec - log.BlockNumber = uint64(block.Block.Height) //nolint:gosec - log.BlockHash = common.BytesToHash(block.BlockID.Hash) - collector.Append(log) - } - } - } - } else { - for _, log := range txLogs { - log.TxIndex = uint(evmTxIndex) //nolint:gosec - log.BlockNumber = uint64(block.Block.Height) //nolint:gosec - log.BlockHash = common.BytesToHash(block.BlockID.Hash) - collector.Append(log) - } - } + txHashes := getTxHashesFromBlock(f.ctxProvider, f.txConfigProvider, f.k, block, f.includeSyntheticReceipts, f.cacheCreationMutex, f.globalBlockCache) + hashes := make([]common.Hash, 0, len(txHashes)) + for _, hash := range txHashes { + hashes = append(hashes, hash.hash) + } - totalLogs += uint(len(txLogs)) - evmTxIndex++ + logs, err := store.FilterLogs(ctx, block.Block.Height, common.BytesToHash(block.BlockID.Hash), hashes, crit, applyExactMatch) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("collectLogs: %s", err.Error())) + return + } + for _, log := range logs { + collector.Append(log) } } @@ -1157,26 +1121,3 @@ func (f *LogFetcher) processBatch(ctx context.Context, start, end int64, crit fi res <- block } } - -func matchTopics(topics [][]common.Hash, eventTopics []common.Hash) bool { - for i, topicList := range topics { - if len(topicList) == 0 { - // anything matches for this position - continue - } - if i >= len(eventTopics) { - return false - } - matched := false - for _, topic := range topicList { - if topic == eventTopics[i] { - matched = true - break - } - } - if !matched { - return false - } - } - return true -} diff --git a/evmrpc/filter_test.go b/evmrpc/filter_test.go index b7893287d0..2461726414 100644 --- a/evmrpc/filter_test.go +++ b/evmrpc/filter_test.go @@ -6,9 +6,8 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" - "github.com/sei-protocol/sei-chain/x/evm/keeper" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/require" ) @@ -580,28 +579,10 @@ func TestCollectLogsEvmTransactionIndex(t *testing.T) { testkeeper.MustMockReceipt(t, k, ctx, txHash, receipt) } - // Test the core logic that collectLogs implements - // This simulates what collectLogs does for each EVM transaction - var collectedLogs []*ethtypes.Log - evmTxIndex := 0 - totalLogs := uint(0) - - for _, txHash := range evmTxHashes { - receipt, err := k.GetReceipt(ctx, txHash) - require.NoError(t, err, "should be able to get receipt for tx %s", txHash.Hex()) - - // This simulates keeper.GetLogsForTx - logs := keeper.GetLogsForTx(receipt, totalLogs) - - // This is the key part we're testing: setting the correct EVM transaction index - for _, log := range logs { - log.TxIndex = uint(evmTxIndex) // This should override receipt.TransactionIndex - collectedLogs = append(collectedLogs, log) - } - - totalLogs += uint(len(receipt.Logs)) - evmTxIndex++ - } + store := k.ReceiptStore() + require.NotNil(t, store) + collectedLogs, err := store.FilterLogs(ctx, 2, common.HexToHash("0x2"), evmTxHashes, filters.FilterCriteria{}, true) + require.NoError(t, err) // Verify that the transaction indices are set correctly require.Equal(t, len(evmTxHashes), len(collectedLogs), "should have one log per transaction") diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index 9f0f556d60..f4dbf8b0ab 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -574,10 +574,10 @@ func init() { testApp.Commit(context.Background()) if store := EVMKeeper.ReceiptStore(); store != nil { latest := int64(math.MaxInt64) - if err := store.SetLatestVersion(latest); err != nil { + if err := store.SetLatestHeight(latest); err != nil { panic(err) } - _ = store.SetEarliestVersion(1, true) + _ = store.SetEarliestHeight(1) } ctxProvider := func(height int64) sdk.Context { if height == MockHeight2 { @@ -1097,10 +1097,10 @@ func setupLogs() { EVMKeeper.SetEvmOnlyBlockBloom(Ctx, []ethtypes.Bloom{bloom4, bloomTx1}) if store := EVMKeeper.ReceiptStore(); store != nil { - if err := store.SetLatestVersion(MockHeight103); err != nil { + if err := store.SetLatestHeight(MockHeight103); err != nil { panic(err) } - _ = store.SetEarliestVersion(1, true) + _ = store.SetEarliestHeight(1) } } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index adf49080e2..a2985338d5 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -20,7 +20,7 @@ import ( "github.com/sei-protocol/sei-chain/app/legacyabci" "github.com/sei-protocol/sei-chain/evmrpc" "github.com/sei-protocol/sei-chain/example/contracts/simplestorage" - sstypes "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/require" @@ -40,7 +40,7 @@ type bcFailClient struct { first bool } -func primeReceiptStore(t *testing.T, store sstypes.StateStore, latest int64) { +func primeReceiptStore(t *testing.T, store evmkeeper.ReceiptStore, latest int64) { t.Helper() if store == nil { return @@ -48,8 +48,8 @@ func primeReceiptStore(t *testing.T, store sstypes.StateStore, latest int64) { if latest <= 0 { latest = 1 } - require.NoError(t, store.SetLatestVersion(latest)) - require.NoError(t, store.SetEarliestVersion(1, true)) + require.NoError(t, store.SetLatestHeight(latest)) + require.NoError(t, store.SetEarliestHeight(1)) } func (bc *bcFailClient) Block(ctx context.Context, h *int64) (*coretypes.ResultBlock, error) { diff --git a/evmrpc/state_test.go b/evmrpc/state_test.go index c9a6b120d1..c316f5c328 100644 --- a/evmrpc/state_test.go +++ b/evmrpc/state_test.go @@ -217,8 +217,8 @@ func TestGetProof(t *testing.T) { require.Nil(t, err) } if store := testApp.EvmKeeper.ReceiptStore(); store != nil { - require.NoError(t, store.SetLatestVersion(MockHeight8)) - require.NoError(t, store.SetEarliestVersion(1, true)) + require.NoError(t, store.SetLatestHeight(MockHeight8)) + require.NoError(t, store.SetEarliestHeight(1)) } client := &MockClient{} ctxProvider := func(height int64) sdk.Context { diff --git a/evmrpc/tests/utils.go b/evmrpc/tests/utils.go index 4fcedfdf8a..b18767fc80 100644 --- a/evmrpc/tests/utils.go +++ b/evmrpc/tests/utils.go @@ -162,10 +162,10 @@ func setupTestServer( } if store := a.EvmKeeper.ReceiptStore(); store != nil { latest := int64(math.MaxInt64) - if err := store.SetLatestVersion(latest); err != nil { + if err := store.SetLatestHeight(latest); err != nil { panic(err) } - _ = store.SetEarliestVersion(1, true) + _ = store.SetEarliestHeight(1) } return TestServer{EVMServer: s, port: port, mockClient: mockClient, app: a} } diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 89c4ad1e0f..0e20465495 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/rpc" sstypes "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/coretypes" ) @@ -22,14 +23,14 @@ type WatermarkManager struct { tmClient rpcclient.Client ctxProvider func(int64) sdk.Context stateStore sstypes.StateStore - receiptStore sstypes.StateStore + receiptStore evmkeeper.ReceiptStore } func NewWatermarkManager( tmClient rpcclient.Client, ctxProvider func(int64) sdk.Context, stateStore sstypes.StateStore, - receiptStore sstypes.StateStore, + receiptStore evmkeeper.ReceiptStore, ) *WatermarkManager { return &WatermarkManager{ tmClient: tmClient, @@ -107,7 +108,7 @@ func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, // Receipt store height participates only in the latest watermark. if m.receiptStore != nil { - if latest := m.receiptStore.GetLatestVersion(); latest > 0 { + if latest := m.receiptStore.LatestHeight(); latest > 0 { setLatest(latest) } } diff --git a/evmrpc/watermark_manager_test.go b/evmrpc/watermark_manager_test.go index 7a0d4ae949..9dc9416ba5 100644 --- a/evmrpc/watermark_manager_test.go +++ b/evmrpc/watermark_manager_test.go @@ -7,6 +7,8 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" @@ -15,6 +17,8 @@ import ( proto "github.com/sei-protocol/sei-chain/sei-db/proto" sstypes "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/bytes" @@ -28,7 +32,7 @@ func TestWatermarksAggregatesSources(t *testing.T) { status: &coretypes.ResultStatus{SyncInfo: coretypes.SyncInfo{LatestBlockHeight: 10, EarliestBlockHeight: 2}}, } stateStore := &fakeStateStore{latest: 8, earliest: 3} - receiptStore := &fakeStateStore{latest: 9, earliest: 3} + receiptStore := &fakeReceiptStore{latest: 9} wm := NewWatermarkManager(tmClient, nil, stateStore, receiptStore) blockEarliest, stateEarliest, latest, err := wm.Watermarks(context.Background()) @@ -233,6 +237,39 @@ func (f *fakeStateStore) RawImport(_ <-chan sstypes.RawSnapshotNode) error func (f *fakeStateStore) Prune(_ int64) error { return nil } func (f *fakeStateStore) Close() error { return nil } +type fakeReceiptStore struct { + latest int64 +} + +func (f *fakeReceiptStore) LatestHeight() int64 { + return f.latest +} + +func (f *fakeReceiptStore) SetLatestHeight(height int64) error { + f.latest = height + return nil +} + +func (f *fakeReceiptStore) SetEarliestHeight(_ int64) error { return nil } + +func (f *fakeReceiptStore) GetReceipt(sdk.Context, common.Hash) (*evmtypes.Receipt, error) { + return nil, errors.New("not found") +} + +func (f *fakeReceiptStore) GetReceiptFromStore(sdk.Context, common.Hash) (*evmtypes.Receipt, error) { + return nil, errors.New("not found") +} + +func (f *fakeReceiptStore) StoreReceipts(sdk.Context, []evmkeeper.ReceiptRecord) error { + return nil +} + +func (f *fakeReceiptStore) FilterLogs(sdk.Context, int64, common.Hash, []common.Hash, filters.FilterCriteria, bool) ([]*ethtypes.Log, error) { + return []*ethtypes.Log{}, nil +} + +func (f *fakeReceiptStore) Close() error { return nil } + type fakeMultiStore struct { earliest int64 latest int64 diff --git a/go.work.sum b/go.work.sum index d518185b6a..85cbbf9cd7 100644 --- a/go.work.sum +++ b/go.work.sum @@ -61,6 +61,7 @@ cloud.google.com/go/gkebackup v1.6.2/go.mod h1:WsTSWqKJkGan1pkp5dS30oxb+Eaa6cLvx cloud.google.com/go/gkeconnect v0.12.0/go.mod h1:zn37LsFiNZxPN4iO7YbUk8l/E14pAJ7KxpoXoxt7Ly0= cloud.google.com/go/gkehub v0.15.2/go.mod h1:8YziTOpwbM8LM3r9cHaOMy2rNgJHXZCrrmGgcau9zbQ= cloud.google.com/go/gkemulticloud v1.4.1/go.mod h1:KRvPYcx53bztNwNInrezdfNF+wwUom8Y3FuJBwhvFpQ= +cloud.google.com/go/grafeas v0.3.11/go.mod h1:dcQyG2+T4tBgG0MvJAh7g2wl/xHV2w+RZIqivwuLjNg= cloud.google.com/go/gsuiteaddons v1.7.2/go.mod h1:GD32J2rN/4APilqZw4JKmwV84+jowYYMkEVwQEYuAWc= cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/iap v1.10.2/go.mod h1:cClgtI09VIfazEK6VMJr6bX8KQfuQ/D3xqX+d0wrUlI= @@ -123,52 +124,123 @@ cloud.google.com/go/vpcaccess v1.8.2/go.mod h1:4yvYKNjlNjvk/ffgZ0PuEhpzNJb8HybSM cloud.google.com/go/webrisk v1.10.2/go.mod h1:c0ODT2+CuKCYjaeHO7b0ni4CUrJ95ScP5UFl9061Qq8= cloud.google.com/go/websecurityscanner v1.7.2/go.mod h1:728wF9yz2VCErfBaACA5px2XSYHQgkK812NmHcUsDXA= cloud.google.com/go/workflows v1.13.2/go.mod h1:l5Wj2Eibqba4BsADIRzPLaevLmIuYF2W+wfFBkRG3vU= +crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/RoaringBitmap/roaring v1.2.2/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= +github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc= +github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k= +github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= +github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew= +github.com/anacrolix/envpprof v1.2.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= +github.com/anacrolix/generics v0.0.0-20220618083756-f99e35403a60/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= +github.com/anacrolix/go-libutp v1.2.0/go.mod h1:RrJ3KcaDcf9Jqp33YL5V/5CBEc6xMc7aJL8wXfuWL50= +github.com/anacrolix/log v0.13.2-0.20221123232138-02e2764801c3/go.mod h1:MD4fn2pYcyhUAQg9SxoGOpTnV/VIdiKVYKZdCbDC97k= +github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc= +github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= +github.com/anacrolix/missinggo/v2 v2.7.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI= +github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc= +github.com/anacrolix/multiless v0.3.0/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4= +github.com/anacrolix/stm v0.4.0/go.mod h1:GCkwqWoAsP7RfLW+jw+Z0ovrt2OO7wRzcTtFYMYY5t8= +github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= +github.com/anacrolix/torrent v1.48.0/go.mod h1:3UtkJ8BnxXDRwvk+eT+uwiZalfFJ8YzAhvxe4QRPSJI= +github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs= +github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bazelbuild/rules_go v0.49.0/go.mod h1:Dhcz716Kqg1RHNWos+N6MlXNkjNP2EwZQ0LukRKJfMs= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og= +github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= +github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/creachadair/command v0.0.0-20220426235536-a748effdf6a1/go.mod h1:bAM+qFQb/KwWyCc9MLC4U1jvn3XyakqP5QRkds5T6cY= +github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/ethereum/c-kzg-4844/v2 v2.1.1/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/googleapis/cloud-bigtable-clients-test v0.0.2/go.mod h1:mk3CrkrouRgtnhID6UZQDK3DrFFa7cYCAJcEmNsHYrY= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/ledgerwatch/interfaces v0.0.0-20230210062155-539b8171d9f0/go.mod h1:ugQv1QllJzBny3cKZKxUrSnykkjkBgm27eQM6dnGAcc= +github.com/ledgerwatch/log/v3 v3.7.0/go.mod h1:J2Jl6zV/58LeA6LTaVVnCGyf1/cYYSEOOLHY4ZN8S2A= +github.com/ledgerwatch/secp256k1 v1.0.0/go.mod h1:SPmqJFciiF/Q0mPt2jVs2dTr/1TZBTIA+kPMmKgBAak= +github.com/ledgerwatch/trackerslist v1.0.0/go.mod h1:pCC+eEw8izNcnBBiSwvIq8kKsxDLInAafSW275jqFrg= +github.com/lispad/go-generics-tools v1.1.0/go.mod h1:2csd1EJljo/gy5qG4khXol7ivCPptNjG5Uv2X8MgK84= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/matryer/moq v0.3.0/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= +github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= +github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE= +github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= +github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= +github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= +github.com/pion/sdp/v3 v3.0.5/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw= +github.com/pion/srtp/v2 v2.0.9/go.mod h1:5TtM9yw6lsH0ppNCehB/EjEUli7VkUgKSPJqWVqbhQ4= github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA= +github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg= +github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw= +github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M= +github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA= github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= @@ -176,33 +248,47 @@ github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOA github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/remyoudompheng/go-dbus v0.0.0-20121104212943-b7232d34b1d5/go.mod h1:+u151txRmLpwxBmpYn9z3d1sdJdjRPQpsXuYeY9jNls= +github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96/go.mod h1:90HvCY7+oHHUKkbeMCiHt1WuFR2/hPJ9QrljDG+v6ls= +github.com/remyoudompheng/go-misc v0.0.0-20190427085024-2d6ac652a50e/go.mod h1:80FQABjoFzZ2M5uEa6FUaJYEmqU2UOKojlFVak1UAwI= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/sei-protocol/go-ethereum v1.15.7-sei-12 h1:3Wj5nU7X0+mKcDho6mwf0leVytQWmNEq6xFv9Wr+HOs= github.com/sei-protocol/go-ethereum v1.15.7-sei-12/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= -github.com/sei-protocol/go-ethereum v1.15.7-sei-14 h1:yJ272fLard1CqL3YvDEB3MFpp9zJ37j3ky4ojqq7NL0= -github.com/sei-protocol/go-ethereum v1.15.7-sei-14/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/torquem-ch/mdbx-go v0.27.5/go.mod h1:T2fsoJDVppxfAPTLd1svUgH1kpPmeXdPESmroSHcL1E= github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= @@ -238,17 +324,20 @@ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:bLYPejkLzwgJuAHlIk1gdPOlx9CUYXLZi2rZxL/ursM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 7a7bc20311..68ddc58e4c 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/tests" "github.com/holiman/uint256" - seidbtypes "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" ibctransferkeeper "github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer/keeper" wasmkeeper "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm/keeper" abci "github.com/tendermint/tendermint/abci/types" @@ -88,7 +87,7 @@ type Keeper struct { Root common.Hash ReplayBlock *ethtypes.Block - receiptStore seidbtypes.StateStore + receiptStore ReceiptStore customPrecompiles map[common.Address]putils.VersionedPrecompiles latestCustomPrecompiles map[common.Address]vm.PrecompiledContract @@ -130,7 +129,7 @@ func (ctx *ReplayChainContext) Config() *params.ChainConfig { } func NewKeeper( - storeKey sdk.StoreKey, transientStoreKey sdk.StoreKey, paramstore paramtypes.Subspace, receiptStateStore seidbtypes.StateStore, + storeKey sdk.StoreKey, transientStoreKey sdk.StoreKey, paramstore paramtypes.Subspace, receiptStore ReceiptStore, bankKeeper bankkeeper.Keeper, accountKeeper *authkeeper.AccountKeeper, stakingKeeper *stakingkeeper.Keeper, transferKeeper ibctransferkeeper.Keeper, wasmKeeper *wasmkeeper.PermissionedKeeper, wasmViewKeeper *wasmkeeper.Keeper, upgradeKeeper *upgradekeeper.Keeper) *Keeper { @@ -152,7 +151,7 @@ func NewKeeper( nonceMx: &sync.RWMutex{}, cachedFeeCollectorAddressMtx: &sync.RWMutex{}, keyToNonce: make(map[tmtypes.TxKey]*AddressNoncePair), - receiptStore: receiptStateStore, + receiptStore: receiptStore, } return k } @@ -213,7 +212,7 @@ func (k *Keeper) BankKeeper() bankkeeper.Keeper { return k.bankKeeper } -func (k *Keeper) ReceiptStore() seidbtypes.StateStore { +func (k *Keeper) ReceiptStore() ReceiptStore { return k.receiptStore } diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index c81209e7de..973ed57a49 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -3,14 +3,11 @@ package keeper import ( "errors" "fmt" - "strings" "time" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - "github.com/sei-protocol/sei-chain/sei-db/proto" - iavl "github.com/sei-protocol/sei-chain/sei-iavl" "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -59,50 +56,18 @@ func (k *Keeper) DeleteTransientReceipt(ctx sdk.Context, txHash common.Hash, txI // Many EVM applications (e.g. MetaMask) relies on being on able to query receipt // by EVM transaction hash (not Sei transaction hash) to function properly. func (k *Keeper) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { - // receipts are immutable, use latest version - lv := k.receiptStore.GetLatestVersion() - - // try persistent store - bz, err := k.receiptStore.Get(types.ReceiptStoreKey, lv, types.ReceiptKey(txHash)) - if err != nil { - return nil, err - } - - if bz == nil { - // try legacy store for older receipts - store := ctx.KVStore(k.storeKey) - bz = store.Get(types.ReceiptKey(txHash)) - if bz == nil { - return nil, errors.New("not found") - } - } - - var r types.Receipt - if err := r.Unmarshal(bz); err != nil { - return nil, err + if k.receiptStore == nil { + return nil, errors.New("receipt store not configured") } - return &r, nil + return k.receiptStore.GetReceipt(ctx, txHash) } // Only used for testing func (k *Keeper) GetReceiptFromReceiptStore(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { - // receipts are immutable, use latest version - lv := k.receiptStore.GetLatestVersion() - - // try persistent store - bz, err := k.receiptStore.Get(types.ReceiptStoreKey, lv, types.ReceiptKey(txHash)) - if err != nil { - return nil, err + if k.receiptStore == nil { + return nil, errors.New("receipt store not configured") } - if bz == nil { - return nil, errors.New("not found") - } - - var r types.Receipt - if err := r.Unmarshal(bz); err != nil { - return nil, err - } - return &r, nil + return k.receiptStore.GetReceiptFromStore(ctx, txHash) } // GetReceiptWithRetry attempts to get a receipt with retries to handle race conditions @@ -177,7 +142,7 @@ func (k *Keeper) flushTransientReceipts(ctx sdk.Context) error { transientReceiptStore := prefix.NewStore(ctx.TransientStore(k.transientStoreKey), types.ReceiptKeyPrefix) iter := transientReceiptStore.Iterator(nil, nil) defer func() { _ = iter.Close() }() - var pairs []*iavl.KVPair + records := make([]ReceiptRecord, 0) // TransientReceiptStore is recreated on commit meaning it will only contain receipts for a single block at a time // and will never flush a subset of block's receipts. @@ -195,36 +160,13 @@ func (k *Keeper) flushTransientReceipts(ctx sdk.Context) error { receipt.CumulativeGasUsed = cumulativeGasUsedPerBlock[receipt.BlockNumber] } - marshalledReceipt, err := receipt.Marshal() - if err != nil { - return err - } - - kvPair := &iavl.KVPair{Key: types.ReceiptKey(types.TransientReceiptKey(iter.Key()).TransactionHash()), Value: marshalledReceipt} - pairs = append(pairs, kvPair) + txHash := types.TransientReceiptKey(iter.Key()).TransactionHash() + records = append(records, ReceiptRecord{TxHash: txHash, Receipt: receipt}) } - ncs := &proto.NamedChangeSet{ - Name: types.ReceiptStoreKey, - Changeset: iavl.ChangeSet{Pairs: pairs}, + if k.receiptStore == nil { + return errors.New("receipt store not configured") } - - var changesets []*proto.NamedChangeSet - changesets = append(changesets, ncs) - // Genesis and some unit tests execute at block height 0. Async writes - // rely on a positive version to avoid regressions in the underlying - // state store metadata, so fall back to a synchronous apply in that case. - if ctx.BlockHeight() == 0 { - return k.receiptStore.ApplyChangesetSync(ctx.BlockHeight(), changesets) - } - err := k.receiptStore.ApplyChangesetAsync(ctx.BlockHeight(), changesets) - if err != nil { - if !strings.Contains(err.Error(), "not implemented") { // for tests - return err - } - // fallback to synchronous apply for stores that do not support async writes - return k.receiptStore.ApplyChangesetSync(ctx.BlockHeight(), []*proto.NamedChangeSet{ncs}) - } - return nil + return k.receiptStore.StoreReceipts(ctx, records) } // MigrateLegacyReceiptsBatch moves up to batchSize receipts from the legacy KV store From 9d38a57aa6c51c4ada9830a2aa0aedf8bcbf7e8a Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 14 Jan 2026 15:38:51 -0500 Subject: [PATCH 02/17] fix --- app/app.go | 5 ++--- app/test_helpers.go | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/app.go b/app/app.go index c8802e260a..f73c37be77 100644 --- a/app/app.go +++ b/app/app.go @@ -98,7 +98,6 @@ import ( evmrpcconfig "github.com/sei-protocol/sei-chain/evmrpc/config" "github.com/sei-protocol/sei-chain/precompiles" putils "github.com/sei-protocol/sei-chain/precompiles/utils" - "github.com/sei-protocol/sei-chain/sei-db/state_db/ss" seidb "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" "github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer" ibctransferkeeper "github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer/keeper" @@ -620,11 +619,11 @@ func New( ssConfig.DBDirectory = receiptStorePath ssConfig.KeepLastVersion = false if app.receiptStore == nil { - receiptStateStore, err := ss.NewStateStore(logger, receiptStorePath, ssConfig) + receiptStore, err := evmkeeper.NewReceiptStore(logger, ssConfig, keys[evmtypes.StoreKey]) if err != nil { panic(fmt.Sprintf("error while creating receipt store: %s", err)) } - app.receiptStore = evmkeeper.NewReceiptStore(receiptStateStore, keys[evmtypes.StoreKey]) + app.receiptStore = receiptStore } app.EvmKeeper = *evmkeeper.NewKeeper(keys[evmtypes.StoreKey], tkeys[evmtypes.TransientStoreKey], app.GetSubspace(evmtypes.ModuleName), app.receiptStore, app.BankKeeper, diff --git a/app/test_helpers.go b/app/test_helpers.go index 0a140e6c2c..1aab2aa5ea 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -14,7 +14,6 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" ssconfig "github.com/sei-protocol/sei-chain/sei-db/config" - "github.com/sei-protocol/sei-chain/sei-db/state_db/ss" "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm" wasmkeeper "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm/keeper" evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" @@ -263,11 +262,11 @@ func setupReceiptStore(storeKey sdk.StoreKey) (evmkeeper.ReceiptStore, error) { ssConfig.KeepRecent = 0 // No min retain blocks in test ssConfig.DBDirectory = tempDir ssConfig.KeepLastVersion = false - receiptStateStore, err := ss.NewStateStore(log.NewNopLogger(), tempDir, ssConfig) + receiptStore, err := evmkeeper.NewReceiptStore(log.NewNopLogger(), ssConfig, storeKey) if err != nil { return nil, err } - return evmkeeper.NewReceiptStore(receiptStateStore, storeKey), nil + return receiptStore, nil } func SetupWithDefaultHome(isCheckTx bool, enableEVMCustomPrecompiles bool, overrideWasmGasMultiplier bool, baseAppOptions ...func(*bam.BaseApp)) (res *App) { From 8717c7c337e53b3ea64c995f499b1a95596a4c79 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 14 Jan 2026 15:39:59 -0500 Subject: [PATCH 03/17] fix --- x/evm/keeper/receipt_store.go | 452 ++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 x/evm/keeper/receipt_store.go diff --git a/x/evm/keeper/receipt_store.go b/x/evm/keeper/receipt_store.go new file mode 100644 index 0000000000..5e22c81f1f --- /dev/null +++ b/x/evm/keeper/receipt_store.go @@ -0,0 +1,452 @@ +package keeper + +import ( + "errors" + "fmt" + "math/rand" + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/sei-protocol/sei-chain/sei-db/changelog/changelog" + dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" + "github.com/sei-protocol/sei-chain/sei-db/common/utils" + dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" + "github.com/sei-protocol/sei-chain/sei-db/db_engine/pebbledb/mvcc" + "github.com/sei-protocol/sei-chain/sei-db/proto" + iavl "github.com/sei-protocol/sei-chain/sei-iavl" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +// ReceiptStore exposes receipt-specific operations without leaking the StateStore interface. +type ReceiptStore interface { + LatestHeight() int64 + SetLatestHeight(height int64) error + SetEarliestHeight(height int64) error + GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) + GetReceiptFromStore(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) + StoreReceipts(ctx sdk.Context, receipts []ReceiptRecord) error + FilterLogs(ctx sdk.Context, blockHeight int64, blockHash common.Hash, txHashes []common.Hash, crit filters.FilterCriteria, applyExactMatch bool) ([]*ethtypes.Log, error) + Close() error +} + +type ReceiptRecord struct { + TxHash common.Hash + Receipt *types.Receipt +} + +type receiptStore struct { + db *mvcc.Database + storeKey sdk.StoreKey +} + +func NewReceiptStore(log dbLogger.Logger, config dbconfig.StateStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { + if log == nil { + log = dbLogger.NewNopLogger() + } + if config.DBDirectory == "" { + return nil, errors.New("receipt store db directory not configured") + } + if config.Backend != "" && config.Backend != "pebbledb" { + return nil, fmt.Errorf("unsupported receipt store backend: %s", config.Backend) + } + + db, err := mvcc.OpenDB(config.DBDirectory, config) + if err != nil { + return nil, err + } + if err := recoverReceiptStore(log, utils.GetChangelogPath(config.DBDirectory), db); err != nil { + _ = db.Close() + return nil, err + } + startReceiptPruning(log, db, int64(config.KeepRecent), int64(config.PruneIntervalSeconds)) + return &receiptStore{ + db: db, + storeKey: storeKey, + }, nil +} + +func (s *receiptStore) LatestHeight() int64 { + if s == nil || s.db == nil { + return 0 + } + return s.db.GetLatestVersion() +} + +func (s *receiptStore) SetLatestHeight(height int64) error { + if s == nil || s.db == nil { + return errors.New("receipt store not configured") + } + return s.db.SetLatestVersion(height) +} + +func (s *receiptStore) SetEarliestHeight(height int64) error { + if s == nil || s.db == nil { + return errors.New("receipt store not configured") + } + return s.db.SetEarliestVersion(height, true) +} + +func (s *receiptStore) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { + if s == nil || s.db == nil { + return nil, errors.New("receipt store not configured") + } + + // receipts are immutable, use latest version + lv := s.db.GetLatestVersion() + + // try persistent store + bz, err := s.db.Get(types.ReceiptStoreKey, lv, types.ReceiptKey(txHash)) + if err != nil { + return nil, err + } + + if bz == nil { + // try legacy store for older receipts + store := ctx.KVStore(s.storeKey) + bz = store.Get(types.ReceiptKey(txHash)) + if bz == nil { + return nil, errors.New("not found") + } + } + + var r types.Receipt + if err := r.Unmarshal(bz); err != nil { + return nil, err + } + return &r, nil +} + +// Only used for testing. +func (s *receiptStore) GetReceiptFromStore(_ sdk.Context, txHash common.Hash) (*types.Receipt, error) { + if s == nil || s.db == nil { + return nil, errors.New("receipt store not configured") + } + + // receipts are immutable, use latest version + lv := s.db.GetLatestVersion() + + // try persistent store + bz, err := s.db.Get(types.ReceiptStoreKey, lv, types.ReceiptKey(txHash)) + if err != nil { + return nil, err + } + if bz == nil { + return nil, errors.New("not found") + } + + var r types.Receipt + if err := r.Unmarshal(bz); err != nil { + return nil, err + } + return &r, nil +} + +func (s *receiptStore) StoreReceipts(ctx sdk.Context, receipts []ReceiptRecord) error { + if s == nil || s.db == nil { + return errors.New("receipt store not configured") + } + + pairs := make([]*iavl.KVPair, 0, len(receipts)) + for _, record := range receipts { + if record.Receipt == nil { + continue + } + marshalledReceipt, err := record.Receipt.Marshal() + if err != nil { + return err + } + kvPair := &iavl.KVPair{ + Key: types.ReceiptKey(record.TxHash), + Value: marshalledReceipt, + } + pairs = append(pairs, kvPair) + } + + ncs := &proto.NamedChangeSet{ + Name: types.ReceiptStoreKey, + Changeset: iavl.ChangeSet{Pairs: pairs}, + } + + // Genesis and some unit tests execute at block height 0. Async writes + // rely on a positive version to avoid regressions in the underlying + // state store metadata, so fall back to a synchronous apply in that case. + if ctx.BlockHeight() == 0 { + return s.db.ApplyChangesetSync(ctx.BlockHeight(), []*proto.NamedChangeSet{ncs}) + } + + err := s.db.ApplyChangesetAsync(ctx.BlockHeight(), []*proto.NamedChangeSet{ncs}) + if err != nil { + if !strings.Contains(err.Error(), "not implemented") { // for tests + return err + } + // fallback to synchronous apply for stores that do not support async writes + return s.db.ApplyChangesetSync(ctx.BlockHeight(), []*proto.NamedChangeSet{ncs}) + } + return nil +} + +func (s *receiptStore) FilterLogs(ctx sdk.Context, blockHeight int64, blockHash common.Hash, txHashes []common.Hash, crit filters.FilterCriteria, applyExactMatch bool) ([]*ethtypes.Log, error) { + if s == nil || s.db == nil { + return nil, errors.New("receipt store not configured") + } + if len(txHashes) == 0 { + return []*ethtypes.Log{}, nil + } + + hasFilters := len(crit.Addresses) != 0 || len(crit.Topics) != 0 + var filterIndexes [][]bloomIndexes + if hasFilters { + filterIndexes = encodeFilters(crit.Addresses, crit.Topics) + } + + logs := make([]*ethtypes.Log, 0) + totalLogs := uint(0) + evmTxIndex := 0 + + for _, txHash := range txHashes { + receipt, err := s.GetReceipt(ctx, txHash) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("collectLogs: unable to find receipt for hash %s", txHash.Hex())) + continue + } + + txLogs := GetLogsForTx(receipt, totalLogs) + + if hasFilters { + if len(receipt.LogsBloom) == 0 || matchFilters(ethtypes.Bloom(receipt.LogsBloom), filterIndexes) { + if applyExactMatch { + for _, log := range txLogs { + log.TxIndex = uint(evmTxIndex) //nolint:gosec + log.BlockNumber = uint64(blockHeight) //nolint:gosec + log.BlockHash = blockHash + if isLogExactMatch(log, crit) { + logs = append(logs, log) + } + } + } else { + for _, log := range txLogs { + log.TxIndex = uint(evmTxIndex) //nolint:gosec + log.BlockNumber = uint64(blockHeight) //nolint:gosec + log.BlockHash = blockHash + logs = append(logs, log) + } + } + } + } else { + for _, log := range txLogs { + log.TxIndex = uint(evmTxIndex) //nolint:gosec + log.BlockNumber = uint64(blockHeight) //nolint:gosec + log.BlockHash = blockHash + logs = append(logs, log) + } + } + + totalLogs += uint(len(txLogs)) + evmTxIndex++ + } + + return logs, nil +} + +func (s *receiptStore) Close() error { + if s == nil || s.db == nil { + return nil + } + return s.db.Close() +} + +func recoverReceiptStore(log dbLogger.Logger, changelogPath string, db *mvcc.Database) error { + ssLatestVersion := db.GetLatestVersion() + log.Info(fmt.Sprintf("Recovering from changelog %s with latest receipt version %d", changelogPath, ssLatestVersion)) + streamHandler, err := changelog.NewStream(log, changelogPath, changelog.Config{}) + if err != nil { + return err + } + firstOffset, errFirst := streamHandler.FirstOffset() + if firstOffset <= 0 || errFirst != nil { + return nil + } + lastOffset, errLast := streamHandler.LastOffset() + if lastOffset <= 0 || errLast != nil { + return nil + } + lastEntry, errRead := streamHandler.ReadAt(lastOffset) + if errRead != nil { + return errRead + } + // Look backward to find where we should start replay from + curVersion := lastEntry.Version + curOffset := lastOffset + if ssLatestVersion > 0 { + for curVersion > ssLatestVersion && curOffset > firstOffset { + curOffset-- + curEntry, errRead := streamHandler.ReadAt(curOffset) + if errRead != nil { + return errRead + } + curVersion = curEntry.Version + } + } else { + // Fresh store (or no applied versions) - start from the first offset + curOffset = firstOffset + } + // Replay from the offset where the version is larger than SS store latest version + targetStartOffset := curOffset + log.Info(fmt.Sprintf("Start replaying changelog to recover ReceiptStore from offset %d to %d", targetStartOffset, lastOffset)) + if targetStartOffset < lastOffset { + return streamHandler.Replay(targetStartOffset, lastOffset, func(index uint64, entry proto.ChangelogEntry) error { + // commit to state store + if err := db.ApplyChangesetSync(entry.Version, entry.Changesets); err != nil { + return err + } + if err := db.SetLatestVersion(entry.Version); err != nil { + return err + } + return nil + }) + } + return nil +} + +func startReceiptPruning(log dbLogger.Logger, db *mvcc.Database, keepRecent int64, pruneInterval int64) { + if keepRecent <= 0 || pruneInterval <= 0 { + return + } + go func() { + for { + pruneStartTime := time.Now() + latestVersion := db.GetLatestVersion() + pruneVersion := latestVersion - keepRecent + if pruneVersion > 0 { + // prune all versions up to and including the pruneVersion + if err := db.Prune(pruneVersion); err != nil { + log.Error("failed to prune receipt store till", "version", pruneVersion, "err", err) + } + log.Info(fmt.Sprintf("Pruned receipt store till version %d took %s\n", pruneVersion, time.Since(pruneStartTime))) + } + + // Generate a random percentage (between 0% and 100%) of the fixed interval as a delay + randomPercentage := rand.Float64() + randomDelay := int64(float64(pruneInterval) * randomPercentage) + time.Sleep(time.Duration(pruneInterval+randomDelay) * time.Second) + } + }() +} + +var receiptStoreBitMasks = [8]uint8{1, 2, 4, 8, 16, 32, 64, 128} + +type bloomIndexes [3]uint + +func calcBloomIndexes(b []byte) bloomIndexes { + b = crypto.Keccak256(b) + + var idxs bloomIndexes + for i := 0; i < len(idxs); i++ { + idxs[i] = (uint(b[2*i])<<8)&2047 + uint(b[2*i+1]) + } + return idxs +} + +// res: AND on outer level, OR on mid level, AND on inner level (i.e. all 3 bits) +func encodeFilters(addresses []common.Address, topics [][]common.Hash) (res [][]bloomIndexes) { + filters := make([][][]byte, 1+len(topics)) + if len(addresses) > 0 { + filter := make([][]byte, len(addresses)) + for i, address := range addresses { + filter[i] = address.Bytes() + } + filters = append(filters, filter) + } + for _, topicList := range topics { + filter := make([][]byte, len(topicList)) + for i, topic := range topicList { + filter[i] = topic.Bytes() + } + filters = append(filters, filter) + } + for _, filter := range filters { + if len(filter) == 0 { + continue + } + bloomBits := make([]bloomIndexes, len(filter)) + for i, clause := range filter { + if clause == nil { + bloomBits = nil + break + } + bloomBits[i] = calcBloomIndexes(clause) + } + if bloomBits != nil { + res = append(res, bloomBits) + } + } + return +} + +func matchFilters(bloom ethtypes.Bloom, filters [][]bloomIndexes) bool { + for _, filter := range filters { + if !matchFilter(bloom, filter) { + return false + } + } + return true +} + +func matchFilter(bloom ethtypes.Bloom, filter []bloomIndexes) bool { + for _, possibility := range filter { + if matchBloomIndexes(bloom, possibility) { + return true + } + } + return false +} + +func matchBloomIndexes(bloom ethtypes.Bloom, idx bloomIndexes) bool { + for _, bit := range idx { + // big endian + whichByte := bloom[ethtypes.BloomByteLength-1-bit/8] + mask := receiptStoreBitMasks[bit%8] + if whichByte&mask == 0 { + return false + } + } + return true +} + +func isLogExactMatch(log *ethtypes.Log, crit filters.FilterCriteria) bool { + addrMatch := len(crit.Addresses) == 0 + for _, addrFilter := range crit.Addresses { + if log.Address == addrFilter { + addrMatch = true + break + } + } + return addrMatch && matchTopics(crit.Topics, log.Topics) +} + +func matchTopics(topics [][]common.Hash, eventTopics []common.Hash) bool { + for i, topicList := range topics { + if len(topicList) == 0 { + continue + } + if i >= len(eventTopics) { + return false + } + matched := false + for _, topic := range topicList { + if topic == eventTopics[i] { + matched = true + break + } + } + if !matched { + return false + } + } + return true +} From 8f8296d2405db36e35e3bd0894c7553b308d4e20 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 14 Jan 2026 15:59:36 -0500 Subject: [PATCH 04/17] fix --- evmrpc/simulate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index a2985338d5..f036da53ef 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -20,8 +20,8 @@ import ( "github.com/sei-protocol/sei-chain/app/legacyabci" "github.com/sei-protocol/sei-chain/evmrpc" "github.com/sei-protocol/sei-chain/example/contracts/simplestorage" - evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/rpc/client/mock" From dc14f13bb1066a49864f2d730fb33252f5dfb39b Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 14 Jan 2026 16:21:52 -0500 Subject: [PATCH 05/17] fix --- app/app.go | 5 ++-- app/test_helpers.go | 6 ++--- evmrpc/simulate_test.go | 4 +-- evmrpc/watermark_manager.go | 6 ++--- evmrpc/watermark_manager_test.go | 4 +-- .../ledger_db/receipt}/receipt_store.go | 25 ++++++++++++++++--- x/evm/keeper/keeper.go | 7 +++--- x/evm/keeper/receipt.go | 15 +++++------ 8 files changed, 46 insertions(+), 26 deletions(-) rename {x/evm/keeper => sei-db/ledger_db/receipt}/receipt_store.go (93%) diff --git a/app/app.go b/app/app.go index f73c37be77..43f44d857b 100644 --- a/app/app.go +++ b/app/app.go @@ -152,6 +152,7 @@ import ( // unnamed import of statik for openapi/swagger UI support _ "github.com/sei-protocol/sei-chain/docs/swagger" ssconfig "github.com/sei-protocol/sei-chain/sei-db/config" + receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" ) // this line is used by starport scaffolding # stargate/wasm/app/enabledProposals @@ -377,7 +378,7 @@ type App struct { genesisImportConfig genesistypes.GenesisImportConfig stateStore seidb.StateStore - receiptStore evmkeeper.ReceiptStore + receiptStore receipt.ReceiptStore forkInitializer func(sdk.Context) @@ -619,7 +620,7 @@ func New( ssConfig.DBDirectory = receiptStorePath ssConfig.KeepLastVersion = false if app.receiptStore == nil { - receiptStore, err := evmkeeper.NewReceiptStore(logger, ssConfig, keys[evmtypes.StoreKey]) + receiptStore, err := receipt.NewReceiptStore(logger, ssConfig, keys[evmtypes.StoreKey]) if err != nil { panic(fmt.Sprintf("error while creating receipt store: %s", err)) } diff --git a/app/test_helpers.go b/app/test_helpers.go index 1aab2aa5ea..e295432b15 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -14,9 +14,9 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" ssconfig "github.com/sei-protocol/sei-chain/sei-db/config" + receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm" wasmkeeper "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm/keeper" - evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/suite" "github.com/tendermint/tendermint/config" @@ -247,7 +247,7 @@ func (s *TestWrapper) EndBlock() { legacyabci.EndBlock(s.Ctx, s.Ctx.BlockHeight(), 0, s.App.EndBlockKeepers) } -func setupReceiptStore(storeKey sdk.StoreKey) (evmkeeper.ReceiptStore, error) { +func setupReceiptStore(storeKey sdk.StoreKey) (receipt.ReceiptStore, error) { // Create a unique temporary directory per test process to avoid Pebble DB lock conflicts baseDir := filepath.Join(os.TempDir(), "sei-testing") if err := os.MkdirAll(baseDir, 0o750); err != nil { @@ -262,7 +262,7 @@ func setupReceiptStore(storeKey sdk.StoreKey) (evmkeeper.ReceiptStore, error) { ssConfig.KeepRecent = 0 // No min retain blocks in test ssConfig.DBDirectory = tempDir ssConfig.KeepLastVersion = false - receiptStore, err := evmkeeper.NewReceiptStore(log.NewNopLogger(), ssConfig, storeKey) + receiptStore, err := receipt.NewReceiptStore(log.NewNopLogger(), ssConfig, storeKey) if err != nil { return nil, err } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index f036da53ef..a6bffbca99 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -20,8 +20,8 @@ import ( "github.com/sei-protocol/sei-chain/app/legacyabci" "github.com/sei-protocol/sei-chain/evmrpc" "github.com/sei-protocol/sei-chain/example/contracts/simplestorage" + receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" - evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/rpc/client/mock" @@ -40,7 +40,7 @@ type bcFailClient struct { first bool } -func primeReceiptStore(t *testing.T, store evmkeeper.ReceiptStore, latest int64) { +func primeReceiptStore(t *testing.T, store receipt.ReceiptStore, latest int64) { t.Helper() if store == nil { return diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 0e20465495..5cb015afce 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -7,8 +7,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/rpc" + receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" sstypes "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" - evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/coretypes" ) @@ -23,14 +23,14 @@ type WatermarkManager struct { tmClient rpcclient.Client ctxProvider func(int64) sdk.Context stateStore sstypes.StateStore - receiptStore evmkeeper.ReceiptStore + receiptStore receipt.ReceiptStore } func NewWatermarkManager( tmClient rpcclient.Client, ctxProvider func(int64) sdk.Context, stateStore sstypes.StateStore, - receiptStore evmkeeper.ReceiptStore, + receiptStore receipt.ReceiptStore, ) *WatermarkManager { return &WatermarkManager{ tmClient: tmClient, diff --git a/evmrpc/watermark_manager_test.go b/evmrpc/watermark_manager_test.go index 9dc9416ba5..dbb8f726cd 100644 --- a/evmrpc/watermark_manager_test.go +++ b/evmrpc/watermark_manager_test.go @@ -15,9 +15,9 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" proto "github.com/sei-protocol/sei-chain/sei-db/proto" sstypes "github.com/sei-protocol/sei-chain/sei-db/state_db/ss/types" - evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" abci "github.com/tendermint/tendermint/abci/types" @@ -260,7 +260,7 @@ func (f *fakeReceiptStore) GetReceiptFromStore(sdk.Context, common.Hash) (*evmty return nil, errors.New("not found") } -func (f *fakeReceiptStore) StoreReceipts(sdk.Context, []evmkeeper.ReceiptRecord) error { +func (f *fakeReceiptStore) StoreReceipts(sdk.Context, []receipt.ReceiptRecord) error { return nil } diff --git a/x/evm/keeper/receipt_store.go b/sei-db/ledger_db/receipt/receipt_store.go similarity index 93% rename from x/evm/keeper/receipt_store.go rename to sei-db/ledger_db/receipt/receipt_store.go index 5e22c81f1f..9639b64d58 100644 --- a/x/evm/keeper/receipt_store.go +++ b/sei-db/ledger_db/receipt/receipt_store.go @@ -1,4 +1,4 @@ -package keeper +package receipt import ( "errors" @@ -14,11 +14,12 @@ import ( "github.com/ethereum/go-ethereum/eth/filters" "github.com/sei-protocol/sei-chain/sei-db/changelog/changelog" dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" - "github.com/sei-protocol/sei-chain/sei-db/common/utils" + dbutils "github.com/sei-protocol/sei-chain/sei-db/common/utils" dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/db_engine/pebbledb/mvcc" "github.com/sei-protocol/sei-chain/sei-db/proto" iavl "github.com/sei-protocol/sei-chain/sei-iavl" + "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -59,7 +60,7 @@ func NewReceiptStore(log dbLogger.Logger, config dbconfig.StateStoreConfig, stor if err != nil { return nil, err } - if err := recoverReceiptStore(log, utils.GetChangelogPath(config.DBDirectory), db); err != nil { + if err := recoverReceiptStore(log, dbutils.GetChangelogPath(config.DBDirectory), db); err != nil { _ = db.Close() return nil, err } @@ -215,7 +216,7 @@ func (s *receiptStore) FilterLogs(ctx sdk.Context, blockHeight int64, blockHash continue } - txLogs := GetLogsForTx(receipt, totalLogs) + txLogs := getLogsForTx(receipt, totalLogs) if hasFilters { if len(receipt.LogsBloom) == 0 || matchFilters(ethtypes.Bloom(receipt.LogsBloom), filterIndexes) { @@ -450,3 +451,19 @@ func matchTopics(topics [][]common.Hash, eventTopics []common.Hash) bool { } return true } + +func getLogsForTx(receipt *types.Receipt, logStartIndex uint) []*ethtypes.Log { + return utils.Map(receipt.Logs, func(l *types.Log) *ethtypes.Log { return convertLog(l, receipt, logStartIndex) }) +} + +func convertLog(l *types.Log, receipt *types.Receipt, logStartIndex uint) *ethtypes.Log { + return ðtypes.Log{ + Address: common.HexToAddress(l.Address), + Topics: utils.Map(l.Topics, common.HexToHash), + Data: l.Data, + BlockNumber: receipt.BlockNumber, + TxHash: common.HexToHash(receipt.TxHashHex), + TxIndex: uint(receipt.TransactionIndex), + Index: uint(l.Index) + logStartIndex, + } +} diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 68ddc58e4c..8521c14612 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/tests" "github.com/holiman/uint256" + receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" ibctransferkeeper "github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer/keeper" wasmkeeper "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm/keeper" abci "github.com/tendermint/tendermint/abci/types" @@ -87,7 +88,7 @@ type Keeper struct { Root common.Hash ReplayBlock *ethtypes.Block - receiptStore ReceiptStore + receiptStore receipt.ReceiptStore customPrecompiles map[common.Address]putils.VersionedPrecompiles latestCustomPrecompiles map[common.Address]vm.PrecompiledContract @@ -129,7 +130,7 @@ func (ctx *ReplayChainContext) Config() *params.ChainConfig { } func NewKeeper( - storeKey sdk.StoreKey, transientStoreKey sdk.StoreKey, paramstore paramtypes.Subspace, receiptStore ReceiptStore, + storeKey sdk.StoreKey, transientStoreKey sdk.StoreKey, paramstore paramtypes.Subspace, receiptStore receipt.ReceiptStore, bankKeeper bankkeeper.Keeper, accountKeeper *authkeeper.AccountKeeper, stakingKeeper *stakingkeeper.Keeper, transferKeeper ibctransferkeeper.Keeper, wasmKeeper *wasmkeeper.PermissionedKeeper, wasmViewKeeper *wasmkeeper.Keeper, upgradeKeeper *upgradekeeper.Keeper) *Keeper { @@ -212,7 +213,7 @@ func (k *Keeper) BankKeeper() bankkeeper.Keeper { return k.bankKeeper } -func (k *Keeper) ReceiptStore() ReceiptStore { +func (k *Keeper) ReceiptStore() receipt.ReceiptStore { return k.receiptStore } diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index 973ed57a49..316f1f45d0 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -142,7 +143,7 @@ func (k *Keeper) flushTransientReceipts(ctx sdk.Context) error { transientReceiptStore := prefix.NewStore(ctx.TransientStore(k.transientStoreKey), types.ReceiptKeyPrefix) iter := transientReceiptStore.Iterator(nil, nil) defer func() { _ = iter.Close() }() - records := make([]ReceiptRecord, 0) + records := make([]receipt.ReceiptRecord, 0) // TransientReceiptStore is recreated on commit meaning it will only contain receipts for a single block at a time // and will never flush a subset of block's receipts. @@ -150,18 +151,18 @@ func (k *Keeper) flushTransientReceipts(ctx sdk.Context) error { // and we need to account for that. cumulativeGasUsedPerBlock := make(map[uint64]uint64) for ; iter.Valid(); iter.Next() { - receipt := &types.Receipt{} - if err := receipt.Unmarshal(iter.Value()); err != nil { + rcpt := &types.Receipt{} + if err := rcpt.Unmarshal(iter.Value()); err != nil { return err } - if !isLegacyReceipt(ctx, receipt) { - cumulativeGasUsedPerBlock[receipt.BlockNumber] += receipt.GasUsed - receipt.CumulativeGasUsed = cumulativeGasUsedPerBlock[receipt.BlockNumber] + if !isLegacyReceipt(ctx, rcpt) { + cumulativeGasUsedPerBlock[rcpt.BlockNumber] += rcpt.GasUsed + rcpt.CumulativeGasUsed = cumulativeGasUsedPerBlock[rcpt.BlockNumber] } txHash := types.TransientReceiptKey(iter.Key()).TransactionHash() - records = append(records, ReceiptRecord{TxHash: txHash, Receipt: receipt}) + records = append(records, receipt.ReceiptRecord{TxHash: txHash, Receipt: rcpt}) } if k.receiptStore == nil { return errors.New("receipt store not configured") From bd21e2ba833edcdfb47538dc62009db2888e92ce Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Thu, 15 Jan 2026 13:59:13 -0500 Subject: [PATCH 06/17] fix --- sei-db/ledger_db/receipt/export_test.go | 29 ++ .../ledger_db/receipt/receipt_store_test.go | 283 ++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 sei-db/ledger_db/receipt/export_test.go create mode 100644 sei-db/ledger_db/receipt/receipt_store_test.go diff --git a/sei-db/ledger_db/receipt/export_test.go b/sei-db/ledger_db/receipt/export_test.go new file mode 100644 index 0000000000..469950cdcf --- /dev/null +++ b/sei-db/ledger_db/receipt/export_test.go @@ -0,0 +1,29 @@ +package receipt + +import ( + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" + "github.com/sei-protocol/sei-chain/sei-db/db_engine/pebbledb/mvcc" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +// NilReceiptStore returns a nil *receiptStore for testing nil receiver behavior. +func NilReceiptStore() ReceiptStore { + return (*receiptStore)(nil) +} + +// MatchTopics exposes matchTopics for testing. +func MatchTopics(topics [][]common.Hash, eventTopics []common.Hash) bool { + return matchTopics(topics, eventTopics) +} + +// RecoverReceiptStore exposes recoverReceiptStore for testing. +func RecoverReceiptStore(log dbLogger.Logger, changelogPath string, db *mvcc.Database) error { + return recoverReceiptStore(log, changelogPath, db) +} + +// GetLogsForTx exposes getLogsForTx for testing. +func GetLogsForTx(receipt *types.Receipt, logStartIndex uint) []*ethtypes.Log { + return getLogsForTx(receipt, logStartIndex) +} diff --git a/sei-db/ledger_db/receipt/receipt_store_test.go b/sei-db/ledger_db/receipt/receipt_store_test.go new file mode 100644 index 0000000000..98508d92df --- /dev/null +++ b/sei-db/ledger_db/receipt/receipt_store_test.go @@ -0,0 +1,283 @@ +package receipt_test + +import ( + "os" + "testing" + "time" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/sei-protocol/sei-chain/sei-db/changelog/changelog" + dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" + dbutils "github.com/sei-protocol/sei-chain/sei-db/common/utils" + dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" + "github.com/sei-protocol/sei-chain/sei-db/db_engine/pebbledb/mvcc" + "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" + "github.com/sei-protocol/sei-chain/sei-db/proto" + iavl "github.com/sei-protocol/sei-chain/sei-iavl" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func setupReceiptStore(t *testing.T) (receipt.ReceiptStore, sdk.Context, storetypes.StoreKey) { + t.Helper() + storeKey := storetypes.NewKVStoreKey("evm") + tkey := storetypes.NewTransientStoreKey("evm_transient") + ctx := testutil.DefaultContext(storeKey, tkey).WithBlockHeight(0) + cfg := dbconfig.DefaultStateStoreConfig() + cfg.DBDirectory = t.TempDir() + cfg.KeepRecent = 0 + cfg.KeepLastVersion = false + store, err := receipt.NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) + require.NoError(t, err) + t.Cleanup(func() { _ = store.Close() }) + return store, ctx, storeKey +} + +func makeReceipt(txHash common.Hash, addr common.Address, topics []common.Hash, logIndex uint32) *types.Receipt { + topicHex := make([]string, 0, len(topics)) + for _, topic := range topics { + topicHex = append(topicHex, topic.Hex()) + } + return &types.Receipt{ + TxHashHex: txHash.Hex(), + BlockNumber: 1, + TransactionIndex: logIndex, + Logs: []*types.Log{ + { + Address: addr.Hex(), + Topics: topicHex, + Data: []byte{0x1}, + Index: logIndex, + }, + }, + } +} + +func withBloom(r *types.Receipt) *types.Receipt { + logs := receipt.GetLogsForTx(r, 0) + bloom := ethtypes.CreateBloom(ðtypes.Receipt{Logs: logs}) + r.LogsBloom = bloom.Bytes() + return r +} + +func TestNewReceiptStoreConfigErrors(t *testing.T) { + storeKey := storetypes.NewKVStoreKey("evm") + cfg := dbconfig.DefaultStateStoreConfig() + cfg.DBDirectory = "" + store, err := receipt.NewReceiptStore(nil, cfg, storeKey) + require.Error(t, err) + require.Nil(t, store) + + cfg.DBDirectory = t.TempDir() + cfg.Backend = "rocksdb" + store, err = receipt.NewReceiptStore(nil, cfg, storeKey) + require.Error(t, err) + require.Nil(t, store) + + cfg.Backend = "pebbledb" + store, err = receipt.NewReceiptStore(nil, cfg, storeKey) + require.NoError(t, err) + require.NotNil(t, store) + require.NoError(t, store.Close()) +} + +func TestReceiptStoreNilReceiver(t *testing.T) { + store := receipt.NilReceiptStore() + require.Equal(t, int64(0), store.LatestHeight()) + require.Error(t, store.SetLatestHeight(1)) + require.Error(t, store.SetEarliestHeight(1)) + _, err := store.GetReceipt(sdk.Context{}, common.Hash{}) + require.Error(t, err) + _, err = store.GetReceiptFromStore(sdk.Context{}, common.Hash{}) + require.Error(t, err) + require.Error(t, store.StoreReceipts(sdk.Context{}, nil)) + _, err = store.FilterLogs(sdk.Context{}, 1, common.Hash{}, nil, filters.FilterCriteria{}, false) + require.Error(t, err) + require.NoError(t, store.Close()) +} + +func TestStoreReceiptsAndGet(t *testing.T) { + store, ctx, _ := setupReceiptStore(t) + txHash := common.HexToHash("0x1") + addr := common.HexToAddress("0x1") + topic := common.HexToHash("0x2") + r := makeReceipt(txHash, addr, []common.Hash{topic}, 0) + + err := store.StoreReceipts(ctx.WithBlockHeight(0), []receipt.ReceiptRecord{ + {TxHash: txHash, Receipt: r}, + {TxHash: txHash}, + }) + require.NoError(t, err) + + got, err := store.GetReceipt(ctx, txHash) + require.NoError(t, err) + require.Equal(t, r.TxHashHex, got.TxHashHex) + + got, err = store.GetReceiptFromStore(ctx, txHash) + require.NoError(t, err) + require.Equal(t, r.TxHashHex, got.TxHashHex) + + _, err = store.GetReceiptFromStore(ctx, common.HexToHash("0x3")) + require.Error(t, err) + + require.GreaterOrEqual(t, store.LatestHeight(), int64(1)) + require.NoError(t, store.SetLatestHeight(10)) + require.Equal(t, int64(10), store.LatestHeight()) + require.NoError(t, store.SetEarliestHeight(1)) +} + +func TestReceiptStoreLegacyFallback(t *testing.T) { + store, ctx, storeKey := setupReceiptStore(t) + txHash := common.HexToHash("0x4") + r := makeReceipt(txHash, common.HexToAddress("0x2"), []common.Hash{common.HexToHash("0x5")}, 0) + bz, err := r.Marshal() + require.NoError(t, err) + + ctx.KVStore(storeKey).Set(types.ReceiptKey(txHash), bz) + + got, err := store.GetReceipt(ctx, txHash) + require.NoError(t, err) + require.Equal(t, r.TxHashHex, got.TxHashHex) + + _, err = store.GetReceiptFromStore(ctx, txHash) + require.Error(t, err) +} + +func TestStoreReceiptsAsync(t *testing.T) { + store, ctx, _ := setupReceiptStore(t) + txHash := common.HexToHash("0x6") + r := makeReceipt(txHash, common.HexToAddress("0x3"), []common.Hash{common.HexToHash("0x7")}, 0) + require.NoError(t, store.StoreReceipts(ctx.WithBlockHeight(2), []receipt.ReceiptRecord{{TxHash: txHash, Receipt: r}})) + + require.Eventually(t, func() bool { + _, err := store.GetReceipt(ctx, txHash) + return err == nil + }, 2*time.Second, 10*time.Millisecond) +} + +func TestFilterLogs(t *testing.T) { + store, ctx, _ := setupReceiptStore(t) + blockHeight := int64(8) + blockHash := common.HexToHash("0xab") + + txHash1 := common.HexToHash("0x10") + txHash2 := common.HexToHash("0x11") + txHash3 := common.HexToHash("0x12") + + addr1 := common.HexToAddress("0x100") + addr2 := common.HexToAddress("0x200") + addr3 := common.HexToAddress("0x300") + + topic1 := common.HexToHash("0xaa") + topic2 := common.HexToHash("0xbb") + topic3 := common.HexToHash("0xcc") + + r1 := withBloom(makeReceipt(txHash1, addr1, []common.Hash{topic1}, 0)) + r2 := makeReceipt(txHash2, addr2, []common.Hash{topic2}, 1) + r3 := withBloom(makeReceipt(txHash3, addr3, []common.Hash{topic3}, 2)) + + err := store.StoreReceipts(ctx.WithBlockHeight(0), []receipt.ReceiptRecord{ + {TxHash: txHash1, Receipt: r1}, + {TxHash: txHash2, Receipt: r2}, + {TxHash: txHash3, Receipt: r3}, + }) + require.NoError(t, err) + + logs, err := store.FilterLogs(ctx, blockHeight, blockHash, nil, filters.FilterCriteria{}, false) + require.NoError(t, err) + require.Len(t, logs, 0) + + crit := filters.FilterCriteria{ + Addresses: []common.Address{addr1}, + Topics: [][]common.Hash{{topic1}}, + } + txHashes := []common.Hash{txHash1, txHash2, txHash3, common.HexToHash("0xdead")} + + logs, err = store.FilterLogs(ctx, blockHeight, blockHash, txHashes, crit, true) + require.NoError(t, err) + require.Len(t, logs, 1) + require.Equal(t, addr1, logs[0].Address) + require.Equal(t, uint64(blockHeight), logs[0].BlockNumber) + require.Equal(t, blockHash, logs[0].BlockHash) + require.Equal(t, uint(0), logs[0].TxIndex) + + logs, err = store.FilterLogs(ctx, blockHeight, blockHash, txHashes, crit, false) + require.NoError(t, err) + require.Len(t, logs, 2) + require.Equal(t, addr1, logs[0].Address) + require.Equal(t, addr2, logs[1].Address) + + logs, err = store.FilterLogs(ctx, blockHeight, blockHash, txHashes[:3], filters.FilterCriteria{}, false) + require.NoError(t, err) + require.Len(t, logs, 3) +} + +func TestMatchTopics(t *testing.T) { + topic1 := common.HexToHash("0x1") + topic2 := common.HexToHash("0x2") + require.True(t, receipt.MatchTopics([][]common.Hash{{topic1}, {}}, []common.Hash{topic1})) + require.False(t, receipt.MatchTopics([][]common.Hash{{topic1}, {topic2}}, []common.Hash{topic1})) +} + +func TestRecoverReceiptStoreReplaysChangelog(t *testing.T) { + dir := t.TempDir() + changelogPath := dbutils.GetChangelogPath(dir) + require.NoError(t, os.MkdirAll(changelogPath, 0o750)) + + stream, err := changelog.NewStream(dbLogger.NewNopLogger(), changelogPath, changelog.Config{}) + require.NoError(t, err) + + txHash1 := common.HexToHash("0x20") + txHash2 := common.HexToHash("0x21") + receipt1 := makeReceipt(txHash1, common.HexToAddress("0x400"), []common.Hash{common.HexToHash("0x22")}, 0) + receipt2 := makeReceipt(txHash2, common.HexToAddress("0x401"), []common.Hash{common.HexToHash("0x23")}, 0) + + entry1, err := makeChangeSetEntry(1, txHash1, receipt1) + require.NoError(t, err) + entry2, err := makeChangeSetEntry(2, txHash2, receipt2) + require.NoError(t, err) + + require.NoError(t, stream.WriteNextEntry(entry1)) + require.NoError(t, stream.WriteNextEntry(entry2)) + require.NoError(t, stream.Close()) + + cfg := dbconfig.DefaultStateStoreConfig() + cfg.DBDirectory = dir + cfg.KeepRecent = 0 + cfg.KeepLastVersion = false + db, err := mvcc.OpenDB(dir, cfg) + require.NoError(t, err) + t.Cleanup(func() { _ = db.Close() }) + + require.NoError(t, db.SetLatestVersion(1)) + require.NoError(t, receipt.RecoverReceiptStore(dbLogger.NewNopLogger(), changelogPath, db)) + require.Equal(t, int64(2), db.GetLatestVersion()) + + bz, err := db.Get(types.ReceiptStoreKey, db.GetLatestVersion(), types.ReceiptKey(txHash2)) + require.NoError(t, err) + require.NotNil(t, bz) +} + +func makeChangeSetEntry(version int64, txHash common.Hash, receipt *types.Receipt) (proto.ChangelogEntry, error) { + marshalledReceipt, err := receipt.Marshal() + if err != nil { + return proto.ChangelogEntry{}, err + } + kvPair := &iavl.KVPair{ + Key: types.ReceiptKey(txHash), + Value: marshalledReceipt, + } + ncs := &proto.NamedChangeSet{ + Name: types.ReceiptStoreKey, + Changeset: iavl.ChangeSet{Pairs: []*iavl.KVPair{kvPair}}, + } + return proto.ChangelogEntry{ + Version: version, + Changesets: []*proto.NamedChangeSet{ncs}, + }, nil +} From 0af64ff301e642cf99326af165dd200a40647e7a Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Thu, 15 Jan 2026 14:26:34 -0500 Subject: [PATCH 07/17] fix go.work.sum --- go.work.sum | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 go.work.sum diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000000..b870e99842 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,255 @@ +cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.8.2/go.mod h1:aEJvHZtpjqstffVwF/2mCXXSQmpskyzvw6zKLvLutZM= +cloud.google.com/go/accesscontextmanager v1.9.2/go.mod h1:T0Sw/PQPyzctnkw1pdmGAKb7XBA84BqQzH0fSU7wzJU= +cloud.google.com/go/aiplatform v1.69.0/go.mod h1:nUsIqzS3khlnWvpjfJbP+2+h+VrFyYsTm7RNCAViiY8= +cloud.google.com/go/analytics v0.25.2/go.mod h1:th0DIunqrhI1ZWVlT3PH2Uw/9ANX8YHfFDEPqf/+7xM= +cloud.google.com/go/apigateway v1.7.2/go.mod h1:+weId+9aR9J6GRwDka7jIUSrKEX60XGcikX7dGU8O7M= +cloud.google.com/go/apigeeconnect v1.7.2/go.mod h1:he/SWi3A63fbyxrxD6jb67ak17QTbWjva1TFbT5w8Kw= +cloud.google.com/go/apigeeregistry v0.9.2/go.mod h1:A5n/DwpG5NaP2fcLYGiFA9QfzpQhPRFNATO1gie8KM8= +cloud.google.com/go/appengine v1.9.2/go.mod h1:bK4dvmMG6b5Tem2JFZcjvHdxco9g6t1pwd3y/1qr+3s= +cloud.google.com/go/area120 v0.9.2/go.mod h1:Ar/KPx51UbrTWGVGgGzFnT7hFYQuk/0VOXkvHdTbQMI= +cloud.google.com/go/artifactregistry v1.16.0/go.mod h1:LunXo4u2rFtvJjrGjO0JS+Gs9Eco2xbZU6JVJ4+T8Sk= +cloud.google.com/go/asset v1.20.3/go.mod h1:797WxTDwdnFAJzbjZ5zc+P5iwqXc13yO9DHhmS6wl+o= +cloud.google.com/go/assuredworkloads v1.12.2/go.mod h1:/WeRr/q+6EQYgnoYrqCVgw7boMoDfjXZZev3iJxs2Iw= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/automl v1.14.2/go.mod h1:mIat+Mf77W30eWQ/vrhjXsXaRh8Qfu4WiymR0hR6Uxk= +cloud.google.com/go/baremetalsolution v1.3.2/go.mod h1:3+wqVRstRREJV/puwaKAH3Pnn7ByreZG2aFRsavnoBQ= +cloud.google.com/go/batch v1.11.2/go.mod h1:ehsVs8Y86Q4K+qhEStxICqQnNqH8cqgpCxx89cmU5h4= +cloud.google.com/go/beyondcorp v1.1.2/go.mod h1:q6YWSkEsSZTU2WDt1qtz6P5yfv79wgktGtNbd0FJTLI= +cloud.google.com/go/bigquery v1.64.0/go.mod h1:gy8Ooz6HF7QmA+TRtX8tZmXBKH5mCFBwUApGAb3zI7Y= +cloud.google.com/go/bigtable v1.33.0/go.mod h1:HtpnH4g25VT1pejHRtInlFPnN5sjTxbQlsYBjh9t5l0= +cloud.google.com/go/billing v1.19.2/go.mod h1:AAtih/X2nka5mug6jTAq8jfh1nPye0OjkHbZEZgU59c= +cloud.google.com/go/binaryauthorization v1.9.2/go.mod h1:T4nOcRWi2WX4bjfSRXJkUnpliVIqjP38V88Z10OvEv4= +cloud.google.com/go/certificatemanager v1.9.2/go.mod h1:PqW+fNSav5Xz8bvUnJpATIRo1aaABP4mUg/7XIeAn6c= +cloud.google.com/go/channel v1.19.1/go.mod h1:ungpP46l6XUeuefbA/XWpWWnAY3897CSRPXUbDstwUo= +cloud.google.com/go/cloudbuild v1.19.0/go.mod h1:ZGRqbNMrVGhknIIjwASa6MqoRTOpXIVMSI+Ew5DMPuY= +cloud.google.com/go/clouddms v1.8.2/go.mod h1:pe+JSp12u4mYOkwXpSMouyCCuQHL3a6xvWH2FgOcAt4= +cloud.google.com/go/cloudtasks v1.13.2/go.mod h1:2pyE4Lhm7xY8GqbZKLnYk7eeuh8L0JwAvXx1ecKxYu8= +cloud.google.com/go/compute v1.29.0/go.mod h1:HFlsDurE5DpQZClAGf/cYh+gxssMhBxBovZDYkEn/Og= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/contactcenterinsights v1.15.1/go.mod h1:cFGxDVm/OwEVAHbU9UO4xQCtQFn0RZSrSUcF/oJ0Bbs= +cloud.google.com/go/container v1.42.0/go.mod h1:YL6lDgCUi3frIWNIFU9qrmF7/6K1EYrtspmFTyyqJ+k= +cloud.google.com/go/containeranalysis v0.13.2/go.mod h1:AiKvXJkc3HiqkHzVIt6s5M81wk+q7SNffc6ZlkTDgiE= +cloud.google.com/go/datacatalog v1.23.0/go.mod h1:9Wamq8TDfL2680Sav7q3zEhBJSPBrDxJU8WtPJ25dBM= +cloud.google.com/go/dataflow v0.10.2/go.mod h1:+HIb4HJxDCZYuCqDGnBHZEglh5I0edi/mLgVbxDf0Ag= +cloud.google.com/go/dataform v0.10.2/go.mod h1:oZHwMBxG6jGZCVZqqMx+XWXK+dA/ooyYiyeRbUxI15M= +cloud.google.com/go/datafusion v1.8.2/go.mod h1:XernijudKtVG/VEvxtLv08COyVuiYPraSxm+8hd4zXA= +cloud.google.com/go/datalabeling v0.9.2/go.mod h1:8me7cCxwV/mZgYWtRAd3oRVGFD6UyT7hjMi+4GRyPpg= +cloud.google.com/go/dataplex v1.19.2/go.mod h1:vsxxdF5dgk3hX8Ens9m2/pMNhQZklUhSgqTghZtF1v4= +cloud.google.com/go/dataproc/v2 v2.10.0/go.mod h1:HD16lk4rv2zHFhbm8gGOtrRaFohMDr9f0lAUMLmg1PM= +cloud.google.com/go/dataqna v0.9.2/go.mod h1:WCJ7pwD0Mi+4pIzFQ+b2Zqy5DcExycNKHuB+VURPPgs= +cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= +cloud.google.com/go/datastream v1.11.2/go.mod h1:RnFWa5zwR5SzHxeZGJOlQ4HKBQPcjGfD219Qy0qfh2k= +cloud.google.com/go/deploy v1.25.0/go.mod h1:h9uVCWxSDanXUereI5WR+vlZdbPJ6XGy+gcfC25v5rM= +cloud.google.com/go/dialogflow v1.60.0/go.mod h1:PjsrI+d2FI4BlGThxL0+Rua/g9vLI+2A1KL7s/Vo3pY= +cloud.google.com/go/dlp v1.20.0/go.mod h1:nrGsA3r8s7wh2Ct9FWu69UjBObiLldNyQda2RCHgdaY= +cloud.google.com/go/documentai v1.35.0/go.mod h1:ZotiWUlDE8qXSUqkJsGMQqVmfTMYATwJEYqbPXTR9kk= +cloud.google.com/go/domains v0.10.2/go.mod h1:oL0Wsda9KdJvvGNsykdalHxQv4Ri0yfdDkIi3bzTUwk= +cloud.google.com/go/edgecontainer v1.4.0/go.mod h1:Hxj5saJT8LMREmAI9tbNTaBpW5loYiWFyisCjDhzu88= +cloud.google.com/go/errorreporting v0.3.1/go.mod h1:6xVQXU1UuntfAf+bVkFk6nld41+CPyF2NSPCyXE3Ztk= +cloud.google.com/go/essentialcontacts v1.7.2/go.mod h1:NoCBlOIVteJFJU+HG9dIG/Cc9kt1K9ys9mbOaGPUmPc= +cloud.google.com/go/eventarc v1.15.0/go.mod h1:PAd/pPIZdJtJQFJI1yDEUms1mqohdNuM1BFEVHHlVFg= +cloud.google.com/go/filestore v1.9.2/go.mod h1:I9pM7Hoetq9a7djC1xtmtOeHSUYocna09ZP6x+PG1Xw= +cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= +cloud.google.com/go/functions v1.19.2/go.mod h1:SBzWwWuaFDLnUyStDAMEysVN1oA5ECLbP3/PfJ9Uk7Y= +cloud.google.com/go/gkebackup v1.6.2/go.mod h1:WsTSWqKJkGan1pkp5dS30oxb+Eaa6cLvxEUxKTUALwk= +cloud.google.com/go/gkeconnect v0.12.0/go.mod h1:zn37LsFiNZxPN4iO7YbUk8l/E14pAJ7KxpoXoxt7Ly0= +cloud.google.com/go/gkehub v0.15.2/go.mod h1:8YziTOpwbM8LM3r9cHaOMy2rNgJHXZCrrmGgcau9zbQ= +cloud.google.com/go/gkemulticloud v1.4.1/go.mod h1:KRvPYcx53bztNwNInrezdfNF+wwUom8Y3FuJBwhvFpQ= +cloud.google.com/go/gsuiteaddons v1.7.2/go.mod h1:GD32J2rN/4APilqZw4JKmwV84+jowYYMkEVwQEYuAWc= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.10.2/go.mod h1:cClgtI09VIfazEK6VMJr6bX8KQfuQ/D3xqX+d0wrUlI= +cloud.google.com/go/ids v1.5.2/go.mod h1:P+ccDD96joXlomfonEdCnyrHvE68uLonc7sJBPVM5T0= +cloud.google.com/go/iot v1.8.2/go.mod h1:UDwVXvRD44JIcMZr8pzpF3o4iPsmOO6fmbaIYCAg1ww= +cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= +cloud.google.com/go/language v1.14.2/go.mod h1:dviAbkxT9art+2ioL9AM05t+3Ql6UPfMpwq1cDsF+rg= +cloud.google.com/go/lifesciences v0.10.2/go.mod h1:vXDa34nz0T/ibUNoeHnhqI+Pn0OazUTdxemd0OLkyoY= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.7.2/go.mod h1:t0WKYzagOoD3FNtJWSWcU8zpWZz2i9cw2sKa9RiPx5I= +cloud.google.com/go/maps v1.15.0/go.mod h1:ZFqZS04ucwFiHSNU8TBYDUr3wYhj5iBFJk24Ibvpf3o= +cloud.google.com/go/mediatranslation v0.9.2/go.mod h1:1xyRoDYN32THzy+QaU62vIMciX0CFexplju9t30XwUc= +cloud.google.com/go/memcache v1.11.2/go.mod h1:jIzHn79b0m5wbkax2SdlW5vNSbpaEk0yWHbeLpMIYZE= +cloud.google.com/go/metastore v1.14.2/go.mod h1:dk4zOBhZIy3TFOQlI8sbOa+ef0FjAcCHEnd8dO2J+LE= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/networkconnectivity v1.15.2/go.mod h1:N1O01bEk5z9bkkWwXLKcN2T53QN49m/pSpjfUvlHDQY= +cloud.google.com/go/networkmanagement v1.16.0/go.mod h1:Yc905R9U5jik5YMt76QWdG5WqzPU4ZsdI/mLnVa62/Q= +cloud.google.com/go/networksecurity v0.10.2/go.mod h1:puU3Gwchd6Y/VTyMkL50GI2RSRMS3KXhcDBY1HSOcck= +cloud.google.com/go/notebooks v1.12.2/go.mod h1:EkLwv8zwr8DUXnvzl944+sRBG+b73HEKzV632YYAGNI= +cloud.google.com/go/optimization v1.7.2/go.mod h1:msYgDIh1SGSfq6/KiWJQ/uxMkWq8LekPyn1LAZ7ifNE= +cloud.google.com/go/orchestration v1.11.1/go.mod h1:RFHf4g88Lbx6oKhwFstYiId2avwb6oswGeAQ7Tjjtfw= +cloud.google.com/go/orgpolicy v1.14.1/go.mod h1:1z08Hsu1mkoH839X7C8JmnrqOkp2IZRSxiDw7W/Xpg4= +cloud.google.com/go/osconfig v1.14.2/go.mod h1:kHtsm0/j8ubyuzGciBsRxFlbWVjc4c7KdrwJw0+g+pQ= +cloud.google.com/go/oslogin v1.14.2/go.mod h1:M7tAefCr6e9LFTrdWRQRrmMeKHbkvc4D9g6tHIjHySA= +cloud.google.com/go/phishingprotection v0.9.2/go.mod h1:mSCiq3tD8fTJAuXq5QBHFKZqMUy8SfWsbUM9NpzJIRQ= +cloud.google.com/go/policytroubleshooter v1.11.2/go.mod h1:1TdeCRv8Qsjcz2qC3wFltg/Mjga4HSpv8Tyr5rzvPsw= +cloud.google.com/go/privatecatalog v0.10.2/go.mod h1:o124dHoxdbO50ImR3T4+x3GRwBSTf4XTn6AatP8MgsQ= +cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc= +cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= +cloud.google.com/go/recaptchaenterprise/v2 v2.19.0/go.mod h1:vnbA2SpVPPwKeoFrCQxR+5a0JFRRytwBBG69Zj9pGfk= +cloud.google.com/go/recommendationengine v0.9.2/go.mod h1:DjGfWZJ68ZF5ZuNgoTVXgajFAG0yLt4CJOpC0aMK3yw= +cloud.google.com/go/recommender v1.13.2/go.mod h1:XJau4M5Re8F4BM+fzF3fqSjxNJuM66fwF68VCy/ngGE= +cloud.google.com/go/redis v1.17.2/go.mod h1:h071xkcTMnJgQnU/zRMOVKNj5J6AttG16RDo+VndoNo= +cloud.google.com/go/resourcemanager v1.10.2/go.mod h1:5f+4zTM/ZOTDm6MmPOp6BQAhR0fi8qFPnvVGSoWszcc= +cloud.google.com/go/resourcesettings v1.8.2/go.mod h1:uEgtPiMA+xuBUM4Exu+ZkNpMYP0BLlYeJbyNHfrc+U0= +cloud.google.com/go/retail v1.19.1/go.mod h1:W48zg0zmt2JMqmJKCuzx0/0XDLtovwzGAeJjmv6VPaE= +cloud.google.com/go/run v1.7.0/go.mod h1:IvJOg2TBb/5a0Qkc6crn5yTy5nkjcgSWQLhgO8QL8PQ= +cloud.google.com/go/scheduler v1.11.2/go.mod h1:GZSv76T+KTssX2I9WukIYQuQRf7jk1WI+LOcIEHUUHk= +cloud.google.com/go/secretmanager v1.14.2/go.mod h1:Q18wAPMM6RXLC/zVpWTlqq2IBSbbm7pKBlM3lCKsmjw= +cloud.google.com/go/security v1.18.2/go.mod h1:3EwTcYw8554iEtgK8VxAjZaq2unFehcsgFIF9nOvQmU= +cloud.google.com/go/securitycenter v1.35.2/go.mod h1:AVM2V9CJvaWGZRHf3eG+LeSTSissbufD27AVBI91C8s= +cloud.google.com/go/servicedirectory v1.12.2/go.mod h1:F0TJdFjqqotiZRlMXgIOzszaplk4ZAmUV8ovHo08M2U= +cloud.google.com/go/shell v1.8.2/go.mod h1:QQR12T6j/eKvqAQLv6R3ozeoqwJ0euaFSz2qLqG93Bs= +cloud.google.com/go/spanner v1.73.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= +cloud.google.com/go/speech v1.25.2/go.mod h1:KPFirZlLL8SqPaTtG6l+HHIFHPipjbemv4iFg7rTlYs= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/storagetransfer v1.11.2/go.mod h1:FcM29aY4EyZ3yVPmW5SxhqUdhjgPBUOFyy4rqiQbias= +cloud.google.com/go/talent v1.7.2/go.mod h1:k1sqlDgS9gbc0gMTRuRQpX6C6VB7bGUxSPcoTRWJod8= +cloud.google.com/go/texttospeech v1.10.0/go.mod h1:215FpCOyRxxrS7DSb2t7f4ylMz8dXsQg8+Vdup5IhP4= +cloud.google.com/go/tpu v1.7.2/go.mod h1:0Y7dUo2LIbDUx0yQ/vnLC6e18FK6NrDfAhYS9wZ/2vs= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/translate v1.12.2/go.mod h1:jjLVf2SVH2uD+BNM40DYvRRKSsuyKxVvs3YjTW/XSWY= +cloud.google.com/go/video v1.23.2/go.mod h1:rNOr2pPHWeCbW0QsOwJRIe0ZiuwHpHtumK0xbiYB1Ew= +cloud.google.com/go/videointelligence v1.12.2/go.mod h1:8xKGlq0lNVyT8JgTkkCUCpyNJnYYEJVWGdqzv+UcwR8= +cloud.google.com/go/vision/v2 v2.9.2/go.mod h1:WuxjVQdAy4j4WZqY5Rr655EdAgi8B707Vdb5T8c90uo= +cloud.google.com/go/vmmigration v1.8.2/go.mod h1:FBejrsr8ZHmJb949BSOyr3D+/yCp9z9Hk0WtsTiHc1Q= +cloud.google.com/go/vmwareengine v1.3.2/go.mod h1:JsheEadzT0nfXOGkdnwtS1FhFAnj4g8qhi4rKeLi/AU= +cloud.google.com/go/vpcaccess v1.8.2/go.mod h1:4yvYKNjlNjvk/ffgZ0PuEhpzNJb8HybSM1otG2aDxnY= +cloud.google.com/go/webrisk v1.10.2/go.mod h1:c0ODT2+CuKCYjaeHO7b0ni4CUrJ95ScP5UFl9061Qq8= +cloud.google.com/go/websecurityscanner v1.7.2/go.mod h1:728wF9yz2VCErfBaACA5px2XSYHQgkK812NmHcUsDXA= +cloud.google.com/go/workflows v1.13.2/go.mod h1:l5Wj2Eibqba4BsADIRzPLaevLmIuYF2W+wfFBkRG3vU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/bazelbuild/rules_go v0.49.0/go.mod h1:Dhcz716Kqg1RHNWos+N6MlXNkjNP2EwZQ0LukRKJfMs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= +github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA= +github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= +github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= +google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= From 6f565be761942bf30bdee06f300c472596f9cfa0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 16 Jan 2026 21:09:25 +0000 Subject: [PATCH 08/17] Improve receipt store error handling and add graceful shutdown - Add sentinel errors ErrNotFound and ErrNotConfigured for consistent error checking - Add shutdown mechanism for pruning goroutine with stopPruning channel - Update Close() to signal pruning goroutine to stop using sync.Once - Update x/evm/keeper/receipt.go to use sentinel errors and errors.Is() - Replace string-based error comparison with errors.Is() for better error handling Co-authored-by: jeremy --- sei-db/ledger_db/receipt/receipt_store.go | 59 +++++++++++++++++------ x/evm/keeper/receipt.go | 22 ++++----- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/sei-db/ledger_db/receipt/receipt_store.go b/sei-db/ledger_db/receipt/receipt_store.go index 9639b64d58..2cd63c90f7 100644 --- a/sei-db/ledger_db/receipt/receipt_store.go +++ b/sei-db/ledger_db/receipt/receipt_store.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "strings" + "sync" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -23,6 +24,12 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/types" ) +// Sentinel errors for consistent error checking. +var ( + ErrNotFound = errors.New("receipt not found") + ErrNotConfigured = errors.New("receipt store not configured") +) + // ReceiptStore exposes receipt-specific operations without leaking the StateStore interface. type ReceiptStore interface { LatestHeight() int64 @@ -41,8 +48,10 @@ type ReceiptRecord struct { } type receiptStore struct { - db *mvcc.Database - storeKey sdk.StoreKey + db *mvcc.Database + storeKey sdk.StoreKey + stopPruning chan struct{} + closeOnce sync.Once } func NewReceiptStore(log dbLogger.Logger, config dbconfig.StateStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { @@ -64,10 +73,12 @@ func NewReceiptStore(log dbLogger.Logger, config dbconfig.StateStoreConfig, stor _ = db.Close() return nil, err } - startReceiptPruning(log, db, int64(config.KeepRecent), int64(config.PruneIntervalSeconds)) + stopPruning := make(chan struct{}) + startReceiptPruning(log, db, int64(config.KeepRecent), int64(config.PruneIntervalSeconds), stopPruning) return &receiptStore{ - db: db, - storeKey: storeKey, + db: db, + storeKey: storeKey, + stopPruning: stopPruning, }, nil } @@ -80,21 +91,21 @@ func (s *receiptStore) LatestHeight() int64 { func (s *receiptStore) SetLatestHeight(height int64) error { if s == nil || s.db == nil { - return errors.New("receipt store not configured") + return ErrNotConfigured } return s.db.SetLatestVersion(height) } func (s *receiptStore) SetEarliestHeight(height int64) error { if s == nil || s.db == nil { - return errors.New("receipt store not configured") + return ErrNotConfigured } return s.db.SetEarliestVersion(height, true) } func (s *receiptStore) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { if s == nil || s.db == nil { - return nil, errors.New("receipt store not configured") + return nil, ErrNotConfigured } // receipts are immutable, use latest version @@ -111,7 +122,7 @@ func (s *receiptStore) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.R store := ctx.KVStore(s.storeKey) bz = store.Get(types.ReceiptKey(txHash)) if bz == nil { - return nil, errors.New("not found") + return nil, ErrNotFound } } @@ -125,7 +136,7 @@ func (s *receiptStore) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.R // Only used for testing. func (s *receiptStore) GetReceiptFromStore(_ sdk.Context, txHash common.Hash) (*types.Receipt, error) { if s == nil || s.db == nil { - return nil, errors.New("receipt store not configured") + return nil, ErrNotConfigured } // receipts are immutable, use latest version @@ -137,7 +148,7 @@ func (s *receiptStore) GetReceiptFromStore(_ sdk.Context, txHash common.Hash) (* return nil, err } if bz == nil { - return nil, errors.New("not found") + return nil, ErrNotFound } var r types.Receipt @@ -149,7 +160,7 @@ func (s *receiptStore) GetReceiptFromStore(_ sdk.Context, txHash common.Hash) (* func (s *receiptStore) StoreReceipts(ctx sdk.Context, receipts []ReceiptRecord) error { if s == nil || s.db == nil { - return errors.New("receipt store not configured") + return ErrNotConfigured } pairs := make([]*iavl.KVPair, 0, len(receipts)) @@ -193,7 +204,7 @@ func (s *receiptStore) StoreReceipts(ctx sdk.Context, receipts []ReceiptRecord) func (s *receiptStore) FilterLogs(ctx sdk.Context, blockHeight int64, blockHash common.Hash, txHashes []common.Hash, crit filters.FilterCriteria, applyExactMatch bool) ([]*ethtypes.Log, error) { if s == nil || s.db == nil { - return nil, errors.New("receipt store not configured") + return nil, ErrNotConfigured } if len(txHashes) == 0 { return []*ethtypes.Log{}, nil @@ -258,7 +269,15 @@ func (s *receiptStore) Close() error { if s == nil || s.db == nil { return nil } - return s.db.Close() + var err error + s.closeOnce.Do(func() { + // Signal the pruning goroutine to stop + if s.stopPruning != nil { + close(s.stopPruning) + } + err = s.db.Close() + }) + return err } func recoverReceiptStore(log dbLogger.Logger, changelogPath string, db *mvcc.Database) error { @@ -314,7 +333,7 @@ func recoverReceiptStore(log dbLogger.Logger, changelogPath string, db *mvcc.Dat return nil } -func startReceiptPruning(log dbLogger.Logger, db *mvcc.Database, keepRecent int64, pruneInterval int64) { +func startReceiptPruning(log dbLogger.Logger, db *mvcc.Database, keepRecent int64, pruneInterval int64, stopCh <-chan struct{}) { if keepRecent <= 0 || pruneInterval <= 0 { return } @@ -334,7 +353,15 @@ func startReceiptPruning(log dbLogger.Logger, db *mvcc.Database, keepRecent int6 // Generate a random percentage (between 0% and 100%) of the fixed interval as a delay randomPercentage := rand.Float64() randomDelay := int64(float64(pruneInterval) * randomPercentage) - time.Sleep(time.Duration(pruneInterval+randomDelay) * time.Second) + sleepDuration := time.Duration(pruneInterval+randomDelay) * time.Second + + select { + case <-stopCh: + log.Info("Receipt store pruning goroutine stopped") + return + case <-time.After(sleepDuration): + // Continue to next iteration + } } }() } diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index 316f1f45d0..109d43d64e 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -39,7 +39,7 @@ func (k *Keeper) GetTransientReceipt(ctx sdk.Context, txHash common.Hash, txInde store := ctx.TransientStore(k.transientStoreKey) bz := store.Get(types.NewTransientReceiptKey(txIndex, txHash)) if bz == nil { - return nil, errors.New("not found") + return nil, receipt.ErrNotFound } r := &types.Receipt{} if err := r.Unmarshal(bz); err != nil { @@ -58,7 +58,7 @@ func (k *Keeper) DeleteTransientReceipt(ctx sdk.Context, txHash common.Hash, txI // by EVM transaction hash (not Sei transaction hash) to function properly. func (k *Keeper) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { if k.receiptStore == nil { - return nil, errors.New("receipt store not configured") + return nil, receipt.ErrNotConfigured } return k.receiptStore.GetReceipt(ctx, txHash) } @@ -66,7 +66,7 @@ func (k *Keeper) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt // Only used for testing func (k *Keeper) GetReceiptFromReceiptStore(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { if k.receiptStore == nil { - return nil, errors.New("receipt store not configured") + return nil, receipt.ErrNotConfigured } return k.receiptStore.GetReceiptFromStore(ctx, txHash) } @@ -76,13 +76,13 @@ func (k *Keeper) GetReceiptFromReceiptStore(ctx sdk.Context, txHash common.Hash) func (k *Keeper) GetReceiptWithRetry(ctx sdk.Context, txHash common.Hash, maxRetries int) (*types.Receipt, error) { var lastErr error for i := 0; i < maxRetries; i++ { - receipt, err := k.GetReceipt(ctx, txHash) + rcpt, err := k.GetReceipt(ctx, txHash) if err == nil { - return receipt, nil + return rcpt, nil } // If it's not a "not found" error, return immediately - if err.Error() != "not found" { + if !errors.Is(err, receipt.ErrNotFound) { return nil, err } @@ -96,9 +96,9 @@ func (k *Keeper) GetReceiptWithRetry(ctx sdk.Context, txHash common.Hash, maxRet // MockReceipt sets a data structure that stores EVM specific transaction metadata. // // this is currently used by a number of tests to set receipts at the moment -func (k *Keeper) MockReceipt(ctx sdk.Context, txHash common.Hash, receipt *types.Receipt) error { +func (k *Keeper) MockReceipt(ctx sdk.Context, txHash common.Hash, rcpt *types.Receipt) error { fmt.Printf("MOCK RECEIPT height=%d, tx=%s\n", ctx.BlockHeight(), txHash.Hex()) - if err := k.SetTransientReceipt(ctx, txHash, receipt); err != nil { + if err := k.SetTransientReceipt(ctx, txHash, rcpt); err != nil { return err } if err := k.FlushTransientReceipts(ctx); err != nil { @@ -108,11 +108,11 @@ func (k *Keeper) MockReceipt(ctx sdk.Context, txHash common.Hash, receipt *types for { if _, err := k.GetReceipt(ctx, txHash); err == nil { return nil - } else if err != nil && err.Error() != "not found" { + } else if err != nil && !errors.Is(err, receipt.ErrNotFound) { return err } if time.Now().After(deadline) { - return errors.New("receipt not found after async flush") + return fmt.Errorf("%w: after async flush", receipt.ErrNotFound) } time.Sleep(10 * time.Millisecond) } @@ -165,7 +165,7 @@ func (k *Keeper) flushTransientReceipts(ctx sdk.Context) error { records = append(records, receipt.ReceiptRecord{TxHash: txHash, Receipt: rcpt}) } if k.receiptStore == nil { - return errors.New("receipt store not configured") + return receipt.ErrNotConfigured } return k.receiptStore.StoreReceipts(ctx, records) } From 512577bb510d89fc3b34999081dc7413318ac60f Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Fri, 16 Jan 2026 16:40:27 -0500 Subject: [PATCH 09/17] fix --- sei-db/ledger_db/receipt/receipt_store.go | 4 ++-- sei-db/ledger_db/receipt/receipt_store_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sei-db/ledger_db/receipt/receipt_store.go b/sei-db/ledger_db/receipt/receipt_store.go index 2cd63c90f7..cc5ce4b0b5 100644 --- a/sei-db/ledger_db/receipt/receipt_store.go +++ b/sei-db/ledger_db/receipt/receipt_store.go @@ -13,12 +13,12 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/sei-protocol/sei-chain/sei-db/changelog/changelog" dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" dbutils "github.com/sei-protocol/sei-chain/sei-db/common/utils" dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/db_engine/pebbledb/mvcc" "github.com/sei-protocol/sei-chain/sei-db/proto" + "github.com/sei-protocol/sei-chain/sei-db/wal" iavl "github.com/sei-protocol/sei-chain/sei-iavl" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -283,7 +283,7 @@ func (s *receiptStore) Close() error { func recoverReceiptStore(log dbLogger.Logger, changelogPath string, db *mvcc.Database) error { ssLatestVersion := db.GetLatestVersion() log.Info(fmt.Sprintf("Recovering from changelog %s with latest receipt version %d", changelogPath, ssLatestVersion)) - streamHandler, err := changelog.NewStream(log, changelogPath, changelog.Config{}) + streamHandler, err := wal.NewChangelogWAL(log, changelogPath, wal.Config{}) if err != nil { return err } diff --git a/sei-db/ledger_db/receipt/receipt_store_test.go b/sei-db/ledger_db/receipt/receipt_store_test.go index 98508d92df..2ac4aff71a 100644 --- a/sei-db/ledger_db/receipt/receipt_store_test.go +++ b/sei-db/ledger_db/receipt/receipt_store_test.go @@ -11,13 +11,13 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/sei-protocol/sei-chain/sei-db/changelog/changelog" dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" dbutils "github.com/sei-protocol/sei-chain/sei-db/common/utils" dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" "github.com/sei-protocol/sei-chain/sei-db/db_engine/pebbledb/mvcc" "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/sei-protocol/sei-chain/sei-db/proto" + "github.com/sei-protocol/sei-chain/sei-db/wal" iavl "github.com/sei-protocol/sei-chain/sei-iavl" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/stretchr/testify/require" @@ -229,7 +229,7 @@ func TestRecoverReceiptStoreReplaysChangelog(t *testing.T) { changelogPath := dbutils.GetChangelogPath(dir) require.NoError(t, os.MkdirAll(changelogPath, 0o750)) - stream, err := changelog.NewStream(dbLogger.NewNopLogger(), changelogPath, changelog.Config{}) + stream, err := wal.NewChangelogWAL(dbLogger.NewNopLogger(), changelogPath, wal.Config{}) require.NoError(t, err) txHash1 := common.HexToHash("0x20") From 48678765670fda0b20bda88e9eee044766899f08 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Fri, 16 Jan 2026 16:48:33 -0500 Subject: [PATCH 10/17] fix --- sei-db/ledger_db/receipt/receipt_store_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sei-db/ledger_db/receipt/receipt_store_test.go b/sei-db/ledger_db/receipt/receipt_store_test.go index 2ac4aff71a..e88eedd165 100644 --- a/sei-db/ledger_db/receipt/receipt_store_test.go +++ b/sei-db/ledger_db/receipt/receipt_store_test.go @@ -242,8 +242,8 @@ func TestRecoverReceiptStoreReplaysChangelog(t *testing.T) { entry2, err := makeChangeSetEntry(2, txHash2, receipt2) require.NoError(t, err) - require.NoError(t, stream.WriteNextEntry(entry1)) - require.NoError(t, stream.WriteNextEntry(entry2)) + require.NoError(t, stream.Write(entry1)) + require.NoError(t, stream.Write(entry2)) require.NoError(t, stream.Close()) cfg := dbconfig.DefaultStateStoreConfig() From aaea971a71e0f81c5061b1087e1dde135f6a56da Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 16 Jan 2026 22:03:28 +0000 Subject: [PATCH 11/17] Rename ReceiptStore methods: StoreReceipts->SetReceipts, Height->Version - Rename StoreReceipts to SetReceipts for consistency - Rename LatestHeight to LatestVersion - Rename SetLatestHeight to SetLatestVersion - Rename SetEarliestHeight to SetEarliestVersion - Update all usages in keeper, evmrpc, and tests Co-authored-by: jeremy --- evmrpc/setup_test.go | 8 +++--- evmrpc/simulate_test.go | 4 +-- evmrpc/state_test.go | 4 +-- evmrpc/tests/utils.go | 4 +-- evmrpc/watermark_manager.go | 4 +-- evmrpc/watermark_manager_test.go | 10 +++---- sei-db/ledger_db/receipt/receipt_store.go | 20 +++++++------- .../ledger_db/receipt/receipt_store_test.go | 26 +++++++++---------- x/evm/keeper/receipt.go | 2 +- 9 files changed, 41 insertions(+), 41 deletions(-) diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index f4dbf8b0ab..0a47bb120e 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -574,10 +574,10 @@ func init() { testApp.Commit(context.Background()) if store := EVMKeeper.ReceiptStore(); store != nil { latest := int64(math.MaxInt64) - if err := store.SetLatestHeight(latest); err != nil { + if err := store.SetLatestVersion(latest); err != nil { panic(err) } - _ = store.SetEarliestHeight(1) + _ = store.SetEarliestVersion(1) } ctxProvider := func(height int64) sdk.Context { if height == MockHeight2 { @@ -1097,10 +1097,10 @@ func setupLogs() { EVMKeeper.SetEvmOnlyBlockBloom(Ctx, []ethtypes.Bloom{bloom4, bloomTx1}) if store := EVMKeeper.ReceiptStore(); store != nil { - if err := store.SetLatestHeight(MockHeight103); err != nil { + if err := store.SetLatestVersion(MockHeight103); err != nil { panic(err) } - _ = store.SetEarliestHeight(1) + _ = store.SetEarliestVersion(1) } } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index a6bffbca99..661857e8b1 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -48,8 +48,8 @@ func primeReceiptStore(t *testing.T, store receipt.ReceiptStore, latest int64) { if latest <= 0 { latest = 1 } - require.NoError(t, store.SetLatestHeight(latest)) - require.NoError(t, store.SetEarliestHeight(1)) + require.NoError(t, store.SetLatestVersion(latest)) + require.NoError(t, store.SetEarliestVersion(1)) } func (bc *bcFailClient) Block(ctx context.Context, h *int64) (*coretypes.ResultBlock, error) { diff --git a/evmrpc/state_test.go b/evmrpc/state_test.go index c316f5c328..ab2a23050c 100644 --- a/evmrpc/state_test.go +++ b/evmrpc/state_test.go @@ -217,8 +217,8 @@ func TestGetProof(t *testing.T) { require.Nil(t, err) } if store := testApp.EvmKeeper.ReceiptStore(); store != nil { - require.NoError(t, store.SetLatestHeight(MockHeight8)) - require.NoError(t, store.SetEarliestHeight(1)) + require.NoError(t, store.SetLatestVersion(MockHeight8)) + require.NoError(t, store.SetEarliestVersion(1)) } client := &MockClient{} ctxProvider := func(height int64) sdk.Context { diff --git a/evmrpc/tests/utils.go b/evmrpc/tests/utils.go index b18767fc80..af536e512b 100644 --- a/evmrpc/tests/utils.go +++ b/evmrpc/tests/utils.go @@ -162,10 +162,10 @@ func setupTestServer( } if store := a.EvmKeeper.ReceiptStore(); store != nil { latest := int64(math.MaxInt64) - if err := store.SetLatestHeight(latest); err != nil { + if err := store.SetLatestVersion(latest); err != nil { panic(err) } - _ = store.SetEarliestHeight(1) + _ = store.SetEarliestVersion(1) } return TestServer{EVMServer: s, port: port, mockClient: mockClient, app: a} } diff --git a/evmrpc/watermark_manager.go b/evmrpc/watermark_manager.go index 5cb015afce..83c5115466 100644 --- a/evmrpc/watermark_manager.go +++ b/evmrpc/watermark_manager.go @@ -106,9 +106,9 @@ func (m *WatermarkManager) Watermarks(ctx context.Context) (int64, int64, int64, } } - // Receipt store height participates only in the latest watermark. + // Receipt store version participates only in the latest watermark. if m.receiptStore != nil { - if latest := m.receiptStore.LatestHeight(); latest > 0 { + if latest := m.receiptStore.LatestVersion(); latest > 0 { setLatest(latest) } } diff --git a/evmrpc/watermark_manager_test.go b/evmrpc/watermark_manager_test.go index dbb8f726cd..94e13a75e8 100644 --- a/evmrpc/watermark_manager_test.go +++ b/evmrpc/watermark_manager_test.go @@ -241,16 +241,16 @@ type fakeReceiptStore struct { latest int64 } -func (f *fakeReceiptStore) LatestHeight() int64 { +func (f *fakeReceiptStore) LatestVersion() int64 { return f.latest } -func (f *fakeReceiptStore) SetLatestHeight(height int64) error { - f.latest = height +func (f *fakeReceiptStore) SetLatestVersion(version int64) error { + f.latest = version return nil } -func (f *fakeReceiptStore) SetEarliestHeight(_ int64) error { return nil } +func (f *fakeReceiptStore) SetEarliestVersion(_ int64) error { return nil } func (f *fakeReceiptStore) GetReceipt(sdk.Context, common.Hash) (*evmtypes.Receipt, error) { return nil, errors.New("not found") @@ -260,7 +260,7 @@ func (f *fakeReceiptStore) GetReceiptFromStore(sdk.Context, common.Hash) (*evmty return nil, errors.New("not found") } -func (f *fakeReceiptStore) StoreReceipts(sdk.Context, []receipt.ReceiptRecord) error { +func (f *fakeReceiptStore) SetReceipts(sdk.Context, []receipt.ReceiptRecord) error { return nil } diff --git a/sei-db/ledger_db/receipt/receipt_store.go b/sei-db/ledger_db/receipt/receipt_store.go index cc5ce4b0b5..18410673bc 100644 --- a/sei-db/ledger_db/receipt/receipt_store.go +++ b/sei-db/ledger_db/receipt/receipt_store.go @@ -32,12 +32,12 @@ var ( // ReceiptStore exposes receipt-specific operations without leaking the StateStore interface. type ReceiptStore interface { - LatestHeight() int64 - SetLatestHeight(height int64) error - SetEarliestHeight(height int64) error + LatestVersion() int64 + SetLatestVersion(version int64) error + SetEarliestVersion(version int64) error GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) GetReceiptFromStore(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) - StoreReceipts(ctx sdk.Context, receipts []ReceiptRecord) error + SetReceipts(ctx sdk.Context, receipts []ReceiptRecord) error FilterLogs(ctx sdk.Context, blockHeight int64, blockHash common.Hash, txHashes []common.Hash, crit filters.FilterCriteria, applyExactMatch bool) ([]*ethtypes.Log, error) Close() error } @@ -82,25 +82,25 @@ func NewReceiptStore(log dbLogger.Logger, config dbconfig.StateStoreConfig, stor }, nil } -func (s *receiptStore) LatestHeight() int64 { +func (s *receiptStore) LatestVersion() int64 { if s == nil || s.db == nil { return 0 } return s.db.GetLatestVersion() } -func (s *receiptStore) SetLatestHeight(height int64) error { +func (s *receiptStore) SetLatestVersion(version int64) error { if s == nil || s.db == nil { return ErrNotConfigured } - return s.db.SetLatestVersion(height) + return s.db.SetLatestVersion(version) } -func (s *receiptStore) SetEarliestHeight(height int64) error { +func (s *receiptStore) SetEarliestVersion(version int64) error { if s == nil || s.db == nil { return ErrNotConfigured } - return s.db.SetEarliestVersion(height, true) + return s.db.SetEarliestVersion(version, true) } func (s *receiptStore) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { @@ -158,7 +158,7 @@ func (s *receiptStore) GetReceiptFromStore(_ sdk.Context, txHash common.Hash) (* return &r, nil } -func (s *receiptStore) StoreReceipts(ctx sdk.Context, receipts []ReceiptRecord) error { +func (s *receiptStore) SetReceipts(ctx sdk.Context, receipts []ReceiptRecord) error { if s == nil || s.db == nil { return ErrNotConfigured } diff --git a/sei-db/ledger_db/receipt/receipt_store_test.go b/sei-db/ledger_db/receipt/receipt_store_test.go index e88eedd165..c0ac704d90 100644 --- a/sei-db/ledger_db/receipt/receipt_store_test.go +++ b/sei-db/ledger_db/receipt/receipt_store_test.go @@ -88,27 +88,27 @@ func TestNewReceiptStoreConfigErrors(t *testing.T) { func TestReceiptStoreNilReceiver(t *testing.T) { store := receipt.NilReceiptStore() - require.Equal(t, int64(0), store.LatestHeight()) - require.Error(t, store.SetLatestHeight(1)) - require.Error(t, store.SetEarliestHeight(1)) + require.Equal(t, int64(0), store.LatestVersion()) + require.Error(t, store.SetLatestVersion(1)) + require.Error(t, store.SetEarliestVersion(1)) _, err := store.GetReceipt(sdk.Context{}, common.Hash{}) require.Error(t, err) _, err = store.GetReceiptFromStore(sdk.Context{}, common.Hash{}) require.Error(t, err) - require.Error(t, store.StoreReceipts(sdk.Context{}, nil)) + require.Error(t, store.SetReceipts(sdk.Context{}, nil)) _, err = store.FilterLogs(sdk.Context{}, 1, common.Hash{}, nil, filters.FilterCriteria{}, false) require.Error(t, err) require.NoError(t, store.Close()) } -func TestStoreReceiptsAndGet(t *testing.T) { +func TestSetReceiptsAndGet(t *testing.T) { store, ctx, _ := setupReceiptStore(t) txHash := common.HexToHash("0x1") addr := common.HexToAddress("0x1") topic := common.HexToHash("0x2") r := makeReceipt(txHash, addr, []common.Hash{topic}, 0) - err := store.StoreReceipts(ctx.WithBlockHeight(0), []receipt.ReceiptRecord{ + err := store.SetReceipts(ctx.WithBlockHeight(0), []receipt.ReceiptRecord{ {TxHash: txHash, Receipt: r}, {TxHash: txHash}, }) @@ -125,10 +125,10 @@ func TestStoreReceiptsAndGet(t *testing.T) { _, err = store.GetReceiptFromStore(ctx, common.HexToHash("0x3")) require.Error(t, err) - require.GreaterOrEqual(t, store.LatestHeight(), int64(1)) - require.NoError(t, store.SetLatestHeight(10)) - require.Equal(t, int64(10), store.LatestHeight()) - require.NoError(t, store.SetEarliestHeight(1)) + require.GreaterOrEqual(t, store.LatestVersion(), int64(1)) + require.NoError(t, store.SetLatestVersion(10)) + require.Equal(t, int64(10), store.LatestVersion()) + require.NoError(t, store.SetEarliestVersion(1)) } func TestReceiptStoreLegacyFallback(t *testing.T) { @@ -148,11 +148,11 @@ func TestReceiptStoreLegacyFallback(t *testing.T) { require.Error(t, err) } -func TestStoreReceiptsAsync(t *testing.T) { +func TestSetReceiptsAsync(t *testing.T) { store, ctx, _ := setupReceiptStore(t) txHash := common.HexToHash("0x6") r := makeReceipt(txHash, common.HexToAddress("0x3"), []common.Hash{common.HexToHash("0x7")}, 0) - require.NoError(t, store.StoreReceipts(ctx.WithBlockHeight(2), []receipt.ReceiptRecord{{TxHash: txHash, Receipt: r}})) + require.NoError(t, store.SetReceipts(ctx.WithBlockHeight(2), []receipt.ReceiptRecord{{TxHash: txHash, Receipt: r}})) require.Eventually(t, func() bool { _, err := store.GetReceipt(ctx, txHash) @@ -181,7 +181,7 @@ func TestFilterLogs(t *testing.T) { r2 := makeReceipt(txHash2, addr2, []common.Hash{topic2}, 1) r3 := withBloom(makeReceipt(txHash3, addr3, []common.Hash{topic3}, 2)) - err := store.StoreReceipts(ctx.WithBlockHeight(0), []receipt.ReceiptRecord{ + err := store.SetReceipts(ctx.WithBlockHeight(0), []receipt.ReceiptRecord{ {TxHash: txHash1, Receipt: r1}, {TxHash: txHash2, Receipt: r2}, {TxHash: txHash3, Receipt: r3}, diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index 109d43d64e..45d98d0383 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -167,7 +167,7 @@ func (k *Keeper) flushTransientReceipts(ctx sdk.Context) error { if k.receiptStore == nil { return receipt.ErrNotConfigured } - return k.receiptStore.StoreReceipts(ctx, records) + return k.receiptStore.SetReceipts(ctx, records) } // MigrateLegacyReceiptsBatch moves up to batchSize receipts from the legacy KV store From a0358d4e9b027d11f32d2a3d3d5c103954ea9170 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Sun, 18 Jan 2026 13:09:18 -0500 Subject: [PATCH 12/17] fix --- evmrpc/tx_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evmrpc/tx_test.go b/evmrpc/tx_test.go index f8c7586a89..52d36e4d56 100644 --- a/evmrpc/tx_test.go +++ b/evmrpc/tx_test.go @@ -139,7 +139,7 @@ func TestGetVMError(t *testing.T) { resObj := sendRequestGood(t, "getVMError", "0xa16d8f7ea8741acd23f15fc19b0dd26512aff68c01c6260d7c3a51b297399d32") require.Equal(t, "", resObj["result"].(string)) resObj = sendRequestGood(t, "getVMError", "0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872f") - require.Equal(t, "not found", resObj["error"].(map[string]interface{})["message"]) + require.Equal(t, "receipt not found", resObj["error"].(map[string]interface{})["message"]) } func TestGetTransactionReceiptExcludeTraceFail(t *testing.T) { From 26338bae930c61166a8dfa486bbecdcaaa91ce90 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 20 Jan 2026 10:19:21 -0500 Subject: [PATCH 13/17] fix --- giga/deps/xevm/keeper/receipt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/giga/deps/xevm/keeper/receipt.go b/giga/deps/xevm/keeper/receipt.go index 53bcbff8ed..f11d611d45 100644 --- a/giga/deps/xevm/keeper/receipt.go +++ b/giga/deps/xevm/keeper/receipt.go @@ -12,9 +12,9 @@ import ( "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - receiptdb "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/sei-protocol/sei-chain/giga/deps/xevm/state" "github.com/sei-protocol/sei-chain/giga/deps/xevm/types" + receiptdb "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/sei-protocol/sei-chain/utils" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" ) From 670931386f147c2e96fc4f914fe83b62a70434c4 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 20 Jan 2026 11:54:47 -0500 Subject: [PATCH 14/17] fix --- giga/deps/xevm/keeper/receipt_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/giga/deps/xevm/keeper/receipt_test.go b/giga/deps/xevm/keeper/receipt_test.go index 9b19732c08..7358046b7d 100644 --- a/giga/deps/xevm/keeper/receipt_test.go +++ b/giga/deps/xevm/keeper/receipt_test.go @@ -24,7 +24,7 @@ func TestReceipt(t *testing.T) { require.Nil(t, err) require.Equal(t, txHash.Hex(), r.TxHashHex) _, err = k.GetReceipt(ctx, common.Hash{1}) - require.Equal(t, "not found", err.Error()) + require.Equal(t, "receipt not found", err.Error()) } func TestGetReceiptWithRetry(t *testing.T) { @@ -36,7 +36,7 @@ func TestGetReceiptWithRetry(t *testing.T) { nonExistentHash := common.Hash{1} _, err := k.GetReceiptWithRetry(ctx, nonExistentHash, 2) require.NotNil(t, err) - require.Equal(t, "not found", err.Error()) + require.Equal(t, "receipt not found", err.Error()) // Then test successful retry go func() { @@ -99,7 +99,7 @@ func TestDeleteTransientReceipt(t *testing.T) { receipt, err = k.GetTransientReceipt(ctx, txHash, 0) require.Nil(t, receipt) - require.Equal(t, "not found", err.Error()) + require.Equal(t, "receipt not found", err.Error()) } // Flush transient receipts should not adjust cumulative gas used for legacy receipts From 6507cc60d556f45509d14d8c40a859705e113720 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 20 Jan 2026 13:52:44 -0500 Subject: [PATCH 15/17] add parquet receiptdb option --- app/app.go | 23 +- app/seidb.go | 4 + app/test_helpers.go | 9 +- cmd/seid/cmd/app_config.go | 26 +- cmd/seid/cmd/init_test.go | 1 + cmd/seid/cmd/root.go | 1 + go.mod | 2 + go.sum | 20 + sei-db/config/config.go | 24 + sei-db/config/toml.go | 19 +- sei-db/ledger_db/receipt/parquet_reader.go | 329 ++++++++ sei-db/ledger_db/receipt/parquet_store.go | 716 ++++++++++++++++++ sei-db/ledger_db/receipt/receipt_cache.go | 211 ++++++ sei-db/ledger_db/receipt/receipt_store.go | 61 +- .../ledger_db/receipt/receipt_store_test.go | 13 +- 15 files changed, 1413 insertions(+), 46 deletions(-) create mode 100644 sei-db/ledger_db/receipt/parquet_reader.go create mode 100644 sei-db/ledger_db/receipt/parquet_store.go create mode 100644 sei-db/ledger_db/receipt/receipt_cache.go diff --git a/app/app.go b/app/app.go index e5c1e102c6..10ada899b8 100644 --- a/app/app.go +++ b/app/app.go @@ -634,13 +634,24 @@ func New( wasmOpts..., ) - receiptStorePath := filepath.Join(homePath, "data", "receipt.db") - ssConfig := ssconfig.DefaultStateStoreConfig() - ssConfig.KeepRecent = cast.ToInt(appOpts.Get(server.FlagMinRetainBlocks)) - ssConfig.DBDirectory = receiptStorePath - ssConfig.KeepLastVersion = false + receiptConfig := ssconfig.DefaultReceiptStoreConfig() + receiptBackend := strings.TrimSpace(cast.ToString(appOpts.Get(FlagRSBackend))) + if receiptBackend != "" { + receiptConfig.Backend = receiptBackend + } + receiptConfig.DBDirectory = strings.TrimSpace(cast.ToString(appOpts.Get(FlagRSDirectory))) + receiptConfig.KeepRecent = cast.ToInt(appOpts.Get(server.FlagMinRetainBlocks)) + if receiptConfig.DBDirectory == "" { + switch strings.ToLower(receiptConfig.Backend) { + case "parquet": + receiptConfig.DBDirectory = filepath.Join(homePath, "data", "receipt.parquet") + default: + receiptConfig.DBDirectory = filepath.Join(homePath, "data", "receipt.db") + } + } + if app.receiptStore == nil { - receiptStore, err := receipt.NewReceiptStore(logger, ssConfig, keys[evmtypes.StoreKey]) + receiptStore, err := receipt.NewReceiptStore(logger, receiptConfig, keys[evmtypes.StoreKey]) if err != nil { panic(fmt.Sprintf("error while creating receipt store: %s", err)) } diff --git a/app/seidb.go b/app/seidb.go index b4d4729c1e..6e3303869f 100644 --- a/app/seidb.go +++ b/app/seidb.go @@ -35,6 +35,10 @@ const ( FlagSSPruneInterval = "state-store.ss-prune-interval" FlagSSImportNumWorkers = "state-store.ss-import-num-workers" + // Receipt store configs + FlagRSBackend = "receipt-store.rs-backend" + FlagRSDirectory = "receipt-store.rs-db-directory" + // Other configs FlagSnapshotInterval = "state-sync.snapshot-interval" FlagMigrateIAVL = "migrate-iavl" diff --git a/app/test_helpers.go b/app/test_helpers.go index e295432b15..9dc9811efb 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -258,11 +258,10 @@ func setupReceiptStore(storeKey sdk.StoreKey) (receipt.ReceiptStore, error) { return nil, err } - ssConfig := ssconfig.DefaultStateStoreConfig() - ssConfig.KeepRecent = 0 // No min retain blocks in test - ssConfig.DBDirectory = tempDir - ssConfig.KeepLastVersion = false - receiptStore, err := receipt.NewReceiptStore(log.NewNopLogger(), ssConfig, storeKey) + receiptConfig := ssconfig.DefaultReceiptStoreConfig() + receiptConfig.KeepRecent = 0 // No min retain blocks in test + receiptConfig.DBDirectory = tempDir + receiptStore, err := receipt.NewReceiptStore(log.NewNopLogger(), receiptConfig, storeKey) if err != nil { return nil, err } diff --git a/cmd/seid/cmd/app_config.go b/cmd/seid/cmd/app_config.go index 6eb2deff47..667ef6acaf 100644 --- a/cmd/seid/cmd/app_config.go +++ b/cmd/seid/cmd/app_config.go @@ -22,23 +22,25 @@ type WASMConfig struct { type CustomAppConfig struct { srvconfig.Config - StateCommit seidbconfig.StateCommitConfig `mapstructure:"state-commit"` - StateStore seidbconfig.StateStoreConfig `mapstructure:"state-store"` - WASM WASMConfig `mapstructure:"wasm"` - EVM evmrpcconfig.Config `mapstructure:"evm"` - GigaExecutor gigaconfig.Config `mapstructure:"giga_executor"` - ETHReplay replay.Config `mapstructure:"eth_replay"` - ETHBlockTest blocktest.Config `mapstructure:"eth_block_test"` - EvmQuery querier.Config `mapstructure:"evm_query"` - LightInvariance seiapp.LightInvarianceConfig `mapstructure:"light_invariance"` + StateCommit seidbconfig.StateCommitConfig `mapstructure:"state-commit"` + StateStore seidbconfig.StateStoreConfig `mapstructure:"state-store"` + ReceiptStore seidbconfig.ReceiptStoreConfig `mapstructure:"receipt-store"` + WASM WASMConfig `mapstructure:"wasm"` + EVM evmrpcconfig.Config `mapstructure:"evm"` + GigaExecutor gigaconfig.Config `mapstructure:"giga_executor"` + ETHReplay replay.Config `mapstructure:"eth_replay"` + ETHBlockTest blocktest.Config `mapstructure:"eth_block_test"` + EvmQuery querier.Config `mapstructure:"evm_query"` + LightInvariance seiapp.LightInvarianceConfig `mapstructure:"light_invariance"` } // NewCustomAppConfig creates a CustomAppConfig with the given base config and EVM config func NewCustomAppConfig(baseConfig *srvconfig.Config, evmConfig evmrpcconfig.Config) CustomAppConfig { return CustomAppConfig{ - Config: *baseConfig, - StateCommit: seidbconfig.DefaultStateCommitConfig(), - StateStore: seidbconfig.DefaultStateStoreConfig(), + Config: *baseConfig, + StateCommit: seidbconfig.DefaultStateCommitConfig(), + StateStore: seidbconfig.DefaultStateStoreConfig(), + ReceiptStore: seidbconfig.DefaultReceiptStoreConfig(), WASM: WASMConfig{ QueryGasLimit: 300000, LruSize: 1, diff --git a/cmd/seid/cmd/init_test.go b/cmd/seid/cmd/init_test.go index 2cabb25df8..e39cca7532 100644 --- a/cmd/seid/cmd/init_test.go +++ b/cmd/seid/cmd/init_test.go @@ -144,6 +144,7 @@ func TestInitModeConfiguration(t *testing.T) { // Build custom template with all sections customAppTemplate := srvconfig.ManualConfigTemplate + seidbconfig.StateCommitConfigTemplate + seidbconfig.StateStoreConfigTemplate + + seidbconfig.ReceiptStoreConfigTemplate + srvconfig.AutoManagedConfigTemplate // Simplified - just need the pruning config srvconfig.SetConfigTemplate(customAppTemplate) diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index 2ead64ad60..5e0f0eeee0 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -439,6 +439,7 @@ func initAppConfig() (string, interface{}) { customAppTemplate := serverconfig.ManualConfigTemplate + seidbconfig.StateCommitConfigTemplate + seidbconfig.StateStoreConfigTemplate + + seidbconfig.ReceiptStoreConfigTemplate + evmrpcconfig.ConfigTemplate + gigaconfig.ConfigTemplate + serverconfig.AutoManagedConfigTemplate + ` diff --git a/go.mod b/go.mod index a78cc97314..8416496e7b 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/cosmos/cosmos-sdk v0.45.10 github.com/cosmos/go-bip39 v1.0.0 github.com/dvsekhvalnov/jose2go v1.5.0 + github.com/duckdb/duckdb-go/v2 v2.5.3 github.com/ethereum/evmc/v12 v12.1.0 github.com/ethereum/go-ethereum v1.16.1 github.com/go-playground/validator/v10 v10.11.1 @@ -32,6 +33,7 @@ require ( github.com/ledgerwatch/erigon-lib v0.0.0-20230210071639-db0e7ed11263 github.com/linxGnu/grocksdb v1.8.11 github.com/mitchellh/mapstructure v1.5.0 + github.com/parquet-go/parquet-go v0.25.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.23.0 github.com/rakyll/statik v0.1.7 diff --git a/go.sum b/go.sum index a01d843507..359e9fcf24 100644 --- a/go.sum +++ b/go.sum @@ -3131,3 +3131,23 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +github.com/duckdb/duckdb-go-bindings v0.1.23 h1:sJRXraxfC/gdHI2T7oHqrdp1VdKemrgqWGQ8986mH1c= +github.com/duckdb/duckdb-go-bindings v0.1.23/go.mod h1:WA7U/o+b37MK2kiOPPueVZ+FIxt5AZFCjszi8hHeH18= +github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.23 h1:Xyw1fWu4jzOtv2Hqkaehr7f+qbIWNRfBMbZyD+g8dyU= +github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.23/go.mod h1:jfbOHwGZqNCpMAxV4g4g5jmWr0gKdMvh2fGusPubxC4= +github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.23 h1:85Xomx5NxZ+Nt+VepUJzuMYbBTH+nB6JlBXIyJuTovA= +github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.23/go.mod h1:zLVtv1a7TBuTPvuAi32AIbnuw7jjaX5JElZ+urv1ydc= +github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.23 h1:RGw8mDqQl9JdlCYV0PAfGBuVAgOguiL5Vz5W8pH8fGw= +github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.23/go.mod h1:GCaBoYnuLZEva7BXzdXehTbqh9VSvpLB80xcmxGBGs8= +github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.23 h1:f8NHa8DGes7vg55BxeMVm0ycddEJTRHEt813USdL0/I= +github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.23/go.mod h1:kpQSpJmDSSZQ3ikbZR1/8UqecqMeUkWFjFX2xZxlCuI= +github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.23 h1:HJqVo+09gT6LQWW6PlN/c7K8s0eQhv5giE7kJcMGMSU= +github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.23/go.mod h1:wa+egSGXTPS16NPADFCK1yFyt3VSXxUS6Pt2fLnvRPM= +github.com/duckdb/duckdb-go/arrowmapping v0.0.26 h1:XKhWpNkLtIbcBE2vnKm7FaAju3daplxo8MJIXOAY/Zg= +github.com/duckdb/duckdb-go/arrowmapping v0.0.26/go.mod h1:R7egXxZcy0hxKY/MsoM2xjkMvRo4H07TffDhYCnhKfQ= +github.com/duckdb/duckdb-go/mapping v0.0.25 h1:z4RhivKCIRv0MWQwtYekqH+ikoA29/n8L+rzgreKvsc= +github.com/duckdb/duckdb-go/mapping v0.0.25/go.mod h1:CIo3WbNx3Txl+VO9+P5eNCN9ZifUA/KIp9NY1rTG/uo= +github.com/duckdb/duckdb-go/v2 v2.5.3 h1:GlT+bXW+/gCYo0Q8P9L6IvvKRzMM0/tDXj5fKkoAfCM= +github.com/duckdb/duckdb-go/v2 v2.5.3/go.mod h1:+mGhZCF5tHYIdBWrp7+KGj6JnTXdm+sBTh3ZSLhXorE= +github.com/parquet-go/parquet-go v0.25.1 h1:l7jJwNM0xrk0cnIIptWMtnSnuxRkwq53S+Po3KG8Xgo= +github.com/parquet-go/parquet-go v0.25.1/go.mod h1:AXBuotO1XiBtcqJb/FKFyjBG4aqa3aQAAWF3ZPzCanY= diff --git a/sei-db/config/config.go b/sei-db/config/config.go index 32ad8d6435..2667834904 100644 --- a/sei-db/config/config.go +++ b/sei-db/config/config.go @@ -110,6 +110,24 @@ type StateStoreConfig struct { UseDefaultComparer bool `mapstructure:"use-default-comparer"` } +type ReceiptStoreConfig struct { + // DBDirectory defines the directory to store the receipt db files. + // If not explicitly set, default to application home directory. + DBDirectory string `mapstructure:"db-directory"` + + // Backend defines the backend used for receipt storage. + // Supported backends: pebble, pebbledb, parquet. + // defaults to pebble. + Backend string `mapstructure:"backend"` + + // KeepRecent defines the number of versions to keep in receipt store. + // Setting it to 0 means keep everything. + KeepRecent int `mapstructure:"keep-recent"` + + // PruneIntervalSeconds defines the interval in seconds to trigger pruning. + PruneIntervalSeconds int `mapstructure:"prune-interval-seconds"` +} + func DefaultStateCommitConfig() StateCommitConfig { return StateCommitConfig{ Enable: true, @@ -133,3 +151,9 @@ func DefaultStateStoreConfig() StateStoreConfig { UseDefaultComparer: false, // TODO: flip to true once MVCCComparer is deprecated } } + +func DefaultReceiptStoreConfig() ReceiptStoreConfig { + return ReceiptStoreConfig{ + Backend: "pebble", + } +} diff --git a/sei-db/config/toml.go b/sei-db/config/toml.go index c57277344d..bb7d1412d6 100644 --- a/sei-db/config/toml.go +++ b/sei-db/config/toml.go @@ -92,5 +92,22 @@ ss-prune-interval = {{ .StateStore.PruneIntervalSeconds }} ss-import-num-workers = {{ .StateStore.ImportNumWorkers }} ` +// ReceiptStoreConfigTemplate defines the configuration template for receipt-store +const ReceiptStoreConfigTemplate = ` +############################################################################### +### Receipt Store Configuration ### +############################################################################### + +[receipt-store] +# Backend defines the receipt store backend. +# Supported backends: pebble, parquet +# defaults to pebble +rs-backend = "{{ .ReceiptStore.Backend }}" + +# Defines the directory to store the receipt db files. +# If not explicitly set, default to application home directory. +rs-db-directory = "{{ .ReceiptStore.DBDirectory }}" +` + // DefaultConfigTemplate combines both templates for backward compatibility -const DefaultConfigTemplate = StateCommitConfigTemplate + StateStoreConfigTemplate +const DefaultConfigTemplate = StateCommitConfigTemplate + StateStoreConfigTemplate + ReceiptStoreConfigTemplate diff --git a/sei-db/ledger_db/receipt/parquet_reader.go b/sei-db/ledger_db/receipt/parquet_reader.go new file mode 100644 index 0000000000..9daa5f4cc3 --- /dev/null +++ b/sei-db/ledger_db/receipt/parquet_reader.go @@ -0,0 +1,329 @@ +package receipt + +import ( + "context" + "database/sql" + "fmt" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "sync" + + "github.com/duckdb/duckdb-go/v2" + "github.com/ethereum/go-ethereum/common" +) + +type parquetReader struct { + db *sql.DB + basePath string + mu sync.RWMutex + closedReceiptFiles []string + closedLogFiles []string +} + +func newParquetReader(basePath string) (*parquetReader, error) { + connector, err := duckdb.NewConnector("", nil) + if err != nil { + return nil, fmt.Errorf("failed to create DuckDB connector: %w", err) + } + + db := sql.OpenDB(connector) + numCPU := runtime.NumCPU() + db.SetMaxOpenConns(numCPU * 2) + db.SetMaxIdleConns(numCPU) + + _, _ = db.Exec(fmt.Sprintf("SET threads TO %d", numCPU)) + _, _ = db.Exec("SET memory_limit = '20GB'") + _, _ = db.Exec("SET enable_object_cache = true") + _, _ = db.Exec("SET parquet_metadata_cache_size = 500") + _, _ = db.Exec("SET access_mode = 'READ_ONLY'") + _, _ = db.Exec("SET enable_progress_bar = false") + _, _ = db.Exec("SET preserve_insertion_order = false") + + reader := &parquetReader{ + db: db, + basePath: basePath, + } + reader.scanExistingFiles() + return reader, nil +} + +func (r *parquetReader) Close() error { + return r.db.Close() +} + +func (r *parquetReader) scanExistingFiles() { + r.mu.Lock() + defer r.mu.Unlock() + + receiptFiles, _ := filepath.Glob(filepath.Join(r.basePath, "receipts_*.parquet")) + r.closedReceiptFiles, _ = r.validateFiles(receiptFiles) + + logFiles, _ := filepath.Glob(filepath.Join(r.basePath, "logs_*.parquet")) + r.closedLogFiles, _ = r.validateFiles(logFiles) +} + +func (r *parquetReader) validateFiles(files []string) ([]string, string) { + if len(files) == 0 { + return nil, "" + } + + sort.Slice(files, func(i, j int) bool { + return extractBlockNumber(files[i]) < extractBlockNumber(files[j]) + }) + + lastFile := files[len(files)-1] + if r.isFileReadable(lastFile) { + return files, "" + } + return files[:len(files)-1], lastFile +} + +func (r *parquetReader) isFileReadable(path string) bool { + _, err := r.db.Exec(fmt.Sprintf("SELECT 1 FROM read_parquet('%s') LIMIT 1", path)) + return err == nil +} + +func (r *parquetReader) onFileRotation(closedFileStartBlock uint64) { + r.mu.Lock() + defer r.mu.Unlock() + + receiptFile := filepath.Join(r.basePath, fmt.Sprintf("receipts_%d.parquet", closedFileStartBlock)) + logFile := filepath.Join(r.basePath, fmt.Sprintf("logs_%d.parquet", closedFileStartBlock)) + r.closedReceiptFiles = append(r.closedReceiptFiles, receiptFile) + r.closedLogFiles = append(r.closedLogFiles, logFile) +} + +func (r *parquetReader) closedReceiptFileCount() int { + r.mu.RLock() + defer r.mu.RUnlock() + return len(r.closedReceiptFiles) +} + +func (r *parquetReader) maxReceiptBlockNumber(ctx context.Context) (uint64, bool, error) { + r.mu.RLock() + closedFiles := r.closedReceiptFiles + r.mu.RUnlock() + if len(closedFiles) == 0 { + return 0, false, nil + } + + var parquetFiles string + if len(closedFiles) == 1 { + parquetFiles = fmt.Sprintf("'%s'", closedFiles[0]) + } else { + parquetFiles = fmt.Sprintf("[%s]", joinQuoted(closedFiles)) + } + + query := fmt.Sprintf("SELECT MAX(block_number) FROM read_parquet(%s, union_by_name=true)", parquetFiles) + row := r.db.QueryRowContext(ctx, query) + var max sql.NullInt64 + if err := row.Scan(&max); err != nil { + return 0, false, fmt.Errorf("failed to query max block number: %w", err) + } + if !max.Valid { + return 0, false, nil + } + return uint64(max.Int64), true, nil +} + +type receiptResult struct { + TxHash []byte + BlockNumber uint64 + ReceiptBytes []byte +} + +func (r *parquetReader) getReceiptByTxHash(ctx context.Context, txHash common.Hash) (*receiptResult, error) { + r.mu.RLock() + closedFiles := r.closedReceiptFiles + r.mu.RUnlock() + + if len(closedFiles) == 0 { + return nil, nil + } + + var parquetFiles string + if len(closedFiles) == 1 { + parquetFiles = fmt.Sprintf("'%s'", closedFiles[0]) + } else { + parquetFiles = fmt.Sprintf("[%s]", joinQuoted(closedFiles)) + } + + query := fmt.Sprintf(` + SELECT + tx_hash, block_number, receipt_bytes + FROM read_parquet(%s, union_by_name=true) + WHERE tx_hash = $1 + LIMIT 1 + `, parquetFiles) + + row := r.db.QueryRowContext(ctx, query, txHash[:]) + var rec receiptResult + if err := row.Scan(&rec.TxHash, &rec.BlockNumber, &rec.ReceiptBytes); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("failed to query receipt: %w", err) + } + return &rec, nil +} + +type logFilter struct { + FromBlock *uint64 + ToBlock *uint64 + Addresses []common.Address + Topics [][]common.Hash + Limit int +} + +type logResult struct { + BlockNumber uint64 + TxHash []byte + TxIndex uint32 + LogIndex uint32 + Address []byte + Topic0 []byte + Topic1 []byte + Topic2 []byte + Topic3 []byte + Data []byte + BlockHash []byte + Removed bool +} + +func (r *parquetReader) getLogs(ctx context.Context, filter logFilter) ([]logResult, error) { + r.mu.RLock() + closedFiles := r.closedLogFiles + r.mu.RUnlock() + + if len(closedFiles) == 0 { + return nil, nil + } + + var files []string + for _, f := range closedFiles { + startBlock := extractBlockNumber(f) + if filter.ToBlock != nil && startBlock > *filter.ToBlock { + continue + } + files = append(files, f) + } + if len(files) == 0 { + return nil, nil + } + + return r.queryLogFiles(ctx, files, filter) +} + +func (r *parquetReader) queryLogFiles(ctx context.Context, files []string, filter logFilter) ([]logResult, error) { + var parquetFiles string + if len(files) == 1 { + parquetFiles = fmt.Sprintf("'%s'", files[0]) + } else { + parquetFiles = fmt.Sprintf("[%s]", joinQuoted(files)) + } + + query := fmt.Sprintf(` + SELECT + block_number, tx_hash, tx_index, log_index, address, + topic0, topic1, topic2, topic3, data, block_hash, removed + FROM read_parquet(%s, union_by_name=true) + WHERE 1=1 + `, parquetFiles) + + var args []any + argIdx := 1 + + if filter.FromBlock != nil { + query += fmt.Sprintf(" AND block_number >= $%d", argIdx) + args = append(args, *filter.FromBlock) + argIdx++ + } + + if filter.ToBlock != nil { + query += fmt.Sprintf(" AND block_number <= $%d", argIdx) + args = append(args, *filter.ToBlock) + argIdx++ + } + + if len(filter.Addresses) > 0 { + placeholders := make([]string, len(filter.Addresses)) + for i, addr := range filter.Addresses { + placeholders[i] = fmt.Sprintf("$%d", argIdx) + args = append(args, addr[:]) + argIdx++ + } + query += fmt.Sprintf(" AND address IN (%s)", strings.Join(placeholders, ", ")) + } + + topicCols := []string{"topic0", "topic1", "topic2", "topic3"} + for i, topicList := range filter.Topics { + if i >= 4 { + break + } + if len(topicList) == 0 { + continue + } + + if len(topicList) == 1 { + query += fmt.Sprintf(" AND %s = $%d", topicCols[i], argIdx) + args = append(args, topicList[0][:]) + argIdx++ + } else { + placeholders := make([]string, len(topicList)) + for j, topic := range topicList { + placeholders[j] = fmt.Sprintf("$%d", argIdx) + args = append(args, topic[:]) + argIdx++ + } + query += fmt.Sprintf(" AND %s IN (%s)", topicCols[i], strings.Join(placeholders, ", ")) + } + } + + query += " ORDER BY block_number, tx_index, log_index" + if filter.Limit > 0 { + query += fmt.Sprintf(" LIMIT %d", filter.Limit) + } + + rows, err := r.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("failed to query logs: %w", err) + } + defer rows.Close() + + var results []logResult + for rows.Next() { + var log logResult + if err := rows.Scan( + &log.BlockNumber, &log.TxHash, &log.TxIndex, &log.LogIndex, + &log.Address, &log.Topic0, &log.Topic1, &log.Topic2, &log.Topic3, + &log.Data, &log.BlockHash, &log.Removed, + ); err != nil { + return nil, fmt.Errorf("failed to scan log: %w", err) + } + results = append(results, log) + } + + return results, rows.Err() +} + +func extractBlockNumber(path string) uint64 { + base := filepath.Base(path) + base = strings.TrimSuffix(base, ".parquet") + parts := strings.Split(base, "_") + if len(parts) < 2 { + return 0 + } + num, _ := strconv.ParseUint(parts[len(parts)-1], 10, 64) + return num +} + +func joinQuoted(files []string) string { + quoted := make([]string, len(files)) + for i, f := range files { + quoted[i] = fmt.Sprintf("'%s'", f) + } + return strings.Join(quoted, ", ") +} diff --git a/sei-db/ledger_db/receipt/parquet_store.go b/sei-db/ledger_db/receipt/parquet_store.go new file mode 100644 index 0000000000..1740b2130f --- /dev/null +++ b/sei-db/ledger_db/receipt/parquet_store.go @@ -0,0 +1,716 @@ +package receipt + +import ( + "context" + "fmt" + "os" + "path/filepath" + "sync" + "sync/atomic" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/parquet-go/parquet-go" + dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" + dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +type receiptRecord struct { + TxHash []byte `parquet:"tx_hash"` + BlockNumber uint64 `parquet:"block_number"` + ReceiptBytes []byte `parquet:"receipt_bytes"` +} + +type logRecord struct { + BlockNumber uint64 `parquet:"block_number"` + TxHash []byte `parquet:"tx_hash"` + TxIndex uint32 `parquet:"tx_index"` + LogIndex uint32 `parquet:"log_index"` + Address []byte `parquet:"address"` + BlockHash []byte `parquet:"block_hash"` + Removed bool `parquet:"removed"` + + Topic0 []byte `parquet:"topic0"` + Topic1 []byte `parquet:"topic1"` + Topic2 []byte `parquet:"topic2"` + Topic3 []byte `parquet:"topic3"` + + Data []byte `parquet:"data"` +} + +type parquetStoreConfig struct { + BlockFlushInterval uint64 + MaxBlocksPerFile uint64 +} + +func defaultParquetStoreConfig() parquetStoreConfig { + return parquetStoreConfig{ + BlockFlushInterval: 1, + MaxBlocksPerFile: 500, + } +} + +type parquetReceiptStore struct { + basePath string + receiptWriter *parquet.GenericWriter[receiptRecord] + logWriter *parquet.GenericWriter[logRecord] + receiptFile *os.File + logFile *os.File + + mu sync.Mutex + fileStartBlock uint64 + receiptsBuffer []receiptRecord + logsBuffer []logRecord + config parquetStoreConfig + lastSeenBlock uint64 + blocksSinceFlush uint64 + blocksInFile uint64 + + reader *parquetReader + cache *ledgerCache + storeKey sdk.StoreKey + latestVersion atomic.Int64 + earliestVersion atomic.Int64 + currentFileStart atomic.Uint64 + closeOnce sync.Once +} + +func newParquetReceiptStore(_ dbLogger.Logger, cfg dbconfig.ReceiptStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { + if err := os.MkdirAll(cfg.DBDirectory, 0o755); err != nil { + return nil, fmt.Errorf("failed to create parquet base directory: %w", err) + } + + reader, err := newParquetReader(cfg.DBDirectory) + if err != nil { + return nil, err + } + + store := &parquetReceiptStore{ + basePath: cfg.DBDirectory, + receiptsBuffer: make([]receiptRecord, 0, 1000), + logsBuffer: make([]logRecord, 0, 10000), + config: defaultParquetStoreConfig(), + reader: reader, + cache: newLedgerCache(), + storeKey: storeKey, + } + + if maxBlock, ok, err := reader.maxReceiptBlockNumber(context.Background()); err != nil { + return nil, err + } else if ok { + store.latestVersion.Store(int64(maxBlock)) + if maxBlock < ^uint64(0) { + store.fileStartBlock = maxBlock + 1 + } + } + + if reader.closedReceiptFileCount() == 0 { + store.fileStartBlock = 0 + } + store.currentFileStart.Store(store.fileStartBlock) + + if err := store.initWriters(); err != nil { + return nil, err + } + + return store, nil +} + +func (s *parquetReceiptStore) LatestVersion() int64 { + if s == nil { + return 0 + } + return s.latestVersion.Load() +} + +func (s *parquetReceiptStore) SetLatestVersion(version int64) error { + if s == nil { + return ErrNotConfigured + } + s.latestVersion.Store(version) + return nil +} + +func (s *parquetReceiptStore) SetEarliestVersion(version int64) error { + if s == nil { + return ErrNotConfigured + } + s.earliestVersion.Store(version) + return nil +} + +func (s *parquetReceiptStore) GetReceipt(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { + if s == nil { + return nil, ErrNotConfigured + } + + if s.cache != nil { + if receipt, ok := s.cache.GetReceipt(txHash); ok { + return receipt, nil + } + } + + if s.reader != nil { + result, err := s.reader.getReceiptByTxHash(ctx.Context(), txHash) + if err != nil { + return nil, err + } + if result != nil { + receipt := &types.Receipt{} + if err := receipt.Unmarshal(result.ReceiptBytes); err != nil { + return nil, err + } + return receipt, nil + } + } + + store := ctx.KVStore(s.storeKey) + bz := store.Get(types.ReceiptKey(txHash)) + if bz == nil { + return nil, ErrNotFound + } + var r types.Receipt + if err := r.Unmarshal(bz); err != nil { + return nil, err + } + return &r, nil +} + +func (s *parquetReceiptStore) GetReceiptFromStore(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error) { + if s == nil { + return nil, ErrNotConfigured + } + + if s.cache != nil { + if receipt, ok := s.cache.GetReceipt(txHash); ok { + return receipt, nil + } + } + + if s.reader == nil { + return nil, ErrNotFound + } + + result, err := s.reader.getReceiptByTxHash(ctx.Context(), txHash) + if err != nil { + return nil, err + } + if result == nil { + return nil, ErrNotFound + } + + receipt := &types.Receipt{} + if err := receipt.Unmarshal(result.ReceiptBytes); err != nil { + return nil, err + } + return receipt, nil +} + +func (s *parquetReceiptStore) SetReceipts(ctx sdk.Context, receipts []ReceiptRecord) error { + if s == nil { + return ErrNotConfigured + } + + if len(receipts) == 0 { + return nil + } + + blockHash := common.Hash{} + + inputs := make([]parquetReceiptInput, 0, len(receipts)) + cacheBatches := make([]cacheBatch, 0) + + var ( + currentBlock uint64 + logStartIndex uint + cacheEntries []receiptCacheEntry + cacheLogs []*ethtypes.Log + maxBlock uint64 + ) + + flushCacheBatch := func(blockNumber uint64) { + if len(cacheEntries) == 0 && len(cacheLogs) == 0 { + return + } + cacheBatches = append(cacheBatches, cacheBatch{ + blockNumber: blockNumber, + entries: cacheEntries, + logs: cacheLogs, + }) + cacheEntries = nil + cacheLogs = nil + } + + for _, record := range receipts { + if record.Receipt == nil { + continue + } + + receipt := record.Receipt + blockNumber := receipt.BlockNumber + if blockNumber > maxBlock { + maxBlock = blockNumber + } + + if currentBlock == 0 { + currentBlock = blockNumber + } + if blockNumber != currentBlock { + flushCacheBatch(currentBlock) + currentBlock = blockNumber + logStartIndex = 0 + } + + receiptBytes, err := receipt.Marshal() + if err != nil { + return err + } + + txLogs := getLogsForTx(receipt, logStartIndex) + logStartIndex += uint(len(txLogs)) + for _, lg := range txLogs { + lg.BlockHash = blockHash + } + + inputs = append(inputs, parquetReceiptInput{ + blockNumber: blockNumber, + receipt: receiptRecord{ + TxHash: copyBytes(record.TxHash[:]), + BlockNumber: blockNumber, + ReceiptBytes: copyBytesOrEmpty(receiptBytes), + }, + logs: buildLogRecords(txLogs, blockHash), + }) + + cacheEntries = append(cacheEntries, receiptCacheEntry{ + TxHash: record.TxHash, + Receipt: receipt, + }) + cacheLogs = append(cacheLogs, txLogs...) + } + flushCacheBatch(currentBlock) + + s.mu.Lock() + for i := range inputs { + if err := s.applyReceiptLocked(inputs[i]); err != nil { + s.mu.Unlock() + return err + } + } + s.mu.Unlock() + + if maxBlock > 0 { + s.latestVersion.Store(int64(maxBlock)) + } + + if s.cache != nil { + for i := range cacheBatches { + s.cache.AddReceiptsBatch(cacheBatches[i].blockNumber, cacheBatches[i].entries) + s.cache.AddLogsForBlock(cacheBatches[i].blockNumber, cacheBatches[i].logs) + } + } + + return nil +} + +func (s *parquetReceiptStore) FilterLogs(ctx sdk.Context, blockHeight int64, blockHash common.Hash, txHashes []common.Hash, crit filters.FilterCriteria, applyExactMatch bool) ([]*ethtypes.Log, error) { + if s == nil { + return nil, ErrNotConfigured + } + if len(txHashes) == 0 { + return []*ethtypes.Log{}, nil + } + + blockNumber := uint64(blockHeight) + if applyExactMatch { + if s.cache != nil && s.useCacheForBlock(blockNumber) { + logs := s.cache.GetLogsWithFilter(blockNumber, blockNumber, crit.Addresses, crit.Topics) + for _, lg := range logs { + lg.BlockHash = blockHash + lg.BlockNumber = blockNumber + } + return logs, nil + } + + if s.reader != nil && !s.useCacheForBlock(blockNumber) { + filter := logFilter{ + FromBlock: &blockNumber, + ToBlock: &blockNumber, + Addresses: crit.Addresses, + Topics: crit.Topics, + } + results, err := s.reader.getLogs(ctx.Context(), filter) + if err != nil { + return nil, err + } + logs := make([]*ethtypes.Log, 0, len(results)) + for i := range results { + lr := results[i] + logEntry := ðtypes.Log{ + BlockNumber: lr.BlockNumber, + TxHash: common.BytesToHash(lr.TxHash), + TxIndex: uint(lr.TxIndex), + Index: uint(lr.LogIndex), + Data: lr.Data, + Removed: lr.Removed, + BlockHash: blockHash, + } + copy(logEntry.Address[:], lr.Address) + logEntry.Topics = buildTopicsFromLogResult(lr) + logEntry.BlockNumber = blockNumber + logs = append(logs, logEntry) + } + return logs, nil + } + } + + return s.filterLogsFromReceipts(ctx, blockHeight, blockHash, txHashes, crit, applyExactMatch) +} + +func (s *parquetReceiptStore) Close() error { + if s == nil { + return nil + } + + var err error + s.closeOnce.Do(func() { + s.mu.Lock() + defer s.mu.Unlock() + + if flushErr := s.flushLocked(); flushErr != nil { + err = flushErr + return + } + if closeErr := s.closeWritersLocked(); closeErr != nil { + err = closeErr + return + } + if s.reader != nil { + if closeErr := s.reader.Close(); closeErr != nil { + err = closeErr + } + } + }) + + return err +} + +type parquetReceiptInput struct { + blockNumber uint64 + receipt receiptRecord + logs []logRecord +} + +type cacheBatch struct { + blockNumber uint64 + entries []receiptCacheEntry + logs []*ethtypes.Log +} + +func (s *parquetReceiptStore) applyReceiptLocked(input parquetReceiptInput) error { + blockNumber := input.blockNumber + isNewBlock := blockNumber != s.lastSeenBlock + if isNewBlock { + if s.lastSeenBlock != 0 { + s.blocksSinceFlush++ + s.blocksInFile++ + } + s.lastSeenBlock = blockNumber + } + + s.receiptsBuffer = append(s.receiptsBuffer, input.receipt) + if len(input.logs) > 0 { + s.logsBuffer = append(s.logsBuffer, input.logs...) + } + + if s.config.BlockFlushInterval > 0 && s.blocksSinceFlush >= s.config.BlockFlushInterval { + if err := s.flushLocked(); err != nil { + return err + } + s.blocksSinceFlush = 0 + } + + if isNewBlock && s.shouldRotateFile() { + if err := s.rotateFileLocked(blockNumber); err != nil { + return err + } + } + + return nil +} + +func (s *parquetReceiptStore) shouldRotateFile() bool { + if s.config.MaxBlocksPerFile > 0 && s.blocksInFile >= s.config.MaxBlocksPerFile { + return true + } + return false +} + +func (s *parquetReceiptStore) rotateFileLocked(newBlockNumber uint64) error { + if err := s.flushLocked(); err != nil { + return err + } + + oldStartBlock := s.fileStartBlock + if err := s.closeWritersLocked(); err != nil { + return err + } + + if s.reader != nil { + s.reader.onFileRotation(oldStartBlock) + } + if s.cache != nil { + s.cache.Rotate() + } + + s.fileStartBlock = newBlockNumber + s.currentFileStart.Store(newBlockNumber) + s.blocksInFile = 0 + + return s.initWriters() +} + +func (s *parquetReceiptStore) initWriters() error { + receiptPath := filepath.Join(s.basePath, fmt.Sprintf("receipts_%d.parquet", s.fileStartBlock)) + logPath := filepath.Join(s.basePath, fmt.Sprintf("logs_%d.parquet", s.fileStartBlock)) + + receiptFile, err := os.Create(receiptPath) + if err != nil { + return fmt.Errorf("failed to create receipt parquet file: %w", err) + } + + logFile, err := os.Create(logPath) + if err != nil { + receiptFile.Close() + return fmt.Errorf("failed to create log parquet file: %w", err) + } + + blockNumberSorting := parquet.SortingWriterConfig( + parquet.SortingColumns(parquet.Ascending("block_number")), + ) + + receiptWriter := parquet.NewGenericWriter[receiptRecord](receiptFile, + parquet.Compression(&parquet.Snappy), + blockNumberSorting, + ) + logWriter := parquet.NewGenericWriter[logRecord](logFile, + parquet.Compression(&parquet.Snappy), + blockNumberSorting, + ) + + s.receiptFile = receiptFile + s.logFile = logFile + s.receiptWriter = receiptWriter + s.logWriter = logWriter + + return nil +} + +func (s *parquetReceiptStore) flushLocked() error { + if len(s.receiptsBuffer) == 0 { + return nil + } + + if _, err := s.receiptWriter.Write(s.receiptsBuffer); err != nil { + return fmt.Errorf("failed to write receipts to parquet: %w", err) + } + if err := s.receiptWriter.Flush(); err != nil { + return fmt.Errorf("failed to flush receipt parquet writer: %w", err) + } + + if len(s.logsBuffer) > 0 { + if _, err := s.logWriter.Write(s.logsBuffer); err != nil { + return fmt.Errorf("failed to write logs to parquet: %w", err) + } + if err := s.logWriter.Flush(); err != nil { + return fmt.Errorf("failed to flush log parquet writer: %w", err) + } + } + + s.receiptsBuffer = s.receiptsBuffer[:0] + s.logsBuffer = s.logsBuffer[:0] + return nil +} + +func (s *parquetReceiptStore) closeWritersLocked() error { + var errs []error + + if s.receiptWriter != nil { + if err := s.receiptWriter.Close(); err != nil { + errs = append(errs, fmt.Errorf("receipt writer: %w", err)) + } + } + if s.logWriter != nil { + if err := s.logWriter.Close(); err != nil { + errs = append(errs, fmt.Errorf("log writer: %w", err)) + } + } + if s.receiptFile != nil { + if err := s.receiptFile.Close(); err != nil { + errs = append(errs, fmt.Errorf("receipt file: %w", err)) + } + } + if s.logFile != nil { + if err := s.logFile.Close(); err != nil { + errs = append(errs, fmt.Errorf("log file: %w", err)) + } + } + + if len(errs) > 0 { + return fmt.Errorf("close errors: %v", errs) + } + return nil +} + +func (s *parquetReceiptStore) useCacheForBlock(blockNumber uint64) bool { + current := s.currentFileStart.Load() + if current == 0 { + return true + } + return blockNumber >= current +} + +func (s *parquetReceiptStore) filterLogsFromReceipts(ctx sdk.Context, blockHeight int64, blockHash common.Hash, txHashes []common.Hash, crit filters.FilterCriteria, applyExactMatch bool) ([]*ethtypes.Log, error) { + hasFilters := len(crit.Addresses) != 0 || len(crit.Topics) != 0 + var filterIndexes [][]bloomIndexes + if hasFilters { + filterIndexes = encodeFilters(crit.Addresses, crit.Topics) + } + + logs := make([]*ethtypes.Log, 0) + totalLogs := uint(0) + evmTxIndex := 0 + + for _, txHash := range txHashes { + receipt, err := s.GetReceipt(ctx, txHash) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("collectLogs: unable to find receipt for hash %s", txHash.Hex())) + continue + } + + txLogs := getLogsForTx(receipt, totalLogs) + + if hasFilters { + if len(receipt.LogsBloom) == 0 || matchFilters(ethtypes.Bloom(receipt.LogsBloom), filterIndexes) { + if applyExactMatch { + for _, log := range txLogs { + log.TxIndex = uint(evmTxIndex) //nolint:gosec + log.BlockNumber = uint64(blockHeight) //nolint:gosec + log.BlockHash = blockHash + if isLogExactMatch(log, crit) { + logs = append(logs, log) + } + } + } else { + for _, log := range txLogs { + log.TxIndex = uint(evmTxIndex) //nolint:gosec + log.BlockNumber = uint64(blockHeight) //nolint:gosec + log.BlockHash = blockHash + logs = append(logs, log) + } + } + } + } else { + for _, log := range txLogs { + log.TxIndex = uint(evmTxIndex) //nolint:gosec + log.BlockNumber = uint64(blockHeight) //nolint:gosec + log.BlockHash = blockHash + logs = append(logs, log) + } + } + + totalLogs += uint(len(txLogs)) + evmTxIndex++ + } + + return logs, nil +} + +func buildLogRecords(logs []*ethtypes.Log, blockHash common.Hash) []logRecord { + if len(logs) == 0 { + return nil + } + + records := make([]logRecord, 0, len(logs)) + for _, lg := range logs { + topic0, topic1, topic2, topic3 := extractLogTopics(lg.Topics) + rec := logRecord{ + BlockNumber: lg.BlockNumber, + TxHash: lg.TxHash[:], + TxIndex: uint32(lg.TxIndex), + LogIndex: uint32(lg.Index), + Address: lg.Address[:], + BlockHash: blockHash[:], + Removed: lg.Removed, + Topic0: topic0, + Topic1: topic1, + Topic2: topic2, + Topic3: topic3, + Data: lg.Data, + } + records = append(records, rec) + } + + return records +} + +func buildTopicsFromLogResult(lr logResult) []common.Hash { + var topicList []common.Hash + if len(lr.Topic0) > 0 { + topicList = append(topicList, common.BytesToHash(lr.Topic0)) + } + if len(lr.Topic1) > 0 { + topicList = append(topicList, common.BytesToHash(lr.Topic1)) + } + if len(lr.Topic2) > 0 { + topicList = append(topicList, common.BytesToHash(lr.Topic2)) + } + if len(lr.Topic3) > 0 { + topicList = append(topicList, common.BytesToHash(lr.Topic3)) + } + return topicList +} + +func extractLogTopics(topics []common.Hash) ([]byte, []byte, []byte, []byte) { + t0 := make([]byte, 0) + t1 := make([]byte, 0) + t2 := make([]byte, 0) + t3 := make([]byte, 0) + + if len(topics) > 0 { + t0 = make([]byte, common.HashLength) + copy(t0, topics[0][:]) + } + if len(topics) > 1 { + t1 = make([]byte, common.HashLength) + copy(t1, topics[1][:]) + } + if len(topics) > 2 { + t2 = make([]byte, common.HashLength) + copy(t2, topics[2][:]) + } + if len(topics) > 3 { + t3 = make([]byte, common.HashLength) + copy(t3, topics[3][:]) + } + return t0, t1, t2, t3 +} + +func copyBytes(src []byte) []byte { + if len(src) == 0 { + return make([]byte, 0) + } + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +func copyBytesOrEmpty(src []byte) []byte { + if src == nil { + return make([]byte, 0) + } + return copyBytes(src) +} diff --git a/sei-db/ledger_db/receipt/receipt_cache.go b/sei-db/ledger_db/receipt/receipt_cache.go new file mode 100644 index 0000000000..2869eea960 --- /dev/null +++ b/sei-db/ledger_db/receipt/receipt_cache.go @@ -0,0 +1,211 @@ +package receipt + +import ( + "sync" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const numCacheChunks = 3 // current (write+read), previous (read), oldest (prune) + +type receiptCacheEntry struct { + TxHash common.Hash + Receipt *types.Receipt +} + +type logChunk struct { + logs map[uint64][]*ethtypes.Log // blockNum -> logs +} + +type receiptChunk struct { + receipts map[uint64]map[common.Hash]*types.Receipt // blockNum -> (txHash -> receipt) + receiptIndex map[common.Hash]uint64 // txHash -> blockNum +} + +// ledgerCache stores recent receipts and logs in rotating chunks. +// It keeps two most-recent chunks and prunes the oldest on rotation. +type ledgerCache struct { + logChunks [numCacheChunks]atomic.Pointer[logChunk] + logWriteSlot atomic.Int32 + logMu sync.RWMutex + + receiptChunks [numCacheChunks]atomic.Pointer[receiptChunk] + receiptWriteSlot atomic.Int32 + receiptMu sync.RWMutex +} + +func newLedgerCache() *ledgerCache { + c := &ledgerCache{} + + firstLogChunk := &logChunk{ + logs: make(map[uint64][]*ethtypes.Log), + } + c.logChunks[0].Store(firstLogChunk) + c.logWriteSlot.Store(0) + + firstReceiptChunk := &receiptChunk{ + receipts: make(map[uint64]map[common.Hash]*types.Receipt), + receiptIndex: make(map[common.Hash]uint64), + } + c.receiptChunks[0].Store(firstReceiptChunk) + c.receiptWriteSlot.Store(0) + + return c +} + +func (c *ledgerCache) Rotate() { + // Rotate logs + c.logMu.Lock() + oldLogSlot := c.logWriteSlot.Load() + newLogSlot := (oldLogSlot + 1) % numCacheChunks + pruneLogSlot := (newLogSlot + 1) % numCacheChunks + + newLogChunk := &logChunk{ + logs: make(map[uint64][]*ethtypes.Log), + } + c.logChunks[newLogSlot].Store(newLogChunk) + c.logWriteSlot.Store(newLogSlot) + c.logChunks[pruneLogSlot].Store(nil) + c.logMu.Unlock() + + // Rotate receipts + c.receiptMu.Lock() + oldReceiptSlot := c.receiptWriteSlot.Load() + newReceiptSlot := (oldReceiptSlot + 1) % numCacheChunks + pruneReceiptSlot := (newReceiptSlot + 1) % numCacheChunks + + newReceiptChunk := &receiptChunk{ + receipts: make(map[uint64]map[common.Hash]*types.Receipt), + receiptIndex: make(map[common.Hash]uint64), + } + c.receiptChunks[newReceiptSlot].Store(newReceiptChunk) + c.receiptWriteSlot.Store(newReceiptSlot) + c.receiptChunks[pruneReceiptSlot].Store(nil) + c.receiptMu.Unlock() +} + +func (c *ledgerCache) GetReceipt(txHash common.Hash) (*types.Receipt, bool) { + c.receiptMu.RLock() + defer c.receiptMu.RUnlock() + + writeSlot := c.receiptWriteSlot.Load() + for i := int32(0); i < numCacheChunks; i++ { + slot := (writeSlot - i + numCacheChunks) % numCacheChunks + chunk := c.receiptChunks[slot].Load() + if chunk == nil { + continue + } + blockNum, exists := chunk.receiptIndex[txHash] + if !exists { + continue + } + blockReceipts, exists := chunk.receipts[blockNum] + if !exists { + continue + } + receipt, found := blockReceipts[txHash] + if found { + return receipt, true + } + } + return nil, false +} + +func (c *ledgerCache) AddReceiptsBatch(blockNumber uint64, receipts []receiptCacheEntry) { + if len(receipts) == 0 { + return + } + + c.receiptMu.Lock() + defer c.receiptMu.Unlock() + + slot := c.receiptWriteSlot.Load() + chunk := c.receiptChunks[slot].Load() + if chunk == nil { + chunk = &receiptChunk{ + receipts: make(map[uint64]map[common.Hash]*types.Receipt), + receiptIndex: make(map[common.Hash]uint64), + } + c.receiptChunks[slot].Store(chunk) + } + + if chunk.receipts[blockNumber] == nil { + chunk.receipts[blockNumber] = make(map[common.Hash]*types.Receipt, len(receipts)) + } + + for i := range receipts { + chunk.receipts[blockNumber][receipts[i].TxHash] = receipts[i].Receipt + chunk.receiptIndex[receipts[i].TxHash] = blockNumber + } +} + +func (c *ledgerCache) GetLogsWithFilter(fromBlock, toBlock uint64, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log { + var addressSet map[common.Address]struct{} + if len(addresses) > 0 { + addressSet = make(map[common.Address]struct{}, len(addresses)) + for _, addr := range addresses { + addressSet[addr] = struct{}{} + } + } + + c.logMu.RLock() + defer c.logMu.RUnlock() + + var result []*ethtypes.Log + for i := 0; i < numCacheChunks; i++ { + chunk := c.logChunks[i].Load() + if chunk == nil { + continue + } + + for blockNum := fromBlock; blockNum <= toBlock; blockNum++ { + logs, exists := chunk.logs[blockNum] + if !exists { + continue + } + + for _, lg := range logs { + if addressSet != nil { + if _, found := addressSet[lg.Address]; !found { + continue + } + } + if !matchTopics(topics, lg.Topics) { + continue + } + result = append(result, lg) + } + } + } + + return result +} + +func (c *ledgerCache) AddLogsForBlock(blockNumber uint64, logs []*ethtypes.Log) { + if len(logs) == 0 { + return + } + + logsCopy := make([]*ethtypes.Log, len(logs)) + for i, lg := range logs { + logCopy := *lg + logsCopy[i] = &logCopy + } + + c.logMu.Lock() + defer c.logMu.Unlock() + + slot := c.logWriteSlot.Load() + chunk := c.logChunks[slot].Load() + if chunk == nil { + chunk = &logChunk{ + logs: make(map[uint64][]*ethtypes.Log), + } + c.logChunks[slot].Store(chunk) + } + chunk.logs[blockNumber] = logsCopy +} diff --git a/sei-db/ledger_db/receipt/receipt_store.go b/sei-db/ledger_db/receipt/receipt_store.go index 18410673bc..9907280902 100644 --- a/sei-db/ledger_db/receipt/receipt_store.go +++ b/sei-db/ledger_db/receipt/receipt_store.go @@ -54,32 +54,57 @@ type receiptStore struct { closeOnce sync.Once } -func NewReceiptStore(log dbLogger.Logger, config dbconfig.StateStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { +func normalizeReceiptBackend(backend string) string { + switch strings.ToLower(strings.TrimSpace(backend)) { + case "", "pebbledb", "pebble": + return "pebble" + case "parquet": + return "parquet" + default: + return strings.ToLower(strings.TrimSpace(backend)) + } +} + +func NewReceiptStore(log dbLogger.Logger, config dbconfig.ReceiptStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { if log == nil { log = dbLogger.NewNopLogger() } if config.DBDirectory == "" { return nil, errors.New("receipt store db directory not configured") } - if config.Backend != "" && config.Backend != "pebbledb" { - return nil, fmt.Errorf("unsupported receipt store backend: %s", config.Backend) - } - db, err := mvcc.OpenDB(config.DBDirectory, config) - if err != nil { - return nil, err - } - if err := recoverReceiptStore(log, dbutils.GetChangelogPath(config.DBDirectory), db); err != nil { - _ = db.Close() - return nil, err + backend := normalizeReceiptBackend(config.Backend) + switch backend { + case "parquet": + return newParquetReceiptStore(log, config, storeKey) + case "pebble": + ssConfig := dbconfig.DefaultStateStoreConfig() + ssConfig.DBDirectory = config.DBDirectory + ssConfig.KeepRecent = config.KeepRecent + if config.PruneIntervalSeconds > 0 { + ssConfig.PruneIntervalSeconds = config.PruneIntervalSeconds + } + ssConfig.KeepLastVersion = false + ssConfig.Backend = "pebbledb" + + db, err := mvcc.OpenDB(ssConfig.DBDirectory, ssConfig) + if err != nil { + return nil, err + } + if err := recoverReceiptStore(log, dbutils.GetChangelogPath(ssConfig.DBDirectory), db); err != nil { + _ = db.Close() + return nil, err + } + stopPruning := make(chan struct{}) + startReceiptPruning(log, db, int64(ssConfig.KeepRecent), int64(ssConfig.PruneIntervalSeconds), stopPruning) + return &receiptStore{ + db: db, + storeKey: storeKey, + stopPruning: stopPruning, + }, nil + default: + return nil, fmt.Errorf("unsupported receipt store backend: %s", config.Backend) } - stopPruning := make(chan struct{}) - startReceiptPruning(log, db, int64(config.KeepRecent), int64(config.PruneIntervalSeconds), stopPruning) - return &receiptStore{ - db: db, - storeKey: storeKey, - stopPruning: stopPruning, - }, nil } func (s *receiptStore) LatestVersion() int64 { diff --git a/sei-db/ledger_db/receipt/receipt_store_test.go b/sei-db/ledger_db/receipt/receipt_store_test.go index c0ac704d90..a5a813ff6b 100644 --- a/sei-db/ledger_db/receipt/receipt_store_test.go +++ b/sei-db/ledger_db/receipt/receipt_store_test.go @@ -28,10 +28,9 @@ func setupReceiptStore(t *testing.T) (receipt.ReceiptStore, sdk.Context, storety storeKey := storetypes.NewKVStoreKey("evm") tkey := storetypes.NewTransientStoreKey("evm_transient") ctx := testutil.DefaultContext(storeKey, tkey).WithBlockHeight(0) - cfg := dbconfig.DefaultStateStoreConfig() + cfg := dbconfig.DefaultReceiptStoreConfig() cfg.DBDirectory = t.TempDir() cfg.KeepRecent = 0 - cfg.KeepLastVersion = false store, err := receipt.NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) require.NoError(t, err) t.Cleanup(func() { _ = store.Close() }) @@ -67,7 +66,7 @@ func withBloom(r *types.Receipt) *types.Receipt { func TestNewReceiptStoreConfigErrors(t *testing.T) { storeKey := storetypes.NewKVStoreKey("evm") - cfg := dbconfig.DefaultStateStoreConfig() + cfg := dbconfig.DefaultReceiptStoreConfig() cfg.DBDirectory = "" store, err := receipt.NewReceiptStore(nil, cfg, storeKey) require.Error(t, err) @@ -79,7 +78,13 @@ func TestNewReceiptStoreConfigErrors(t *testing.T) { require.Error(t, err) require.Nil(t, store) - cfg.Backend = "pebbledb" + cfg.Backend = "pebble" + store, err = receipt.NewReceiptStore(nil, cfg, storeKey) + require.NoError(t, err) + require.NotNil(t, store) + require.NoError(t, store.Close()) + + cfg.Backend = "parquet" store, err = receipt.NewReceiptStore(nil, cfg, storeKey) require.NoError(t, err) require.NotNil(t, store) From e31e198a7112033c95ebda5fdf2761ebd6fc447e Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Tue, 20 Jan 2026 14:05:29 -0500 Subject: [PATCH 16/17] fix --- go.work.sum | 29 +++ .../ledger_db/receipt/parquet_store_test.go | 169 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 sei-db/ledger_db/receipt/parquet_store_test.go diff --git a/go.work.sum b/go.work.sum index 2aaacdf66c..7e4a794253 100644 --- a/go.work.sum +++ b/go.work.sum @@ -156,6 +156,8 @@ github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cY github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= @@ -172,6 +174,8 @@ github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582/go.mod h1:k/zV github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/creachadair/command v0.0.0-20220426235536-a748effdf6a1/go.mod h1:bAM+qFQb/KwWyCc9MLC4U1jvn3XyakqP5QRkds5T6cY= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= @@ -180,9 +184,12 @@ github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZ github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/ethereum/c-kzg-4844/v2 v2.1.1/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= @@ -204,6 +211,7 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/ledgerwatch/interfaces v0.0.0-20230210062155-539b8171d9f0/go.mod h1:ugQv1QllJzBny3cKZKxUrSnykkjkBgm27eQM6dnGAcc= github.com/ledgerwatch/log/v3 v3.7.0/go.mod h1:J2Jl6zV/58LeA6LTaVVnCGyf1/cYYSEOOLHY4ZN8S2A= github.com/ledgerwatch/secp256k1 v1.0.0/go.mod h1:SPmqJFciiF/Q0mPt2jVs2dTr/1TZBTIA+kPMmKgBAak= @@ -223,7 +231,10 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ= github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE= github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8= @@ -243,6 +254,8 @@ github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M github.com/pion/webrtc/v3 v3.1.42/go.mod h1:ffD9DulDrPxyWvDPUIPAOSAWx9GUlOExiJPf7cCcMLA= github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= @@ -294,9 +307,14 @@ go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSW go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= @@ -304,8 +322,11 @@ golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -315,16 +336,24 @@ golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= diff --git a/sei-db/ledger_db/receipt/parquet_store_test.go b/sei-db/ledger_db/receipt/parquet_store_test.go new file mode 100644 index 0000000000..6f3812b46c --- /dev/null +++ b/sei-db/ledger_db/receipt/parquet_store_test.go @@ -0,0 +1,169 @@ +package receipt + +import ( + "testing" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" + dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func makeTestReceipt(txHash common.Hash, blockNumber uint64, txIndex uint32, addr common.Address, topics []common.Hash) *types.Receipt { + topicHex := make([]string, 0, len(topics)) + for _, topic := range topics { + topicHex = append(topicHex, topic.Hex()) + } + + return &types.Receipt{ + TxHashHex: txHash.Hex(), + BlockNumber: blockNumber, + TransactionIndex: txIndex, + Logs: []*types.Log{ + { + Address: addr.Hex(), + Topics: topicHex, + Data: []byte{0x1}, + Index: 0, + }, + }, + } +} + +func newTestContext() (sdk.Context, storetypes.StoreKey) { + storeKey := storetypes.NewKVStoreKey("evm") + tkey := storetypes.NewTransientStoreKey("evm_transient") + ctx := testutil.DefaultContext(storeKey, tkey).WithBlockHeight(1) + return ctx, storeKey +} + +func TestLedgerCacheReceiptsAndLogs(t *testing.T) { + cache := newLedgerCache() + txHash := common.HexToHash("0x1") + blockNumber := uint64(10) + + cache.AddReceiptsBatch(blockNumber, []receiptCacheEntry{ + { + TxHash: txHash, + Receipt: &types.Receipt{TxHashHex: txHash.Hex(), BlockNumber: blockNumber}, + }, + }) + + got, ok := cache.GetReceipt(txHash) + require.True(t, ok) + require.Equal(t, txHash.Hex(), got.TxHashHex) + + addr := common.HexToAddress("0x100") + topic := common.HexToHash("0xabc") + cache.AddLogsForBlock(blockNumber, []*ethtypes.Log{ + { + Address: addr, + Topics: []common.Hash{topic}, + BlockNumber: blockNumber, + TxHash: txHash, + TxIndex: 0, + Index: 0, + }, + }) + + logs := cache.GetLogsWithFilter(blockNumber, blockNumber, []common.Address{addr}, [][]common.Hash{{topic}}) + require.Len(t, logs, 1) + require.Equal(t, addr, logs[0].Address) + require.Equal(t, topic, logs[0].Topics[0]) +} + +func TestLedgerCacheRotatePrunes(t *testing.T) { + cache := newLedgerCache() + txHash := common.HexToHash("0x2") + blockNumber := uint64(1) + cache.AddReceiptsBatch(blockNumber, []receiptCacheEntry{ + { + TxHash: txHash, + Receipt: &types.Receipt{TxHashHex: txHash.Hex(), BlockNumber: blockNumber}, + }, + }) + + _, ok := cache.GetReceipt(txHash) + require.True(t, ok) + + cache.Rotate() + cache.Rotate() + + _, ok = cache.GetReceipt(txHash) + require.False(t, ok) +} + +func TestParquetReceiptStoreCacheLogs(t *testing.T) { + ctx, storeKey := newTestContext() + cfg := dbconfig.DefaultReceiptStoreConfig() + cfg.Backend = "parquet" + cfg.DBDirectory = t.TempDir() + + store, err := NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) + require.NoError(t, err) + t.Cleanup(func() { _ = store.Close() }) + + txHash := common.HexToHash("0x10") + addr := common.HexToAddress("0x200") + topic := common.HexToHash("0x1234") + receipt := makeTestReceipt(txHash, 10, 2, addr, []common.Hash{topic}) + + require.NoError(t, store.SetReceipts(ctx, []ReceiptRecord{ + {TxHash: txHash, Receipt: receipt}, + })) + + blockHash := common.HexToHash("0xbeef") + logs, err := store.FilterLogs(ctx, 10, blockHash, []common.Hash{txHash}, filters.FilterCriteria{ + Addresses: []common.Address{addr}, + Topics: [][]common.Hash{{topic}}, + }, true) + require.NoError(t, err) + require.Len(t, logs, 1) + require.Equal(t, blockHash, logs[0].BlockHash) + require.Equal(t, uint64(10), logs[0].BlockNumber) + require.Equal(t, uint(2), logs[0].TxIndex) +} + +func TestParquetReceiptStoreReopenQueries(t *testing.T) { + ctx, storeKey := newTestContext() + cfg := dbconfig.DefaultReceiptStoreConfig() + cfg.Backend = "parquet" + cfg.DBDirectory = t.TempDir() + + store, err := NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) + require.NoError(t, err) + + txHash := common.HexToHash("0x20") + addr := common.HexToAddress("0x300") + topic := common.HexToHash("0x5678") + receipt := makeTestReceipt(txHash, 5, 1, addr, []common.Hash{topic}) + + require.NoError(t, store.SetReceipts(ctx, []ReceiptRecord{ + {TxHash: txHash, Receipt: receipt}, + })) + require.NoError(t, store.Close()) + + store, err = NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) + require.NoError(t, err) + t.Cleanup(func() { _ = store.Close() }) + + got, err := store.GetReceiptFromStore(ctx, txHash) + require.NoError(t, err) + require.Equal(t, receipt.TxHashHex, got.TxHashHex) + + blockHash := common.HexToHash("0xcafe") + logs, err := store.FilterLogs(ctx, 5, blockHash, []common.Hash{txHash}, filters.FilterCriteria{ + Addresses: []common.Address{addr}, + Topics: [][]common.Hash{{topic}}, + }, true) + require.NoError(t, err) + require.Len(t, logs, 1) + require.Equal(t, blockHash, logs[0].BlockHash) + require.Equal(t, uint64(5), logs[0].BlockNumber) +} From 53555d2df35ce931b32c4e64bc20638f881155d7 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Wed, 21 Jan 2026 10:02:18 -0500 Subject: [PATCH 17/17] add WAL --- sei-db/ledger_db/receipt/parquet_store.go | 171 +++++++++++++++++- .../ledger_db/receipt/parquet_store_test.go | 42 +++++ sei-db/ledger_db/receipt/parquet_wal.go | 32 ++++ 3 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 sei-db/ledger_db/receipt/parquet_wal.go diff --git a/sei-db/ledger_db/receipt/parquet_store.go b/sei-db/ledger_db/receipt/parquet_store.go index 1740b2130f..c634f70420 100644 --- a/sei-db/ledger_db/receipt/parquet_store.go +++ b/sei-db/ledger_db/receipt/parquet_store.go @@ -15,6 +15,7 @@ import ( "github.com/parquet-go/parquet-go" dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" dbconfig "github.com/sei-protocol/sei-chain/sei-db/config" + dbwal "github.com/sei-protocol/sei-chain/sei-db/wal" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -72,13 +73,14 @@ type parquetReceiptStore struct { reader *parquetReader cache *ledgerCache storeKey sdk.StoreKey + wal dbwal.GenericWAL[parquetWALEntry] latestVersion atomic.Int64 earliestVersion atomic.Int64 currentFileStart atomic.Uint64 closeOnce sync.Once } -func newParquetReceiptStore(_ dbLogger.Logger, cfg dbconfig.ReceiptStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { +func newParquetReceiptStore(log dbLogger.Logger, cfg dbconfig.ReceiptStoreConfig, storeKey sdk.StoreKey) (ReceiptStore, error) { if err := os.MkdirAll(cfg.DBDirectory, 0o755); err != nil { return nil, fmt.Errorf("failed to create parquet base directory: %w", err) } @@ -88,6 +90,12 @@ func newParquetReceiptStore(_ dbLogger.Logger, cfg dbconfig.ReceiptStoreConfig, return nil, err } + walDir := filepath.Join(cfg.DBDirectory, "parquet-wal") + receiptWAL, err := newParquetWAL(log, walDir) + if err != nil { + return nil, err + } + store := &parquetReceiptStore{ basePath: cfg.DBDirectory, receiptsBuffer: make([]receiptRecord, 0, 1000), @@ -96,6 +104,7 @@ func newParquetReceiptStore(_ dbLogger.Logger, cfg dbconfig.ReceiptStoreConfig, reader: reader, cache: newLedgerCache(), storeKey: storeKey, + wal: receiptWAL, } if maxBlock, ok, err := reader.maxReceiptBlockNumber(context.Background()); err != nil { @@ -116,6 +125,10 @@ func newParquetReceiptStore(_ dbLogger.Logger, cfg dbconfig.ReceiptStoreConfig, return nil, err } + if err := store.replayWAL(); err != nil { + return nil, err + } + return store, nil } @@ -222,6 +235,7 @@ func (s *parquetReceiptStore) SetReceipts(ctx sdk.Context, receipts []ReceiptRec inputs := make([]parquetReceiptInput, 0, len(receipts)) cacheBatches := make([]cacheBatch, 0) + walEntries := make([]parquetWALEntry, 0, len(receipts)) var ( currentBlock uint64 @@ -229,6 +243,7 @@ func (s *parquetReceiptStore) SetReceipts(ctx sdk.Context, receipts []ReceiptRec cacheEntries []receiptCacheEntry cacheLogs []*ethtypes.Log maxBlock uint64 + dropOffset uint64 ) flushCacheBatch := func(blockNumber uint64) { @@ -268,6 +283,9 @@ func (s *parquetReceiptStore) SetReceipts(ctx sdk.Context, receipts []ReceiptRec if err != nil { return err } + walEntries = append(walEntries, parquetWALEntry{ + ReceiptBytes: copyBytesOrEmpty(receiptBytes), + }) txLogs := getLogsForTx(receipt, logStartIndex) logStartIndex += uint(len(txLogs)) @@ -293,6 +311,14 @@ func (s *parquetReceiptStore) SetReceipts(ctx sdk.Context, receipts []ReceiptRec } flushCacheBatch(currentBlock) + if s.wal != nil { + for i := range walEntries { + if err := s.wal.Write(walEntries[i]); err != nil { + return err + } + } + } + s.mu.Lock() for i := range inputs { if err := s.applyReceiptLocked(inputs[i]); err != nil { @@ -388,6 +414,12 @@ func (s *parquetReceiptStore) Close() error { err = closeErr return } + if s.wal != nil { + if closeErr := s.wal.Close(); closeErr != nil { + err = closeErr + return + } + } if s.reader != nil { if closeErr := s.reader.Close(); closeErr != nil { err = closeErr @@ -465,6 +497,7 @@ func (s *parquetReceiptStore) rotateFileLocked(newBlockNumber uint64) error { if s.cache != nil { s.cache.Rotate() } + s.clearWAL() s.fileStartBlock = newBlockNumber s.currentFileStart.Store(newBlockNumber) @@ -629,6 +662,142 @@ func (s *parquetReceiptStore) filterLogsFromReceipts(ctx sdk.Context, blockHeigh return logs, nil } +func (s *parquetReceiptStore) replayWAL() error { + if s.wal == nil { + return nil + } + + firstOffset, errFirst := s.wal.FirstOffset() + if errFirst != nil || firstOffset <= 0 { + return nil + } + lastOffset, errLast := s.wal.LastOffset() + if errLast != nil || lastOffset <= 0 { + return nil + } + + s.mu.Lock() + defer s.mu.Unlock() + + var ( + currentBlock uint64 + logStartIndex uint + cacheEntries []receiptCacheEntry + cacheLogs []*ethtypes.Log + maxBlock uint64 + ) + + flushCacheBatch := func(blockNumber uint64) { + if s.cache == nil || len(cacheEntries) == 0 { + cacheEntries = nil + cacheLogs = nil + return + } + s.cache.AddReceiptsBatch(blockNumber, cacheEntries) + if len(cacheLogs) > 0 { + s.cache.AddLogsForBlock(blockNumber, cacheLogs) + } + cacheEntries = nil + cacheLogs = nil + } + + blockHash := common.Hash{} + fileStartBlock := s.fileStartBlock + + err := s.wal.Replay(firstOffset, lastOffset, func(offset uint64, entry parquetWALEntry) error { + if len(entry.ReceiptBytes) == 0 { + return nil + } + + receipt := &types.Receipt{} + if err := receipt.Unmarshal(entry.ReceiptBytes); err != nil { + return err + } + + blockNumber := receipt.BlockNumber + if blockNumber < fileStartBlock { + dropOffset = offset + return nil + } + + txHash := common.HexToHash(receipt.TxHashHex) + + if currentBlock == 0 { + currentBlock = blockNumber + } + if blockNumber != currentBlock { + flushCacheBatch(currentBlock) + currentBlock = blockNumber + logStartIndex = 0 + } + + txLogs := getLogsForTx(receipt, logStartIndex) + logStartIndex += uint(len(txLogs)) + for _, lg := range txLogs { + lg.BlockHash = blockHash + } + + input := parquetReceiptInput{ + blockNumber: blockNumber, + receipt: receiptRecord{ + TxHash: copyBytes(txHash[:]), + BlockNumber: blockNumber, + ReceiptBytes: copyBytesOrEmpty(entry.ReceiptBytes), + }, + logs: buildLogRecords(txLogs, blockHash), + } + + if err := s.applyReceiptLocked(input); err != nil { + return err + } + + if s.cache != nil { + cacheEntries = append(cacheEntries, receiptCacheEntry{ + TxHash: txHash, + Receipt: receipt, + }) + cacheLogs = append(cacheLogs, txLogs...) + } + + if blockNumber > maxBlock { + maxBlock = blockNumber + } + + return nil + }) + if err != nil { + return err + } + + flushCacheBatch(currentBlock) + + if maxBlock > 0 { + s.latestVersion.Store(int64(maxBlock)) + } + if dropOffset > 0 { + _ = s.wal.TruncateBefore(dropOffset + 1) + } + return nil +} + +func (s *parquetReceiptStore) clearWAL() { + if s.wal == nil { + return + } + firstOffset, errFirst := s.wal.FirstOffset() + if errFirst != nil || firstOffset <= 0 { + return + } + lastOffset, errLast := s.wal.LastOffset() + if errLast != nil || lastOffset <= 0 { + return + } + if lastOffset < firstOffset { + return + } + _ = s.wal.TruncateBefore(lastOffset + 1) +} + func buildLogRecords(logs []*ethtypes.Log, blockHash common.Hash) []logRecord { if len(logs) == 0 { return nil diff --git a/sei-db/ledger_db/receipt/parquet_store_test.go b/sei-db/ledger_db/receipt/parquet_store_test.go index 6f3812b46c..3588a2dac3 100644 --- a/sei-db/ledger_db/receipt/parquet_store_test.go +++ b/sei-db/ledger_db/receipt/parquet_store_test.go @@ -1,6 +1,8 @@ package receipt import ( + "os" + "path/filepath" "testing" storetypes "github.com/cosmos/cosmos-sdk/store/types" @@ -167,3 +169,43 @@ func TestParquetReceiptStoreReopenQueries(t *testing.T) { require.Equal(t, blockHash, logs[0].BlockHash) require.Equal(t, uint64(5), logs[0].BlockNumber) } + +func TestParquetReceiptStoreWALReplay(t *testing.T) { + ctx, storeKey := newTestContext() + cfg := dbconfig.DefaultReceiptStoreConfig() + cfg.Backend = "parquet" + cfg.DBDirectory = t.TempDir() + + store, err := NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) + require.NoError(t, err) + + txHash := common.HexToHash("0x30") + addr := common.HexToAddress("0x400") + topic := common.HexToHash("0x9abc") + receipt := makeTestReceipt(txHash, 3, 0, addr, []common.Hash{topic}) + + require.NoError(t, store.SetReceipts(ctx, []ReceiptRecord{ + {TxHash: txHash, Receipt: receipt}, + })) + require.NoError(t, store.Close()) + + receiptFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "receipts_*.parquet")) + require.NoError(t, err) + for _, file := range receiptFiles { + require.NoError(t, os.Remove(file)) + } + + logFiles, err := filepath.Glob(filepath.Join(cfg.DBDirectory, "logs_*.parquet")) + require.NoError(t, err) + for _, file := range logFiles { + require.NoError(t, os.Remove(file)) + } + + store, err = NewReceiptStore(dbLogger.NewNopLogger(), cfg, storeKey) + require.NoError(t, err) + t.Cleanup(func() { _ = store.Close() }) + + got, err := store.GetReceiptFromStore(ctx, txHash) + require.NoError(t, err) + require.Equal(t, receipt.TxHashHex, got.TxHashHex) +} diff --git a/sei-db/ledger_db/receipt/parquet_wal.go b/sei-db/ledger_db/receipt/parquet_wal.go new file mode 100644 index 0000000000..9f503392a1 --- /dev/null +++ b/sei-db/ledger_db/receipt/parquet_wal.go @@ -0,0 +1,32 @@ +package receipt + +import ( + "encoding/json" + "os" + + dbLogger "github.com/sei-protocol/sei-chain/sei-db/common/logger" + dbwal "github.com/sei-protocol/sei-chain/sei-db/wal" +) + +type parquetWALEntry struct { + ReceiptBytes []byte `json:"receipt_bytes"` +} + +func newParquetWAL(logger dbLogger.Logger, dir string) (dbwal.GenericWAL[parquetWALEntry], error) { + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + return dbwal.NewWAL( + func(entry parquetWALEntry) ([]byte, error) { + return json.Marshal(entry) + }, + func(data []byte) (parquetWALEntry, error) { + var entry parquetWALEntry + err := json.Unmarshal(data, &entry) + return entry, err + }, + logger, + dir, + dbwal.Config{}, + ) +}