github.com/ethereum/go-ethereum@v1.16.1/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  //
   116  // The state iterator is still trie-based and can be converted to snapshot-based
   117  // once the state snapshot is fully integrated into database. TODO(rjl493456442).
   118  func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
   119  	// Sanitize the input to allow nil configs
   120  	if conf == nil {
   121  		conf = new(DumpConfig)
   122  	}
   123  	var (
   124  		missingPreimages int
   125  		accounts         uint64
   126  		start            = time.Now()
   127  		logged           = time.Now()
   128  	)
   129  	log.Info("Trie dumping started", "root", s.originalRoot)
   130  	c.OnRoot(s.originalRoot)
   131  
   132  	tr, err := s.db.OpenTrie(s.originalRoot)
   133  	if err != nil {
   134  		return nil
   135  	}
   136  	trieIt, err := tr.NodeIterator(conf.Start)
   137  	if err != nil {
   138  		log.Error("Trie dumping error", "err", err)
   139  		return nil
   140  	}
   141  	it := trie.NewIterator(trieIt)
   142  
   143  	for it.Next() {
   144  		var data types.StateAccount
   145  		if err := rlp.DecodeBytes(it.Value, &data); err != nil {
   146  			panic(err)
   147  		}
   148  		var (
   149  			account = DumpAccount{
   150  				Balance:     data.Balance.String(),
   151  				Nonce:       data.Nonce,
   152  				Root:        data.Root[:],
   153  				CodeHash:    data.CodeHash,
   154  				AddressHash: it.Key,
   155  			}
   156  			address   *common.Address
   157  			addr      common.Address
   158  			addrBytes = tr.GetKey(it.Key)
   159  		)
   160  		if addrBytes == nil {
   161  			missingPreimages++
   162  			if conf.OnlyWithAddresses {
   163  				continue
   164  			}
   165  		} else {
   166  			addr = common.BytesToAddress(addrBytes)
   167  			address = &addr
   168  			account.Address = address
   169  		}
   170  		obj := newObject(s, addr, &data)
   171  		if !conf.SkipCode {
   172  			account.Code = obj.Code()
   173  		}
   174  		if !conf.SkipStorage {
   175  			account.Storage = make(map[common.Hash]string)
   176  
   177  			storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr)
   178  			if err != nil {
   179  				log.Error("Failed to load storage trie", "err", err)
   180  				continue
   181  			}
   182  			trieIt, err := storageTr.NodeIterator(nil)
   183  			if err != nil {
   184  				log.Error("Failed to create trie iterator", "err", err)
   185  				continue
   186  			}
   187  			storageIt := trie.NewIterator(trieIt)
   188  			for storageIt.Next() {
   189  				_, content, _, err := rlp.Split(storageIt.Value)
   190  				if err != nil {
   191  					log.Error("Failed to decode the value returned by iterator", "error", err)
   192  					continue
   193  				}
   194  				key := storageTr.GetKey(storageIt.Key)
   195  				if key == nil {
   196  					continue
   197  				}
   198  				account.Storage[common.BytesToHash(key)] = common.Bytes2Hex(content)
   199  			}
   200  		}
   201  		c.OnAccount(address, account)
   202  		accounts++
   203  		if time.Since(logged) > 8*time.Second {
   204  			log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts,
   205  				"elapsed", common.PrettyDuration(time.Since(start)))
   206  			logged = time.Now()
   207  		}
   208  		if conf.Max > 0 && accounts >= conf.Max {
   209  			if it.Next() {
   210  				nextKey = it.Key
   211  			}
   212  			break
   213  		}
   214  	}
   215  	if missingPreimages > 0 {
   216  		log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
   217  	}
   218  	log.Info("Trie dumping complete", "accounts", accounts,
   219  		"elapsed", common.PrettyDuration(time.Since(start)))
   220  
   221  	return nextKey
   222  }
   223  
   224  // RawDump returns the state. If the processing is aborted e.g. due to options
   225  // reaching Max, the `Next` key is set on the returned Dump.
   226  func (s *StateDB) RawDump(opts *DumpConfig) Dump {
   227  	dump := &Dump{
   228  		Accounts: make(map[string]DumpAccount),
   229  	}
   230  	dump.Next = s.DumpToCollector(dump, opts)
   231  	return *dump
   232  }
   233  
   234  // Dump returns a JSON string representing the entire state as a single json-object
   235  func (s *StateDB) Dump(opts *DumpConfig) []byte {
   236  	dump := s.RawDump(opts)
   237  	json, err := json.MarshalIndent(dump, "", "    ")
   238  	if err != nil {
   239  		log.Error("Error dumping state", "err", err)
   240  	}
   241  	return json
   242  }
   243  
   244  // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout
   245  func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) {
   246  	s.DumpToCollector(iterativeDump{output}, opts)
   247  }