func (a *Accounting) settle(peer swarm.Address, balance *accountingPeer) error {
	now := a.timeNow().Unix()
	timeElapsed := now - balance.refreshTimestamp
	oldBalance, err := a.Balance(peer)
	if err != nil {
		if !errors.Is(err, ErrPeerNoBalance) {
			return fmt.Errorf("failed to load balance: %w", err)
		}
	}
	compensatedBalance, err := a.CompensatedBalance(peer)
	if err != nil {
		return err
	}
	paymentAmount := new(big.Int).Neg(compensatedBalance)
	if paymentAmount.Cmp(big.NewInt(0)) > 0 && timeElapsed > 0 {
		shadowBalance, err := a.shadowBalance(peer)
		if err != nil {
			return err
		}
		acceptedAmount, timestamp, err := a.refreshFunction(context.Background(), peer, paymentAmount, shadowBalance)
		if err != nil {
			a.metrics.AccountingDisconnectsEnforceRefreshCount.Inc()
			_ = a.blocklist(peer, 1)
			return fmt.Errorf("refresh failure: %w", err)
		}
		balance.refreshTimestamp = timestamp
		oldBalance = new(big.Int).Add(oldBalance, acceptedAmount)
		a.logger.Tracef("registering refreshment sent to peer %v with amount %d, new balance is %d", peer, acceptedAmount, oldBalance)
		err = a.store.Put(peerBalanceKey(peer), oldBalance)
		if err != nil {
			return fmt.Errorf("settle: failed to persist balance: %w", err)
		}
		err = a.decreaseOriginatedBalanceTo(peer, oldBalance)
		if err != nil {
			return fmt.Errorf("settle: failed to decrease originated balance: %w", err)
		}
	}
	if a.payFunction != nil && !balance.paymentOngoing {
		difference := now - balance.lastSettlementFailureTimestamp
		if difference > failedSettlementInterval {
			originatedBalance, err := a.OriginatedBalance(peer)
			if err != nil {
				if !errors.Is(err, ErrPeerNoBalance) {
					return fmt.Errorf("failed to load originated balance to settle: %w", err)
				}
			}
			paymentAmount := new(big.Int).Neg(originatedBalance)
			if paymentAmount.Cmp(a.minimumPayment) >= 0 {
				balance.paymentOngoing = true
				balance.shadowReservedBalance.Add(balance.shadowReservedBalance, paymentAmount)
				a.wg.Add(1)
				go a.payFunction(context.Background(), peer, paymentAmount)
			}
		}
	}
	return nil
}
Settle all debt with a peer. The lock on the accountingPeer must be held when
called.
package accounting
import (
	"context"
	"errors"
	"fmt"
	"math/big"
	"strings"
	"sync"
	"time"
	"github.com/ethsana/sana/pkg/logging"
	"github.com/ethsana/sana/pkg/p2p"
	"github.com/ethsana/sana/pkg/pricing"
	"github.com/ethsana/sana/pkg/storage"
	"github.com/ethsana/sana/pkg/swarm"
)
var (
	_                        Interface = (*Accounting)(nil)
	balancesPrefix                     = "accounting_balance_"
	balancesSurplusPrefix              = "accounting_surplusbalance_"
	balancesOriginatedPrefix           = "accounting_originatedbalance_"
	minimumPaymentDivisor    = int64(5)
	failedSettlementInterval = int64(10)  
)
type Interface interface {
	Reserve(ctx context.Context, peer swarm.Address, price uint64) error
	Release(peer swarm.Address, price uint64)
	Credit(peer swarm.Address, price uint64, originated bool) error
	PrepareDebit(peer swarm.Address, price uint64) (Action, error)
	Balance(peer swarm.Address) (*big.Int, error)
	SurplusBalance(peer swarm.Address) (*big.Int, error)
	Balances() (map[string]*big.Int, error)
	CompensatedBalance(peer swarm.Address) (*big.Int, error)
	CompensatedBalances() (map[string]*big.Int, error)
}
type Action interface {
	Cleanup()
	Apply() error
}
type debitAction struct {
	accounting     *Accounting
	price          *big.Int
	peer           swarm.Address
	accountingPeer *accountingPeer
	applied        bool
}
type PayFunc func(context.Context, swarm.Address, *big.Int)
type RefreshFunc func(context.Context, swarm.Address, *big.Int, *big.Int) (*big.Int, int64, error)
type accountingPeer struct {
	lock                           sync.Mutex  
	reservedBalance                *big.Int    
	shadowReservedBalance          *big.Int    
	ghostBalance                   *big.Int    
	paymentThreshold               *big.Int    
	refreshTimestamp               int64       
	paymentOngoing                 bool        
	lastSettlementFailureTimestamp int64       
	connected                      bool
}
type Accounting struct {
	accountingPeersMu sync.Mutex
	accountingPeers   map[string]*accountingPeer
	logger            logging.Logger
	store             storage.StateStorer
	paymentThreshold *big.Int
	paymentTolerance *big.Int
	earlyPayment *big.Int
	disconnectLimit *big.Int
	payFunction PayFunc
	refreshFunction RefreshFunc
	refreshRate *big.Int
	minimumPayment *big.Int
	pricing        pricing.Interface
	metrics        metrics
	wg             sync.WaitGroup
	p2p            p2p.Service
	timeNow        func() time.Time
}
var (
	ErrOverdraft = errors.New("attempted overdraft")
	ErrDisconnectThresholdExceeded = errors.New("disconnect threshold exceeded")
	ErrPeerNoBalance = errors.New("no balance for peer")
	ErrInvalidValue = errors.New("invalid value")
)
func NewAccounting(
	PaymentThreshold,
	PaymentTolerance,
	EarlyPayment *big.Int,
	Logger logging.Logger,
	Store storage.StateStorer,
	Pricing pricing.Interface,
	refreshRate *big.Int,
	p2pService p2p.Service,
) (*Accounting, error) {
	return &Accounting{
		accountingPeers:  make(map[string]*accountingPeer),
		paymentThreshold: new(big.Int).Set(PaymentThreshold),
		paymentTolerance: new(big.Int).Set(PaymentTolerance),
		earlyPayment:     new(big.Int).Set(EarlyPayment),
		disconnectLimit:  new(big.Int).Add(PaymentThreshold, PaymentTolerance),
		logger:           Logger,
		store:            Store,
		pricing:          Pricing,
		metrics:          newMetrics(),
		refreshRate:      refreshRate,
		timeNow:          time.Now,
		minimumPayment:   new(big.Int).Div(refreshRate, big.NewInt(minimumPaymentDivisor)),
		p2p:              p2pService,
	}, nil
}
func (a *Accounting) getIncreasedExpectedDebt(peer swarm.Address, accountingPeer *accountingPeer, bigPrice *big.Int) (*big.Int, *big.Int, error) {
	nextReserved := new(big.Int).Add(accountingPeer.reservedBalance, bigPrice)
	currentBalance, err := a.Balance(peer)
	if err != nil && !errors.Is(err, ErrPeerNoBalance) {
		return nil, nil, fmt.Errorf("failed to load balance: %w", err)
	}
	currentDebt := new(big.Int).Neg(currentBalance)
	if currentDebt.Cmp(big.NewInt(0)) < 0 {
		currentDebt.SetInt64(0)
	}
	expectedDebt := new(big.Int).Add(currentDebt, nextReserved)
	additionalDebt, err := a.SurplusBalance(peer)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to load surplus balance: %w", err)
	}
	return new(big.Int).Add(expectedDebt, additionalDebt), currentBalance, nil
}
func (a *Accounting) Reserve(ctx context.Context, peer swarm.Address, price uint64) error {
	accountingPeer := a.getAccountingPeer(peer)
	accountingPeer.lock.Lock()
	defer accountingPeer.lock.Unlock()
	if !accountingPeer.connected {
		return fmt.Errorf("connection not initialized yet")
	}
	a.metrics.AccountingReserveCount.Inc()
	bigPrice := new(big.Int).SetUint64(price)
	threshold := new(big.Int).Set(accountingPeer.paymentThreshold)
	if threshold.Cmp(a.earlyPayment) > 0 {
		threshold.Sub(threshold, a.earlyPayment)
	} else {
		threshold.SetInt64(0)
	}
	increasedExpectedDebt, currentBalance, err := a.getIncreasedExpectedDebt(peer, accountingPeer, bigPrice)
	if err != nil {
		return err
	}
	increasedExpectedDebtReduced := new(big.Int).Sub(increasedExpectedDebt, accountingPeer.shadowReservedBalance)
	if increasedExpectedDebtReduced.Cmp(threshold) >= 0 && currentBalance.Cmp(big.NewInt(0)) < 0 {
		err = a.settle(peer, accountingPeer)
		if err != nil {
			return fmt.Errorf("failed to settle with peer %v: %v", peer, err)
		}
		increasedExpectedDebt, _, err = a.getIncreasedExpectedDebt(peer, accountingPeer, bigPrice)
		if err != nil {
			return err
		}
	}
	if increasedExpectedDebt.Cmp(accountingPeer.paymentThreshold) > 0 {
		a.metrics.AccountingBlocksCount.Inc()
		return ErrOverdraft
	}
	accountingPeer.reservedBalance = new(big.Int).Add(accountingPeer.reservedBalance, bigPrice)
	return nil
}
func (a *Accounting) Release(peer swarm.Address, price uint64) {
	accountingPeer := a.getAccountingPeer(peer)
	accountingPeer.lock.Lock()
	defer accountingPeer.lock.Unlock()
	bigPrice := new(big.Int).SetUint64(price)
	if bigPrice.Cmp(accountingPeer.reservedBalance) > 0 {
		a.logger.Error("attempting to release more balance than was reserved for peer")
		accountingPeer.reservedBalance.SetUint64(0)
	} else {
		accountingPeer.reservedBalance.Sub(accountingPeer.reservedBalance, bigPrice)
	}
}
func (a *Accounting) Credit(peer swarm.Address, price uint64, originated bool) error {
	accountingPeer := a.getAccountingPeer(peer)
	accountingPeer.lock.Lock()
	defer accountingPeer.lock.Unlock()
	currentBalance, err := a.Balance(peer)
	if err != nil {
		if !errors.Is(err, ErrPeerNoBalance) {
			return fmt.Errorf("failed to load balance: %w", err)
		}
	}
	nextBalance := new(big.Int).Sub(currentBalance, new(big.Int).SetUint64(price))
	a.logger.Tracef("crediting peer %v with price %d, new balance is %d", peer, price, nextBalance)
	err = a.store.Put(peerBalanceKey(peer), nextBalance)
	if err != nil {
		return fmt.Errorf("failed to persist balance: %w", err)
	}
	a.metrics.TotalCreditedAmount.Add(float64(price))
	a.metrics.CreditEventsCount.Inc()
	if !originated {
		return nil
	}
	originBalance, err := a.OriginatedBalance(peer)
	if err != nil && !errors.Is(err, ErrPeerNoBalance) {
		return fmt.Errorf("failed to load originated balance: %w", err)
	}
	nextOriginBalance := new(big.Int).Sub(originBalance, new(big.Int).SetUint64(price))
	a.logger.Tracef("crediting peer %v with price %d, new originated balance is %d", peer, price, nextOriginBalance)
	zero := big.NewInt(0)
	if nextBalance.Cmp(zero) > 0 {
		nextBalance.Set(zero)
	}
	if nextOriginBalance.Cmp(nextBalance) < 0 {
		nextOriginBalance.Set(nextBalance)
		a.logger.Tracef("decreasing originated balance to peer %v to current balance %d", peer, nextOriginBalance)
	}
	err = a.store.Put(originatedBalanceKey(peer), nextOriginBalance)
	if err != nil {
		return fmt.Errorf("failed to persist originated balance: %w", err)
	}
	a.metrics.TotalOriginatedCreditedAmount.Add(float64(price))
	a.metrics.OriginatedCreditEventsCount.Inc()
	return nil
}