github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/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 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 Geth 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 ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, bool, error) { 153 // If snapshotting is disabled (initial sync in progress), don't do anything, 154 // wait for the chain to permit us to do something meaningful 155 if rawdb.ReadSnapshotDisabled(diskdb) { 156 return nil, true, nil 157 } 158 // Retrieve the block number and hash of the snapshot, failing if no snapshot 159 // is present in the database (or crashed mid-update). 160 baseRoot := rawdb.ReadSnapshotRoot(diskdb) 161 if baseRoot == (common.Hash{}) { 162 return nil, false, errors.New("missing or corrupted snapshot") 163 } 164 base := &diskLayer{ 165 diskdb: diskdb, 166 triedb: triedb, 167 cache: fastcache.New(cache * 1024 * 1024), 168 root: baseRoot, 169 } 170 snapshot, generator, err := loadAndParseJournal(diskdb, base) 171 if err != nil { 172 log.Warn("Failed to load new-format journal", "error", err) 173 return nil, false, 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: Geth 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 !recovery { 188 return nil, false, 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 // Whether or not wiping was in progress, load any generator progress too 199 base.genMarker = generator.Marker 200 if base.genMarker == nil { 201 base.genMarker = []byte{} 202 } 203 base.genPending = make(chan struct{}) 204 base.genAbort = make(chan chan *generatorStats) 205 206 var origin uint64 207 if len(generator.Marker) >= 8 { 208 origin = binary.BigEndian.Uint64(generator.Marker) 209 } 210 go base.generate(&generatorStats{ 211 origin: origin, 212 start: time.Now(), 213 accounts: generator.Accounts, 214 slots: generator.Slots, 215 storage: common.StorageSize(generator.Storage), 216 }) 217 } 218 return snapshot, false, nil 219 } 220 221 // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new 222 // diff and verifying that it can be linked to the requested parent. 223 func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { 224 // Read the next diff journal entry 225 var root common.Hash 226 if err := r.Decode(&root); err != nil { 227 // The first read may fail with EOF, marking the end of the journal 228 if err == io.EOF { 229 return parent, nil 230 } 231 return nil, fmt.Errorf("load diff root: %v", err) 232 } 233 var destructs []journalDestruct 234 if err := r.Decode(&destructs); err != nil { 235 return nil, fmt.Errorf("load diff destructs: %v", err) 236 } 237 destructSet := make(map[common.Hash]struct{}) 238 for _, entry := range destructs { 239 destructSet[entry.Hash] = struct{}{} 240 } 241 var accounts []journalAccount 242 if err := r.Decode(&accounts); err != nil { 243 return nil, fmt.Errorf("load diff accounts: %v", err) 244 } 245 accountData := make(map[common.Hash][]byte) 246 for _, entry := range accounts { 247 if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 248 accountData[entry.Hash] = entry.Blob 249 } else { 250 accountData[entry.Hash] = nil 251 } 252 } 253 var storage []journalStorage 254 if err := r.Decode(&storage); err != nil { 255 return nil, fmt.Errorf("load diff storage: %v", err) 256 } 257 storageData := make(map[common.Hash]map[common.Hash][]byte) 258 for _, entry := range storage { 259 slots := make(map[common.Hash][]byte) 260 for i, key := range entry.Keys { 261 if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 262 slots[key] = entry.Vals[i] 263 } else { 264 slots[key] = nil 265 } 266 } 267 storageData[entry.Hash] = slots 268 } 269 return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r) 270 } 271 272 // Journal terminates any in-progress snapshot generation, also implicitly pushing 273 // the progress into the database. 274 func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 275 // If the snapshot is currently being generated, abort it 276 var stats *generatorStats 277 if dl.genAbort != nil { 278 abort := make(chan *generatorStats) 279 dl.genAbort <- abort 280 281 if stats = <-abort; stats != nil { 282 stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) 283 } 284 } 285 // Ensure the layer didn't get stale 286 dl.lock.RLock() 287 defer dl.lock.RUnlock() 288 289 if dl.stale { 290 return common.Hash{}, ErrSnapshotStale 291 } 292 // Ensure the generator stats is written even if none was ran this cycle 293 journalProgress(dl.diskdb, dl.genMarker, stats) 294 295 log.Debug("Journalled disk layer", "root", dl.root) 296 return dl.root, nil 297 } 298 299 // Journal writes the memory layer contents into a buffer to be stored in the 300 // database as the snapshot journal. 301 func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 302 // Journal the parent first 303 base, err := dl.parent.Journal(buffer) 304 if err != nil { 305 return common.Hash{}, err 306 } 307 // Ensure the layer didn't get stale 308 dl.lock.RLock() 309 defer dl.lock.RUnlock() 310 311 if dl.Stale() { 312 return common.Hash{}, ErrSnapshotStale 313 } 314 // Everything below was journalled, persist this layer too 315 if err := rlp.Encode(buffer, dl.root); err != nil { 316 return common.Hash{}, err 317 } 318 destructs := make([]journalDestruct, 0, len(dl.destructSet)) 319 for hash := range dl.destructSet { 320 destructs = append(destructs, journalDestruct{Hash: hash}) 321 } 322 if err := rlp.Encode(buffer, destructs); err != nil { 323 return common.Hash{}, err 324 } 325 accounts := make([]journalAccount, 0, len(dl.accountData)) 326 for hash, blob := range dl.accountData { 327 accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) 328 } 329 if err := rlp.Encode(buffer, accounts); err != nil { 330 return common.Hash{}, err 331 } 332 storage := make([]journalStorage, 0, len(dl.storageData)) 333 for hash, slots := range dl.storageData { 334 keys := make([]common.Hash, 0, len(slots)) 335 vals := make([][]byte, 0, len(slots)) 336 for key, val := range slots { 337 keys = append(keys, key) 338 vals = append(vals, val) 339 } 340 storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) 341 } 342 if err := rlp.Encode(buffer, storage); err != nil { 343 return common.Hash{}, err 344 } 345 log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) 346 return base, nil 347 }