github.com/ethersphere/bee/v2@v2.2.0/pkg/settlement/pseudosettle/pseudosettle.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package pseudosettle
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"math/big"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/ethersphere/bee/v2/pkg/log"
    17  	"github.com/ethersphere/bee/v2/pkg/p2p"
    18  	"github.com/ethersphere/bee/v2/pkg/p2p/protobuf"
    19  	"github.com/ethersphere/bee/v2/pkg/settlement"
    20  	pb "github.com/ethersphere/bee/v2/pkg/settlement/pseudosettle/pb"
    21  	"github.com/ethersphere/bee/v2/pkg/storage"
    22  	"github.com/ethersphere/bee/v2/pkg/swarm"
    23  )
    24  
    25  // loggerName is the tree path name of the logger for this package.
    26  const loggerName = "pseudosettle"
    27  
    28  const (
    29  	protocolName    = "pseudosettle"
    30  	protocolVersion = "1.0.0"
    31  	streamName      = "pseudosettle"
    32  )
    33  
    34  var (
    35  	SettlementReceivedPrefix = "pseudosettle_total_received_"
    36  	SettlementSentPrefix     = "pseudosettle_total_sent_"
    37  
    38  	ErrSettlementTooSoon              = errors.New("settlement too soon")
    39  	ErrNoPseudoSettlePeer             = errors.New("settlement peer not found")
    40  	ErrDisconnectAllowanceCheckFailed = errors.New("settlement allowance below enforced amount")
    41  	ErrTimeOutOfSyncAlleged           = errors.New("settlement allowance timestamps from peer were decreasing")
    42  	ErrTimeOutOfSyncRecent            = errors.New("settlement allowance timestamps from peer differed from our measurement by more than 2 seconds")
    43  	ErrTimeOutOfSyncInterval          = errors.New("settlement allowance interval from peer differed from local interval by more than 3 seconds")
    44  	ErrRefreshmentBelowExpected       = errors.New("refreshment below expected")
    45  	ErrRefreshmentAboveExpected       = errors.New("refreshment above expected")
    46  )
    47  
    48  type Service struct {
    49  	streamer         p2p.Streamer
    50  	logger           log.Logger
    51  	store            storage.StateStorer
    52  	accounting       settlement.Accounting
    53  	metrics          metrics
    54  	refreshRate      *big.Int
    55  	lightRefreshRate *big.Int
    56  	p2pService       p2p.Service
    57  	timeNow          func() time.Time
    58  	peersMu          sync.Mutex
    59  	peers            map[string]*pseudoSettlePeer
    60  }
    61  
    62  type pseudoSettlePeer struct {
    63  	lock     sync.Mutex // lock to be held during receiving a payment from this peer
    64  	fullNode bool
    65  }
    66  
    67  type lastPayment struct {
    68  	Timestamp      int64
    69  	CheckTimestamp int64
    70  	Total          *big.Int
    71  }
    72  
    73  func New(streamer p2p.Streamer, logger log.Logger, store storage.StateStorer, accounting settlement.Accounting, refreshRate, lightRefreshRate *big.Int, p2pService p2p.Service) *Service {
    74  	return &Service{
    75  		streamer:         streamer,
    76  		logger:           logger.WithName(loggerName).Register(),
    77  		metrics:          newMetrics(),
    78  		store:            store,
    79  		accounting:       accounting,
    80  		p2pService:       p2pService,
    81  		refreshRate:      refreshRate,
    82  		lightRefreshRate: lightRefreshRate,
    83  		timeNow:          time.Now,
    84  		peers:            make(map[string]*pseudoSettlePeer),
    85  	}
    86  }
    87  
    88  func (s *Service) Protocol() p2p.ProtocolSpec {
    89  	return p2p.ProtocolSpec{
    90  		Name:    protocolName,
    91  		Version: protocolVersion,
    92  		StreamSpecs: []p2p.StreamSpec{
    93  			{
    94  				Name:    streamName,
    95  				Handler: s.handler,
    96  			},
    97  		},
    98  		ConnectIn:     s.init,
    99  		ConnectOut:    s.init,
   100  		DisconnectIn:  s.terminate,
   101  		DisconnectOut: s.terminate,
   102  	}
   103  }
   104  
   105  func (s *Service) init(ctx context.Context, p p2p.Peer) error {
   106  	s.peersMu.Lock()
   107  	defer s.peersMu.Unlock()
   108  
   109  	_, ok := s.peers[p.Address.String()]
   110  	if !ok {
   111  		peerData := &pseudoSettlePeer{fullNode: p.FullNode}
   112  		s.peers[p.Address.String()] = peerData
   113  	}
   114  
   115  	go s.accounting.Connect(p.Address, p.FullNode)
   116  	return nil
   117  }
   118  
   119  func (s *Service) terminate(p p2p.Peer) error {
   120  	s.peersMu.Lock()
   121  	defer s.peersMu.Unlock()
   122  
   123  	delete(s.peers, p.Address.String())
   124  
   125  	go s.accounting.Disconnect(p.Address)
   126  	return nil
   127  }
   128  
   129  func totalKey(peer swarm.Address, prefix string) string {
   130  	return fmt.Sprintf("%v%v", prefix, peer.String())
   131  }
   132  
   133  func totalKeyPeer(key []byte, prefix string) (peer swarm.Address, err error) {
   134  	k := string(key)
   135  
   136  	split := strings.SplitAfter(k, prefix)
   137  	if len(split) != 2 {
   138  		return swarm.ZeroAddress, errors.New("no peer in key")
   139  	}
   140  	return swarm.ParseHexAddress(split[1])
   141  }
   142  
   143  // peerAllowance computes the maximum incoming payment value we accept
   144  // this is the time based allowance or the peers actual debt, whichever is less
   145  func (s *Service) peerAllowance(peer swarm.Address, fullNode bool) (limit *big.Int, stamp int64, err error) {
   146  	var lastTime lastPayment
   147  	err = s.store.Get(totalKey(peer, SettlementReceivedPrefix), &lastTime)
   148  	if err != nil {
   149  		if !errors.Is(err, storage.ErrNotFound) {
   150  			return nil, 0, err
   151  		}
   152  		lastTime.Timestamp = int64(0)
   153  	}
   154  
   155  	currentTime := s.timeNow().Unix()
   156  	if currentTime == lastTime.Timestamp {
   157  		return nil, 0, ErrSettlementTooSoon
   158  	}
   159  
   160  	var refreshRateUsed *big.Int
   161  
   162  	if fullNode {
   163  		refreshRateUsed = s.refreshRate
   164  	} else {
   165  		refreshRateUsed = s.lightRefreshRate
   166  	}
   167  
   168  	maxAllowance := new(big.Int).Mul(big.NewInt(currentTime-lastTime.Timestamp), refreshRateUsed)
   169  
   170  	peerDebt, err := s.accounting.PeerDebt(peer)
   171  	if err != nil {
   172  		return nil, 0, err
   173  	}
   174  
   175  	if peerDebt.Cmp(maxAllowance) >= 0 {
   176  		return maxAllowance, currentTime, nil
   177  	}
   178  
   179  	return peerDebt, currentTime, nil
   180  }
   181  
   182  func (s *Service) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) (err error) {
   183  	loggerV1 := s.logger.V(1).Register()
   184  
   185  	w, r := protobuf.NewWriterAndReader(stream)
   186  	defer func() {
   187  		if err != nil {
   188  			_ = stream.Reset()
   189  			s.metrics.ReceivedPseudoSettlementsErrors.Inc()
   190  		} else {
   191  			stream.FullClose()
   192  		}
   193  	}()
   194  	var req pb.Payment
   195  	if err := r.ReadMsgWithContext(ctx, &req); err != nil {
   196  		return fmt.Errorf("read request from peer %v: %w", p.Address, err)
   197  	}
   198  
   199  	attemptedAmount := big.NewInt(0).SetBytes(req.Amount)
   200  
   201  	paymentAmount := new(big.Int).Set(attemptedAmount)
   202  
   203  	s.peersMu.Lock()
   204  	pseudoSettlePeer, ok := s.peers[p.Address.String()]
   205  	s.peersMu.Unlock()
   206  	if !ok {
   207  		return ErrNoPseudoSettlePeer
   208  	}
   209  
   210  	pseudoSettlePeer.lock.Lock()
   211  	defer pseudoSettlePeer.lock.Unlock()
   212  
   213  	allowance, timestamp, err := s.peerAllowance(p.Address, pseudoSettlePeer.fullNode)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	if allowance.Cmp(attemptedAmount) < 0 {
   219  		paymentAmount.Set(allowance)
   220  	}
   221  	loggerV1.Debug("pseudosettle accepting payment message from peer", "peer_address", p.Address, "amount", paymentAmount)
   222  
   223  	if paymentAmount.Cmp(big.NewInt(0)) < 0 {
   224  		paymentAmount.Set(big.NewInt(0))
   225  	}
   226  
   227  	err = w.WriteMsgWithContext(ctx, &pb.PaymentAck{
   228  		Amount:    paymentAmount.Bytes(),
   229  		Timestamp: timestamp,
   230  	})
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	var lastTime lastPayment
   236  	err = s.store.Get(totalKey(p.Address, SettlementReceivedPrefix), &lastTime)
   237  	if err != nil {
   238  		if !errors.Is(err, storage.ErrNotFound) {
   239  			return err
   240  		}
   241  		lastTime.Total = big.NewInt(0)
   242  	}
   243  
   244  	lastTime.Total = lastTime.Total.Add(lastTime.Total, paymentAmount)
   245  	lastTime.Timestamp = timestamp
   246  
   247  	err = s.store.Put(totalKey(p.Address, SettlementReceivedPrefix), lastTime)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	receivedPaymentF64, _ := big.NewFloat(0).SetInt(paymentAmount).Float64()
   253  	s.metrics.TotalReceivedPseudoSettlements.Add(receivedPaymentF64)
   254  	s.metrics.ReceivedPseudoSettlements.Inc()
   255  	return s.accounting.NotifyRefreshmentReceived(p.Address, paymentAmount, timestamp)
   256  }
   257  
   258  // Pay initiates a payment to the given peer
   259  func (s *Service) Pay(ctx context.Context, peer swarm.Address, amount *big.Int) {
   260  	loggerV1 := s.logger.V(1).Register()
   261  	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
   262  	defer cancel()
   263  
   264  	var err error
   265  
   266  	defer func() {
   267  		if err != nil {
   268  			s.metrics.SentPseudoSettlementsErrors.Inc()
   269  		}
   270  	}()
   271  
   272  	var lastTime lastPayment
   273  	err = s.store.Get(totalKey(peer, SettlementSentPrefix), &lastTime)
   274  	if err != nil {
   275  		if !errors.Is(err, storage.ErrNotFound) {
   276  			s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err)
   277  			return
   278  		}
   279  		lastTime.Total = big.NewInt(0)
   280  		lastTime.Timestamp = 0
   281  	}
   282  
   283  	stream, err := s.streamer.NewStream(ctx, peer, nil, protocolName, protocolVersion, streamName)
   284  	if err != nil {
   285  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err)
   286  		return
   287  	}
   288  	defer func() {
   289  		if err != nil {
   290  			_ = stream.Reset()
   291  		} else {
   292  			_ = stream.FullClose()
   293  		}
   294  	}()
   295  
   296  	loggerV1.Debug("pseudosettle sending payment message to peer", "peer_address", peer, "amount", amount)
   297  	w, r := protobuf.NewWriterAndReader(stream)
   298  
   299  	err = w.WriteMsgWithContext(ctx, &pb.Payment{
   300  		Amount: amount.Bytes(),
   301  	})
   302  	if err != nil {
   303  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err)
   304  		return
   305  	}
   306  
   307  	var paymentAck pb.PaymentAck
   308  	err = r.ReadMsgWithContext(ctx, &paymentAck)
   309  	if err != nil {
   310  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err)
   311  		return
   312  	}
   313  
   314  	checkTime := s.timeNow().UnixMilli()
   315  
   316  	acceptedAmount := new(big.Int).SetBytes(paymentAck.Amount)
   317  	if acceptedAmount.Cmp(amount) > 0 {
   318  		err = fmt.Errorf("pseudosettle: peer %v: %w", peer, ErrRefreshmentAboveExpected)
   319  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err)
   320  		return
   321  	}
   322  
   323  	experiencedInterval := checkTime/1000 - lastTime.CheckTimestamp
   324  	allegedInterval := paymentAck.Timestamp - lastTime.Timestamp
   325  
   326  	if allegedInterval < 0 {
   327  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, ErrTimeOutOfSyncAlleged)
   328  		return
   329  	}
   330  
   331  	experienceDifferenceRecent := paymentAck.Timestamp - checkTime/1000
   332  
   333  	if experienceDifferenceRecent < -2 || experienceDifferenceRecent > 2 {
   334  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, ErrTimeOutOfSyncRecent)
   335  		return
   336  	}
   337  
   338  	experienceDifferenceInterval := experiencedInterval - allegedInterval
   339  	if experienceDifferenceInterval < -3 || experienceDifferenceInterval > 3 {
   340  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, ErrTimeOutOfSyncInterval)
   341  		return
   342  	}
   343  
   344  	lastTime.Total = lastTime.Total.Add(lastTime.Total, acceptedAmount)
   345  	lastTime.Timestamp = paymentAck.Timestamp
   346  	lastTime.CheckTimestamp = checkTime / 1000
   347  
   348  	err = s.store.Put(totalKey(peer, SettlementSentPrefix), lastTime)
   349  	if err != nil {
   350  		s.accounting.NotifyRefreshmentSent(peer, nil, nil, 0, 0, err)
   351  		return
   352  	}
   353  
   354  	amountFloat, _ := new(big.Float).SetInt(acceptedAmount).Float64()
   355  	s.metrics.TotalSentPseudoSettlements.Add(amountFloat)
   356  	s.metrics.SentPseudoSettlements.Inc()
   357  
   358  	s.accounting.NotifyRefreshmentSent(peer, amount, acceptedAmount, checkTime, allegedInterval, nil)
   359  }
   360  
   361  func (s *Service) SetAccounting(accounting settlement.Accounting) {
   362  	s.accounting = accounting
   363  }
   364  
   365  // TotalSent returns the total amount sent to a peer
   366  func (s *Service) TotalSent(peer swarm.Address) (totalSent *big.Int, err error) {
   367  	var lastTime lastPayment
   368  
   369  	err = s.store.Get(totalKey(peer, SettlementSentPrefix), &lastTime)
   370  	if err != nil {
   371  		if !errors.Is(err, storage.ErrNotFound) {
   372  			return nil, settlement.ErrPeerNoSettlements
   373  		}
   374  		lastTime.Total = big.NewInt(0)
   375  	}
   376  
   377  	return lastTime.Total, nil
   378  }
   379  
   380  // TotalReceived returns the total amount received from a peer
   381  func (s *Service) TotalReceived(peer swarm.Address) (totalReceived *big.Int, err error) {
   382  	var lastTime lastPayment
   383  
   384  	err = s.store.Get(totalKey(peer, SettlementReceivedPrefix), &lastTime)
   385  	if err != nil {
   386  		if !errors.Is(err, storage.ErrNotFound) {
   387  			return nil, settlement.ErrPeerNoSettlements
   388  		}
   389  		lastTime.Total = big.NewInt(0)
   390  	}
   391  
   392  	return lastTime.Total, nil
   393  }
   394  
   395  // SettlementsSent returns all stored sent settlement values for a given type of prefix
   396  func (s *Service) SettlementsSent() (map[string]*big.Int, error) {
   397  	sent := make(map[string]*big.Int)
   398  	err := s.store.Iterate(SettlementSentPrefix, func(key, val []byte) (stop bool, err error) {
   399  		addr, err := totalKeyPeer(key, SettlementSentPrefix)
   400  		if err != nil {
   401  			return false, fmt.Errorf("parse address from key: %s: %w", string(key), err)
   402  		}
   403  		if _, ok := sent[addr.String()]; !ok {
   404  			var storevalue lastPayment
   405  			err = s.store.Get(totalKey(addr, SettlementSentPrefix), &storevalue)
   406  			if err != nil {
   407  				return false, fmt.Errorf("get peer %s settlement balance: %w", addr.String(), err)
   408  			}
   409  
   410  			sent[addr.String()] = storevalue.Total
   411  		}
   412  		return false, nil
   413  	})
   414  	if err != nil {
   415  		return nil, err
   416  	}
   417  	return sent, nil
   418  }
   419  
   420  // SettlementsReceived returns all stored received settlement values for a given type of prefix
   421  func (s *Service) SettlementsReceived() (map[string]*big.Int, error) {
   422  	received := make(map[string]*big.Int)
   423  	err := s.store.Iterate(SettlementReceivedPrefix, func(key, val []byte) (stop bool, err error) {
   424  		addr, err := totalKeyPeer(key, SettlementReceivedPrefix)
   425  		if err != nil {
   426  			return false, fmt.Errorf("parse address from key: %s: %w", string(key), err)
   427  		}
   428  		if _, ok := received[addr.String()]; !ok {
   429  			var storevalue lastPayment
   430  			err = s.store.Get(totalKey(addr, SettlementReceivedPrefix), &storevalue)
   431  			if err != nil {
   432  				return false, fmt.Errorf("get peer %s settlement balance: %w", addr.String(), err)
   433  			}
   434  
   435  			received[addr.String()] = storevalue.Total
   436  		}
   437  		return false, nil
   438  	})
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  	return received, nil
   443  }