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