Commit 80d9ac0c authored by metacertain's avatar metacertain Committed by GitHub

Safety checks in accounting (#642)

Safety checks in accounting
Correct restoring balance after unsuccessful payment
Added code comments for arithmetic safety related parts
Co-authored-by: default avatarPavle Batuta <pavle@batuta.xyz>
parent fd0fbb93
This diff is collapsed.
......@@ -8,6 +8,7 @@ import (
"context"
"errors"
"io/ioutil"
"math"
"testing"
"github.com/ethersphere/bee/pkg/accounting"
......@@ -18,9 +19,10 @@ import (
)
const (
testPaymentThreshold = 10000
testPaymentTolerance = 1000
testPrice = uint64(10)
testPaymentThreshold = 10000
testPaymentThresholdLarge = math.MaxInt64 - 1
testPaymentTolerance = 1000
testPrice = uint64(10)
)
// booking represents an accounting action and the expected result afterwards
......@@ -197,7 +199,196 @@ func TestAccountingReserve(t *testing.T) {
}
}
// TestAccountingDisconnect tests that exceeding the disconnect threshold with Debit returns a p2p.BlockPeerError
func TestAccountingOverflowReserve(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mock.NewStateStore()
defer store.Close()
settlement := &settlementMock{}
acc, err := accounting.NewAccounting(accounting.Options{
PaymentThreshold: testPaymentThresholdLarge,
Logger: logger,
Store: store,
Settlement: settlement,
})
if err != nil {
t.Fatal(err)
}
peer1Addr, err := swarm.ParseHexAddress("00112233")
if err != nil {
t.Fatal(err)
}
// Try crediting near maximal value for peer
err = acc.Credit(peer1Addr, math.MaxInt64-2)
if err != nil {
t.Fatal(err)
}
// Try reserving further maximal value
err = acc.Reserve(peer1Addr, math.MaxInt64)
if err != nil {
t.Fatal(err)
}
// Try reserving further value, should overflow
err = acc.Reserve(peer1Addr, 1)
if err == nil {
t.Fatal("expected error")
}
// If we had other error, assert fail
if !errors.Is(err, accounting.ErrOverflow) {
t.Fatalf("expected overflow error from Debit, got %v", err)
}
// Try reserving further near maximal value
err = acc.Reserve(peer1Addr, math.MaxInt64-2)
if err == nil {
t.Fatal("expected error")
}
// If we had other error, assert fail
if !errors.Is(err, accounting.ErrOverflow) {
t.Fatalf("expected overflow error from Debit, got %v", err)
}
}
func TestAccountingOverflowNotifyPayment(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mock.NewStateStore()
defer store.Close()
settlement := &settlementMock{}
acc, err := accounting.NewAccounting(accounting.Options{
PaymentThreshold: testPaymentThresholdLarge,
Logger: logger,
Store: store,
Settlement: settlement,
})
if err != nil {
t.Fatal(err)
}
peer1Addr, err := swarm.ParseHexAddress("00112233")
if err != nil {
t.Fatal(err)
}
// Try Crediting a large amount to peer so balance is negative
err = acc.Credit(peer1Addr, math.MaxInt64)
if err != nil {
t.Fatal(err)
}
// Notify of incoming payment from same peer, further decreasing balance, this should overflow
err = acc.NotifyPayment(peer1Addr, math.MaxInt64)
if err == nil {
t.Fatal("Expected overflow from NotifyPayment")
}
// If we had other error, assert fail
if !errors.Is(err, accounting.ErrOverflow) {
t.Fatalf("expected overflow error from Debit, got %v", err)
}
}
func TestAccountingOverflowDebit(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(accounting.Options{
PaymentThreshold: testPaymentThresholdLarge,
Logger: logger,
Store: store,
})
if err != nil {
t.Fatal(err)
}
peer1Addr, err := swarm.ParseHexAddress("00112233")
if err != nil {
t.Fatal(err)
}
// Try increasing peer debit with near maximal value
err = acc.Debit(peer1Addr, math.MaxInt64-2)
if err != nil {
t.Fatal(err)
}
// Try further increasing peer debit with near maximal value, this should fail
err = acc.Debit(peer1Addr, math.MaxInt64-2)
if err == nil {
t.Fatal("Expected error from overflow Debit")
}
// If we had other error, assert fail
if !errors.Is(err, accounting.ErrOverflow) {
t.Fatalf("expected overflow error from Credit, got %v", err)
}
// Try further increasing peer debit with near maximal value, this should fail
err = acc.Debit(peer1Addr, math.MaxInt64)
if err == nil {
t.Fatal("Expected error from overflow Debit")
}
// If we had other error, assert fail
if !errors.Is(err, accounting.ErrOverflow) {
t.Fatalf("expected overflow error from Debit, got %v", err)
}
}
func TestAccountingOverflowCredit(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(accounting.Options{
PaymentThreshold: testPaymentThresholdLarge,
Logger: logger,
Store: store,
})
if err != nil {
t.Fatal(err)
}
peer1Addr, err := swarm.ParseHexAddress("00112233")
if err != nil {
t.Fatal(err)
}
// Try increasing peer credit with near maximal value
err = acc.Credit(peer1Addr, math.MaxInt64-2)
if err != nil {
t.Fatal(err)
}
// Try increasing with a further near maximal value, this must overflow
err = acc.Credit(peer1Addr, math.MaxInt64-2)
if err == nil {
t.Fatal("Expected error from overflow Credit")
}
// Try increasing with a small amount, which should also overflow
err = acc.Credit(peer1Addr, 3)
if err == nil {
t.Fatal("Expected error from overflow Credit")
}
// If we had other error, assert fail
if !errors.Is(err, accounting.ErrOverflow) {
t.Fatalf("expected overflow error from Credit, got %v", err)
}
// Try increasing with maximal value
err = acc.Credit(peer1Addr, math.MaxInt64)
if err == nil {
t.Fatal("Expected error from overflow Credit")
}
// If we had other error, assert fail
if !errors.Is(err, accounting.ErrOverflow) {
t.Fatalf("expected overflow error from Credit, got %v", err)
}
}
// TestAccountingDisconnect tests that exceeding the disconnect threshold with Debit returns a p2p.DisconnectError
func TestAccountingDisconnect(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
......
......@@ -64,6 +64,7 @@ func newMetrics() metrics {
}
}
// Metrics returns the prometheus Collector for the accounting service.
func (a *Accounting) Metrics() []prometheus.Collector {
return m.PrometheusCollectorsFromFields(a.metrics)
}
......@@ -8,18 +8,21 @@ import (
"github.com/ethersphere/bee/pkg/swarm"
)
// Pricer returns pricing information for chunk hashes.
type Pricer interface {
// PeerPrice is the price the peer charges for a given chunk hash
// PeerPrice is the price the peer charges for a given chunk hash.
PeerPrice(peer, chunk swarm.Address) uint64
// Price is the price we charge for a given chunk hash
// Price is the price we charge for a given chunk hash.
Price(chunk swarm.Address) uint64
}
// FixedPricer is a Pricer that has a fixed price for chunks.
type FixedPricer struct {
overlay swarm.Address
poPrice uint64
}
// NewFixedPricer returns a new FixedPricer with a given price.
func NewFixedPricer(overlay swarm.Address, poPrice uint64) *FixedPricer {
return &FixedPricer{
overlay: overlay,
......@@ -27,10 +30,12 @@ func NewFixedPricer(overlay swarm.Address, poPrice uint64) *FixedPricer {
}
}
// PeerPrice implements Pricer.
func (pricer *FixedPricer) PeerPrice(peer, chunk swarm.Address) uint64 {
return uint64(swarm.MaxPO-swarm.Proximity(peer.Bytes(), chunk.Bytes())) * pricer.poPrice
}
// Price implements Pricer.
func (pricer *FixedPricer) Price(chunk swarm.Address) uint64 {
return pricer.PeerPrice(pricer.overlay, chunk)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment