github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/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 29 "github.com/unicornultrafoundation/go-u2u/common" 30 "github.com/unicornultrafoundation/go-u2u/core/rawdb" 31 "github.com/unicornultrafoundation/go-u2u/ethdb" 32 "github.com/unicornultrafoundation/go-u2u/log" 33 "github.com/unicornultrafoundation/go-u2u/rlp" 34 "github.com/unicornultrafoundation/go-u2u/trie" 35 ) 36 37 const journalVersion uint64 = 0 38 39 // journalGenerator is a disk layer entry containing the generator progress marker. 40 type journalGenerator struct { 41 // Indicator that whether the database was in progress of being wiped. 42 // It's deprecated but keep it here for background compatibility. 43 Wiping bool 44 45 Done bool // Whether the generator finished creating the snapshot 46 Marker []byte 47 Accounts uint64 48 Slots uint64 49 Storage uint64 50 } 51 52 // journalDestruct is an account deletion entry in a diffLayer's disk journal. 53 type journalDestruct struct { 54 Hash common.Hash 55 } 56 57 // journalAccount is an account entry in a diffLayer's disk journal. 58 type journalAccount struct { 59 Hash common.Hash 60 Blob []byte 61 } 62 63 // journalStorage is an account's storage map in a diffLayer's disk journal. 64 type journalStorage struct { 65 Hash common.Hash 66 Keys []common.Hash 67 Vals [][]byte 68 } 69 70 // loadAndParseJournal tries to parse the snapshot journal in latest format. 71 func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { 72 // Retrieve the disk layer generator. It must exist, no matter the 73 // snapshot is fully generated or not. Otherwise the entire disk 74 // layer is invalid. 75 generatorBlob := rawdb.ReadSnapshotGenerator(db) 76 if len(generatorBlob) == 0 { 77 return nil, journalGenerator{}, errors.New("missing snapshot generator") 78 } 79 var generator journalGenerator 80 if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { 81 return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err) 82 } 83 // Retrieve the diff layer journal. It's possible that the journal is 84 // not existent, e.g. the disk layer is generating while that the Geth 85 // crashes without persisting the diff journal. 86 // So if there is no journal, or the journal is invalid(e.g. the journal 87 // is not matched with disk layer; or the it's the legacy-format journal, 88 // etc.), we just discard all diffs and try to recover them later. 89 journal := rawdb.ReadSnapshotJournal(db) 90 if len(journal) == 0 { 91 log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing") 92 return base, generator, nil 93 } 94 r := rlp.NewStream(bytes.NewReader(journal), 0) 95 96 // Firstly, resolve the first element as the journal version 97 version, err := r.Uint() 98 if err != nil { 99 log.Warn("Failed to resolve the journal version", "error", err) 100 return base, generator, nil 101 } 102 if version != journalVersion { 103 log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version) 104 return base, generator, nil 105 } 106 // Secondly, resolve the disk layer root, ensure it's continuous 107 // with disk layer. Note now we can ensure it's the snapshot journal 108 // correct version, so we expect everything can be resolved properly. 109 var root common.Hash 110 if err := r.Decode(&root); err != nil { 111 return nil, journalGenerator{}, errors.New("missing disk layer root") 112 } 113 // The diff journal is not matched with disk, discard them. 114 // It can happen that Geth crashes without persisting the latest 115 // diff journal. 116 if !bytes.Equal(root.Bytes(), base.root.Bytes()) { 117 log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched") 118 return base, generator, nil 119 } 120 // Load all the snapshot diffs from the journal 121 snapshot, err := loadDiffLayer(base, r) 122 if err != nil { 123 return nil, journalGenerator{}, err 124 } 125 log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root()) 126 return snapshot, generator, nil 127 } 128 129 // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. 130 func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, bool, error) { 131 // If snapshotting is disabled (initial sync in progress), don't do anything, 132 // wait for the chain to permit us to do something meaningful 133 if rawdb.ReadSnapshotDisabled(diskdb) { 134 return nil, true, nil 135 } 136 // Retrieve the block number and hash of the snapshot, failing if no snapshot 137 // is present in the database (or crashed mid-update). 138 baseRoot := rawdb.ReadSnapshotRoot(diskdb) 139 if baseRoot == (common.Hash{}) { 140 return nil, false, errors.New("missing or corrupted snapshot") 141 } 142 base := &diskLayer{ 143 diskdb: diskdb, 144 triedb: triedb, 145 cache: fastcache.New(cache * 1024 * 1024), 146 root: baseRoot, 147 } 148 snapshot, generator, err := loadAndParseJournal(diskdb, base) 149 if err != nil { 150 log.Warn("Failed to load new-format journal", "error", err) 151 return nil, false, err 152 } 153 // Entire snapshot journal loaded, sanity check the head. If the loaded 154 // snapshot is not matched with current state root, print a warning log 155 // or discard the entire snapshot it's legacy snapshot. 156 // 157 // Possible scenario: Geth was crashed without persisting journal and then 158 // restart, the head is rewound to the point with available state(trie) 159 // which is below the snapshot. In this case the snapshot can be recovered 160 // by re-executing blocks but right now it's unavailable. 161 if head := snapshot.Root(); head != root { 162 // If it's legacy snapshot, or it's new-format snapshot but 163 // it's not in recovery mode, returns the error here for 164 // rebuilding the entire snapshot forcibly. 165 if !recovery { 166 return nil, false, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) 167 } 168 // It's in snapshot recovery, the assumption is held that 169 // the disk layer is always higher than chain head. It can 170 // be eventually recovered when the chain head beyonds the 171 // disk layer. 172 log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root) 173 } 174 // Everything loaded correctly, resume any suspended operations 175 if !generator.Done { 176 // Whether or not wiping was in progress, load any generator progress too 177 base.genMarker = generator.Marker 178 if base.genMarker == nil { 179 base.genMarker = []byte{} 180 } 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 // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new 200 // diff and verifying that it can be linked to the requested parent. 201 func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { 202 // Read the next diff journal entry 203 var root common.Hash 204 if err := r.Decode(&root); err != nil { 205 // The first read may fail with EOF, marking the end of the journal 206 if err == io.EOF { 207 return parent, nil 208 } 209 return nil, fmt.Errorf("load diff root: %v", err) 210 } 211 var destructs []journalDestruct 212 if err := r.Decode(&destructs); err != nil { 213 return nil, fmt.Errorf("load diff destructs: %v", err) 214 } 215 destructSet := make(map[common.Hash]struct{}) 216 for _, entry := range destructs { 217 destructSet[entry.Hash] = struct{}{} 218 } 219 var accounts []journalAccount 220 if err := r.Decode(&accounts); err != nil { 221 return nil, fmt.Errorf("load diff accounts: %v", err) 222 } 223 accountData := make(map[common.Hash][]byte) 224 for _, entry := range accounts { 225 if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 226 accountData[entry.Hash] = entry.Blob 227 } else { 228 accountData[entry.Hash] = nil 229 } 230 } 231 var storage []journalStorage 232 if err := r.Decode(&storage); err != nil { 233 return nil, fmt.Errorf("load diff storage: %v", err) 234 } 235 storageData := make(map[common.Hash]map[common.Hash][]byte) 236 for _, entry := range storage { 237 slots := make(map[common.Hash][]byte) 238 for i, key := range entry.Keys { 239 if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that 240 slots[key] = entry.Vals[i] 241 } else { 242 slots[key] = nil 243 } 244 } 245 storageData[entry.Hash] = slots 246 } 247 return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r) 248 } 249 250 // Journal terminates any in-progress snapshot generation, also implicitly pushing 251 // the progress into the database. 252 func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 253 // If the snapshot is currently being generated, abort it 254 var stats *generatorStats 255 if dl.genAbort != nil { 256 abort := make(chan *generatorStats) 257 dl.genAbort <- abort 258 259 if stats = <-abort; stats != nil { 260 stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) 261 } 262 } 263 // Ensure the layer didn't get stale 264 dl.lock.RLock() 265 defer dl.lock.RUnlock() 266 267 if dl.stale { 268 return common.Hash{}, ErrSnapshotStale 269 } 270 // Ensure the generator stats is written even if none was ran this cycle 271 journalProgress(dl.diskdb, dl.genMarker, stats) 272 273 log.Debug("Journalled disk layer", "root", dl.root) 274 return dl.root, nil 275 } 276 277 // Journal writes the memory layer contents into a buffer to be stored in the 278 // database as the snapshot journal. 279 func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { 280 // Journal the parent first 281 base, err := dl.parent.Journal(buffer) 282 if err != nil { 283 return common.Hash{}, err 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 // Everything below was journalled, persist this layer too 293 if err := rlp.Encode(buffer, dl.root); err != nil { 294 return common.Hash{}, err 295 } 296 destructs := make([]journalDestruct, 0, len(dl.destructSet)) 297 for hash := range dl.destructSet { 298 destructs = append(destructs, journalDestruct{Hash: hash}) 299 } 300 if err := rlp.Encode(buffer, destructs); err != nil { 301 return common.Hash{}, err 302 } 303 accounts := make([]journalAccount, 0, len(dl.accountData)) 304 for hash, blob := range dl.accountData { 305 accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) 306 } 307 if err := rlp.Encode(buffer, accounts); err != nil { 308 return common.Hash{}, err 309 } 310 storage := make([]journalStorage, 0, len(dl.storageData)) 311 for hash, slots := range dl.storageData { 312 keys := make([]common.Hash, 0, len(slots)) 313 vals := make([][]byte, 0, len(slots)) 314 for key, val := range slots { 315 keys = append(keys, key) 316 vals = append(vals, val) 317 } 318 storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) 319 } 320 if err := rlp.Encode(buffer, storage); err != nil { 321 return common.Hash{}, err 322 } 323 log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) 324 return base, nil 325 }