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