// Copyright 2014 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 . package state import ( "bytes" "encoding/json" "fmt" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) // DumpConfig is a set of options to control what portions of the statewill be // iterated and collected. type DumpConfig struct { SkipCode bool SkipStorage bool OnlyWithAddresses bool HoldStorage bool Start []byte End []byte StateStart []byte StateEnd []byte Max uint64 } // 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 OnAccountStart(common.Address, DumpAccount) // OnAccount is called once for each account in the trie OnAccountState(_ common.Address, StateSecureKey hexutil.Bytes, key, value []byte) OnAccountEnd(common.Address, DumpAccount) } // DumpAccount represents an account in the state. type DumpAccount struct { Balance string `json:"balance"` Nonce uint64 `json:"nonce"` Root hexutil.Bytes `json:"root"` CodeHash hexutil.Bytes `json:"codeHash"` Code hexutil.Bytes `json:"code,omitempty"` Storage map[common.Hash]string `json:"storage,omitempty"` Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key } // Dump represents the full dump in a collected format, as one large map. type Dump struct { Root string `json:"root"` Accounts map[common.Address]DumpAccount `json:"accounts"` } // OnRoot implements DumpCollector interface func (d *Dump) OnRoot(root common.Hash) { d.Root = fmt.Sprintf("%x", root) } // OnAccount implements DumpCollector interface func (d *Dump) OnAccountStart(addr common.Address, account DumpAccount) { } // OnAccount implements DumpCollector interface func (d *Dump) OnAccountState(_ common.Address, StateSecureKey hexutil.Bytes, key, value []byte) { } // OnAccount implements DumpCollector interface func (d *Dump) OnAccountEnd(addr common.Address, account DumpAccount) { d.Accounts[addr] = account } // IteratorDump is an implementation for iterating over data. type IteratorDump struct { Root string `json:"root"` Accounts map[common.Address]DumpAccount `json:"accounts"` Next []byte `json:"next,omitempty"` // nil if no more accounts } // OnRoot implements DumpCollector interface func (d *IteratorDump) OnRoot(root common.Hash) { d.Root = fmt.Sprintf("%x", root) } // OnAccount implements DumpCollector interface func (d *IteratorDump) OnAccountStart(addr common.Address, account DumpAccount) { } // OnAccount implements DumpCollector interface func (d *IteratorDump) OnAccountState(_ common.Address, StateSecureKey hexutil.Bytes, key, value []byte) { } // OnAccount implements DumpCollector interface func (d *IteratorDump) OnAccountEnd(addr common.Address, account DumpAccount) { d.Accounts[addr] = account } // iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively. type iterativeDump struct { *json.Encoder } // OnAccount implements DumpCollector interface func (d iterativeDump) OnAccountStart(addr common.Address, account DumpAccount) { } // OnAccount implements DumpCollector interface func (d iterativeDump) OnAccountState(_ common.Address, StateSecureKey hexutil.Bytes, key, value []byte) { } // OnAccount implements DumpCollector interface func (d iterativeDump) OnAccountEnd(addr common.Address, account DumpAccount) { dumpAccount := &DumpAccount{ Balance: account.Balance, Nonce: account.Nonce, Root: account.Root, CodeHash: account.CodeHash, Code: account.Code, Storage: account.Storage, SecureKey: account.SecureKey, Address: nil, } if addr != (common.Address{}) { dumpAccount.Address = &addr } d.Encode(dumpAccount) } // OnRoot implements DumpCollector interface func (d iterativeDump) OnRoot(root common.Hash) { d.Encode(struct { Root common.Hash `json:"root"` }{root}) } // DumpToCollector iterates the state according to the given options and inserts // the items into a collector for aggregation or serialization. func (s *DB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) { // Sanitize the input to allow nil configs if conf == nil { conf = new(DumpConfig) } var ( missingPreimages int accounts uint64 start = time.Now() logged = time.Now() ) log.Info("Trie dumping started", "root", s.trie.Hash()) c.OnRoot(s.trie.Hash()) hasEnd := len(conf.End) > 0 stateStart := conf.StateStart hasStateEnd := len(conf.StateEnd) > 0 it := trie.NewIterator(s.trie.NodeIterator(conf.Start)) for it.Next() { if hasEnd && bytes.Compare(it.Key, conf.End) >= 0 { break } var data Account if err := rlp.DecodeBytes(it.Value, &data); err != nil { panic(err) } account := DumpAccount{ Balance: data.Balance.String(), Nonce: data.Nonce, Root: data.Root[:], CodeHash: data.CodeHash, SecureKey: it.Key, } addrBytes := s.trie.GetKey(it.Key) if addrBytes == nil { // Preimage missing missingPreimages++ if conf.OnlyWithAddresses { continue } account.SecureKey = it.Key } addr := common.BytesToAddress(addrBytes) obj := newObject(s, addr, data) if !conf.SkipCode { account.Code = obj.Code(s.db) } c.OnAccountStart(addr, account) if !conf.SkipStorage { account.Storage = make(map[common.Hash]string) storageIt := trie.NewIterator(obj.getTrie(s.db).NodeIterator(stateStart)) for storageIt.Next() { if hasStateEnd && bytes.Compare(storageIt.Key, conf.StateEnd) >= 0 { break } key := s.trie.GetKey(storageIt.Key) c.OnAccountState(addr, storageIt.Key, key, storageIt.Value) if conf.HoldStorage { _, content, _, err := rlp.Split(storageIt.Value) if err != nil { log.Error("Failed to decode the value returned by iterator", "error", err) continue } account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content) } } stateStart = nil hasStateEnd = false } c.OnAccountEnd(addr, account) accounts++ if time.Since(logged) > 8*time.Second { log.Info("Trie dumping in progress", "at", it.Key, "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start))) logged = time.Now() } if conf.Max > 0 && accounts >= conf.Max { if it.Next() { nextKey = it.Key } break } } if missingPreimages > 0 { log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages) } log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start))) return nextKey } // RawDump returns the entire state an a single large object func (s *DB) RawDump(opts *DumpConfig) Dump { dump := &Dump{ Accounts: make(map[common.Address]DumpAccount), } s.DumpToCollector(dump, opts) return *dump } // Dump returns a JSON string representing the entire state as a single json-object func (s *DB) Dump(opts *DumpConfig) []byte { dump := s.RawDump(opts) json, err := json.MarshalIndent(dump, "", " ") if err != nil { fmt.Println("Dump err", err) } return json } // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout func (s *DB) IterativeDump(opts *DumpConfig, output *json.Encoder) { s.DumpToCollector(iterativeDump{output}, opts) } // IteratorDump dumps out a batch of accounts starts with the given start key func (s *DB) IteratorDump(opts *DumpConfig) IteratorDump { iterator := &IteratorDump{ Accounts: make(map[common.Address]DumpAccount), } iterator.Next = s.DumpToCollector(iterator, opts) return *iterator }