github.com/ethereum/go-ethereum@v1.16.1/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/triedb" 34 ) 35 36 const ( 37 journalV0 uint64 = 0 // initial version 38 journalV1 uint64 = 1 // current version, with destruct flag (in diff layers) removed 39 journalCurrentVersion = journalV1 40 ) 41 42 // journalGenerator is a disk layer entry containing the generator progress marker. 43 type journalGenerator struct { 44 // Indicator that whether the database was in progress of being wiped. 45 // It's deprecated but keep it here for background compatibility. 46 Wiping bool 47 48 Done bool // Whether the generator finished creating the snapshot 49 Marker []byte 50 Accounts uint64 51 Slots uint64 52 Storage uint64 53 } 54 55 // journalDestruct is an account deletion entry in a diffLayer's disk journal. 56 type journalDestruct struct { 57 Hash common.Hash 58 } 59 60 // journalAccount is an account entry in a diffLayer's disk journal. 61 type journalAccount struct { 62 Hash common.Hash 63 Blob []byte 64 } 65 66 // journalStorage is an account's storage map in a diffLayer's disk journal. 67 type journalStorage struct { 68 Hash common.Hash 69 Keys []common.Hash 70 Vals [][]byte 71 } 72 73 func ParseGeneratorStatus(generatorBlob []byte) string { 74 if len(generatorBlob) == 0 { 75 return "" 76 } 77 var generator journalGenerator 78 if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { 79 log.Warn("failed to decode snapshot generator", "err", err) 80 return "" 81 } 82 // Figure out whether we're after or within an account 83 var m string 84 switch marker := generator.Marker; len(marker) { 85 case common.HashLength: 86 m = fmt.Sprintf("at %#x", marker) 87 case 2 * common.HashLength: 88 m = fmt.Sprintf("in %#x at %#x", marker[:common.HashLength], marker[common.HashLength:]) 89 default: 90 m = fmt.Sprintf("%#x", marker) 91 } 92 return fmt.Sprintf(`Done: %v, Accounts: %d, Slots: %d, Storage: %d, Marker: %s`, 93 generator.Done, generator.Accounts, generator.Slots, generator.Storage, m) 94 } 95 96 // loadAndParseJournal tries to parse the snapshot journal in latest format. 97 func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { 98 // Retrieve the disk layer generator. It must exist, no matter the 99 // snapshot is fully generated or not. Otherwise the entire disk 100 // layer is invalid. 101 generatorBlob := rawdb.ReadSnapshotGenerator(db) 102 if len(generatorBlob) == 0 { 103 return nil, journalGenerator{}, errors.New("missing snapshot generator") 104 } 105 var generator journalGenerator 106 if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { 107 return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err) 108 } 109 // Retrieve the diff layer journal. It's possible that the journal is 110 // not existent, e.g. the disk layer is generating while that the Geth 111 // crashes without persisting the diff journal. 112 // So if there is no journal, or the journal is invalid(e.g. the journal 113 // is not matched with disk layer; or the it's the legacy-format journal, 114 // etc.), we just discard all diffs and try to recover them later. 115 var current snapshot = base 116 err := iterateJournal(db, func(parent common.Hash, root common.Hash, accountData map[common.Hash][]byte, storageData map[common.Hash]map[common.Hash][]byte) error { 117 current = newDiffLayer(current, root, accountData, storageData) 118 return nil 119 }) 120 if err != nil { 121 return base, generator, nil 122 } 123 return current, generator, nil 124 } 125 126 // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. 127 func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *triedb.Database, root common.Hash, cache int, recovery bool, noBuild bool) (snapshot, bool, error) { 128 // If snapshotting is disabled (initial sync in progress), don't do anything, 129 // wait for the chain to permit us to do something meaningful 130 if rawdb.ReadSnapshotDisabled(diskdb) { 131 return nil, true, nil 132 } 133 // Retrieve the block number and hash of the snapshot, failing if no snapshot 134 // is present in the database (or crashed mid-update). 135 baseRoot := rawdb.ReadSnapshotRoot(diskdb) 136 if baseRoot == (common.Hash{}) { 137 return nil, false, errors.New("missing or corrupted snapshot") 138 } 139 base := &diskLayer{ 140 diskdb: diskdb, 141 triedb: triedb, 142 cache: fastcache.New(cache * 1024 * 1024), 143 root: baseRoot, 144 } 145 snapshot, generator, err := loadAndParseJournal(diskdb, base) 146 if err != nil { 147 log.Warn("Failed to load journal", "error", err) 148 return nil, false, err 149 } 150 // Entire snapshot journal loaded, sanity check the head. If the loaded 151 // snapshot is not matched with current state root, print a warning log 152 // or discard the entire snapshot it's legacy snapshot. 153 // 154 // Possible scenario: Geth was crashed without persisting journal and then 155 // restart, the head is rewound to the point with available state(trie) 156 // which is below the snapshot. In this case the snapshot can be recovered 157 // by re-executing blocks but right now it's unavailable. 158 if head := snapshot.Root(); head != root { 159 // If it's legacy snapshot, or it's new-format snapshot but 160 // it's not in recovery mode, returns the error here for 161 // rebuilding the entire snapshot forcibly. 162 if !recovery { 163 return nil, false, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) 164 } 165 // It's in snapshot recovery, the assumption is held that 166 // the disk layer is always higher than chain head. It can 167 // be eventually recovered when the chain head beyonds the 168 // disk layer. 169 log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root) 170 } 171 // Load the disk layer status from the generator if it's not complete 172 if !generator.Done { 173 base.genMarker = generator.Marker 174 if base.genMarker == nil { 175 base.genMarker = []byte{} 176 } 177 } 178 // Everything loaded correctly, resume any suspended operations 179 // if the background generation is allowed 180 if !generator.Done && !noBuild { 181 base.genPending = make(chan struct{}) 182 base.genAbort = make(chan chan *generatorStats) 183 184 var origin uint64 185 if len(generator.Marker) >= 8 { 186 origin = binary.BigEndian.Uint64(generator.Marker) 187 } 188 go base.generate(&generatorStats{ 189 origin: origin, 190 start: time.Now(), 191 accounts: generator.Accounts, 192 slots: generator.Slots, 193 storage: common.StorageSize(generator.Storage), 194 }) 195 } 196 return snapshot, false, nil 197 } 198 199 // Journal terminates any in-progress snapshot generation, also implicitly pushing 200 // the progress into the database. 201 func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 202 // If the snapshot is currently being generated, abort it 203 var stats *generatorStats 204 if dl.genAbort != nil { 205 abort := make(chan *generatorStats) 206 dl.genAbort <- abort 207 208 if stats = <-abort; stats != nil { 209 stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) 210 } 211 } 212 // Ensure the layer didn't get stale 213 dl.lock.RLock() 214 defer dl.lock.RUnlock() 215 216 if dl.stale { 217 return common.Hash{}, ErrSnapshotStale 218 } 219 // Ensure the generator stats is written even if none was ran this cycle 220 journalProgress(dl.diskdb, dl.genMarker, stats) 221 222 log.Debug("Journalled disk layer", "root", dl.root) 223 return dl.root, nil 224 } 225 226 // Journal writes the memory layer contents into a buffer to be stored in the 227 // database as the snapshot journal. 228 func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 229 // Journal the parent first 230 base, err := dl.parent.Journal(buffer) 231 if err != nil { 232 return common.Hash{}, err 233 } 234 // Ensure the layer didn't get stale 235 dl.lock.RLock() 236 defer dl.lock.RUnlock() 237 238 if dl.Stale() { 239 return common.Hash{}, ErrSnapshotStale 240 } 241 // Everything below was journalled, persist this layer too 242 if err := rlp.Encode(buffer, dl.root); err != nil { 243 return common.Hash{}, err 244 } 245 accounts := make([]journalAccount, 0, len(dl.accountData)) 246 for hash, blob := range dl.accountData { 247 accounts = append(accounts, journalAccount{ 248 Hash: hash, 249 Blob: blob, 250 }) 251 } 252 if err := rlp.Encode(buffer, accounts); err != nil { 253 return common.Hash{}, err 254 } 255 storage := make([]journalStorage, 0, len(dl.storageData)) 256 for hash, slots := range dl.storageData { 257 keys := make([]common.Hash, 0, len(slots)) 258 vals := make([][]byte, 0, len(slots)) 259 for key, val := range slots { 260 keys = append(keys, key) 261 vals = append(vals, val) 262 } 263 storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) 264 } 265 if err := rlp.Encode(buffer, storage); err != nil { 266 return common.Hash{}, err 267 } 268 log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) 269 return base, nil 270 } 271 272 // journalCallback is a function which is invoked by iterateJournal, every 273 // time a difflayer is loaded from disk. 274 type journalCallback = func(parent common.Hash, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error 275 276 // iterateJournal iterates through the journalled difflayers, loading them from 277 // the database, and invoking the callback for each loaded layer. 278 // The order is incremental; starting with the bottom-most difflayer, going towards 279 // the most recent layer. 280 // This method returns error either if there was some error reading from disk, 281 // OR if the callback returns an error when invoked. 282 func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error { 283 journal := rawdb.ReadSnapshotJournal(db) 284 if len(journal) == 0 { 285 log.Warn("Loaded snapshot journal", "diffs", "missing") 286 return nil 287 } 288 r := rlp.NewStream(bytes.NewReader(journal), 0) 289 // Firstly, resolve the first element as the journal version 290 version, err := r.Uint64() 291 if err != nil { 292 log.Warn("Failed to resolve the journal version", "error", err) 293 return errors.New("failed to resolve journal version") 294 } 295 if version != journalV0 && version != journalCurrentVersion { 296 log.Warn("Discarded journal with wrong version", "required", journalCurrentVersion, "got", version) 297 return errors.New("wrong journal version") 298 } 299 // Secondly, resolve the disk layer root, ensure it's continuous 300 // with disk layer. Note now we can ensure it's the snapshot journal 301 // correct version, so we expect everything can be resolved properly. 302 var parent common.Hash 303 if err := r.Decode(&parent); err != nil { 304 return errors.New("missing disk layer root") 305 } 306 if baseRoot := rawdb.ReadSnapshotRoot(db); baseRoot != parent { 307 log.Warn("Loaded snapshot journal", "diskroot", baseRoot, "diffs", "unmatched") 308 return errors.New("mismatched disk and diff layers") 309 } 310 for { 311 var ( 312 root common.Hash 313 accounts []journalAccount 314 storage []journalStorage 315 accountData = make(map[common.Hash][]byte) 316 storageData = make(map[common.Hash]map[common.Hash][]byte) 317 ) 318 // Read the next diff journal entry 319 if err := r.Decode(&root); err != nil { 320 // The first read may fail with EOF, marking the end of the journal 321 if errors.Is(err, io.EOF) { 322 return nil 323 } 324 return fmt.Errorf("load diff root: %v", err) 325 } 326 // If a legacy journal is detected, decode the destruct set from the stream. 327 // The destruct set has been deprecated. If the journal contains non-empty 328 // destruct set, then it is deemed incompatible. 329 // 330 // Since self-destruction has been deprecated following the cancun fork, 331 // the destruct set is expected to be nil for layers above the fork block. 332 // However, an exception occurs during contract deployment: pre-funded accounts 333 // may self-destruct, causing accounts with non-zero balances to be removed 334 // from the state. For example, 335 // https://etherscan.io/tx/0xa087333d83f0cd63b96bdafb686462e1622ce25f40bd499e03efb1051f31fe49). 336 // 337 // For nodes with a fully synced state, the legacy journal is likely compatible 338 // with the updated definition, eliminating the need for regeneration. Unfortunately, 339 // nodes performing a full sync of historical chain segments or encountering 340 // pre-funded account deletions may face incompatibilities, leading to automatic 341 // snapshot regeneration. 342 // 343 // This approach minimizes snapshot regeneration for Geth nodes upgrading from a 344 // legacy version that are already synced. The workaround can be safely removed 345 // after the next hard fork. 346 if version == journalV0 { 347 var destructs []journalDestruct 348 if err := r.Decode(&destructs); err != nil { 349 return fmt.Errorf("load diff destructs: %v", err) 350 } 351 if len(destructs) > 0 { 352 log.Warn("Incompatible legacy journal detected", "version", journalV0) 353 return fmt.Errorf("incompatible legacy journal detected") 354 } 355 } 356 if err := r.Decode(&accounts); err != nil { 357 return fmt.Errorf("load diff accounts: %v", err) 358 } 359 if err := r.Decode(&storage); err != nil { 360 return fmt.Errorf("load diff storage: %v", err) 361 } 362 for _, entry := range accounts { 363 if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 364 accountData[entry.Hash] = entry.Blob 365 } else { 366 accountData[entry.Hash] = nil 367 } 368 } 369 for _, entry := range storage { 370 slots := make(map[common.Hash][]byte) 371 for i, key := range entry.Keys { 372 if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 373 slots[key] = entry.Vals[i] 374 } else { 375 slots[key] = nil 376 } 377 } 378 storageData[entry.Hash] = slots 379 } 380 if err := callback(parent, root, accountData, storageData); err != nil { 381 return err 382 } 383 parent = root 384 } 385 }