github.com/juliankolbe/go-ethereum@v1.9.992/core/state/snapshot/generate.go (about) 1 // Copyright 2019 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 snapshot 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "fmt" 23 "math/big" 24 "time" 25 26 "github.com/VictoriaMetrics/fastcache" 27 "github.com/juliankolbe/go-ethereum/common" 28 "github.com/juliankolbe/go-ethereum/common/math" 29 "github.com/juliankolbe/go-ethereum/core/rawdb" 30 "github.com/juliankolbe/go-ethereum/crypto" 31 "github.com/juliankolbe/go-ethereum/ethdb" 32 "github.com/juliankolbe/go-ethereum/log" 33 "github.com/juliankolbe/go-ethereum/rlp" 34 "github.com/juliankolbe/go-ethereum/trie" 35 ) 36 37 var ( 38 // emptyRoot is the known root hash of an empty trie. 39 emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") 40 41 // emptyCode is the known hash of the empty EVM bytecode. 42 emptyCode = crypto.Keccak256Hash(nil) 43 ) 44 45 // generatorStats is a collection of statistics gathered by the snapshot generator 46 // for logging purposes. 47 type generatorStats struct { 48 wiping chan struct{} // Notification channel if wiping is in progress 49 origin uint64 // Origin prefix where generation started 50 start time.Time // Timestamp when generation started 51 accounts uint64 // Number of accounts indexed 52 slots uint64 // Number of storage slots indexed 53 storage common.StorageSize // Account and storage slot size 54 } 55 56 // Log creates an contextual log with the given message and the context pulled 57 // from the internally maintained statistics. 58 func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) { 59 var ctx []interface{} 60 if root != (common.Hash{}) { 61 ctx = append(ctx, []interface{}{"root", root}...) 62 } 63 // Figure out whether we're after or within an account 64 switch len(marker) { 65 case common.HashLength: 66 ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...) 67 case 2 * common.HashLength: 68 ctx = append(ctx, []interface{}{ 69 "in", common.BytesToHash(marker[:common.HashLength]), 70 "at", common.BytesToHash(marker[common.HashLength:]), 71 }...) 72 } 73 // Add the usual measurements 74 ctx = append(ctx, []interface{}{ 75 "accounts", gs.accounts, 76 "slots", gs.slots, 77 "storage", gs.storage, 78 "elapsed", common.PrettyDuration(time.Since(gs.start)), 79 }...) 80 // Calculate the estimated indexing time based on current stats 81 if len(marker) > 0 { 82 if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 { 83 left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8]) 84 85 speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero 86 ctx = append(ctx, []interface{}{ 87 "eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond), 88 }...) 89 } 90 } 91 log.Info(msg, ctx...) 92 } 93 94 // generateSnapshot regenerates a brand new snapshot based on an existing state 95 // database and head block asynchronously. The snapshot is returned immediately 96 // and generation is continued in the background until done. 97 func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, wiper chan struct{}) *diskLayer { 98 // Wipe any previously existing snapshot from the database if no wiper is 99 // currently in progress. 100 if wiper == nil { 101 wiper = wipeSnapshot(diskdb, true) 102 } 103 // Create a new disk layer with an initialized state marker at zero 104 var ( 105 stats = &generatorStats{wiping: wiper, start: time.Now()} 106 batch = diskdb.NewBatch() 107 genMarker = []byte{} // Initialized but empty! 108 ) 109 rawdb.WriteSnapshotRoot(batch, root) 110 journalProgress(batch, genMarker, stats) 111 if err := batch.Write(); err != nil { 112 log.Crit("Failed to write initialized state marker", "error", err) 113 } 114 base := &diskLayer{ 115 diskdb: diskdb, 116 triedb: triedb, 117 root: root, 118 cache: fastcache.New(cache * 1024 * 1024), 119 genMarker: genMarker, 120 genPending: make(chan struct{}), 121 genAbort: make(chan chan *generatorStats), 122 } 123 go base.generate(stats) 124 log.Debug("Start snapshot generation", "root", root) 125 return base 126 } 127 128 // journalProgress persists the generator stats into the database to resume later. 129 func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) { 130 // Write out the generator marker. Note it's a standalone disk layer generator 131 // which is not mixed with journal. It's ok if the generator is persisted while 132 // journal is not. 133 entry := journalGenerator{ 134 Done: marker == nil, 135 Marker: marker, 136 } 137 if stats != nil { 138 entry.Wiping = (stats.wiping != nil) 139 entry.Accounts = stats.accounts 140 entry.Slots = stats.slots 141 entry.Storage = uint64(stats.storage) 142 } 143 blob, err := rlp.EncodeToBytes(entry) 144 if err != nil { 145 panic(err) // Cannot happen, here to catch dev errors 146 } 147 var logstr string 148 switch { 149 case marker == nil: 150 logstr = "done" 151 case bytes.Equal(marker, []byte{}): 152 logstr = "empty" 153 case len(marker) == common.HashLength: 154 logstr = fmt.Sprintf("%#x", marker) 155 default: 156 logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:]) 157 } 158 log.Debug("Journalled generator progress", "progress", logstr) 159 rawdb.WriteSnapshotGenerator(db, blob) 160 } 161 162 // generate is a background thread that iterates over the state and storage tries, 163 // constructing the state snapshot. All the arguments are purely for statistics 164 // gathering and logging, since the method surfs the blocks as they arrive, often 165 // being restarted. 166 func (dl *diskLayer) generate(stats *generatorStats) { 167 // If a database wipe is in operation, wait until it's done 168 if stats.wiping != nil { 169 stats.Log("Wiper running, state snapshotting paused", common.Hash{}, dl.genMarker) 170 select { 171 // If wiper is done, resume normal mode of operation 172 case <-stats.wiping: 173 stats.wiping = nil 174 stats.start = time.Now() 175 176 // If generator was aborted during wipe, return 177 case abort := <-dl.genAbort: 178 abort <- stats 179 return 180 } 181 } 182 // Create an account and state iterator pointing to the current generator marker 183 accTrie, err := trie.NewSecure(dl.root, dl.triedb) 184 if err != nil { 185 // The account trie is missing (GC), surf the chain until one becomes available 186 stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) 187 188 abort := <-dl.genAbort 189 abort <- stats 190 return 191 } 192 stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker) 193 194 var accMarker []byte 195 if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that 196 accMarker = dl.genMarker[:common.HashLength] 197 } 198 accIt := trie.NewIterator(accTrie.NodeIterator(accMarker)) 199 batch := dl.diskdb.NewBatch() 200 201 // Iterate from the previous marker and continue generating the state snapshot 202 logged := time.Now() 203 for accIt.Next() { 204 // Retrieve the current account and flatten it into the internal format 205 accountHash := common.BytesToHash(accIt.Key) 206 207 var acc struct { 208 Nonce uint64 209 Balance *big.Int 210 Root common.Hash 211 CodeHash []byte 212 } 213 if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { 214 log.Crit("Invalid account encountered during snapshot creation", "err", err) 215 } 216 data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) 217 218 // If the account is not yet in-progress, write it out 219 if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) { 220 rawdb.WriteAccountSnapshot(batch, accountHash, data) 221 stats.storage += common.StorageSize(1 + common.HashLength + len(data)) 222 stats.accounts++ 223 } 224 // If we've exceeded our batch allowance or termination was requested, flush to disk 225 var abort chan *generatorStats 226 select { 227 case abort = <-dl.genAbort: 228 default: 229 } 230 if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { 231 // Only write and set the marker if we actually did something useful 232 if batch.ValueSize() > 0 { 233 // Ensure the generator entry is in sync with the data 234 marker := accountHash[:] 235 journalProgress(batch, marker, stats) 236 237 batch.Write() 238 batch.Reset() 239 240 dl.lock.Lock() 241 dl.genMarker = marker 242 dl.lock.Unlock() 243 } 244 if abort != nil { 245 stats.Log("Aborting state snapshot generation", dl.root, accountHash[:]) 246 abort <- stats 247 return 248 } 249 } 250 // If the account is in-progress, continue where we left off (otherwise iterate all) 251 if acc.Root != emptyRoot { 252 storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) 253 if err != nil { 254 log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err) 255 abort := <-dl.genAbort 256 abort <- stats 257 return 258 } 259 var storeMarker []byte 260 if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength { 261 storeMarker = dl.genMarker[common.HashLength:] 262 } 263 storeIt := trie.NewIterator(storeTrie.NodeIterator(storeMarker)) 264 for storeIt.Next() { 265 rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value) 266 stats.storage += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value)) 267 stats.slots++ 268 269 // If we've exceeded our batch allowance or termination was requested, flush to disk 270 var abort chan *generatorStats 271 select { 272 case abort = <-dl.genAbort: 273 default: 274 } 275 if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { 276 // Only write and set the marker if we actually did something useful 277 if batch.ValueSize() > 0 { 278 // Ensure the generator entry is in sync with the data 279 marker := append(accountHash[:], storeIt.Key...) 280 journalProgress(batch, marker, stats) 281 282 batch.Write() 283 batch.Reset() 284 285 dl.lock.Lock() 286 dl.genMarker = marker 287 dl.lock.Unlock() 288 } 289 if abort != nil { 290 stats.Log("Aborting state snapshot generation", dl.root, append(accountHash[:], storeIt.Key...)) 291 abort <- stats 292 return 293 } 294 if time.Since(logged) > 8*time.Second { 295 stats.Log("Generating state snapshot", dl.root, append(accountHash[:], storeIt.Key...)) 296 logged = time.Now() 297 } 298 } 299 } 300 if err := storeIt.Err; err != nil { 301 log.Error("Generator failed to iterate storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) 302 abort := <-dl.genAbort 303 abort <- stats 304 return 305 } 306 } 307 if time.Since(logged) > 8*time.Second { 308 stats.Log("Generating state snapshot", dl.root, accIt.Key) 309 logged = time.Now() 310 } 311 // Some account processed, unmark the marker 312 accMarker = nil 313 } 314 if err := accIt.Err; err != nil { 315 log.Error("Generator failed to iterate account trie", "root", dl.root, "err", err) 316 abort := <-dl.genAbort 317 abort <- stats 318 return 319 } 320 // Snapshot fully generated, set the marker to nil. 321 // Note even there is nothing to commit, persist the 322 // generator anyway to mark the snapshot is complete. 323 journalProgress(batch, nil, stats) 324 batch.Write() 325 326 log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, 327 "storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start))) 328 329 dl.lock.Lock() 330 dl.genMarker = nil 331 close(dl.genPending) 332 dl.lock.Unlock() 333 334 // Someone will be looking for us, wait it out 335 abort := <-dl.genAbort 336 abort <- nil 337 }