github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/les/costtracker.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 detailct.
    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  	"encoding/binary"
    21  	"math"
    22  	"sync"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/common/mclock"
    27  	"github.com/ethereum/go-ethereum/eth"
    28  	"github.com/ethereum/go-ethereum/ethdb"
    29  	"github.com/ethereum/go-ethereum/les/flowcontrol"
    30  	"github.com/ethereum/go-ethereum/log"
    31  )
    32  
    33  const makeCostStats = false // make request cost statistics during operation
    34  
    35  var (
    36  	// average request cost estimates based on serving time
    37  	reqAvgTimeCost = requestCostTable{
    38  		GetBlockHeadersMsg:     {150000, 30000},
    39  		GetBlockBodiesMsg:      {0, 700000},
    40  		GetReceiptsMsg:         {0, 1000000},
    41  		GetCodeMsg:             {0, 450000},
    42  		GetProofsV2Msg:         {0, 600000},
    43  		GetHelperTrieProofsMsg: {0, 1000000},
    44  		SendTxV2Msg:            {0, 450000},
    45  		GetTxStatusMsg:         {0, 250000},
    46  	}
    47  	// maximum incoming message size estimates
    48  	reqMaxInSize = requestCostTable{
    49  		GetBlockHeadersMsg:     {40, 0},
    50  		GetBlockBodiesMsg:      {0, 40},
    51  		GetReceiptsMsg:         {0, 40},
    52  		GetCodeMsg:             {0, 80},
    53  		GetProofsV2Msg:         {0, 80},
    54  		GetHelperTrieProofsMsg: {0, 20},
    55  		SendTxV2Msg:            {0, 66000},
    56  		GetTxStatusMsg:         {0, 50},
    57  	}
    58  	// maximum outgoing message size estimates
    59  	reqMaxOutSize = requestCostTable{
    60  		GetBlockHeadersMsg:     {0, 556},
    61  		GetBlockBodiesMsg:      {0, 100000},
    62  		GetReceiptsMsg:         {0, 200000},
    63  		GetCodeMsg:             {0, 50000},
    64  		GetProofsV2Msg:         {0, 4000},
    65  		GetHelperTrieProofsMsg: {0, 4000},
    66  		SendTxV2Msg:            {0, 100},
    67  		GetTxStatusMsg:         {0, 100},
    68  	}
    69  	minBufLimit = uint64(50000000 * maxCostFactor)  // minimum buffer limit allowed for a client
    70  	minCapacity = (minBufLimit-1)/bufLimitRatio + 1 // minimum capacity allowed for a client
    71  )
    72  
    73  const (
    74  	maxCostFactor    = 2 // ratio of maximum and average cost estimates
    75  	gfInitWeight     = time.Second * 10
    76  	gfMaxWeight      = time.Hour
    77  	gfUsageThreshold = 0.5
    78  	gfUsageTC        = time.Second
    79  	gfDbKey          = "_globalCostFactor"
    80  )
    81  
    82  // costTracker is responsible for calculating costs and cost estimates on the
    83  // server side. It continuously updates the global cost factor which is defined
    84  // as the number of cost units per nanosecond of serving time in a single thread.
    85  // It is based on statistics collected during serving requests in high-load periods
    86  // and practically acts as a one-dimension request price scaling factor over the
    87  // pre-defined cost estimate table. Instead of scaling the cost values, the real
    88  // value of cost units is changed by applying the factor to the serving times. This
    89  // is more convenient because the changes in the cost factor can be applied immediately
    90  // without always notifying the clients about the changed cost tables.
    91  type costTracker struct {
    92  	db     ethdb.Database
    93  	stopCh chan chan struct{}
    94  
    95  	inSizeFactor, outSizeFactor float64
    96  	gf, utilTarget              float64
    97  
    98  	gfUpdateCh      chan gfUpdate
    99  	gfLock          sync.RWMutex
   100  	totalRechargeCh chan uint64
   101  
   102  	stats map[uint64][]uint64
   103  }
   104  
   105  // newCostTracker creates a cost tracker and loads the cost factor statistics from the database
   106  func newCostTracker(db ethdb.Database, config *eth.Config) *costTracker {
   107  	utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100
   108  	ct := &costTracker{
   109  		db:         db,
   110  		stopCh:     make(chan chan struct{}),
   111  		utilTarget: utilTarget,
   112  	}
   113  	if config.LightBandwidthIn > 0 {
   114  		ct.inSizeFactor = utilTarget / float64(config.LightBandwidthIn)
   115  	}
   116  	if config.LightBandwidthOut > 0 {
   117  		ct.outSizeFactor = utilTarget / float64(config.LightBandwidthOut)
   118  	}
   119  	if makeCostStats {
   120  		ct.stats = make(map[uint64][]uint64)
   121  		for code := range reqAvgTimeCost {
   122  			ct.stats[code] = make([]uint64, 10)
   123  		}
   124  	}
   125  	ct.gfLoop()
   126  	return ct
   127  }
   128  
   129  // stop stops the cost tracker and saves the cost factor statistics to the database
   130  func (ct *costTracker) stop() {
   131  	stopCh := make(chan struct{})
   132  	ct.stopCh <- stopCh
   133  	<-stopCh
   134  	if makeCostStats {
   135  		ct.printStats()
   136  	}
   137  }
   138  
   139  // makeCostList returns upper cost estimates based on the hardcoded cost estimate
   140  // tables and the optionally specified incoming/outgoing bandwidth limits
   141  func (ct *costTracker) makeCostList() RequestCostList {
   142  	maxCost := func(avgTime, inSize, outSize uint64) uint64 {
   143  		globalFactor := ct.globalFactor()
   144  
   145  		cost := avgTime * maxCostFactor
   146  		inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor * maxCostFactor)
   147  		if inSizeCost > cost {
   148  			cost = inSizeCost
   149  		}
   150  		outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor * maxCostFactor)
   151  		if outSizeCost > cost {
   152  			cost = outSizeCost
   153  		}
   154  		return cost
   155  	}
   156  	var list RequestCostList
   157  	for code, data := range reqAvgTimeCost {
   158  		list = append(list, requestCostListItem{
   159  			MsgCode:  code,
   160  			BaseCost: maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost),
   161  			ReqCost:  maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost),
   162  		})
   163  	}
   164  	return list
   165  }
   166  
   167  type gfUpdate struct {
   168  	avgTime, servingTime float64
   169  }
   170  
   171  // gfLoop starts an event loop which updates the global cost factor which is
   172  // calculated as a weighted average of the average estimate / serving time ratio.
   173  // The applied weight equals the serving time if gfUsage is over a threshold,
   174  // zero otherwise. gfUsage is the recent average serving time per time unit in
   175  // an exponential moving window. This ensures that statistics are collected only
   176  // under high-load circumstances where the measured serving times are relevant.
   177  // The total recharge parameter of the flow control system which controls the
   178  // total allowed serving time per second but nominated in cost units, should
   179  // also be scaled with the cost factor and is also updated by this loop.
   180  func (ct *costTracker) gfLoop() {
   181  	var gfUsage, gfSum, gfWeight float64
   182  	lastUpdate := mclock.Now()
   183  	expUpdate := lastUpdate
   184  
   185  	data, _ := ct.db.Get([]byte(gfDbKey))
   186  	if len(data) == 16 {
   187  		gfSum = math.Float64frombits(binary.BigEndian.Uint64(data[0:8]))
   188  		gfWeight = math.Float64frombits(binary.BigEndian.Uint64(data[8:16]))
   189  	}
   190  	if gfWeight < float64(gfInitWeight) {
   191  		gfSum = float64(gfInitWeight)
   192  		gfWeight = float64(gfInitWeight)
   193  	}
   194  	gf := gfSum / gfWeight
   195  	ct.gf = gf
   196  	ct.gfUpdateCh = make(chan gfUpdate, 100)
   197  
   198  	go func() {
   199  		for {
   200  			select {
   201  			case r := <-ct.gfUpdateCh:
   202  				now := mclock.Now()
   203  				max := r.servingTime * gf
   204  				if r.avgTime > max {
   205  					max = r.avgTime
   206  				}
   207  				dt := float64(now - expUpdate)
   208  				expUpdate = now
   209  				gfUsage = gfUsage*math.Exp(-dt/float64(gfUsageTC)) + max*1000000/float64(gfUsageTC)
   210  
   211  				if gfUsage >= gfUsageThreshold*ct.utilTarget*gf {
   212  					gfSum += r.avgTime
   213  					gfWeight += r.servingTime
   214  					if time.Duration(now-lastUpdate) > time.Second {
   215  						gf = gfSum / gfWeight
   216  						if gfWeight >= float64(gfMaxWeight) {
   217  							gfSum = gf * float64(gfMaxWeight)
   218  							gfWeight = float64(gfMaxWeight)
   219  						}
   220  						lastUpdate = now
   221  						ct.gfLock.Lock()
   222  						ct.gf = gf
   223  						ch := ct.totalRechargeCh
   224  						ct.gfLock.Unlock()
   225  						if ch != nil {
   226  							select {
   227  							case ct.totalRechargeCh <- uint64(ct.utilTarget * gf):
   228  							default:
   229  							}
   230  						}
   231  						log.Debug("global cost factor updated", "gf", gf, "weight", time.Duration(gfWeight))
   232  					}
   233  				}
   234  			case stopCh := <-ct.stopCh:
   235  				var data [16]byte
   236  				binary.BigEndian.PutUint64(data[0:8], math.Float64bits(gfSum))
   237  				binary.BigEndian.PutUint64(data[8:16], math.Float64bits(gfWeight))
   238  				ct.db.Put([]byte(gfDbKey), data[:])
   239  				log.Debug("global cost factor saved", "sum", time.Duration(gfSum), "weight", time.Duration(gfWeight))
   240  				close(stopCh)
   241  				return
   242  			}
   243  		}
   244  	}()
   245  }
   246  
   247  // globalFactor returns the current value of the global cost factor
   248  func (ct *costTracker) globalFactor() float64 {
   249  	ct.gfLock.RLock()
   250  	defer ct.gfLock.RUnlock()
   251  
   252  	return ct.gf
   253  }
   254  
   255  // totalRecharge returns the current total recharge parameter which is used by
   256  // flowcontrol.ClientManager and is scaled by the global cost factor
   257  func (ct *costTracker) totalRecharge() uint64 {
   258  	ct.gfLock.RLock()
   259  	defer ct.gfLock.RUnlock()
   260  
   261  	return uint64(ct.gf * ct.utilTarget)
   262  }
   263  
   264  // subscribeTotalRecharge returns all future updates to the total recharge value
   265  // through a channel and also returns the current value
   266  func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 {
   267  	ct.gfLock.Lock()
   268  	defer ct.gfLock.Unlock()
   269  
   270  	ct.totalRechargeCh = ch
   271  	return uint64(ct.gf * ct.utilTarget)
   272  }
   273  
   274  // updateStats updates the global cost factor and (if enabled) the real cost vs.
   275  // average estimate statistics
   276  func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) {
   277  	avg := reqAvgTimeCost[code]
   278  	avgTime := avg.baseCost + amount*avg.reqCost
   279  	select {
   280  	case ct.gfUpdateCh <- gfUpdate{float64(avgTime), float64(servingTime)}:
   281  	default:
   282  	}
   283  	if makeCostStats {
   284  		realCost <<= 4
   285  		l := 0
   286  		for l < 9 && realCost > avgTime {
   287  			l++
   288  			realCost >>= 1
   289  		}
   290  		atomic.AddUint64(&ct.stats[code][l], 1)
   291  	}
   292  }
   293  
   294  // realCost calculates the final cost of a request based on actual serving time,
   295  // incoming and outgoing message size
   296  //
   297  // Note: message size is only taken into account if bandwidth limitation is applied
   298  // and the cost based on either message size is greater than the cost based on
   299  // serving time. A maximum of the three costs is applied instead of their sum
   300  // because the three limited resources (serving thread time and i/o bandwidth) can
   301  // also be maxed out simultaneously.
   302  func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 {
   303  	cost := float64(servingTime)
   304  	inSizeCost := float64(inSize) * ct.inSizeFactor
   305  	if inSizeCost > cost {
   306  		cost = inSizeCost
   307  	}
   308  	outSizeCost := float64(outSize) * ct.outSizeFactor
   309  	if outSizeCost > cost {
   310  		cost = outSizeCost
   311  	}
   312  	return uint64(cost * ct.globalFactor())
   313  }
   314  
   315  // printStats prints the distribution of real request cost relative to the average estimates
   316  func (ct *costTracker) printStats() {
   317  	if ct.stats == nil {
   318  		return
   319  	}
   320  	for code, arr := range ct.stats {
   321  		log.Info("Request cost statistics", "code", code, "1/16", arr[0], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9])
   322  	}
   323  }
   324  
   325  type (
   326  	// requestCostTable assigns a cost estimate function to each request type
   327  	// which is a linear function of the requested amount
   328  	// (cost = baseCost + reqCost * amount)
   329  	requestCostTable map[uint64]*requestCosts
   330  	requestCosts     struct {
   331  		baseCost, reqCost uint64
   332  	}
   333  
   334  	// RequestCostList is a list representation of request costs which is used for
   335  	// database storage and communication through the network
   336  	RequestCostList     []requestCostListItem
   337  	requestCostListItem struct {
   338  		MsgCode, BaseCost, ReqCost uint64
   339  	}
   340  )
   341  
   342  // getCost calculates the estimated cost for a given request type and amount
   343  func (table requestCostTable) getCost(code, amount uint64) uint64 {
   344  	costs := table[code]
   345  	return costs.baseCost + amount*costs.reqCost
   346  }
   347  
   348  // decode converts a cost list to a cost table
   349  func (list RequestCostList) decode() requestCostTable {
   350  	table := make(requestCostTable)
   351  	for _, e := range list {
   352  		table[e.MsgCode] = &requestCosts{
   353  			baseCost: e.BaseCost,
   354  			reqCost:  e.ReqCost,
   355  		}
   356  	}
   357  	return table
   358  }
   359  
   360  // testCostList returns a dummy request cost list used by tests
   361  func testCostList() RequestCostList {
   362  	cl := make(RequestCostList, len(reqAvgTimeCost))
   363  	var max uint64
   364  	for code := range reqAvgTimeCost {
   365  		if code > max {
   366  			max = code
   367  		}
   368  	}
   369  	i := 0
   370  	for code := uint64(0); code <= max; code++ {
   371  		if _, ok := reqAvgTimeCost[code]; ok {
   372  			cl[i].MsgCode = code
   373  			cl[i].BaseCost = 0
   374  			cl[i].ReqCost = 0
   375  			i++
   376  		}
   377  	}
   378  	return cl
   379  }