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