diff --git a/arbitrum/apibackend.go b/arbitrum/apibackend.go index 9cd3073a41..6d0e6b7532 100644 --- a/arbitrum/apibackend.go +++ b/arbitrum/apibackend.go @@ -45,6 +45,8 @@ var ( type APIBackend struct { b *Backend + dbForAPICalls ethdb.Database + fallbackClient types.FallbackClient sync SyncProgressBackend } @@ -101,8 +103,15 @@ func createRegisterAPIBackend(backend *Backend, filterConfig filters.Config, fal if err != nil { return nil, err } + // discard stylus-tag on any call made from api database + dbForAPICalls := backend.chainDb + wasmStore, tag := backend.chainDb.WasmDataBase() + if tag != 0 { + dbForAPICalls = rawdb.WrapDatabaseWithWasm(backend.chainDb, wasmStore, 0) + } backend.apiBackend = &APIBackend{ b: backend, + dbForAPICalls: dbForAPICalls, fallbackClient: fallbackClient, } filterSystem := filters.NewFilterSystem(backend.apiBackend, filterConfig) @@ -314,7 +323,7 @@ func (a *APIBackend) FeeHistory( } func (a *APIBackend) ChainDb() ethdb.Database { - return a.b.chainDb + return a.dbForAPICalls } func (a *APIBackend) AccountManager() *accounts.Manager { diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index 6da71a497e..43d17f5b05 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -55,12 +55,6 @@ func (db *RecordingKV) Get(key []byte) ([]byte, error) { // Retrieving code copy(hash[:], key[len(rawdb.CodePrefix):]) res, err = db.diskDb.Get(key) - } else if ok, _ := rawdb.IsActivatedAsmKey(key); ok { - // Arbitrum: the asm is non-consensus - return db.diskDb.Get(key) - } else if ok, _ := rawdb.IsActivatedModuleKey(key); ok { - // Arbitrum: the module is non-consensus (only its hash is) - return db.diskDb.Get(key) } else { err = fmt.Errorf("recording KV attempted to access non-hash key %v", hex.EncodeToString(key)) } @@ -275,7 +269,7 @@ func (r *RecordingDatabase) PrepareRecording(ctx context.Context, lastBlockHeade defer func() { r.Dereference(finalDereference) }() recordingKeyValue := newRecordingKV(r.db.TrieDB(), r.db.DiskDB()) - recordingStateDatabase := state.NewDatabase(rawdb.NewDatabase(recordingKeyValue)) + recordingStateDatabase := state.NewDatabase(rawdb.WrapDatabaseWithWasm(rawdb.NewDatabase(recordingKeyValue), r.db.WasmStore(), 0)) var prevRoot common.Hash if lastBlockHeader != nil { prevRoot = lastBlockHeader.Root diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 29a3ec1615..3aa099c0f8 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -42,6 +42,11 @@ type freezerdb struct { ethdb.AncientStore } +// AncientDatadir returns the path of root ancient directory. +func (frdb *freezerdb) WasmDataBase() (ethdb.KeyValueStore, uint32) { + return frdb, 0 +} + // AncientDatadir returns the path of root ancient directory. func (frdb *freezerdb) AncientDatadir() (string, error) { return frdb.ancientRoot, nil @@ -165,12 +170,40 @@ func (db *nofreezedb) AncientDatadir() (string, error) { return "", errNotSupported } +// AncientDatadir returns the path of root ancient directory. +func (db *nofreezedb) WasmDataBase() (ethdb.KeyValueStore, uint32) { + return db, 0 +} + // NewDatabase creates a high level database on top of a given key-value data // store without a freezer moving immutable chain segments into cold storage. func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { return &nofreezedb{KeyValueStore: db} } +type dbWithWasmEntry struct { + ethdb.Database + wasmDb ethdb.KeyValueStore + wasmCacheTag uint32 +} + +func (db *dbWithWasmEntry) WasmDataBase() (ethdb.KeyValueStore, uint32) { + return db.wasmDb, db.wasmCacheTag +} + +func (db *dbWithWasmEntry) Close() error { + dbErr := db.Database.Close() + wasmErr := db.wasmDb.Close() + if dbErr != nil { + return dbErr + } + return wasmErr +} + +func WrapDatabaseWithWasm(db ethdb.Database, wasm ethdb.KeyValueStore, cacheTag uint32) ethdb.Database { + return &dbWithWasmEntry{db, wasm, cacheTag} +} + // resolveChainFreezerDir is a helper function which resolves the absolute path // of chain freezer by considering backward compatibility. func resolveChainFreezerDir(ancient string) string { diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 19e4ed5b5c..5f09b48203 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -40,6 +40,10 @@ func (t *table) Close() error { return nil } +func (t *table) WasmDataBase() (ethdb.KeyValueStore, uint32) { + return t.db.WasmDataBase() +} + // Has retrieves if a prefixed version of a key is present in the database. func (t *table) Has(key []byte) (bool, error) { return t.db.Has(append([]byte(t.prefix), key...)) diff --git a/core/state/database.go b/core/state/database.go index 48022d0e99..aa671be038 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -54,6 +54,8 @@ type Database interface { // Arbitrum: Read activated Stylus contracts ActivatedAsm(moduleHash common.Hash) (asm []byte, err error) ActivatedModule(moduleHash common.Hash) (module []byte, err error) + WasmStore() ethdb.KeyValueStore + WasmCacheTag() uint32 // OpenTrie opens the main account trie. OpenTrie(root common.Hash) (Trie, error) @@ -158,12 +160,15 @@ func NewDatabase(db ethdb.Database) Database { // is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a // large memory cache. func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { + wasmdb, wasmTag := db.WasmDataBase() cdb := &cachingDB{ // Arbitrum only activatedAsmCache: lru.NewSizeConstrainedCache[common.Hash, []byte](activatedWasmCacheSize), activatedModuleCache: lru.NewSizeConstrainedCache[common.Hash, []byte](activatedWasmCacheSize), + wasmTag: wasmTag, disk: db, + wasmdb: wasmdb, codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: trie.NewDatabase(db, config), @@ -173,12 +178,15 @@ func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { // NewDatabaseWithNodeDB creates a state database with an already initialized node database. func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database { + wasmdb, wasmTag := db.WasmDataBase() cdb := &cachingDB{ // Arbitrum only activatedAsmCache: lru.NewSizeConstrainedCache[common.Hash, []byte](activatedWasmCacheSize), activatedModuleCache: lru.NewSizeConstrainedCache[common.Hash, []byte](activatedWasmCacheSize), + wasmTag: wasmTag, disk: db, + wasmdb: wasmdb, codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: triedb, @@ -190,13 +198,23 @@ type cachingDB struct { // Arbitrum activatedAsmCache *lru.SizeConstrainedCache[common.Hash, []byte] activatedModuleCache *lru.SizeConstrainedCache[common.Hash, []byte] + wasmTag uint32 disk ethdb.KeyValueStore + wasmdb ethdb.KeyValueStore codeSizeCache *lru.Cache[common.Hash, int] codeCache *lru.SizeConstrainedCache[common.Hash, []byte] triedb *trie.Database } +func (db *cachingDB) WasmStore() ethdb.KeyValueStore { + return db.wasmdb +} + +func (db *cachingDB) WasmCacheTag() uint32 { + return db.wasmTag +} + // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { diff --git a/core/state/database_arbitrum.go b/core/state/database_arbitrum.go index 6e545eb0a0..6171b4cd10 100644 --- a/core/state/database_arbitrum.go +++ b/core/state/database_arbitrum.go @@ -12,7 +12,7 @@ func (db *cachingDB) ActivatedAsm(moduleHash common.Hash) ([]byte, error) { return asm, nil } wasmKey := rawdb.ActivatedAsmKey(moduleHash) - asm, err := db.disk.Get(wasmKey[:]) + asm, err := db.wasmdb.Get(wasmKey[:]) if err != nil { return nil, err } @@ -28,7 +28,7 @@ func (db *cachingDB) ActivatedModule(moduleHash common.Hash) ([]byte, error) { return module, nil } wasmKey := rawdb.ActivatedModuleKey(moduleHash) - module, err := db.disk.Get(wasmKey[:]) + module, err := db.wasmdb.Get(wasmKey[:]) if err != nil { return nil, err } diff --git a/core/state/journal_arbitrum.go b/core/state/journal_arbitrum.go index 804a308ec2..69b2415b79 100644 --- a/core/state/journal_arbitrum.go +++ b/core/state/journal_arbitrum.go @@ -17,17 +17,18 @@ func (ch wasmActivation) dirtied() *common.Address { } // Updates the Rust-side recent program cache -var CacheWasmRust func(asm []byte, moduleHash common.Hash, version uint16, debug bool) = func([]byte, common.Hash, uint16, bool) {} -var EvictWasmRust func(moduleHash common.Hash, version uint16, debug bool) = func(common.Hash, uint16, bool) {} +var CacheWasmRust func(asm []byte, moduleHash common.Hash, version uint16, tag uint32, debug bool) = func([]byte, common.Hash, uint16, uint32, bool) {} +var EvictWasmRust func(moduleHash common.Hash, version uint16, tag uint32, debug bool) = func(common.Hash, uint16, uint32, bool) {} type CacheWasm struct { ModuleHash common.Hash Version uint16 + Tag uint32 Debug bool } func (ch CacheWasm) revert(s *StateDB) { - EvictWasmRust(ch.ModuleHash, ch.Version, ch.Debug) + EvictWasmRust(ch.ModuleHash, ch.Version, ch.Tag, ch.Debug) } func (ch CacheWasm) dirtied() *common.Address { @@ -37,12 +38,16 @@ func (ch CacheWasm) dirtied() *common.Address { type EvictWasm struct { ModuleHash common.Hash Version uint16 + Tag uint32 Debug bool } func (ch EvictWasm) revert(s *StateDB) { - asm := s.GetActivatedAsm(ch.ModuleHash) // only happens in native mode - CacheWasmRust(asm, ch.ModuleHash, ch.Version, ch.Debug) + asm, err := s.TryGetActivatedAsm(ch.ModuleHash) // only happens in native mode + if err == nil && len(asm) != 0 { + //if we failed to get it - it's not in the current rust cache + CacheWasmRust(asm, ch.ModuleHash, ch.Version, ch.Tag, ch.Debug) + } } func (ch EvictWasm) dirtied() *common.Address { diff --git a/core/state/statedb.go b/core/state/statedb.go index 28c6aeb2c2..6dc72f88e6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1249,6 +1249,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er storageTrieNodesDeleted int nodes = trienode.NewMergedNodeSet() codeWriter = s.db.DiskDB().NewBatch() + wasmCodeWriter = s.db.WasmStore().NewBatch() ) // Handle all state deletions first incomplete, err := s.handleDestruction(nodes) @@ -1286,7 +1287,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // Arbitrum: write Stylus programs to disk for moduleHash, info := range s.arbExtraData.activatedWasms { - rawdb.WriteActivation(codeWriter, moduleHash, info.Asm, info.Module) + rawdb.WriteActivation(wasmCodeWriter, moduleHash, info.Asm, info.Module) } if len(s.arbExtraData.activatedWasms) > 0 { s.arbExtraData.activatedWasms = make(map[common.Hash]*ActivatedWasm) @@ -1297,6 +1298,11 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er log.Crit("Failed to commit dirty codes", "error", err) } } + if wasmCodeWriter.ValueSize() > 0 { + if err := wasmCodeWriter.Write(); err != nil { + log.Crit("Failed to commit dirty stylus codes", "error", err) + } + } // Write the account trie changes, measuring the amount of wasted time var start time.Time if metrics.EnabledExpensive { diff --git a/core/state/statedb_arbitrum.go b/core/state/statedb_arbitrum.go index 0d769f8f56..e459ad4570 100644 --- a/core/state/statedb_arbitrum.go +++ b/core/state/statedb_arbitrum.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -89,16 +90,12 @@ func (s *StateDB) ActivateWasm(moduleHash common.Hash, asm, module []byte) { }) } -func (s *StateDB) GetActivatedAsm(moduleHash common.Hash) []byte { +func (s *StateDB) TryGetActivatedAsm(moduleHash common.Hash) ([]byte, error) { info, exists := s.arbExtraData.activatedWasms[moduleHash] if exists { - return info.Asm + return info.Asm, nil } - asm, err := s.db.ActivatedAsm(moduleHash) - if err != nil { - s.setError(fmt.Errorf("failed to load asm for %x: %v", moduleHash, err)) - } - return asm + return s.db.ActivatedAsm(moduleHash) } func (s *StateDB) GetActivatedModule(moduleHash common.Hash) []byte { @@ -237,9 +234,13 @@ func (s *StateDB) StartRecording() { } func (s *StateDB) RecordProgram(moduleHash common.Hash) { + asm, err := s.TryGetActivatedAsm(moduleHash) + if err != nil { + log.Crit("can't find activated wasm while recording", "modulehash", moduleHash) + } if s.arbExtraData.userWasms != nil { s.arbExtraData.userWasms[moduleHash] = ActivatedWasm{ - Asm: s.GetActivatedAsm(moduleHash), + Asm: asm, Module: s.GetActivatedModule(moduleHash), } } diff --git a/core/vm/interface.go b/core/vm/interface.go index d94d9cfcec..43393b54f7 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -30,7 +30,7 @@ import ( type StateDB interface { // Arbitrum: manage Stylus wasms ActivateWasm(moduleHash common.Hash, asm, module []byte) - GetActivatedAsm(moduleHash common.Hash) (asm []byte) + TryGetActivatedAsm(moduleHash common.Hash) (asm []byte, err error) GetActivatedModule(moduleHash common.Hash) (module []byte) RecordCacheWasm(wasm state.CacheWasm) RecordEvictWasm(wasm state.EvictWasm) diff --git a/ethdb/database.go b/ethdb/database.go index 4d4817daf2..f8e9be0ca3 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -178,6 +178,10 @@ type AncientStore interface { io.Closer } +type WasmDataBaseRetriever interface { + WasmDataBase() (KeyValueStore, uint32) +} + // Database contains all the methods required by the high level database to not // only access the key-value data store but also the chain freezer. type Database interface { @@ -189,4 +193,5 @@ type Database interface { Compacter Snapshotter io.Closer + WasmDataBaseRetriever } diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index c1c803caf2..ef38ce786d 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -39,6 +39,10 @@ func (db *Database) Has(key []byte) (bool, error) { return true, nil } +func (t *Database) WasmDataBase() (ethdb.KeyValueStore, uint32) { + return t, 0 +} + func (db *Database) Get(key []byte) ([]byte, error) { var resp hexutil.Bytes err := db.remote.Call(&resp, "debug_dbGet", hexutil.Bytes(key)) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fcc5d28446..1223441b98 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -968,6 +968,9 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { return nil } for addr, account := range *diff { + if addr == types.ArbosStateAddress { + return fmt.Errorf("overriding address %v not allowed", types.ArbosStateAddress) + } // Override account nonce. if account.Nonce != nil { state.SetNonce(addr, uint64(*account.Nonce)) diff --git a/node/node.go b/node/node.go index be015d343d..d8a2905787 100644 --- a/node/node.go +++ b/node/node.go @@ -810,7 +810,6 @@ func (n *Node) OpenDatabaseWithFreezerWithExtraOptions(name string, cache, handl PebbleExtraOptions: pebbleExtraOptions, }) } - if err == nil { db = n.wrapDatabase(db) }