github.com/lukso-network/go-ethereum@v1.8.22/p2p/protocols/accounting.go (about)

     1  // Copyright 2018 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 protocols
    18  
    19  import (
    20  	"time"
    21  
    22  	"github.com/ethereum/go-ethereum/metrics"
    23  )
    24  
    25  // define some metrics
    26  var (
    27  	// All metrics are cumulative
    28  
    29  	// total amount of units credited
    30  	mBalanceCredit metrics.Counter
    31  	// total amount of units debited
    32  	mBalanceDebit metrics.Counter
    33  	// total amount of bytes credited
    34  	mBytesCredit metrics.Counter
    35  	// total amount of bytes debited
    36  	mBytesDebit metrics.Counter
    37  	// total amount of credited messages
    38  	mMsgCredit metrics.Counter
    39  	// total amount of debited messages
    40  	mMsgDebit metrics.Counter
    41  	// how many times local node had to drop remote peers
    42  	mPeerDrops metrics.Counter
    43  	// how many times local node overdrafted and dropped
    44  	mSelfDrops metrics.Counter
    45  
    46  	MetricsRegistry metrics.Registry
    47  )
    48  
    49  // Prices defines how prices are being passed on to the accounting instance
    50  type Prices interface {
    51  	// Return the Price for a message
    52  	Price(interface{}) *Price
    53  }
    54  
    55  type Payer bool
    56  
    57  const (
    58  	Sender   = Payer(true)
    59  	Receiver = Payer(false)
    60  )
    61  
    62  // Price represents the costs of a message
    63  type Price struct {
    64  	Value   uint64
    65  	PerByte bool // True if the price is per byte or for unit
    66  	Payer   Payer
    67  }
    68  
    69  // For gives back the price for a message
    70  // A protocol provides the message price in absolute value
    71  // This method then returns the correct signed amount,
    72  // depending on who pays, which is identified by the `payer` argument:
    73  // `Send` will pass a `Sender` payer, `Receive` will pass the `Receiver` argument.
    74  // Thus: If Sending and sender pays, amount positive, otherwise negative
    75  // If Receiving, and receiver pays, amount positive, otherwise negative
    76  func (p *Price) For(payer Payer, size uint32) int64 {
    77  	price := p.Value
    78  	if p.PerByte {
    79  		price *= uint64(size)
    80  	}
    81  	if p.Payer == payer {
    82  		return 0 - int64(price)
    83  	}
    84  	return int64(price)
    85  }
    86  
    87  // Balance is the actual accounting instance
    88  // Balance defines the operations needed for accounting
    89  // Implementations internally maintain the balance for every peer
    90  type Balance interface {
    91  	// Adds amount to the local balance with remote node `peer`;
    92  	// positive amount = credit local node
    93  	// negative amount = debit local node
    94  	Add(amount int64, peer *Peer) error
    95  }
    96  
    97  // Accounting implements the Hook interface
    98  // It interfaces to the balances through the Balance interface,
    99  // while interfacing with protocols and its prices through the Prices interface
   100  type Accounting struct {
   101  	Balance // interface to accounting logic
   102  	Prices  // interface to prices logic
   103  }
   104  
   105  func NewAccounting(balance Balance, po Prices) *Accounting {
   106  	ah := &Accounting{
   107  		Prices:  po,
   108  		Balance: balance,
   109  	}
   110  	return ah
   111  }
   112  
   113  // SetupAccountingMetrics creates a separate registry for p2p accounting metrics;
   114  // this registry should be independent of any other metrics as it persists at different endpoints.
   115  // It also instantiates the given metrics and starts the persisting go-routine which
   116  // at the passed interval writes the metrics to a LevelDB
   117  func SetupAccountingMetrics(reportInterval time.Duration, path string) *AccountingMetrics {
   118  	// create an empty registry
   119  	MetricsRegistry = metrics.NewRegistry()
   120  	// instantiate the metrics
   121  	mBalanceCredit = metrics.NewRegisteredCounterForced("account.balance.credit", MetricsRegistry)
   122  	mBalanceDebit = metrics.NewRegisteredCounterForced("account.balance.debit", MetricsRegistry)
   123  	mBytesCredit = metrics.NewRegisteredCounterForced("account.bytes.credit", MetricsRegistry)
   124  	mBytesDebit = metrics.NewRegisteredCounterForced("account.bytes.debit", MetricsRegistry)
   125  	mMsgCredit = metrics.NewRegisteredCounterForced("account.msg.credit", MetricsRegistry)
   126  	mMsgDebit = metrics.NewRegisteredCounterForced("account.msg.debit", MetricsRegistry)
   127  	mPeerDrops = metrics.NewRegisteredCounterForced("account.peerdrops", MetricsRegistry)
   128  	mSelfDrops = metrics.NewRegisteredCounterForced("account.selfdrops", MetricsRegistry)
   129  	// create the DB and start persisting
   130  	return NewAccountingMetrics(MetricsRegistry, reportInterval, path)
   131  }
   132  
   133  // Send takes a peer, a size and a msg and
   134  //   - calculates the cost for the local node sending a msg of size to peer using the Prices interface
   135  //   - credits/debits local node using balance interface
   136  func (ah *Accounting) Send(peer *Peer, size uint32, msg interface{}) error {
   137  	// get the price for a message (through the protocol spec)
   138  	price := ah.Price(msg)
   139  	// this message doesn't need accounting
   140  	if price == nil {
   141  		return nil
   142  	}
   143  	// evaluate the price for sending messages
   144  	costToLocalNode := price.For(Sender, size)
   145  	// do the accounting
   146  	err := ah.Add(costToLocalNode, peer)
   147  	// record metrics: just increase counters for user-facing metrics
   148  	ah.doMetrics(costToLocalNode, size, err)
   149  	return err
   150  }
   151  
   152  // Receive takes a peer, a size and a msg and
   153  //   - calculates the cost for the local node receiving a msg of size from peer using the Prices interface
   154  //   - credits/debits local node using balance interface
   155  func (ah *Accounting) Receive(peer *Peer, size uint32, msg interface{}) error {
   156  	// get the price for a message (through the protocol spec)
   157  	price := ah.Price(msg)
   158  	// this message doesn't need accounting
   159  	if price == nil {
   160  		return nil
   161  	}
   162  	// evaluate the price for receiving messages
   163  	costToLocalNode := price.For(Receiver, size)
   164  	// do the accounting
   165  	err := ah.Add(costToLocalNode, peer)
   166  	// record metrics: just increase counters for user-facing metrics
   167  	ah.doMetrics(costToLocalNode, size, err)
   168  	return err
   169  }
   170  
   171  // record some metrics
   172  // this is not an error handling. `err` is returned by both `Send` and `Receive`
   173  // `err` will only be non-nil if a limit has been violated (overdraft), in which case the peer has been dropped.
   174  // if the limit has been violated and `err` is thus not nil:
   175  //   * if the price is positive, local node has been credited; thus `err` implicitly signals the REMOTE has been dropped
   176  //   * if the price is negative, local node has been debited, thus `err` implicitly signals LOCAL node "overdraft"
   177  func (ah *Accounting) doMetrics(price int64, size uint32, err error) {
   178  	if price > 0 {
   179  		mBalanceCredit.Inc(price)
   180  		mBytesCredit.Inc(int64(size))
   181  		mMsgCredit.Inc(1)
   182  		if err != nil {
   183  			// increase the number of times a remote node has been dropped due to "overdraft"
   184  			mPeerDrops.Inc(1)
   185  		}
   186  	} else {
   187  		mBalanceDebit.Inc(price)
   188  		mBytesDebit.Inc(int64(size))
   189  		mMsgDebit.Inc(1)
   190  		if err != nil {
   191  			// increase the number of times the local node has done an "overdraft" in respect to other nodes
   192  			mSelfDrops.Inc(1)
   193  		}
   194  	}
   195  }