Skip to content

Commit

Permalink
feat: time based settlements
Browse files Browse the repository at this point in the history
  • Loading branch information
ralph-pichler committed May 15, 2021
1 parent 324133d commit 7c9719d
Show file tree
Hide file tree
Showing 18 changed files with 1,750 additions and 250 deletions.
428 changes: 303 additions & 125 deletions pkg/accounting/accounting.go

Large diffs are not rendered by default.

40 changes: 30 additions & 10 deletions pkg/accounting/accounting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@ func TestAccountingAddBalance(t *testing.T) {
}
acc.Release(booking.peer, uint64(-booking.price))
} else {
err = acc.Debit(booking.peer, uint64(booking.price))
debitAction := acc.PrepareDebit(booking.peer, uint64(booking.price))
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
}
debitAction.Cleanup()
}

balance, err := acc.Balance(booking.peer)
Expand Down Expand Up @@ -125,10 +127,12 @@ func TestAccountingAdd_persistentBalances(t *testing.T) {
}

peer1DebitAmount := testPrice
err = acc.Debit(peer1Addr, peer1DebitAmount)
debitAction := acc.PrepareDebit(peer1Addr, peer1DebitAmount)
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
}
debitAction.Cleanup()

peer2CreditAmount := 2 * testPrice
err = acc.Credit(peer2Addr, peer2CreditAmount)
Expand Down Expand Up @@ -206,16 +210,20 @@ func TestAccountingDisconnect(t *testing.T) {
}

// put the peer 1 unit away from disconnect
err = acc.Debit(peer1Addr, testPaymentThreshold.Uint64()+testPaymentTolerance.Uint64()-1)
debitAction := acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64()+testPaymentTolerance.Uint64()-1)
err = debitAction.Apply()
if err != nil {
t.Fatal("expected no error while still within tolerance")
}
debitAction.Cleanup()

// put the peer over thee threshold
err = acc.Debit(peer1Addr, 1)
debitAction = acc.PrepareDebit(peer1Addr, 1)
err = debitAction.Apply()
if err == nil {
t.Fatal("expected Add to return error")
}
debitAction.Cleanup()

var e *p2p.BlockPeerError
if !errors.As(err, &e) {
Expand Down Expand Up @@ -415,10 +423,12 @@ func TestAccountingSurplusBalance(t *testing.T) {
t.Fatal(err)
}
// Try Debiting a large amount to peer so balance is large positive
err = acc.Debit(peer1Addr, testPaymentThreshold.Uint64()-1)
debitAction := acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64()-1)
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
}
debitAction.Cleanup()
// Notify of incoming payment from same peer, so balance goes to 0 with surplusbalance 2
err = acc.NotifyPaymentReceived(peer1Addr, new(big.Int).Add(testPaymentThreshold, big.NewInt(1)))
if err != nil {
Expand Down Expand Up @@ -462,10 +472,12 @@ 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
err = acc.Debit(peer1Addr, testPaymentThreshold.Uint64())
debitAction = acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64())
err = debitAction.Apply()
if err != nil {
t.Fatal("Unexpected error from Credit")
}
debitAction.Cleanup()
// samity check surplus balance
val, err = acc.SurplusBalance(peer1Addr)
if err != nil {
Expand All @@ -483,10 +495,12 @@ 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
err = acc.Debit(peer1Addr, testPaymentThreshold.Uint64())
debitAction = acc.PrepareDebit(peer1Addr, testPaymentThreshold.Uint64())
err = debitAction.Apply()
if err != nil {
t.Fatal("Unexpected error from Debit")
}
debitAction.Cleanup()
// samity check surplus balance
val, err = acc.SurplusBalance(peer1Addr)
if err != nil {
Expand Down Expand Up @@ -523,20 +537,24 @@ func TestAccountingNotifyPaymentReceived(t *testing.T) {
}

debtAmount := uint64(100)
err = acc.Debit(peer1Addr, debtAmount+testPaymentTolerance.Uint64())
debitAction := acc.PrepareDebit(peer1Addr, debtAmount+testPaymentTolerance.Uint64())
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
}
debitAction.Cleanup()

err = acc.NotifyPaymentReceived(peer1Addr, new(big.Int).SetUint64(debtAmount+testPaymentTolerance.Uint64()))
if err != nil {
t.Fatal(err)
}

err = acc.Debit(peer1Addr, debtAmount)
debitAction = acc.PrepareDebit(peer1Addr, debtAmount)
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
}
debitAction.Cleanup()

