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