diff --git a/app/app.go b/app/app.go index e65a1038fe..8eae8ecd2b 100644 --- a/app/app.go +++ b/app/app.go @@ -14,8 +14,10 @@ import ( "sync" "time" + "github.com/cosmos/cosmos-sdk/server" "github.com/gorilla/mux" "github.com/rakyll/statik/fs" + "github.com/sei-protocol/sei-db/ss" "github.com/ethereum/go-ethereum/ethclient" ethrpc "github.com/ethereum/go-ethereum/rpc" @@ -26,14 +28,6 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" - "github.com/sei-protocol/sei-chain/aclmapping" - aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" - appparams "github.com/sei-protocol/sei-chain/app/params" - "github.com/sei-protocol/sei-chain/app/upgrades" - v0upgrade "github.com/sei-protocol/sei-chain/app/upgrades/v0" - "github.com/sei-protocol/sei-chain/utils" - "github.com/sei-protocol/sei-chain/wasmbinding" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" @@ -47,20 +41,16 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/ante" - "github.com/cosmos/cosmos-sdk/x/authz" - authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" - authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" - - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" aclmodule "github.com/cosmos/cosmos-sdk/x/accesscontrol" aclclient "github.com/cosmos/cosmos-sdk/x/accesscontrol/client" aclconstants "github.com/cosmos/cosmos-sdk/x/accesscontrol/constants" aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" @@ -68,6 +58,9 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/auth/vesting" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" "github.com/cosmos/cosmos-sdk/x/bank" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -118,11 +111,13 @@ import ( ibcporttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types" ibchost "github.com/cosmos/ibc-go/v3/modules/core/24-host" ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper" - "github.com/sei-protocol/sei-chain/x/mint" - mintclient "github.com/sei-protocol/sei-chain/x/mint/client/cli" - mintkeeper "github.com/sei-protocol/sei-chain/x/mint/keeper" - minttypes "github.com/sei-protocol/sei-chain/x/mint/types" - + "github.com/sei-protocol/sei-chain/aclmapping" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + appparams "github.com/sei-protocol/sei-chain/app/params" + "github.com/sei-protocol/sei-chain/app/upgrades" + v0upgrade "github.com/sei-protocol/sei-chain/app/upgrades/v0" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/wasmbinding" "github.com/sei-protocol/sei-chain/x/evm" evmante "github.com/sei-protocol/sei-chain/x/evm/ante" "github.com/sei-protocol/sei-chain/x/evm/blocktest" @@ -130,6 +125,11 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/querier" "github.com/sei-protocol/sei-chain/x/evm/replay" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/sei-protocol/sei-chain/x/mint" + mintclient "github.com/sei-protocol/sei-chain/x/mint/client/cli" + mintkeeper "github.com/sei-protocol/sei-chain/x/mint/keeper" + minttypes "github.com/sei-protocol/sei-chain/x/mint/types" + seidb "github.com/sei-protocol/sei-db/ss/types" "github.com/spf13/cast" abci "github.com/tendermint/tendermint/abci/types" tmcfg "github.com/tendermint/tendermint/config" @@ -166,6 +166,8 @@ import ( // unnamed import of statik for openapi/swagger UI support _ "github.com/sei-protocol/sei-chain/docs/swagger" + + ssconfig "github.com/sei-protocol/sei-db/config" ) // this line is used by starport scaffolding # stargate/wasm/app/enabledProposals @@ -264,7 +266,8 @@ var ( // EmptyAclmOpts defines a type alias for a list of wasm options. EmptyACLOpts []aclkeeper.Option // EnableOCC allows tests to override default OCC enablement behavior - EnableOCC = true + EnableOCC = true + EmptyAppOptions []AppOption ) var ( @@ -382,8 +385,12 @@ type App struct { encodingConfig appparams.EncodingConfig evmRPCConfig evmrpc.Config lightInvarianceConfig LightInvarianceConfig + + receiptStore seidb.StateStore } +type AppOption func(*App) + // New returns a reference to an initialized blockchain app func New( logger log.Logger, @@ -400,6 +407,7 @@ func New( appOpts servertypes.AppOptions, wasmOpts []wasm.Option, aclOpts []aclkeeper.Option, + appOptions []AppOption, baseAppOptions ...func(*baseapp.BaseApp), ) *App { appCodec := encodingConfig.Marshaler @@ -407,6 +415,7 @@ func New( interfaceRegistry := encodingConfig.InterfaceRegistry bAppOptions := SetupSeiDB(logger, homePath, appOpts, baseAppOptions) + bApp := baseapp.NewBaseApp(AppName, logger, db, encodingConfig.TxConfig.TxDecoder(), tmConfig, appOpts, bAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetVersion(version.Version) @@ -423,7 +432,7 @@ func New( tokenfactorytypes.StoreKey, // this line is used by starport scaffolding # stargate/app/storeKey ) - tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey, evmtypes.TransientStoreKey) memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, dexmoduletypes.MemStoreKey, banktypes.DeferredCacheStoreKey, evmtypes.MemStoreKey, oracletypes.MemStoreKey) app := &App{ @@ -440,6 +449,11 @@ func New( metricCounter: &map[string]float32{}, encodingConfig: encodingConfig, } + + for _, option := range appOptions { + option(app) + } + app.ParamsKeeper = initParamsKeeper(appCodec, cdc, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) // set the BaseApp's parameter store @@ -596,9 +610,26 @@ func New( wasmOpts..., ) + receiptStorePath := filepath.Join(homePath, "data", "receipt.db") + ssConfig := ssconfig.DefaultStateStoreConfig() + ssConfig.DedicatedChangelog = true + ssConfig.KeepRecent = cast.ToInt(appOpts.Get(server.FlagMinRetainBlocks)) + ssConfig.DBDirectory = receiptStorePath + ssConfig.KeepLastVersion = false + if app.receiptStore == nil { + app.receiptStore, err = ss.NewStateStore(logger, receiptStorePath, ssConfig) + if err != nil { + panic(fmt.Sprintf("error while creating receipt store: %s", err)) + } + } app.EvmKeeper = *evmkeeper.NewKeeper(keys[evmtypes.StoreKey], memKeys[evmtypes.MemStoreKey], - app.GetSubspace(evmtypes.ModuleName), app.BankKeeper, &app.AccountKeeper, &app.StakingKeeper, - app.TransferKeeper, wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper), &app.WasmKeeper) + tkeys[evmtypes.TransientStoreKey], app.GetSubspace(evmtypes.ModuleName), app.receiptStore, app.BankKeeper, + &app.AccountKeeper, &app.StakingKeeper, app.TransferKeeper, + wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper), &app.WasmKeeper) + + bApp.SetPreCommitHandler(app.HandlePreCommit) + bApp.SetCloseHandler(app.HandleClose) + app.evmRPCConfig, err = evmrpc.ReadConfig(appOpts) if err != nil { panic(fmt.Sprintf("error reading EVM config due to %s", err)) @@ -970,6 +1001,19 @@ func New( return app } +// HandlePreCommit happens right before the block is committed +func (app *App) HandlePreCommit(ctx sdk.Context) error { + return app.EvmKeeper.FlushTransientReceipts(ctx) +} + +// Close closes all items that needs closing (called by baseapp) +func (app *App) HandleClose() error { + if app.receiptStore != nil { + return app.receiptStore.Close() + } + return nil +} + // Add (or remove) keepers when they are introduced / removed in different versions func (app *App) SetStoreUpgradeHandlers() { upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() diff --git a/app/test_helpers.go b/app/test_helpers.go index 87b501b749..e80e7f3573 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -177,6 +177,13 @@ func Setup(isCheckTx bool, enableEVMCustomPrecompiles bool, baseAppOptions ...fu db := dbm.NewMemDB() encodingConfig := MakeEncodingConfig() cdc := encodingConfig.Marshaler + + options := []AppOption{ + func(app *App) { + app.receiptStore = NewInMemoryStateStore() + }, + } + res = New( log.NewNopLogger(), db, @@ -192,6 +199,7 @@ func Setup(isCheckTx bool, enableEVMCustomPrecompiles bool, baseAppOptions ...fu TestAppOpts{}, EmptyWasmOpts, EmptyACLOpts, + options, baseAppOptions..., ) if !isCheckTx { @@ -220,6 +228,13 @@ func SetupWithSc(isCheckTx bool, enableEVMCustomPrecompiles bool, baseAppOptions db := dbm.NewMemDB() encodingConfig := MakeEncodingConfig() cdc := encodingConfig.Marshaler + + options := []AppOption{ + func(app *App) { + app.receiptStore = NewInMemoryStateStore() + }, + } + res = New( log.NewNopLogger(), db, @@ -235,6 +250,7 @@ func SetupWithSc(isCheckTx bool, enableEVMCustomPrecompiles bool, baseAppOptions TestAppOpts{true}, EmptyWasmOpts, EmptyACLOpts, + options, baseAppOptions..., ) if !isCheckTx { @@ -285,6 +301,7 @@ func SetupTestingAppWithLevelDb(isCheckTx bool, enableEVMCustomPrecompiles bool) TestAppOpts{}, EmptyWasmOpts, EmptyACLOpts, + nil, ) if !isCheckTx { genesisState := NewDefaultGenesisState(cdc) diff --git a/app/test_state_store.go b/app/test_state_store.go new file mode 100644 index 0000000000..520c06c0f8 --- /dev/null +++ b/app/test_state_store.go @@ -0,0 +1,306 @@ +package app + +import ( + "errors" + "sync" + + seidbproto "github.com/sei-protocol/sei-db/proto" + "github.com/sei-protocol/sei-db/ss/types" +) + +// InMemoryStateStore this implements seidb state store with an inmemory store +type InMemoryStateStore struct { + mu sync.RWMutex + data map[string]map[int64]map[string][]byte + latestVersion int64 + earliestVersion int64 +} + +func NewInMemoryStateStore() *InMemoryStateStore { + return &InMemoryStateStore{ + data: make(map[string]map[int64]map[string][]byte), + latestVersion: 0, + earliestVersion: 0, + } +} + +func (s *InMemoryStateStore) Get(storeKey string, version int64, key []byte) ([]byte, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + store, ok := s.data[storeKey] + if !ok { + return nil, errors.New("not found") + } + + versionData, ok := store[version] + if !ok { + return nil, errors.New("not found") + } + + value, ok := versionData[string(key)] + if !ok { + return nil, errors.New("not found") + } + + return value, nil +} + +func (s *InMemoryStateStore) Has(storeKey string, version int64, key []byte) (bool, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + store, ok := s.data[storeKey] + if !ok { + return false, nil + } + + versionData, ok := store[version] + if !ok { + return false, nil + } + + _, ok = versionData[string(key)] + return ok, nil +} + +func (s *InMemoryStateStore) Iterator(storeKey string, version int64, start, end []byte) (types.DBIterator, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + store, ok := s.data[storeKey] + if !ok { + return nil, errors.New("store not found") + } + + versionData, ok := store[version] + if !ok { + return nil, errors.New("version not found") + } + + return NewInMemoryIterator(versionData, start, end), nil +} + +func (s *InMemoryStateStore) ReverseIterator(storeKey string, version int64, start, end []byte) (types.DBIterator, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + store, ok := s.data[storeKey] + if !ok { + return nil, errors.New("store not found") + } + + versionData, ok := store[version] + if !ok { + return nil, errors.New("version not found") + } + + iter := NewInMemoryIterator(versionData, start, end) + + // Reverse the keys for reverse iteration + for i, j := 0, len(iter.keys)-1; i < j; i, j = i+1, j-1 { + iter.keys[i], iter.keys[j] = iter.keys[j], iter.keys[i] + } + + return iter, nil +} + +func (s *InMemoryStateStore) RawIterate(storeKey string, fn func([]byte, []byte, int64) bool) (bool, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + for version, versionData := range s.data[storeKey] { + for key, value := range versionData { + if !fn([]byte(key), value, version) { + return false, nil + } + } + } + + return true, nil +} + +func (s *InMemoryStateStore) GetLatestVersion() (int64, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.latestVersion, nil +} + +func (s *InMemoryStateStore) SetLatestVersion(version int64) error { + s.mu.Lock() + defer s.mu.Unlock() + + s.latestVersion = version + return nil +} + +func (s *InMemoryStateStore) GetEarliestVersion() (int64, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.earliestVersion, nil +} + +func (s *InMemoryStateStore) SetEarliestVersion(version int64) error { + s.mu.Lock() + defer s.mu.Unlock() + + s.earliestVersion = version + return nil +} + +func (s *InMemoryStateStore) ApplyChangeset(version int64, cs *seidbproto.NamedChangeSet) error { + s.mu.Lock() + defer s.mu.Unlock() + + for _, pair := range cs.Changeset.Pairs { + storeKey := cs.Name + key := pair.Key + value := pair.Value + + if s.data[storeKey] == nil { + s.data[storeKey] = make(map[int64]map[string][]byte) + } + + if s.data[storeKey][version] == nil { + s.data[storeKey][version] = make(map[string][]byte) + } + + if pair.Delete { + delete(s.data[storeKey][version], string(key)) + } else { + s.data[storeKey][version][string(key)] = value + } + } + + s.latestVersion = version + return nil +} + +func (s *InMemoryStateStore) ApplyChangesetAsync(version int64, changesets []*seidbproto.NamedChangeSet) error { + // Implementation for async write, currently just calls ApplyChangeset + for _, cs := range changesets { + if err := s.ApplyChangeset(version, cs); err != nil { + return err + } + } + return nil +} + +func (s *InMemoryStateStore) Import(version int64, ch <-chan types.SnapshotNode) error { + s.mu.Lock() + defer s.mu.Unlock() + + for node := range ch { + storeKey := node.StoreKey + key := node.Key + value := node.Value + + if s.data[storeKey] == nil { + s.data[storeKey] = make(map[int64]map[string][]byte) + } + + if s.data[storeKey][version] == nil { + s.data[storeKey][version] = make(map[string][]byte) + } + + s.data[storeKey][version][string(key)] = value + } + + s.latestVersion = version + return nil +} + +func (s *InMemoryStateStore) Prune(version int64) error { + s.mu.Lock() + defer s.mu.Unlock() + + for storeKey, store := range s.data { + for ver := range store { + if ver <= version { + delete(store, ver) + } + } + if len(store) == 0 { + delete(s.data, storeKey) + } + } + + if s.earliestVersion <= version { + s.earliestVersion = version + 1 + } + + return nil +} + +func (s *InMemoryStateStore) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + + s.data = make(map[string]map[int64]map[string][]byte) + return nil +} + +type InMemoryIterator struct { + data map[string][]byte + keys []string + current int + start []byte + end []byte +} + +func NewInMemoryIterator(data map[string][]byte, start, end []byte) *InMemoryIterator { + keys := make([]string, 0, len(data)) + for k := range data { + if (start == nil || k >= string(start)) && (end == nil || k < string(end)) { + keys = append(keys, k) + } + } + + return &InMemoryIterator{ + data: data, + keys: keys, + current: 0, + start: start, + end: end, + } +} + +func (it *InMemoryIterator) Domain() (start, end []byte) { + return it.start, it.end +} + +func (it *InMemoryIterator) Valid() bool { + return it.current >= 0 && it.current < len(it.keys) +} + +func (it *InMemoryIterator) Next() { + if it.Valid() { + it.current++ + } +} + +func (it *InMemoryIterator) Key() (key []byte) { + if !it.Valid() { + panic("iterator is invalid") + } + return []byte(it.keys[it.current]) +} + +func (it *InMemoryIterator) Value() (value []byte) { + if !it.Valid() { + panic("iterator is invalid") + } + return it.data[it.keys[it.current]] +} + +func (it *InMemoryIterator) Error() error { + return nil +} + +func (it *InMemoryIterator) Close() error { + it.data = nil + it.keys = nil + return nil +} diff --git a/app/test_state_store_test.go b/app/test_state_store_test.go new file mode 100644 index 0000000000..141294b58c --- /dev/null +++ b/app/test_state_store_test.go @@ -0,0 +1,166 @@ +package app + +import ( + "testing" + + "github.com/cosmos/iavl" + seidbproto "github.com/sei-protocol/sei-db/proto" + "github.com/stretchr/testify/assert" +) + +func TestApplyChangesetAndGet(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.ApplyChangeset(1, &seidbproto.NamedChangeSet{ + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: []byte("key1"), Value: []byte("value1")}, + }, + }, + Name: "exampleStore", + }) + assert.NoError(t, err) + + value, err := store.Get("exampleStore", 1, []byte("key1")) + assert.NoError(t, err) + assert.Equal(t, []byte("value1"), value) +} + +func TestHas(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.ApplyChangeset(1, &seidbproto.NamedChangeSet{ + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: []byte("key1"), Value: []byte("value1")}, + }, + }, + Name: "exampleStore", + }) + assert.NoError(t, err) + + has, err := store.Has("exampleStore", 1, []byte("key1")) + assert.NoError(t, err) + assert.True(t, has) + + has, err = store.Has("exampleStore", 1, []byte("key2")) + assert.NoError(t, err) + assert.False(t, has) +} + +func TestIterator(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.ApplyChangeset(1, &seidbproto.NamedChangeSet{ + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: []byte("key1"), Value: []byte("value1")}, + {Key: []byte("key2"), Value: []byte("value2")}, + }, + }, + Name: "exampleStore", + }) + assert.NoError(t, err) + + iter, err := store.Iterator("exampleStore", 1, nil, nil) + assert.NoError(t, err) + assert.NotNil(t, iter) + + assert.True(t, iter.Valid()) + assert.Equal(t, []byte("key1"), iter.Key()) + assert.Equal(t, []byte("value1"), iter.Value()) + + iter.Next() + assert.True(t, iter.Valid()) + assert.Equal(t, []byte("key2"), iter.Key()) + assert.Equal(t, []byte("value2"), iter.Value()) + + iter.Next() + assert.False(t, iter.Valid()) + iter.Close() +} + +func TestReverseIterator(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.ApplyChangeset(1, &seidbproto.NamedChangeSet{ + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: []byte("key1"), Value: []byte("value1")}, + {Key: []byte("key2"), Value: []byte("value2")}, + }, + }, + Name: "exampleStore", + }) + assert.NoError(t, err) + + iter, err := store.ReverseIterator("exampleStore", 1, nil, nil) + assert.NoError(t, err) + assert.NotNil(t, iter) + + assert.True(t, iter.Valid()) + assert.Equal(t, []byte("key2"), iter.Key()) + assert.Equal(t, []byte("value2"), iter.Value()) + + iter.Next() + assert.True(t, iter.Valid()) + assert.Equal(t, []byte("key1"), iter.Key()) + assert.Equal(t, []byte("value1"), iter.Value()) + + iter.Next() + assert.False(t, iter.Valid()) + iter.Close() +} + +func TestGetLatestVersionAndSetLatestVersion(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.SetLatestVersion(2) + assert.NoError(t, err) + + version, err := store.GetLatestVersion() + assert.NoError(t, err) + assert.Equal(t, int64(2), version) +} + +func TestGetEarliestVersionAndSetEarliestVersion(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.SetEarliestVersion(1) + assert.NoError(t, err) + + version, err := store.GetEarliestVersion() + assert.NoError(t, err) + assert.Equal(t, int64(1), version) +} + +func TestPrune(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.ApplyChangeset(1, &seidbproto.NamedChangeSet{ + Changeset: iavl.ChangeSet{ + Pairs: []*iavl.KVPair{ + {Key: []byte("key1"), Value: []byte("value1")}, + {Key: []byte("key2"), Value: []byte("value2")}, + }, + }, + Name: "exampleStore", + }) + assert.NoError(t, err) + + err = store.Prune(1) + assert.NoError(t, err) + + _, err = store.Get("exampleStore", 1, []byte("key1")) + assert.Error(t, err) + + _, err = store.Get("exampleStore", 1, []byte("key2")) + assert.Error(t, err) +} + +func TestClose(t *testing.T) { + store := NewInMemoryStateStore() + + err := store.Close() + assert.NoError(t, err) +} diff --git a/cmd/seid/cmd/blocktest.go b/cmd/seid/cmd/blocktest.go index 177eef0e69..61bf133d0b 100644 --- a/cmd/seid/cmd/blocktest.go +++ b/cmd/seid/cmd/blocktest.go @@ -81,6 +81,7 @@ func BlocktestCmd(defaultNodeHome string) *cobra.Command { ), }, []aclkeeper.Option{}, + app.EmptyAppOptions, baseapp.SetPruning(storetypes.PruneEverything), baseapp.SetMinGasPrices(cast.ToString(serverCtx.Viper.Get(server.FlagMinGasPrices))), baseapp.SetMinRetainBlocks(cast.ToUint64(serverCtx.Viper.Get(server.FlagMinRetainBlocks))), diff --git a/cmd/seid/cmd/ethreplay.go b/cmd/seid/cmd/ethreplay.go index 5c331393cb..620c398d68 100644 --- a/cmd/seid/cmd/ethreplay.go +++ b/cmd/seid/cmd/ethreplay.go @@ -76,6 +76,7 @@ func ReplayCmd(defaultNodeHome string) *cobra.Command { ), }, []aclkeeper.Option{}, + app.EmptyAppOptions, baseapp.SetPruning(storetypes.PruneEverything), baseapp.SetMinGasPrices(cast.ToString(serverCtx.Viper.Get(server.FlagMinGasPrices))), baseapp.SetMinRetainBlocks(cast.ToUint64(serverCtx.Viper.Get(server.FlagMinRetainBlocks))), diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index 3c267eec53..f8f5a51f65 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -287,6 +287,7 @@ func newApp( ), }, []aclkeeper.Option{}, + app.EmptyAppOptions, baseapp.SetPruning(pruningOpts), baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))), @@ -324,12 +325,12 @@ func appExport( } if height != -1 { - exportableApp = app.New(logger, db, traceStore, false, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), true, nil, encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts) + exportableApp = app.New(logger, db, traceStore, false, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), true, nil, encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts, app.EmptyAppOptions) if err := exportableApp.LoadHeight(height); err != nil { return servertypes.ExportedApp{}, err } } else { - exportableApp = app.New(logger, db, traceStore, true, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), true, nil, encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts) + exportableApp = app.New(logger, db, traceStore, true, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), true, nil, encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts, app.EmptyAppOptions) } return exportableApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs) diff --git a/docker/localnode/Dockerfile b/docker/localnode/Dockerfile index 49283c86d3..ca705b2b61 100644 --- a/docker/localnode/Dockerfile +++ b/docker/localnode/Dockerfile @@ -2,8 +2,8 @@ FROM ubuntu:latest RUN apt-get update && \ apt-get install -y make git wget build-essential jq python3 curl vim uuid-runtime RUN rm -rf build/generated -RUN wget https://go.dev/dl/go1.21.4.linux-amd64.tar.gz -RUN tar -xvf go1.21.4.linux-amd64.tar.gz +RUN wget https://go.dev/dl/go1.22.4.linux-amd64.tar.gz +RUN tar -xvf go1.22.4.linux-amd64.tar.gz RUN mv go /usr/local/ RUN curl -L https://foundry.paradigm.xyz | bash RUN /root/.foundry/bin/foundryup diff --git a/evmrpc/filter_test.go b/evmrpc/filter_test.go index 051dc28b71..77e211b74a 100644 --- a/evmrpc/filter_test.go +++ b/evmrpc/filter_test.go @@ -255,7 +255,8 @@ func TestFilterGetFilterChanges(t *testing.T) { Address: common.HexToAddress("0x1111111111111111111111111111111111111112"), Topics: []common.Hash{}, }}}}) - EVMKeeper.SetReceipt(Ctx, common.HexToHash("0x123456789012345678902345678901234567890123456789012345678900005"), &types.Receipt{ + Ctx = Ctx.WithBlockHeight(9) + EVMKeeper.MockReceipt(Ctx, common.HexToHash("0x123456789012345678902345678901234567890123456789012345678900005"), &types.Receipt{ BlockNumber: 9, LogsBloom: bloom[:], Logs: []*types.Log{{ diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index 03e132d488..73b861e388 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -540,7 +540,7 @@ func generateTxData() { MultiTxBlockTx4 = txBuilder4.GetTx() DebugTraceTx = debugTraceTxBuilder.GetTx() TxNonEvm = app.TestTx{} - if err := EVMKeeper.SetReceipt(Ctx, tx1.Hash(), &types.Receipt{ + if err := EVMKeeper.MockReceipt(Ctx, tx1.Hash(), &types.Receipt{ From: "0x1234567890123456789012345678901234567890", To: "0x1234567890123456789012345678901234567890", TransactionIndex: 0, @@ -646,7 +646,7 @@ func setupLogs() { common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000123"), }, }}}}) - EVMKeeper.SetReceipt(Ctx, multiTxBlockTx1.Hash(), &types.Receipt{ + EVMKeeper.MockReceipt(Ctx, multiTxBlockTx1.Hash(), &types.Receipt{ BlockNumber: MultiTxBlockHeight, TransactionIndex: 1, // start at 1 bc 0 is the non-evm tx TxHashHex: multiTxBlockTx1.Hash().Hex(), @@ -666,7 +666,7 @@ func setupLogs() { common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000456"), }, }}}}) - EVMKeeper.SetReceipt(Ctx, multiTxBlockTx2.Hash(), &types.Receipt{ + EVMKeeper.MockReceipt(Ctx, multiTxBlockTx2.Hash(), &types.Receipt{ BlockNumber: MultiTxBlockHeight, TransactionIndex: 3, TxHashHex: multiTxBlockTx2.Hash().Hex(), @@ -683,7 +683,7 @@ func setupLogs() { common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000456"), }, }}}}) - EVMKeeper.SetReceipt(Ctx, multiTxBlockTx3.Hash(), &types.Receipt{ + EVMKeeper.MockReceipt(Ctx, multiTxBlockTx3.Hash(), &types.Receipt{ BlockNumber: MultiTxBlockHeight, TransactionIndex: 4, TxHashHex: multiTxBlockTx3.Hash().Hex(), @@ -700,7 +700,7 @@ func setupLogs() { common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000456"), }, }}}}) - EVMKeeper.SetReceipt(Ctx, multiTxBlockTx4.Hash(), &types.Receipt{ + EVMKeeper.MockReceipt(Ctx, multiTxBlockTx4.Hash(), &types.Receipt{ BlockNumber: MockHeight, TransactionIndex: 0, TxHashHex: multiTxBlockTx4.Hash().Hex(), @@ -710,7 +710,7 @@ func setupLogs() { Topics: []string{"0x0000000000000000000000000000000000000000000000000000000000000123", "0x0000000000000000000000000000000000000000000000000000000000000456"}, }}, }) - EVMKeeper.SetReceipt(Ctx, common.HexToHash(DebugTraceHashHex), &types.Receipt{ + EVMKeeper.MockReceipt(Ctx, common.HexToHash(DebugTraceHashHex), &types.Receipt{ BlockNumber: DebugTraceMockHeight, TransactionIndex: 0, TxHashHex: DebugTraceHashHex, diff --git a/evmrpc/tx_test.go b/evmrpc/tx_test.go index 002c31f87b..3e36046da1 100644 --- a/evmrpc/tx_test.go +++ b/evmrpc/tx_test.go @@ -24,7 +24,7 @@ func TestGetTxReceipt(t *testing.T) { receipt, err := EVMKeeper.GetReceipt(Ctx, common.HexToHash("0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e")) require.Nil(t, err) receipt.To = "" - EVMKeeper.SetReceipt(Ctx, common.HexToHash("0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e"), receipt) + EVMKeeper.MockReceipt(Ctx, common.HexToHash("0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e"), receipt) body := "{\"jsonrpc\": \"2.0\",\"method\": \"eth_getTransactionReceipt\",\"params\":[\"0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e\"],\"id\":\"test\"}" req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:%d", TestAddr, TestPort), strings.NewReader(body)) @@ -68,7 +68,7 @@ func TestGetTxReceipt(t *testing.T) { require.Nil(t, err) receipt.ContractAddress = "" receipt.To = "0x1234567890123456789012345678901234567890" - EVMKeeper.SetReceipt(Ctx, common.HexToHash("0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e"), receipt) + EVMKeeper.MockReceipt(Ctx, common.HexToHash("0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e"), receipt) body = "{\"jsonrpc\": \"2.0\",\"method\": \"eth_getTransactionReceipt\",\"params\":[\"0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e\"],\"id\":\"test\"}" req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:%d", TestAddr, TestPort), strings.NewReader(body)) require.Nil(t, err) @@ -215,7 +215,7 @@ func TestGetTransactionCount(t *testing.T) { func TestGetTransactionError(t *testing.T) { h := common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") - EVMKeeper.SetReceipt(Ctx, h, &types.Receipt{VmError: "test error"}) + EVMKeeper.MockReceipt(Ctx, h, &types.Receipt{VmError: "test error"}) resObj := sendRequestGood(t, "getTransactionErrorByHash", "0x1111111111111111111111111111111111111111111111111111111111111111") require.Equal(t, "test error", resObj["result"]) diff --git a/go.mod b/go.mod index 8960a30830..06a52bd876 100644 --- a/go.mod +++ b/go.mod @@ -346,12 +346,12 @@ require ( replace ( github.com/CosmWasm/wasmd => github.com/sei-protocol/sei-wasmd v0.1.5 github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.19 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.23 github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.1.9 github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.1 github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-22 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/sei-protocol/sei-db => github.com/sei-protocol/sei-db v0.0.38 + github.com/sei-protocol/sei-db => github.com/sei-protocol/sei-db v0.0.39 // Latest goleveldb is broken, we have to stick to this version github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.3.4 diff --git a/go.sum b/go.sum index 5cdf31b6ca..60c7f93541 100644 --- a/go.sum +++ b/go.sum @@ -1347,10 +1347,10 @@ github.com/sei-protocol/go-ethereum v1.13.5-sei-22 h1:t/m1qXER+DEMrcpqgoYmUxifkA github.com/sei-protocol/go-ethereum v1.13.5-sei-22/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= github.com/sei-protocol/goutils v0.0.2 h1:Bfa7Sv+4CVLNM20QcpvGb81B8C5HkQC/kW1CQpIbXDA= github.com/sei-protocol/goutils v0.0.2/go.mod h1:iYE2DuJfEnM+APPehr2gOUXfuLuPsVxorcDO+Tzq9q8= -github.com/sei-protocol/sei-cosmos v0.3.19 h1:EQZ+i0virWhmE6XY4w9Dc4tYhVOdZos6Gl9qU39eMGU= -github.com/sei-protocol/sei-cosmos v0.3.19/go.mod h1:rWb90yP2YTqF3CxcdcMvxHYsdLihTNMA5wZi7FwtaeI= -github.com/sei-protocol/sei-db v0.0.38 h1:GiQl3qBd6XgGsHkJd4I8GnOmGjGoWQg3SJAS82TTNao= -github.com/sei-protocol/sei-db v0.0.38/go.mod h1:F/ZKZA8HJPcUzSZPA8yt6pfwlGriJ4RDR4eHKSGLStI= +github.com/sei-protocol/sei-cosmos v0.3.23 h1:ObuJ1EWrQKvqpKye4WFXaGwttpXTNrVmzSSskisFNFw= +github.com/sei-protocol/sei-cosmos v0.3.23/go.mod h1:og/KbejR/zSQ8otapODEDU9zYNhFSUDbq9+tgeYePyU= +github.com/sei-protocol/sei-db v0.0.39 h1:/XwwlObPhWnX8zH8GDbDvyn+a/K2911HZlmlBZzN+gQ= +github.com/sei-protocol/sei-db v0.0.39/go.mod h1:F/ZKZA8HJPcUzSZPA8yt6pfwlGriJ4RDR4eHKSGLStI= github.com/sei-protocol/sei-iavl v0.1.9 h1:y4mVYftxLNRs6533zl7N0/Ch+CzRQc04JDfHolIxgBE= github.com/sei-protocol/sei-iavl v0.1.9/go.mod h1:7PfkEVT5dcoQE+s/9KWdoXJ8VVVP1QpYYPLdxlkSXFk= github.com/sei-protocol/sei-ibc-go/v3 v3.3.1 h1:BPG9LWe27x3SATpY9nj8JPe+0igyKyrcpB0z2ZvdcXQ= diff --git a/testutil/network/network.go b/testutil/network/network.go index 836703fa6f..14b0e6d22b 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -76,6 +76,7 @@ func DefaultConfig() network.Config { &TestAppOptions{}, nil, app.EmptyACLOpts, + app.EmptyAppOptions, baseapp.SetPruning(storetypes.NewPruningOptionsFromString(val.AppConfig.Pruning)), baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices), ) diff --git a/x/evm/artifacts/native/artifacts_test.go b/x/evm/artifacts/native/artifacts_test.go index e023def9b2..46aa6b7fd9 100644 --- a/x/evm/artifacts/native/artifacts_test.go +++ b/x/evm/artifacts/native/artifacts_test.go @@ -68,6 +68,7 @@ func TestSimple(t *testing.T) { require.Nil(t, err) require.Empty(t, res.VmError) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) diff --git a/x/evm/client/wasm/query_test.go b/x/evm/client/wasm/query_test.go index 194aec2cc4..5b04471e91 100644 --- a/x/evm/client/wasm/query_test.go +++ b/x/evm/client/wasm/query_test.go @@ -345,6 +345,7 @@ func deployContract(t *testing.T, ctx sdk.Context, k *keeper.Keeper, path string res, err := msgServer.EVMTransaction(sdk.WrapSDKContext(ctx), req) require.Nil(t, err) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) if receipt.Status != 1 { diff --git a/x/evm/genesis_test.go b/x/evm/genesis_test.go index 19a0a9f961..bcb78c0cb5 100644 --- a/x/evm/genesis_test.go +++ b/x/evm/genesis_test.go @@ -21,7 +21,7 @@ func TestExportImportGenesis(t *testing.T) { keeper.SetCode(ctx, codeAddr, []byte("abcde")) keeper.SetState(ctx, codeAddr, common.BytesToHash([]byte("123")), common.BytesToHash([]byte("456"))) keeper.SetNonce(ctx, evmAddr, 2) - keeper.SetReceipt(ctx, common.BytesToHash([]byte("789")), &types.Receipt{TxType: 2}) + keeper.MockReceipt(ctx, common.BytesToHash([]byte("789")), &types.Receipt{TxType: 2}) keeper.SetBlockBloom(ctx, 5, []ethtypes.Bloom{{1}}) keeper.SetTxHashesOnHeight(ctx, 5, []common.Hash{common.BytesToHash([]byte("123"))}) keeper.SetERC20CW20Pointer(ctx, "cw20addr", codeAddr) diff --git a/x/evm/integration_test.go b/x/evm/integration_test.go index f28397768d..808de0fe7f 100644 --- a/x/evm/integration_test.go +++ b/x/evm/integration_test.go @@ -177,6 +177,8 @@ func TestCW2981PointerToERC2981(t *testing.T) { require.Nil(t, err) res := testkeeper.EVMTestApp.DeliverTx(ctx, abci.RequestDeliverTx{Tx: txbz}, cosmosTx, sha256.Sum256(txbz)) require.Equal(t, uint32(0), res.Code) + err = k.FlushTransientReceipts(ctx) + require.NoError(t, err) receipt, err := k.GetReceipt(ctx, tx.Hash()) require.Nil(t, err) require.NotEmpty(t, receipt.ContractAddress) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 1bc21c2dd6..f361ba890a 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/tests" + seidbtypes "github.com/sei-protocol/sei-db/ss/types" abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" @@ -42,9 +43,11 @@ import ( ) type Keeper struct { - storeKey sdk.StoreKey - memStoreKey sdk.StoreKey - Paramstore paramtypes.Subspace + storeKey sdk.StoreKey + memStoreKey sdk.StoreKey + transientStoreKey sdk.StoreKey + + Paramstore paramtypes.Subspace deferredInfo *sync.Map txResults []*abci.ExecTxResult @@ -77,6 +80,8 @@ type Keeper struct { DB ethstate.Database Root common.Hash ReplayBlock *ethtypes.Block + + receiptStore seidbtypes.StateStore } type EvmTxDeferredInfo struct { @@ -116,7 +121,7 @@ func (ctx *ReplayChainContext) GetHeader(hash common.Hash, number uint64) *ethty } func NewKeeper( - storeKey sdk.StoreKey, memStoreKey sdk.StoreKey, paramstore paramtypes.Subspace, + storeKey sdk.StoreKey, memStoreKey sdk.StoreKey, transientStoreKey sdk.StoreKey, paramstore paramtypes.Subspace, receiptStateStore seidbtypes.StateStore, bankKeeper bankkeeper.Keeper, accountKeeper *authkeeper.AccountKeeper, stakingKeeper *stakingkeeper.Keeper, transferKeeper ibctransferkeeper.Keeper, wasmKeeper *wasmkeeper.PermissionedKeeper, wasmViewKeeper *wasmkeeper.Keeper) *Keeper { if !paramstore.HasKeyTable() { @@ -125,6 +130,7 @@ func NewKeeper( k := &Keeper{ storeKey: storeKey, memStoreKey: memStoreKey, + transientStoreKey: transientStoreKey, Paramstore: paramstore, bankKeeper: bankKeeper, accountKeeper: accountKeeper, @@ -137,6 +143,7 @@ func NewKeeper( cachedFeeCollectorAddressMtx: &sync.RWMutex{}, keyToNonce: make(map[tmtypes.TxKey]*AddressNoncePair), deferredInfo: &sync.Map{}, + receiptStore: receiptStateStore, } return k } diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 2ec76736e2..1ee18a7d3b 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -254,7 +254,7 @@ func (server msgServer) writeReceipt(ctx sdk.Context, origMsg *types.MsgEVMTrans receipt.From = origMsg.Derived.SenderEVMAddr.Hex() - return receipt, server.SetReceipt(ctx, tx.Hash(), receipt) + return receipt, server.SetTransientReceipt(ctx, tx.Hash(), receipt) } func (server msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { diff --git a/x/evm/keeper/msg_server_test.go b/x/evm/keeper/msg_server_test.go index bfccd537e0..7a96a68f59 100644 --- a/x/evm/keeper/msg_server_test.go +++ b/x/evm/keeper/msg_server_test.go @@ -94,6 +94,7 @@ func TestEVMTransaction(t *testing.T) { require.NotEmpty(t, res.Hash) require.Equal(t, uint64(1000000)-res.GasUsed, k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64()) require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx.TxIndex()), k.GetBaseDenom(ctx)).Amount.Uint64()) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) @@ -128,6 +129,7 @@ func TestEVMTransaction(t *testing.T) { require.Nil(t, err) require.LessOrEqual(t, res.GasUsed, uint64(200000)) require.Empty(t, res.VmError) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err = k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) @@ -179,6 +181,7 @@ func TestEVMTransactionError(t *testing.T) { require.NotEmpty(t, res.VmError) // gas should be charged and receipt should be created require.Equal(t, uint64(800000), k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64()) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.Equal(t, uint32(ethtypes.ReceiptStatusFailed), receipt.Status) @@ -284,6 +287,7 @@ func TestEVMDynamicFeeTransaction(t *testing.T) { require.NotEmpty(t, res.ReturnData) require.NotEmpty(t, res.Hash) require.LessOrEqual(t, k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64(), uint64(1000000)-res.GasUsed) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) @@ -342,6 +346,7 @@ func TestEVMPrecompiles(t *testing.T) { require.NotEmpty(t, res.Hash) require.Equal(t, uint64(1000000)-res.GasUsed, k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64()) require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx.TxIndex()), k.GetBaseDenom(ctx)).Amount.Uint64()) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) @@ -385,6 +390,7 @@ func TestEVMPrecompiles(t *testing.T) { require.Nil(t, err) require.LessOrEqual(t, res.GasUsed, uint64(200000)) require.Empty(t, res.VmError) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err = k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) @@ -457,6 +463,7 @@ func TestEVMBlockEnv(t *testing.T) { require.NotEmpty(t, res.Hash) require.Equal(t, uint64(1000000)-res.GasUsed, k.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), "usei").Amount.Uint64()) require.Equal(t, res.GasUsed, k.BankKeeper().GetBalance(ctx, state.GetCoinbaseAddress(ctx.TxIndex()), k.GetBaseDenom(ctx)).Amount.Uint64()) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) @@ -491,6 +498,7 @@ func TestEVMBlockEnv(t *testing.T) { require.Nil(t, err) require.LessOrEqual(t, res.GasUsed, uint64(200000)) require.Empty(t, res.VmError) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err = k.GetReceipt(ctx, common.HexToHash(res.Hash)) require.Nil(t, err) require.NotNil(t, receipt) @@ -639,6 +647,7 @@ func TestEvmError(t *testing.T) { res := testkeeper.EVMTestApp.DeliverTx(ctx, abci.RequestDeliverTx{Tx: txbz}, sdktx, sha256.Sum256(txbz)) require.Equal(t, uint32(0), res.Code) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err := k.GetReceipt(ctx, common.HexToHash(res.EvmTxInfo.TxHash)) require.Nil(t, err) @@ -671,6 +680,7 @@ func TestEvmError(t *testing.T) { res = testkeeper.EVMTestApp.DeliverTx(ctx, abci.RequestDeliverTx{Tx: txbz}, sdktx, sha256.Sum256(txbz)) require.Equal(t, uint32(45), res.Code) + require.NoError(t, k.FlushTransientReceipts(ctx)) receipt, err = k.GetReceipt(ctx, common.HexToHash(res.EvmTxInfo.TxHash)) require.Nil(t, err) require.Equal(t, receipt.VmError, res.EvmTxInfo.VmError) diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index 43e6c93880..5872ea6a74 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -2,34 +2,88 @@ package keeper import ( "errors" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/iavl" "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-db/proto" + "github.com/sei-protocol/sei-chain/x/evm/types" ) -// Receipt is a data structure that stores EVM specific transaction metadata. +// SetTransientReceipt sets a data structure that stores EVM specific transaction metadata. +func (k *Keeper) SetTransientReceipt(ctx sdk.Context, txHash common.Hash, receipt *types.Receipt) error { + store := ctx.TransientStore(k.transientStoreKey) + bz, err := receipt.Marshal() + if err != nil { + return err + } + store.Set(types.ReceiptKey(txHash), bz) + return nil +} + +// GetReceipt returns a data structure that stores EVM specific transaction metadata. // 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) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ReceiptKey(txHash)) + + // receipts are immutable, use latest version + lv, err := k.receiptStore.GetLatestVersion() + if err != nil { + return nil, err + } + + // try persistent store + bz, err := k.receiptStore.Get(types.ReceiptStoreKey, lv, types.ReceiptKey(txHash)) + if err != nil { + return nil, err + } + if bz == nil { - return nil, errors.New("not found") + // 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") + } } - r := types.Receipt{} + + var r types.Receipt if err := r.Unmarshal(bz); err != nil { return nil, err } return &r, nil } -func (k *Keeper) SetReceipt(ctx sdk.Context, txHash common.Hash, receipt *types.Receipt) error { - store := ctx.KVStore(k.storeKey) - bz, err := receipt.Marshal() - if err != nil { +// 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 { + fmt.Printf("MOCK RECEIPT height=%d, tx=%s\n", ctx.BlockHeight(), txHash.Hex()) + if err := k.SetTransientReceipt(ctx, txHash, receipt); err != nil { return err } - store.Set(types.ReceiptKey(txHash), bz) - return nil + return k.FlushTransientReceipts(ctx) +} + +func (k *Keeper) FlushTransientReceipts(ctx sdk.Context) error { + iter := ctx.TransientStore(k.transientStoreKey).Iterator(nil, nil) + defer iter.Close() + var pairs []*iavl.KVPair + var changesets []*proto.NamedChangeSet + for ; iter.Valid(); iter.Next() { + kvPair := &iavl.KVPair{Key: iter.Key(), Value: iter.Value()} + pairs = append(pairs, kvPair) + } + if len(pairs) == 0 { + return nil + } + ncs := &proto.NamedChangeSet{ + Name: types.ReceiptStoreKey, + Changeset: iavl.ChangeSet{Pairs: pairs}, + } + changesets = append(changesets, ncs) + + return k.receiptStore.ApplyChangesetAsync(ctx.BlockHeight(), changesets) } diff --git a/x/evm/keeper/receipt_test.go b/x/evm/keeper/receipt_test.go index fc1971d8f9..5111e4081a 100644 --- a/x/evm/keeper/receipt_test.go +++ b/x/evm/keeper/receipt_test.go @@ -14,7 +14,7 @@ func TestReceipt(t *testing.T) { txHash := common.HexToHash("0x0750333eac0be1203864220893d8080dd8a8fd7a2ed098dfd92a718c99d437f2") _, err := k.GetReceipt(ctx, txHash) require.NotNil(t, err) - k.SetReceipt(ctx, txHash, &types.Receipt{TxHashHex: txHash.Hex()}) + k.MockReceipt(ctx, txHash, &types.Receipt{TxHashHex: txHash.Hex()}) r, err := k.GetReceipt(ctx, txHash) require.Nil(t, err) require.Equal(t, txHash.Hex(), r.TxHashHex) diff --git a/x/evm/module.go b/x/evm/module.go index 0fa5a2d8a1..9f6209c797 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -230,7 +230,7 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val surplus := am.keeper.GetAnteSurplusSum(ctx) for _, deferredInfo := range evmTxDeferredInfoList { if deferredInfo.Error != "" && deferredInfo.TxHash.Cmp(ethtypes.EmptyTxsHash) != 0 { - _ = am.keeper.SetReceipt(ctx, deferredInfo.TxHash, &types.Receipt{ + _ = am.keeper.SetTransientReceipt(ctx, deferredInfo.TxHash, &types.Receipt{ TxHashHex: deferredInfo.TxHash.Hex(), TransactionIndex: uint32(deferredInfo.TxIndx), VmError: deferredInfo.Error, diff --git a/x/evm/module_test.go b/x/evm/module_test.go index e48acce5ac..12d1b99036 100644 --- a/x/evm/module_test.go +++ b/x/evm/module_test.go @@ -110,6 +110,8 @@ func TestABCI(t *testing.T) { k.AppendErrorToEvmTxDeferredInfo(ctx.WithTxIndex(0), common.Hash{1}, "test error") k.SetTxResults([]*abci.ExecTxResult{{Code: 1}}) m.EndBlock(ctx, abci.RequestEndBlock{}) + err = k.FlushTransientReceipts(ctx) + require.NoError(t, err) receipt, err := k.GetReceipt(ctx, common.Hash{1}) require.Nil(t, err) require.Equal(t, receipt.BlockNumber, uint64(ctx.BlockHeight())) diff --git a/x/evm/types/keys.go b/x/evm/types/keys.go index 70bd371192..946cf78947 100644 --- a/x/evm/types/keys.go +++ b/x/evm/types/keys.go @@ -18,6 +18,10 @@ const ( MemStoreKey = "evm_mem" + TransientStoreKey = "evm_transient" + + ReceiptStoreKey = "receipt" + // QuerierRoute is the querier route for auth QuerierRoute = ModuleName )