github.com/ethereum/go-ethereum@v1.14.3/core/state/dump.go (about)

     1  // Copyright 2014 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package state
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/common/hexutil"
    26  	"github.com/ethereum/go-ethereum/core/types"
    27  	"github.com/ethereum/go-ethereum/log"
    28  	"github.com/ethereum/go-ethereum/rlp"
    29  	"github.com/ethereum/go-ethereum/trie"
    30  )
    31  
    32  // DumpConfig is a set of options to control what portions of the state will be
    33  // iterated and collected.
    34  type DumpConfig struct {
    35  	SkipCode          bool
    36  	SkipStorage       bool
    37  	OnlyWithAddresses bool
    38  	Start             []byte
    39  	Max               uint64
    40  }
    41  
    42  // DumpCollector interface which the state trie calls during iteration
    43  type DumpCollector interface {
    44  	// OnRoot is called with the state root
    45  	OnRoot(common.Hash)
    46  	// OnAccount is called once for each account in the trie
    47  	OnAccount(*common.Address, DumpAccount)
    48  }
    49  
    50  // DumpAccount represents an account in the state.
    51  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  	AddressHash hexutil.Bytes          `json:"key,omitempty"`     // If we don't have address, we can output the key
    60  }
    61  
    62  // Dump represents the full dump in a collected format, as one large map.
    63  type Dump struct {
    64  	Root     string                 `json:"root"`
    65  	Accounts map[string]DumpAccount `json:"accounts"`
    66  	// Next can be set to represent that this dump is only partial, and Next
    67  	// is where an iterator should be positioned in order to continue the dump.
    68  	Next []byte `json:"next,omitempty"` // nil if no more accounts
    69  }
    70  
    71  // OnRoot implements DumpCollector interface
    72  func (d *Dump) OnRoot(root common.Hash) {
    73  	d.Root = fmt.Sprintf("%x", root)
    74  }
    75  
    76  // OnAccount implements DumpCollector interface
    77  func (d *Dump) OnAccount(addr *common.Address, account DumpAccount) {
    78  	if addr == nil {
    79  		d.Accounts[fmt.Sprintf("pre(%s)", account.AddressHash)] = account
    80  	}
    81  	if addr != nil {
    82  		d.Accounts[(*addr).String()] = account
    83  	}
    84  }
    85  
    86  // iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively.
    87  type iterativeDump struct {
    88  	*json.Encoder
    89  }
    90  
    91  // OnAccount implements DumpCollector interface
    92  func (d iterativeDump) OnAccount(addr *common.Address, account DumpAccount) {
    93  	dumpAccount := &DumpAccount{
    94  		Balance:     account.Balance,
    95  		Nonce:       account.Nonce,
    96  		Root:        account.Root,
    97  		CodeHash:    account.CodeHash,
    98  		Code:        account.Code,
    99  		Storage:     account.Storage,
   100  		AddressHash: account.AddressHash,
   101  		Address:     addr,
   102  	}
   103  	d.Encode(dumpAccount)
   104  }
   105  
   106  // OnRoot implements DumpCollector interface
   107  func (d iterativeDump) OnRoot(root common.Hash) {
   108  	d.Encode(struct {
   109  		Root common.Hash `json:"root"`
   110  	}{root})
   111  }
   112  
   113  // DumpToCollector iterates the state according to the given options and inserts
   114  // the items into a collector for aggregation or serialization.
   115  func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
   116  	// Sanitize the input to allow nil configs
   117  	if conf == nil {
   118  		conf = new(DumpConfig)
   119  	}
   120  	var (
   121  		missingPreimages int
   122  		accounts         uint64
   123  		start            = time.Now()
   124  		logged           = time.Now()
   125  	)
   126  	log.Info("Trie dumping started", "root", s.trie.Hash())
   127  	c.OnRoot(s.trie.Hash())
   128  
   129  	trieIt, err := s.trie.NodeIterator(conf.Start)
   130  	if err != nil {
   131  		log.Error("Trie dumping error", "err", err)
   132  		return nil
   133  	}
   134  	it := trie.NewIterator(trieIt)
   135  	for it.Next() {
   136  		var data types.StateAccount
   137  		if err := rlp.DecodeBytes(it.Value, &data); err != nil {
   138  			panic(err)
   139  		}
   140  		var (
   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  			}
   148  			address   *common.Address
   149  			addr      common.Address
   150  			addrBytes = s.trie.GetKey(it.Key)
   151  		)
   152  		if addrBytes == nil {
   153  			missingPreimages++
   154  			if conf.OnlyWithAddresses {
   155  				continue
   156  			}
   157  		} else {
   158  			addr = common.BytesToAddress(addrBytes)
   159  			address = &addr
   160  			account.Address = address
   161  		}
   162  		obj := newObject(s, addr, &data)
   163  		if !conf.SkipCode {
   164  			account.Code = obj.Code()
   165  		}
   166  		if !conf.SkipStorage {
   167  			account.Storage = make(map[common.Hash]string)
   168  			tr, err := obj.getTrie()
   169  			if err != nil {
   170  				log.Error("Failed to load storage trie", "err", err)
   171  				continue
   172  			}
   173  			trieIt, err := tr.NodeIterator(nil)
   174  			if err != nil {
   175  				log.Error("Failed to create trie iterator", "err", err)
   176  				continue
   177  			}
   178  			storageIt := trie.NewIterator(trieIt)
   179  			for storageIt.Next() {
   180  				_, content, _, err := rlp.Split(storageIt.Value)
   181  				if err != nil {
   182  					log.Error("Failed to decode the value returned by iterator", "error", err)
   183  					continue
   184  				}
   185  				account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content)
   186  			}
   187  		}
   188  		c.OnAccount(address, account)
   189  		accounts++
   190  		if time.Since(logged) > 8*time.Second {
   191  			log.Info("Trie dumping in progress", "at", it.Key, "accounts", accounts,
   192  				"elapsed", common.PrettyDuration(time.Since(start)))
   193  			logged = time.Now()
   194  		}
   195  		if conf.Max > 0 && accounts >= conf.Max {
   196  			if it.Next() {
   197  				nextKey = it.Key
   198  			}
   199  			break
   200  		}
   201  	}
   202  	if missingPreimages > 0 {
   203  		log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
   204  	}
   205  	log.Info("Trie dumping complete", "accounts", accounts,
   206  		"elapsed", common.PrettyDuration(time.Since(start)))
   207  
   208  	return nextKey
   209  }
   210  
   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.
   213  func (s *StateDB) RawDump(opts *DumpConfig) Dump {
   214  	dump := &Dump{
   215  		Accounts: make(map[string]DumpAccount),
   216  	}
   217  	dump.Next = s.DumpToCollector(dump, opts)
   218  	return *dump
   219  }
   220  
   221  // Dump returns a JSON string representing the entire state as a single json-object
   222  func (s *StateDB) Dump(opts *DumpConfig) []byte {
   223  	dump := s.RawDump(opts)
   224  	json, err := json.MarshalIndent(dump, "", "    ")
   225  	if err != nil {
   226  		log.Error("Error dumping state", "err", err)
   227  	}
   228  	return json
   229  }
   230  
   231  // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout
   232  func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) {
   233  	s.DumpToCollector(iterativeDump{output}, opts)
   234  }