Skip to content

Commit 63979bc

Browse files
authored
cmd/evm, core/state: fix post-exec dump of state (statetests, blockchaintests) (#28504)
There were several problems related to dumping state. - If a preimage was missing, even if we had set the `OnlyWithAddresses` to `false`, to export them anyway, the way the mapping was constructed (using `common.Address` as key) made the entries get lost anyway. Concerns both state- and blockchain tests. - Blockchain test execution was not configured to store preimages. This changes makes it so that the block test executor takes a callback, just like the state test executor already does. This callback can be used to examine the post-execution state, e.g. to aid debugging of test failures.
1 parent 58297e3 commit 63979bc

File tree

9 files changed

+92
-99
lines changed

9 files changed

+92
-99
lines changed

cmd/evm/blockrunner.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"regexp"
2525
"sort"
2626

27+
"github.com/ethereum/go-ethereum/core"
2728
"github.com/ethereum/go-ethereum/core/rawdb"
2829
"github.com/ethereum/go-ethereum/core/vm"
2930
"github.com/ethereum/go-ethereum/eth/tracers/logger"
@@ -85,7 +86,13 @@ func blockTestCmd(ctx *cli.Context) error {
8586
continue
8687
}
8788
test := tests[name]
88-
if err := test.Run(false, rawdb.HashScheme, tracer); err != nil {
89+
if err := test.Run(false, rawdb.HashScheme, tracer, func(res error, chain *core.BlockChain) {
90+
if ctx.Bool(DumpFlag.Name) {
91+
if state, _ := chain.State(); state != nil {
92+
fmt.Println(string(state.Dump(nil)))
93+
}
94+
}
95+
}); err != nil {
8996
return fmt.Errorf("test %v: %w", name, err)
9097
}
9198
}

cmd/geth/chaincmd.go

-5
Original file line numberDiff line numberDiff line change
@@ -473,11 +473,6 @@ func dump(ctx *cli.Context) error {
473473
if ctx.Bool(utils.IterativeOutputFlag.Name) {
474474
state.IterativeDump(conf, json.NewEncoder(os.Stdout))
475475
} else {
476-
if conf.OnlyWithAddresses {
477-
fmt.Fprintf(os.Stderr, "If you want to include accounts with missing preimages, you need iterative output, since"+
478-
" otherwise the accounts will overwrite each other in the resulting mapping.")
479-
return errors.New("incompatible options")
480-
}
481476
fmt.Println(string(state.Dump(conf)))
482477
}
483478
return nil

cmd/geth/snapshot.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -580,11 +580,11 @@ func dumpState(ctx *cli.Context) error {
580580
return err
581581
}
582582
da := &state.DumpAccount{
583-
Balance: account.Balance.String(),
584-
Nonce: account.Nonce,
585-
Root: account.Root.Bytes(),
586-
CodeHash: account.CodeHash,
587-
SecureKey: accIt.Hash().Bytes(),
583+
Balance: account.Balance.String(),
584+
Nonce: account.Nonce,
585+
Root: account.Root.Bytes(),
586+
CodeHash: account.CodeHash,
587+
AddressHash: accIt.Hash().Bytes(),
588588
}
589589
if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
590590
da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))

core/state/dump.go

