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