github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/server.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
    18  
    19  import (
    20  	"crypto/ecdsa"
    21  	"reflect"
    22  	"time"
    23  
    24  	"github.com/cryptogateway/go-paymex/common/mclock"
    25  	"github.com/cryptogateway/go-paymex/eth"
    26  	"github.com/cryptogateway/go-paymex/les/flowcontrol"
    27  	lps "github.com/cryptogateway/go-paymex/les/lespay/server"
    28  	"github.com/cryptogateway/go-paymex/light"
    29  	"github.com/cryptogateway/go-paymex/log"
    30  	"github.com/cryptogateway/go-paymex/node"
    31  	"github.com/cryptogateway/go-paymex/p2p"
    32  	"github.com/cryptogateway/go-paymex/p2p/enode"
    33  	"github.com/cryptogateway/go-paymex/p2p/enr"
    34  	"github.com/cryptogateway/go-paymex/p2p/nodestate"
    35  	"github.com/cryptogateway/go-paymex/params"
    36  	"github.com/cryptogateway/go-paymex/rpc"
    37  )
    38  
    39  var (
    40  	serverSetup         = &nodestate.Setup{}
    41  	clientPeerField     = serverSetup.NewField("clientPeer", reflect.TypeOf(&clientPeer{}))
    42  	clientInfoField     = serverSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{}))
    43  	connAddressField    = serverSetup.NewField("connAddr", reflect.TypeOf(""))
    44  	balanceTrackerSetup = lps.NewBalanceTrackerSetup(serverSetup)
    45  	priorityPoolSetup   = lps.NewPriorityPoolSetup(serverSetup)
    46  )
    47  
    48  func init() {
    49  	balanceTrackerSetup.Connect(connAddressField, priorityPoolSetup.CapacityField)
    50  	priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority
    51  }
    52  
    53  type LesServer struct {
    54  	lesCommons
    55  
    56  	ns          *nodestate.NodeStateMachine
    57  	archiveMode bool // Flag whether the ethereum node runs in archive mode.
    58  	handler     *serverHandler
    59  	broadcaster *broadcaster
    60  	privateKey  *ecdsa.PrivateKey
    61  
    62  	// Flow control and capacity management
    63  	fcManager    *flowcontrol.ClientManager
    64  	costTracker  *costTracker
    65  	defParams    flowcontrol.ServerParams
    66  	servingQueue *servingQueue
    67  	clientPool   *clientPool
    68  
    69  	minCapacity, maxCapacity uint64
    70  	threadsIdle              int // Request serving threads count when system is idle.
    71  	threadsBusy              int // Request serving threads count when system is busy(block insertion).
    72  
    73  	p2pSrv *p2p.Server
    74  }
    75  
    76  func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
    77  	ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup)
    78  	// Calculate the number of threads used to service the light client
    79  	// requests based on the user-specified value.
    80  	threads := config.LightServ * 4 / 100
    81  	if threads < 4 {
    82  		threads = 4
    83  	}
    84  	srv := &LesServer{
    85  		lesCommons: lesCommons{
    86  			genesis:          e.BlockChain().Genesis().Hash(),
    87  			config:           config,
    88  			chainConfig:      e.BlockChain().Config(),
    89  			iConfig:          light.DefaultServerIndexerConfig,
    90  			chainDb:          e.ChainDb(),
    91  			chainReader:      e.BlockChain(),
    92  			chtIndexer:       light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations, true),
    93  			bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true),
    94  			closeCh:          make(chan struct{}),
    95  		},
    96  		ns:           ns,
    97  		archiveMode:  e.ArchiveMode(),
    98  		broadcaster:  newBroadcaster(ns),
    99  		fcManager:    flowcontrol.NewClientManager(nil, &mclock.System{}),
   100  		servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100),
   101  		threadsBusy:  config.LightServ/100 + 1,
   102  		threadsIdle:  threads,
   103  		p2pSrv:       node.Server(),
   104  	}
   105  	srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced)
   106  	srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config)
   107  	srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config)
   108  
   109  	// Initialize the bloom trie indexer.
   110  	e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer)
   111  
   112  	// Initialize server capacity management fields.
   113  	srv.defParams = flowcontrol.ServerParams{
   114  		BufLimit:    srv.minCapacity * bufLimitRatio,
   115  		MinRecharge: srv.minCapacity,
   116  	}
   117  	// LES flow control tries to more or less guarantee the possibility for the
   118  	// clients to send a certain amount of requests at any time and get a quick
   119  	// response. Most of the clients want this guarantee but don't actually need
   120  	// to send requests most of the time. Our goal is to serve as many clients as
   121  	// possible while the actually used server capacity does not exceed the limits
   122  	totalRecharge := srv.costTracker.totalRecharge()
   123  	srv.maxCapacity = srv.minCapacity * uint64(srv.config.LightPeers)
   124  	if totalRecharge > srv.maxCapacity {
   125  		srv.maxCapacity = totalRecharge
   126  	}
   127  	srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2)
   128  	srv.clientPool = newClientPool(ns, srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient)
   129  	srv.clientPool.setDefaultFactors(lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1})
   130  
   131  	checkpoint := srv.latestLocalCheckpoint()
   132  	if !checkpoint.Empty() {
   133  		log.Info("Loaded latest checkpoint", "section", checkpoint.SectionIndex, "head", checkpoint.SectionHead,
   134  			"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
   135  	}
   136  	srv.chtIndexer.Start(e.BlockChain())
   137  
   138  	node.RegisterProtocols(srv.Protocols())
   139  	node.RegisterAPIs(srv.APIs())
   140  	node.RegisterLifecycle(srv)
   141  
   142  	// disconnect all peers at nsm shutdown
   143  	ns.SubscribeField(clientPeerField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
   144  		if state.Equals(serverSetup.OfflineFlag()) && oldValue != nil {
   145  			oldValue.(*clientPeer).Peer.Disconnect(p2p.DiscRequested)
   146  		}
   147  	})
   148  	ns.Start()
   149  	return srv, nil
   150  }
   151  
   152  func (s *LesServer) APIs() []rpc.API {
   153  	return []rpc.API{
   154  		{
   155  			Namespace: "les",
   156  			Version:   "1.0",
   157  			Service:   NewPrivateLightAPI(&s.lesCommons),
   158  			Public:    false,
   159  		},
   160  		{
   161  			Namespace: "les",
   162  			Version:   "1.0",
   163  			Service:   NewPrivateLightServerAPI(s),
   164  			Public:    false,
   165  		},
   166  		{
   167  			Namespace: "debug",
   168  			Version:   "1.0",
   169  			Service:   NewPrivateDebugAPI(s),
   170  			Public:    false,
   171  		},
   172  	}
   173  }
   174  
   175  func (s *LesServer) Protocols() []p2p.Protocol {
   176  	ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
   177  		if p := s.getClient(id); p != nil {
   178  			return p.Info()
   179  		}
   180  		return nil
   181  	}, nil)
   182  	// Add "les" ENR entries.
   183  	for i := range ps {
   184  		ps[i].Attributes = []enr.Entry{&lesEntry{}}
   185  	}
   186  	return ps
   187  }
   188  
   189  // Start starts the LES server
   190  func (s *LesServer) Start() error {
   191  	s.privateKey = s.p2pSrv.PrivateKey
   192  	s.broadcaster.setSignerKey(s.privateKey)
   193  	s.handler.start()
   194  
   195  	s.wg.Add(1)
   196  	go s.capacityManagement()
   197  
   198  	return nil
   199  }
   200  
   201  // Stop stops the LES service
   202  func (s *LesServer) Stop() error {
   203  	close(s.closeCh)
   204  
   205  	s.clientPool.stop()
   206  	s.ns.Stop()
   207  	s.fcManager.Stop()
   208  	s.costTracker.stop()
   209  	s.handler.stop()
   210  	s.servingQueue.stop()
   211  
   212  	// Note, bloom trie indexer is closed by parent bloombits indexer.
   213  	s.chtIndexer.Close()
   214  	s.wg.Wait()
   215  	log.Info("Les server stopped")
   216  
   217  	return nil
   218  }
   219  
   220  // capacityManagement starts an event handler loop that updates the recharge curve of
   221  // the client manager and adjusts the client pool's size according to the total
   222  // capacity updates coming from the client manager
   223  func (s *LesServer) capacityManagement() {
   224  	defer s.wg.Done()
   225  
   226  	processCh := make(chan bool, 100)
   227  	sub := s.handler.blockchain.SubscribeBlockProcessingEvent(processCh)
   228  	defer sub.Unsubscribe()
   229  
   230  	totalRechargeCh := make(chan uint64, 100)
   231  	totalRecharge := s.costTracker.subscribeTotalRecharge(totalRechargeCh)
   232  
   233  	totalCapacityCh := make(chan uint64, 100)
   234  	totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
   235  	s.clientPool.setLimits(s.config.LightPeers, totalCapacity)
   236  
   237  	var (
   238  		busy         bool
   239  		freePeers    uint64
   240  		blockProcess mclock.AbsTime
   241  	)
   242  	updateRecharge := func() {
   243  		if busy {
   244  			s.servingQueue.setThreads(s.threadsBusy)
   245  			s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}})
   246  		} else {
   247  			s.servingQueue.setThreads(s.threadsIdle)
   248  			s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 10, totalRecharge}, {totalRecharge, totalRecharge}})
   249  		}
   250  	}
   251  	updateRecharge()
   252  
   253  	for {
   254  		select {
   255  		case busy = <-processCh:
   256  			if busy {
   257  				blockProcess = mclock.Now()
   258  			} else {
   259  				blockProcessingTimer.Update(time.Duration(mclock.Now() - blockProcess))
   260  			}
   261  			updateRecharge()
   262  		case totalRecharge = <-totalRechargeCh:
   263  			totalRechargeGauge.Update(int64(totalRecharge))
   264  			updateRecharge()
   265  		case totalCapacity = <-totalCapacityCh:
   266  			totalCapacityGauge.Update(int64(totalCapacity))
   267  			newFreePeers := totalCapacity / s.minCapacity
   268  			if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) {
   269  				log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers)
   270  			}
   271  			freePeers = newFreePeers
   272  			s.clientPool.setLimits(s.config.LightPeers, totalCapacity)
   273  		case <-s.closeCh:
   274  			return
   275  		}
   276  	}
   277  }
   278  
   279  func (s *LesServer) getClient(id enode.ID) *clientPeer {
   280  	if node := s.ns.GetNode(id); node != nil {
   281  		if p, ok := s.ns.GetField(node, clientPeerField).(*clientPeer); ok {
   282  			return p
   283  		}
   284  	}
   285  	return nil
   286  }
   287  
   288  func (s *LesServer) dropClient(id enode.ID) {
   289  	if p := s.getClient(id); p != nil {
   290  		p.Peer.Disconnect(p2p.DiscRequested)
   291  	}
   292  }