Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
} else if res.Requests != nil {
return errors.New("block has requests before prague fork")
}

// Validate the state root against the received state root and throw
// an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
Expand Down
6 changes: 6 additions & 0 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,18 @@ type Trie interface {
// in the trie with provided address.
UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error

// UpdateAccountBatch attempts to update a list accounts in the batch manner.
UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error

// UpdateStorage associates key with value in the trie. If value has length zero,
// any existing value is deleted from the trie. The value bytes must not be modified
// by the caller while they are stored in the trie. If a node was not found in the
// database, a trie.MissingNodeError is returned.
UpdateStorage(addr common.Address, key, value []byte) error

// UpdateStorageBatch attempts to update a list storages in the batch manner.
UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error

// DeleteAccount abstracts an account deletion from the trie.
DeleteAccount(address common.Address) error

Expand Down
18 changes: 12 additions & 6 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,10 @@ func (s *stateObject) updateTrie() (Trie, error) {
// into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
var (
deletions []common.Hash
used = make([]common.Hash, 0, len(s.uncommittedStorage))
deletions []common.Hash
used = make([]common.Hash, 0, len(s.uncommittedStorage))
updateKeys [][]byte
updateValues [][]byte
)
for key, origin := range s.uncommittedStorage {
// Skip noop changes, persist actual changes
Expand All @@ -337,17 +339,21 @@ func (s *stateObject) updateTrie() (Trie, error) {
continue
}
if (value != common.Hash{}) {
if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil {
s.db.setError(err)
return nil, err
}
updateKeys = append(updateKeys, key[:])
updateValues = append(updateValues, common.TrimLeftZeroes(value[:]))
s.db.StorageUpdated.Add(1)
} else {
deletions = append(deletions, key)
}
// Cache the items for preloading
used = append(used, key) // Copy needed for closure
}
if len(updateKeys) > 0 {
if err := tr.UpdateStorageBatch(common.Address{}, updateKeys, updateValues); err != nil {
s.db.setError(err)
return nil, err
}
}
for _, key := range deletions {
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
s.db.setError(err)
Expand Down
51 changes: 49 additions & 2 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,25 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
}
}
func (s *StateDB) updateStateObjects(objs []*stateObject) {
var addrs []common.Address
var accts []*types.StateAccount

for _, obj := range objs {
addrs = append(addrs, obj.Address())
accts = append(accts, &obj.data)
}

if err := s.trie.UpdateAccountBatch(addrs, accts, nil); err != nil {
s.setError(fmt.Errorf("updateStateObjects error: %v", err))
}

for _, obj := range objs {
if obj.dirtyCode {
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
}
}
}

// deleteStateObject removes the given object from the state trie.
func (s *StateDB) deleteStateObject(addr common.Address) {
Expand Down Expand Up @@ -883,6 +902,31 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
workers.Wait()
s.StorageUpdates += time.Since(start)

/*
fmt.Println("begin print mutations")
fmt.Println(s.txIndex)
for addr, _ := range s.mutations {
if _, ok := s.stateObjects[addr]; !ok {
continue
}
if s.stateObjects[addr].trie == nil {
continue
}
fmt.Printf("mut %x/%x:\n", addr, s.stateObjects[addr].addrHash)
it, err := s.stateObjects[addr].trie.NodeIterator([]byte{})
if err != nil {
panic(err)
}
for it.Next(true) {
if it.Leaf() {
fmt.Printf("%x: %x\n", it.Path(), it.LeafBlob())
} else {
fmt.Printf("%x: %x\n", it.Path(), it.Hash())
}
}
}
*/

// Now we're about to start to write changes to the trie. The trie is so far
// _untouched_. We can check with the prefetcher, if it can give us a trie
// which has the same root, but also has some content loaded into it.
Expand Down Expand Up @@ -911,6 +955,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
var (
usedAddrs []common.Address
deletedAddrs []common.Address
updatedObjs []*stateObject
)
for addr, op := range s.mutations {
if op.applied {
Expand All @@ -921,11 +966,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
if op.isDelete() {
deletedAddrs = append(deletedAddrs, addr)
} else {
s.updateStateObject(s.stateObjects[addr])
updatedObjs = append(updatedObjs, s.stateObjects[addr])
s.AccountUpdated += 1
}
usedAddrs = append(usedAddrs, addr) // Copy needed for closure
}
if len(updatedObjs) > 0 {
s.updateStateObjects(updatedObjs)
}
for _, deletedAddr := range deletedAddrs {
s.deleteStateObject(deletedAddr)
s.AccountDeleted += 1
Expand All @@ -937,7 +985,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
}
// Track the amount of time wasted on hashing the account trie
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())

hash := s.trie.Hash()

// If witness building is enabled, gather the account trie witness
Expand Down
2 changes: 1 addition & 1 deletion tests/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
}
for _, snapshot := range snapshotConf {
for _, dbscheme := range dbschemeConf {
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil {
if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, false, nil, nil)); err != nil {
t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err)
return
}
Expand Down
46 changes: 46 additions & 0 deletions trie/secure_trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,29 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
return nil
}

