github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/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 "path" 25 "path/filepath" 26 "strings" 27 "sync/atomic" 28 "time" 29 30 "github.com/olekukonko/tablewriter" 31 "github.com/tacshi/go-ethereum/common" 32 "github.com/tacshi/go-ethereum/ethdb" 33 "github.com/tacshi/go-ethereum/ethdb/leveldb" 34 "github.com/tacshi/go-ethereum/ethdb/memorydb" 35 "github.com/tacshi/go-ethereum/log" 36 ) 37 38 // freezerdb is a database wrapper that enabled freezer data retrievals. 39 type freezerdb struct { 40 ancientRoot string 41 ethdb.KeyValueStore 42 ethdb.AncientStore 43 } 44 45 // AncientDatadir returns the path of root ancient directory. 46 func (frdb *freezerdb) AncientDatadir() (string, error) { 47 return frdb.ancientRoot, nil 48 } 49 50 // Close implements io.Closer, closing both the fast key-value store as well as 51 // the slow ancient tables. 52 func (frdb *freezerdb) Close() error { 53 var errs []error 54 if err := frdb.AncientStore.Close(); err != nil { 55 errs = append(errs, err) 56 } 57 if err := frdb.KeyValueStore.Close(); err != nil { 58 errs = append(errs, err) 59 } 60 if len(errs) != 0 { 61 return fmt.Errorf("%v", errs) 62 } 63 return nil 64 } 65 66 // Freeze is a helper method used for external testing to trigger and block until 67 // a freeze cycle completes, without having to sleep for a minute to trigger the 68 // automatic background run. 69 func (frdb *freezerdb) Freeze(threshold uint64) error { 70 if frdb.AncientStore.(*chainFreezer).readonly { 71 return errReadOnly 72 } 73 // Set the freezer threshold to a temporary value 74 defer func(old uint64) { 75 atomic.StoreUint64(&frdb.AncientStore.(*chainFreezer).threshold, old) 76 }(atomic.LoadUint64(&frdb.AncientStore.(*chainFreezer).threshold)) 77 atomic.StoreUint64(&frdb.AncientStore.(*chainFreezer).threshold, threshold) 78 79 // Trigger a freeze cycle and block until it's done 80 trigger := make(chan struct{}, 1) 81 frdb.AncientStore.(*chainFreezer).trigger <- trigger 82 <-trigger 83 return nil 84 } 85 86 // nofreezedb is a database wrapper that disables freezer data retrievals. 87 type nofreezedb struct { 88 ethdb.KeyValueStore 89 } 90 91 // HasAncient returns an error as we don't have a backing chain freezer. 92 func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) { 93 return false, errNotSupported 94 } 95 96 // Ancient returns an error as we don't have a backing chain freezer. 97 func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) { 98 return nil, errNotSupported 99 } 100 101 // AncientRange returns an error as we don't have a backing chain freezer. 102 func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64) ([][]byte, error) { 103 return nil, errNotSupported 104 } 105 106 // Ancients returns an error as we don't have a backing chain freezer. 107 func (db *nofreezedb) Ancients() (uint64, error) { 108 return 0, errNotSupported 109 } 110 111 // Tail returns an error as we don't have a backing chain freezer. 112 func (db *nofreezedb) Tail() (uint64, error) { 113 return 0, errNotSupported 114 } 115 116 // AncientSize returns an error as we don't have a backing chain freezer. 117 func (db *nofreezedb) AncientSize(kind string) (uint64, error) { 118 return 0, errNotSupported 119 } 120 121 // ModifyAncients is not supported. 122 func (db *nofreezedb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, error) { 123 return 0, errNotSupported 124 } 125 126 // TruncateHead returns an error as we don't have a backing chain freezer. 127 func (db *nofreezedb) TruncateHead(items uint64) error { 128 return errNotSupported 129 } 130 131 // TruncateTail returns an error as we don't have a backing chain freezer. 132 func (db *nofreezedb) TruncateTail(items uint64) error { 133 return errNotSupported 134 } 135 136 // Sync returns an error as we don't have a backing chain freezer. 137 func (db *nofreezedb) Sync() error { 138 return errNotSupported 139 } 140 141 func (db *nofreezedb) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err error) { 142 // Unlike other ancient-related methods, this method does not return 143 // errNotSupported when invoked. 144 // The reason for this is that the caller might want to do several things: 145 // 1. Check if something is in freezer, 146 // 2. If not, check leveldb. 147 // 148 // This will work, since the ancient-checks inside 'fn' will return errors, 149 // and the leveldb work will continue. 150 // 151 // If we instead were to return errNotSupported here, then the caller would 152 // have to explicitly check for that, having an extra clause to do the 153 // non-ancient operations. 154 return fn(db) 155 } 156 157 // MigrateTable processes the entries in a given table in sequence 158 // converting them to a new format if they're of an old format. 159 func (db *nofreezedb) MigrateTable(kind string, convert convertLegacyFn) error { 160 return errNotSupported 161 } 162 163 // AncientDatadir returns an error as we don't have a backing chain freezer. 164 func (db *nofreezedb) AncientDatadir() (string, error) { 165 return "", errNotSupported 166 } 167 168 // NewDatabase creates a high level database on top of a given key-value data 169 // store without a freezer moving immutable chain segments into cold storage. 170 func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { 171 return &nofreezedb{KeyValueStore: db} 172 } 173 174 // resolveChainFreezerDir is a helper function which resolves the absolute path 175 // of chain freezer by considering backward compatibility. 176 func resolveChainFreezerDir(ancient string) string { 177 // Check if the chain freezer is already present in the specified 178 // sub folder, if not then two possibilities: 179 // - chain freezer is not initialized 180 // - chain freezer exists in legacy location (root ancient folder) 181 freezer := path.Join(ancient, chainFreezerName) 182 if !common.FileExist(freezer) { 183 if !common.FileExist(ancient) { 184 // The entire ancient store is not initialized, still use the sub 185 // folder for initialization. 186 } else { 187 // Ancient root is already initialized, then we hold the assumption 188 // that chain freezer is also initialized and located in root folder. 189 // In this case fallback to legacy location. 190 freezer = ancient 191 log.Info("Found legacy ancient chain path", "location", ancient) 192 } 193 } 194 return freezer 195 } 196 197 // NewDatabaseWithFreezer creates a high level database on top of a given key- 198 // value data store with a freezer moving immutable chain segments into cold 199 // storage. The passed ancient indicates the path of root ancient directory 200 // where the chain freezer can be opened. 201 func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) { 202 // Create the idle freezer instance 203 frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly) 204 if err != nil { 205 printChainMetadata(db) 206 return nil, err 207 } 208 // Since the freezer can be stored separately from the user's key-value database, 209 // there's a fairly high probability that the user requests invalid combinations 210 // of the freezer and database. Ensure that we don't shoot ourselves in the foot 211 // by serving up conflicting data, leading to both datastores getting corrupted. 212 // 213 // - If both the freezer and key-value store is empty (no genesis), we just 214 // initialized a new empty freezer, so everything's fine. 215 // - If the key-value store is empty, but the freezer is not, we need to make 216 // sure the user's genesis matches the freezer. That will be checked in the 217 // blockchain, since we don't have the genesis block here (nor should we at 218 // this point care, the key-value/freezer combo is valid). 219 // - If neither the key-value store nor the freezer is empty, cross validate 220 // the genesis hashes to make sure they are compatible. If they are, also 221 // ensure that there's no gap between the freezer and subsequently leveldb. 222 // - If the key-value store is not empty, but the freezer is we might just be 223 // upgrading to the freezer release, or we might have had a small chain and 224 // not frozen anything yet. Ensure that no blocks are missing yet from the 225 // key-value store, since that would mean we already had an old freezer. 226 227 // If the genesis hash is empty, we have a new key-value store, so nothing to 228 // validate in this method. If, however, the genesis hash is not nil, compare 229 // it to the freezer content. 230 if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 { 231 if frozen, _ := frdb.Ancients(); frozen > 0 { 232 // If the freezer already contains something, ensure that the genesis blocks 233 // match, otherwise we might mix up freezers across chains and destroy both 234 // the freezer and the key-value store. 235 frgenesis, err := frdb.Ancient(ChainFreezerHashTable, 0) 236 if err != nil { 237 printChainMetadata(db) 238 return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) 239 } else if !bytes.Equal(kvgenesis, frgenesis) { 240 printChainMetadata(db) 241 return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) 242 } 243 // Key-value store and freezer belong to the same network. Ensure that they 244 // are contiguous, otherwise we might end up with a non-functional freezer. 245 if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { 246 // Subsequent header after the freezer limit is missing from the database. 247 // Reject startup if the database has a more recent head. 248 if head := *ReadHeaderNumber(db, ReadHeadHeaderHash(db)); head > frozen-1 { 249 // Find the smallest block stored in the key-value store 250 // in range of [frozen, head] 251 var number uint64 252 for number = frozen; number <= head; number++ { 253 if present, _ := db.Has(headerHashKey(number)); present { 254 break 255 } 256 } 257 // We are about to exit on error. Print database metdata beore exiting 258 printChainMetadata(db) 259 return nil, fmt.Errorf("gap in the chain between ancients [0 - #%d] and leveldb [#%d - #%d] ", 260 frozen-1, number, head) 261 } 262 // Database contains only older data than the freezer, this happens if the 263 // state was wiped and reinited from an existing freezer. 264 } 265 // Otherwise, key-value store continues where the freezer left off, all is fine. 266 // We might have duplicate blocks (crash after freezer write but before key-value 267 // store deletion, but that's fine). 268 } else { 269 // If the freezer is empty, ensure nothing was moved yet from the key-value 270 // store, otherwise we'll end up missing data. We check block #1 to decide 271 // if we froze anything previously or not, but do take care of databases with 272 // only the genesis block. 273 if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { 274 // Key-value store contains more data than the genesis block, make sure we 275 // didn't freeze anything yet. 276 if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { 277 printChainMetadata(db) 278 return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") 279 } 280 // Block #1 is still in the database, we're allowed to init a new freezer 281 } 282 // Otherwise, the head header is still the genesis, we're allowed to init a new 283 // freezer. 284 } 285 } 286 // Freezer is consistent with the key-value database, permit combining the two 287 if !frdb.readonly { 288 frdb.wg.Add(1) 289 go func() { 290 frdb.freeze(db) 291 frdb.wg.Done() 292 }() 293 } 294 return &freezerdb{ 295 ancientRoot: ancient, 296 KeyValueStore: db, 297 AncientStore: frdb, 298 }, nil 299 } 300 301 // NewMemoryDatabase creates an ephemeral in-memory key-value database without a 302 // freezer moving immutable chain segments into cold storage. 303 func NewMemoryDatabase() ethdb.Database { 304 return NewDatabase(memorydb.New()) 305 } 306 307 // NewMemoryDatabaseWithCap creates an ephemeral in-memory key-value database 308 // with an initial starting capacity, but without a freezer moving immutable 309 // chain segments into cold storage. 310 func NewMemoryDatabaseWithCap(size int) ethdb.Database { 311 return NewDatabase(memorydb.NewWithCap(size)) 312 } 313 314 // NewLevelDBDatabase creates a persistent key-value database without a freezer 315 // moving immutable chain segments into cold storage. 316 func NewLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { 317 db, err := leveldb.New(file, cache, handles, namespace, readonly) 318 if err != nil { 319 return nil, err 320 } 321 log.Info("Using LevelDB as the backing database") 322 return NewDatabase(db), nil 323 } 324 325 const ( 326 dbPebble = "pebble" 327 dbLeveldb = "leveldb" 328 ) 329 330 // hasPreexistingDb checks the given data directory whether a database is already 331 // instantiated at that location, and if so, returns the type of database (or the 332 // empty string). 333 func hasPreexistingDb(path string) string { 334 if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil { 335 return "" // No pre-existing db 336 } 337 if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil { 338 if err != nil { 339 panic(err) // only possible if the pattern is malformed 340 } 341 return dbPebble 342 } 343 return dbLeveldb 344 } 345 346 // OpenOptions contains the options to apply when opening a database. 347 // OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used. 348 type OpenOptions struct { 349 Type string // "leveldb" | "pebble" 350 Directory string // the datadir 351 AncientsDirectory string // the ancients-dir 352 Namespace string // the namespace for database relevant metrics 353 Cache int // the capacity(in megabytes) of the data caching 354 Handles int // number of files to be open simultaneously 355 ReadOnly bool 356 } 357 358 // openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble. 359 // 360 // type == null type != null 361 // +---------------------------------------- 362 // db is non-existent | leveldb default | specified type 363 // db is existent | from db | specified type (if compatible) 364 func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) { 365 existingDb := hasPreexistingDb(o.Directory) 366 if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb { 367 return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb) 368 } 369 if o.Type == dbPebble || existingDb == dbPebble { 370 if PebbleEnabled { 371 log.Info("Using pebble as the backing database") 372 return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) 373 } else { 374 return nil, errors.New("db.engine 'pebble' not supported on this platform") 375 } 376 } 377 if len(o.Type) != 0 && o.Type != dbLeveldb { 378 return nil, fmt.Errorf("unknown db.engine %v", o.Type) 379 } 380 log.Info("Using leveldb as the backing database") 381 // Use leveldb, either as default (no explicit choice), or pre-existing, or chosen explicitly 382 return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) 383 } 384 385 // Open opens both a disk-based key-value database such as leveldb or pebble, but also 386 // integrates it with a freezer database -- if the AncientDir option has been 387 // set on the provided OpenOptions. 388 // The passed o.AncientDir indicates the path of root ancient directory where 389 // the chain freezer can be opened. 390 func Open(o OpenOptions) (ethdb.Database, error) { 391 kvdb, err := openKeyValueDatabase(o) 392 if err != nil { 393 return nil, err 394 } 395 if len(o.AncientsDirectory) == 0 { 396 return kvdb, nil 397 } 398 frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly) 399 if err != nil { 400 kvdb.Close() 401 return nil, err 402 } 403 return frdb, nil 404 } 405 406 type counter uint64 407 408 func (c counter) String() string { 409 return fmt.Sprintf("%d", c) 410 } 411 412 func (c counter) Percentage(current uint64) string { 413 return fmt.Sprintf("%d", current*100/uint64(c)) 414 } 415 416 // stat stores sizes and count for a parameter 417 type stat struct { 418 size common.StorageSize 419 count counter 420 } 421 422 // Add size to the stat and increase the counter by 1 423 func (s *stat) Add(size common.StorageSize) { 424 s.size += size 425 s.count++ 426 } 427 428 func (s *stat) Size() string { 429 return s.size.String() 430 } 431 432 func (s *stat) Count() string { 433 return s.count.String() 434 } 435 436 // InspectDatabase traverses the entire database and checks the size 437 // of all different categories of data. 438 func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { 439 it := db.NewIterator(keyPrefix, keyStart) 440 defer it.Release() 441 442 var ( 443 count int64 444 start = time.Now() 445 logged = time.Now() 446 447 // Key-value store statistics 448 headers stat 449 bodies stat 450 receipts stat 451 tds stat 452 numHashPairings stat 453 hashNumPairings stat 454 tries stat 455 codes stat 456 txLookups stat 457 accountSnaps stat 458 storageSnaps stat 459 preimages stat 460 bloomBits stat 461 beaconHeaders stat 462 cliqueSnaps stat 463 464 // Les statistic 465 chtTrieNodes stat 466 bloomTrieNodes stat 467 468 // Meta- and unaccounted data 469 metadata stat 470 unaccounted stat 471 472 // Totals 473 total common.StorageSize 474 ) 475 // Inspect key-value database first. 476 for it.Next() { 477 var ( 478 key = it.Key() 479 size = common.StorageSize(len(key) + len(it.Value())) 480 ) 481 total += size 482 switch { 483 case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): 484 headers.Add(size) 485 case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength): 486 bodies.Add(size) 487 case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): 488 receipts.Add(size) 489 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): 490 tds.Add(size) 491 case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): 492 numHashPairings.Add(size) 493 case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): 494 hashNumPairings.Add(size) 495 case len(key) == common.HashLength: 496 tries.Add(size) 497 case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: 498 codes.Add(size) 499 case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): 500 txLookups.Add(size) 501 case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): 502 accountSnaps.Add(size) 503 case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): 504 storageSnaps.Add(size) 505 case bytes.HasPrefix(key, PreimagePrefix) && len(key) == (len(PreimagePrefix)+common.HashLength): 506 preimages.Add(size) 507 case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength): 508 metadata.Add(size) 509 case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength): 510 metadata.Add(size) 511 case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): 512 bloomBits.Add(size) 513 case bytes.HasPrefix(key, BloomBitsIndexPrefix): 514 bloomBits.Add(size) 515 case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8): 516 beaconHeaders.Add(size) 517 case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength: 518 cliqueSnaps.Add(size) 519 case bytes.HasPrefix(key, ChtTablePrefix) || 520 bytes.HasPrefix(key, ChtIndexTablePrefix) || 521 bytes.HasPrefix(key, ChtPrefix): // Canonical hash trie 522 chtTrieNodes.Add(size) 523 case bytes.HasPrefix(key, BloomTrieTablePrefix) || 524 bytes.HasPrefix(key, BloomTrieIndexPrefix) || 525 bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub 526 bloomTrieNodes.Add(size) 527 default: 528 var accounted bool 529 for _, meta := range [][]byte{ 530 databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey, 531 lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey, 532 snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, 533 uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey, 534 } { 535 if bytes.Equal(key, meta) { 536 metadata.Add(size) 537 accounted = true 538 break 539 } 540 } 541 if !accounted { 542 unaccounted.Add(size) 543 } 544 } 545 count++ 546 if count%1000 == 0 && time.Since(logged) > 8*time.Second { 547 log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) 548 logged = time.Now() 549 } 550 } 551 // Display the database statistic of key-value store. 552 stats := [][]string{ 553 {"Key-Value store", "Headers", headers.Size(), headers.Count()}, 554 {"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, 555 {"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, 556 {"Key-Value store", "Difficulties", tds.Size(), tds.Count()}, 557 {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, 558 {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, 559 {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, 560 {"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()}, 561 {"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, 562 {"Key-Value store", "Trie nodes", tries.Size(), tries.Count()}, 563 {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, 564 {"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, 565 {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, 566 {"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()}, 567 {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, 568 {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, 569 {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, 570 {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, 571 } 572 // Inspect all registered append-only file store then. 573 ancients, err := inspectFreezers(db) 574 if err != nil { 575 return err 576 } 577 for _, ancient := range ancients { 578 for _, table := range ancient.sizes { 579 stats = append(stats, []string{ 580 fmt.Sprintf("Ancient store (%s)", strings.Title(ancient.name)), 581 strings.Title(table.name), 582 table.size.String(), 583 fmt.Sprintf("%d", ancient.count()), 584 }) 585 } 586 total += ancient.size() 587 } 588 table := tablewriter.NewWriter(os.Stdout) 589 table.SetHeader([]string{"Database", "Category", "Size", "Items"}) 590 table.SetFooter([]string{"", "Total", total.String(), " "}) 591 table.AppendBulk(stats) 592 table.Render() 593 594 if unaccounted.size > 0 { 595 log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) 596 } 597 return nil 598 } 599 600 // printChainMetadata prints out chain metadata to stderr. 601 func printChainMetadata(db ethdb.KeyValueStore) { 602 fmt.Fprintf(os.Stderr, "Chain metadata\n") 603 for _, v := range ReadChainMetadata(db) { 604 fmt.Fprintf(os.Stderr, " %s\n", strings.Join(v, ": ")) 605 } 606 fmt.Fprintf(os.Stderr, "\n\n") 607 } 608 609 // ReadChainMetadata returns a set of key/value pairs that contains informatin 610 // about the database chain status. This can be used for diagnostic purposes 611 // when investigating the state of the node. 612 func ReadChainMetadata(db ethdb.KeyValueStore) [][]string { 613 pp := func(val *uint64) string { 614 if val == nil { 615 return "<nil>" 616 } 617 return fmt.Sprintf("%d (%#x)", *val, *val) 618 } 619 data := [][]string{ 620 {"databaseVersion", pp(ReadDatabaseVersion(db))}, 621 {"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))}, 622 {"headFastBlockHash", fmt.Sprintf("%v", ReadHeadFastBlockHash(db))}, 623 {"headHeaderHash", fmt.Sprintf("%v", ReadHeadHeaderHash(db))}, 624 {"lastPivotNumber", pp(ReadLastPivotNumber(db))}, 625 {"len(snapshotSyncStatus)", fmt.Sprintf("%d bytes", len(ReadSnapshotSyncStatus(db)))}, 626 {"snapshotDisabled", fmt.Sprintf("%v", ReadSnapshotDisabled(db))}, 627 {"snapshotJournal", fmt.Sprintf("%d bytes", len(ReadSnapshotJournal(db)))}, 628 {"snapshotRecoveryNumber", pp(ReadSnapshotRecoveryNumber(db))}, 629 {"snapshotRoot", fmt.Sprintf("%v", ReadSnapshotRoot(db))}, 630 {"txIndexTail", pp(ReadTxIndexTail(db))}, 631 {"fastTxLookupLimit", pp(ReadFastTxLookupLimit(db))}, 632 } 633 if b := ReadSkeletonSyncStatus(db); b != nil { 634 data = append(data, []string{"SkeletonSyncStatus", string(b)}) 635 } 636 return data 637 }