err = acc.NotifyPaymentReceived(peer1Addr, new(big.Int).SetUint64(debtAmount+testPaymentTolerance.Uint64()+1))
if err != nil {
Expand Down Expand Up @@ -677,10 +695,12 @@ func TestAccountingPeerDebt(t *testing.T) {

peer1Addr := swarm.MustParseHexAddress("00112233")
debt := uint64(1000)
err = acc.Debit(peer1Addr, debt)
debitAction := acc.PrepareDebit(peer1Addr, debt)
err = debitAction.Apply()
if err != nil {
t.Fatal(err)
}
debitAction.Cleanup()
actualDebt, err := acc.PeerDebt(peer1Addr)
if err != nil {
t.Fatal(err)
Expand Down
58 changes: 47 additions & 11 deletions pkg/accounting/mock/accounting.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@ 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) error
debitFunc func(peer swarm.Address, price uint64) error
prepareDebitFunc func(peer swarm.Address, price uint64) accounting.Action
balanceFunc func(swarm.Address) (*big.Int, error)
shadowBalanceFunc func(swarm.Address) (*big.Int, error)
balancesFunc func() (map[string]*big.Int, error)
compensatedBalanceFunc func(swarm.Address) (*big.Int, error)
compensatedBalancesFunc func() (map[string]*big.Int, error)

balanceSurplusFunc func(swarm.Address) (*big.Int, error)
}

type debitAction struct {
accounting *Service
price *big.Int
peer swarm.Address
applied bool
}

// WithReserveFunc sets the mock Reserve function
func WithReserveFunc(f func(ctx context.Context, peer swarm.Address, price uint64) error) Option {
return optionFunc(func(s *Service) {
Expand All @@ -53,9 +61,9 @@ func WithCreditFunc(f func(peer swarm.Address, price uint64) error) Option {
}

// WithDebitFunc sets the mock Debit function
func WithDebitFunc(f func(peer swarm.Address, price uint64) error) Option {
func WithPrepareDebitFunc(f func(peer swarm.Address, price uint64) accounting.Action) Option {
return optionFunc(func(s *Service) {
s.debitFunc = f
s.prepareDebitFunc = f
})
}

Expand Down Expand Up @@ -136,21 +144,36 @@ func (s *Service) Credit(peer swarm.Address, price uint64) error {
}

// Debit is the mock function wrapper that calls the set implementation
func (s *Service) Debit(peer swarm.Address, price uint64) error {
if s.debitFunc != nil {
return s.debitFunc(peer, price)
func (s *Service) PrepareDebit(peer swarm.Address, price uint64) accounting.Action {
if s.prepareDebitFunc != nil {
return s.prepareDebitFunc(peer, price)
}
s.lock.Lock()
defer s.lock.Unlock()

if bal, ok := s.balances[peer.String()]; ok {
s.balances[peer.String()] = new(big.Int).Add(bal, new(big.Int).SetUint64(price))
bigPrice := new(big.Int).SetUint64(price)
return &debitAction{
accounting: s,
price: bigPrice,
peer: peer,
applied: false,
}

}

func (a *debitAction) Apply() error {
a.accounting.lock.Lock()
defer a.accounting.lock.Unlock()

if bal, ok := a.accounting.balances[a.peer.String()]; ok {
a.accounting.balances[a.peer.String()] = new(big.Int).Add(bal, new(big.Int).Set(a.price))
} else {
s.balances[peer.String()] = new(big.Int).SetUint64(price)
a.accounting.balances[a.peer.String()] = new(big.Int).Set(a.price)
}

return nil
}

func (a *debitAction) Cleanup() {}

// Balance is the mock function wrapper that calls the set implementation
func (s *Service) Balance(peer swarm.Address) (*big.Int, error) {
if s.balanceFunc != nil {
Expand All @@ -165,6 +188,19 @@ func (s *Service) Balance(peer swarm.Address) (*big.Int, error) {
}
}

func (s *Service) ShadowBalance(peer swarm.Address) (*big.Int, error) {
if s.shadowBalanceFunc != nil {
return s.shadowBalanceFunc(peer)
}
s.lock.Lock()
defer s.lock.Unlock()
if bal, ok := s.balances[peer.String()]; ok {
return new(big.Int).Neg(bal), nil
} else {
return big.NewInt(0), nil
}
}

// Balances is the mock function wrapper that calls the set implementation
func (s *Service) Balances() (map[string]*big.Int, error) {
if s.balancesFunc != nil {
Expand Down
5 changes: 5 additions & 0 deletions pkg/debugapi/debugapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Service struct {
tags *tags.Tags
accounting accounting.Interface
settlement settlement.Interface
pseudo settlement.Interface
chequebookEnabled bool
chequebook chequebook.Service
swap swap.ApiInterface
Expand Down Expand Up @@ -97,6 +98,10 @@ func (s *Service) Configure(p2p p2p.DebugService, pingpong pingpong.Interface, t
s.setRouter(s.newRouter())
}

func (s *Service) SetPseudo(pseudo settlement.Interface) {
s.pseudo = pseudo
}

// ServeHTTP implements http.Handler interface.
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// protect handler as it is changed by the Configure method
Expand Down
4 changes: 4 additions & 0 deletions pkg/debugapi/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ func (s *Service) newRouter() *mux.Router {
"GET": http.HandlerFunc(s.settlementsHandler),
})

router.Handle("/pseudo", jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.settlementsHandlerPseudo),
})

router.Handle("/settlements/{peer}", jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.peerSettlementsHandler),
})
Expand Down
56 changes: 56 additions & 0 deletions pkg/debugapi/settlements.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,59 @@ func (s *Service) peerSettlementsHandler(w http.ResponseWriter, r *http.Request)
SettlementSent: sent,
})
}

func (s *Service) settlementsHandlerPseudo(w http.ResponseWriter, r *http.Request) {

settlementsSent, err := s.pseudo.SettlementsSent()
if err != nil {
jsonhttp.InternalServerError(w, errCantSettlements)
s.logger.Debugf("debug api: sent settlements: %v", err)
s.logger.Error("debug api: can not get sent settlements")
return
}
settlementsReceived, err := s.pseudo.SettlementsReceived()
if err != nil {
jsonhttp.InternalServerError(w, errCantSettlements)
s.logger.Debugf("debug api: received settlements: %v", err)
s.logger.Error("debug api: can not get received settlements")
return
}

totalReceived := big.NewInt(0)
totalSent := big.NewInt(0)

settlementResponses := make(map[string]settlementResponse)

for a, b := range settlementsSent {
settlementResponses[a] = settlementResponse{
Peer: a,
SettlementSent: b,
SettlementReceived: big.NewInt(0),
}
totalSent.Add(b, totalSent)
}

for a, b := range settlementsReceived {
if _, ok := settlementResponses[a]; ok {
t := settlementResponses[a]
t.SettlementReceived = b
settlementResponses[a] = t
} else {
settlementResponses[a] = settlementResponse{
Peer: a,
SettlementSent: big.NewInt(0),
SettlementReceived: b,
}
}
totalReceived.Add(b, totalReceived)
}

settlementResponsesArray := make([]settlementResponse, len(settlementResponses))
i := 0
for k := range settlementResponses {
settlementResponsesArray[i] = settlementResponses[k]
i++
}

jsonhttp.OK(w, settlementsResponse{TotalSettlementReceived: totalReceived, TotalSettlementSent: totalSent, Settlements: settlementResponsesArray})
}
Loading

0 comments on commit 7c9719d

Please sign in to comment.