github.com/ethersphere/bee/v2@v2.2.0/pkg/api/chequebook.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 api 6 7 import ( 8 "errors" 9 "math/big" 10 "net/http" 11 12 "github.com/ethereum/go-ethereum/common" 13 "github.com/ethersphere/bee/v2/pkg/bigint" 14 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 15 "github.com/ethersphere/bee/v2/pkg/postage/postagecontract" 16 "github.com/ethersphere/bee/v2/pkg/settlement/swap" 17 "github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook" 18 19 "github.com/ethersphere/bee/v2/pkg/swarm" 20 "github.com/gorilla/mux" 21 ) 22 23 const ( 24 errChequebookBalance = "cannot get chequebook balance" 25 errChequebookNoAmount = "did not specify amount" 26 errChequebookNoWithdraw = "cannot withdraw" 27 errChequebookNoDeposit = "cannot deposit" 28 errChequebookInsufficientFunds = "insufficient funds" 29 errCantLastChequePeer = "cannot get last cheque for peer" 30 errCantLastCheque = "cannot get last cheque for all peers" 31 errCannotCash = "cannot cash cheque" 32 errCannotCashStatus = "cannot get cashout status" 33 errNoCashout = "no prior cashout" 34 errNoCheque = "no prior cheque" 35 ) 36 37 type chequebookBalanceResponse struct { 38 TotalBalance *bigint.BigInt `json:"totalBalance"` 39 AvailableBalance *bigint.BigInt `json:"availableBalance"` 40 } 41 42 type chequebookAddressResponse struct { 43 Address string `json:"chequebookAddress"` 44 } 45 46 type chequebookLastChequePeerResponse struct { 47 Beneficiary string `json:"beneficiary"` 48 Chequebook string `json:"chequebook"` 49 Payout *bigint.BigInt `json:"payout"` 50 } 51 52 type chequebookLastChequesPeerResponse struct { 53 Peer string `json:"peer"` 54 LastReceived *chequebookLastChequePeerResponse `json:"lastreceived"` 55 LastSent *chequebookLastChequePeerResponse `json:"lastsent"` 56 } 57 58 type chequebookLastChequesResponse struct { 59 LastCheques []chequebookLastChequesPeerResponse `json:"lastcheques"` 60 } 61 62 func (s *Service) chequebookBalanceHandler(w http.ResponseWriter, r *http.Request) { 63 logger := s.logger.WithName("get_chequebook_balance").Build() 64 65 balance, err := s.chequebook.Balance(r.Context()) 66 if errors.Is(err, postagecontract.ErrChainDisabled) { 67 logger.Debug("get balance failed", "error", err) 68 logger.Error(nil, "get balance failed") 69 jsonhttp.MethodNotAllowed(w, err) 70 return 71 } 72 if err != nil { 73 jsonhttp.InternalServerError(w, errChequebookBalance) 74 logger.Debug("get balance failed", "error", err) 75 logger.Error(nil, "get balance failed") 76 return 77 } 78 79 availableBalance, err := s.chequebook.AvailableBalance(r.Context()) 80 if err != nil { 81 jsonhttp.InternalServerError(w, errChequebookBalance) 82 logger.Debug("get available balance failed", "error", err) 83 logger.Error(nil, "get available balance failed") 84 return 85 } 86 87 jsonhttp.OK(w, chequebookBalanceResponse{TotalBalance: bigint.Wrap(balance), AvailableBalance: bigint.Wrap(availableBalance)}) 88 } 89 90 func (s *Service) chequebookAddressHandler(w http.ResponseWriter, _ *http.Request) { 91 jsonhttp.OK(w, chequebookAddressResponse{Address: s.chequebook.Address().String()}) 92 } 93 94 func (s *Service) chequebookLastPeerHandler(w http.ResponseWriter, r *http.Request) { 95 logger := s.logger.WithName("get_chequebook_cheque_by_peer").Build() 96 97 paths := struct { 98 Peer swarm.Address `map:"peer" validate:"required"` 99 }{} 100 if response := s.mapStructure(mux.Vars(r), &paths); response != nil { 101 response("invalid path params", logger, w) 102 return 103 } 104 105 var lastSentResponse *chequebookLastChequePeerResponse 106 lastSent, err := s.swap.LastSentCheque(paths.Peer) 107 if errors.Is(err, postagecontract.ErrChainDisabled) { 108 logger.Debug("get last sent cheque failed", "peer_address", paths.Peer, "error", err) 109 logger.Error(nil, "get last sent cheque failed", "peer_address", paths.Peer) 110 jsonhttp.MethodNotAllowed(w, err) 111 return 112 } 113 if err != nil && !errors.Is(err, chequebook.ErrNoCheque) && !errors.Is(err, swap.ErrNoChequebook) { 114 logger.Debug("get last sent cheque failed", "peer_address", paths.Peer, "error", err) 115 logger.Error(nil, "get last sent cheque failed", "peer_address", paths.Peer) 116 jsonhttp.InternalServerError(w, errCantLastChequePeer) 117 return 118 } 119 if err == nil { 120 lastSentResponse = &chequebookLastChequePeerResponse{ 121 Beneficiary: lastSent.Cheque.Beneficiary.String(), 122 Chequebook: lastSent.Cheque.Chequebook.String(), 123 Payout: bigint.Wrap(lastSent.Cheque.CumulativePayout), 124 } 125 } 126 127 var lastReceivedResponse *chequebookLastChequePeerResponse 128 lastReceived, err := s.swap.LastReceivedCheque(paths.Peer) 129 if err != nil && !errors.Is(err, chequebook.ErrNoCheque) { 130 logger.Debug("get last received cheque failed", "peer_address", paths.Peer, "error", err) 131 logger.Error(nil, "get last received cheque failed", "peer_address", paths.Peer) 132 jsonhttp.InternalServerError(w, errCantLastChequePeer) 133 return 134 } 135 if err == nil { 136 lastReceivedResponse = &chequebookLastChequePeerResponse{ 137 Beneficiary: lastReceived.Cheque.Beneficiary.String(), 138 Chequebook: lastReceived.Cheque.Chequebook.String(), 139 Payout: bigint.Wrap(lastReceived.Cheque.CumulativePayout), 140 } 141 } 142 143 jsonhttp.OK(w, chequebookLastChequesPeerResponse{ 144 Peer: paths.Peer.String(), 145 LastReceived: lastReceivedResponse, 146 LastSent: lastSentResponse, 147 }) 148 } 149 150 func (s *Service) chequebookAllLastHandler(w http.ResponseWriter, _ *http.Request) { 151 logger := s.logger.WithName("get_chequebook_cheques").Build() 152 153 lastchequessent, err := s.swap.LastSentCheques() 154 if errors.Is(err, postagecontract.ErrChainDisabled) { 155 logger.Debug("get all last sent cheque failed", "error", err) 156 logger.Error(nil, "get all last sent cheque failed") 157 jsonhttp.MethodNotAllowed(w, err) 158 return 159 } 160 if err != nil { 161 if !errors.Is(err, swap.ErrNoChequebook) { 162 logger.Debug("get all last sent cheque failed", "error", err) 163 logger.Error(nil, "get all last sent cheque failed") 164 jsonhttp.InternalServerError(w, errCantLastCheque) 165 return 166 } 167 lastchequessent = map[string]*chequebook.SignedCheque{} 168 } 169 lastchequesreceived, err := s.swap.LastReceivedCheques() 170 if err != nil { 171 logger.Debug("get all last received cheque failed", "error", err) 172 logger.Error(nil, "get all last received cheque failed") 173 jsonhttp.InternalServerError(w, errCantLastCheque) 174 return 175 } 176 177 lcr := make(map[string]chequebookLastChequesPeerResponse) 178 for i, j := range lastchequessent { 179 lcr[i] = chequebookLastChequesPeerResponse{ 180 Peer: i, 181 LastSent: &chequebookLastChequePeerResponse{ 182 Beneficiary: j.Cheque.Beneficiary.String(), 183 Chequebook: j.Cheque.Chequebook.String(), 184 Payout: bigint.Wrap(j.Cheque.CumulativePayout), 185 }, 186 LastReceived: nil, 187 } 188 } 189 for i, j := range lastchequesreceived { 190 if _, ok := lcr[i]; ok { 191 t := lcr[i] 192 t.LastReceived = &chequebookLastChequePeerResponse{ 193 Beneficiary: j.Cheque.Beneficiary.String(), 194 Chequebook: j.Cheque.Chequebook.String(), 195 Payout: bigint.Wrap(j.Cheque.CumulativePayout), 196 } 197 lcr[i] = t 198 } else { 199 lcr[i] = chequebookLastChequesPeerResponse{ 200 Peer: i, 201 LastSent: nil, 202 LastReceived: &chequebookLastChequePeerResponse{ 203 Beneficiary: j.Cheque.Beneficiary.String(), 204 Chequebook: j.Cheque.Chequebook.String(), 205 Payout: bigint.Wrap(j.Cheque.CumulativePayout), 206 }, 207 } 208 } 209 } 210 211 lcresponses := make([]chequebookLastChequesPeerResponse, len(lcr)) 212 i := 0 213 for k := range lcr { 214 lcresponses[i] = lcr[k] 215 i++ 216 } 217 218 jsonhttp.OK(w, chequebookLastChequesResponse{LastCheques: lcresponses}) 219 } 220 221 type swapCashoutResponse struct { 222 TransactionHash string `json:"transactionHash"` 223 } 224 225 func (s *Service) swapCashoutHandler(w http.ResponseWriter, r *http.Request) { 226 logger := s.logger.WithName("post_chequebook_cashout").Build() 227 228 paths := struct { 229 Peer swarm.Address `map:"peer" validate:"required"` 230 }{} 231 if response := s.mapStructure(mux.Vars(r), &paths); response != nil { 232 response("invalid path params", logger, w) 233 return 234 } 235 236 if !s.cashOutChequeSem.TryAcquire(1) { 237 logger.Debug("simultaneous on-chain operations not supported") 238 logger.Error(nil, "simultaneous on-chain operations not supported") 239 jsonhttp.TooManyRequests(w, "simultaneous on-chain operations not supported") 240 return 241 } 242 defer s.cashOutChequeSem.Release(1) 243 244 txHash, err := s.swap.CashCheque(r.Context(), paths.Peer) 245 if errors.Is(err, postagecontract.ErrChainDisabled) { 246 logger.Debug("cash cheque failed", "peer_address", paths.Peer, "error", err) 247 logger.Error(nil, "cash cheque failed", "peer_address", paths.Peer) 248 jsonhttp.MethodNotAllowed(w, err) 249 return 250 } 251 if err != nil { 252 logger.Debug("cash cheque failed", "peer_address", paths.Peer, "error", err) 253 logger.Error(nil, "cash cheque failed", "peer_address", paths.Peer) 254 jsonhttp.InternalServerError(w, errCannotCash) 255 return 256 } 257 258 jsonhttp.OK(w, swapCashoutResponse{TransactionHash: txHash.String()}) 259 } 260 261 type swapCashoutStatusResult struct { 262 Recipient common.Address `json:"recipient"` 263 LastPayout *bigint.BigInt `json:"lastPayout"` 264 Bounced bool `json:"bounced"` 265 } 266 267 type swapCashoutStatusResponse struct { 268 Peer swarm.Address `json:"peer"` 269 Cheque *chequebookLastChequePeerResponse `json:"lastCashedCheque"` 270 TransactionHash *common.Hash `json:"transactionHash"` 271 Result *swapCashoutStatusResult `json:"result"` 272 UncashedAmount *bigint.BigInt `json:"uncashedAmount"` 273 } 274 275 func (s *Service) swapCashoutStatusHandler(w http.ResponseWriter, r *http.Request) { 276 logger := s.logger.WithName("get_chequebook_cashout").Build() 277 278 paths := struct { 279 Peer swarm.Address `map:"peer" validate:"required"` 280 }{} 281 if response := s.mapStructure(mux.Vars(r), &paths); response != nil { 282 response("invalid path params", logger, w) 283 return 284 } 285 286 status, err := s.swap.CashoutStatus(r.Context(), paths.Peer) 287 if errors.Is(err, postagecontract.ErrChainDisabled) { 288 logger.Debug("get cashout status failed", "peer_address", paths.Peer, "error", err) 289 logger.Error(nil, "get cashout status failed", "peer_address", paths.Peer) 290 jsonhttp.MethodNotAllowed(w, err) 291 return 292 } 293 if err != nil { 294 if errors.Is(err, chequebook.ErrNoCheque) { 295 logger.Debug("get cashout status failed", "peer_address", paths.Peer, "error", err) 296 logger.Error(nil, "get cashout status failed", "peer_address", paths.Peer) 297 jsonhttp.NotFound(w, errNoCheque) 298 return 299 } 300 if errors.Is(err, chequebook.ErrNoCashout) { 301 logger.Debug("get cashout status failed", "peer_address", paths.Peer, "error", err) 302 logger.Error(nil, "get cashout status failed", "peer_address", paths.Peer) 303 jsonhttp.NotFound(w, errNoCashout) 304 return 305 } 306 logger.Debug("get cashout status failed", "peer_address", paths.Peer, "error", err) 307 logger.Error(nil, "get cashout status failed", "peer_address", paths.Peer) 308 jsonhttp.InternalServerError(w, errCannotCashStatus) 309 return 310 } 311 312 var result *swapCashoutStatusResult 313 var txHash *common.Hash 314 var chequeResponse *chequebookLastChequePeerResponse 315 if status.Last != nil { 316 if status.Last.Result != nil { 317 result = &swapCashoutStatusResult{ 318 Recipient: status.Last.Result.Recipient, 319 LastPayout: bigint.Wrap(status.Last.Result.TotalPayout), 320 Bounced: status.Last.Result.Bounced, 321 } 322 } 323 chequeResponse = &chequebookLastChequePeerResponse{ 324 Chequebook: status.Last.Cheque.Chequebook.String(), 325 Payout: bigint.Wrap(status.Last.Cheque.CumulativePayout), 326 Beneficiary: status.Last.Cheque.Beneficiary.String(), 327 } 328 txHash = &status.Last.TxHash 329 } 330 331 jsonhttp.OK(w, swapCashoutStatusResponse{ 332 Peer: paths.Peer, 333 TransactionHash: txHash, 334 Cheque: chequeResponse, 335 Result: result, 336 UncashedAmount: bigint.Wrap(status.UncashedAmount), 337 }) 338 } 339 340 type chequebookTxResponse struct { 341 TransactionHash common.Hash `json:"transactionHash"` 342 } 343 344 func (s *Service) chequebookWithdrawHandler(w http.ResponseWriter, r *http.Request) { 345 logger := s.logger.WithName("post_chequebook_withdraw").Build() 346 347 queries := struct { 348 Amount *big.Int `map:"amount" validate:"required"` 349 }{} 350 if response := s.mapStructure(r.URL.Query(), &queries); response != nil { 351 response("invalid query params", logger, w) 352 return 353 } 354 355 txHash, err := s.chequebook.Withdraw(r.Context(), queries.Amount) 356 if errors.Is(err, chequebook.ErrInsufficientFunds) { 357 logger.Debug("withdraw failed", "error", err) 358 logger.Error(nil, "withdraw failed") 359 jsonhttp.BadRequest(w, errChequebookInsufficientFunds) 360 return 361 } 362 if err != nil { 363 logger.Debug("withdraw failed", "error", err) 364 logger.Error(nil, "withdraw failed") 365 jsonhttp.InternalServerError(w, errChequebookNoWithdraw) 366 return 367 } 368 369 jsonhttp.OK(w, chequebookTxResponse{TransactionHash: txHash}) 370 } 371 372 func (s *Service) chequebookDepositHandler(w http.ResponseWriter, r *http.Request) { 373 logger := s.logger.WithName("post_chequebook_deposit").Build() 374 375 queries := struct { 376 Amount *big.Int `map:"amount" validate:"required"` 377 }{} 378 if response := s.mapStructure(r.URL.Query(), &queries); response != nil { 379 response("invalid query params", logger, w) 380 return 381 } 382 383 txHash, err := s.chequebook.Deposit(r.Context(), queries.Amount) 384 if errors.Is(err, chequebook.ErrInsufficientFunds) { 385 logger.Debug("chequebook deposit: deposit failed", "error", err) 386 logger.Error(nil, "chequebook deposit: deposit failed") 387 jsonhttp.BadRequest(w, errChequebookInsufficientFunds) 388 return 389 } 390 if err != nil { 391 logger.Debug("chequebook deposit: deposit failed", "error", err) 392 logger.Error(nil, "chequebook deposit: deposit failed") 393 jsonhttp.InternalServerError(w, errChequebookNoDeposit) 394 return 395 } 396 397 jsonhttp.OK(w, chequebookTxResponse{TransactionHash: txHash}) 398 }