remove remaining trie access, ForEachStorage is the only method which requires this access pattern and afaict it is only ever used in tests so we can leave it unimplemented

This commit is contained in:
i-norden 2023-02-28 11:33:23 -06:00
parent a99694749f
commit 1741e5f790
2 changed files with 28 additions and 150 deletions

View File

@ -6,16 +6,22 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
var emptyCodeHash = crypto.Keccak256(nil) var (
// emptyRoot is the known root hash of an empty trie.
// this is calculated as: emptyRoot = crypto.Keccak256(rlp.Encode([][]byte{}))
// that is, the keccak356 hash of the rlp encoding of an empty trie node (empty byte slice array)
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
// emptyCodeHash is the CodeHash for an EOA, for an account without contract code deployed
emptyCodeHash = crypto.Keccak256(nil)
)
type Code []byte type Code []byte
@ -61,8 +67,7 @@ type stateObject struct {
dbErr error dbErr error
// Write caches. // Write caches.
trie state.Trie // storage trie, which becomes non-nil on first access code Code // contract bytecode, which gets set when code is loaded
code Code // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
@ -126,18 +131,6 @@ func (s *stateObject) touch() {
} }
} }
func (s *stateObject) getTrie(db Database) state.Trie {
if s.trie == nil {
var err error
s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root)
if err != nil {
s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{})
s.setError(fmt.Errorf("can't create storage trie: %v", err))
}
}
return s.trie
}
// GetState retrieves a value from the account storage trie. // GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(db Database, key common.Hash) common.Hash { func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
// If the fake storage is set, only lookup the state here(in the debugging mode) // If the fake storage is set, only lookup the state here(in the debugging mode)
@ -166,10 +159,16 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
if value, cached := s.originStorage[key]; cached { if value, cached := s.originStorage[key]; cached {
return value return value
} }
// If no live objects are available, attempt to use snapshots // If no live objects are available, load from database
var ( start := time.Now()
enc []byte enc, err := db.StorageSlot(s.addrHash, key)
) if metrics.EnabledExpensive {
s.db.StorageReads += time.Since(start)
}
if err != nil {
s.setError(err)
return common.Hash{}
}
var value common.Hash var value common.Hash
if len(enc) > 0 { if len(enc) > 0 {
_, content, _, err := rlp.Split(enc) _, content, _, err := rlp.Split(enc)
@ -207,75 +206,6 @@ func (s *stateObject) setState(key, value common.Hash) {
s.dirtyStorage[key] = value s.dirtyStorage[key] = value
} }
// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
func (s *stateObject) finalise() {
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage {
s.pendingStorage[key] = value
if value != s.originStorage[key] {
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
}
}
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
}
}
// updateTrie writes cached storage modifications into the object's storage trie.
// It will return nil if the trie has not been loaded and no changes have been made
func (s *stateObject) updateTrie(db Database) state.Trie {
// Make sure all dirty slots are finalized into the pending storage area
s.finalise()
if len(s.pendingStorage) == 0 {
return s.trie
}
// Track the amount of time wasted on updating the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
}
// Insert all the pending updates into the trie
tr := s.getTrie(db)
usedStorage := make([][]byte, 0, len(s.pendingStorage))
for key, value := range s.pendingStorage {
// Skip noop changes, persist actual changes
if value == s.originStorage[key] {
continue
}
s.originStorage[key] = value
var v []byte
if (value == common.Hash{}) {
s.setError(tr.TryDelete(key[:]))
s.db.StorageDeleted += 1
} else {
// Encoding []byte cannot fail, ok to ignore the error.
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
s.setError(tr.TryUpdate(key[:], v))
s.db.StorageUpdated += 1
}
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
}
if len(s.pendingStorage) > 0 {
s.pendingStorage = make(Storage)
}
return tr
}
// UpdateRoot sets the trie root to the current root hash of
func (s *stateObject) updateRoot(db Database) {
// If nothing changed, don't bother with hashing anything
if s.updateTrie(db) == nil {
return
}
// Track the amount of time wasted on hashing the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
}
s.data.Root = s.trie.Hash()
}
// AddBalance adds amount to s's balance. // AddBalance adds amount to s's balance.
// It is used to add funds to the destination account of a transfer. // It is used to add funds to the destination account of a transfer.
func (s *stateObject) AddBalance(amount *big.Int) { func (s *stateObject) AddBalance(amount *big.Int) {
@ -311,21 +241,6 @@ func (s *stateObject) setBalance(amount *big.Int) {
s.data.Balance = amount s.data.Balance = amount
} }
func (s *stateObject) deepCopy(db *StateDB) *stateObject {
stateObject := newObject(db, s.address, s.data)
if s.trie != nil {
stateObject.trie = db.db.CopyTrie(s.trie)
}
stateObject.code = s.code
stateObject.dirtyStorage = s.dirtyStorage.Copy()
stateObject.originStorage = s.originStorage.Copy()
stateObject.pendingStorage = s.pendingStorage.Copy()
stateObject.suicided = s.suicided
stateObject.dirtyCode = s.dirtyCode
stateObject.deleted = s.deleted
return stateObject
}
// //
// Attribute accessors // Attribute accessors
// //

View File

@ -7,14 +7,11 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
) )
/* /*
@ -44,20 +41,14 @@ type revision struct {
journalIndex int journalIndex int
} }
var (
// emptyRoot is the known root hash of an empty trie.
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
)
// StateDB structs within the ethereum protocol are used to store anything // StateDB structs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing // within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve: // nested states. It's the general query interface to retrieve:
// * Contracts // * Contracts
// * Accounts // * Accounts
type StateDB struct { type StateDB struct {
db Database db Database
trie state.Trie hasher crypto.KeccakState
hasher crypto.KeccakState
// originalRoot is the pre-state root, before any changes were made. // originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called. // It will be updated when the Commit is called.
@ -120,13 +111,8 @@ type StateDB struct {
// New creates a new state from a given trie. // New creates a new state from a given trie.
func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) { func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil {
return nil, err
}
sdb := &StateDB{ sdb := &StateDB{
db: db, db: db,
trie: tr,
originalRoot: root, originalRoot: root,
snaps: snaps, snaps: snaps,
stateObjects: make(map[common.Address]*stateObject), stateObjects: make(map[common.Address]*stateObject),
@ -362,7 +348,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
// TODO: REPLACE TRIE ACCESS HERE // TODO: REPLACE TRIE ACCESS HERE
// can add a fallback option to use ipfsethdb to do the trie access if direct access fails // can add a fallback option to use ipfsethdb to do the trie access if direct access fails
start := time.Now() start := time.Now()
data, err := s.trie.TryGetAccount(addr.Bytes()) data, err := s.db.StateAccount(addr)
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
s.AccountReads += time.Since(start) s.AccountReads += time.Since(start)
} }
@ -426,35 +412,12 @@ func (s *StateDB) CreateAccount(addr common.Address) {
} }
} }
// TODO: not sure trie access can be replaced for this method, might need to use ipfs-ethdb // ForEachStorage satisfies vm.StateDB but is not implemented
// but, as far as I can tell, this method is only ever used in tests
func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
so := db.getStateObject(addr) // NOTE: as far as I can tell this method is only ever used in tests
if so == nil { // in that case, we can leave it unimplemented
return nil // or if it needs to be implemented we can use iplfs-ethdb to do normal trie access
} panic("ForEachStorage is not implemented")
it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil))
for it.Next() {
key := common.BytesToHash(db.trie.GetKey(it.Key))
if value, dirty := so.dirtyStorage[key]; dirty {
if !cb(key, value) {
return nil
}
continue
}
if len(it.Value) > 0 {
_, content, _, err := rlp.Split(it.Value)
if err != nil {
return err
}
if !cb(key, common.BytesToHash(content)) {
return nil
}
}
}
return nil
} }
// Snapshot returns an identifier for the current revision of the state. // Snapshot returns an identifier for the current revision of the state.