+40-60
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,24 @@ type DumpCollector interface {
4949

5050
// DumpAccount represents an account in the state.
5151
type DumpAccount struct {
52-
Balance string `json:"balance"`
53-
Nonce uint64 `json:"nonce"`
54-
Root hexutil.Bytes `json:"root"`
55-
CodeHash hexutil.Bytes `json:"codeHash"`
56-
Code hexutil.Bytes `json:"code,omitempty"`
57-
Storage map[common.Hash]string `json:"storage,omitempty"`
58-
Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
59-
SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key
52+
Balance string `json:"balance"`
53+
Nonce uint64 `json:"nonce"`
54+
Root hexutil.Bytes `json:"root"`
55+
CodeHash hexutil.Bytes `json:"codeHash"`
56+
Code hexutil.Bytes `json:"code,omitempty"`
57+
Storage map[common.Hash]string `json:"storage,omitempty"`
58+
Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode
59+
AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key
6060

6161
}
6262

6363
// Dump represents the full dump in a collected format, as one large map.
6464
type Dump struct {
65-
Root string `json:"root"`
66-
Accounts map[common.Address]DumpAccount `json:"accounts"`
65+
Root string `json:"root"`
66+
Accounts map[string]DumpAccount `json:"accounts"`
67+
// Next can be set to represent that this dump is only partial, and Next
68+
// is where an iterator should be positioned in order to continue the dump.
69+
Next []byte `json:"next,omitempty"` // nil if no more accounts
6770
}
6871

6972
// OnRoot implements DumpCollector interface
@@ -73,27 +76,11 @@ func (d *Dump) OnRoot(root common.Hash) {
7376

7477
// OnAccount implements DumpCollector interface
7578
func (d *Dump) OnAccount(addr *common.Address, account DumpAccount) {
76-
if addr != nil {
77-
d.Accounts[*addr] = account
79+
if addr == nil {
80+
d.Accounts[fmt.Sprintf("pre(%s)", account.AddressHash)] = account
7881
}
79-
}
80-
81-
// IteratorDump is an implementation for iterating over data.
82-
type IteratorDump struct {
83-
Root string `json:"root"`
84-
Accounts map[common.Address]DumpAccount `json:"accounts"`
85-
Next []byte `json:"next,omitempty"` // nil if no more accounts
86-
}
87-
88-
// OnRoot implements DumpCollector interface
89-
func (d *IteratorDump) OnRoot(root common.Hash) {
90-
d.Root = fmt.Sprintf("%x", root)
91-
}
92-
93-
// OnAccount implements DumpCollector interface
94-
func (d *IteratorDump) OnAccount(addr *common.Address, account DumpAccount) {
9582
if addr != nil {
96-
d.Accounts[*addr] = account
83+
d.Accounts[(*addr).String()] = account
9784
}
9885
}
9986

@@ -105,14 +92,14 @@ type iterativeDump struct {
10592
// OnAccount implements DumpCollector interface
10693
func (d iterativeDump) OnAccount(addr *common.Address, account DumpAccount) {
10794
dumpAccount := &DumpAccount{
108-
Balance: account.Balance,
109-
Nonce: account.Nonce,
110-
Root: account.Root,
111-
CodeHash: account.CodeHash,
112-
Code: account.Code,
113-
Storage: account.Storage,
114-
SecureKey: account.SecureKey,
115-
Address: addr,
95+
Balance: account.Balance,
96+
Nonce: account.Nonce,
97+
Root: account.Root,
98+
CodeHash: account.CodeHash,
99+
Code: account.Code,
100+
Storage: account.Storage,
101+
AddressHash: account.AddressHash,
102+
Address: addr,
116103
}
117104
d.Encode(dumpAccount)
118105
}
@@ -150,26 +137,27 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
150137
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
151138
panic(err)
152139
}
153-
account := DumpAccount{
154-
Balance: data.Balance.String(),
155-
Nonce: data.Nonce,
156-
Root: data.Root[:],
157-
CodeHash: data.CodeHash,
158-
SecureKey: it.Key,
159-
}
160140
var (
161-
addrBytes = s.trie.GetKey(it.Key)
162-
addr = common.BytesToAddress(addrBytes)
141+
account = DumpAccount{
142+
Balance: data.Balance.String(),
143+
Nonce: data.Nonce,
144+
Root: data.Root[:],
145+
CodeHash: data.CodeHash,
146+
AddressHash: it.Key,
147+
}
163148
address *common.Address
149+
addr common.Address
150+
addrBytes = s.trie.GetKey(it.Key)
164151
)
165152
if addrBytes == nil {
166-
// Preimage missing
167153
missingPreimages++
168154
if conf.OnlyWithAddresses {
169155
continue
170156
}
171157
} else {
158+
addr = common.BytesToAddress(addrBytes)
172159
address = &addr
160+
account.Address = address
173161
}
174162
obj := newObject(s, addr, &data)
175163
if !conf.SkipCode {
@@ -220,12 +208,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
220208
return nextKey
221209
}
222210

223-
// RawDump returns the entire state an a single large object
211+
// RawDump returns the state. If the processing is aborted e.g. due to options
212+
// reaching Max, the `Next` key is set on the returned Dump.
224213
func (s *StateDB) RawDump(opts *DumpConfig) Dump {
225214
dump := &Dump{
226-
Accounts: make(map[common.Address]DumpAccount),
215+
Accounts: make(map[string]DumpAccount),
227216
}
228-
s.DumpToCollector(dump, opts)
217+
dump.Next = s.DumpToCollector(dump, opts)
229218
return *dump
230219
}
231220

@@ -234,7 +223,7 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte {
234223
dump := s.RawDump(opts)
235224
json, err := json.MarshalIndent(dump, "", " ")
236225
if err != nil {
237-
fmt.Println("Dump err", err)
226+
log.Error("Error dumping state", "err", err)
238227
}
239228
return json
240229
}
@@ -243,12 +232,3 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte {
243232
func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) {
244233
s.DumpToCollector(iterativeDump{output}, opts)
245234
}
246-
247-
// IteratorDump dumps out a batch of accounts starts with the given start key
248-
func (s *StateDB) IteratorDump(opts *DumpConfig) IteratorDump {
249-
iterator := &IteratorDump{
250-
Accounts: make(map[common.Address]DumpAccount),
251-
}
252-
iterator.Next = s.DumpToCollector(iterator, opts)
253-
return *iterator
254-
}

core/state/state_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,15 @@ func TestDump(t *testing.T) {
7171
"nonce": 0,
7272
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
7373
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
74+
"address": "0x0000000000000000000000000000000000000001",
7475
"key": "0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d"
7576
},
7677
"0x0000000000000000000000000000000000000002": {
7778
"balance": "44",
7879
"nonce": 0,
7980
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
8081
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
82+
"address": "0x0000000000000000000000000000000000000002",
8183
"key": "0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62"
8284
},
8385
"0x0000000000000000000000000000000000000102": {
@@ -86,6 +88,7 @@ func TestDump(t *testing.T) {
8688
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
8789
"codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3",
8890
"code": "0x03030303030303",
91+
"address": "0x0000000000000000000000000000000000000102",
8992
"key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"
9093
}
9194
}

eth/api_debug.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error)
133133
const AccountRangeMaxResults = 256
134134

135135
// AccountRange enumerates all accounts in the given block and start point in paging request
136-
func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) {
136+
func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.Dump, error) {
137137
var stateDb *state.StateDB
138138
var err error
139139

@@ -144,7 +144,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
144144
// the miner and operate on those
145145
_, stateDb = api.eth.miner.Pending()
146146
if stateDb == nil {
147-
return state.IteratorDump{}, errors.New("pending state is not available")
147+
return state.Dump{}, errors.New("pending state is not available")
148148
}
149149
} else {
150150
var header *types.Header
@@ -158,29 +158,29 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
158158
default:
159159
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
160160
if block == nil {
161-
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
161+
return state.Dump{}, fmt.Errorf("block #%d not found", number)
162162
}
163163
header = block.Header()
164164
}
165165
if header == nil {
166-
return state.IteratorDump{}, fmt.Errorf("block #%d not found", number)
166+
return state.Dump{}, fmt.Errorf("block #%d not found", number)
167167
}
168168
stateDb, err = api.eth.BlockChain().StateAt(header.Root)
169169
if err != nil {
170-
return state.IteratorDump{}, err
170+
return state.Dump{}, err
171171
}
172172
}
173173
} else if hash, ok := blockNrOrHash.Hash(); ok {
174174
block := api.eth.blockchain.GetBlockByHash(hash)
175175
if block == nil {
176-
return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex())
176+
return state.Dump{}, fmt.Errorf("block %s not found", hash.Hex())
177177
}
178178
stateDb, err = api.eth.BlockChain().StateAt(block.Root())
179179
if err != nil {
180-
return state.IteratorDump{}, err
180+
return state.Dump{}, err
181181
}
182182
} else {
183-
return state.IteratorDump{}, errors.New("either block number or block hash must be specified")
183+
return state.Dump{}, errors.New("either block number or block hash must be specified")
184184
}
185185

