core/state: value diff tracking in StateDB (#27349)

This change makes the StateDB track the state key value diff of a block transition.
We already tracked current account and storage values for the purpose of updating
the state snapshot. With this PR, we now also track the original (pre-transition) values
of accounts and storage slots.
This commit is contained in:
rjl493456442
2023-07-11 15:43:23 +02:00
committed by GitHub
parent aecf3f9579
commit 4b06e4f25e
24 changed files with 907 additions and 227 deletions
+8 -1
View File
@@ -24,12 +24,16 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie/triedb/hashdb"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate"
)
// Config defines all necessary options for database.
type Config struct {
Cache int // Memory allowance (MB) to use for caching trie nodes in memory
Preimages bool // Flag whether the preimage of trie key is recorded
// Testing hooks
OnCommit func(states *triestate.Set) // Hook invoked when commit is performed
}
// backend defines the methods needed to access/update trie nodes in different
@@ -114,7 +118,10 @@ func (db *Database) Reader(blockRoot common.Hash) (Reader, error) {
// given set in order to update state from the specified parent to the specified
// root. The held pre-images accumulated up to this point will be flushed in case
// the size exceeds the threshold.
func (db *Database) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error {
func (db *Database) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
if db.config != nil && db.config.OnCommit != nil {
db.config.OnCommit(states)
}
if db.preimages != nil {
db.preimages.commit(false)
}
+9 -9
View File
@@ -63,7 +63,7 @@ func TestIterator(t *testing.T) {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
trie, _ = New(TrieID(root), db)
found := make(map[string]string)
@@ -255,7 +255,7 @@ func TestDifferenceIterator(t *testing.T) {
triea.MustUpdate([]byte(val.k), []byte(val.v))
}
rootA, nodesA, _ := triea.Commit(false)
dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA))
dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA), nil)
triea, _ = New(TrieID(rootA), dba)
dbb := NewDatabase(rawdb.NewMemoryDatabase())
@@ -264,7 +264,7 @@ func TestDifferenceIterator(t *testing.T) {
trieb.MustUpdate([]byte(val.k), []byte(val.v))
}
rootB, nodesB, _ := trieb.Commit(false)
dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB))
dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB), nil)
trieb, _ = New(TrieID(rootB), dbb)
found := make(map[string]string)
@@ -297,7 +297,7 @@ func TestUnionIterator(t *testing.T) {
triea.MustUpdate([]byte(val.k), []byte(val.v))
}
rootA, nodesA, _ := triea.Commit(false)
dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA))
dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA), nil)
triea, _ = New(TrieID(rootA), dba)
dbb := NewDatabase(rawdb.NewMemoryDatabase())
@@ -306,7 +306,7 @@ func TestUnionIterator(t *testing.T) {
trieb.MustUpdate([]byte(val.k), []byte(val.v))
}
rootB, nodesB, _ := trieb.Commit(false)
dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB))
dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB), nil)
trieb, _ = New(TrieID(rootB), dbb)
di, _ := NewUnionIterator([]NodeIterator{triea.MustNodeIterator(nil), trieb.MustNodeIterator(nil)})
@@ -368,7 +368,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) {
tr.MustUpdate([]byte(val.k), []byte(val.v))
}
root, nodes, _ := tr.Commit(false)
tdb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
tdb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
if !memonly {
tdb.Commit(root, false)
}
@@ -484,7 +484,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme strin
break
}
}
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
if !memonly {
triedb.Commit(root, false)
}
@@ -605,7 +605,7 @@ func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) {
trie.MustUpdate(key, val)
}
root, nodes, _ := trie.Commit(false)
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
triedb.Commit(root, false)
// Return the generated trie
@@ -648,7 +648,7 @@ func testIteratorNodeBlob(t *testing.T, scheme string) {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
root, nodes, _ := trie.Commit(false)
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
triedb.Commit(root, false)
var found = make(map[common.Hash][]byte)
+1 -1
View File
@@ -61,7 +61,7 @@ func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) {
}
}
root, nodes, _ := trie.Commit(false)
if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil {
if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil); err != nil {
panic(fmt.Errorf("failed to commit db %v", err))
}
// Re-create the trie based on the new state
+3 -3
View File
@@ -57,7 +57,7 @@ func makeTestTrie(scheme string) (ethdb.Database, *Database, *StateTrie, map[str
}
}
root, nodes, _ := trie.Commit(false)
if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil {
if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil); err != nil {
panic(fmt.Errorf("failed to commit db %v", err))
}
if err := triedb.Commit(root, false); err != nil {
@@ -740,7 +740,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) {
diff[string(key)] = val
}
root, nodes, _ := srcTrie.Commit(false)
if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil {
if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes), nil); err != nil {
panic(err)
}
if err := srcDb.Commit(root, false); err != nil {
@@ -765,7 +765,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) {
reverted[k] = val
}
root, nodes, _ = srcTrie.Commit(false)
if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil {
if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes), nil); err != nil {
panic(err)
}
if err := srcDb.Commit(root, false); err != nil {
+9 -9
View File
@@ -71,7 +71,7 @@ func testTrieTracer(t *testing.T, vals []struct{ k, v string }) {
insertSet := copySet(trie.tracer.inserts) // copy before commit
deleteSet := copySet(trie.tracer.deletes) // copy before commit
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
seen := setKeys(iterNodes(db, root))
if !compareSet(insertSet, seen) {
@@ -137,7 +137,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
trie, _ = New(TrieID(root), db)
if err := verifyAccessList(orig, trie, nodes); err != nil {
@@ -152,7 +152,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
trie.MustUpdate([]byte(val.k), randBytes(32))
}
root, nodes, _ = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
db.Update(root, parent, trienode.NewWithNodeSet(nodes), nil)
trie, _ = New(TrieID(root), db)
if err := verifyAccessList(orig, trie, nodes); err != nil {
@@ -170,7 +170,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
trie.MustUpdate(key, randBytes(32))
}
root, nodes, _ = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
db.Update(root, parent, trienode.NewWithNodeSet(nodes), nil)
trie, _ = New(TrieID(root), db)
if err := verifyAccessList(orig, trie, nodes); err != nil {
@@ -185,7 +185,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
trie.MustUpdate([]byte(key), nil)
}
root, nodes, _ = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
db.Update(root, parent, trienode.NewWithNodeSet(nodes), nil)
trie, _ = New(TrieID(root), db)
if err := verifyAccessList(orig, trie, nodes); err != nil {
@@ -200,7 +200,7 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) {
trie.MustUpdate([]byte(val.k), nil)
}
root, nodes, _ = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(nodes))
db.Update(root, parent, trienode.NewWithNodeSet(nodes), nil)
trie, _ = New(TrieID(root), db)
if err := verifyAccessList(orig, trie, nodes); err != nil {
@@ -219,7 +219,7 @@ func TestAccessListLeak(t *testing.T) {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
var cases = []struct {
op func(tr *Trie)
@@ -269,7 +269,7 @@ func TestTinyTree(t *testing.T) {
trie.MustUpdate([]byte(val.k), randBytes(32))
}
root, set, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(set))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(set), nil)
parent := root
trie, _ = New(TrieID(root), db)
@@ -278,7 +278,7 @@ func TestTinyTree(t *testing.T) {
trie.MustUpdate([]byte(val.k), []byte(val.v))
}
root, set, _ = trie.Commit(false)
db.Update(root, parent, trienode.NewWithNodeSet(set))
db.Update(root, parent, trienode.NewWithNodeSet(set), nil)
trie, _ = New(TrieID(root), db)
if err := verifyAccessList(orig, trie, set); err != nil {
+10 -10
View File
@@ -89,7 +89,7 @@ func testMissingNode(t *testing.T, memonly bool, scheme string) {
updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer")
updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
root, nodes, _ := trie.Commit(false)
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
if !memonly {
triedb.Commit(root, false)
@@ -203,7 +203,7 @@ func TestGet(t *testing.T) {
return
}
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
trie, _ = New(TrieID(root), db)
}
}
@@ -275,7 +275,7 @@ func TestReplication(t *testing.T) {
updateString(trie, val.k, val.v)
}
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
// create a new trie on top of the database and check that lookups work.
trie2, err := New(TrieID(root), db)
@@ -294,7 +294,7 @@ func TestReplication(t *testing.T) {
// recreate the trie after commit
if nodes != nil {
db.Update(hash, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(hash, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
}
trie2, err = New(TrieID(hash), db)
if err != nil {
@@ -503,7 +503,7 @@ func runRandTest(rt randTest) bool {
case opCommit:
root, nodes, _ := tr.Commit(true)
if nodes != nil {
triedb.Update(root, origin, trienode.NewWithNodeSet(nodes))
triedb.Update(root, origin, trienode.NewWithNodeSet(nodes), nil)
}
newtr, err := New(TrieID(root), triedb)
if err != nil {
@@ -838,7 +838,7 @@ func TestCommitSequence(t *testing.T) {
}
// Flush trie -> database
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
// Flush memdb -> disk (sponge)
db.Commit(root, false)
if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
@@ -879,7 +879,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
}
// Flush trie -> database
root, nodes, _ := trie.Commit(false)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
// Flush memdb -> disk (sponge)
db.Commit(root, false)
if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
@@ -919,7 +919,7 @@ func TestCommitSequenceStackTrie(t *testing.T) {
// Flush trie -> database
root, nodes, _ := trie.Commit(false)
// Flush memdb -> disk (sponge)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
db.Commit(root, false)
// And flush stacktrie -> disk
stRoot, err := stTrie.Commit()
@@ -967,7 +967,7 @@ func TestCommitSequenceSmallRoot(t *testing.T) {
// Flush trie -> database
root, nodes, _ := trie.Commit(false)
// Flush memdb -> disk (sponge)
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
db.Commit(root, false)
// And flush stacktrie -> disk
stRoot, err := stTrie.Commit()
@@ -1139,7 +1139,7 @@ func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts []
}
h := trie.Hash()
root, nodes, _ := trie.Commit(false)
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes), nil)
b.StartTimer()
triedb.Dereference(h)
b.StopTimer()
+22 -2
View File
@@ -123,6 +123,26 @@ func (set *NodeSet) AddNode(path []byte, n *WithPrev) {
set.Nodes[string(path)] = n
}
// Merge adds a set of nodes into the set.
func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*WithPrev) error {
if set.Owner != owner {
return fmt.Errorf("nodesets belong to different owner are not mergeable %x-%x", set.Owner, owner)
}
for path, node := range nodes {
prev, ok := set.Nodes[path]
if ok {
// overwrite happens, revoke the counter
if prev.IsDeleted() {
set.deletes -= 1
} else {
set.updates -= 1
}
}
set.AddNode([]byte(path), node)
}
return nil
}
// AddLeaf adds the provided leaf node into set. TODO(rjl493456442) how can
// we get rid of it?
func (set *NodeSet) AddLeaf(parent common.Hash, blob []byte) {
@@ -190,9 +210,9 @@ func NewWithNodeSet(set *NodeSet) *MergedNodeSet {
// Merge merges the provided dirty nodes of a trie into the set. The assumption
// is held that no duplicated set belonging to the same trie will be merged twice.
func (set *MergedNodeSet) Merge(other *NodeSet) error {
_, present := set.Sets[other.Owner]
subset, present := set.Sets[other.Owner]
if present {
return fmt.Errorf("duplicate trie for owner %#x", other.Owner)
return subset.Merge(other.Owner, other.Nodes)
}
set.Sets[other.Owner] = other
return nil
+28
View File
@@ -0,0 +1,28 @@
// 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 triestate
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
// is made. Nil means that the state was not present previously.
type Set struct {
Accounts map[common.Hash][]byte // Mutated account set, nil means the account was not present
Storages map[common.Hash]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present
Incomplete map[common.Hash]struct{} // Indicator whether the storage slot is incomplete due to large deletion
}