github.com/ethersphere/bee/v2@v2.2.0/pkg/settlement/swap/chequebook/chequestore.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 chequebook
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"math/big"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethersphere/bee/v2/pkg/crypto"
    17  	"github.com/ethersphere/bee/v2/pkg/storage"
    18  	"github.com/ethersphere/bee/v2/pkg/transaction"
    19  )
    20  
    21  const (
    22  	// prefix for the persistence key
    23  	lastReceivedChequePrefix = "swap_chequebook_last_received_cheque_"
    24  )
    25  
    26  var (
    27  	// ErrNoCheque is the error returned if there is no prior cheque for a chequebook or beneficiary.
    28  	ErrNoCheque = errors.New("no cheque")
    29  	// ErrChequeNotIncreasing is the error returned if the cheque amount is the same or lower.
    30  	ErrChequeNotIncreasing = errors.New("cheque cumulativePayout is not increasing")
    31  	// ErrChequeInvalid is the error returned if the cheque itself is invalid.
    32  	ErrChequeInvalid = errors.New("invalid cheque")
    33  	// ErrWrongBeneficiary is the error returned if the cheque has the wrong beneficiary.
    34  	ErrWrongBeneficiary = errors.New("wrong beneficiary")
    35  	// ErrBouncingCheque is the error returned if the chequebook is demonstrably illiquid.
    36  	ErrBouncingCheque = errors.New("bouncing cheque")
    37  	// ErrChequeValueTooLow is the error returned if the after deduction value of a cheque did not cover 1 accounting credit
    38  	ErrChequeValueTooLow = errors.New("cheque value lower than acceptable")
    39  )
    40  
    41  // ChequeStore handles the verification and storage of received cheques
    42  type ChequeStore interface {
    43  	// ReceiveCheque verifies and stores a cheque. It returns the total amount earned.
    44  	ReceiveCheque(ctx context.Context, cheque *SignedCheque, exchangeRate, deduction *big.Int) (*big.Int, error)
    45  	// LastCheque returns the last cheque we received from a specific chequebook.
    46  	LastCheque(chequebook common.Address) (*SignedCheque, error)
    47  	// LastCheques returns the last received cheques from every known chequebook.
    48  	LastCheques() (map[common.Address]*SignedCheque, error)
    49  }
    50  
    51  type chequeStore struct {
    52  	lock               sync.Mutex
    53  	store              storage.StateStorer
    54  	factory            Factory
    55  	chaindID           int64
    56  	transactionService transaction.Service
    57  	beneficiary        common.Address // the beneficiary we expect in cheques sent to us
    58  	recoverChequeFunc  RecoverChequeFunc
    59  }
    60  
    61  type RecoverChequeFunc func(cheque *SignedCheque, chainID int64) (common.Address, error)
    62  
    63  // NewChequeStore creates new ChequeStore
    64  func NewChequeStore(
    65  	store storage.StateStorer,
    66  	factory Factory,
    67  	chainID int64,
    68  	beneficiary common.Address,
    69  	transactionService transaction.Service,
    70  	recoverChequeFunc RecoverChequeFunc) ChequeStore {
    71  	return &chequeStore{
    72  		store:              store,
    73  		factory:            factory,
    74  		chaindID:           chainID,
    75  		transactionService: transactionService,
    76  		beneficiary:        beneficiary,
    77  		recoverChequeFunc:  recoverChequeFunc,
    78  	}
    79  }
    80  
    81  // lastReceivedChequeKey computes the key where to store the last cheque received from a chequebook.
    82  func lastReceivedChequeKey(chequebook common.Address) string {
    83  	return fmt.Sprintf("%s_%x", lastReceivedChequePrefix, chequebook)
    84  }
    85  
    86  // LastCheque returns the last cheque we received from a specific chequebook.
    87  func (s *chequeStore) LastCheque(chequebook common.Address) (*SignedCheque, error) {
    88  	var cheque *SignedCheque
    89  	err := s.store.Get(lastReceivedChequeKey(chequebook), &cheque)
    90  	if err != nil {
    91  		if !errors.Is(err, storage.ErrNotFound) {
    92  			return nil, err
    93  		}
    94  		return nil, ErrNoCheque
    95  	}
    96  
    97  	return cheque, nil
    98  }
    99  
   100  // ReceiveCheque verifies and stores a cheque. It returns the totam amount earned.
   101  func (s *chequeStore) ReceiveCheque(ctx context.Context, cheque *SignedCheque, exchangeRate, deduction *big.Int) (*big.Int, error) {
   102  	// verify we are the beneficiary
   103  	if cheque.Beneficiary != s.beneficiary {
   104  		return nil, ErrWrongBeneficiary
   105  	}
   106  
   107  	// don't allow concurrent processing of cheques
   108  	// this would be sufficient on a per chequebook basis
   109  	s.lock.Lock()
   110  	defer s.lock.Unlock()
   111  
   112  	// load the lastCumulativePayout for the cheques chequebook
   113  	var lastCumulativePayout *big.Int
   114  	var lastReceivedCheque *SignedCheque
   115  	err := s.store.Get(lastReceivedChequeKey(cheque.Chequebook), &lastReceivedCheque)
   116  	if err != nil {
   117  		if !errors.Is(err, storage.ErrNotFound) {
   118  			return nil, err
   119  		}
   120  
   121  		// if this is the first cheque from this chequebook, verify with the factory.
   122  		err = s.factory.VerifyChequebook(ctx, cheque.Chequebook)
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  
   127  		lastCumulativePayout = big.NewInt(0)
   128  	} else {
   129  		lastCumulativePayout = lastReceivedCheque.CumulativePayout
   130  	}
   131  
   132  	// check this cheque is actually increasing in value
   133  	amount := big.NewInt(0).Sub(cheque.CumulativePayout, lastCumulativePayout)
   134  
   135  	if amount.Cmp(big.NewInt(0)) <= 0 {
   136  		return nil, ErrChequeNotIncreasing
   137  	}
   138  
   139  	deducedAmount := new(big.Int).Sub(amount, deduction)
   140  
   141  	if deducedAmount.Cmp(exchangeRate) < 0 {
   142  		return nil, ErrChequeValueTooLow
   143  	}
   144  
   145  	// blockchain calls below
   146  	contract := newChequebookContract(cheque.Chequebook, s.transactionService)
   147  
   148  	// this does not change for the same chequebook
   149  	expectedIssuer, err := contract.Issuer(ctx)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	// verify the cheque signature
   155  	issuer, err := s.recoverChequeFunc(cheque, s.chaindID)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	if issuer != expectedIssuer {
   161  		return nil, ErrChequeInvalid
   162  	}
   163  
   164  	// basic liquidity check
   165  	// could be omitted as it is not particularly useful
   166  	balance, err := contract.Balance(ctx)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	alreadyPaidOut, err := contract.PaidOut(ctx, s.beneficiary)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	if balance.Cmp(big.NewInt(0).Sub(cheque.CumulativePayout, alreadyPaidOut)) < 0 {
   177  		return nil, ErrBouncingCheque
   178  	}
   179  
   180  	// store the accepted cheque
   181  	err = s.store.Put(lastReceivedChequeKey(cheque.Chequebook), cheque)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	return amount, nil
   187  }
   188  
   189  // RecoverCheque recovers the issuer ethereum address from a signed cheque
   190  func RecoverCheque(cheque *SignedCheque, chaindID int64) (common.Address, error) {
   191  	eip712Data := eip712DataForCheque(&cheque.Cheque, chaindID)
   192  
   193  	pubkey, err := crypto.RecoverEIP712(cheque.Signature, eip712Data)
   194  	if err != nil {
   195  		return common.Address{}, err
   196  	}
   197  
   198  	ethAddr, err := crypto.NewEthereumAddress(*pubkey)
   199  	if err != nil {
   200  		return common.Address{}, err
   201  	}
   202  
   203  	var issuer common.Address
   204  	copy(issuer[:], ethAddr)
   205  	return issuer, nil
   206  }
   207  
   208  // keyChequebook computes the chequebook a store entry is for.
   209  func keyChequebook(key []byte, prefix string) (chequebook common.Address, err error) {
   210  	k := string(key)
   211  
   212  	split := strings.SplitAfter(k, prefix)
   213  	if len(split) != 2 {
   214  		return common.Address{}, errors.New("no peer in key")
   215  	}
   216  	return common.HexToAddress(split[1]), nil
   217  }
   218  
   219  // LastCheques returns the last received cheques from every known chequebook.
   220  func (s *chequeStore) LastCheques() (map[common.Address]*SignedCheque, error) {
   221  	result := make(map[common.Address]*SignedCheque)
   222  	err := s.store.Iterate(lastReceivedChequePrefix, func(key, val []byte) (stop bool, err error) {
   223  		addr, err := keyChequebook(key, lastReceivedChequePrefix+"_")
   224  		if err != nil {
   225  			return false, fmt.Errorf("parse address from key: %s: %w", string(key), err)
   226  		}
   227  
   228  		if _, ok := result[addr]; !ok {
   229  			lastCheque, err := s.LastCheque(addr)
   230  			if err != nil && !errors.Is(err, ErrNoCheque) {
   231  				return false, err
   232  			} else if errors.Is(err, ErrNoCheque) {
   233  				return false, nil
   234  			}
   235  
   236  			result[addr] = lastCheque
   237  		}
   238  		return false, nil
   239  	})
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	return result, nil
   244  }