github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/gossip/evmstore/store.go (about)

     1  package evmstore
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/syndtr/goleveldb/leveldb/opt"
     7  	"github.com/unicornultrafoundation/go-helios/hash"
     8  	"github.com/unicornultrafoundation/go-helios/native/idx"
     9  	"github.com/unicornultrafoundation/go-helios/u2udb"
    10  	"github.com/unicornultrafoundation/go-helios/u2udb/nokeyiserr"
    11  	"github.com/unicornultrafoundation/go-helios/u2udb/table"
    12  	"github.com/unicornultrafoundation/go-helios/utils/wlru"
    13  	"github.com/unicornultrafoundation/go-u2u/common"
    14  	"github.com/unicornultrafoundation/go-u2u/common/prque"
    15  	"github.com/unicornultrafoundation/go-u2u/core/rawdb"
    16  	"github.com/unicornultrafoundation/go-u2u/core/state"
    17  	"github.com/unicornultrafoundation/go-u2u/core/state/snapshot"
    18  	"github.com/unicornultrafoundation/go-u2u/core/types"
    19  	"github.com/unicornultrafoundation/go-u2u/ethdb"
    20  	"github.com/unicornultrafoundation/go-u2u/trie"
    21  
    22  	"github.com/unicornultrafoundation/go-u2u/logger"
    23  	"github.com/unicornultrafoundation/go-u2u/native/iblockproc"
    24  	"github.com/unicornultrafoundation/go-u2u/topicsdb"
    25  	"github.com/unicornultrafoundation/go-u2u/utils/adapters/udb2ethdb"
    26  	"github.com/unicornultrafoundation/go-u2u/utils/rlpstore"
    27  )
    28  
    29  const nominalSize uint = 1
    30  
    31  // Store is a node persistent storage working over physical key-value database.
    32  type Store struct {
    33  	cfg StoreConfig
    34  
    35  	table struct {
    36  		Evm u2udb.Store `table:"M"`
    37  		// API-only tables
    38  		Receipts    u2udb.Store `table:"r"`
    39  		TxPositions u2udb.Store `table:"x"`
    40  		Txs         u2udb.Store `table:"X"`
    41  	}
    42  
    43  	EvmDb    ethdb.Database
    44  	EvmState state.Database
    45  	EvmLogs  topicsdb.Index
    46  	Snaps    *snapshot.Tree
    47  
    48  	cache struct {
    49  		TxPositions *wlru.Cache `cache:"-"` // store by pointer
    50  		Receipts    *wlru.Cache `cache:"-"` // store by value
    51  		EvmBlocks   *wlru.Cache `cache:"-"` // store by pointer
    52  	}
    53  
    54  	rlp rlpstore.Helper
    55  
    56  	triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
    57  
    58  	logger.Instance
    59  }
    60  
    61  const (
    62  	TriesInMemory = 16
    63  )
    64  
    65  // NewStore creates store over key-value db.
    66  func NewStore(dbs u2udb.DBProducer, cfg StoreConfig) *Store {
    67  	s := &Store{
    68  		cfg:      cfg,
    69  		Instance: logger.New("evm-store"),
    70  		rlp:      rlpstore.Helper{logger.New("rlp")},
    71  		triegc:   prque.New(nil),
    72  	}
    73  
    74  	err := table.OpenTables(&s.table, dbs, "evm")
    75  	if err != nil {
    76  		s.Log.Crit("Failed to open tables", "err", err)
    77  	}
    78  
    79  	s.initEVMDB()
    80  	s.EvmLogs = topicsdb.NewWithThreadPool(dbs)
    81  	s.initCache()
    82  
    83  	return s
    84  }
    85  
    86  // Close closes underlying database.
    87  func (s *Store) Close() {
    88  	setnil := func() interface{} {
    89  		return nil
    90  	}
    91  
    92  	_ = table.CloseTables(&s.table)
    93  	table.MigrateTables(&s.table, nil)
    94  	table.MigrateCaches(&s.cache, setnil)
    95  	s.EvmLogs.Close()
    96  }
    97  
    98  func (s *Store) initCache() {
    99  	s.cache.Receipts = s.makeCache(s.cfg.Cache.ReceiptsSize, s.cfg.Cache.ReceiptsBlocks)
   100  	s.cache.TxPositions = s.makeCache(nominalSize*uint(s.cfg.Cache.TxPositions), s.cfg.Cache.TxPositions)
   101  	s.cache.EvmBlocks = s.makeCache(s.cfg.Cache.EvmBlocksSize, s.cfg.Cache.EvmBlocksNum)
   102  }
   103  
   104  func (s *Store) initEVMDB() {
   105  	s.EvmDb = rawdb.NewDatabase(
   106  		udb2ethdb.Wrap(
   107  			nokeyiserr.Wrap(
   108  				s.table.Evm)))
   109  	s.EvmState = state.NewDatabaseWithConfig(s.EvmDb, &trie.Config{
   110  		Cache:     s.cfg.Cache.EvmDatabase / opt.MiB,
   111  		Journal:   s.cfg.Cache.TrieCleanJournal,
   112  		Preimages: s.cfg.EnablePreimageRecording,
   113  		GreedyGC:  s.cfg.Cache.GreedyGC,
   114  	})
   115  }
   116  
   117  func (s *Store) ResetWithEVMDB(evmStore u2udb.Store) *Store {
   118  	cp := *s
   119  	cp.table.Evm = evmStore
   120  	cp.initEVMDB()
   121  	cp.Snaps = nil
   122  	return &cp
   123  }
   124  
   125  func (s *Store) EVMDB() u2udb.Store {
   126  	return s.table.Evm
   127  }
   128  
   129  func (s *Store) GenerateEvmSnapshot(root common.Hash, rebuild, async bool) (err error) {
   130  	if s.Snaps != nil {
   131  		return errors.New("EVM snapshot is already opened")
   132  	}
   133  	s.Snaps, err = snapshot.New(
   134  		s.EvmDb,
   135  		s.EvmState.TrieDB(),
   136  		s.cfg.Cache.EvmSnap/opt.MiB,
   137  		root,
   138  		async,
   139  		rebuild,
   140  		false)
   141  	return
   142  }
   143  
   144  func (s *Store) RebuildEvmSnapshot(root common.Hash) {
   145  	if s.Snaps == nil {
   146  		return
   147  	}
   148  	s.Snaps.Rebuild(root)
   149  }
   150  
   151  // CleanCommit clean old state trie and commit changes.
   152  func (s *Store) CleanCommit(block iblockproc.BlockState) error {
   153  	// Don't need to reference the current state root
   154  	// due to it already be referenced on `Commit()` function
   155  	triedb := s.EvmState.TrieDB()
   156  	stateRoot := common.Hash(block.FinalizedStateRoot)
   157  	if current := uint64(block.LastBlock.Idx); current > TriesInMemory {
   158  		// Find the next state trie we need to commit
   159  		chosen := current - TriesInMemory
   160  		// Garbage collect all below the chosen block
   161  		for !s.triegc.Empty() {
   162  			root, number := s.triegc.Pop()
   163  			if uint64(-number) > chosen {
   164  				s.triegc.Push(root, number)
   165  				break
   166  			}
   167  			triedb.Dereference(root.(common.Hash))
   168  		}
   169  	}
   170  	// commit the state trie after clean up
   171  	err := triedb.Commit(stateRoot, false, nil)
   172  	if err != nil {
   173  		s.Log.Error("Failed to flush trie DB into main DB", "err", err)
   174  	}
   175  	return err
   176  }
   177  
   178  func (s *Store) PauseEvmSnapshot() {
   179  	s.Snaps.Disable()
   180  }
   181  
   182  func (s *Store) IsEvmSnapshotPaused() bool {
   183  	return rawdb.ReadSnapshotDisabled(s.table.Evm)
   184  }
   185  
   186  // Commit changes.
   187  func (s *Store) Commit(block idx.Block, root hash.Hash, flush bool) error {
   188  	triedb := s.EvmState.TrieDB()
   189  	stateRoot := common.Hash(root)
   190  	// If we're applying genesis or running an archive node, always flush
   191  	if flush || s.cfg.Cache.TrieDirtyDisabled {
   192  		err := triedb.Commit(stateRoot, false, nil)
   193  		if err != nil {
   194  			s.Log.Error("Failed to flush trie DB into main DB", "err", err)
   195  		}
   196  		return err
   197  	} else {
   198  		// Full but not archive node, do proper garbage collection
   199  		triedb.Reference(stateRoot, common.Hash{}) // metadata reference to keep trie alive
   200  		s.triegc.Push(stateRoot, -int64(block))
   201  
   202  		if current := uint64(block); current > TriesInMemory {
   203  			// If we exceeded our memory allowance, flush matured singleton nodes to disk
   204  			s.Cap()
   205  
   206  			// Find the next state trie we need to commit
   207  			chosen := current - TriesInMemory
   208  
   209  			// Garbage collect all below the chosen block
   210  			for !s.triegc.Empty() {
   211  				root, number := s.triegc.Pop()
   212  				if uint64(-number) > chosen {
   213  					s.triegc.Push(root, number)
   214  					break
   215  				}
   216  				triedb.Dereference(root.(common.Hash))
   217  			}
   218  		}
   219  		return nil
   220  	}
   221  }
   222  
   223  func (s *Store) Flush(block iblockproc.BlockState) {
   224  	// Ensure that the entirety of the state snapshot is journalled to disk.
   225  	var snapBase common.Hash
   226  	if s.Snaps != nil {
   227  		var err error
   228  		if snapBase, err = s.Snaps.Journal(common.Hash(block.FinalizedStateRoot)); err != nil {
   229  			s.Log.Error("Failed to journal state snapshot", "err", err)
   230  		}
   231  	}
   232  	// Ensure the state of a recent block is also stored to disk before exiting.
   233  	if !s.cfg.Cache.TrieDirtyDisabled {
   234  		triedb := s.EvmState.TrieDB()
   235  
   236  		if number := uint64(block.LastBlock.Idx); number > 0 {
   237  			s.Log.Info("Writing cached state to disk", "block", number, "root", block.FinalizedStateRoot)
   238  			if err := triedb.Commit(common.Hash(block.FinalizedStateRoot), true, nil); err != nil {
   239  				s.Log.Error("Failed to commit recent state trie", "err", err)
   240  			}
   241  		}
   242  		if snapBase != (common.Hash{}) {
   243  			s.Log.Info("Writing snapshot state to disk", "root", snapBase)
   244  			if err := triedb.Commit(snapBase, true, nil); err != nil {
   245  				s.Log.Error("Failed to commit recent state trie", "err", err)
   246  			}
   247  		}
   248  	}
   249  	// Ensure all live cached entries be saved into disk, so that we can skip
   250  	// cache warmup when node restarts.
   251  	if s.cfg.Cache.TrieCleanJournal != "" {
   252  		triedb := s.EvmState.TrieDB()
   253  		triedb.SaveCache(s.cfg.Cache.TrieCleanJournal)
   254  	}
   255  }
   256  
   257  // Cap flush matured singleton nodes to disk
   258  func (s *Store) Cap() {
   259  	triedb := s.EvmState.TrieDB()
   260  	var (
   261  		nodes, imgs = triedb.Size()
   262  		limit       = common.StorageSize(s.cfg.Cache.TrieDirtyLimit)
   263  	)
   264  	// If we exceeded our memory allowance, flush matured singleton nodes to disk
   265  	if nodes > limit+ethdb.IdealBatchSize || imgs > 4*1024*1024 {
   266  		triedb.Cap(limit)
   267  	}
   268  }
   269  
   270  // StateDB returns state database.
   271  func (s *Store) StateDB(from hash.Hash) (*state.StateDB, error) {
   272  	return state.NewWithSnapLayers(common.Hash(from), s.EvmState, s.Snaps, 0)
   273  }
   274  
   275  // HasStateDB returns if state database exists
   276  func (s *Store) HasStateDB(from hash.Hash) bool {
   277  	_, err := s.StateDB(from)
   278  	return err == nil
   279  }
   280  
   281  // IndexLogs indexes EVM logs
   282  func (s *Store) IndexLogs(recs ...*types.Log) {
   283  	err := s.EvmLogs.Push(recs...)
   284  	if err != nil {
   285  		s.Log.Crit("DB logs index error", "err", err)
   286  	}
   287  }
   288  
   289  func (s *Store) Snapshots() *snapshot.Tree {
   290  	return s.Snaps
   291  }
   292  
   293  /*
   294   * Utils:
   295   */
   296  
   297  func (s *Store) makeCache(weight uint, size int) *wlru.Cache {
   298  	cache, err := wlru.New(weight, size)
   299  	if err != nil {
   300  		s.Log.Crit("Failed to create LRU cache", "err", err)
   301  		return nil
   302  	}
   303  	return cache
   304  }