// UpdateStorageBatch attempts to update a list storages in the batch manner.
func (t *StateTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error {
var (
hkeys = make([][]byte, 0, len(keys))
evals = make([][]byte, 0, len(values))
)
for _, key := range keys {
hk := crypto.Keccak256(key)
if t.preimages != nil {
t.secKeyCache[common.Hash(hk)] = key
}
hkeys = append(hkeys, hk)
}
for _, val := range values {
data, err := rlp.EncodeToBytes(val)
if err != nil {
return err
}
evals = append(evals, data)
}
return t.trie.UpdateBatch(hkeys, evals)
}

// UpdateAccount will abstract the write of an account to the secure trie.
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error {
hk := crypto.Keccak256(address.Bytes())
Expand All @@ -206,6 +229,29 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun
return nil
}

// UpdateAccountBatch attempts to update a list accounts in the batch manner.
func (t *StateTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error {
var (
hkeys = make([][]byte, 0, len(addresses))
values = make([][]byte, 0, len(accounts))
)
for _, addr := range addresses {
hk := crypto.Keccak256(addr.Bytes())
if t.preimages != nil {
t.secKeyCache[common.Hash(hk)] = addr.Bytes()
}
hkeys = append(hkeys, hk)
}
for _, acc := range accounts {
data, err := rlp.EncodeToBytes(acc)
if err != nil {
return err
}
values = append(values, data)
}
return t.trie.UpdateBatch(hkeys, values)
}

func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
return nil
}
Expand Down
42 changes: 36 additions & 6 deletions trie/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package trie
import (
"maps"
"slices"
"sync"
)

// opTracer tracks the changes of trie nodes. During the trie operations,
Expand All @@ -33,12 +34,10 @@ import (
// while the latter is inserted/deleted in order to follow the rule of trie.
// This tool can track all of them no matter the node is embedded in its
// parent or not, but valueNode is never tracked.
//
// Note opTracer is not thread-safe, callers should be responsible for handling
// the concurrency issues by themselves.
type opTracer struct {
inserts map[string]struct{}
deletes map[string]struct{}
lock sync.RWMutex
}

// newOpTracer initializes the tracer for capturing trie changes.
Expand All @@ -53,6 +52,9 @@ func newOpTracer() *opTracer {
// in the deletion set (resurrected node), then just wipe it from
// the deletion set as it's "untouched".
func (t *opTracer) onInsert(path []byte) {
t.lock.Lock()
defer t.lock.Unlock()

if _, present := t.deletes[string(path)]; present {
delete(t.deletes, string(path))
return
Expand All @@ -64,6 +66,9 @@ func (t *opTracer) onInsert(path []byte) {
// in the addition set, then just wipe it from the addition set
// as it's untouched.
func (t *opTracer) onDelete(path []byte) {
t.lock.Lock()
defer t.lock.Unlock()

if _, present := t.inserts[string(path)]; present {
delete(t.inserts, string(path))
return
Expand All @@ -73,12 +78,18 @@ func (t *opTracer) onDelete(path []byte) {

// reset clears the content tracked by tracer.
func (t *opTracer) reset() {
t.lock.Lock()
defer t.lock.Unlock()

clear(t.inserts)
clear(t.deletes)
}

// copy returns a deep copied tracer instance.
func (t *opTracer) copy() *opTracer {
t.lock.RLock()
defer t.lock.RUnlock()

return &opTracer{
inserts: maps.Clone(t.inserts),
deletes: maps.Clone(t.deletes),
Expand All @@ -87,6 +98,9 @@ func (t *opTracer) copy() *opTracer {

// deletedList returns a list of node paths which are deleted from the trie.
func (t *opTracer) deletedList() [][]byte {
t.lock.RLock()
defer t.lock.RUnlock()

paths := make([][]byte, 0, len(t.deletes))
for path := range t.deletes {
paths = append(paths, []byte(path))
Expand All @@ -97,11 +111,9 @@ func (t *opTracer) deletedList() [][]byte {
// prevalueTracer tracks the original values of resolved trie nodes. Cached trie
// node values are expected to be immutable. A zero-size node value is treated as
// non-existent and should not occur in practice.
//
// Note prevalueTracer is not thread-safe, callers should be responsible for
// handling the concurrency issues by themselves.
type prevalueTracer struct {
data map[string][]byte
lock sync.RWMutex
}

// newPrevalueTracer initializes the tracer for capturing resolved trie nodes.
Expand All @@ -115,18 +127,27 @@ func newPrevalueTracer() *prevalueTracer {
// blob internally. Do not modify the value outside this function,
// as it is not deep-copied.
func (t *prevalueTracer) put(path []byte, val []byte) {
t.lock.Lock()
defer t.lock.Unlock()

t.data[string(path)] = val
}

// get returns the cached trie node value. If the node is not found, nil will
// be returned.
func (t *prevalueTracer) get(path []byte) []byte {
t.lock.RLock()
defer t.lock.RUnlock()

return t.data[string(path)]
}

// hasList returns a list of flags indicating whether the corresponding trie nodes
// specified by the path exist in the trie.
func (t *prevalueTracer) hasList(list [][]byte) []bool {
t.lock.RLock()
defer t.lock.RUnlock()

exists := make([]bool, 0, len(list))
for _, path := range list {
_, ok := t.data[string(path)]
Expand All @@ -137,16 +158,25 @@ func (t *prevalueTracer) hasList(list [][]byte) []bool {

// values returns a list of values of the cached trie nodes.
func (t *prevalueTracer) values() [][]byte {
t.lock.RLock()
defer t.lock.RUnlock()

return slices.Collect(maps.Values(t.data))
}

// reset resets the cached content in the prevalueTracer.
func (t *prevalueTracer) reset() {
t.lock.Lock()
defer t.lock.Unlock()

clear(t.data)
}

// copy returns a copied prevalueTracer instance.
func (t *prevalueTracer) copy() *prevalueTracer {
t.lock.RLock()
defer t.lock.RUnlock()

// Shadow clone is used, as the cached trie node values are immutable
return &prevalueTracer{
data: maps.Clone(t.data),
Expand Down
Loading
Loading