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 ( ...@@ -8,6 +8,7 @@ import (
"context" "context"
"errors" "errors"
"io/ioutil" "io/ioutil"
"math"
"testing" "testing"
"github.com/ethersphere/bee/pkg/accounting" "github.com/ethersphere/bee/pkg/accounting"
...@@ -18,9 +19,10 @@ import ( ...@@ -18,9 +19,10 @@ import (
) )
const ( const (
testPaymentThreshold = 10000 testPaymentThreshold = 10000
testPaymentTolerance = 1000 testPaymentThresholdLarge = math.MaxInt64 - 1
testPrice = uint64(10) testPaymentTolerance = 1000
testPrice = uint64(10)
) )
// booking represents an accounting action and the expected result afterwards // booking represents an accounting action and the expected result afterwards
...@@ -197,7 +199,196 @@ func TestAccountingReserve(t *testing.T) { ...@@ -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) { func TestAccountingDisconnect(t *testing.T) {
logger := logging.New(ioutil.Discard, 0) logger := logging.New(ioutil.Discard, 0)
......
...@@ -64,6 +64,7 @@ func newMetrics() metrics { ...@@ -64,6 +64,7 @@ func newMetrics() metrics {
} }
} }
// Metrics returns the prometheus Collector for the accounting service.
func (a *Accounting) Metrics() []prometheus.Collector { func (a *Accounting) Metrics() []prometheus.Collector {
return m.PrometheusCollectorsFromFields(a.metrics) return m.PrometheusCollectorsFromFields(a.metrics)
} }
...@@ -8,18 +8,21 @@ import ( ...@@ -8,18 +8,21 @@ import (
"github.com/ethersphere/bee/pkg/swarm" "github.com/ethersphere/bee/pkg/swarm"
) )
// Pricer returns pricing information for chunk hashes.
type Pricer interface { 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 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 Price(chunk swarm.Address) uint64
} }
// FixedPricer is a Pricer that has a fixed price for chunks.
type FixedPricer struct { type FixedPricer struct {
overlay swarm.Address overlay swarm.Address
poPrice uint64 poPrice uint64
} }
// NewFixedPricer returns a new FixedPricer with a given price.
func NewFixedPricer(overlay swarm.Address, poPrice uint64) *FixedPricer { func NewFixedPricer(overlay swarm.Address, poPrice uint64) *FixedPricer {
return &FixedPricer{ return &FixedPricer{
overlay: overlay, overlay: overlay,
...@@ -27,10 +30,12 @@ func NewFixedPricer(overlay swarm.Address, poPrice uint64) *FixedPricer { ...@@ -27,10 +30,12 @@ func NewFixedPricer(overlay swarm.Address, poPrice uint64) *FixedPricer {
} }
} }
// PeerPrice implements Pricer.
func (pricer *FixedPricer) PeerPrice(peer, chunk swarm.Address) uint64 { func (pricer *FixedPricer) PeerPrice(peer, chunk swarm.Address) uint64 {
return uint64(swarm.MaxPO-swarm.Proximity(peer.Bytes(), chunk.Bytes())) * pricer.poPrice return uint64(swarm.MaxPO-swarm.Proximity(peer.Bytes(), chunk.Bytes())) * pricer.poPrice
} }
// Price implements Pricer.
func (pricer *FixedPricer) Price(chunk swarm.Address) uint64 { func (pricer *FixedPricer) Price(chunk swarm.Address) uint64 {
return pricer.PeerPrice(pricer.overlay, chunk) 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