github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/core/rawdb/database.go (about) 1 // Copyright 2018 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 rawdb 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "os" 24 "sync/atomic" 25 "time" 26 27 "github.com/ethereum/go-ethereum/common" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/ethdb/leveldb" 30 "github.com/ethereum/go-ethereum/ethdb/memorydb" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/olekukonko/tablewriter" 33 ) 34 35 // freezerdb is a database wrapper that enabled freezer data retrievals. 36 type freezerdb struct { 37 ethdb.KeyValueStore 38 ethdb.AncientStore 39 } 40 41 // Close implements io.Closer, closing both the fast key-value store as well as 42 // the slow ancient tables. 43 func (frdb *freezerdb) Close() error { 44 var errs []error 45 if err := frdb.AncientStore.Close(); err != nil { 46 errs = append(errs, err) 47 } 48 if err := frdb.KeyValueStore.Close(); err != nil { 49 errs = append(errs, err) 50 } 51 if len(errs) != 0 { 52 return fmt.Errorf("%v", errs) 53 } 54 return nil 55 } 56 57 // Freeze is a helper method used for external testing to trigger and block until 58 // a freeze cycle completes, without having to sleep for a minute to trigger the 59 // automatic background run. 60 func (frdb *freezerdb) Freeze(threshold uint64) error { 61 if frdb.AncientStore.(*freezer).readonly { 62 return errReadOnly 63 } 64 // Set the freezer threshold to a temporary value 65 defer func(old uint64) { 66 atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, old) 67 }(atomic.LoadUint64(&frdb.AncientStore.(*freezer).threshold)) 68 atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, threshold) 69 70 // Trigger a freeze cycle and block until it's done 71 trigger := make(chan struct{}, 1) 72 frdb.AncientStore.(*freezer).trigger <- trigger 73 <-trigger 74 return nil 75 } 76 77 // nofreezedb is a database wrapper that disables freezer data retrievals. 78 type nofreezedb struct { 79 ethdb.KeyValueStore 80 } 81 82 // HasAncient returns an error as we don't have a backing chain freezer. 83 func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) { 84 return false, errNotSupported 85 } 86 87 // Ancient returns an error as we don't have a backing chain freezer. 88 func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) { 89 return nil, errNotSupported 90 } 91 92 // AncientRange returns an error as we don't have a backing chain freezer. 93 func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64) ([][]byte, error) { 94 return nil, errNotSupported 95 } 96 97 // Ancients returns an error as we don't have a backing chain freezer. 98 func (db *nofreezedb) Ancients() (uint64, error) { 99 return 0, errNotSupported 100 } 101 102 // Tail returns an error as we don't have a backing chain freezer. 103 func (db *nofreezedb) Tail() (uint64, error) { 104 return 0, errNotSupported 105 } 106 107 // AncientSize returns an error as we don't have a backing chain freezer. 108 func (db *nofreezedb) AncientSize(kind string) (uint64, error) { 109 return 0, errNotSupported 110 } 111 112 // ModifyAncients is not supported. 113 func (db *nofreezedb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, error) { 114 return 0, errNotSupported 115 } 116 117 // TruncateHead returns an error as we don't have a backing chain freezer. 118 func (db *nofreezedb) TruncateHead(items uint64) error { 119 return errNotSupported 120 } 121 122 // TruncateTail returns an error as we don't have a backing chain freezer. 123 func (db *nofreezedb) TruncateTail(items uint64) error { 124 return errNotSupported 125 } 126 127 // Sync returns an error as we don't have a backing chain freezer. 128 func (db *nofreezedb) Sync() error { 129 return errNotSupported 130 } 131 132 func (db *nofreezedb) ReadAncients(fn func(reader ethdb.AncientReader) error) (err error) { 133 // Unlike other ancient-related methods, this method does not return 134 // errNotSupported when invoked. 135 // The reason for this is that the caller might want to do several things: 136 // 1. Check if something is in freezer, 137 // 2. If not, check leveldb. 138 // 139 // This will work, since the ancient-checks inside 'fn' will return errors, 140 // and the leveldb work will continue. 141 // 142 // If we instead were to return errNotSupported here, then the caller would 143 // have to explicitly check for that, having an extra clause to do the 144 // non-ancient operations. 145 return fn(db) 146 } 147 148 // MigrateTable processes the entries in a given table in sequence 149 // converting them to a new format if they're of an old format. 150 func (db *nofreezedb) MigrateTable(kind string, convert convertLegacyFn) error { 151 return errNotSupported 152 } 153 154 // NewDatabase creates a high level database on top of a given key-value data 155 // store without a freezer moving immutable chain segments into cold storage. 156 func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { 157 return &nofreezedb{KeyValueStore: db} 158 } 159 160 // NewDatabaseWithFreezer creates a high level database on top of a given key- 161 // value data store with a freezer moving immutable chain segments into cold 162 // storage. 163 func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { 164 // Create the idle freezer instance 165 frdb, err := newFreezer(freezer, namespace, readonly, freezerTableSize, FreezerNoSnappy) 166 if err != nil { 167 return nil, err 168 } 169 // Since the freezer can be stored separately from the user's key-value database, 170 // there's a fairly high probability that the user requests invalid combinations 171 // of the freezer and database. Ensure that we don't shoot ourselves in the foot 172 // by serving up conflicting data, leading to both datastores getting corrupted. 173 // 174 // - If both the freezer and key-value store is empty (no genesis), we just 175 // initialized a new empty freezer, so everything's fine. 176 // - If the key-value store is empty, but the freezer is not, we need to make 177 // sure the user's genesis matches the freezer. That will be checked in the 178 // blockchain, since we don't have the genesis block here (nor should we at 179 // this point care, the key-value/freezer combo is valid). 180 // - If neither the key-value store nor the freezer is empty, cross validate 181 // the genesis hashes to make sure they are compatible. If they are, also 182 // ensure that there's no gap between the freezer and sunsequently leveldb. 183 // - If the key-value store is not empty, but the freezer is we might just be 184 // upgrading to the freezer release, or we might have had a small chain and 185 // not frozen anything yet. Ensure that no blocks are missing yet from the 186 // key-value store, since that would mean we already had an old freezer. 187 188 // If the genesis hash is empty, we have a new key-value store, so nothing to 189 // validate in this method. If, however, the genesis hash is not nil, compare 190 // it to the freezer content. 191 if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 { 192 if frozen, _ := frdb.Ancients(); frozen > 0 { 193 // If the freezer already contains something, ensure that the genesis blocks 194 // match, otherwise we might mix up freezers across chains and destroy both 195 // the freezer and the key-value store. 196 frgenesis, err := frdb.Ancient(freezerHashTable, 0) 197 if err != nil { 198 return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) 199 } else if !bytes.Equal(kvgenesis, frgenesis) { 200 return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) 201 } 202 // Key-value store and freezer belong to the same network. Ensure that they 203 // are contiguous, otherwise we might end up with a non-functional freezer. 204 if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { 205 // Subsequent header after the freezer limit is missing from the database. 206 // Reject startup is the database has a more recent head. 207 if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 { 208 return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen) 209 } 210 // Database contains only older data than the freezer, this happens if the 211 // state was wiped and reinited from an existing freezer. 212 } 213 // Otherwise, key-value store continues where the freezer left off, all is fine. 214 // We might have duplicate blocks (crash after freezer write but before key-value 215 // store deletion, but that's fine). 216 } else { 217 // If the freezer is empty, ensure nothing was moved yet from the key-value 218 // store, otherwise we'll end up missing data. We check block #1 to decide 219 // if we froze anything previously or not, but do take care of databases with 220 // only the genesis block. 221 if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { 222 // Key-value store contains more data than the genesis block, make sure we 223 // didn't freeze anything yet. 224 if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { 225 return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") 226 } 227 // Block #1 is still in the database, we're allowed to init a new feezer 228 } 229 // Otherwise, the head header is still the genesis, we're allowed to init a new 230 // freezer. 231 } 232 } 233 // Freezer is consistent with the key-value database, permit combining the two 234 if !frdb.readonly { 235 frdb.wg.Add(1) 236 go func() { 237 frdb.freeze(db) 238 frdb.wg.Done() 239 }() 240 } 241 return &freezerdb{ 242 KeyValueStore: db, 243 AncientStore: frdb, 244 }, nil 245 } 246 247 // NewMemoryDatabase creates an ephemeral in-memory key-value database without a 248 // freezer moving immutable chain segments into cold storage. 249 func NewMemoryDatabase() ethdb.Database { 250 return NewDatabase(memorydb.New()) 251 } 252 253 // NewMemoryDatabaseWithCap creates an ephemeral in-memory key-value database 254 // with an initial starting capacity, but without a freezer moving immutable 255 // chain segments into cold storage. 256 func NewMemoryDatabaseWithCap(size int) ethdb.Database { 257 return NewDatabase(memorydb.NewWithCap(size)) 258 } 259 260 // NewLevelDBDatabase creates a persistent key-value database without a freezer 261 // moving immutable chain segments into cold storage. 262 func NewLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { 263 db, err := leveldb.New(file, cache, handles, namespace, readonly) 264 if err != nil { 265 return nil, err 266 } 267 return NewDatabase(db), nil 268 } 269 270 // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a 271 // freezer moving immutable chain segments into cold storage. 272 func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool) (ethdb.Database, error) { 273 kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) 274 if err != nil { 275 return nil, err 276 } 277 frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly) 278 if err != nil { 279 kvdb.Close() 280 return nil, err 281 } 282 return frdb, nil 283 } 284 285 type counter uint64 286 287 func (c counter) String() string { 288 return fmt.Sprintf("%d", c) 289 } 290 291 func (c counter) Percentage(current uint64) string { 292 return fmt.Sprintf("%d", current*100/uint64(c)) 293 } 294 295 // stat stores sizes and count for a parameter 296 type stat struct { 297 size common.StorageSize 298 count counter 299 } 300 301 // Add size to the stat and increase the counter by 1 302 func (s *stat) Add(size common.StorageSize) { 303 s.size += size 304 s.count++ 305 } 306 307 func (s *stat) Size() string { 308 return s.size.String() 309 } 310 311 func (s *stat) Count() string { 312 return s.count.String() 313 } 314 315 // InspectDatabase traverses the entire database and checks the size 316 // of all different categories of data. 317 func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { 318 it := db.NewIterator(keyPrefix, keyStart) 319 defer it.Release() 320 321 var ( 322 count int64 323 start = time.Now() 324 logged = time.Now() 325 326 // Key-value store statistics 327 headers stat 328 bodies stat 329 receipts stat 330 tds stat 331 numHashPairings stat 332 hashNumPairings stat 333 tries stat 334 codes stat 335 txLookups stat 336 accountSnaps stat 337 storageSnaps stat 338 preimages stat 339 bloomBits stat 340 beaconHeaders stat 341 cliqueSnaps stat 342 343 // Ancient store statistics 344 ancientHeadersSize common.StorageSize 345 ancientBodiesSize common.StorageSize 346 ancientReceiptsSize common.StorageSize 347 ancientTdsSize common.StorageSize 348 ancientHashesSize common.StorageSize 349 350 // Les statistic 351 chtTrieNodes stat 352 bloomTrieNodes stat 353 354 // Meta- and unaccounted data 355 metadata stat 356 unaccounted stat 357 358 // Totals 359 total common.StorageSize 360 ) 361 // Inspect key-value database first. 362 for it.Next() { 363 var ( 364 key = it.Key() 365 size = common.StorageSize(len(key) + len(it.Value())) 366 ) 367 total += size 368 switch { 369 case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): 370 headers.Add(size) 371 case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength): 372 bodies.Add(size) 373 case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): 374 receipts.Add(size) 375 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): 376 tds.Add(size) 377 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): 378 numHashPairings.Add(size) 379 case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): 380 hashNumPairings.Add(size) 381 case len(key) == common.HashLength: 382 tries.Add(size) 383 case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: 384 codes.Add(size) 385 case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): 386 txLookups.Add(size) 387 case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): 388 accountSnaps.Add(size) 389 case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): 390 storageSnaps.Add(size) 391 case bytes.HasPrefix(key, PreimagePrefix) && len(key) == (len(PreimagePrefix)+common.HashLength): 392 preimages.Add(size) 393 case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength): 394 metadata.Add(size) 395 case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength): 396 metadata.Add(size) 397 case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): 398 bloomBits.Add(size) 399 case bytes.HasPrefix(key, BloomBitsIndexPrefix): 400 bloomBits.Add(size) 401 case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8): 402 beaconHeaders.Add(size) 403 case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength: 404 cliqueSnaps.Add(size) 405 case bytes.HasPrefix(key, []byte("cht-")) || 406 bytes.HasPrefix(key, []byte("chtIndexV2-")) || 407 bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie 408 chtTrieNodes.Add(size) 409 case bytes.HasPrefix(key, []byte("blt-")) || 410 bytes.HasPrefix(key, []byte("bltIndex-")) || 411 bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub 412 bloomTrieNodes.Add(size) 413 default: 414 var accounted bool 415 for _, meta := range [][]byte{ 416 databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey, 417 fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey, 418 snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, 419 uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey, 420 } { 421 if bytes.Equal(key, meta) { 422 metadata.Add(size) 423 accounted = true 424 break 425 } 426 } 427 if !accounted { 428 unaccounted.Add(size) 429 } 430 } 431 count++ 432 if count%1000 == 0 && time.Since(logged) > 8*time.Second { 433 log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) 434 logged = time.Now() 435 } 436 } 437 // Inspect append-only file store then. 438 ancientSizes := []*common.StorageSize{&ancientHeadersSize, &ancientBodiesSize, &ancientReceiptsSize, &ancientHashesSize, &ancientTdsSize} 439 for i, category := range []string{freezerHeaderTable, freezerBodiesTable, freezerReceiptTable, freezerHashTable, freezerDifficultyTable} { 440 if size, err := db.AncientSize(category); err == nil { 441 *ancientSizes[i] += common.StorageSize(size) 442 total += common.StorageSize(size) 443 } 444 } 445 // Get number of ancient rows inside the freezer 446 ancients := counter(0) 447 if count, err := db.Ancients(); err == nil { 448 ancients = counter(count) 449 } 450 // Display the database statistic. 451 stats := [][]string{ 452 {"Key-Value store", "Headers", headers.Size(), headers.Count()}, 453 {"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, 454 {"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, 455 {"Key-Value store", "Difficulties", tds.Size(), tds.Count()}, 456 {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, 457 {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, 458 {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, 459 {"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()}, 460 {"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, 461 {"Key-Value store", "Trie nodes", tries.Size(), tries.Count()}, 462 {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, 463 {"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, 464 {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, 465 {"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()}, 466 {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, 467 {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, 468 {"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()}, 469 {"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()}, 470 {"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()}, 471 {"Ancient store", "Difficulties", ancientTdsSize.String(), ancients.String()}, 472 {"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()}, 473 {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, 474 {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, 475 } 476 table := tablewriter.NewWriter(os.Stdout) 477 table.SetHeader([]string{"Database", "Category", "Size", "Items"}) 478 table.SetFooter([]string{"", "Total", total.String(), " "}) 479 table.AppendBulk(stats) 480 table.Render() 481 482 if unaccounted.size > 0 { 483 log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) 484 } 485 486 return nil 487 }