github.com/DxChainNetwork/dxc@v0.8.1-0.20220824085222-1162e304b6e7/core/rawdb/database.go (about)

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