bsc/core/state/caching_versa_db.go
2024-08-16 16:51:10 +08:00

418 lines
12 KiB
Go

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)
}
log.Info("delete account", "mode", vt.mode, "addr", address.String())
return err
}
func (vt *VersaTree) DeleteStorage(address 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)
}
log.Info("delete storage", "mode", vt.mode, "handler", vt.handler, "owner", address.String(), "key", common.Bytes2Hex(key), "stateRoot", vt.stateRoot.String(), "root", vt.root.String())
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")
}
}