mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-04 05:58:40 +00:00
trie/bintrie: record inserted leaves for t8n (#34843)
Because the UBT doesn't differentiate slots from accounts, the content of the tree can not be exported as a `GenesisAlloc`, which means that `evm t8n` can not intergrate it. We have tried integrating the new format into execution-specs, but this is very hard to maintain because the team doesn't see it as a priority and their own repository is seeing a lot of churn. This PR adds the ability to capture the structure of what is being inserted in the tree, so that the information isn't lost and it can be dumped in the t8n context. --------- Co-authored-by: felipe <fselmo2@gmail.com>
This commit is contained in:
parent
95320ffe69
commit
61342e9c01
7 changed files with 556 additions and 10 deletions
|
|
@ -418,9 +418,24 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
||||||
return statedb, execRs, body, nil
|
return statedb, execRs, body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newPrestateTrieDBConfig returns the triedb config used to construct the
|
||||||
|
// prestate. UBT mode requires the path-based backend; the legacy hash-based
|
||||||
|
// backend cannot decode UBT-encoded nodes.
|
||||||
|
func newPrestateTrieDBConfig(isBintrie bool) *triedb.Config {
|
||||||
|
if isBintrie {
|
||||||
|
cfg := *triedb.UBTDefaults
|
||||||
|
cfg.Preimages = true
|
||||||
|
return &cfg
|
||||||
|
}
|
||||||
|
return &triedb.Config{Preimages: true}
|
||||||
|
}
|
||||||
|
|
||||||
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool) *state.StateDB {
|
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool) *state.StateDB {
|
||||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie})
|
tdb := triedb.NewDatabase(db, newPrestateTrieDBConfig(isBintrie))
|
||||||
sdb := state.NewDatabase(tdb, nil)
|
sdb := state.NewDatabase(tdb, nil)
|
||||||
|
if isBintrie {
|
||||||
|
sdb.(*state.UBTDatabase).EnableAllocRecording()
|
||||||
|
}
|
||||||
|
|
||||||
root := types.EmptyRootHash
|
root := types.EmptyRootHash
|
||||||
if isBintrie {
|
if isBintrie {
|
||||||
|
|
@ -458,8 +473,11 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool
|
||||||
// MakePreStateStreaming is like MakePreState, but decodes the alloc from disk
|
// MakePreStateStreaming is like MakePreState, but decodes the alloc from disk
|
||||||
// one account at a time so the full map is never held in memory.
|
// one account at a time so the full map is never held in memory.
|
||||||
func MakePreStateStreaming(db ethdb.Database, allocPath string, isBintrie bool) (*state.StateDB, error) {
|
func MakePreStateStreaming(db ethdb.Database, allocPath string, isBintrie bool) (*state.StateDB, error) {
|
||||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsUBT: isBintrie})
|
tdb := triedb.NewDatabase(db, newPrestateTrieDBConfig(isBintrie))
|
||||||
sdb := state.NewDatabase(tdb, nil)
|
sdb := state.NewDatabase(tdb, nil)
|
||||||
|
if isBintrie {
|
||||||
|
sdb.(*state.UBTDatabase).EnableAllocRecording()
|
||||||
|
}
|
||||||
|
|
||||||
root := types.EmptyRootHash
|
root := types.EmptyRootHash
|
||||||
if isBintrie {
|
if isBintrie {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/overlay"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
|
|
@ -243,14 +244,55 @@ func Transition(ctx *cli.Context) error {
|
||||||
collector = make(Alloc)
|
collector = make(Alloc)
|
||||||
s.DumpToCollector(collector, nil)
|
s.DumpToCollector(collector, nil)
|
||||||
default:
|
default:
|
||||||
btleaves = make(map[common.Hash]hexutil.Bytes)
|
udb, ok := s.Database().(*state.UBTDatabase)
|
||||||
if err := s.DumpBinTrieLeaves(btleaves); err != nil {
|
if !ok {
|
||||||
return err
|
return NewError(ErrorEVM, errors.New("expected UBTDatabase in binary trie mode"))
|
||||||
|
}
|
||||||
|
rec := udb.AllocRecorder()
|
||||||
|
if rec == nil {
|
||||||
|
return NewError(ErrorEVM, errors.New("UBT alloc recorder was not enabled"))
|
||||||
|
}
|
||||||
|
collector = Alloc(rec.Alloc())
|
||||||
|
if err := mergeUnmigratedBaseAlloc(udb, s.IntermediateRoot(false), collector); err != nil {
|
||||||
|
return NewError(ErrorEVM, fmt.Errorf("failed to merge base MPT alloc: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dispatchOutput(ctx, baseDir, result, collector, allocOutput, body, btleaves)
|
return dispatchOutput(ctx, baseDir, result, collector, allocOutput, body, btleaves)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeUnmigratedBaseAlloc(udb *state.UBTDatabase, currentRoot common.Hash, dst Alloc) error {
|
||||||
|
ts := overlay.LoadTransitionState(udb.TrieDB().Disk(), currentRoot, true)
|
||||||
|
if !ts.InTransition() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ts.BaseRoot == (common.Hash{}) || ts.BaseRoot == types.EmptyRootHash {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mptDB := state.NewMPTDatabase(udb.TrieDB(), nil)
|
||||||
|
sdb, err := state.New(ts.BaseRoot, mptDB)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open base MPT at %x: %w", ts.BaseRoot, err)
|
||||||
|
}
|
||||||
|
if _, err := sdb.DumpToCollector(mergeAlloc(dst), nil); err != nil {
|
||||||
|
return fmt.Errorf("walk base MPT at %x: %w", ts.BaseRoot, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mergeAlloc Alloc
|
||||||
|
|
||||||
|
func (m mergeAlloc) OnRoot(common.Hash) {}
|
||||||
|
|
||||||
|
func (m mergeAlloc) OnAccount(addr *common.Address, da state.DumpAccount) {
|
||||||
|
if addr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, exists := m[*addr]; exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m[*addr] = dumpAccountToTypesAccount(da)
|
||||||
|
}
|
||||||
|
|
||||||
// writeStreamedAlloc writes the post-state alloc to path one account at a
|
// writeStreamedAlloc writes the post-state alloc to path one account at a
|
||||||
// time, producing the same JSON shape as saveFile on an Alloc map.
|
// time, producing the same JSON shape as saveFile on an Alloc map.
|
||||||
func writeStreamedAlloc(path string, s *state.StateDB) error {
|
func writeStreamedAlloc(path string, s *state.StateDB) error {
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,26 @@ import (
|
||||||
// It provides the same functionality as MPTDatabase but uses unified binary
|
// It provides the same functionality as MPTDatabase but uses unified binary
|
||||||
// trie for state hashing instead of Merkle Patricia Tries.
|
// trie for state hashing instead of Merkle Patricia Tries.
|
||||||
type UBTDatabase struct {
|
type UBTDatabase struct {
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
codedb *CodeDB
|
codedb *CodeDB
|
||||||
|
recorder *bintrie.Recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableAllocRecording installs an alloc recorder shared across every binary
|
||||||
|
// trie opened from this database. The recorder captures account, storage, and
|
||||||
|
// code writes keyed by their original (unhashed) addresses, which is required
|
||||||
|
// for tooling like evm t8n to render the post-state as a types.GenesisAlloc.
|
||||||
|
func (db *UBTDatabase) EnableAllocRecording() *bintrie.Recorder {
|
||||||
|
if db.recorder == nil {
|
||||||
|
db.recorder = bintrie.NewRecorder()
|
||||||
|
}
|
||||||
|
return db.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllocRecorder returns the attached recorder, or nil if recording was never
|
||||||
|
// enabled on this database.
|
||||||
|
func (db *UBTDatabase) AllocRecorder() *bintrie.Recorder { return db.recorder }
|
||||||
|
|
||||||
// Type returns Binary, indicating this database is backed by a Universal Binary Trie.
|
// Type returns Binary, indicating this database is backed by a Universal Binary Trie.
|
||||||
func (db *UBTDatabase) Type() DatabaseType { return TypeUBT }
|
func (db *UBTDatabase) Type() DatabaseType { return TypeUBT }
|
||||||
|
|
||||||
|
|
@ -96,7 +112,14 @@ func (db *UBTDatabase) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Rea
|
||||||
|
|
||||||
// OpenTrie opens the main account trie at a specific root hash.
|
// OpenTrie opens the main account trie at a specific root hash.
|
||||||
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) {
|
func (db *UBTDatabase) OpenTrie(root common.Hash) (Trie, error) {
|
||||||
return bintrie.NewBinaryTrie(root, db.triedb, db.triedb.BinTrieGroupDepth())
|
tr, err := bintrie.NewBinaryTrie(root, db.triedb, db.triedb.BinTrieGroupDepth())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if db.recorder != nil {
|
||||||
|
tr.SetRecorder(db.recorder)
|
||||||
|
}
|
||||||
|
return tr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenStorageTrie opens the storage trie of an account. In binary trie mode,
|
// OpenStorageTrie opens the storage trie of an account. In binary trie mode,
|
||||||
|
|
|
||||||
|
|
@ -776,6 +776,34 @@ var Forks = map[string]*params.ChainConfig{
|
||||||
ShanghaiTime: u64(0),
|
ShanghaiTime: u64(0),
|
||||||
UBTTime: u64(0),
|
UBTTime: u64(0),
|
||||||
},
|
},
|
||||||
|
"Binary": {
|
||||||
|
ChainID: big.NewInt(1),
|
||||||
|
HomesteadBlock: big.NewInt(0),
|
||||||
|
EIP150Block: big.NewInt(0),
|
||||||
|
EIP155Block: big.NewInt(0),
|
||||||
|
EIP158Block: big.NewInt(0),
|
||||||
|
ByzantiumBlock: big.NewInt(0),
|
||||||
|
ConstantinopleBlock: big.NewInt(0),
|
||||||
|
PetersburgBlock: big.NewInt(0),
|
||||||
|
IstanbulBlock: big.NewInt(0),
|
||||||
|
MuirGlacierBlock: big.NewInt(0),
|
||||||
|
BerlinBlock: big.NewInt(0),
|
||||||
|
LondonBlock: big.NewInt(0),
|
||||||
|
ArrowGlacierBlock: big.NewInt(0),
|
||||||
|
MergeNetsplitBlock: big.NewInt(0),
|
||||||
|
TerminalTotalDifficulty: big.NewInt(0),
|
||||||
|
ShanghaiTime: u64(0),
|
||||||
|
CancunTime: u64(0),
|
||||||
|
PragueTime: u64(0),
|
||||||
|
OsakaTime: u64(0),
|
||||||
|
UBTTime: u64(0),
|
||||||
|
DepositContractAddress: params.MainnetChainConfig.DepositContractAddress,
|
||||||
|
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||||
|
Cancun: params.DefaultCancunBlobConfig,
|
||||||
|
Prague: params.DefaultPragueBlobConfig,
|
||||||
|
Osaka: params.DefaultOsakaBlobConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var bpo1BlobConfig = ¶ms.BlobConfig{
|
var bpo1BlobConfig = ¶ms.BlobConfig{
|
||||||
|
|
|
||||||
126
trie/bintrie/recorder.go
Normal file
126
trie/bintrie/recorder.go
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright 2026 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 bintrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recorder maintains the inverse of the binary-trie key transform: it captures
|
||||||
|
// every mutation applied to a BinaryTrie keyed by the original address (and,
|
||||||
|
// for storage, the original slot key) so the post-state can be rendered as a
|
||||||
|
// types.GenesisAlloc.
|
||||||
|
type Recorder struct {
|
||||||
|
accounts map[common.Address]*types.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecorder returns an empty Recorder.
|
||||||
|
func NewRecorder() *Recorder {
|
||||||
|
return &Recorder{accounts: make(map[common.Address]*types.Account)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// entry returns the existing account entry, or creates a fresh one.
|
||||||
|
func (r *Recorder) entry(addr common.Address) *types.Account {
|
||||||
|
if acc, ok := r.accounts[addr]; ok {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
acc := &types.Account{}
|
||||||
|
r.accounts[addr] = acc
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordAccount upserts the nonce and balance for addr. Existing storage and
|
||||||
|
// code on the entry are preserved.
|
||||||
|
func (r *Recorder) RecordAccount(addr common.Address, acc *types.StateAccount) {
|
||||||
|
e := r.entry(addr)
|
||||||
|
e.Nonce = acc.Nonce
|
||||||
|
if acc.Balance != nil {
|
||||||
|
e.Balance = acc.Balance.ToBig()
|
||||||
|
} else {
|
||||||
|
e.Balance = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordStorage records a storage write. A zero value removes the slot.
|
||||||
|
func (r *Recorder) RecordStorage(addr common.Address, key, value []byte) {
|
||||||
|
k := bytesToHash(key)
|
||||||
|
v := bytesToHash(value)
|
||||||
|
e := r.entry(addr)
|
||||||
|
if (v == common.Hash{}) {
|
||||||
|
if e.Storage != nil {
|
||||||
|
delete(e.Storage, k)
|
||||||
|
if len(e.Storage) == 0 {
|
||||||
|
e.Storage = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.Storage == nil {
|
||||||
|
e.Storage = make(map[common.Hash]common.Hash)
|
||||||
|
}
|
||||||
|
e.Storage[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordCode records the contract code for addr. Empty code clears the field.
|
||||||
|
func (r *Recorder) RecordCode(addr common.Address, code []byte) {
|
||||||
|
e := r.entry(addr)
|
||||||
|
if len(code) == 0 {
|
||||||
|
e.Code = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.Code = common.CopyBytes(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordDeleteAccount drops addr entirely from the recorded set.
|
||||||
|
func (r *Recorder) RecordDeleteAccount(addr common.Address) {
|
||||||
|
delete(r.accounts, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordDeleteStorage clears a single storage slot for addr.
|
||||||
|
func (r *Recorder) RecordDeleteStorage(addr common.Address, key []byte) {
|
||||||
|
r.RecordStorage(addr, key, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alloc returns the recorded post-state as a types.GenesisAlloc. The returned
|
||||||
|
// map shares storage with the recorder; callers must not mutate it concurrently
|
||||||
|
// with further Record calls.
|
||||||
|
func (r *Recorder) Alloc() types.GenesisAlloc {
|
||||||
|
out := make(types.GenesisAlloc, len(r.accounts))
|
||||||
|
for addr, a := range r.accounts {
|
||||||
|
out[addr] = *a
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has reports whether addr has been recorded.
|
||||||
|
func (r *Recorder) Has(addr common.Address) bool {
|
||||||
|
_, ok := r.accounts[addr]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesToHash left-pads short slices into a common.Hash, matching the
|
||||||
|
// normalization performed by BinaryTrie.UpdateStorage on values.
|
||||||
|
func bytesToHash(b []byte) common.Hash {
|
||||||
|
var h common.Hash
|
||||||
|
if len(b) >= common.HashLength {
|
||||||
|
copy(h[:], b[:common.HashLength])
|
||||||
|
} else {
|
||||||
|
copy(h[common.HashLength-len(b):], b)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
277
trie/bintrie/recorder_test.go
Normal file
277
trie/bintrie/recorder_test.go
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
// Copyright 2026 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 bintrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRecorderTestTrie() *BinaryTrie {
|
||||||
|
return &BinaryTrie{
|
||||||
|
store: newNodeStore(),
|
||||||
|
tracer: trie.NewPrevalueTracer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderCapturesAccountWrite verifies the recorder mirrors a single
|
||||||
|
// UpdateAccount call into the resulting GenesisAlloc.
|
||||||
|
func TestRecorderCapturesAccountWrite(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
rec := NewRecorder()
|
||||||
|
tr.SetRecorder(rec)
|
||||||
|
|
||||||
|
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||||
|
acc := &types.StateAccount{
|
||||||
|
Nonce: 7,
|
||||||
|
Balance: uint256.NewInt(42),
|
||||||
|
CodeHash: common.HexToHash("aa").Bytes(),
|
||||||
|
}
|
||||||
|
if err := tr.UpdateAccount(addr, acc, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc := rec.Alloc()
|
||||||
|
got, ok := alloc[addr]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("address %x missing from alloc", addr)
|
||||||
|
}
|
||||||
|
if got.Nonce != 7 {
|
||||||
|
t.Errorf("nonce: got %d want 7", got.Nonce)
|
||||||
|
}
|
||||||
|
if got.Balance == nil || got.Balance.Uint64() != 42 {
|
||||||
|
t.Errorf("balance: got %v want 42", got.Balance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderStorageRoundTrip verifies that storage writes are recorded with
|
||||||
|
// the original (unhashed) slot keys.
|
||||||
|
func TestRecorderStorageRoundTrip(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
rec := NewRecorder()
|
||||||
|
tr.SetRecorder(rec)
|
||||||
|
|
||||||
|
addr := common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||||
|
acc := &types.StateAccount{Nonce: 1, Balance: uint256.NewInt(1)}
|
||||||
|
if err := tr.UpdateAccount(addr, acc, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slot := common.HexToHash("00000000000000000000000000000000000000000000000000000000000000ff")
|
||||||
|
value := common.HexToHash("00000000000000000000000000000000000000000000000000000000deadbeef")
|
||||||
|
if err := tr.UpdateStorage(addr, slot[:], value[:]); err != nil {
|
||||||
|
t.Fatalf("UpdateStorage: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc := rec.Alloc()
|
||||||
|
got := alloc[addr]
|
||||||
|
if got.Storage == nil {
|
||||||
|
t.Fatalf("storage map nil")
|
||||||
|
}
|
||||||
|
if got.Storage[slot] != value {
|
||||||
|
t.Errorf("storage[%x] = %x, want %x", slot, got.Storage[slot], value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderDeleteStorage verifies that writing a zero value (or calling
|
||||||
|
// DeleteStorage) removes the slot from the recorded set, matching MPT-dump
|
||||||
|
// semantics.
|
||||||
|
func TestRecorderDeleteStorage(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
rec := NewRecorder()
|
||||||
|
tr.SetRecorder(rec)
|
||||||
|
|
||||||
|
addr := common.HexToAddress("0x3333333333333333333333333333333333333333")
|
||||||
|
if err := tr.UpdateAccount(addr, &types.StateAccount{Nonce: 1, Balance: uint256.NewInt(1)}, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slotKept := common.HexToHash("00000000000000000000000000000000000000000000000000000000000000ff")
|
||||||
|
slotGone := common.HexToHash("0100000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
if err := tr.UpdateStorage(addr, slotKept[:], common.HexToHash("01").Bytes()); err != nil {
|
||||||
|
t.Fatalf("UpdateStorage(kept): %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.UpdateStorage(addr, slotGone[:], common.HexToHash("02").Bytes()); err != nil {
|
||||||
|
t.Fatalf("UpdateStorage(gone): %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.DeleteStorage(addr, slotGone[:]); err != nil {
|
||||||
|
t.Fatalf("DeleteStorage: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc := rec.Alloc()
|
||||||
|
if _, exists := alloc[addr].Storage[slotGone]; exists {
|
||||||
|
t.Errorf("deleted slot still present")
|
||||||
|
}
|
||||||
|
if _, exists := alloc[addr].Storage[slotKept]; !exists {
|
||||||
|
t.Errorf("retained slot missing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderDeleteAccount verifies an account removed via DeleteAccount
|
||||||
|
// disappears from the alloc entirely, including its storage.
|
||||||
|
func TestRecorderDeleteAccount(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
rec := NewRecorder()
|
||||||
|
tr.SetRecorder(rec)
|
||||||
|
|
||||||
|
addrKept := common.HexToAddress("0x4444444444444444444444444444444444444444")
|
||||||
|
addrGone := common.HexToAddress("0x5555555555555555555555555555555555555555")
|
||||||
|
for _, a := range []common.Address{addrKept, addrGone} {
|
||||||
|
if err := tr.UpdateAccount(a, &types.StateAccount{Nonce: 1, Balance: uint256.NewInt(1)}, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount(%x): %v", a, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slot := common.HexToHash("0100000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
if err := tr.UpdateStorage(addrGone, slot[:], common.HexToHash("0a").Bytes()); err != nil {
|
||||||
|
t.Fatalf("UpdateStorage: %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.DeleteAccount(addrGone); err != nil {
|
||||||
|
t.Fatalf("DeleteAccount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc := rec.Alloc()
|
||||||
|
if _, exists := alloc[addrGone]; exists {
|
||||||
|
t.Errorf("deleted account still present in alloc")
|
||||||
|
}
|
||||||
|
if _, exists := alloc[addrKept]; !exists {
|
||||||
|
t.Errorf("untouched account missing from alloc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderDeleteThenRecreate verifies that recreating an account after a
|
||||||
|
// delete starts from a fresh entry — old storage and code do not bleed into
|
||||||
|
// the new account.
|
||||||
|
func TestRecorderDeleteThenRecreate(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
rec := NewRecorder()
|
||||||
|
tr.SetRecorder(rec)
|
||||||
|
|
||||||
|
addr := common.HexToAddress("0x6666666666666666666666666666666666666666")
|
||||||
|
slot := common.HexToHash("0100000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
|
||||||
|
if err := tr.UpdateAccount(addr, &types.StateAccount{Nonce: 1, Balance: uint256.NewInt(100)}, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount #1: %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.UpdateStorage(addr, slot[:], common.HexToHash("0a").Bytes()); err != nil {
|
||||||
|
t.Fatalf("UpdateStorage: %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.UpdateContractCode(addr, common.Hash{}, []byte{0x60, 0x00}); err != nil {
|
||||||
|
t.Fatalf("UpdateContractCode: %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.DeleteAccount(addr); err != nil {
|
||||||
|
t.Fatalf("DeleteAccount: %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.UpdateAccount(addr, &types.StateAccount{Nonce: 7, Balance: uint256.NewInt(9999)}, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount #2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc := rec.Alloc()
|
||||||
|
got := alloc[addr]
|
||||||
|
if got.Nonce != 7 {
|
||||||
|
t.Errorf("nonce after recreate: got %d want 7", got.Nonce)
|
||||||
|
}
|
||||||
|
if got.Balance == nil || got.Balance.Uint64() != 9999 {
|
||||||
|
t.Errorf("balance after recreate: got %v want 9999", got.Balance)
|
||||||
|
}
|
||||||
|
if len(got.Storage) != 0 {
|
||||||
|
t.Errorf("recreated account has stale storage: %v", got.Storage)
|
||||||
|
}
|
||||||
|
if len(got.Code) != 0 {
|
||||||
|
t.Errorf("recreated account has stale code: %x", got.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderCodeOverwrite verifies that a second UpdateContractCode call
|
||||||
|
// replaces the previously-recorded code.
|
||||||
|
func TestRecorderCodeOverwrite(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
rec := NewRecorder()
|
||||||
|
tr.SetRecorder(rec)
|
||||||
|
|
||||||
|
addr := common.HexToAddress("0x7777777777777777777777777777777777777777")
|
||||||
|
if err := tr.UpdateAccount(addr, &types.StateAccount{Nonce: 1, Balance: uint256.NewInt(1)}, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount: %v", err)
|
||||||
|
}
|
||||||
|
first := []byte{0x60, 0x01}
|
||||||
|
second := []byte{0x60, 0x02, 0x60, 0x03}
|
||||||
|
if err := tr.UpdateContractCode(addr, common.Hash{}, first); err != nil {
|
||||||
|
t.Fatalf("UpdateContractCode #1: %v", err)
|
||||||
|
}
|
||||||
|
if err := tr.UpdateContractCode(addr, common.Hash{}, second); err != nil {
|
||||||
|
t.Fatalf("UpdateContractCode #2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc := rec.Alloc()
|
||||||
|
if !bytes.Equal(alloc[addr].Code, second) {
|
||||||
|
t.Errorf("code: got %x want %x", alloc[addr].Code, second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderPartialUpdatePreservesStorage verifies that a nonce/balance
|
||||||
|
// update on an account does not wipe its previously-recorded storage or code.
|
||||||
|
func TestRecorderPartialUpdatePreservesStorage(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
rec := NewRecorder()
|
||||||
|
tr.SetRecorder(rec)
|
||||||
|
|
||||||
|
addr := common.HexToAddress("0x8888888888888888888888888888888888888888")
|
||||||
|
if err := tr.UpdateAccount(addr, &types.StateAccount{Nonce: 1, Balance: uint256.NewInt(1)}, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount: %v", err)
|
||||||
|
}
|
||||||
|
slot := common.HexToHash("0100000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
if err := tr.UpdateStorage(addr, slot[:], common.HexToHash("0a").Bytes()); err != nil {
|
||||||
|
t.Fatalf("UpdateStorage: %v", err)
|
||||||
|
}
|
||||||
|
code := []byte{0x60, 0x05}
|
||||||
|
if err := tr.UpdateContractCode(addr, common.Hash{}, code); err != nil {
|
||||||
|
t.Fatalf("UpdateContractCode: %v", err)
|
||||||
|
}
|
||||||
|
// Bump nonce only; storage and code should survive.
|
||||||
|
if err := tr.UpdateAccount(addr, &types.StateAccount{Nonce: 2, Balance: uint256.NewInt(1)}, len(code)); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount #2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc := rec.Alloc()
|
||||||
|
got := alloc[addr]
|
||||||
|
if got.Nonce != 2 {
|
||||||
|
t.Errorf("nonce: got %d want 2", got.Nonce)
|
||||||
|
}
|
||||||
|
if got.Storage[slot] == (common.Hash{}) {
|
||||||
|
t.Errorf("storage was cleared by partial update")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got.Code, code) {
|
||||||
|
t.Errorf("code was cleared by partial update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRecorderDisabledByDefault confirms that without SetRecorder the trie
|
||||||
|
// performs no recording (sanity check that hooks are gated).
|
||||||
|
func TestRecorderDisabledByDefault(t *testing.T) {
|
||||||
|
tr := newRecorderTestTrie()
|
||||||
|
if tr.Recorder() != nil {
|
||||||
|
t.Fatal("Recorder() should be nil before SetRecorder")
|
||||||
|
}
|
||||||
|
addr := common.HexToAddress("0x9999999999999999999999999999999999999999")
|
||||||
|
if err := tr.UpdateAccount(addr, &types.StateAccount{Nonce: 1, Balance: uint256.NewInt(1)}, 0); err != nil {
|
||||||
|
t.Fatalf("UpdateAccount: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -111,12 +111,22 @@ type BinaryTrie struct {
|
||||||
reader *trie.Reader
|
reader *trie.Reader
|
||||||
tracer *trie.PrevalueTracer
|
tracer *trie.PrevalueTracer
|
||||||
groupDepth int // Number of levels per serialized group (1-8, default 8)
|
groupDepth int // Number of levels per serialized group (1-8, default 8)
|
||||||
|
recorder *Recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BinaryTrie) GroupDepth() int {
|
func (t *BinaryTrie) GroupDepth() int {
|
||||||
return t.groupDepth
|
return t.groupDepth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRecorder attaches an alloc recorder to the trie. Subsequent mutating
|
||||||
|
// operations will report the original (unhashed) account, storage, and code
|
||||||
|
// writes to the recorder so the post-state can be exported as a GenesisAlloc.
|
||||||
|
// Pass nil to detach.
|
||||||
|
func (t *BinaryTrie) SetRecorder(r *Recorder) { t.recorder = r }
|
||||||
|
|
||||||
|
// Recorder returns the currently attached alloc recorder, or nil.
|
||||||
|
func (t *BinaryTrie) Recorder() *Recorder { return t.recorder }
|
||||||
|
|
||||||
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
||||||
func (t *BinaryTrie) ToDot() string {
|
func (t *BinaryTrie) ToDot() string {
|
||||||
t.store.computeHash(t.store.root)
|
t.store.computeHash(t.store.root)
|
||||||
|
|
@ -255,7 +265,13 @@ func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount,
|
||||||
values[BasicDataLeafKey] = basicData[:]
|
values[BasicDataLeafKey] = basicData[:]
|
||||||
values[CodeHashLeafKey] = acc.CodeHash[:]
|
values[CodeHashLeafKey] = acc.CodeHash[:]
|
||||||
|
|
||||||
return t.store.InsertValuesAtStem(stem, values, t.nodeResolver)
|
if err := t.store.InsertValuesAtStem(stem, values, t.nodeResolver); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t.recorder != nil {
|
||||||
|
t.recorder.RecordAccount(addr, acc)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStem updates the values for the given stem key.
|
// UpdateStem updates the values for the given stem key.
|
||||||
|
|
@ -279,6 +295,9 @@ func (t *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
|
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
|
||||||
}
|
}
|
||||||
|
if t.recorder != nil {
|
||||||
|
t.recorder.RecordStorage(address, key, value)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,7 +312,13 @@ func (t *BinaryTrie) DeleteAccount(addr common.Address) error {
|
||||||
values[BasicDataLeafKey] = zero[:]
|
values[BasicDataLeafKey] = zero[:]
|
||||||
values[CodeHashLeafKey] = zero[:]
|
values[CodeHashLeafKey] = zero[:]
|
||||||
|
|
||||||
return t.store.InsertValuesAtStem(stem, values, t.nodeResolver)
|
if err := t.store.InsertValuesAtStem(stem, values, t.nodeResolver); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t.recorder != nil {
|
||||||
|
t.recorder.RecordDeleteAccount(addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteStorage removes any existing value for key from the trie. If a node was not
|
// DeleteStorage removes any existing value for key from the trie. If a node was not
|
||||||
|
|
@ -305,6 +330,9 @@ func (t *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
|
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
|
||||||
}
|
}
|
||||||
|
if t.recorder != nil {
|
||||||
|
t.recorder.RecordDeleteStorage(addr, key)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,6 +380,7 @@ func (t *BinaryTrie) Copy() *BinaryTrie {
|
||||||
reader: t.reader,
|
reader: t.reader,
|
||||||
tracer: t.tracer.Copy(),
|
tracer: t.tracer.Copy(),
|
||||||
groupDepth: t.groupDepth,
|
groupDepth: t.groupDepth,
|
||||||
|
recorder: t.recorder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,6 +419,9 @@ func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Has
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if t.recorder != nil {
|
||||||
|
t.recorder.RecordCode(addr, code)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue