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  }