github.com/core-coin/go-core/v2@v2.1.9/core/rawdb/database.go (about)

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