github.com/DxChainNetwork/dxc@v0.8.1-0.20220824085222-1162e304b6e7/les/client.go (about)

     1  // Copyright 2016 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 les implements the Light Ethereum Subprotocol.
    18  package les
    19  
    20  import (
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/DxChainNetwork/dxc/accounts"
    25  	"github.com/DxChainNetwork/dxc/common"
    26  	"github.com/DxChainNetwork/dxc/common/hexutil"
    27  	"github.com/DxChainNetwork/dxc/common/mclock"
    28  	"github.com/DxChainNetwork/dxc/consensus"
    29  	"github.com/DxChainNetwork/dxc/core"
    30  	"github.com/DxChainNetwork/dxc/core/bloombits"
    31  	"github.com/DxChainNetwork/dxc/core/rawdb"
    32  	"github.com/DxChainNetwork/dxc/core/types"
    33  	"github.com/DxChainNetwork/dxc/eth/downloader"
    34  	"github.com/DxChainNetwork/dxc/eth/ethconfig"
    35  	"github.com/DxChainNetwork/dxc/eth/filters"
    36  	"github.com/DxChainNetwork/dxc/eth/gasprice"
    37  	"github.com/DxChainNetwork/dxc/event"
    38  	"github.com/DxChainNetwork/dxc/internal/ethapi"
    39  	"github.com/DxChainNetwork/dxc/les/vflux"
    40  	vfc "github.com/DxChainNetwork/dxc/les/vflux/client"
    41  	"github.com/DxChainNetwork/dxc/light"
    42  	"github.com/DxChainNetwork/dxc/log"
    43  	"github.com/DxChainNetwork/dxc/node"
    44  	"github.com/DxChainNetwork/dxc/p2p"
    45  	"github.com/DxChainNetwork/dxc/p2p/enode"
    46  	"github.com/DxChainNetwork/dxc/p2p/enr"
    47  	"github.com/DxChainNetwork/dxc/params"
    48  	"github.com/DxChainNetwork/dxc/rlp"
    49  	"github.com/DxChainNetwork/dxc/rpc"
    50  )
    51  
    52  type LightEthereum struct {
    53  	lesCommons
    54  
    55  	peers              *serverPeerSet
    56  	reqDist            *requestDistributor
    57  	retriever          *retrieveManager
    58  	odr                *LesOdr
    59  	relay              *lesTxRelay
    60  	handler            *clientHandler
    61  	txPool             *light.TxPool
    62  	blockchain         *light.LightChain
    63  	serverPool         *vfc.ServerPool
    64  	serverPoolIterator enode.Iterator
    65  	pruner             *pruner
    66  
    67  	bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
    68  	bloomIndexer  *core.ChainIndexer             // Bloom indexer operating during block imports
    69  
    70  	ApiBackend     *LesApiBackend
    71  	eventMux       *event.TypeMux
    72  	engine         consensus.Engine
    73  	accountManager *accounts.Manager
    74  	netRPCService  *ethapi.PublicNetAPI
    75  
    76  	p2pServer  *p2p.Server
    77  	p2pConfig  *p2p.Config
    78  	udpEnabled bool
    79  }
    80  
    81  // New creates an instance of the light client.
    82  func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
    83  	chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/", false)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideLondon)
    92  	if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat {
    93  		return nil, genesisErr
    94  	}
    95  	log.Info("Initialised chain configuration", "config", chainConfig)
    96  
    97  	peers := newServerPeerSet()
    98  	leth := &LightEthereum{
    99  		lesCommons: lesCommons{
   100  			genesis:     genesisHash,
   101  			config:      config,
   102  			chainConfig: chainConfig,
   103  			iConfig:     light.DefaultClientIndexerConfig,
   104  			chainDb:     chainDb,
   105  			lesDb:       lesDb,
   106  			closeCh:     make(chan struct{}),
   107  		},
   108  		peers:          peers,
   109  		eventMux:       stack.EventMux(),
   110  		reqDist:        newRequestDistributor(peers, &mclock.System{}),
   111  		accountManager: stack.AccountManager(),
   112  		engine:         ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb),
   113  		bloomRequests:  make(chan chan *bloombits.Retrieval),
   114  		bloomIndexer:   core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
   115  		p2pServer:      stack.Server(),
   116  		p2pConfig:      &stack.Config().P2P,
   117  		udpEnabled:     stack.Config().P2P.DiscoveryV5,
   118  	}
   119  
   120  	var prenegQuery vfc.QueryFunc
   121  	if leth.udpEnabled {
   122  		prenegQuery = leth.prenegQuery
   123  	}
   124  	leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, config.UltraLightServers, requestList)
   125  	leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter)
   126  
   127  	leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout)
   128  	leth.relay = newLesTxRelay(peers, leth.retriever)
   129  
   130  	leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever)
   131  	leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune)
   132  	leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune)
   133  	leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)
   134  
   135  	checkpoint := config.Checkpoint
   136  	if checkpoint == nil {
   137  		checkpoint = params.TrustedCheckpoints[genesisHash]
   138  	}
   139  	// Note: NewLightChain adds the trusted checkpoint so it needs an ODR with
   140  	// indexers already set but not started yet
   141  	if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine, checkpoint); err != nil {
   142  		return nil, err
   143  	}
   144  	leth.chainReader = leth.blockchain
   145  	leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
   146  
   147  	// Set up checkpoint oracle.
   148  	leth.oracle = leth.setupOracle(stack, genesisHash, config)
   149  
   150  	// Note: AddChildIndexer starts the update process for the child
   151  	leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
   152  	leth.chtIndexer.Start(leth.blockchain)
   153  	leth.bloomIndexer.Start(leth.blockchain)
   154  
   155  	// Start a light chain pruner to delete useless historical data.
   156  	leth.pruner = newPruner(chainDb, leth.chtIndexer, leth.bloomTrieIndexer)
   157  
   158  	// Rewind the chain in case of an incompatible config upgrade.
   159  	if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
   160  		log.Warn("Rewinding chain to upgrade configuration", "err", compat)
   161  		leth.blockchain.SetHead(compat.RewindTo)
   162  		rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
   163  	}
   164  
   165  	leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth, nil}
   166  	gpoParams := config.GPO
   167  	if gpoParams.Default == nil {
   168  		gpoParams.Default = config.Miner.GasPrice
   169  	}
   170  	leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
   171  
   172  	leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth)
   173  	if leth.handler.ulc != nil {
   174  		log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction)
   175  		leth.blockchain.DisableCheckFreq()
   176  	}
   177  
   178  	leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId)
   179  
   180  	// Register the backend on the node
   181  	stack.RegisterAPIs(leth.APIs())
   182  	stack.RegisterProtocols(leth.Protocols())
   183  	stack.RegisterLifecycle(leth)
   184  
   185  	// Check for unclean shutdown
   186  	if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
   187  		log.Error("Could not update unclean-shutdown-marker list", "error", err)
   188  	} else {
   189  		if discards > 0 {
   190  			log.Warn("Old unclean shutdowns found", "count", discards)
   191  		}
   192  		for _, tstamp := range uncleanShutdowns {
   193  			t := time.Unix(int64(tstamp), 0)
   194  			log.Warn("Unclean shutdown detected", "booted", t,
   195  				"age", common.PrettyAge(t))
   196  		}
   197  	}
   198  	return leth, nil
   199  }
   200  
   201  // VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses
   202  func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies {
   203  	if !s.udpEnabled {
   204  		return nil
   205  	}
   206  	reqsEnc, _ := rlp.EncodeToBytes(&reqs)
   207  	repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc)
   208  	var replies vflux.Replies
   209  	if len(repliesEnc) == 0 || rlp.DecodeBytes(repliesEnc, &replies) != nil {
   210  		return nil
   211  	}
   212  	return replies
   213  }
   214  
   215  // vfxVersion returns the version number of the "les" service subdomain of the vflux UDP
   216  // service, as advertised in the ENR record
   217  func (s *LightEthereum) vfxVersion(n *enode.Node) uint {
   218  	if n.Seq() == 0 {
   219  		var err error
   220  		if !s.udpEnabled {
   221  			return 0
   222  		}
   223  		if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 {
   224  			s.serverPool.Persist(n)
   225  		} else {
   226  			return 0
   227  		}
   228  	}
   229  
   230  	var les []rlp.RawValue
   231  	if err := n.Load(enr.WithEntry("les", &les)); err != nil || len(les) < 1 {
   232  		return 0
   233  	}
   234  	var version uint
   235  	rlp.DecodeBytes(les[0], &version) // Ignore additional fields (for forward compatibility).
   236  	return version
   237  }
   238  
   239  // prenegQuery sends a capacity query to the given server node to determine whether
   240  // a connection slot is immediately available
   241  func (s *LightEthereum) prenegQuery(n *enode.Node) int {
   242  	if s.vfxVersion(n) < 1 {
   243  		// UDP query not supported, always try TCP connection
   244  		return 1
   245  	}
   246  
   247  	var requests vflux.Requests
   248  	requests.Add("les", vflux.CapacityQueryName, vflux.CapacityQueryReq{
   249  		Bias:      180,
   250  		AddTokens: []vflux.IntOrInf{{}},
   251  	})
   252  	replies := s.VfluxRequest(n, requests)
   253  	var cqr vflux.CapacityQueryReply
   254  	if replies.Get(0, &cqr) != nil || len(cqr) != 1 { // Note: Get returns an error if replies is nil
   255  		return -1
   256  	}
   257  	if cqr[0] > 0 {
   258  		return 1
   259  	}
   260  	return 0
   261  }
   262  
   263  type LightDummyAPI struct{}
   264  
   265  // Etherbase is the address that mining rewards will be send to
   266  func (s *LightDummyAPI) Etherbase() (common.Address, error) {
   267  	return common.Address{}, fmt.Errorf("mining is not supported in light mode")
   268  }
   269  
   270  // Coinbase is the address that mining rewards will be send to (alias for Etherbase)
   271  func (s *LightDummyAPI) Coinbase() (common.Address, error) {
   272  	return common.Address{}, fmt.Errorf("mining is not supported in light mode")
   273  }
   274  
   275  // Hashrate returns the POW hashrate
   276  func (s *LightDummyAPI) Hashrate() hexutil.Uint {
   277  	return 0
   278  }
   279  
   280  // Mining returns an indication if this node is currently mining.
   281  func (s *LightDummyAPI) Mining() bool {
   282  	return false
   283  }
   284  
   285  // APIs returns the collection of RPC services the ethereum package offers.
   286  // NOTE, some of these services probably need to be moved to somewhere else.
   287  func (s *LightEthereum) APIs() []rpc.API {
   288  	apis := ethapi.GetAPIs(s.ApiBackend)
   289  	apis = append(apis, s.engine.APIs(s.BlockChain().HeaderChain())...)
   290  	return append(apis, []rpc.API{
   291  		{
   292  			Namespace: "eth",
   293  			Version:   "1.0",
   294  			Service:   &LightDummyAPI{},
   295  			Public:    true,
   296  		}, {
   297  			Namespace: "eth",
   298  			Version:   "1.0",
   299  			Service:   downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
   300  			Public:    true,
   301  		}, {
   302  			Namespace: "eth",
   303  			Version:   "1.0",
   304  			Service:   filters.NewPublicFilterAPI(s.ApiBackend, true, 5*time.Minute),
   305  			Public:    true,
   306  		}, {
   307  			Namespace: "net",
   308  			Version:   "1.0",
   309  			Service:   s.netRPCService,
   310  			Public:    true,
   311  		}, {
   312  			Namespace: "les",
   313  			Version:   "1.0",
   314  			Service:   NewPrivateLightAPI(&s.lesCommons),
   315  			Public:    false,
   316  		}, {
   317  			Namespace: "vflux",
   318  			Version:   "1.0",
   319  			Service:   s.serverPool.API(),
   320  			Public:    false,
   321  		},
   322  	}...)
   323  }
   324  
   325  func (s *LightEthereum) ResetWithGenesisBlock(gb *types.Block) {
   326  	s.blockchain.ResetWithGenesisBlock(gb)
   327  }
   328  
   329  func (s *LightEthereum) BlockChain() *light.LightChain      { return s.blockchain }
   330  func (s *LightEthereum) TxPool() *light.TxPool              { return s.txPool }
   331  func (s *LightEthereum) Engine() consensus.Engine           { return s.engine }
   332  func (s *LightEthereum) LesVersion() int                    { return int(ClientProtocolVersions[0]) }
   333  func (s *LightEthereum) Downloader() *downloader.Downloader { return s.handler.downloader }
   334  func (s *LightEthereum) EventMux() *event.TypeMux           { return s.eventMux }
   335  
   336  // Protocols returns all the currently configured network protocols to start.
   337  func (s *LightEthereum) Protocols() []p2p.Protocol {
   338  	return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
   339  		if p := s.peers.peer(id.String()); p != nil {
   340  			return p.Info()
   341  		}
   342  		return nil
   343  	}, s.serverPoolIterator)
   344  }
   345  
   346  // Start implements node.Lifecycle, starting all internal goroutines needed by the
   347  // light ethereum protocol implementation.
   348  func (s *LightEthereum) Start() error {
   349  	log.Warn("Light client mode is an experimental feature")
   350  
   351  	if s.udpEnabled && s.p2pServer.DiscV5 == nil {
   352  		s.udpEnabled = false
   353  		log.Error("Discovery v5 is not initialized")
   354  	}
   355  	discovery, err := s.setupDiscovery()
   356  	if err != nil {
   357  		return err
   358  	}
   359  	s.serverPool.AddSource(discovery)
   360  	s.serverPool.Start()
   361  	// Start bloom request workers.
   362  	s.wg.Add(bloomServiceThreads)
   363  	s.startBloomHandlers(params.BloomBitsBlocksClient)
   364  	s.handler.start()
   365  
   366  	return nil
   367  }
   368  
   369  // Stop implements node.Lifecycle, terminating all internal goroutines used by the
   370  // Ethereum protocol.
   371  func (s *LightEthereum) Stop() error {
   372  	close(s.closeCh)
   373  	s.serverPool.Stop()
   374  	s.peers.close()
   375  	s.reqDist.close()
   376  	s.odr.Stop()
   377  	s.relay.Stop()
   378  	s.bloomIndexer.Close()
   379  	s.chtIndexer.Close()
   380  	s.blockchain.Stop()
   381  	s.handler.stop()
   382  	s.txPool.Stop()
   383  	s.engine.Close()
   384  	s.pruner.close()
   385  	s.eventMux.Stop()
   386  	rawdb.PopUncleanShutdownMarker(s.chainDb)
   387  	s.chainDb.Close()
   388  	s.lesDb.Close()
   389  	s.wg.Wait()
   390  	log.Info("Light ethereum stopped")
   391  	return nil
   392  }