github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/core/rawdb/database.go (about)

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