-
Notifications
You must be signed in to change notification settings - Fork 20.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* p2p/protocols: introduced protocol accounting * p2p/protocols: added TestExchange simulation * p2p/protocols: add accounting simulation * p2p/protocols: remove unnecessary tests * p2p/protocols: comments for accounting simulation * p2p/protocols: addressed PR comments * p2p/protocols: finalized accounting implementation * p2p/protocols: removed unused code * p2p/protocols: addressed @nonsense PR comments
- Loading branch information
1 parent
80d3907
commit 8ed4739
Showing
5 changed files
with
937 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
// Copyright 2018 The go-ethereum Authors | ||
// This file is part of the go-ethereum library. | ||
// | ||
// The go-ethereum library is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Lesser General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// The go-ethereum library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Lesser General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License | ||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package protocols | ||
|
||
import "github.com/ethereum/go-ethereum/metrics" | ||
|
||
//define some metrics | ||
var ( | ||
//NOTE: these metrics just define the interfaces and are currently *NOT persisted* over sessions | ||
//All metrics are cumulative | ||
|
||
//total amount of units credited | ||
mBalanceCredit = metrics.NewRegisteredCounterForced("account.balance.credit", nil) | ||
//total amount of units debited | ||
mBalanceDebit = metrics.NewRegisteredCounterForced("account.balance.debit", nil) | ||
//total amount of bytes credited | ||
mBytesCredit = metrics.NewRegisteredCounterForced("account.bytes.credit", nil) | ||
//total amount of bytes debited | ||
mBytesDebit = metrics.NewRegisteredCounterForced("account.bytes.debit", nil) | ||
//total amount of credited messages | ||
mMsgCredit = metrics.NewRegisteredCounterForced("account.msg.credit", nil) | ||
//total amount of debited messages | ||
mMsgDebit = metrics.NewRegisteredCounterForced("account.msg.debit", nil) | ||
//how many times local node had to drop remote peers | ||
mPeerDrops = metrics.NewRegisteredCounterForced("account.peerdrops", nil) | ||
//how many times local node overdrafted and dropped | ||
mSelfDrops = metrics.NewRegisteredCounterForced("account.selfdrops", nil) | ||
) | ||
|
||
//Prices defines how prices are being passed on to the accounting instance | ||
type Prices interface { | ||
//Return the Price for a message | ||
Price(interface{}) *Price | ||
} | ||
|
||
type Payer bool | ||
|
||
const ( | ||
Sender = Payer(true) | ||
Receiver = Payer(false) | ||
) | ||
|
||
//Price represents the costs of a message | ||
type Price struct { | ||
Value uint64 // | ||
PerByte bool //True if the price is per byte or for unit | ||
Payer Payer | ||
} | ||
|
||
//For gives back the price for a message | ||
//A protocol provides the message price in absolute value | ||
//This method then returns the correct signed amount, | ||
//depending on who pays, which is identified by the `payer` argument: | ||
//`Send` will pass a `Sender` payer, `Receive` will pass the `Receiver` argument. | ||
//Thus: If Sending and sender pays, amount positive, otherwise negative | ||
//If Receiving, and receiver pays, amount positive, otherwise negative | ||
func (p *Price) For(payer Payer, size uint32) int64 { | ||
price := p.Value | ||
if p.PerByte { | ||
price *= uint64(size) | ||
} | ||
if p.Payer == payer { | ||
return 0 - int64(price) | ||
} | ||
return int64(price) | ||
} | ||
|
||
//Balance is the actual accounting instance | ||
//Balance defines the operations needed for accounting | ||
//Implementations internally maintain the balance for every peer | ||
type Balance interface { | ||
//Adds amount to the local balance with remote node `peer`; | ||
//positive amount = credit local node | ||
//negative amount = debit local node | ||
Add(amount int64, peer *Peer) error | ||
} | ||
|
||
//Accounting implements the Hook interface | ||
//It interfaces to the balances through the Balance interface, | ||
//while interfacing with protocols and its prices through the Prices interface | ||
type Accounting struct { | ||
Balance //interface to accounting logic | ||
Prices //interface to prices logic | ||
} | ||
|
||
func NewAccounting(balance Balance, po Prices) *Accounting { | ||
ah := &Accounting{ | ||
Prices: po, | ||
Balance: balance, | ||
} | ||
return ah | ||
} | ||
|
||
//Implement Hook.Send | ||
// Send takes a peer, a size and a msg and | ||
// - calculates the cost for the local node sending a msg of size to peer using the Prices interface | ||
// - credits/debits local node using balance interface | ||
func (ah *Accounting) Send(peer *Peer, size uint32, msg interface{}) error { | ||
//get the price for a message (through the protocol spec) | ||
price := ah.Price(msg) | ||
//this message doesn't need accounting | ||
if price == nil { | ||
return nil | ||
} | ||
//evaluate the price for sending messages | ||
costToLocalNode := price.For(Sender, size) | ||
//do the accounting | ||
err := ah.Add(costToLocalNode, peer) | ||
//record metrics: just increase counters for user-facing metrics | ||
ah.doMetrics(costToLocalNode, size, err) | ||
return err | ||
} | ||
|
||
//Implement Hook.Receive | ||
// Receive takes a peer, a size and a msg and | ||
// - calculates the cost for the local node receiving a msg of size from peer using the Prices interface | ||
// - credits/debits local node using balance interface | ||
func (ah *Accounting) Receive(peer *Peer, size uint32, msg interface{}) error { | ||
//get the price for a message (through the protocol spec) | ||
price := ah.Price(msg) | ||
//this message doesn't need accounting | ||
if price == nil { | ||
return nil | ||
} | ||
//evaluate the price for receiving messages | ||
costToLocalNode := price.For(Receiver, size) | ||
//do the accounting | ||
err := ah.Add(costToLocalNode, peer) | ||
//record metrics: just increase counters for user-facing metrics | ||
ah.doMetrics(costToLocalNode, size, err) | ||
return err | ||
} | ||
|
||
//record some metrics | ||
//this is not an error handling. `err` is returned by both `Send` and `Receive` | ||
//`err` will only be non-nil if a limit has been violated (overdraft), in which case the peer has been dropped. | ||
//if the limit has been violated and `err` is thus not nil: | ||
// * if the price is positive, local node has been credited; thus `err` implicitly signals the REMOTE has been dropped | ||
// * if the price is negative, local node has been debited, thus `err` implicitly signals LOCAL node "overdraft" | ||
func (ah *Accounting) doMetrics(price int64, size uint32, err error) { | ||
if price > 0 { | ||
mBalanceCredit.Inc(price) | ||
mBytesCredit.Inc(int64(size)) | ||
mMsgCredit.Inc(1) | ||
if err != nil { | ||
//increase the number of times a remote node has been dropped due to "overdraft" | ||
mPeerDrops.Inc(1) | ||
} | ||
} else { | ||
mBalanceDebit.Inc(price) | ||
mBytesDebit.Inc(int64(size)) | ||
mMsgDebit.Inc(1) | ||
if err != nil { | ||
//increase the number of times the local node has done an "overdraft" in respect to other nodes | ||
mSelfDrops.Inc(1) | ||
} | ||
} | ||
} |
Oops, something went wrong.