186186
opts := &state.DumpConfig{
@@ -193,7 +193,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex
193193
if maxResults > AccountRangeMaxResults || maxResults <= 0 {
194194
opts.Max = AccountRangeMaxResults
195195
}
196-
return stateDb.IteratorDump(opts), nil
196+
return stateDb.RawDump(opts), nil
197197
}
198198

199199
// StorageRangeResult is the result of a debug_storageRangeAt API call.

eth/api_debug_test.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"math/big"
2323
"reflect"
24+
"strings"
2425
"testing"
2526

2627
"github.com/davecgh/go-spew/spew"
@@ -35,8 +36,8 @@ import (
3536

3637
var dumper = spew.ConfigState{Indent: " "}
3738

38-
func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump {
39-
result := statedb.IteratorDump(&state.DumpConfig{
39+
func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump {
40+
result := statedb.RawDump(&state.DumpConfig{
4041
SkipCode: true,
4142
SkipStorage: true,
4243
OnlyWithAddresses: false,
@@ -47,12 +48,12 @@ func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, st
4748
if len(result.Accounts) != expectedNum {
4849
t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts))
4950
}
50-
for address := range result.Accounts {
51-
if address == (common.Address{}) {
52-
t.Fatalf("empty address returned")
51+
for addr, acc := range result.Accounts {
52+
if strings.HasSuffix(addr, "pre") || acc.Address == nil {
53+
t.Fatalf("account without prestate (address) returned: %v", addr)
5354
}
54-
if !statedb.Exist(address) {
55-
t.Fatalf("account not found in state %s", address.Hex())
55+
if !statedb.Exist(*acc.Address) {
56+
t.Fatalf("account not found in state %s", acc.Address.Hex())
5657
}
5758
}
5859
return result
@@ -92,16 +93,16 @@ func TestAccountRange(t *testing.T) {
9293
secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults)
9394

9495
hList := make([]common.Hash, 0)
95-
for addr1 := range firstResult.Accounts {
96-
// If address is empty, then it makes no sense to compare
96+
for addr1, acc := range firstResult.Accounts {
97+
// If address is non-available, then it makes no sense to compare
9798
// them as they might be two different accounts.
98-
if addr1 == (common.Address{}) {
99+
if acc.Address == nil {
99100
continue
100101
}
101102
if _, duplicate := secondResult.Accounts[addr1]; duplicate {
102103
t.Fatalf("pagination test failed: results should not overlap")
103104
}
104-
hList = append(hList, crypto.Keccak256Hash(addr1.Bytes()))
105+
hList = append(hList, crypto.Keccak256Hash(acc.Address.Bytes()))
105106
}
106107
// Test to see if it's possible to recover from the middle of the previous
107108
// set and get an even split between the first and second sets.
@@ -140,7 +141,7 @@ func TestEmptyAccountRange(t *testing.T) {
140141
st.Commit(0, true)
141142
st, _ = state.New(types.EmptyRootHash, statedb, nil)
142143

143-
results := st.IteratorDump(&state.DumpConfig{
144+
results := st.RawDump(&state.DumpConfig{
144145
SkipCode: true,
145146
SkipStorage: true,
146147
OnlyWithAddresses: true,

tests/block_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,19 @@ func TestExecutionSpec(t *testing.T) {
7474
}
7575

7676
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
77-
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil)); err != nil {
77+
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil {
7878
t.Errorf("test in hash mode without snapshotter failed: %v", err)
7979
return
8080
}
81-
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil)); err != nil {
81+
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil {
8282
t.Errorf("test in hash mode with snapshotter failed: %v", err)
8383
return
8484
}
85-
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil)); err != nil {
85+
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil {
8686
t.Errorf("test in path mode without snapshotter failed: %v", err)
8787
return
8888
}
89-
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil)); err != nil {
89+
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil {
9090
t.Errorf("test in path mode with snapshotter failed: %v", err)
9191
return
9292
}

0 commit comments

Comments
 (0)