github.com/MetalBlockchain/subnet-evm@v0.4.9/core/state_manager.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2014 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  package core
    28  
    29  import (
    30  	"fmt"
    31  	"math/rand"
    32  	"time"
    33  
    34  	"github.com/MetalBlockchain/subnet-evm/core/types"
    35  	"github.com/MetalBlockchain/subnet-evm/ethdb"
    36  	"github.com/ethereum/go-ethereum/common"
    37  )
    38  
    39  func init() {
    40  	rand.Seed(time.Now().UnixNano())
    41  }
    42  
    43  const (
    44  	// tipBufferSize is the number of recent accepted tries to keep in the TrieDB
    45  	// dirties cache at tip (only applicable in [pruning] mode).
    46  	//
    47  	// Keeping extra tries around at tip enables clients to query data from
    48  	// recent trie roots.
    49  	tipBufferSize = 32
    50  
    51  	// flushWindow is the distance to the [commitInterval] when we start
    52  	// optimistically flushing trie nodes to disk (only applicable in [pruning]
    53  	// mode).
    54  	//
    55  	// We perform this optimistic flushing to reduce synchronized database IO at the
    56  	// [commitInterval].
    57  	flushWindow = 768
    58  )
    59  
    60  type TrieWriter interface {
    61  	InsertTrie(block *types.Block) error // Handle inserted trie reference of [root]
    62  	AcceptTrie(block *types.Block) error // Mark [root] as part of an accepted block
    63  	RejectTrie(block *types.Block) error // Notify TrieWriter that the block containing [root] has been rejected
    64  	Shutdown() error
    65  }
    66  
    67  type TrieDB interface {
    68  	Dereference(root common.Hash)
    69  	Commit(root common.Hash, report bool, callback func(common.Hash)) error
    70  	Size() (common.StorageSize, common.StorageSize)
    71  	Cap(limit common.StorageSize) error
    72  }
    73  
    74  func NewTrieWriter(db TrieDB, config *CacheConfig) TrieWriter {
    75  	if config.Pruning {
    76  		cm := &cappedMemoryTrieWriter{
    77  			TrieDB:           db,
    78  			memoryCap:        common.StorageSize(config.TrieDirtyLimit) * 1024 * 1024,
    79  			targetCommitSize: common.StorageSize(config.TrieDirtyCommitTarget) * 1024 * 1024,
    80  			imageCap:         4 * 1024 * 1024,
    81  			commitInterval:   config.CommitInterval,
    82  			tipBuffer:        NewBoundedBuffer(tipBufferSize, db.Dereference),
    83  		}
    84  		cm.flushStepSize = (cm.memoryCap - cm.targetCommitSize) / common.StorageSize(flushWindow)
    85  		return cm
    86  	} else {
    87  		return &noPruningTrieWriter{
    88  			TrieDB: db,
    89  		}
    90  	}
    91  }
    92  
    93  type noPruningTrieWriter struct {
    94  	TrieDB
    95  }
    96  
    97  func (np *noPruningTrieWriter) InsertTrie(block *types.Block) error {
    98  	// We don't attempt to [Cap] here because we should never have
    99  	// a significant amount of [TrieDB.Dirties] (we commit each block).
   100  	return nil
   101  }
   102  
   103  func (np *noPruningTrieWriter) AcceptTrie(block *types.Block) error {
   104  	// We don't need to call [Dereference] on the block root at the end of this
   105  	// function because it is removed from the [TrieDB.Dirties] map in [Commit].
   106  	return np.TrieDB.Commit(block.Root(), false, nil)
   107  }
   108  
   109  func (np *noPruningTrieWriter) RejectTrie(block *types.Block) error {
   110  	np.TrieDB.Dereference(block.Root())
   111  	return nil
   112  }
   113  
   114  func (np *noPruningTrieWriter) Shutdown() error { return nil }
   115  
   116  type cappedMemoryTrieWriter struct {
   117  	TrieDB
   118  	memoryCap        common.StorageSize
   119  	targetCommitSize common.StorageSize
   120  	flushStepSize    common.StorageSize
   121  	imageCap         common.StorageSize
   122  	commitInterval   uint64
   123  
   124  	tipBuffer *BoundedBuffer[common.Hash]
   125  }
   126  
   127  func (cm *cappedMemoryTrieWriter) InsertTrie(block *types.Block) error {
   128  	// The use of [Cap] in [InsertTrie] prevents exceeding the configured memory
   129  	// limit (and OOM) in case there is a large backlog of processing (unaccepted) blocks.
   130  	nodes, imgs := cm.TrieDB.Size()
   131  	if nodes <= cm.memoryCap && imgs <= cm.imageCap {
   132  		return nil
   133  	}
   134  	if err := cm.TrieDB.Cap(cm.memoryCap - ethdb.IdealBatchSize); err != nil {
   135  		return fmt.Errorf("failed to cap trie for block %s: %w", block.Hash().Hex(), err)
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error {
   142  	root := block.Root()
   143  
   144  	// Attempt to dereference roots at least [tipBufferSize] old (so queries at tip
   145  	// can still be completed).
   146  	//
   147  	// Note: It is safe to dereference roots that have been committed to disk
   148  	// (they are no-ops).
   149  	cm.tipBuffer.Insert(root)
   150  
   151  	// Commit this root if we have reached the [commitInterval].
   152  	modCommitInterval := block.NumberU64() % cm.commitInterval
   153  	if modCommitInterval == 0 {
   154  		if err := cm.TrieDB.Commit(root, true, nil); err != nil {
   155  			return fmt.Errorf("failed to commit trie for block %s: %w", block.Hash().Hex(), err)
   156  		}
   157  		return nil
   158  	}
   159  
   160  	// Write at least [flushStepSize] of the oldest nodes in the trie database
   161  	// dirty cache to disk as we approach the [commitInterval] to reduce the number of trie nodes
   162  	// that will need to be written at once on [Commit] (to roughly [targetCommitSize]).
   163  	//
   164  	// To reduce the number of useless trie nodes that are committed during this
   165  	// capping, we only optimistically flush within the [flushWindow]. During
   166  	// this period, the [targetMemory] decreases stepwise by [flushStepSize]
   167  	// as we get closer to the commit boundary.
   168  	//
   169  	// Most trie nodes are 300B, so we will write at least ~1000 trie nodes in
   170  	// a single optimistic flush (with the default [flushStepSize]=312KB).
   171  	distanceFromCommit := cm.commitInterval - modCommitInterval // this cannot be 0
   172  	if distanceFromCommit > flushWindow {
   173  		return nil
   174  	}
   175  	targetMemory := cm.targetCommitSize + cm.flushStepSize*common.StorageSize(distanceFromCommit)
   176  	nodes, _ := cm.TrieDB.Size()
   177  	if nodes <= targetMemory {
   178  		return nil
   179  	}
   180  	targetCap := targetMemory - ethdb.IdealBatchSize
   181  	if err := cm.TrieDB.Cap(targetCap); err != nil {
   182  		return fmt.Errorf("failed to cap trie for block %s (target=%s): %w", block.Hash().Hex(), targetCap, err)
   183  	}
   184  	return nil
   185  }
   186  
   187  func (cm *cappedMemoryTrieWriter) RejectTrie(block *types.Block) error {
   188  	cm.TrieDB.Dereference(block.Root())
   189  	return nil
   190  }
   191  
   192  func (cm *cappedMemoryTrieWriter) Shutdown() error {
   193  	// If [tipBuffer] entry is empty, no need to do any cleanup on
   194  	// shutdown.
   195  	last, exists := cm.tipBuffer.Last()
   196  	if !exists {
   197  		return nil
   198  	}
   199  
   200  	// Attempt to commit last item added to [dereferenceQueue] on shutdown to avoid
   201  	// re-processing the state on the next startup.
   202  	return cm.TrieDB.Commit(last, true, nil)
   203  }