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 }