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