Commit 0f889d8d authored by metacertain's avatar metacertain Committed by GitHub

feat: Always reset balances to zero after disconnection or blocklisting (#1983)

parent 0a861f1a
......@@ -46,7 +46,7 @@ type Interface interface {
// Credit increases the balance the peer has with us (we "pay" the peer).
Credit(peer swarm.Address, price uint64, originated bool) error
// PrepareDebit returns an accounting Action for the later debit to be executed on and to implement shadowing a possibly credited part of reserve on the other side.
PrepareDebit(peer swarm.Address, price uint64) Action
PrepareDebit(peer swarm.Address, price uint64) (Action, error)
// Balance returns the current balance for the given peer.
Balance(peer swarm.Address) (*big.Int, error)
// SurplusBalance returns the current surplus balance for the given peer.
......@@ -87,10 +87,13 @@ type accountingPeer struct {
lock sync.Mutex // lock to be held during any accounting action for this peer
reservedBalance *big.Int // amount currently reserved for active peer interaction
shadowReservedBalance *big.Int // amount potentially to be debited for active peer interaction
ghostBalance *big.Int // amount potentially could have been debited for but was not
paymentThreshold *big.Int // the threshold at which the peer expects us to pay
refreshTimestamp int64 // last time we attempted time-based settlement
paymentOngoing bool // indicate if we are currently settling with the peer
lastSettlementFailureTimestamp int64 // time of last unsuccessful attempt to issue a cheque
reconnectAllowTimestamp int64
lastSettlementFailureTimestamp int64 // time of last unsuccessful attempt to issue a cheque
connected bool
}
// Accounting is the main implementation of the accounting interface.
......@@ -120,6 +123,7 @@ type Accounting struct {
pricing pricing.Interface
metrics metrics
wg sync.WaitGroup
p2p p2p.Service
timeNow func() time.Time
}
......@@ -143,6 +147,8 @@ func NewAccounting(
Store storage.StateStorer,
Pricing pricing.Interface,
refreshRate *big.Int,
p2pService p2p.Service,
) (*Accounting, error) {
return &Accounting{
accountingPeers: make(map[string]*accountingPeer),
......@@ -157,6 +163,7 @@ func NewAccounting(
refreshRate: refreshRate,
timeNow: time.Now,
minimumPayment: new(big.Int).Div(refreshRate, big.NewInt(minimumPaymentDivisor)),
p2p: p2pService,
}, nil
}
......@@ -165,6 +172,11 @@ func (a *Accounting) Reserve(ctx context.Context, peer swarm.Address, price uint
accountingPeer := a.getAccountingPeer(peer)
accountingPeer.lock.Lock()
if !accountingPeer.connected {
return fmt.Errorf("connection not initialized yet")
}
defer accountingPeer.lock.Unlock()
a.metrics.AccountingReserveCount.Inc()
......@@ -485,8 +497,10 @@ func (a *Accounting) getAccountingPeer(peer swarm.Address) *accountingPeer {
peerData = &accountingPeer{
reservedBalance: big.NewInt(0),
shadowReservedBalance: big.NewInt(0),
ghostBalance: big.NewInt(0),
// initially assume the peer has the same threshold as us
paymentThreshold: new(big.Int).Set(a.paymentThreshold),
connected: false,
}
a.accountingPeers[peer.String()] = peerData
}
......@@ -633,6 +647,36 @@ func (a *Accounting) PeerDebt(peer swarm.Address) (*big.Int, error) {
return peerDebt, nil
}
// peerLatentDebt returns the sum of the positive part of the outstanding balance, shadow reserve and the ghost balance
func (a *Accounting) peerLatentDebt(peer swarm.Address) (*big.Int, error) {
accountingPeer := a.getAccountingPeer(peer)
balance := new(big.Int)
zero := big.NewInt(0)
err := a.store.Get(peerBalanceKey(peer), &balance)
if err != nil {
if !errors.Is(err, storage.ErrNotFound) {
return nil, err
}
balance = big.NewInt(0)
}
if balance.Cmp(zero) < 0 {
balance.Set(zero)
}
peerDebt := new(big.Int).Add(balance, accountingPeer.shadowReservedBalance)
peerLatentDebt := new(big.Int).Add(peerDebt, accountingPeer.ghostBalance)
if peerLatentDebt.Cmp(zero) < 0 {
return zero, nil
}
return peerLatentDebt, nil
}
// shadowBalance returns the current debt reduced by any potentially debitable amount stored in shadowReservedBalance
// this represents how much less our debt could potentially be seen by the other party if it's ahead with processing credits corresponding to our shadow reserve
func (a *Accounting) shadowBalance(peer swarm.Address) (shadowBalance *big.Int, err error) {
......@@ -827,12 +871,16 @@ func (a *Accounting) NotifyRefreshmentReceived(peer swarm.Address, amount *big.I
}
// PrepareDebit prepares a debit operation by increasing the shadowReservedBalance
func (a *Accounting) PrepareDebit(peer swarm.Address, price uint64) Action {
func (a *Accounting) PrepareDebit(peer swarm.Address, price uint64) (Action, error) {
accountingPeer := a.getAccountingPeer(peer)
accountingPeer.lock.Lock()
defer accountingPeer.lock.Unlock()
if !accountingPeer.connected {
return nil, fmt.Errorf("connection not initialized yet")
}
bigPrice := new(big.Int).SetUint64(price)
accountingPeer.shadowReservedBalance = new(big.Int).Add(accountingPeer.shadowReservedBalance, bigPrice)
......@@ -843,7 +891,7 @@ func (a *Accounting) PrepareDebit(peer swarm.Address, price uint64) Action {
peer: peer,
accountingPeer: accountingPeer,
applied: false,
}
}, nil
}
func (a *Accounting) increaseBalance(peer swarm.Address, accountingPeer *accountingPeer, price *big.Int) (*big.Int, error) {
......@@ -940,7 +988,13 @@ func (d *debitAction) Apply() error {
if nextBalance.Cmp(a.disconnectLimit) >= 0 {
// peer too much in debt
a.metrics.AccountingDisconnectsCount.Inc()
return p2p.NewBlockPeerError(24*time.Hour, ErrDisconnectThresholdExceeded)
disconnectFor, err := a.blocklistUntil(d.peer, 1)
if err != nil {
return p2p.NewBlockPeerError(1*time.Minute, ErrDisconnectThresholdExceeded)
}
return p2p.NewBlockPeerError(time.Duration(disconnectFor), ErrDisconnectThresholdExceeded)
}
return nil
......@@ -951,7 +1005,75 @@ func (d *debitAction) Cleanup() {
if !d.applied {
d.accountingPeer.lock.Lock()
defer d.accountingPeer.lock.Unlock()
a := d.accounting
d.accountingPeer.shadowReservedBalance = new(big.Int).Sub(d.accountingPeer.shadowReservedBalance, d.price)
d.accountingPeer.ghostBalance = new(big.Int).Add(d.accountingPeer.ghostBalance, d.price)
if d.accountingPeer.ghostBalance.Cmp(a.disconnectLimit) > 0 {
_ = a.blocklist(d.peer, 1)
}
}
}
func (a *Accounting) blocklistUntil(peer swarm.Address, multiplier int64) (int64, error) {
debt, err := a.peerLatentDebt(peer)
if err != nil {
return 0, err
}
if debt.Cmp(a.refreshRate) < 0 {
debt.Set(a.refreshRate)
}
additionalDebt := new(big.Int).Add(debt, a.paymentThreshold)
multiplyDebt := new(big.Int).Mul(additionalDebt, big.NewInt(multiplier))
k := new(big.Int).Div(multiplyDebt, a.refreshRate)
kInt := k.Int64()
return kInt, nil
}
func (a *Accounting) blocklist(peer swarm.Address, multiplier int64) error {
disconnectFor, err := a.blocklistUntil(peer, multiplier)
if err != nil {
return a.p2p.Blocklist(peer, 1*time.Minute)
}
return a.p2p.Blocklist(peer, time.Duration(disconnectFor)*time.Second)
}
func (a *Accounting) Connect(peer swarm.Address) {
accountingPeer := a.getAccountingPeer(peer)
zero := big.NewInt(0)
accountingPeer.lock.Lock()
defer accountingPeer.lock.Unlock()
accountingPeer.connected = true
accountingPeer.shadowReservedBalance.Set(zero)
accountingPeer.ghostBalance.Set(zero)
accountingPeer.reservedBalance.Set(zero)
err := a.store.Put(peerBalanceKey(peer), zero)
if err != nil {
a.logger.Errorf("failed to persist balance: %w", err)
}
err = a.store.Put(peerSurplusBalanceKey(peer), zero)
if err != nil {
a.logger.Errorf("failed to persist surplus balance: %w", err)
}
if accountingPeer.reconnectAllowTimestamp != 0 {
timeNow := a.timeNow().Unix()
if timeNow < accountingPeer.reconnectAllowTimestamp {
disconnectFor := accountingPeer.reconnectAllowTimestamp - timeNow
_ = a.p2p.Blocklist(peer, time.Duration(disconnectFor)*time.Second)
}
}
}
......@@ -1003,6 +1125,23 @@ func (a *Accounting) decreaseOriginatedBalanceBy(peer swarm.Address, amount *big
return nil
}
func (a *Accounting) Disconnect(peer swarm.Address) {
accountingPeer := a.getAccountingPeer(peer)
accountingPeer.lock.Lock()
defer accountingPeer.lock.Unlock()
timeNow := a.timeNow().Unix()
disconnectFor, err := a.blocklistUntil(peer, 1)
if err != nil {
disconnectFor = int64(60)
}
timestamp := timeNow + disconnectFor
accountingPeer.connected = false
accountingPeer.reconnectAllowTimestamp = timestamp
}
func (a *Accounting) SetRefreshFunc(f RefreshFunc) {
a.refreshFunction = f
}
......
......@@ -15,7 +15,9 @@ import (
"github.com/ethersphere/bee/pkg/accounting"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/p2p"
p2pmock "github.com/ethersphere/bee/pkg/p2p/mock"
"github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/swarm"
)
......@@ -53,7 +55,7 @@ func TestAccountingAddBalance(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -68,6 +70,9 @@ func TestAccountingAddBalance(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
acc.Connect(peer2Addr)
bookings := []booking{
{peer: peer1Addr, price: 100, expectedBalance: 100},
{peer: peer2Addr, price: 200, expectedBalance: 200},
......@@ -88,7 +93,10 @@ func TestAccountingAddBalance(t *testing.T) {
}
acc.Release(booking.peer, uint64(-booking.price))
} else {
debitAction := acc.PrepareDebit(booking.peer, uint64(booking.price))
debitAction, err := acc.PrepareDebit(booking.peer, uint64(booking.price))
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
......@@ -114,7 +122,7 @@ func TestAccountingAddOriginatedBalance(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -130,6 +138,8 @@ func TestAccountingAddOriginatedBalance(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
bookings := []booking{
// originated credit
{peer: peer1Addr, price: -2000, expectedBalance: -2000, originatedBalance: -2000, originatedCredit: true},
......@@ -155,7 +165,10 @@ func TestAccountingAddOriginatedBalance(t *testing.T) {
pay := func(ctx context.Context, peer swarm.Address, amount *big.Int) {
if booking.overpay != 0 {
debitAction := acc.PrepareDebit(peer, booking.overpay)
debitAction, err := acc.PrepareDebit(peer, booking.overpay)
if err != nil {
t.Fatal(err)
}
_ = debitAction.Apply()
}
......@@ -185,7 +198,10 @@ func TestAccountingAddOriginatedBalance(t *testing.T) {
}
acc.Release(booking.peer, uint64(-booking.price))
} else {
debitAction := acc.PrepareDebit(booking.peer, uint64(booking.price))
debitAction, err := acc.PrepareDebit(booking.peer, uint64(booking.price))
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
......@@ -222,7 +238,7 @@ func TestAccountingAdd_persistentBalances(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -237,8 +253,14 @@ func TestAccountingAdd_persistentBalances(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
acc.Connect(peer2Addr)
peer1DebitAmount := testPrice
debitAction := acc.PrepareDebit(peer1Addr, peer1DebitAmount)
debitAction, err := acc.PrepareDebit(peer1Addr, peer1DebitAmount)
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
......@@ -251,7 +273,7 @@ func TestAccountingAdd_persistentBalances(t *testing.T) {
t.Fatal(err)
}
acc, err = accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err = accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -282,7 +304,7 @@ func TestAccountingReserve(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -292,6 +314,8 @@ func TestAccountingReserve(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
// it should allow to cross the threshold one time
err = acc.Reserve(context.Background(), peer1Addr, testPaymentThreshold.Uint64()+1)
if err == nil {
......@@ -310,7 +334,7 @@ func TestAccountingDisconnect(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -320,8 +344,13 @@ func TestAccountingDisconnect(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
// put the peer 1 unit away from disconnect
debitAction := acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64()+testPaymentTolerance.Uint64()-1)
debitAction, err := acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64()+testPaymentTolerance.Uint64()-1)
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal("expected no error while still within tolerance")
......@@ -329,7 +358,10 @@ func TestAccountingDisconnect(t *testing.T) {
debitAction.Cleanup()
// put the peer over thee threshold
debitAction = acc.PrepareDebit(peer1Addr, 1)
debitAction, err = acc.PrepareDebit(peer1Addr, 1)
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err == nil {
t.Fatal("expected Add to return error")
......@@ -349,7 +381,7 @@ func TestAccountingCallSettlement(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -372,6 +404,8 @@ func TestAccountingCallSettlement(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
requestPrice := testPaymentThreshold.Uint64() - 1000
err = acc.Reserve(context.Background(), peer1Addr, requestPrice)
......@@ -472,7 +506,7 @@ func TestAccountingCallSettlementMonetary(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -496,6 +530,8 @@ func TestAccountingCallSettlementMonetary(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
requestPrice := testPaymentThreshold.Uint64() - 1000
err = acc.Reserve(context.Background(), peer1Addr, requestPrice)
......@@ -589,7 +625,7 @@ func TestAccountingCallSettlementTooSoon(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -613,6 +649,8 @@ func TestAccountingCallSettlementTooSoon(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
requestPrice := testPaymentThreshold.Uint64() - 1000
err = acc.Reserve(context.Background(), peer1Addr, requestPrice)
......@@ -729,7 +767,7 @@ func TestAccountingCallSettlementEarly(t *testing.T) {
debt := uint64(500)
earlyPayment := big.NewInt(1000)
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, earlyPayment, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, earlyPayment, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -748,6 +786,8 @@ func TestAccountingCallSettlementEarly(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
err = acc.Credit(peer1Addr, debt, true)
if err != nil {
t.Fatal(err)
......@@ -788,7 +828,7 @@ func TestAccountingSurplusBalance(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, big.NewInt(0), big.NewInt(0), logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, big.NewInt(0), big.NewInt(0), logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -796,8 +836,13 @@ func TestAccountingSurplusBalance(t *testing.T) {
if err != nil {
t.Fatal(err)
}
acc.Connect(peer1Addr)
// Try Debiting a large amount to peer so balance is large positive
debitAction := acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64()-1)
debitAction, err := acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64()-1)
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
......@@ -846,7 +891,10 @@ func TestAccountingSurplusBalance(t *testing.T) {
t.Fatal("Not expected balance, expected 0")
}
// Debit for same peer, so balance stays 0 with surplusbalance decreasing to 2
debitAction = acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64())
debitAction, err = acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64())
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal("Unexpected error from Credit")
......@@ -869,7 +917,10 @@ func TestAccountingSurplusBalance(t *testing.T) {
t.Fatal("Not expected balance, expected 0")
}
// Debit for same peer, so balance goes to 9998 (testpaymentthreshold - 2) with surplusbalance decreasing to 0
debitAction = acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64())
debitAction, err = acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64())
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal("Unexpected error from Debit")
......@@ -900,7 +951,7 @@ func TestAccountingNotifyPaymentReceived(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -910,8 +961,13 @@ func TestAccountingNotifyPaymentReceived(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
debtAmount := uint64(100)
debitAction := acc.PrepareDebit(peer1Addr, debtAmount+testPaymentTolerance.Uint64())
debitAction, err := acc.PrepareDebit(peer1Addr, debtAmount+testPaymentTolerance.Uint64())
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
......@@ -923,7 +979,10 @@ func TestAccountingNotifyPaymentReceived(t *testing.T) {
t.Fatal(err)
}
debitAction = acc.PrepareDebit(peer1Addr, debtAmount)
debitAction, err = acc.PrepareDebit(peer1Addr, debtAmount)
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
......@@ -957,7 +1016,7 @@ func TestAccountingConnected(t *testing.T) {
pricing := &pricingMock{}
_, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, pricing, big.NewInt(testRefreshRate))
_, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, pricing, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -993,7 +1052,7 @@ func TestAccountingNotifyPaymentThreshold(t *testing.T) {
pricing := &pricingMock{}
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, big.NewInt(0), logger, store, pricing, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, big.NewInt(0), logger, store, pricing, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -1012,6 +1071,8 @@ func TestAccountingNotifyPaymentThreshold(t *testing.T) {
t.Fatal(err)
}
acc.Connect(peer1Addr)
debt := uint64(50)
lowerThreshold := uint64(100)
......@@ -1055,14 +1116,19 @@ func TestAccountingPeerDebt(t *testing.T) {
pricing := &pricingMock{}
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, big.NewInt(0), logger, store, pricing, big.NewInt(testRefreshRate))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, big.NewInt(0), logger, store, pricing, big.NewInt(testRefreshRate), p2pmock.New())
if err != nil {
t.Fatal(err)
}
peer1Addr := swarm.MustParseHexAddress("00112233")
acc.Connect(peer1Addr)
debt := uint64(1000)
debitAction := acc.PrepareDebit(peer1Addr, debt)
debitAction, err := acc.PrepareDebit(peer1Addr, debt)
if err != nil {
t.Fatal(err)
}
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
......@@ -1106,7 +1172,7 @@ func TestAccountingCallPaymentFailureRetries(t *testing.T) {
store := mock.NewStateStore()
defer store.Close()
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(1))
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(1), p2pmock.New())
if err != nil {
t.Fatal(err)
}
......@@ -1130,6 +1196,7 @@ func TestAccountingCallPaymentFailureRetries(t *testing.T) {
if err != nil {
t.Fatal(err)
}
acc.Connect(peer1Addr)
requestPrice := testPaymentThreshold.Uint64() - 100
......@@ -1209,3 +1276,232 @@ func TestAccountingCallPaymentFailureRetries(t *testing.T) {
acc.Release(peer1Addr, 1)
}
func TestAccountingGhostOverdraft(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mock.NewStateStore()
defer store.Close()
var blocklistTime int64
paymentThresholdInRefreshmentSeconds := new(big.Int).Div(testPaymentThreshold, big.NewInt(testRefreshRate)).Uint64()
f := func(s swarm.Address, t time.Duration) error {
blocklistTime = int64(t.Seconds())
return nil
}
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New(p2pmock.WithBlocklistFunc(f)))
if err != nil {
t.Fatal(err)
}
ts := int64(1000)
acc.SetTime(ts)
peer, err := swarm.ParseHexAddress("00112233")
if err != nil {
t.Fatal(err)
}
acc.Connect(peer)
requestPrice := testPaymentThreshold.Uint64()
debitActionNormal, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
err = debitActionNormal.Apply()
if err != nil {
t.Fatal(err)
}
debitActionNormal.Cleanup()
// debit ghost balance
debitActionGhost, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
debitActionGhost.Cleanup()
// increase shadow reserve
debitActionShadow, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
_ = debitActionShadow
if blocklistTime != 0 {
t.Fatal("unexpected blocklist")
}
// ghost overdraft triggering blocklist
debitAction4, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
debitAction4.Cleanup()
if blocklistTime != int64(5*paymentThresholdInRefreshmentSeconds) {
t.Fatalf("unexpected blocklisting time, got %v expected %v", blocklistTime, 5*paymentThresholdInRefreshmentSeconds)
}
}
func TestAccountingReconnectBeforeAllowed(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mock.NewStateStore()
defer store.Close()
var blocklistTime int64
paymentThresholdInRefreshmentSeconds := new(big.Int).Div(testPaymentThreshold, big.NewInt(testRefreshRate)).Uint64()
f := func(s swarm.Address, t time.Duration) error {
blocklistTime = int64(t.Seconds())
return nil
}
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New(p2pmock.WithBlocklistFunc(f)))
if err != nil {
t.Fatal(err)
}
ts := int64(1000)
acc.SetTime(ts)
peer, err := swarm.ParseHexAddress("00112233")
if err != nil {
t.Fatal(err)
}
acc.Connect(peer)
requestPrice := testPaymentThreshold.Uint64()
debitActionNormal, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
err = debitActionNormal.Apply()
if err != nil {
t.Fatal(err)
}
debitActionNormal.Cleanup()
// debit ghost balance
debitActionGhost, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
debitActionGhost.Cleanup()
// increase shadow reserve
debitActionShadow, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
_ = debitActionShadow
if blocklistTime != 0 {
t.Fatal("unexpected blocklist")
}
acc.Disconnect(peer)
if blocklistTime != 0 {
t.Fatal("unexpected blocklist")
}
//peer attempts to reconnect immediately
acc.Connect(peer)
if blocklistTime != int64(4*paymentThresholdInRefreshmentSeconds) {
t.Fatalf("unexpected blocklisting time, got %v expected %v", blocklistTime, 4*paymentThresholdInRefreshmentSeconds)
}
// 30 seconds pass, check whether we blocklist for the correct leftover time after a later connect attempt
ts = int64(1030)
acc.SetTime(ts)
acc.Connect(peer)
if blocklistTime != int64(paymentThresholdInRefreshmentSeconds) {
t.Fatalf("unexpected blocklisting time, got %v expected %v", blocklistTime, paymentThresholdInRefreshmentSeconds)
}
}
func TestAccountingReconnectAfterAllowed(t *testing.T) {
logger := logging.New(ioutil.Discard, 0)
store := mock.NewStateStore()
defer store.Close()
var blocklistTime int64
f := func(s swarm.Address, t time.Duration) error {
blocklistTime = int64(t.Seconds())
return nil
}
acc, err := accounting.NewAccounting(testPaymentThreshold, testPaymentTolerance, testPaymentEarly, logger, store, nil, big.NewInt(testRefreshRate), p2pmock.New(p2pmock.WithBlocklistFunc(f)))
if err != nil {
t.Fatal(err)
}
ts := int64(1000)
acc.SetTime(ts)
peer, err := swarm.ParseHexAddress("00112233")
if err != nil {
t.Fatal(err)
}
acc.Connect(peer)
requestPrice := testPaymentThreshold.Uint64()
debitActionNormal, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
err = debitActionNormal.Apply()
if err != nil {
t.Fatal(err)
}
debitActionNormal.Cleanup()
// debit ghost balance
debitActionGhost, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
debitActionGhost.Cleanup()
// increase shadow reserve
debitActionShadow, err := acc.PrepareDebit(peer, requestPrice)
if err != nil {
t.Fatal(err)
}
_ = debitActionShadow
if blocklistTime != 0 {
t.Fatal("unexpected blocklist")
}
acc.Disconnect(peer)
if blocklistTime != 0 {
t.Fatal("unexpected blocklist")
}
ts = int64(1040)
acc.SetTime(ts)
acc.Connect(peer)
if blocklistTime != 0 {
t.Fatalf("unexpected blocklisting time, got %v expected %v", blocklistTime, 0)
}
}
......@@ -22,7 +22,7 @@ type Service struct {
reserveFunc func(ctx context.Context, peer swarm.Address, price uint64) error
releaseFunc func(peer swarm.Address, price uint64)
creditFunc func(peer swarm.Address, price uint64, orig bool) error
prepareDebitFunc func(peer swarm.Address, price uint64) accounting.Action
prepareDebitFunc func(peer swarm.Address, price uint64) (accounting.Action, error)
balanceFunc func(swarm.Address) (*big.Int, error)
shadowBalanceFunc func(swarm.Address) (*big.Int, error)
balancesFunc func() (map[string]*big.Int, error)
......@@ -61,7 +61,7 @@ func WithCreditFunc(f func(peer swarm.Address, price uint64, orig bool) error) O
}
// WithDebitFunc sets the mock Debit function
func WithPrepareDebitFunc(f func(peer swarm.Address, price uint64) accounting.Action) Option {
func WithPrepareDebitFunc(f func(peer swarm.Address, price uint64) (accounting.Action, error)) Option {
return optionFunc(func(s *Service) {
s.prepareDebitFunc = f
})
......@@ -144,7 +144,7 @@ func (s *Service) Credit(peer swarm.Address, price uint64, orig bool) error {
}
// Debit is the mock function wrapper that calls the set implementation
func (s *Service) PrepareDebit(peer swarm.Address, price uint64) accounting.Action {
func (s *Service) PrepareDebit(peer swarm.Address, price uint64) (accounting.Action, error) {
if s.prepareDebitFunc != nil {
return s.prepareDebitFunc(peer, price)
}
......@@ -155,7 +155,7 @@ func (s *Service) PrepareDebit(peer swarm.Address, price uint64) accounting.Acti
price: bigPrice,
peer: peer,
applied: false,
}
}, nil
}
......@@ -227,6 +227,14 @@ func (s *Service) CompensatedBalances() (map[string]*big.Int, error) {
return s.balances, nil
}
func (s *Service) Connect(peer swarm.Address) {
}
func (s *Service) Disconnect(peer swarm.Address) {
}
//
func (s *Service) SurplusBalance(peer swarm.Address) (*big.Int, error) {
if s.balanceFunc != nil {
......
......@@ -522,6 +522,7 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
stateStore,
pricing,
big.NewInt(refreshRate),
p2ps,
)
if err != nil {
return nil, fmt.Errorf("accounting: %w", err)
......
......@@ -163,7 +163,10 @@ func (ps *PushSync) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream)
return fmt.Errorf("chunk store: %w", err)
}
debit := ps.accounting.PrepareDebit(p.Address, price)
debit, err := ps.accounting.PrepareDebit(p.Address, price)
if err != nil {
return fmt.Errorf("prepare debit to peer %s before writeback: %w", p.Address.String(), err)
}
defer debit.Cleanup()
// return back receipt
......@@ -308,7 +311,10 @@ func (ps *PushSync) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream)
}
// return back receipt
debit := ps.accounting.PrepareDebit(p.Address, price)
debit, err := ps.accounting.PrepareDebit(p.Address, price)
if err != nil {
return fmt.Errorf("prepare debit to peer %s before writeback: %w", p.Address.String(), err)
}
defer debit.Cleanup()
receipt := pb.Receipt{Address: chunk.Address().Bytes(), Signature: signature}
......@@ -322,7 +328,10 @@ func (ps *PushSync) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream)
}
debit := ps.accounting.PrepareDebit(p.Address, price)
debit, err := ps.accounting.PrepareDebit(p.Address, price)
if err != nil {
return fmt.Errorf("prepare debit to peer %s before writeback: %w", p.Address.String(), err)
}
defer debit.Cleanup()
// pass back the receipt
......
......@@ -429,7 +429,10 @@ func (s *Service) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) (e
}
chunkPrice := s.pricer.Price(chunk.Address())
debit := s.accounting.PrepareDebit(p.Address, chunkPrice)
debit, err := s.accounting.PrepareDebit(p.Address, chunkPrice)
if err != nil {
return fmt.Errorf("prepare debit to peer %s before writeback: %w", p.Address.String(), err)
}
defer debit.Cleanup()
if err := w.WriteMsgWithContext(ctx, &pb.Delivery{
......
......@@ -32,4 +32,6 @@ type Accounting interface {
NotifyPaymentReceived(peer swarm.Address, amount *big.Int) error
NotifyPaymentSent(peer swarm.Address, amount *big.Int, receivedError error)
NotifyRefreshmentReceived(peer swarm.Address, amount *big.Int) error
Connect(peer swarm.Address)
Disconnect(peer swarm.Address)
}
......@@ -102,6 +102,7 @@ func (s *Service) init(ctx context.Context, p p2p.Peer) error {
s.peers[p.Address.String()] = peerData
}
s.accounting.Connect(p.Address)
return nil
}
......@@ -110,6 +111,8 @@ func (s *Service) terminate(p p2p.Peer) error {
defer s.peersMu.Unlock()
delete(s.peers, p.Address.String())
s.accounting.Disconnect(p.Address)
return nil
}
......
......@@ -61,6 +61,14 @@ func (t *testObserver) PeerDebt(peer swarm.Address) (*big.Int, error) {
return nil, errors.New("Peer not listed")
}
func (t *testObserver) Connect(peer swarm.Address) {
}
func (t *testObserver) Disconnect(peer swarm.Address) {
}
func (t *testObserver) NotifyRefreshmentReceived(peer swarm.Address, amount *big.Int) error {
t.receivedCalled <- notifyPaymentReceivedCall{
peer: peer,
......
......@@ -82,6 +82,14 @@ func (t *testObserver) NotifyPaymentSent(peer swarm.Address, amount *big.Int, er
}
}
func (t *testObserver) Connect(peer swarm.Address) {
}
func (t *testObserver) Disconnect(peer swarm.Address) {
}
type addressbookMock struct {
beneficiary func(peer swarm.Address) (beneficiary common.Address, known bool, err error)
chequebook func(peer swarm.Address) (chequebookAddress common.Address, known bool, err error)
......
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