github.com/dominant-strategies/go-quai@v0.28.2/eth/backend.go (about)

     1  // Copyright 2014 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 eth implements the Quai protocol.
    18  package eth
    19  
    20  import (
    21  	"fmt"
    22  	"math/big"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/dominant-strategies/go-quai/common"
    28  	"github.com/dominant-strategies/go-quai/consensus"
    29  	"github.com/dominant-strategies/go-quai/core"
    30  	"github.com/dominant-strategies/go-quai/core/bloombits"
    31  	"github.com/dominant-strategies/go-quai/core/rawdb"
    32  	"github.com/dominant-strategies/go-quai/core/state/pruner"
    33  	"github.com/dominant-strategies/go-quai/core/types"
    34  	"github.com/dominant-strategies/go-quai/core/vm"
    35  	"github.com/dominant-strategies/go-quai/eth/downloader"
    36  	"github.com/dominant-strategies/go-quai/eth/ethconfig"
    37  	"github.com/dominant-strategies/go-quai/eth/filters"
    38  	"github.com/dominant-strategies/go-quai/eth/gasprice"
    39  	"github.com/dominant-strategies/go-quai/eth/protocols/eth"
    40  	"github.com/dominant-strategies/go-quai/ethdb"
    41  	"github.com/dominant-strategies/go-quai/event"
    42  	"github.com/dominant-strategies/go-quai/internal/quaiapi"
    43  	"github.com/dominant-strategies/go-quai/log"
    44  	"github.com/dominant-strategies/go-quai/node"
    45  	"github.com/dominant-strategies/go-quai/p2p"
    46  	"github.com/dominant-strategies/go-quai/p2p/dnsdisc"
    47  	"github.com/dominant-strategies/go-quai/p2p/enode"
    48  	"github.com/dominant-strategies/go-quai/params"
    49  	"github.com/dominant-strategies/go-quai/rpc"
    50  )
    51  
    52  // Config contains the configuration options of the ETH protocol.
    53  // Deprecated: use ethconfig.Config instead.
    54  type Config = ethconfig.Config
    55  
    56  // Quai implements the Quai full node service.
    57  type Quai struct {
    58  	config *ethconfig.Config
    59  
    60  	// Handlers
    61  	core               *core.Core
    62  	handler            *handler
    63  	ethDialCandidates  enode.Iterator
    64  	snapDialCandidates enode.Iterator
    65  
    66  	// DB interfaces
    67  	chainDb ethdb.Database // Block chain database
    68  
    69  	eventMux *event.TypeMux
    70  	engine   consensus.Engine
    71  
    72  	bloomRequests     chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
    73  	bloomIndexer      *core.ChainIndexer             // Bloom indexer operating during block imports
    74  	closeBloomHandler chan struct{}
    75  
    76  	APIBackend *QuaiAPIBackend
    77  
    78  	gasPrice  *big.Int
    79  	etherbase common.Address
    80  
    81  	networkID     uint64
    82  	netRPCService *quaiapi.PublicNetAPI
    83  
    84  	p2pServer *p2p.Server
    85  
    86  	lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
    87  }
    88  
    89  // New creates a new Quai object (including the
    90  // initialisation of the common Quai object)
    91  func New(stack *node.Node, config *ethconfig.Config) (*Quai, error) {
    92  	nodeCtx := common.NodeLocation.Context()
    93  	// Ensure configuration values are compatible and sane
    94  	if !config.SyncMode.IsValid() {
    95  		return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
    96  	}
    97  	if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 {
    98  		log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice)
    99  		config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice)
   100  	}
   101  	if config.NoPruning && config.TrieDirtyCache > 0 {
   102  		if config.SnapshotCache > 0 {
   103  			config.TrieCleanCache += config.TrieDirtyCache * 3 / 5
   104  			config.SnapshotCache += config.TrieDirtyCache * 2 / 5
   105  		} else {
   106  			config.TrieCleanCache += config.TrieDirtyCache
   107  		}
   108  		config.TrieDirtyCache = 0
   109  	}
   110  	log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
   111  
   112  	// Assemble the Quai object
   113  	chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	chainConfig, _, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis)
   118  	if genesisErr != nil {
   119  		return nil, genesisErr
   120  	}
   121  
   122  	if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {
   123  		log.Error("Failed to recover state", "error", err)
   124  	}
   125  	eth := &Quai{
   126  		config:            config,
   127  		chainDb:           chainDb,
   128  		eventMux:          stack.EventMux(),
   129  		closeBloomHandler: make(chan struct{}),
   130  		networkID:         config.NetworkId,
   131  		gasPrice:          config.Miner.GasPrice,
   132  		etherbase:         config.Miner.Etherbase,
   133  		bloomRequests:     make(chan chan *bloombits.Retrieval),
   134  		p2pServer:         stack.Server(),
   135  	}
   136  
   137  	if config.ConsensusEngine == "blake3" {
   138  		blake3Config := config.Blake3Pow
   139  		blake3Config.NotifyFull = config.Miner.NotifyFull
   140  		eth.engine = ethconfig.CreateBlake3ConsensusEngine(stack, chainConfig, &blake3Config, config.Miner.Notify, config.Miner.Noverify, chainDb)
   141  	} else {
   142  		// Transfer mining-related config to the progpow config.
   143  		progpowConfig := config.Progpow
   144  		progpowConfig.NotifyFull = config.Miner.NotifyFull
   145  		eth.engine = ethconfig.CreateProgpowConsensusEngine(stack, chainConfig, &progpowConfig, config.Miner.Notify, config.Miner.Noverify, chainDb)
   146  	}
   147  	log.Info("Initialised chain configuration", "config", chainConfig)
   148  
   149  	bcVersion := rawdb.ReadDatabaseVersion(chainDb)
   150  	var dbVer = "<nil>"
   151  	if bcVersion != nil {
   152  		dbVer = fmt.Sprintf("%d", *bcVersion)
   153  	}
   154  	log.Info("Initialising Quai protocol", "network", config.NetworkId, "dbversion", dbVer)
   155  
   156  	if !config.SkipBcVersionCheck {
   157  		if bcVersion != nil && *bcVersion > core.BlockChainVersion {
   158  			return nil, fmt.Errorf("database version is v%d, Quai %s only supports v%d", *bcVersion, params.Version.Full(), core.BlockChainVersion)
   159  		} else if bcVersion == nil || *bcVersion < core.BlockChainVersion {
   160  			if bcVersion != nil { // only print warning on upgrade, not on init
   161  				log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
   162  			}
   163  			rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
   164  		}
   165  	}
   166  	var (
   167  		vmConfig = vm.Config{
   168  			EnablePreimageRecording: config.EnablePreimageRecording,
   169  		}
   170  		cacheConfig = &core.CacheConfig{
   171  			TrieCleanLimit:      config.TrieCleanCache,
   172  			TrieCleanJournal:    stack.ResolvePath(config.TrieCleanCacheJournal),
   173  			TrieCleanRejournal:  config.TrieCleanCacheRejournal,
   174  			TrieCleanNoPrefetch: config.NoPrefetch,
   175  			TrieDirtyLimit:      config.TrieDirtyCache,
   176  			TrieTimeLimit:       config.TrieTimeout,
   177  			SnapshotLimit:       config.SnapshotCache,
   178  			Preimages:           config.Preimages,
   179  		}
   180  	)
   181  
   182  	if config.TxPool.Journal != "" {
   183  		config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
   184  	}
   185  
   186  	eth.core, err = core.NewCore(chainDb, &config.Miner, eth.isLocalBlock, &config.TxPool, &config.TxLookupLimit, chainConfig, eth.config.SlicesRunning, eth.config.DomUrl, eth.config.SubUrls, eth.engine, cacheConfig, vmConfig, config.Genesis)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	// Only index bloom if processing state
   192  	if eth.core.ProcessingState() && nodeCtx == common.ZONE_CTX {
   193  		eth.bloomIndexer = core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms)
   194  		eth.bloomIndexer.Start(eth.Core().Slice().HeaderChain())
   195  	}
   196  
   197  	// Permit the downloader to use the trie cache allowance during fast sync
   198  	cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
   199  	if eth.handler, err = newHandler(&handlerConfig{
   200  		Database:      chainDb,
   201  		Core:          eth.core,
   202  		TxPool:        eth.core.TxPool(),
   203  		Network:       config.NetworkId,
   204  		Sync:          config.SyncMode,
   205  		BloomCache:    uint64(cacheLimit),
   206  		EventMux:      eth.eventMux,
   207  		Whitelist:     config.Whitelist,
   208  		SlicesRunning: config.SlicesRunning,
   209  	}); err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	eth.APIBackend = &QuaiAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil}
   214  	// Gasprice oracle is only initiated in zone chains
   215  	if nodeCtx == common.ZONE_CTX && eth.core.ProcessingState() {
   216  		gpoParams := config.GPO
   217  		if gpoParams.Default == nil {
   218  			gpoParams.Default = config.Miner.GasPrice
   219  		}
   220  		eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
   221  	}
   222  
   223  	// Setup DNS discovery iterators.
   224  	dnsclient := dnsdisc.NewClient(dnsdisc.Config{})
   225  	eth.ethDialCandidates, err = dnsclient.NewIterator(eth.config.EthDiscoveryURLs...)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	// Start the RPC service
   231  	eth.netRPCService = quaiapi.NewPublicNetAPI(eth.p2pServer, config.NetworkId)
   232  
   233  	// Register the backend on the node
   234  	stack.RegisterAPIs(eth.APIs())
   235  	stack.RegisterProtocols(eth.Protocols())
   236  	stack.RegisterLifecycle(eth)
   237  	// Check for unclean shutdown
   238  	if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
   239  		log.Error("Could not update unclean-shutdown-marker list", "error", err)
   240  	} else {
   241  		if discards > 0 {
   242  			log.Warn("Old unclean shutdowns found", "count", discards)
   243  		}
   244  		for _, tstamp := range uncleanShutdowns {
   245  			t := time.Unix(int64(tstamp), 0)
   246  			log.Warn("Unclean shutdown detected", "booted", t,
   247  				"age", common.PrettyAge(t))
   248  		}
   249  	}
   250  	return eth, nil
   251  }
   252  
   253  // APIs return the collection of RPC services the go-quai package offers.
   254  // NOTE, some of these services probably need to be moved to somewhere else.
   255  func (s *Quai) APIs() []rpc.API {
   256  	apis := quaiapi.GetAPIs(s.APIBackend)
   257  
   258  	// Append any APIs exposed explicitly by the consensus engine
   259  	apis = append(apis, s.engine.APIs(s.Core())...)
   260  
   261  	// Append all the local APIs and return
   262  	return append(apis, []rpc.API{
   263  		{
   264  			Namespace: "eth",
   265  			Version:   "1.0",
   266  			Service:   NewPublicQuaiAPI(s),
   267  			Public:    true,
   268  		}, {
   269  			Namespace: "eth",
   270  			Version:   "1.0",
   271  			Service:   NewPublicMinerAPI(s),
   272  			Public:    true,
   273  		}, {
   274  			Namespace: "eth",
   275  			Version:   "1.0",
   276  			Service:   downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
   277  			Public:    true,
   278  		}, {
   279  			Namespace: "miner",
   280  			Version:   "1.0",
   281  			Service:   NewPrivateMinerAPI(s),
   282  			Public:    false,
   283  		}, {
   284  			Namespace: "eth",
   285  			Version:   "1.0",
   286  			Service:   filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute),
   287  			Public:    true,
   288  		}, {
   289  			Namespace: "quai",
   290  			Version:   "1.0",
   291  			Service:   filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute),
   292  			Public:    true,
   293  		}, {
   294  			Namespace: "admin",
   295  			Version:   "1.0",
   296  			Service:   NewPrivateAdminAPI(s),
   297  		}, {
   298  			Namespace: "debug",
   299  			Version:   "1.0",
   300  			Service:   NewPublicDebugAPI(s),
   301  			Public:    true,
   302  		}, {
   303  			Namespace: "debug",
   304  			Version:   "1.0",
   305  			Service:   NewPrivateDebugAPI(s),
   306  		}, {
   307  			Namespace: "net",
   308  			Version:   "1.0",
   309  			Service:   s.netRPCService,
   310  			Public:    true,
   311  		},
   312  	}...)
   313  }
   314  
   315  func (s *Quai) Etherbase() (eb common.Address, err error) {
   316  	s.lock.RLock()
   317  	etherbase := s.etherbase
   318  	s.lock.RUnlock()
   319  
   320  	if !etherbase.Equal(common.ZeroAddr) {
   321  		return etherbase, nil
   322  	}
   323  
   324  	return common.ZeroAddr, fmt.Errorf("etherbase must be explicitly specified")
   325  }
   326  
   327  // isLocalBlock checks whether the specified block is mined
   328  // by local miner accounts.
   329  //
   330  // We regard two types of accounts as local miner account: etherbase
   331  // and accounts specified via `txpool.locals` flag.
   332  func (s *Quai) isLocalBlock(header *types.Header) bool {
   333  	author, err := s.engine.Author(header)
   334  	if err != nil {
   335  		log.Warn("Failed to retrieve block author", "number", header.NumberU64(), "hash", header.Hash(), "err", err)
   336  		return false
   337  	}
   338  	// Check whether the given address is etherbase.
   339  	s.lock.RLock()
   340  	etherbase := s.etherbase
   341  	s.lock.RUnlock()
   342  	if author.Equal(etherbase) {
   343  		return true
   344  	}
   345  	internal, err := author.InternalAddress()
   346  	if err != nil {
   347  		log.Error("Failed to retrieve author internal address", "err", err)
   348  	}
   349  	// Check whether the given address is specified by `txpool.local`
   350  	// CLI flag.
   351  	for _, account := range s.config.TxPool.Locals {
   352  		if account == internal {
   353  			return true
   354  		}
   355  	}
   356  	return false
   357  }
   358  
   359  // shouldPreserve checks whether we should preserve the given block
   360  // during the chain reorg depending on whether the author of block
   361  // is a local account.
   362  func (s *Quai) shouldPreserve(block *types.Block) bool {
   363  	return s.isLocalBlock(block.Header())
   364  }
   365  
   366  func (s *Quai) Core() *core.Core                   { return s.core }
   367  func (s *Quai) EventMux() *event.TypeMux           { return s.eventMux }
   368  func (s *Quai) Engine() consensus.Engine           { return s.engine }
   369  func (s *Quai) ChainDb() ethdb.Database            { return s.chainDb }
   370  func (s *Quai) IsListening() bool                  { return true } // Always listening
   371  func (s *Quai) Downloader() *downloader.Downloader { return s.handler.downloader }
   372  func (s *Quai) Synced() bool                       { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 }
   373  func (s *Quai) ArchiveMode() bool                  { return s.config.NoPruning }
   374  func (s *Quai) BloomIndexer() *core.ChainIndexer   { return s.bloomIndexer }
   375  
   376  // Protocols returns all the currently configured
   377  // network protocols to start.
   378  func (s *Quai) Protocols() []p2p.Protocol {
   379  	protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates)
   380  	return protos
   381  }
   382  
   383  // Start implements node.Lifecycle, starting all internal goroutines needed by the
   384  // Quai protocol implementation.
   385  func (s *Quai) Start() error {
   386  	eth.StartENRUpdater(s.core, s.p2pServer.LocalNode())
   387  
   388  	if s.core.ProcessingState() && common.NodeLocation.Context() == common.ZONE_CTX {
   389  		// Start the bloom bits servicing goroutines
   390  		s.startBloomHandlers(params.BloomBitsBlocks)
   391  	}
   392  
   393  	// Figure out a max peers count based on the server limits
   394  	maxPeers := s.p2pServer.MaxPeers
   395  	// Start the networking layer
   396  	s.handler.Start(maxPeers)
   397  	return nil
   398  }
   399  
   400  // Stop implements node.Lifecycle, terminating all internal goroutines used by the
   401  // Quai protocol.
   402  func (s *Quai) Stop() error {
   403  	// Stop all the peer-related stuff first.
   404  	s.ethDialCandidates.Close()
   405  	s.handler.Stop()
   406  
   407  	if s.core.ProcessingState() && common.NodeLocation.Context() == common.ZONE_CTX {
   408  		// Then stop everything else.
   409  		s.bloomIndexer.Close()
   410  		close(s.closeBloomHandler)
   411  	}
   412  	s.core.Stop()
   413  	s.engine.Close()
   414  	rawdb.PopUncleanShutdownMarker(s.chainDb)
   415  	s.chainDb.Close()
   416  	s.eventMux.Stop()
   417  
   418  	return nil
   419  }