github.com/MetalBlockchain/subnet-evm@v0.4.9/eth/backend.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 eth implements the Ethereum protocol.
    28  package eth
    29  
    30  import (
    31  	"errors"
    32  	"fmt"
    33  	"strings"
    34  	"sync"
    35  	"time"
    36  
    37  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    38  	"github.com/MetalBlockchain/subnet-evm/accounts"
    39  	"github.com/MetalBlockchain/subnet-evm/consensus"
    40  	"github.com/MetalBlockchain/subnet-evm/consensus/dummy"
    41  	"github.com/MetalBlockchain/subnet-evm/core"
    42  	"github.com/MetalBlockchain/subnet-evm/core/bloombits"
    43  	"github.com/MetalBlockchain/subnet-evm/core/rawdb"
    44  	"github.com/MetalBlockchain/subnet-evm/core/state/pruner"
    45  	"github.com/MetalBlockchain/subnet-evm/core/types"
    46  	"github.com/MetalBlockchain/subnet-evm/core/vm"
    47  	"github.com/MetalBlockchain/subnet-evm/eth/ethconfig"
    48  	"github.com/MetalBlockchain/subnet-evm/eth/filters"
    49  	"github.com/MetalBlockchain/subnet-evm/eth/gasprice"
    50  	"github.com/MetalBlockchain/subnet-evm/eth/tracers"
    51  	"github.com/MetalBlockchain/subnet-evm/ethdb"
    52  	"github.com/MetalBlockchain/subnet-evm/internal/ethapi"
    53  	"github.com/MetalBlockchain/subnet-evm/internal/shutdowncheck"
    54  	"github.com/MetalBlockchain/subnet-evm/miner"
    55  	"github.com/MetalBlockchain/subnet-evm/node"
    56  	"github.com/MetalBlockchain/subnet-evm/params"
    57  	"github.com/MetalBlockchain/subnet-evm/rpc"
    58  	"github.com/ethereum/go-ethereum/common"
    59  	"github.com/ethereum/go-ethereum/event"
    60  	"github.com/ethereum/go-ethereum/log"
    61  )
    62  
    63  // Config contains the configuration options of the ETH protocol.
    64  // Deprecated: use ethconfig.Config instead.
    65  type Config = ethconfig.Config
    66  
    67  var DefaultSettings Settings = Settings{MaxBlocksPerRequest: 2000}
    68  
    69  type Settings struct {
    70  	MaxBlocksPerRequest int64 // Maximum number of blocks to serve per getLogs request
    71  }
    72  
    73  // Ethereum implements the Ethereum full node service.
    74  type Ethereum struct {
    75  	config *Config
    76  
    77  	// Handlers
    78  	txPool     *core.TxPool
    79  	blockchain *core.BlockChain
    80  
    81  	// DB interfaces
    82  	chainDb ethdb.Database // Block chain database
    83  
    84  	eventMux       *event.TypeMux
    85  	engine         consensus.Engine
    86  	accountManager *accounts.Manager
    87  
    88  	bloomRequests     chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
    89  	bloomIndexer      *core.ChainIndexer             // Bloom indexer operating during block imports
    90  	closeBloomHandler chan struct{}
    91  
    92  	APIBackend *EthAPIBackend
    93  
    94  	miner     *miner.Miner
    95  	etherbase common.Address
    96  
    97  	networkID     uint64
    98  	netRPCService *ethapi.NetAPI
    99  
   100  	lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
   101  
   102  	shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
   103  
   104  	stackRPCs []rpc.API
   105  
   106  	settings Settings // Settings for Ethereum API
   107  }
   108  
   109  // roundUpCacheSize returns [input] rounded up to the next multiple of [allocSize]
   110  func roundUpCacheSize(input int, allocSize int) int {
   111  	cacheChunks := (input + allocSize - 1) / allocSize
   112  	return cacheChunks * allocSize
   113  }
   114  
   115  // New creates a new Ethereum object (including the
   116  // initialisation of the common Ethereum object)
   117  func New(
   118  	stack *node.Node,
   119  	config *Config,
   120  	chainDb ethdb.Database,
   121  	settings Settings,
   122  	lastAcceptedHash common.Hash,
   123  	clock *mockable.Clock,
   124  ) (*Ethereum, error) {
   125  	if chainDb == nil {
   126  		return nil, errors.New("chainDb cannot be nil")
   127  	}
   128  
   129  	// round TrieCleanCache and SnapshotCache up to nearest 64MB, since fastcache will mmap
   130  	// memory in 64MBs chunks.
   131  	config.TrieCleanCache = roundUpCacheSize(config.TrieCleanCache, 64)
   132  	config.SnapshotCache = roundUpCacheSize(config.SnapshotCache, 64)
   133  
   134  	log.Info(
   135  		"Allocated memory caches",
   136  		"trie clean", common.StorageSize(config.TrieCleanCache)*1024*1024,
   137  		"trie dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024,
   138  		"snapshot clean", common.StorageSize(config.SnapshotCache)*1024*1024,
   139  	)
   140  
   141  	chainConfig, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis, lastAcceptedHash, config.SkipUpgradeCheck)
   142  	if genesisErr != nil {
   143  		return nil, genesisErr
   144  	}
   145  	log.Info("")
   146  	log.Info(strings.Repeat("-", 153))
   147  	for _, line := range strings.Split(chainConfig.String(), "\n") {
   148  		log.Info(line)
   149  	}
   150  	log.Info(strings.Repeat("-", 153))
   151  	log.Info("")
   152  	// Free airdrop data to save memory usage
   153  	config.Genesis.AirdropData = nil
   154  
   155  	// Note: RecoverPruning must be called to handle the case that we are midway through offline pruning.
   156  	// If the data directory is changed in between runs preventing RecoverPruning from performing its job correctly,
   157  	// it may cause DB corruption.
   158  	// Since RecoverPruning will only continue a pruning run that already began, we do not need to ensure that
   159  	// reprocessState has already been called and completed successfully. To ensure this, we must maintain
   160  	// that Prune is only run after reprocessState has finished successfully.
   161  	if err := pruner.RecoverPruning(config.OfflinePruningDataDirectory, chainDb); err != nil {
   162  		log.Error("Failed to recover state", "error", err)
   163  	}
   164  	eth := &Ethereum{
   165  		config:            config,
   166  		chainDb:           chainDb,
   167  		eventMux:          new(event.TypeMux),
   168  		accountManager:    stack.AccountManager(),
   169  		engine:            dummy.NewFakerWithClock(clock),
   170  		closeBloomHandler: make(chan struct{}),
   171  		networkID:         config.NetworkId,
   172  		etherbase:         config.Miner.Etherbase,
   173  		bloomRequests:     make(chan chan *bloombits.Retrieval),
   174  		bloomIndexer:      core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
   175  		settings:          settings,
   176  		shutdownTracker:   shutdowncheck.NewShutdownTracker(chainDb),
   177  	}
   178  
   179  	bcVersion := rawdb.ReadDatabaseVersion(chainDb)
   180  	dbVer := "<nil>"
   181  	if bcVersion != nil {
   182  		dbVer = fmt.Sprintf("%d", *bcVersion)
   183  	}
   184  	log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer)
   185  
   186  	if !config.SkipBcVersionCheck {
   187  		if bcVersion != nil && *bcVersion > core.BlockChainVersion {
   188  			return nil, fmt.Errorf("database version is v%d, Subnet EVM %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion)
   189  		} else if bcVersion == nil || *bcVersion < core.BlockChainVersion {
   190  			log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
   191  			rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
   192  		}
   193  	}
   194  	var (
   195  		vmConfig = vm.Config{
   196  			EnablePreimageRecording: config.EnablePreimageRecording,
   197  			AllowUnfinalizedQueries: config.AllowUnfinalizedQueries,
   198  		}
   199  		cacheConfig = &core.CacheConfig{
   200  			TrieCleanLimit:                  config.TrieCleanCache,
   201  			TrieCleanJournal:                config.TrieCleanJournal,
   202  			TrieCleanRejournal:              config.TrieCleanRejournal,
   203  			TrieDirtyLimit:                  config.TrieDirtyCache,
   204  			TrieDirtyCommitTarget:           config.TrieDirtyCommitTarget,
   205  			Pruning:                         config.Pruning,
   206  			AcceptorQueueLimit:              config.AcceptorQueueLimit,
   207  			CommitInterval:                  config.CommitInterval,
   208  			PopulateMissingTries:            config.PopulateMissingTries,
   209  			PopulateMissingTriesParallelism: config.PopulateMissingTriesParallelism,
   210  			AllowMissingTries:               config.AllowMissingTries,
   211  			SnapshotDelayInit:               config.SnapshotDelayInit,
   212  			SnapshotLimit:                   config.SnapshotCache,
   213  			SnapshotAsync:                   config.SnapshotAsync,
   214  			SnapshotVerify:                  config.SnapshotVerify,
   215  			SkipSnapshotRebuild:             config.SkipSnapshotRebuild,
   216  			Preimages:                       config.Preimages,
   217  			AcceptedCacheSize:               config.AcceptedCacheSize,
   218  			TxLookupLimit:                   config.TxLookupLimit,
   219  		}
   220  	)
   221  
   222  	if err := eth.precheckPopulateMissingTries(); err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	var err error
   227  	eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, lastAcceptedHash)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	if err := eth.handleOfflinePruning(cacheConfig, chainConfig, vmConfig, lastAcceptedHash); err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	eth.bloomIndexer.Start(eth.blockchain)
   237  
   238  	config.TxPool.Journal = ""
   239  	eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain)
   240  
   241  	eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, clock)
   242  
   243  	allowUnprotectedTxHashes := make(map[common.Hash]struct{})
   244  	for _, txHash := range config.AllowUnprotectedTxHashes {
   245  		allowUnprotectedTxHashes[txHash] = struct{}{}
   246  	}
   247  
   248  	eth.APIBackend = &EthAPIBackend{
   249  		extRPCEnabled:            stack.Config().ExtRPCEnabled(),
   250  		allowUnprotectedTxs:      config.AllowUnprotectedTxs,
   251  		allowUnprotectedTxHashes: allowUnprotectedTxHashes,
   252  		eth:                      eth,
   253  	}
   254  	if config.AllowUnprotectedTxs {
   255  		log.Info("Unprotected transactions allowed")
   256  	}
   257  	gpoParams := config.GPO
   258  	eth.APIBackend.gpo, err = gasprice.NewOracle(eth.APIBackend, gpoParams)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	// Start the RPC service
   264  	eth.netRPCService = ethapi.NewNetAPI(eth.NetVersion())
   265  
   266  	eth.stackRPCs = stack.APIs()
   267  
   268  	// Successful startup; push a marker and check previous unclean shutdowns.
   269  	eth.shutdownTracker.MarkStartup()
   270  
   271  	return eth, nil
   272  }
   273  
   274  // APIs return the collection of RPC services the ethereum package offers.
   275  // NOTE, some of these services probably need to be moved to somewhere else.
   276  func (s *Ethereum) APIs() []rpc.API {
   277  	apis := ethapi.GetAPIs(s.APIBackend)
   278  
   279  	// Append tracing APIs
   280  	apis = append(apis, tracers.APIs(s.APIBackend)...)
   281  
   282  	// Add the APIs from the node
   283  	apis = append(apis, s.stackRPCs...)
   284  
   285  	// Create [filterSystem] with the log cache size set in the config.
   286  	filterSystem := filters.NewFilterSystem(s.APIBackend, filters.Config{
   287  		Timeout: 5 * time.Minute,
   288  	})
   289  
   290  	// Append all the local APIs and return
   291  	return append(apis, []rpc.API{
   292  		{
   293  			Namespace: "eth",
   294  			Service:   NewEthereumAPI(s),
   295  			Name:      "eth",
   296  		}, {
   297  			Namespace: "eth",
   298  			Service:   filters.NewFilterAPI(filterSystem, false /* isLightClient */),
   299  			Name:      "eth-filter",
   300  		}, {
   301  			Namespace: "admin",
   302  			Service:   NewAdminAPI(s),
   303  			Name:      "admin",
   304  		}, {
   305  			Namespace: "debug",
   306  			Service:   NewDebugAPI(s),
   307  			Name:      "debug",
   308  		}, {
   309  			Namespace: "net",
   310  			Service:   s.netRPCService,
   311  			Name:      "net",
   312  		},
   313  	}...)
   314  }
   315  
   316  func (s *Ethereum) Etherbase() (eb common.Address, err error) {
   317  	s.lock.RLock()
   318  	etherbase := s.etherbase
   319  	s.lock.RUnlock()
   320  
   321  	if etherbase != (common.Address{}) {
   322  		return etherbase, nil
   323  	}
   324  	if wallets := s.AccountManager().Wallets(); len(wallets) > 0 {
   325  		if accounts := wallets[0].Accounts(); len(accounts) > 0 {
   326  			etherbase := accounts[0].Address
   327  
   328  			s.lock.Lock()
   329  			s.etherbase = etherbase
   330  			s.lock.Unlock()
   331  
   332  			log.Info("Etherbase automatically configured", "address", etherbase)
   333  			return etherbase, nil
   334  		}
   335  	}
   336  	return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
   337  }
   338  
   339  // SetEtherbase sets the mining reward address.
   340  func (s *Ethereum) SetEtherbase(etherbase common.Address) {
   341  	s.lock.Lock()
   342  	s.etherbase = etherbase
   343  	s.lock.Unlock()
   344  
   345  	s.miner.SetEtherbase(etherbase)
   346  }
   347  
   348  func (s *Ethereum) Miner() *miner.Miner { return s.miner }
   349  
   350  func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
   351  func (s *Ethereum) BlockChain() *core.BlockChain      { return s.blockchain }
   352  func (s *Ethereum) TxPool() *core.TxPool              { return s.txPool }
   353  func (s *Ethereum) EventMux() *event.TypeMux          { return s.eventMux }
   354  func (s *Ethereum) Engine() consensus.Engine          { return s.engine }
   355  func (s *Ethereum) ChainDb() ethdb.Database           { return s.chainDb }
   356  
   357  func (s *Ethereum) NetVersion() uint64               { return s.networkID }
   358  func (s *Ethereum) ArchiveMode() bool                { return !s.config.Pruning }
   359  func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
   360  
   361  // Start implements node.Lifecycle, starting all internal goroutines needed by the
   362  // Ethereum protocol implementation.
   363  func (s *Ethereum) Start() {
   364  	// Start the bloom bits servicing goroutines
   365  	s.startBloomHandlers(params.BloomBitsBlocks)
   366  
   367  	// Regularly update shutdown marker
   368  	s.shutdownTracker.Start()
   369  }
   370  
   371  // Stop implements node.Lifecycle, terminating all internal goroutines used by the
   372  // Ethereum protocol.
   373  // FIXME remove error from type if this will never return an error
   374  func (s *Ethereum) Stop() error {
   375  	s.bloomIndexer.Close()
   376  	close(s.closeBloomHandler)
   377  	s.txPool.Stop()
   378  	s.blockchain.Stop()
   379  	s.engine.Close()
   380  
   381  	// Clean shutdown marker as the last thing before closing db
   382  	s.shutdownTracker.Stop()
   383  
   384  	s.chainDb.Close()
   385  	s.eventMux.Stop()
   386  	return nil
   387  }
   388  
   389  func (s *Ethereum) LastAcceptedBlock() *types.Block {
   390  	return s.blockchain.LastAcceptedBlock()
   391  }
   392  
   393  // precheckPopulateMissingTries returns an error if config flags should prevent
   394  // [populateMissingTries]
   395  //
   396  // NOTE: [populateMissingTries] is called from [New] to ensure all
   397  // state is repaired before any async processes (specifically snapshot re-generation)
   398  // are started which could interfere with historical re-generation.
   399  func (s *Ethereum) precheckPopulateMissingTries() error {
   400  	if s.config.PopulateMissingTries != nil && (s.config.Pruning || s.config.OfflinePruning) {
   401  		return fmt.Errorf("cannot run populate missing tries when pruning (enabled: %t)/offline pruning (enabled: %t) is enabled", s.config.Pruning, s.config.OfflinePruning)
   402  	}
   403  
   404  	if s.config.PopulateMissingTries == nil {
   405  		// Delete the populate missing tries marker to indicate that the node started with
   406  		// populate missing tries disabled.
   407  		if err := rawdb.DeletePopulateMissingTries(s.chainDb); err != nil {
   408  			return fmt.Errorf("failed to write populate missing tries disabled marker: %w", err)
   409  		}
   410  		return nil
   411  	}
   412  
   413  	if lastRun, err := rawdb.ReadPopulateMissingTries(s.chainDb); err == nil {
   414  		log.Error("Populate missing tries is not meant to be left enabled permanently. Please disable populate missing tries and allow your node to start successfully before running again.")
   415  		return fmt.Errorf("cannot start chain with populate missing tries enabled on consecutive starts (last=%v)", lastRun)
   416  	}
   417  
   418  	// Note: Time Marker is written inside of [populateMissingTries] once it
   419  	// succeeds inside of [NewBlockChain]
   420  	return nil
   421  }
   422  
   423  func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, chainConfig *params.ChainConfig, vmConfig vm.Config, lastAcceptedHash common.Hash) error {
   424  	if s.config.OfflinePruning && !s.config.Pruning {
   425  		return core.ErrRefuseToCorruptArchiver
   426  	}
   427  
   428  	if !s.config.OfflinePruning {
   429  		// Delete the offline pruning marker to indicate that the node started with offline pruning disabled.
   430  		if err := rawdb.DeleteOfflinePruning(s.chainDb); err != nil {
   431  			return fmt.Errorf("failed to write offline pruning disabled marker: %w", err)
   432  		}
   433  		return nil
   434  	}
   435  
   436  	// Perform offline pruning after NewBlockChain has been called to ensure that we have rolled back the chain
   437  	// to the last accepted block before pruning begins.
   438  	// If offline pruning marker is on disk, then we force the node to be started with offline pruning disabled
   439  	// before allowing another run of offline pruning.
   440  	if lastRun, err := rawdb.ReadOfflinePruning(s.chainDb); err == nil {
   441  		log.Error("Offline pruning is not meant to be left enabled permanently. Please disable offline pruning and allow your node to start successfully before running offline pruning again.")
   442  		return fmt.Errorf("cannot start chain with offline pruning enabled on consecutive starts (last=%v)", lastRun)
   443  	}
   444  
   445  	// Clean up middle roots
   446  	if err := s.blockchain.CleanBlockRootsAboveLastAccepted(); err != nil {
   447  		return err
   448  	}
   449  	targetRoot := s.blockchain.LastAcceptedBlock().Root()
   450  
   451  	// Allow the blockchain to be garbage collected immediately, since we will shut down the chain after offline pruning completes.
   452  	s.blockchain.Stop()
   453  	s.blockchain = nil
   454  	log.Info("Starting offline pruning", "dataDir", s.config.OfflinePruningDataDirectory, "bloomFilterSize", s.config.OfflinePruningBloomFilterSize)
   455  	pruner, err := pruner.NewPruner(s.chainDb, s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize)
   456  	if err != nil {
   457  		return fmt.Errorf("failed to create new pruner with data directory: %s, size: %d, due to: %w", s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize, err)
   458  	}
   459  	if err := pruner.Prune(targetRoot); err != nil {
   460  		return fmt.Errorf("failed to prune blockchain with target root: %s due to: %w", targetRoot, err)
   461  	}
   462  	// Note: Time Marker is written inside of [Prune] before compaction begins
   463  	// (considered an optional optimization)
   464  	s.blockchain, err = core.NewBlockChain(s.chainDb, cacheConfig, chainConfig, s.engine, vmConfig, lastAcceptedHash)
   465  	if err != nil {
   466  		return fmt.Errorf("failed to re-initialize blockchain after offline pruning: %w", err)
   467  	}
   468  
   469  	return nil
   470  }