github.com/calmw/ethereum@v0.1.1/trie/database_wrap.go (about)

     1  // Copyright 2022 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 trie
    18  
    19  import (
    20  	"errors"
    21  	"runtime"
    22  	"time"
    23  
    24  	"github.com/VictoriaMetrics/fastcache"
    25  	"github.com/calmw/ethereum/common"
    26  	"github.com/calmw/ethereum/ethdb"
    27  	"github.com/calmw/ethereum/log"
    28  	"github.com/calmw/ethereum/trie/triedb/hashdb"
    29  	"github.com/calmw/ethereum/trie/trienode"
    30  )
    31  
    32  // Config defines all necessary options for database.
    33  type Config struct {
    34  	Cache     int    // Memory allowance (MB) to use for caching trie nodes in memory
    35  	Journal   string // Journal of clean cache to survive node restarts
    36  	Preimages bool   // Flag whether the preimage of trie key is recorded
    37  }
    38  
    39  // backend defines the methods needed to access/update trie nodes in different
    40  // state scheme.
    41  type backend interface {
    42  	// Scheme returns the identifier of used storage scheme.
    43  	Scheme() string
    44  
    45  	// Initialized returns an indicator if the state data is already initialized
    46  	// according to the state scheme.
    47  	Initialized(genesisRoot common.Hash) bool
    48  
    49  	// Size returns the current storage size of the memory cache in front of the
    50  	// persistent database layer.
    51  	Size() common.StorageSize
    52  
    53  	// Update performs a state transition by committing dirty nodes contained
    54  	// in the given set in order to update state from the specified parent to
    55  	// the specified root.
    56  	Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error
    57  
    58  	// Commit writes all relevant trie nodes belonging to the specified state
    59  	// to disk. Report specifies whether logs will be displayed in info level.
    60  	Commit(root common.Hash, report bool) error
    61  
    62  	// Close closes the trie database backend and releases all held resources.
    63  	Close() error
    64  }
    65  
    66  // Database is the wrapper of the underlying backend which is shared by different
    67  // types of node backend as an entrypoint. It's responsible for all interactions
    68  // relevant with trie nodes and node preimages.
    69  type Database struct {
    70  	config    *Config          // Configuration for trie database
    71  	diskdb    ethdb.Database   // Persistent database to store the snapshot
    72  	cleans    *fastcache.Cache // Megabytes permitted using for read caches
    73  	preimages *preimageStore   // The store for caching preimages
    74  	backend   backend          // The backend for managing trie nodes
    75  }
    76  
    77  // prepare initializes the database with provided configs, but the
    78  // database backend is still left as nil.
    79  func prepare(diskdb ethdb.Database, config *Config) *Database {
    80  	var cleans *fastcache.Cache
    81  	if config != nil && config.Cache > 0 {
    82  		if config.Journal == "" {
    83  			cleans = fastcache.New(config.Cache * 1024 * 1024)
    84  		} else {
    85  			cleans = fastcache.LoadFromFileOrNew(config.Journal, config.Cache*1024*1024)
    86  		}
    87  	}
    88  	var preimages *preimageStore
    89  	if config != nil && config.Preimages {
    90  		preimages = newPreimageStore(diskdb)
    91  	}
    92  	return &Database{
    93  		config:    config,
    94  		diskdb:    diskdb,
    95  		cleans:    cleans,
    96  		preimages: preimages,
    97  	}
    98  }
    99  
   100  // NewDatabase initializes the trie database with default settings, namely
   101  // the legacy hash-based scheme is used by default.
   102  func NewDatabase(diskdb ethdb.Database) *Database {
   103  	return NewDatabaseWithConfig(diskdb, nil)
   104  }
   105  
   106  // NewDatabaseWithConfig initializes the trie database with provided configs.
   107  // The path-based scheme is not activated yet, always initialized with legacy
   108  // hash-based scheme by default.
   109  func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database {
   110  	db := prepare(diskdb, config)
   111  	db.backend = hashdb.New(diskdb, db.cleans, mptResolver{})
   112  	return db
   113  }
   114  
   115  // Reader returns a reader for accessing all trie nodes with provided state root.
   116  // Nil is returned in case the state is not available.
   117  func (db *Database) Reader(blockRoot common.Hash) Reader {
   118  	return db.backend.(*hashdb.Database).Reader(blockRoot)
   119  }
   120  
   121  // Update performs a state transition by committing dirty nodes contained in the
   122  // given set in order to update state from the specified parent to the specified
   123  // root. The held pre-images accumulated up to this point will be flushed in case
   124  // the size exceeds the threshold.
   125  func (db *Database) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error {
   126  	if db.preimages != nil {
   127  		db.preimages.commit(false)
   128  	}
   129  	return db.backend.Update(root, parent, nodes)
   130  }
   131  
   132  // Commit iterates over all the children of a particular node, writes them out
   133  // to disk. As a side effect, all pre-images accumulated up to this point are
   134  // also written.
   135  func (db *Database) Commit(root common.Hash, report bool) error {
   136  	if db.preimages != nil {
   137  		db.preimages.commit(true)
   138  	}
   139  	return db.backend.Commit(root, report)
   140  }
   141  
   142  // Size returns the storage size of dirty trie nodes in front of the persistent
   143  // database and the size of cached preimages.
   144  func (db *Database) Size() (common.StorageSize, common.StorageSize) {
   145  	var (
   146  		storages  common.StorageSize
   147  		preimages common.StorageSize
   148  	)
   149  	storages = db.backend.Size()
   150  	if db.preimages != nil {
   151  		preimages = db.preimages.size()
   152  	}
   153  	return storages, preimages
   154  }
   155  
   156  // Initialized returns an indicator if the state data is already initialized
   157  // according to the state scheme.
   158  func (db *Database) Initialized(genesisRoot common.Hash) bool {
   159  	return db.backend.Initialized(genesisRoot)
   160  }
   161  
   162  // Scheme returns the node scheme used in the database.
   163  func (db *Database) Scheme() string {
   164  	return db.backend.Scheme()
   165  }
   166  
   167  // Close flushes the dangling preimages to disk and closes the trie database.
   168  // It is meant to be called when closing the blockchain object, so that all
   169  // resources held can be released correctly.
   170  func (db *Database) Close() error {
   171  	if db.preimages != nil {
   172  		db.preimages.commit(true)
   173  	}
   174  	return db.backend.Close()
   175  }
   176  
   177  // saveCache saves clean state cache to given directory path
   178  // using specified CPU cores.
   179  func (db *Database) saveCache(dir string, threads int) error {
   180  	if db.cleans == nil {
   181  		return nil
   182  	}
   183  	log.Info("Writing clean trie cache to disk", "path", dir, "threads", threads)
   184  
   185  	start := time.Now()
   186  	err := db.cleans.SaveToFileConcurrent(dir, threads)
   187  	if err != nil {
   188  		log.Error("Failed to persist clean trie cache", "error", err)
   189  		return err
   190  	}
   191  	log.Info("Persisted the clean trie cache", "path", dir, "elapsed", common.PrettyDuration(time.Since(start)))
   192  	return nil
   193  }
   194  
   195  // SaveCache atomically saves fast cache data to the given dir using all
   196  // available CPU cores.
   197  func (db *Database) SaveCache(dir string) error {
   198  	return db.saveCache(dir, runtime.GOMAXPROCS(0))
   199  }
   200  
   201  // SaveCachePeriodically atomically saves fast cache data to the given dir with
   202  // the specified interval. All dump operation will only use a single CPU core.
   203  func (db *Database) SaveCachePeriodically(dir string, interval time.Duration, stopCh <-chan struct{}) {
   204  	ticker := time.NewTicker(interval)
   205  	defer ticker.Stop()
   206  
   207  	for {
   208  		select {
   209  		case <-ticker.C:
   210  			db.saveCache(dir, 1)
   211  		case <-stopCh:
   212  			return
   213  		}
   214  	}
   215  }
   216  
   217  // Cap iteratively flushes old but still referenced trie nodes until the total
   218  // memory usage goes below the given threshold. The held pre-images accumulated
   219  // up to this point will be flushed in case the size exceeds the threshold.
   220  //
   221  // It's only supported by hash-based database and will return an error for others.
   222  func (db *Database) Cap(limit common.StorageSize) error {
   223  	hdb, ok := db.backend.(*hashdb.Database)
   224  	if !ok {
   225  		return errors.New("not supported")
   226  	}
   227  	if db.preimages != nil {
   228  		db.preimages.commit(false)
   229  	}
   230  	return hdb.Cap(limit)
   231  }
   232  
   233  // Reference adds a new reference from a parent node to a child node. This function
   234  // is used to add reference between internal trie node and external node(e.g. storage
   235  // trie root), all internal trie nodes are referenced together by database itself.
   236  //
   237  // It's only supported by hash-based database and will return an error for others.
   238  func (db *Database) Reference(root common.Hash, parent common.Hash) error {
   239  	hdb, ok := db.backend.(*hashdb.Database)
   240  	if !ok {
   241  		return errors.New("not supported")
   242  	}
   243  	hdb.Reference(root, parent)
   244  	return nil
   245  }
   246  
   247  // Dereference removes an existing reference from a root node. It's only
   248  // supported by hash-based database and will return an error for others.
   249  func (db *Database) Dereference(root common.Hash) error {
   250  	hdb, ok := db.backend.(*hashdb.Database)
   251  	if !ok {
   252  		return errors.New("not supported")
   253  	}
   254  	hdb.Dereference(root)
   255  	return nil
   256  }
   257  
   258  // Node retrieves the rlp-encoded node blob with provided node hash. It's
   259  // only supported by hash-based database and will return an error for others.
   260  // Note, this function should be deprecated once ETH66 is deprecated.
   261  func (db *Database) Node(hash common.Hash) ([]byte, error) {
   262  	hdb, ok := db.backend.(*hashdb.Database)
   263  	if !ok {
   264  		return nil, errors.New("not supported")
   265  	}
   266  	return hdb.Node(hash)
   267  }