github.com/snowblossomcoin/go-ethereum@v1.9.25/core/state/snapshot/journal.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 "errors" 23 "fmt" 24 "io" 25 "time" 26 27 "github.com/VictoriaMetrics/fastcache" 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/core/rawdb" 30 "github.com/ethereum/go-ethereum/ethdb" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/rlp" 33 "github.com/ethereum/go-ethereum/trie" 34 ) 35 36 const journalVersion uint64 = 0 37 38 // journalGenerator is a disk layer entry containing the generator progress marker. 39 type journalGenerator struct { 40 Wiping bool // Whether the database was in progress of being wiped 41 Done bool // Whether the generator finished creating the snapshot 42 Marker []byte 43 Accounts uint64 44 Slots uint64 45 Storage uint64 46 } 47 48 // journalDestruct is an account deletion entry in a diffLayer's disk journal. 49 type journalDestruct struct { 50 Hash common.Hash 51 } 52 53 // journalAccount is an account entry in a diffLayer's disk journal. 54 type journalAccount struct { 55 Hash common.Hash 56 Blob []byte 57 } 58 59 // journalStorage is an account's storage map in a diffLayer's disk journal. 60 type journalStorage struct { 61 Hash common.Hash 62 Keys []common.Hash 63 Vals [][]byte 64 } 65 66 // loadAndParseLegacyJournal tries to parse the snapshot journal in legacy format. 67 func loadAndParseLegacyJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { 68 // Retrieve the journal, for legacy journal it must exist since even for 69 // 0 layer it stores whether we've already generated the snapshot or are 70 // in progress only. 71 journal := rawdb.ReadSnapshotJournal(db) 72 if len(journal) == 0 { 73 return nil, journalGenerator{}, errors.New("missing or corrupted snapshot journal") 74 } 75 r := rlp.NewStream(bytes.NewReader(journal), 0) 76 77 // Read the snapshot generation progress for the disk layer 78 var generator journalGenerator 79 if err := r.Decode(&generator); err != nil { 80 return nil, journalGenerator{}, fmt.Errorf("failed to load snapshot progress marker: %v", err) 81 } 82 // Load all the snapshot diffs from the journal 83 snapshot, err := loadDiffLayer(base, r) 84 if err != nil { 85 return nil, generator, err 86 } 87 return snapshot, generator, nil 88 } 89 90 // loadAndParseJournal tries to parse the snapshot journal in latest format. 91 func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { 92 // Retrieve the disk layer generator. It must exist, no matter the 93 // snapshot is fully generated or not. Otherwise the entire disk 94 // layer is invalid. 95 generatorBlob := rawdb.ReadSnapshotGenerator(db) 96 if len(generatorBlob) == 0 { 97 return nil, journalGenerator{}, errors.New("missing snapshot generator") 98 } 99 var generator journalGenerator 100 if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { 101 return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err) 102 } 103 // Retrieve the diff layer journal. It's possible that the journal is 104 // not existent, e.g. the disk layer is generating while that the Geth 105 // crashes without persisting the diff journal. 106 // So if there is no journal, or the journal is not matched with disk 107 // layer, we just discard all diffs and try to recover them later. 108 journal := rawdb.ReadSnapshotJournal(db) 109 if len(journal) == 0 { 110 log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing") 111 return base, generator, nil 112 } 113 r := rlp.NewStream(bytes.NewReader(journal), 0) 114 115 // Firstly, resolve the first element as the journal version 116 version, err := r.Uint() 117 if err != nil { 118 return nil, journalGenerator{}, err 119 } 120 if version != journalVersion { 121 return nil, journalGenerator{}, fmt.Errorf("journal version mismatch, want %d got %v", journalVersion, version) 122 } 123 // Secondly, resolve the disk layer root, ensure it's continuous 124 // with disk layer. 125 var root common.Hash 126 if err := r.Decode(&root); err != nil { 127 return nil, journalGenerator{}, errors.New("missing disk layer root") 128 } 129 // The diff journal is not matched with disk, discard them. 130 // It can happen that Geth crashes without persisting the latest 131 // diff journal. 132 if !bytes.Equal(root.Bytes(), base.root.Bytes()) { 133 log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched") 134 return base, generator, nil 135 } 136 // Load all the snapshot diffs from the journal 137 snapshot, err := loadDiffLayer(base, r) 138 if err != nil { 139 return nil, journalGenerator{}, err 140 } 141 log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root()) 142 return snapshot, generator, nil 143 } 144 145 // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. 146 func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, error) { 147 // Retrieve the block number and hash of the snapshot, failing if no snapshot 148 // is present in the database (or crashed mid-update). 149 baseRoot := rawdb.ReadSnapshotRoot(diskdb) 150 if baseRoot == (common.Hash{}) { 151 return nil, errors.New("missing or corrupted snapshot") 152 } 153 base := &diskLayer{ 154 diskdb: diskdb, 155 triedb: triedb, 156 cache: fastcache.New(cache * 1024 * 1024), 157 root: baseRoot, 158 } 159 var legacy bool 160 snapshot, generator, err := loadAndParseJournal(diskdb, base) 161 if err != nil { 162 log.Debug("Failed to load new-format journal", "error", err) 163 snapshot, generator, err = loadAndParseLegacyJournal(diskdb, base) 164 legacy = true 165 } 166 if err != nil { 167 return nil, err 168 } 169 // Entire snapshot journal loaded, sanity check the head. If the loaded 170 // snapshot is not matched with current state root, print a warning log 171 // or discard the entire snapshot it's legacy snapshot. 172 // 173 // Possible scenario: Geth was crashed without persisting journal and then 174 // restart, the head is rewound to the point with available state(trie) 175 // which is below the snapshot. In this case the snapshot can be recovered 176 // by re-executing blocks but right now it's unavailable. 177 if head := snapshot.Root(); head != root { 178 // If it's legacy snapshot, or it's new-format snapshot but 179 // it's not in recovery mode, returns the error here for 180 // rebuilding the entire snapshot forcibly. 181 if legacy || !recovery { 182 return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) 183 } 184 // It's in snapshot recovery, the assumption is held that 185 // the disk layer is always higher than chain head. It can 186 // be eventually recovered when the chain head beyonds the 187 // disk layer. 188 log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root) 189 } 190 // Everything loaded correctly, resume any suspended operations 191 if !generator.Done { 192 // If the generator was still wiping, restart one from scratch (fine for 193 // now as it's rare and the wiper deletes the stuff it touches anyway, so 194 // restarting won't incur a lot of extra database hops. 195 var wiper chan struct{} 196 if generator.Wiping { 197 log.Info("Resuming previous snapshot wipe") 198 wiper = wipeSnapshot(diskdb, false) 199 } 200 // Whether or not wiping was in progress, load any generator progress too 201 base.genMarker = generator.Marker 202 if base.genMarker == nil { 203 base.genMarker = []byte{} 204 } 205 base.genPending = make(chan struct{}) 206 base.genAbort = make(chan chan *generatorStats) 207 208 var origin uint64 209 if len(generator.Marker) >= 8 { 210 origin = binary.BigEndian.Uint64(generator.Marker) 211 } 212 go base.generate(&generatorStats{ 213 wiping: wiper, 214 origin: origin, 215 start: time.Now(), 216 accounts: generator.Accounts, 217 slots: generator.Slots, 218 storage: common.StorageSize(generator.Storage), 219 }) 220 } 221 return snapshot, nil 222 } 223 224 // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new 225 // diff and verifying that it can be linked to the requested parent. 226 func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { 227 // Read the next diff journal entry 228 var root common.Hash 229 if err := r.Decode(&root); err != nil { 230 // The first read may fail with EOF, marking the end of the journal 231 if err == io.EOF { 232 return parent, nil 233 } 234 return nil, fmt.Errorf("load diff root: %v", err) 235 } 236 var destructs []journalDestruct 237 if err := r.Decode(&destructs); err != nil { 238 return nil, fmt.Errorf("load diff destructs: %v", err) 239 } 240 destructSet := make(map[common.Hash]struct{}) 241 for _, entry := range destructs { 242 destructSet[entry.Hash] = struct{}{} 243 } 244 var accounts []journalAccount 245 if err := r.Decode(&accounts); err != nil { 246 return nil, fmt.Errorf("load diff accounts: %v", err) 247 } 248 accountData := make(map[common.Hash][]byte) 249 for _, entry := range accounts { 250 if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 251 accountData[entry.Hash] = entry.Blob 252 } else { 253 accountData[entry.Hash] = nil 254 } 255 } 256 var storage []journalStorage 257 if err := r.Decode(&storage); err != nil { 258 return nil, fmt.Errorf("load diff storage: %v", err) 259 } 260 storageData := make(map[common.Hash]map[common.Hash][]byte) 261 for _, entry := range storage { 262 slots := make(map[common.Hash][]byte) 263 for i, key := range entry.Keys { 264 if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 265 slots[key] = entry.Vals[i] 266 } else { 267 slots[key] = nil 268 } 269 } 270 storageData[entry.Hash] = slots 271 } 272 return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r) 273 } 274 275 // Journal writes the persistent layer generator stats into a buffer to be stored 276 // in the database as the snapshot journal. 277 func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 278 // If the snapshot is currently being generated, abort it 279 var stats *generatorStats 280 if dl.genAbort != nil { 281 abort := make(chan *generatorStats) 282 dl.genAbort <- abort 283 284 if stats = <-abort; stats != nil { 285 stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) 286 } 287 } 288 // Ensure the layer didn't get stale 289 dl.lock.RLock() 290 defer dl.lock.RUnlock() 291 292 if dl.stale { 293 return common.Hash{}, ErrSnapshotStale 294 } 295 // Write out the generator marker. Note it's a standalone disk layer generator 296 // which is not mixed with journal. It's ok if the generator is persisted while 297 // journal is not. 298 entry := journalGenerator{ 299 Done: dl.genMarker == nil, 300 Marker: dl.genMarker, 301 } 302 if stats != nil { 303 entry.Wiping = (stats.wiping != nil) 304 entry.Accounts = stats.accounts 305 entry.Slots = stats.slots 306 entry.Storage = uint64(stats.storage) 307 } 308 blob, err := rlp.EncodeToBytes(entry) 309 if err != nil { 310 return common.Hash{}, err 311 } 312 log.Debug("Journalled disk layer", "root", dl.root, "complete", dl.genMarker == nil) 313 rawdb.WriteSnapshotGenerator(dl.diskdb, blob) 314 return dl.root, nil 315 } 316 317 // Journal writes the memory layer contents into a buffer to be stored in the 318 // database as the snapshot journal. 319 func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 320 // Journal the parent first 321 base, err := dl.parent.Journal(buffer) 322 if err != nil { 323 return common.Hash{}, err 324 } 325 // Ensure the layer didn't get stale 326 dl.lock.RLock() 327 defer dl.lock.RUnlock() 328 329 if dl.Stale() { 330 return common.Hash{}, ErrSnapshotStale 331 } 332 // Everything below was journalled, persist this layer too 333 if err := rlp.Encode(buffer, dl.root); err != nil { 334 return common.Hash{}, err 335 } 336 destructs := make([]journalDestruct, 0, len(dl.destructSet)) 337 for hash := range dl.destructSet { 338 destructs = append(destructs, journalDestruct{Hash: hash}) 339 } 340 if err := rlp.Encode(buffer, destructs); err != nil { 341 return common.Hash{}, err 342 } 343 accounts := make([]journalAccount, 0, len(dl.accountData)) 344 for hash, blob := range dl.accountData { 345 accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) 346 } 347 if err := rlp.Encode(buffer, accounts); err != nil { 348 return common.Hash{}, err 349 } 350 storage := make([]journalStorage, 0, len(dl.storageData)) 351 for hash, slots := range dl.storageData { 352 keys := make([]common.Hash, 0, len(slots)) 353 vals := make([][]byte, 0, len(slots)) 354 for key, val := range slots { 355 keys = append(keys, key) 356 vals = append(vals, val) 357 } 358 storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) 359 } 360 if err := rlp.Encode(buffer, storage); err != nil { 361 return common.Hash{}, err 362 } 363 log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) 364 return base, nil 365 } 366 367 // LegacyJournal writes the persistent layer generator stats into a buffer 368 // to be stored in the database as the snapshot journal. 369 // 370 // Note it's the legacy version which is only used in testing right now. 371 func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { 372 // If the snapshot is currently being generated, abort it 373 var stats *generatorStats 374 if dl.genAbort != nil { 375 abort := make(chan *generatorStats) 376 dl.genAbort <- abort 377 378 if stats = <-abort; stats != nil { 379 stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) 380 } 381 } 382 // Ensure the layer didn't get stale 383 dl.lock.RLock() 384 defer dl.lock.RUnlock() 385 386 if dl.stale { 387 return common.Hash{}, ErrSnapshotStale 388 } 389 // Write out the generator marker 390 entry := journalGenerator{ 391 Done: dl.genMarker == nil, 392 Marker: dl.genMarker, 393 } 394 if stats != nil { 395 entry.Wiping = (stats.wiping != nil) 396 entry.Accounts = stats.accounts 397 entry.Slots = stats.slots 398 entry.Storage = uint64(stats.storage) 399 } 400 if err := rlp.Encode(buffer, entry); err != nil { 401 return common.Hash{}, err 402 } 403 return dl.root, nil 404 } 405 406 // Journal writes the memory layer contents into a buffer to be stored in the 407 // database as the snapshot journal. 408 // 409 // Note it's the legacy version which is only used in testing right now. 410 func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { 411 // Journal the parent first 412 base, err := dl.parent.LegacyJournal(buffer) 413 if err != nil { 414 return common.Hash{}, err 415 } 416 // Ensure the layer didn't get stale 417 dl.lock.RLock() 418 defer dl.lock.RUnlock() 419 420 if dl.Stale() { 421 return common.Hash{}, ErrSnapshotStale 422 } 423 // Everything below was journalled, persist this layer too 424 if err := rlp.Encode(buffer, dl.root); err != nil { 425 return common.Hash{}, err 426 } 427 destructs := make([]journalDestruct, 0, len(dl.destructSet)) 428 for hash := range dl.destructSet { 429 destructs = append(destructs, journalDestruct{Hash: hash}) 430 } 431 if err := rlp.Encode(buffer, destructs); err != nil { 432 return common.Hash{}, err 433 } 434 accounts := make([]journalAccount, 0, len(dl.accountData)) 435 for hash, blob := range dl.accountData { 436 accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) 437 } 438 if err := rlp.Encode(buffer, accounts); err != nil { 439 return common.Hash{}, err 440 } 441 storage := make([]journalStorage, 0, len(dl.storageData)) 442 for hash, slots := range dl.storageData { 443 keys := make([]common.Hash, 0, len(slots)) 444 vals := make([][]byte, 0, len(slots)) 445 for key, val := range slots { 446 keys = append(keys, key) 447 vals = append(vals, val) 448 } 449 storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) 450 } 451 if err := rlp.Encode(buffer, storage); err != nil { 452 return common.Hash{}, err 453 } 454 log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) 455 return base, nil 456 }