github.com/prajjawalk/go-ethereum@v1.9.7/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  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/ethdb"
    28  	"github.com/ethereum/go-ethereum/ethdb/leveldb"
    29  	"github.com/ethereum/go-ethereum/ethdb/memorydb"
    30  	"github.com/ethereum/go-ethereum/log"
    31  	"github.com/olekukonko/tablewriter"
    32  )
    33  
    34  // freezerdb is a database wrapper that enabled freezer data retrievals.
    35  type freezerdb struct {
    36  	ethdb.KeyValueStore
    37  	ethdb.AncientStore
    38  }
    39  
    40  // Close implements io.Closer, closing both the fast key-value store as well as
    41  // the slow ancient tables.
    42  func (frdb *freezerdb) Close() error {
    43  	var errs []error
    44  	if err := frdb.KeyValueStore.Close(); err != nil {
    45  		errs = append(errs, err)
    46  	}
    47  	if err := frdb.AncientStore.Close(); err != nil {
    48  		errs = append(errs, err)
    49  	}
    50  	if len(errs) != 0 {
    51  		return fmt.Errorf("%v", errs)
    52  	}
    53  	return nil
    54  }
    55  
    56  // nofreezedb is a database wrapper that disables freezer data retrievals.
    57  type nofreezedb struct {
    58  	ethdb.KeyValueStore
    59  }
    60  
    61  // HasAncient returns an error as we don't have a backing chain freezer.
    62  func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) {
    63  	return false, errNotSupported
    64  }
    65  
    66  // Ancient returns an error as we don't have a backing chain freezer.
    67  func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) {
    68  	return nil, errNotSupported
    69  }
    70  
    71  // Ancients returns an error as we don't have a backing chain freezer.
    72  func (db *nofreezedb) Ancients() (uint64, error) {
    73  	return 0, errNotSupported
    74  }
    75  
    76  // AncientSize returns an error as we don't have a backing chain freezer.
    77  func (db *nofreezedb) AncientSize(kind string) (uint64, error) {
    78  	return 0, errNotSupported
    79  }
    80  
    81  // AppendAncient returns an error as we don't have a backing chain freezer.
    82  func (db *nofreezedb) AppendAncient(number uint64, hash, header, body, receipts, td []byte) error {
    83  	return errNotSupported
    84  }
    85  
    86  // TruncateAncients returns an error as we don't have a backing chain freezer.
    87  func (db *nofreezedb) TruncateAncients(items uint64) error {
    88  	return errNotSupported
    89  }
    90  
    91  // Sync returns an error as we don't have a backing chain freezer.
    92  func (db *nofreezedb) Sync() error {
    93  	return errNotSupported
    94  }
    95  
    96  // NewDatabase creates a high level database on top of a given key-value data
    97  // store without a freezer moving immutable chain segments into cold storage.
    98  func NewDatabase(db ethdb.KeyValueStore) ethdb.Database {
    99  	return &nofreezedb{
   100  		KeyValueStore: db,
   101  	}
   102  }
   103  
   104  // NewDatabaseWithFreezer creates a high level database on top of a given key-
   105  // value data store with a freezer moving immutable chain segments into cold
   106  // storage.
   107  func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string) (ethdb.Database, error) {
   108  	// Create the idle freezer instance
   109  	frdb, err := newFreezer(freezer, namespace)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	// Since the freezer can be stored separately from the user's key-value database,
   114  	// there's a fairly high probability that the user requests invalid combinations
   115  	// of the freezer and database. Ensure that we don't shoot ourselves in the foot
   116  	// by serving up conflicting data, leading to both datastores getting corrupted.
   117  	//
   118  	//   - If both the freezer and key-value store is empty (no genesis), we just
   119  	//     initialized a new empty freezer, so everything's fine.
   120  	//   - If the key-value store is empty, but the freezer is not, we need to make
   121  	//     sure the user's genesis matches the freezer. That will be checked in the
   122  	//     blockchain, since we don't have the genesis block here (nor should we at
   123  	//     this point care, the key-value/freezer combo is valid).
   124  	//   - If neither the key-value store nor the freezer is empty, cross validate
   125  	//     the genesis hashes to make sure they are compatible. If they are, also
   126  	//     ensure that there's no gap between the freezer and sunsequently leveldb.
   127  	//   - If the key-value store is not empty, but the freezer is we might just be
   128  	//     upgrading to the freezer release, or we might have had a small chain and
   129  	//     not frozen anything yet. Ensure that no blocks are missing yet from the
   130  	//     key-value store, since that would mean we already had an old freezer.
   131  
   132  	// If the genesis hash is empty, we have a new key-value store, so nothing to
   133  	// validate in this method. If, however, the genesis hash is not nil, compare
   134  	// it to the freezer content.
   135  	if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 {
   136  		if frozen, _ := frdb.Ancients(); frozen > 0 {
   137  			// If the freezer already contains something, ensure that the genesis blocks
   138  			// match, otherwise we might mix up freezers across chains and destroy both
   139  			// the freezer and the key-value store.
   140  			if frgenesis, _ := frdb.Ancient(freezerHashTable, 0); !bytes.Equal(kvgenesis, frgenesis) {
   141  				return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis)
   142  			}
   143  			// Key-value store and freezer belong to the same network. Ensure that they
   144  			// are contiguous, otherwise we might end up with a non-functional freezer.
   145  			if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 {
   146  				// Subsequent header after the freezer limit is missing from the database.
   147  				// Reject startup is the database has a more recent head.
   148  				if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 {
   149  					return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen)
   150  				}
   151  				// Database contains only older data than the freezer, this happens if the
   152  				// state was wiped and reinited from an existing freezer.
   153  			} else {
   154  				// Key-value store continues where the freezer left off, all is fine. We might
   155  				// have duplicate blocks (crash after freezer write but before kay-value store
   156  				// deletion, but that's fine).
   157  			}
   158  		} else {
   159  			// If the freezer is empty, ensure nothing was moved yet from the key-value
   160  			// store, otherwise we'll end up missing data. We check block #1 to decide
   161  			// if we froze anything previously or not, but do take care of databases with
   162  			// only the genesis block.
   163  			if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) {
   164  				// Key-value store contains more data than the genesis block, make sure we
   165  				// didn't freeze anything yet.
   166  				if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 {
   167  					return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path")
   168  				}
   169  				// Block #1 is still in the database, we're allowed to init a new feezer
   170  			} else {
   171  				// The head header is still the genesis, we're allowed to init a new feezer
   172  			}
   173  		}
   174  	}
   175  	// Freezer is consistent with the key-value database, permit combining the two
   176  	go frdb.freeze(db)
   177  
   178  	return &freezerdb{
   179  		KeyValueStore: db,
   180  		AncientStore:  frdb,
   181  	}, nil
   182  }
   183  
   184  // NewMemoryDatabase creates an ephemeral in-memory key-value database without a
   185  // freezer moving immutable chain segments into cold storage.
   186  func NewMemoryDatabase() ethdb.Database {
   187  	return NewDatabase(memorydb.New())
   188  }
   189  
   190  // NewMemoryDatabaseWithCap creates an ephemeral in-memory key-value database
   191  // with an initial starting capacity, but without a freezer moving immutable
   192  // chain segments into cold storage.
   193  func NewMemoryDatabaseWithCap(size int) ethdb.Database {
   194  	return NewDatabase(memorydb.NewWithCap(size))
   195  }
   196  
   197  // NewLevelDBDatabase creates a persistent key-value database without a freezer
   198  // moving immutable chain segments into cold storage.
   199  func NewLevelDBDatabase(file string, cache int, handles int, namespace string) (ethdb.Database, error) {
   200  	db, err := leveldb.New(file, cache, handles, namespace)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	return NewDatabase(db), nil
   205  }
   206  
   207  // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a
   208  // freezer moving immutable chain segments into cold storage.
   209  func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) {
   210  	kvdb, err := leveldb.New(file, cache, handles, namespace)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace)
   215  	if err != nil {
   216  		kvdb.Close()
   217  		return nil, err
   218  	}
   219  	return frdb, nil
   220  }
   221  
   222  // InspectDatabase traverses the entire database and checks the size
   223  // of all different categories of data.
   224  func InspectDatabase(db ethdb.Database) error {
   225  	it := db.NewIterator()
   226  	defer it.Release()
   227  
   228  	var (
   229  		count  int64
   230  		start  = time.Now()
   231  		logged = time.Now()
   232  
   233  		// Key-value store statistics
   234  		total           common.StorageSize
   235  		headerSize      common.StorageSize
   236  		bodySize        common.StorageSize
   237  		receiptSize     common.StorageSize
   238  		tdSize          common.StorageSize
   239  		numHashPairing  common.StorageSize
   240  		hashNumPairing  common.StorageSize
   241  		trieSize        common.StorageSize
   242  		txlookupSize    common.StorageSize
   243  		preimageSize    common.StorageSize
   244  		bloomBitsSize   common.StorageSize
   245  		cliqueSnapsSize common.StorageSize
   246  
   247  		// Ancient store statistics
   248  		ancientHeaders  common.StorageSize
   249  		ancientBodies   common.StorageSize
   250  		ancientReceipts common.StorageSize
   251  		ancientHashes   common.StorageSize
   252  		ancientTds      common.StorageSize
   253  
   254  		// Les statistic
   255  		chtTrieNodes   common.StorageSize
   256  		bloomTrieNodes common.StorageSize
   257  
   258  		// Meta- and unaccounted data
   259  		metadata    common.StorageSize
   260  		unaccounted common.StorageSize
   261  	)
   262  	// Inspect key-value database first.
   263  	for it.Next() {
   264  		var (
   265  			key  = it.Key()
   266  			size = common.StorageSize(len(key) + len(it.Value()))
   267  		)
   268  		total += size
   269  		switch {
   270  		case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix):
   271  			tdSize += size
   272  		case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix):
   273  			numHashPairing += size
   274  		case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength):
   275  			headerSize += size
   276  		case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
   277  			hashNumPairing += size
   278  		case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength):
   279  			bodySize += size
   280  		case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
   281  			receiptSize += size
   282  		case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength):
   283  			txlookupSize += size
   284  		case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength):
   285  			preimageSize += size
   286  		case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
   287  			bloomBitsSize += size
   288  		case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength:
   289  			cliqueSnapsSize += size
   290  		case bytes.HasPrefix(key, []byte("cht-")) && len(key) == 4+common.HashLength:
   291  			chtTrieNodes += size
   292  		case bytes.HasPrefix(key, []byte("blt-")) && len(key) == 4+common.HashLength:
   293  			bloomTrieNodes += size
   294  		case len(key) == common.HashLength:
   295  			trieSize += size
   296  		default:
   297  			var accounted bool
   298  			for _, meta := range [][]byte{databaseVerisionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey} {
   299  				if bytes.Equal(key, meta) {
   300  					metadata += size
   301  					accounted = true
   302  					break
   303  				}
   304  			}
   305  			if !accounted {
   306  				unaccounted += size
   307  			}
   308  		}
   309  		count += 1
   310  		if count%1000 == 0 && time.Since(logged) > 8*time.Second {
   311  			log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start)))
   312  			logged = time.Now()
   313  		}
   314  	}
   315  	// Inspect append-only file store then.
   316  	ancients := []*common.StorageSize{&ancientHeaders, &ancientBodies, &ancientReceipts, &ancientHashes, &ancientTds}
   317  	for i, category := range []string{freezerHeaderTable, freezerBodiesTable, freezerReceiptTable, freezerHashTable, freezerDifficultyTable} {
   318  		if size, err := db.AncientSize(category); err == nil {
   319  			*ancients[i] += common.StorageSize(size)
   320  			total += common.StorageSize(size)
   321  		}
   322  	}
   323  	// Display the database statistic.
   324  	stats := [][]string{
   325  		{"Key-Value store", "Headers", headerSize.String()},
   326  		{"Key-Value store", "Bodies", bodySize.String()},
   327  		{"Key-Value store", "Receipts", receiptSize.String()},
   328  		{"Key-Value store", "Difficulties", tdSize.String()},
   329  		{"Key-Value store", "Block number->hash", numHashPairing.String()},
   330  		{"Key-Value store", "Block hash->number", hashNumPairing.String()},
   331  		{"Key-Value store", "Transaction index", txlookupSize.String()},
   332  		{"Key-Value store", "Bloombit index", bloomBitsSize.String()},
   333  		{"Key-Value store", "Trie nodes", trieSize.String()},
   334  		{"Key-Value store", "Trie preimages", preimageSize.String()},
   335  		{"Key-Value store", "Clique snapshots", cliqueSnapsSize.String()},
   336  		{"Key-Value store", "Singleton metadata", metadata.String()},
   337  		{"Ancient store", "Headers", ancientHeaders.String()},
   338  		{"Ancient store", "Bodies", ancientBodies.String()},
   339  		{"Ancient store", "Receipts", ancientReceipts.String()},
   340  		{"Ancient store", "Difficulties", ancientTds.String()},
   341  		{"Ancient store", "Block number->hash", ancientHashes.String()},
   342  		{"Light client", "CHT trie nodes", chtTrieNodes.String()},
   343  		{"Light client", "Bloom trie nodes", bloomTrieNodes.String()},
   344  	}
   345  	table := tablewriter.NewWriter(os.Stdout)
   346  	table.SetHeader([]string{"Database", "Category", "Size"})
   347  	table.SetFooter([]string{"", "Total", total.String()})
   348  	table.AppendBulk(stats)
   349  	table.Render()
   350  
   351  	if unaccounted > 0 {
   352  		log.Error("Database contains unaccounted data", "size", unaccounted)
   353  	}
   354  	return nil
   355  }