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