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 }