github.com/dim4egster/coreth@v0.10.2/core/state/snapshot/generate.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2019 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package snapshot 28 29 import ( 30 "bytes" 31 "encoding/binary" 32 "fmt" 33 "math/big" 34 "time" 35 36 "github.com/VictoriaMetrics/fastcache" 37 "github.com/dim4egster/coreth/core/rawdb" 38 "github.com/dim4egster/coreth/ethdb" 39 "github.com/dim4egster/coreth/trie" 40 "github.com/ethereum/go-ethereum/common" 41 "github.com/ethereum/go-ethereum/common/math" 42 "github.com/ethereum/go-ethereum/crypto" 43 "github.com/ethereum/go-ethereum/log" 44 "github.com/ethereum/go-ethereum/rlp" 45 ) 46 47 var ( 48 // emptyRoot is the known root hash of an empty trie. 49 emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") 50 51 // emptyCode is the known hash of the empty EVM bytecode. 52 emptyCode = crypto.Keccak256Hash(nil) 53 ) 54 55 // generatorStats is a collection of statistics gathered by the snapshot generator 56 // for logging purposes. 57 type generatorStats struct { 58 wiping chan struct{} // Notification channel if wiping is in progress 59 origin uint64 // Origin prefix where generation started 60 start time.Time // Timestamp when generation started 61 accounts uint64 // Number of accounts indexed(generated or recovered) 62 slots uint64 // Number of storage slots indexed(generated or recovered) 63 storage common.StorageSize // Total account and storage slot size(generation or recovery) 64 } 65 66 // Info creates an contextual info-level log with the given message and the context pulled 67 // from the internally maintained statistics. 68 func (gs *generatorStats) Info(msg string, root common.Hash, marker []byte) { 69 gs.log(log.LvlInfo, msg, root, marker) 70 } 71 72 // Debug creates an contextual debug-level log with the given message and the context pulled 73 // from the internally maintained statistics. 74 func (gs *generatorStats) Debug(msg string, root common.Hash, marker []byte) { 75 gs.log(log.LvlDebug, msg, root, marker) 76 } 77 78 // log creates an contextual log with the given message and the context pulled 79 // from the internally maintained statistics. 80 func (gs *generatorStats) log(level log.Lvl, msg string, root common.Hash, marker []byte) { 81 var ctx []interface{} 82 if root != (common.Hash{}) { 83 ctx = append(ctx, []interface{}{"root", root}...) 84 } 85 // Figure out whether we're after or within an account 86 switch len(marker) { 87 case common.HashLength: 88 ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...) 89 case 2 * common.HashLength: 90 ctx = append(ctx, []interface{}{ 91 "in", common.BytesToHash(marker[:common.HashLength]), 92 "at", common.BytesToHash(marker[common.HashLength:]), 93 }...) 94 } 95 // Add the usual measurements 96 ctx = append(ctx, []interface{}{ 97 "accounts", gs.accounts, 98 "slots", gs.slots, 99 "storage", gs.storage, 100 "elapsed", common.PrettyDuration(time.Since(gs.start)), 101 }...) 102 // Calculate the estimated indexing time based on current stats 103 if len(marker) > 0 { 104 if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 { 105 left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8]) 106 107 speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero 108 ctx = append(ctx, []interface{}{ 109 "eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond), 110 }...) 111 } 112 } 113 114 switch level { 115 case log.LvlTrace: 116 log.Trace(msg, ctx...) 117 case log.LvlDebug: 118 log.Debug(msg, ctx...) 119 case log.LvlInfo: 120 log.Info(msg, ctx...) 121 case log.LvlWarn: 122 log.Warn(msg, ctx...) 123 case log.LvlError: 124 log.Error(msg, ctx...) 125 case log.LvlCrit: 126 log.Crit(msg, ctx...) 127 default: 128 log.Error(fmt.Sprintf("log with invalid log level %s: %s", level, msg), ctx...) 129 } 130 } 131 132 // generateSnapshot regenerates a brand new snapshot based on an existing state 133 // database and head block asynchronously. The snapshot is returned immediately 134 // and generation is continued in the background until done. 135 func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, blockHash, root common.Hash, wiper chan struct{}) *diskLayer { 136 // Wipe any previously existing snapshot from the database if no wiper is 137 // currently in progress. 138 if wiper == nil { 139 wiper = WipeSnapshot(diskdb, true) 140 } 141 // Create a new disk layer with an initialized state marker at zero 142 var ( 143 stats = &generatorStats{wiping: wiper, start: time.Now()} 144 batch = diskdb.NewBatch() 145 genMarker = []byte{} // Initialized but empty! 146 ) 147 rawdb.WriteSnapshotBlockHash(batch, blockHash) 148 rawdb.WriteSnapshotRoot(batch, root) 149 journalProgress(batch, genMarker, stats) 150 if err := batch.Write(); err != nil { 151 log.Crit("Failed to write initialized state marker", "err", err) 152 } 153 base := &diskLayer{ 154 diskdb: diskdb, 155 triedb: triedb, 156 blockHash: blockHash, 157 root: root, 158 cache: fastcache.New(cache * 1024 * 1024), 159 genMarker: genMarker, 160 genPending: make(chan struct{}), 161 genAbort: make(chan chan struct{}), 162 created: time.Now(), 163 } 164 go base.generate(stats) 165 log.Debug("Start snapshot generation", "root", root) 166 return base 167 } 168 169 // journalProgress persists the generator stats into the database to resume later. 170 func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) { 171 // Write out the generator marker. Note it's a standalone disk layer generator 172 // which is not mixed with journal. It's ok if the generator is persisted while 173 // journal is not. 174 entry := journalGenerator{ 175 Done: marker == nil, 176 Marker: marker, 177 } 178 if stats != nil { 179 entry.Wiping = (stats.wiping != nil) 180 entry.Accounts = stats.accounts 181 entry.Slots = stats.slots 182 entry.Storage = uint64(stats.storage) 183 } 184 blob, err := rlp.EncodeToBytes(entry) 185 if err != nil { 186 panic(err) // Cannot happen, here to catch dev errors 187 } 188 var logstr string 189 switch { 190 case marker == nil: 191 logstr = "done" 192 case bytes.Equal(marker, []byte{}): 193 logstr = "empty" 194 case len(marker) == common.HashLength: 195 logstr = fmt.Sprintf("%#x", marker) 196 default: 197 logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:]) 198 } 199 log.Debug("Journalled generator progress", "progress", logstr) 200 rawdb.WriteSnapshotGenerator(db, blob) 201 } 202 203 // checkAndFlush checks to see if snapshot generation has been aborted or if 204 // the current batch size is greater than ethdb.IdealBatchSize. If so, it saves 205 // the current progress to disk and returns true. Else, it could log current 206 // progress and returns true. 207 func (dl *diskLayer) checkAndFlush(batch ethdb.Batch, stats *generatorStats, currentLocation []byte) bool { 208 // If we've exceeded our batch allowance or termination was requested, flush to disk 209 var abort chan struct{} 210 select { 211 case abort = <-dl.genAbort: 212 default: 213 } 214 if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { 215 if bytes.Compare(currentLocation, dl.genMarker) < 0 { 216 log.Error("Snapshot generator went backwards", 217 "currentLocation", fmt.Sprintf("%x", currentLocation), 218 "genMarker", fmt.Sprintf("%x", dl.genMarker)) 219 } 220 // Flush out the batch anyway no matter it's empty or not. 221 // It's possible that all the states are recovered and the 222 // generation indeed makes progress. 223 journalProgress(batch, currentLocation, stats) 224 225 if err := batch.Write(); err != nil { 226 log.Error("Failed to flush batch", "err", err) 227 if abort == nil { 228 abort = <-dl.genAbort 229 } 230 dl.genStats = stats 231 close(abort) 232 return true 233 } 234 batch.Reset() 235 236 dl.lock.Lock() 237 dl.genMarker = currentLocation 238 dl.lock.Unlock() 239 240 if abort != nil { 241 stats.Debug("Aborting state snapshot generation", dl.root, currentLocation) 242 dl.genStats = stats 243 close(abort) 244 return true 245 } 246 } 247 if time.Since(dl.logged) > 8*time.Second { 248 stats.Info("Generating state snapshot", dl.root, currentLocation) 249 dl.logged = time.Now() 250 } 251 return false 252 } 253 254 // generate is a background thread that iterates over the state and storage tries, 255 // constructing the state snapshot. All the arguments are purely for statistics 256 // gathering and logging, since the method surfs the blocks as they arrive, often 257 // being restarted. 258 func (dl *diskLayer) generate(stats *generatorStats) { 259 // If a database wipe is in operation, wait until it's done 260 if stats.wiping != nil { 261 stats.Info("Wiper running, state snapshotting paused", common.Hash{}, dl.genMarker) 262 select { 263 // If wiper is done, resume normal mode of operation 264 case <-stats.wiping: 265 stats.wiping = nil 266 stats.start = time.Now() 267 268 // If generator was aborted during wipe, return 269 case abort := <-dl.genAbort: 270 stats.Debug("Aborting state snapshot generation", dl.root, dl.genMarker) 271 dl.genStats = stats 272 close(abort) 273 return 274 } 275 } 276 // Create an account and state iterator pointing to the current generator marker 277 accTrie, err := trie.NewStateTrie(common.Hash{}, dl.root, dl.triedb) 278 if err != nil { 279 // The account trie is missing (GC), surf the chain until one becomes available 280 stats.Info("Trie missing, state snapshotting paused", dl.root, dl.genMarker) 281 abort := <-dl.genAbort 282 dl.genStats = stats 283 close(abort) 284 return 285 } 286 stats.Debug("Resuming state snapshot generation", dl.root, dl.genMarker) 287 288 var accMarker []byte 289 if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that 290 accMarker = dl.genMarker[:common.HashLength] 291 } 292 accIt := trie.NewIterator(accTrie.NodeIterator(accMarker)) 293 batch := dl.diskdb.NewBatch() 294 295 // Iterate from the previous marker and continue generating the state snapshot 296 dl.logged = time.Now() 297 for accIt.Next() { 298 // Retrieve the current account and flatten it into the internal format 299 accountHash := common.BytesToHash(accIt.Key) 300 301 var acc struct { 302 Nonce uint64 303 Balance *big.Int 304 Root common.Hash 305 CodeHash []byte 306 IsMultiCoin bool 307 } 308 if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { 309 log.Crit("Invalid account encountered during snapshot creation", "err", err) 310 } 311 data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash, acc.IsMultiCoin) 312 313 // If the account is not yet in-progress, write it out 314 if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) { 315 rawdb.WriteAccountSnapshot(batch, accountHash, data) 316 stats.storage += common.StorageSize(1 + common.HashLength + len(data)) 317 stats.accounts++ 318 } 319 marker := accountHash[:] 320 // If the snap generation goes here after interrupted, genMarker may go backward 321 // when last genMarker is consisted of accountHash and storageHash 322 if accMarker != nil && bytes.Equal(marker, accMarker) && len(dl.genMarker) > common.HashLength { 323 marker = dl.genMarker[:] 324 } 325 if dl.checkAndFlush(batch, stats, marker) { 326 // checkAndFlush handles abort 327 return 328 } 329 // If the iterated account is a contract, iterate through corresponding contract 330 // storage to generate snapshot entries. 331 if acc.Root != emptyRoot { 332 storeTrie, err := trie.NewStateTrie(accountHash, acc.Root, dl.triedb) 333 if err != nil { 334 log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err) 335 abort := <-dl.genAbort 336 dl.genStats = stats 337 close(abort) 338 return 339 } 340 var storeMarker []byte 341 if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength { 342 storeMarker = dl.genMarker[common.HashLength:] 343 } 344 storeIt := trie.NewIterator(storeTrie.NodeIterator(storeMarker)) 345 for storeIt.Next() { 346 rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value) 347 stats.storage += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value)) 348 stats.slots++ 349 350 if dl.checkAndFlush(batch, stats, append(accountHash[:], storeIt.Key...)) { 351 // checkAndFlush handles abort 352 return 353 } 354 } 355 if err := storeIt.Err; err != nil { 356 log.Error("Generator failed to iterate storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) 357 abort := <-dl.genAbort 358 dl.genStats = stats 359 close(abort) 360 return 361 } 362 } 363 if time.Since(dl.logged) > 8*time.Second { 364 stats.Info("Generating state snapshot", dl.root, accIt.Key) 365 dl.logged = time.Now() 366 } 367 // Some account processed, unmark the marker 368 accMarker = nil 369 } 370 if err := accIt.Err; err != nil { 371 log.Error("Generator failed to iterate account trie", "root", dl.root, "err", err) 372 abort := <-dl.genAbort 373 dl.genStats = stats 374 close(abort) 375 return 376 } 377 // Snapshot fully generated, set the marker to nil. 378 // Note even there is nothing to commit, persist the 379 // generator anyway to mark the snapshot is complete. 380 journalProgress(batch, nil, stats) 381 if err := batch.Write(); err != nil { 382 log.Error("Failed to flush batch", "err", err) 383 abort := <-dl.genAbort 384 dl.genStats = stats 385 close(abort) 386 return 387 } 388 389 log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, 390 "storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start))) 391 392 dl.lock.Lock() 393 dl.genMarker = nil 394 dl.genStats = stats 395 close(dl.genPending) 396 dl.lock.Unlock() 397 398 // Someone will be looking for us, wait it out 399 abort := <-dl.genAbort 400 close(abort) 401 }