package state import ( "errors" "fmt" "sync/atomic" versa "github.com/bnb-chain/versioned-state-database" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/triedb" ) const InvalidSateObjectVersion int64 = math.MinInt64 type cachingVersaDB struct { triedb *triedb.Database versionDB versa.Database codeDB ethdb.KeyValueStore codeSizeCache *lru.Cache[common.Hash, int] codeCache *lru.SizeConstrainedCache[common.Hash, []byte] accTree *VersaTree state versa.StateHandler root common.Hash mode versa.StateMode hasState atomic.Bool } // NewVersaDatabase should be call by NewDatabaseWithNodeDB // TODO:: NewDatabaseWithNodeDB should add mode param. func NewVersaDatabase(db ethdb.Database, triedb *triedb.Database, mode versa.StateMode) Database { return &cachingVersaDB{ triedb: triedb, versionDB: triedb.VersaDB(), codeDB: db, codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), mode: mode, state: versa.ErrStateHandler, } } func (cv *cachingVersaDB) Copy() Database { cp := &cachingVersaDB{} cp.codeCache = cv.codeCache cp.codeSizeCache = cv.codeSizeCache cp.triedb = cv.triedb cp.versionDB = cv.versionDB cp.codeDB = cv.codeDB cp.mode = versa.S_RW // it is important // TODO:: maybe add lock for cv.root if cv.hasState.Load() { _, err := cp.OpenTrie(cv.root) if err != nil { log.Error("failed to open trie in copy caching versa db", "error", err) return cp } } return cp } // CopyTrie is used with Copy() func (cv *cachingVersaDB) CopyTrie(tr Trie) Trie { vtr, ok := tr.(*VersaTree) if !ok { panic("caching versa db copy non versa tree") } if vtr.accountTree { if cv.accTree != nil { if cv.accTree.root.Cmp(vtr.root) != 0 { panic("copy acc trie mismatch") } return cv.accTree } tree, err := cv.OpenTrie(vtr.root) if err != nil { log.Error("failed to open trie in CopyTrie", "error", err) return nil } return tree } else { tree, err := cv.OpenStorageTrie(vtr.stateRoot, vtr.address, vtr.root, nil) if err != nil { log.Error("failed to open storage trie in CopyTrie", "error", err) return nil } return tree } return nil } func (cv *cachingVersaDB) HasState(root common.Hash) bool { return cv.versionDB.HasState(root) } func (cv *cachingVersaDB) OpenTrie(root common.Hash) (Trie, error) { if cv.hasState.Load() { //TODO:: will change to log.Error after stabilization panic("account tree has open") } // TODO:: if root tree, versa db should ignore check version, temp use -1 state, err := cv.versionDB.OpenState(-1, root, cv.mode) if err != nil { log.Error("failed to open state", "error", err) return nil, err } handler, err := cv.versionDB.OpenTree(state, -1, common.Hash{}, root) if err != nil { log.Error("failed to open trie", "error", err) return nil, err } tree := &VersaTree{ db: cv.versionDB, handler: handler, accountTree: true, root: root, mode: cv.mode, } cv.state = state cv.hasState.Store(true) // if set, can't change cv.accTree = tree cv.root = root return tree, nil } func (cv *cachingVersaDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, _ Trie) (Trie, error) { version, _, err := cv.accTree.getAccountWithVersion(address) if err != nil { log.Error("failed to open storage trie", "error", err) return nil, err } return cv.openStorageTreeWithVersion(version, stateRoot, address, root) } func (cv *cachingVersaDB) openStorageTreeWithVersion(version int64, stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error) { if !cv.hasState.Load() { //TODO:: will change to log.Error after stabilization panic("open account tree, before open storage tree") } if cv.root.Cmp(stateRoot) != 0 { panic(fmt.Sprintf("account root mismatch, on open storage tree, actual: %s, expect: %s", root.String(), cv.root.String())) } handler, err := cv.versionDB.OpenTree(cv.state, version, crypto.Keccak256Hash(address.Bytes()), root) if err != nil { log.Error("failed to open storage trie", "error", err) return nil, err } log.Info("open storage tree", "address", address.String(), "hash address", crypto.Keccak256Hash(address.Bytes()).String()) tree := &VersaTree{ db: cv.versionDB, handler: handler, version: version, root: root, stateRoot: stateRoot, address: address, mode: cv.mode, } return tree, nil } // Flush unique to versa func (cv *cachingVersaDB) Flush() error { return cv.versionDB.Flush(cv.state) } // Release unique to versa func (cv *cachingVersaDB) Release() error { //log.Info("close state", "state info", cv.versionDB.ParseStateHandler(cv.state)) if cv.state != versa.ErrStateHandler { if err := cv.versionDB.CloseState(cv.state); err != nil { return err } cv.hasState.Store(false) cv.accTree = nil cv.state = versa.ErrStateHandler cv.root = common.Hash{} } return nil } func (cv *cachingVersaDB) Reset() { if cv.state != versa.ErrStateHandler { _ = cv.versionDB.CloseState(cv.state) panic("close state in reset") } cv.hasState.Store(false) cv.accTree = nil cv.state = versa.ErrStateHandler cv.root = common.Hash{} } func (cv *cachingVersaDB) HasTreeExpired(tr Trie) bool { vtr, ok := tr.(*VersaTree) if !ok { panic("trie type mismatch") } return cv.versionDB.HasTreeExpired(vtr.handler) } func (cv *cachingVersaDB) Scheme() string { return cv.triedb.Scheme() } func (cv *cachingVersaDB) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) { code, _ := cv.codeCache.Get(codeHash) if len(code) > 0 { return code, nil } code = rawdb.ReadCode(cv.codeDB, codeHash) if len(code) > 0 { cv.codeCache.Add(codeHash, code) cv.codeSizeCache.Add(codeHash, len(code)) return code, nil } return nil, errors.New("not found") } func (cv *cachingVersaDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { if cached, ok := cv.codeSizeCache.Get(codeHash); ok { return cached, nil } code, err := cv.ContractCode(addr, codeHash) return len(code), err } func (cv *cachingVersaDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) { code, _ := cv.codeCache.Get(codeHash) if len(code) > 0 { return code, nil } code = rawdb.ReadCodeWithPrefix(cv.codeDB, codeHash) if len(code) > 0 { cv.codeCache.Add(codeHash, code) cv.codeSizeCache.Add(codeHash, len(code)) return code, nil } return nil, errors.New("not found") } func (cv *cachingVersaDB) DiskDB() ethdb.KeyValueStore { return cv.codeDB } func (cv *cachingVersaDB) TrieDB() *triedb.Database { return cv.triedb } func (cv *cachingVersaDB) NoTries() bool { // TODO:: not support fastnode return false } type VersaTree struct { db versa.Database handler versa.TreeHandler version int64 accountTree bool // TODO:: debugging, used for logging stateRoot common.Hash root common.Hash address common.Address mode versa.StateMode } func (vt *VersaTree) GetKey(key []byte) []byte { _, val, err := vt.db.Get(vt.handler, key) if err != nil { log.Warn("failed to get key from version db") } return val } func (vt *VersaTree) GetAccount(address common.Address) (*types.StateAccount, error) { _, res, err := vt.getAccountWithVersion(address) if err != nil { log.Error("failed to get account", "error", err) } return res, err } func (vt *VersaTree) getAccountWithVersion(address common.Address) (int64, *types.StateAccount, error) { vt.CheckAccountTree() ver, res, err := vt.db.Get(vt.handler, address.Bytes()) if res == nil || err != nil { return ver, nil, err } ret := new(types.StateAccount) err = rlp.DecodeBytes(res, ret) log.Info("get account", "mode", vt.mode, "addr", address.String(), "nonce", ret.Nonce, "balance", ret.Balance, "root", ret.Root.String(), "code", common.Bytes2Hex(ret.CodeHash), "version", ver) return ver, ret, err } func (vt *VersaTree) GetStorage(address common.Address, key []byte) ([]byte, error) { if vt.address.Cmp(address) != 0 { panic(fmt.Sprintf("address mismatch in get storage, expect: %s, actul: %s", vt.address.String(), address.String())) } vt.CheckStorageTree() _, enc, err := vt.db.Get(vt.handler, key) if err != nil || len(enc) == 0 { return nil, err } _, content, _, err := rlp.Split(enc) log.Info("get storage", "mode", vt.mode, "handler", vt.handler, "owner", address.String(), "key", common.Bytes2Hex(key), "val", common.Bytes2Hex(content), "stateRoot", vt.stateRoot.String(), "root", vt.root.String(), "version", vt.version) if err != nil { log.Error("failed to get storage", "error", err) } return content, err } func (vt *VersaTree) UpdateAccount(address common.Address, account *types.StateAccount) error { vt.CheckAccountTree() data, err := rlp.EncodeToBytes(account) if err != nil { log.Error("failed to update account", "error", err) return err } log.Info("update account", "mode", vt.mode, "addr", address.String(), "nonce", account.Nonce, "balance", account.Balance, "root", account.Root.String(), "code", common.Bytes2Hex(account.CodeHash)) return vt.db.Put(vt.handler, address.Bytes(), data) } func (vt *VersaTree) UpdateStorage(address common.Address, key, value []byte) error { if vt.address.Cmp(address) != 0 { panic(fmt.Sprintf("address mismatch in get storage, expect: %s, actul: %s", vt.address.String(), address.String())) } vt.CheckStorageTree() v, _ := rlp.EncodeToBytes(value) log.Info("update storage", "mode", vt.mode, "handler", vt.handler, "owner", address.String(), "key", common.Bytes2Hex(key), "val", common.Bytes2Hex(value), "stateRoot", vt.stateRoot.String(), "root", vt.root.String()) err := vt.db.Put(vt.handler, key, v) if err != nil { log.Error("failed to update storage", "error", err) } return err } func (vt *VersaTree) DeleteAccount(address common.Address) error { vt.CheckAccountTree() err := vt.db.Delete(vt.handler, address.Bytes()) if err != nil { log.Error("failed to delete account", "error", err) } return err } func (vt *VersaTree) DeleteStorage(_ common.Address, key []byte) error { vt.CheckStorageTree() err := vt.db.Delete(vt.handler, key) if err != nil { log.Error("failed to delete storage", "error", err) } return err } func (vt *VersaTree) UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error { return nil } func (vt *VersaTree) Hash() common.Hash { hash, err := vt.db.CalcRootHash(vt.handler) if err != nil { // TODO:: debug code, will be change to log error log.Crit("calc tree root hash", "tree handler info", vt.db.ParseTreeHandler(vt.handler), "error", err.Error()) } return hash } func (vt *VersaTree) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) { hash, err := vt.db.Commit(vt.handler) if err != nil { log.Warn("failed to commit versa tree", "error", err) } return hash, nil, err } func (vt *VersaTree) NodeIterator(startKey []byte) (trie.NodeIterator, error) { panic("versa tree not support iterate") return nil, nil } func (vt *VersaTree) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { panic("versa tree not support prove") return nil } // TODO:: debug code, will be deleted after stabilization func (vt *VersaTree) CheckAccountTree() { if !vt.accountTree { panic("sub tree can't operate account") } } // TODO:: debug code, will be deleted after stabilization func (vt *VersaTree) CheckStorageTree() { if vt.accountTree { panic("root tree can't operate storage") } }