trie: relocate state execution logic into pathdb package (#29861)
This commit is contained in:
parent
269e80b07e
commit
045b9718d5
|
@ -24,6 +24,16 @@ import (
|
|||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
)
|
||||
|
||||
// preimageStore wraps the methods of a backing store for reading and writing
|
||||
// trie node preimages.
|
||||
type preimageStore interface {
|
||||
// Preimage retrieves the preimage of the specified hash.
|
||||
Preimage(hash common.Hash) []byte
|
||||
|
||||
// InsertPreimage commits a set of preimages along with their hashes.
|
||||
InsertPreimage(preimages map[common.Hash][]byte)
|
||||
}
|
||||
|
||||
// SecureTrie is the old name of StateTrie.
|
||||
// Deprecated: use StateTrie.
|
||||
type SecureTrie = StateTrie
|
||||
|
@ -52,6 +62,7 @@ func NewSecure(stateRoot common.Hash, owner common.Hash, root common.Hash, db da
|
|||
type StateTrie struct {
|
||||
trie Trie
|
||||
db database.Database
|
||||
preimages preimageStore
|
||||
hashKeyBuf [common.HashLength]byte
|
||||
secKeyCache map[string][]byte
|
||||
secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch
|
||||
|
@ -70,7 +81,14 @@ func NewStateTrie(id *ID, db database.Database) (*StateTrie, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StateTrie{trie: *trie, db: db}, nil
|
||||
tr := &StateTrie{trie: *trie, db: db}
|
||||
|
||||
// link the preimage store if it's supported
|
||||
preimages, ok := db.(preimageStore)
|
||||
if ok {
|
||||
tr.preimages = preimages
|
||||
}
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// MustGet returns the value for key stored in the trie.
|
||||
|
@ -211,7 +229,10 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
|
|||
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
|
||||
return key
|
||||
}
|
||||
return t.db.Preimage(common.BytesToHash(shaKey))
|
||||
if t.preimages == nil {
|
||||
return nil
|
||||
}
|
||||
return t.preimages.Preimage(common.BytesToHash(shaKey))
|
||||
}
|
||||
|
||||
// Witness returns a set containing all trie nodes that have been accessed.
|
||||
|
@ -233,7 +254,9 @@ func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
|||
for hk, key := range t.secKeyCache {
|
||||
preimages[common.BytesToHash([]byte(hk))] = key
|
||||
}
|
||||
t.db.InsertPreimage(preimages)
|
||||
if t.preimages != nil {
|
||||
t.preimages.InsertPreimage(preimages)
|
||||
}
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
// Commit the trie and return its modified nodeset.
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
)
|
||||
|
||||
|
@ -72,23 +71,3 @@ func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) {
|
|||
}
|
||||
return blob, nil
|
||||
}
|
||||
|
||||
// MerkleLoader implements triestate.TrieLoader for constructing tries.
|
||||
type MerkleLoader struct {
|
||||
db database.Database
|
||||
}
|
||||
|
||||
// NewMerkleLoader creates the merkle trie loader.
|
||||
func NewMerkleLoader(db database.Database) *MerkleLoader {
|
||||
return &MerkleLoader{db: db}
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie.
|
||||
func (l *MerkleLoader) OpenTrie(root common.Hash) (triestate.Trie, error) {
|
||||
return New(TrieID(root), l.db)
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (l *MerkleLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) {
|
||||
return New(StorageTrieID(stateRoot, addrHash, root), l.db)
|
||||
}
|
||||
|
|
|
@ -16,43 +16,7 @@
|
|||
|
||||
package triestate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
)
|
||||
|
||||
// Trie is an Ethereum state trie, can be implemented by Ethereum Merkle Patricia
|
||||
// tree or Verkle tree.
|
||||
type Trie interface {
|
||||
// Get returns the value for key stored in the trie.
|
||||
Get(key []byte) ([]byte, error)
|
||||
|
||||
// Update associates key with value in the trie.
|
||||
Update(key, value []byte) error
|
||||
|
||||
// Delete removes any existing value for key from the trie.
|
||||
Delete(key []byte) error
|
||||
|
||||
// Commit the trie and returns a set of dirty nodes generated along with
|
||||
// the new root hash.
|
||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
|
||||
}
|
||||
|
||||
// TrieLoader wraps functions to load tries.
|
||||
type TrieLoader interface {
|
||||
// OpenTrie opens the main account trie.
|
||||
OpenTrie(root common.Hash) (Trie, error)
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error)
|
||||
}
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
// Set represents a collection of mutated states during a state transition.
|
||||
// The value refers to the original content of state before the transition
|
||||
|
@ -87,177 +51,3 @@ func (s *Set) Size() common.StorageSize {
|
|||
}
|
||||
return s.size
|
||||
}
|
||||
|
||||
// context wraps all fields for executing state diffs.
|
||||
type context struct {
|
||||
prevRoot common.Hash
|
||||
postRoot common.Hash
|
||||
accounts map[common.Address][]byte
|
||||
storages map[common.Address]map[common.Hash][]byte
|
||||
accountTrie Trie
|
||||
nodes *trienode.MergedNodeSet
|
||||
}
|
||||
|
||||
// Apply traverses the provided state diffs, apply them in the associated
|
||||
// post-state and return the generated dirty trie nodes. The state can be
|
||||
// loaded via the provided trie loader.
|
||||
func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, loader TrieLoader) (map[common.Hash]map[string]*trienode.Node, error) {
|
||||
tr, err := loader.OpenTrie(postRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx := &context{
|
||||
prevRoot: prevRoot,
|
||||
postRoot: postRoot,
|
||||
accounts: accounts,
|
||||
storages: storages,
|
||||
accountTrie: tr,
|
||||
nodes: trienode.NewMergedNodeSet(),
|
||||
}
|
||||
for addr, account := range accounts {
|
||||
var err error
|
||||
if len(account) == 0 {
|
||||
err = deleteAccount(ctx, loader, addr)
|
||||
} else {
|
||||
err = updateAccount(ctx, loader, addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to revert state, err: %w", err)
|
||||
}
|
||||
}
|
||||
root, result := tr.Commit(false)
|
||||
if root != prevRoot {
|
||||
return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root)
|
||||
}
|
||||
if err := ctx.nodes.Merge(result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctx.nodes.Flatten(), nil
|
||||
}
|
||||
|
||||
// updateAccount the account was present in prev-state, and may or may not
|
||||
// existent in post-state. Apply the reverse diff and verify if the storage
|
||||
// root matches the one in prev-state account.
|
||||
func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error {
|
||||
// The account was present in prev-state, decode it from the
|
||||
// 'slim-rlp' format bytes.
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
|
||||
addrHash := h.hash(addr.Bytes())
|
||||
prev, err := types.FullAccount(ctx.accounts[addr])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The account may or may not existent in post-state, try to
|
||||
// load it and decode if it's found.
|
||||
blob, err := ctx.accountTrie.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
post := types.NewEmptyStateAccount()
|
||||
if len(blob) != 0 {
|
||||
if err := rlp.DecodeBytes(blob, &post); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Apply all storage changes into the post-state storage trie.
|
||||
st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range ctx.storages[addr] {
|
||||
var err error
|
||||
if len(val) == 0 {
|
||||
err = st.Delete(key.Bytes())
|
||||
} else {
|
||||
err = st.Update(key.Bytes(), val)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root, result := st.Commit(false)
|
||||
if root != prev.Root {
|
||||
return errors.New("failed to reset storage trie")
|
||||
}
|
||||
// The returned set can be nil if storage trie is not changed
|
||||
// at all.
|
||||
if result != nil {
|
||||
if err := ctx.nodes.Merge(result); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Write the prev-state account into the main trie
|
||||
full, err := rlp.EncodeToBytes(prev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.accountTrie.Update(addrHash.Bytes(), full)
|
||||
}
|
||||
|
||||
// deleteAccount the account was not present in prev-state, and is expected
|
||||
// to be existent in post-state. Apply the reverse diff and verify if the
|
||||
// account and storage is wiped out correctly.
|
||||
func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error {
|
||||
// The account must be existent in post-state, load the account.
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
|
||||
addrHash := h.hash(addr.Bytes())
|
||||
blob, err := ctx.accountTrie.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(blob) == 0 {
|
||||
return fmt.Errorf("account is non-existent %#x", addrHash)
|
||||
}
|
||||
var post types.StateAccount
|
||||
if err := rlp.DecodeBytes(blob, &post); err != nil {
|
||||
return err
|
||||
}
|
||||
st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range ctx.storages[addr] {
|
||||
if len(val) != 0 {
|
||||
return errors.New("expect storage deletion")
|
||||
}
|
||||
if err := st.Delete(key.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root, result := st.Commit(false)
|
||||
if root != types.EmptyRootHash {
|
||||
return errors.New("failed to clear storage trie")
|
||||
}
|
||||
// The returned set can be nil if storage trie is not changed
|
||||
// at all.
|
||||
if result != nil {
|
||||
if err := ctx.nodes.Merge(result); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Delete the post-state account from the main trie.
|
||||
return ctx.accountTrie.Delete(addrHash.Bytes())
|
||||
}
|
||||
|
||||
// hasher is used to compute the sha256 hash of the provided data.
|
||||
type hasher struct{ sha crypto.KeccakState }
|
||||
|
||||
var hasherPool = sync.Pool{
|
||||
New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
|
||||
}
|
||||
|
||||
func newHasher() *hasher {
|
||||
return hasherPool.Get().(*hasher)
|
||||
}
|
||||
|
||||
func (h *hasher) hash(data []byte) common.Hash {
|
||||
return crypto.HashData(h.sha, data)
|
||||
}
|
||||
|
||||
func (h *hasher) release() {
|
||||
hasherPool.Put(h)
|
||||
}
|
||||
|
|
|
@ -264,14 +264,7 @@ func (db *Database) Recover(target common.Hash) error {
|
|||
if !ok {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
var loader triestate.TrieLoader
|
||||
if db.config.IsVerkle {
|
||||
// TODO define verkle loader
|
||||
log.Crit("Verkle loader is not defined")
|
||||
} else {
|
||||
loader = trie.NewMerkleLoader(db)
|
||||
}
|
||||
return pdb.Recover(target, loader)
|
||||
return pdb.Recover(target)
|
||||
}
|
||||
|
||||
// Recoverable returns the indicator if the specified state is enabled to be
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
// Reader wraps the Node method of a backing trie reader.
|
||||
type Reader interface {
|
||||
|
@ -31,20 +29,8 @@ type Reader interface {
|
|||
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
||||
}
|
||||
|
||||
// PreimageStore wraps the methods of a backing store for reading and writing
|
||||
// trie node preimages.
|
||||
type PreimageStore interface {
|
||||
// Preimage retrieves the preimage of the specified hash.
|
||||
Preimage(hash common.Hash) []byte
|
||||
|
||||
// InsertPreimage commits a set of preimages along with their hashes.
|
||||
InsertPreimage(preimages map[common.Hash][]byte)
|
||||
}
|
||||
|
||||
// Database wraps the methods of a backing trie store.
|
||||
type Database interface {
|
||||
PreimageStore
|
||||
|
||||
// Reader returns a node reader associated with the specific state.
|
||||
// An error will be returned if the specified state is not available.
|
||||
Reader(stateRoot common.Hash) (Reader, error)
|
||||
|
|
|
@ -345,7 +345,7 @@ func (db *Database) Enable(root common.Hash) error {
|
|||
// Recover rollbacks the database to a specified historical point.
|
||||
// The state is supported as the rollback destination only if it's
|
||||
// canonical state and the corresponding trie histories are existent.
|
||||
func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error {
|
||||
func (db *Database) Recover(root common.Hash) error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
|
@ -371,7 +371,7 @@ func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dl, err = dl.revert(h, loader)
|
||||
dl, err = dl.revert(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -29,24 +29,31 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) {
|
||||
h, err := newTestHasher(addrHash, root, cleans)
|
||||
func updateTrie(db *Database, stateRoot common.Hash, addrHash common.Hash, root common.Hash, dirties map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) {
|
||||
var id *trie.ID
|
||||
if addrHash == (common.Hash{}) {
|
||||
id = trie.StateTrieID(stateRoot)
|
||||
} else {
|
||||
id = trie.StorageTrieID(stateRoot, addrHash, root)
|
||||
}
|
||||
tr, err := trie.New(id, db)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create hasher, err: %w", err))
|
||||
panic(fmt.Errorf("failed to load trie, err: %w", err))
|
||||
}
|
||||
for key, val := range dirties {
|
||||
if len(val) == 0 {
|
||||
h.Delete(key.Bytes())
|
||||
tr.Delete(key.Bytes())
|
||||
} else {
|
||||
h.Update(key.Bytes(), val)
|
||||
tr.Update(key.Bytes(), val)
|
||||
}
|
||||
}
|
||||
return h.Commit(false)
|
||||
return tr.Commit(false)
|
||||
}
|
||||
|
||||
func generateAccount(storageRoot common.Hash) types.StateAccount {
|
||||
|
@ -66,6 +73,7 @@ const (
|
|||
)
|
||||
|
||||
type genctx struct {
|
||||
stateRoot common.Hash
|
||||
accounts map[common.Hash][]byte
|
||||
storages map[common.Hash]map[common.Hash][]byte
|
||||
accountOrigin map[common.Address][]byte
|
||||
|
@ -73,8 +81,9 @@ type genctx struct {
|
|||
nodes *trienode.MergedNodeSet
|
||||
}
|
||||
|
||||
func newCtx() *genctx {
|
||||
func newCtx(stateRoot common.Hash) *genctx {
|
||||
return &genctx{
|
||||
stateRoot: stateRoot,
|
||||
accounts: make(map[common.Hash][]byte),
|
||||
storages: make(map[common.Hash]map[common.Hash][]byte),
|
||||
accountOrigin: make(map[common.Address][]byte),
|
||||
|
@ -151,7 +160,7 @@ func (t *tester) generateStorage(ctx *genctx, addr common.Address) common.Hash {
|
|||
storage[hash] = v
|
||||
origin[hash] = nil
|
||||
}
|
||||
root, set := updateTrie(addrHash, types.EmptyRootHash, storage, nil)
|
||||
root, set := updateTrie(t.db, ctx.stateRoot, addrHash, types.EmptyRootHash, storage)
|
||||
|
||||
ctx.storages[addrHash] = storage
|
||||
ctx.storageOrigin[addr] = origin
|
||||
|
@ -180,7 +189,7 @@ func (t *tester) mutateStorage(ctx *genctx, addr common.Address, root common.Has
|
|||
storage[hash] = v
|
||||
origin[hash] = nil
|
||||
}
|
||||
root, set := updateTrie(crypto.Keccak256Hash(addr.Bytes()), root, storage, t.storages[addrHash])
|
||||
root, set := updateTrie(t.db, ctx.stateRoot, crypto.Keccak256Hash(addr.Bytes()), root, storage)
|
||||
|
||||
ctx.storages[addrHash] = storage
|
||||
ctx.storageOrigin[addr] = origin
|
||||
|
@ -198,7 +207,7 @@ func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash
|
|||
origin[hash] = val
|
||||
storage[hash] = nil
|
||||
}
|
||||
root, set := updateTrie(addrHash, root, storage, t.storages[addrHash])
|
||||
root, set := updateTrie(t.db, ctx.stateRoot, addrHash, root, storage)
|
||||
if root != types.EmptyRootHash {
|
||||
panic("failed to clear storage trie")
|
||||
}
|
||||
|
@ -210,7 +219,7 @@ func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash
|
|||
|
||||
func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNodeSet, *triestate.Set) {
|
||||
var (
|
||||
ctx = newCtx()
|
||||
ctx = newCtx(parent)
|
||||
dirties = make(map[common.Hash]struct{})
|
||||
)
|
||||
for i := 0; i < 20; i++ {
|
||||
|
@ -271,7 +280,7 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode
|
|||
ctx.accountOrigin[addr] = account
|
||||
}
|
||||
}
|
||||
root, set := updateTrie(common.Hash{}, parent, ctx.accounts, t.accounts)
|
||||
root, set := updateTrie(t.db, parent, common.Hash{}, parent, ctx.accounts)
|
||||
ctx.nodes.Merge(set)
|
||||
|
||||
// Save state snapshot before commit
|
||||
|
@ -297,6 +306,9 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode
|
|||
t.storages[addrHash][sHash] = slot
|
||||
}
|
||||
}
|
||||
if len(t.storages[addrHash]) == 0 {
|
||||
delete(t.storages, addrHash)
|
||||
}
|
||||
}
|
||||
return root, ctx.nodes, triestate.New(ctx.accountOrigin, ctx.storageOrigin)
|
||||
}
|
||||
|
@ -310,25 +322,31 @@ func (t *tester) lastHash() common.Hash {
|
|||
}
|
||||
|
||||
func (t *tester) verifyState(root common.Hash) error {
|
||||
reader, err := t.db.Reader(root)
|
||||
tr, err := trie.New(trie.StateTrieID(root), t.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = reader.Node(common.Hash{}, nil, root)
|
||||
if err != nil {
|
||||
return errors.New("root node is not available")
|
||||
}
|
||||
for addrHash, account := range t.snapAccounts[root] {
|
||||
path := crypto.Keccak256(addrHash.Bytes())
|
||||
blob, err := reader.Node(common.Hash{}, path, crypto.Keccak256Hash(account))
|
||||
blob, err := tr.Get(addrHash.Bytes())
|
||||
if err != nil || !bytes.Equal(blob, account) {
|
||||
return fmt.Errorf("account is mismatched: %w", err)
|
||||
}
|
||||
}
|
||||
for addrHash, slots := range t.snapStorages[root] {
|
||||
blob := t.snapAccounts[root][addrHash]
|
||||
if len(blob) == 0 {
|
||||
return fmt.Errorf("account %x is missing", addrHash)
|
||||
}
|
||||
account := new(types.StateAccount)
|
||||
if err := rlp.DecodeBytes(blob, account); err != nil {
|
||||
return err
|
||||
}
|
||||
storageIt, err := trie.New(trie.StorageTrieID(root, addrHash, account.Root), t.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for hash, slot := range slots {
|
||||
path := crypto.Keccak256(hash.Bytes())
|
||||
blob, err := reader.Node(addrHash, path, crypto.Keccak256Hash(slot))
|
||||
blob, err := storageIt.Get(hash.Bytes())
|
||||
if err != nil || !bytes.Equal(blob, slot) {
|
||||
return fmt.Errorf("slot is mismatched: %w", err)
|
||||
}
|
||||
|
@ -395,13 +413,11 @@ func TestDatabaseRollback(t *testing.T) {
|
|||
}
|
||||
// Revert database from top to bottom
|
||||
for i := tester.bottomIndex(); i >= 0; i-- {
|
||||
root := tester.roots[i]
|
||||
parent := types.EmptyRootHash
|
||||
if i > 0 {
|
||||
parent = tester.roots[i-1]
|
||||
}
|
||||
loader := newHashLoader(tester.snapAccounts[root], tester.snapStorages[root])
|
||||
if err := tester.db.Recover(parent, loader); err != nil {
|
||||
if err := tester.db.Recover(parent); err != nil {
|
||||
t.Fatalf("Failed to revert db, err: %v", err)
|
||||
}
|
||||
if i > 0 {
|
||||
|
|
|
@ -219,7 +219,7 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
|
|||
}
|
||||
|
||||
// revert applies the given state history and return a reverted disk layer.
|
||||
func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer, error) {
|
||||
func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
|
||||
if h.meta.root != dl.rootHash() {
|
||||
return nil, errUnexpectedHistory
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer
|
|||
// Apply the reverse state changes upon the current state. This must
|
||||
// be done before holding the lock in order to access state in "this"
|
||||
// layer.
|
||||
nodes, err := triestate.Apply(h.meta.parent, h.meta.root, h.accounts, h.storages, loader)
|
||||
nodes, err := apply(dl.db, h.meta.parent, h.meta.root, h.accounts, h.storages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package pathdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"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/database"
|
||||
)
|
||||
|
||||
// context wraps all fields for executing state diffs.
|
||||
type context struct {
|
||||
prevRoot common.Hash
|
||||
postRoot common.Hash
|
||||
accounts map[common.Address][]byte
|
||||
storages map[common.Address]map[common.Hash][]byte
|
||||
nodes *trienode.MergedNodeSet
|
||||
|
||||
// TODO (rjl493456442) abstract out the state hasher
|
||||
// for supporting verkle tree.
|
||||
accountTrie *trie.Trie
|
||||
}
|
||||
|
||||
// apply processes the given state diffs, updates the corresponding post-state
|
||||
// and returns the trie nodes that have been modified.
|
||||
func apply(db database.Database, prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte) (map[common.Hash]map[string]*trienode.Node, error) {
|
||||
tr, err := trie.New(trie.TrieID(postRoot), db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx := &context{
|
||||
prevRoot: prevRoot,
|
||||
postRoot: postRoot,
|
||||
accounts: accounts,
|
||||
storages: storages,
|
||||
accountTrie: tr,
|
||||
nodes: trienode.NewMergedNodeSet(),
|
||||
}
|
||||
for addr, account := range accounts {
|
||||
var err error
|
||||
if len(account) == 0 {
|
||||
err = deleteAccount(ctx, db, addr)
|
||||
} else {
|
||||
err = updateAccount(ctx, db, addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to revert state, err: %w", err)
|
||||
}
|
||||
}
|
||||
root, result := tr.Commit(false)
|
||||
if root != prevRoot {
|
||||
return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root)
|
||||
}
|
||||
if err := ctx.nodes.Merge(result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctx.nodes.Flatten(), nil
|
||||
}
|
||||
|
||||
// updateAccount the account was present in prev-state, and may or may not
|
||||
// existent in post-state. Apply the reverse diff and verify if the storage
|
||||
// root matches the one in prev-state account.
|
||||
func updateAccount(ctx *context, db database.Database, addr common.Address) error {
|
||||
// The account was present in prev-state, decode it from the
|
||||
// 'slim-rlp' format bytes.
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
|
||||
addrHash := h.hash(addr.Bytes())
|
||||
prev, err := types.FullAccount(ctx.accounts[addr])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The account may or may not existent in post-state, try to
|
||||
// load it and decode if it's found.
|
||||
blob, err := ctx.accountTrie.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
post := types.NewEmptyStateAccount()
|
||||
if len(blob) != 0 {
|
||||
if err := rlp.DecodeBytes(blob, &post); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Apply all storage changes into the post-state storage trie.
|
||||
st, err := trie.New(trie.StorageTrieID(ctx.postRoot, addrHash, post.Root), db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range ctx.storages[addr] {
|
||||
var err error
|
||||
if len(val) == 0 {
|
||||
err = st.Delete(key.Bytes())
|
||||
} else {
|
||||
err = st.Update(key.Bytes(), val)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root, result := st.Commit(false)
|
||||
if root != prev.Root {
|
||||
return errors.New("failed to reset storage trie")
|
||||
}
|
||||
// The returned set can be nil if storage trie is not changed
|
||||
// at all.
|
||||
if result != nil {
|
||||
if err := ctx.nodes.Merge(result); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Write the prev-state account into the main trie
|
||||
full, err := rlp.EncodeToBytes(prev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.accountTrie.Update(addrHash.Bytes(), full)
|
||||
}
|
||||
|
||||
// deleteAccount the account was not present in prev-state, and is expected
|
||||
// to be existent in post-state. Apply the reverse diff and verify if the
|
||||
// account and storage is wiped out correctly.
|
||||
func deleteAccount(ctx *context, db database.Database, addr common.Address) error {
|
||||
// The account must be existent in post-state, load the account.
|
||||
h := newHasher()
|
||||
defer h.release()
|
||||
|
||||
addrHash := h.hash(addr.Bytes())
|
||||
blob, err := ctx.accountTrie.Get(addrHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(blob) == 0 {
|
||||
return fmt.Errorf("account is non-existent %#x", addrHash)
|
||||
}
|
||||
var post types.StateAccount
|
||||
if err := rlp.DecodeBytes(blob, &post); err != nil {
|
||||
return err
|
||||
}
|
||||
st, err := trie.New(trie.StorageTrieID(ctx.postRoot, addrHash, post.Root), db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range ctx.storages[addr] {
|
||||
if len(val) != 0 {
|
||||
return errors.New("expect storage deletion")
|
||||
}
|
||||
if err := st.Delete(key.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
root, result := st.Commit(false)
|
||||
if root != types.EmptyRootHash {
|
||||
return errors.New("failed to clear storage trie")
|
||||
}
|
||||
// The returned set can be nil if storage trie is not changed
|
||||
// at all.
|
||||
if result != nil {
|
||||
if err := ctx.nodes.Merge(result); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Delete the post-state account from the main trie.
|
||||
return ctx.accountTrie.Delete(addrHash.Bytes())
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package pathdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
)
|
||||
|
||||
// testHasher is a test utility for computing root hash of a batch of state
|
||||
// elements. The hash algorithm is to sort all the elements in lexicographical
|
||||
// order, concat the key and value in turn, and perform hash calculation on
|
||||
// the concatenated bytes. Except the root hash, a nodeset will be returned
|
||||
// once Commit is called, which contains all the changes made to hasher.
|
||||
type testHasher struct {
|
||||
owner common.Hash // owner identifier
|
||||
root common.Hash // original root
|
||||
dirties map[common.Hash][]byte // dirty states
|
||||
cleans map[common.Hash][]byte // clean states
|
||||
}
|
||||
|
||||
// newTestHasher constructs a hasher object with provided states.
|
||||
func newTestHasher(owner common.Hash, root common.Hash, cleans map[common.Hash][]byte) (*testHasher, error) {
|
||||
if cleans == nil {
|
||||
cleans = make(map[common.Hash][]byte)
|
||||
}
|
||||
if got, _ := hash(cleans); got != root {
|
||||
return nil, fmt.Errorf("state root mismatched, want: %x, got: %x", root, got)
|
||||
}
|
||||
return &testHasher{
|
||||
owner: owner,
|
||||
root: root,
|
||||
dirties: make(map[common.Hash][]byte),
|
||||
cleans: cleans,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get returns the value for key stored in the trie.
|
||||
func (h *testHasher) Get(key []byte) ([]byte, error) {
|
||||
hash := common.BytesToHash(key)
|
||||
val, ok := h.dirties[hash]
|
||||
if ok {
|
||||
return val, nil
|
||||
}
|
||||
return h.cleans[hash], nil
|
||||
}
|
||||
|
||||
// Update associates key with value in the trie.
|
||||
func (h *testHasher) Update(key, value []byte) error {
|
||||
h.dirties[common.BytesToHash(key)] = common.CopyBytes(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes any existing value for key from the trie.
|
||||
func (h *testHasher) Delete(key []byte) error {
|
||||
h.dirties[common.BytesToHash(key)] = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit computes the new hash of the states and returns the set with all
|
||||
// state changes.
|
||||
func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||
var (
|
||||
nodes = make(map[common.Hash][]byte)
|
||||
set = trienode.NewNodeSet(h.owner)
|
||||
)
|
||||
for hash, val := range h.cleans {
|
||||
nodes[hash] = val
|
||||
}
|
||||
for hash, val := range h.dirties {
|
||||
nodes[hash] = val
|
||||
if bytes.Equal(val, h.cleans[hash]) {
|
||||
continue
|
||||
}
|
||||
// Utilize the hash of the state key as the node path to mitigate
|
||||
// potential collisions within the path.
|
||||
path := crypto.Keccak256(hash.Bytes())
|
||||
if len(val) == 0 {
|
||||
set.AddNode(path, trienode.NewDeleted())
|
||||
} else {
|
||||
set.AddNode(path, trienode.New(crypto.Keccak256Hash(val), val))
|
||||
}
|
||||
}
|
||||
root, blob := hash(nodes)
|
||||
|
||||
// Include the dirty root node as well.
|
||||
if root != types.EmptyRootHash && root != h.root {
|
||||
set.AddNode(nil, trienode.New(root, blob))
|
||||
}
|
||||
if root == types.EmptyRootHash && h.root != types.EmptyRootHash {
|
||||
set.AddNode(nil, trienode.NewDeleted())
|
||||
}
|
||||
return root, set
|
||||
}
|
||||
|
||||
// hash performs the hash computation upon the provided states.
|
||||
func hash(states map[common.Hash][]byte) (common.Hash, []byte) {
|
||||
var hs []common.Hash
|
||||
for hash := range states {
|
||||
hs = append(hs, hash)
|
||||
}
|
||||
slices.SortFunc(hs, common.Hash.Cmp)
|
||||
|
||||
var input []byte
|
||||
for _, hash := range hs {
|
||||
if len(states[hash]) == 0 {
|
||||
continue
|
||||
}
|
||||
input = append(input, hash.Bytes()...)
|
||||
input = append(input, states[hash]...)
|
||||
}
|
||||
if len(input) == 0 {
|
||||
return types.EmptyRootHash, nil
|
||||
}
|
||||
return crypto.Keccak256Hash(input), input
|
||||
}
|
||||
|
||||
type hashLoader struct {
|
||||
accounts map[common.Hash][]byte
|
||||
storages map[common.Hash]map[common.Hash][]byte
|
||||
}
|
||||
|
||||
func newHashLoader(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) *hashLoader {
|
||||
return &hashLoader{
|
||||
accounts: accounts,
|
||||
storages: storages,
|
||||
}
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie.
|
||||
func (l *hashLoader) OpenTrie(root common.Hash) (triestate.Trie, error) {
|
||||
return newTestHasher(common.Hash{}, root, l.accounts)
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (l *hashLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) {
|
||||
return newTestHasher(addrHash, root, l.storages[addrHash])
|
||||
}
|
Loading…
Reference in New Issue