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