core/state: add state transition tool for testing #20958 (#1200)

This commit is contained in:
Daniel Liu 2025-08-20 16:30:34 +08:00 committed by GitHub
parent a8b841c0a0
commit eaa1452a53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 29 deletions

View file

@ -28,6 +28,14 @@ import (
"github.com/XinFinOrg/XDPoSChain/trie" "github.com/XinFinOrg/XDPoSChain/trie"
) )
// DumpCollector interface which the state trie calls during iteration
type DumpCollector interface {
// OnRoot is called with the state root
OnRoot(common.Hash)
// OnAccount is called once for each account in the trie
OnAccount(common.Address, DumpAccount)
}
// DumpAccount represents an account in the state. // DumpAccount represents an account in the state.
type DumpAccount struct { type DumpAccount struct {
Balance string `json:"balance"` Balance string `json:"balance"`
@ -46,9 +54,14 @@ type Dump struct {
Accounts map[common.Address]DumpAccount `json:"accounts"` Accounts map[common.Address]DumpAccount `json:"accounts"`
} }
// iterativeDump is a 'collector'-implementation which dump output line-by-line iteratively. // OnRoot implements DumpCollector interface
type iterativeDump struct { func (d *Dump) OnRoot(root common.Hash) {
*json.Encoder d.Root = fmt.Sprintf("%x", root)
}
// OnAccount implements DumpCollector interface
func (d *Dump) OnAccount(addr common.Address, account DumpAccount) {
d.Accounts[addr] = account
} }
// IteratorDump is an implementation for iterating over data. // IteratorDump is an implementation for iterating over data.
@ -58,28 +71,23 @@ type IteratorDump struct {
Next []byte `json:"next,omitempty"` // nil if no more accounts Next []byte `json:"next,omitempty"` // nil if no more accounts
} }
// Collector interface which the state trie calls during iteration // OnRoot implements DumpCollector interface
type collector interface { func (d *IteratorDump) OnRoot(root common.Hash) {
onRoot(common.Hash)
onAccount(common.Address, DumpAccount)
}
func (d *Dump) onRoot(root common.Hash) {
d.Root = fmt.Sprintf("%x", root) d.Root = fmt.Sprintf("%x", root)
} }
func (d *Dump) onAccount(addr common.Address, account DumpAccount) { // OnAccount implements DumpCollector interface
d.Accounts[addr] = account func (d *IteratorDump) OnAccount(addr common.Address, account DumpAccount) {
}
func (d *IteratorDump) onRoot(root common.Hash) {
d.Root = fmt.Sprintf("%x", root)
}
func (d *IteratorDump) onAccount(addr common.Address, account DumpAccount) {
d.Accounts[addr] = account d.Accounts[addr] = account
} }
func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) { // iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively.
type iterativeDump struct {
*json.Encoder
}
// OnAccount implements DumpCollector interface
func (d iterativeDump) OnAccount(addr common.Address, account DumpAccount) {
dumpAccount := &DumpAccount{ dumpAccount := &DumpAccount{
Balance: account.Balance, Balance: account.Balance,
Nonce: account.Nonce, Nonce: account.Nonce,
@ -96,15 +104,16 @@ func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) {
d.Encode(dumpAccount) d.Encode(dumpAccount)
} }
func (d iterativeDump) onRoot(root common.Hash) { // OnRoot implements DumpCollector interface
func (d iterativeDump) OnRoot(root common.Hash) {
d.Encode(struct { d.Encode(struct {
Root common.Hash `json:"root"` Root common.Hash `json:"root"`
}{root}) }{root})
} }
func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) { func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) {
missingPreimages := 0 missingPreimages := 0
c.onRoot(s.trie.Hash()) c.OnRoot(s.trie.Hash())
var count int var count int
it := trie.NewIterator(s.trie.NodeIterator(start)) it := trie.NewIterator(s.trie.NodeIterator(start))
@ -145,7 +154,7 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP
account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content) account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content)
} }
} }
c.onAccount(addr, account) c.OnAccount(addr, account)
count++ count++
if maxResults > 0 && count >= maxResults { if maxResults > 0 && count >= maxResults {
if it.Next() { if it.Next() {
@ -166,7 +175,7 @@ func (s *StateDB) RawDump(excludeCode, excludeStorage, excludeMissingPreimages b
dump := &Dump{ dump := &Dump{
Accounts: make(map[common.Address]DumpAccount), Accounts: make(map[common.Address]DumpAccount),
} }
s.dump(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) s.DumpToCollector(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
return *dump return *dump
} }
@ -175,14 +184,14 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool
dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages) dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages)
json, err := json.MarshalIndent(dump, "", " ") json, err := json.MarshalIndent(dump, "", " ")
if err != nil { if err != nil {
fmt.Println("dump err", err) fmt.Println("Dump err", err)
} }
return json return json
} }
// IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout
func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) { func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) {
s.dump(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) s.DumpToCollector(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
} }
// IteratorDump dumps out a batch of accounts starts with the given start key // IteratorDump dumps out a batch of accounts starts with the given start key
@ -190,6 +199,6 @@ func (s *StateDB) IteratorDump(excludeCode, excludeStorage, excludeMissingPreima
iterator := &IteratorDump{ iterator := &IteratorDump{
Accounts: make(map[common.Address]DumpAccount), Accounts: make(map[common.Address]DumpAccount),
} }
iterator.Next = s.dump(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults) iterator.Next = s.DumpToCollector(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults)
return *iterator return *iterator
} }

View file

@ -57,7 +57,7 @@ func TestDump(t *testing.T) {
s.state.updateStateObject(obj2) s.state.updateStateObject(obj2)
s.state.Commit(false) s.state.Commit(false)
// check that dump contains the state objects that are in trie // check that DumpToCollector contains the state objects that are in trie
got := string(s.state.Dump(false, false, true)) got := string(s.state.Dump(false, false, true))
want := `{ want := `{
"root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2",
@ -84,7 +84,7 @@ func TestDump(t *testing.T) {
} }
}` }`
if got != want { if got != want {
t.Errorf("dump mismatch:\ngot: %s\nwant: %s\n", got, want) t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want)
} }
} }