chequebook.go 12.6 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package debugapi

import (
Ralph Pichler's avatar
Ralph Pichler committed
8
	"errors"
9 10
	"math/big"
	"net/http"
11
	"strconv"
12

Ralph Pichler's avatar
Ralph Pichler committed
13
	"github.com/ethereum/go-ethereum/common"
14
	"github.com/ethersphere/bee/pkg/jsonhttp"
15
	"github.com/ethersphere/bee/pkg/sctx"
16 17 18 19
	"github.com/ethersphere/bee/pkg/settlement/swap/chequebook"

	"github.com/ethersphere/bee/pkg/swarm"
	"github.com/gorilla/mux"
20 21 22
)

var (
23 24 25 26 27 28 29 30 31 32 33
	errChequebookBalance           = "cannot get chequebook balance"
	errChequebookNoAmount          = "did not specify amount"
	errChequebookNoWithdraw        = "cannot withdraw"
	errChequebookNoDeposit         = "cannot deposit"
	errChequebookInsufficientFunds = "insufficient funds"
	errCantLastChequePeer          = "cannot get last cheque for peer"
	errCantLastCheque              = "cannot get last cheque for all peers"
	errCannotCash                  = "cannot cash cheque"
	errCannotCashStatus            = "cannot get cashout status"
	errNoCashout                   = "no prior cashout"
	errNoCheque                    = "no prior cheque"
34 35 36 37 38
	errBadGasPrice                 = "bad gas price"
	errBadGasLimit                 = "bad gas limit"

	gasPriceHeader = "Gas-Price"
	gasLimitHeader = "Gas-Limit"
39 40 41
)

type chequebookBalanceResponse struct {
42 43
	TotalBalance     *big.Int `json:"totalBalance"`
	AvailableBalance *big.Int `json:"availableBalance"`
44 45 46
}

type chequebookAddressResponse struct {
47
	Address string `json:"chequebookAddress"`
48 49
}

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
type chequebookLastChequePeerResponse struct {
	Beneficiary string   `json:"beneficiary"`
	Chequebook  string   `json:"chequebook"`
	Payout      *big.Int `json:"payout"`
}

type chequebookLastChequesPeerResponse struct {
	Peer         string                            `json:"peer"`
	LastReceived *chequebookLastChequePeerResponse `json:"lastreceived"`
	LastSent     *chequebookLastChequePeerResponse `json:"lastsent"`
}

type chequebookLastChequesResponse struct {
	LastCheques []chequebookLastChequesPeerResponse `json:"lastcheques"`
}

66 67
func (s *Service) chequebookBalanceHandler(w http.ResponseWriter, r *http.Request) {
	balance, err := s.chequebook.Balance(r.Context())
68 69
	if err != nil {
		jsonhttp.InternalServerError(w, errChequebookBalance)
70 71
		s.logger.Debugf("debug api: chequebook balance: %v", err)
		s.logger.Error("debug api: cannot get chequebook balance")
72 73 74
		return
	}

75
	availableBalance, err := s.chequebook.AvailableBalance(r.Context())
76 77
	if err != nil {
		jsonhttp.InternalServerError(w, errChequebookBalance)
78 79
		s.logger.Debugf("debug api: chequebook availableBalance: %v", err)
		s.logger.Error("debug api: cannot get chequebook availableBalance")
80 81 82 83
		return
	}

	jsonhttp.OK(w, chequebookBalanceResponse{TotalBalance: balance, AvailableBalance: availableBalance})
84 85
}

86 87
func (s *Service) chequebookAddressHandler(w http.ResponseWriter, r *http.Request) {
	address := s.chequebook.Address()
88 89
	jsonhttp.OK(w, chequebookAddressResponse{Address: address.String()})
}
90

91
func (s *Service) chequebookLastPeerHandler(w http.ResponseWriter, r *http.Request) {
92 93 94
	addr := mux.Vars(r)["peer"]
	peer, err := swarm.ParseHexAddress(addr)
	if err != nil {
95 96
		s.logger.Debugf("debug api: chequebook cheque peer: invalid peer address %s: %v", addr, err)
		s.logger.Errorf("debug api: chequebook cheque peer: invalid peer address %s", addr)
97
		jsonhttp.NotFound(w, errInvalidAddress)
98 99 100 101
		return
	}

	var lastSentResponse *chequebookLastChequePeerResponse
102
	lastSent, err := s.swap.LastSentCheque(peer)
103
	if err != nil && err != chequebook.ErrNoCheque {
104 105
		s.logger.Debugf("debug api: chequebook cheque peer: get peer %s last cheque: %v", peer.String(), err)
		s.logger.Errorf("debug api: chequebook cheque peer: can't get peer %s last cheque", peer.String())
106 107 108 109 110 111 112 113 114 115 116 117
		jsonhttp.InternalServerError(w, errCantLastChequePeer)
		return
	}
	if err == nil {
		lastSentResponse = &chequebookLastChequePeerResponse{
			Beneficiary: lastSent.Cheque.Beneficiary.String(),
			Chequebook:  lastSent.Cheque.Chequebook.String(),
			Payout:      lastSent.Cheque.CumulativePayout,
		}
	}

	var lastReceivedResponse *chequebookLastChequePeerResponse
118
	lastReceived, err := s.swap.LastReceivedCheque(peer)
119
	if err != nil && err != chequebook.ErrNoCheque {
120 121
		s.logger.Debugf("debug api: chequebook cheque peer: get peer %s last cheque: %v", peer.String(), err)
		s.logger.Errorf("debug api: chequebook cheque peer: can't get peer %s last cheque", peer.String())
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
		jsonhttp.InternalServerError(w, errCantLastChequePeer)
		return
	}
	if err == nil {
		lastReceivedResponse = &chequebookLastChequePeerResponse{
			Beneficiary: lastReceived.Cheque.Beneficiary.String(),
			Chequebook:  lastReceived.Cheque.Chequebook.String(),
			Payout:      lastReceived.Cheque.CumulativePayout,
		}
	}

	jsonhttp.OK(w, chequebookLastChequesPeerResponse{
		Peer:         addr,
		LastReceived: lastReceivedResponse,
		LastSent:     lastSentResponse,
	})
}

140 141
func (s *Service) chequebookAllLastHandler(w http.ResponseWriter, r *http.Request) {
	lastchequessent, err := s.swap.LastSentCheques()
142
	if err != nil {
143 144
		s.logger.Debugf("debug api: chequebook cheque all: get all last cheques: %v", err)
		s.logger.Errorf("debug api: chequebook cheque all: can't get all last cheques")
145 146 147
		jsonhttp.InternalServerError(w, errCantLastCheque)
		return
	}
148
	lastchequesreceived, err := s.swap.LastReceivedCheques()
149
	if err != nil {
150 151
		s.logger.Debugf("debug api: chequebook cheque all: get all last cheques: %v", err)
		s.logger.Errorf("debug api: chequebook cheque all: can't get all last cheques")
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
		jsonhttp.InternalServerError(w, errCantLastCheque)
		return
	}

	lcr := make(map[string]chequebookLastChequesPeerResponse)
	for i, j := range lastchequessent {
		lcr[i] = chequebookLastChequesPeerResponse{
			Peer: i,
			LastSent: &chequebookLastChequePeerResponse{
				Beneficiary: j.Cheque.Beneficiary.String(),
				Chequebook:  j.Cheque.Chequebook.String(),
				Payout:      j.Cheque.CumulativePayout,
			},
			LastReceived: nil,
		}
	}
	for i, j := range lastchequesreceived {
		if _, ok := lcr[i]; ok {
			t := lcr[i]
			t.LastReceived = &chequebookLastChequePeerResponse{
				Beneficiary: j.Cheque.Beneficiary.String(),
				Chequebook:  j.Cheque.Chequebook.String(),
				Payout:      j.Cheque.CumulativePayout,
			}
			lcr[i] = t
		} else {
			lcr[i] = chequebookLastChequesPeerResponse{
				Peer:     i,
				LastSent: nil,
				LastReceived: &chequebookLastChequePeerResponse{
					Beneficiary: j.Cheque.Beneficiary.String(),
					Chequebook:  j.Cheque.Chequebook.String(),
					Payout:      j.Cheque.CumulativePayout,
				},
			}
		}
	}

	lcresponses := make([]chequebookLastChequesPeerResponse, len(lcr))
	i := 0
	for k := range lcr {
		lcresponses[i] = lcr[k]
		i++
	}

	jsonhttp.OK(w, chequebookLastChequesResponse{LastCheques: lcresponses})
}
Ralph Pichler's avatar
Ralph Pichler committed
199 200 201 202 203

type swapCashoutResponse struct {
	TransactionHash string `json:"transactionHash"`
}

204
func (s *Service) swapCashoutHandler(w http.ResponseWriter, r *http.Request) {
Ralph Pichler's avatar
Ralph Pichler committed
205 206 207
	addr := mux.Vars(r)["peer"]
	peer, err := swarm.ParseHexAddress(addr)
	if err != nil {
208 209
		s.logger.Debugf("debug api: cashout peer: invalid peer address %s: %v", addr, err)
		s.logger.Errorf("debug api: cashout peer: invalid peer address %s", addr)
210
		jsonhttp.NotFound(w, errInvalidAddress)
Ralph Pichler's avatar
Ralph Pichler committed
211 212 213
		return
	}

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
	ctx := r.Context()
	if price, ok := r.Header[gasPriceHeader]; ok {
		p, ok := big.NewInt(0).SetString(price[0], 10)
		if !ok {
			s.logger.Error("debug api: cashout peer: bad gas price")
			jsonhttp.BadRequest(w, errBadGasPrice)
			return
		}
		ctx = sctx.SetGasPrice(ctx, p)
	}

	if limit, ok := r.Header[gasLimitHeader]; ok {
		l, err := strconv.ParseUint(limit[0], 10, 64)
		if err != nil {
			s.logger.Debugf("debug api: cashout peer: bad gas limit: %v", err)
			s.logger.Error("debug api: cashout peer: bad gas limit")
			jsonhttp.BadRequest(w, errBadGasLimit)
			return
		}
		ctx = sctx.SetGasLimit(ctx, l)
	}

	txHash, err := s.swap.CashCheque(ctx, peer)
Ralph Pichler's avatar
Ralph Pichler committed
237
	if err != nil {
238 239
		s.logger.Debugf("debug api: cashout peer: cannot cash %s: %v", addr, err)
		s.logger.Errorf("debug api: cashout peer: cannot cash %s", addr)
Ralph Pichler's avatar
Ralph Pichler committed
240 241 242 243 244 245 246 247 248 249 250 251 252 253
		jsonhttp.InternalServerError(w, errCannotCash)
		return
	}

	jsonhttp.OK(w, swapCashoutResponse{TransactionHash: txHash.String()})
}

type swapCashoutStatusResult struct {
	Recipient  common.Address `json:"recipient"`
	LastPayout *big.Int       `json:"lastPayout"`
	Bounced    bool           `json:"bounced"`
}

type swapCashoutStatusResponse struct {
254 255 256 257 258
	Peer            swarm.Address                     `json:"peer"`
	Cheque          *chequebookLastChequePeerResponse `json:"lastCashedCheque"`
	TransactionHash *common.Hash                      `json:"transactionHash"`
	Result          *swapCashoutStatusResult          `json:"result"`
	UncashedAmount  *big.Int                          `json:"uncashedAmount"`
Ralph Pichler's avatar
Ralph Pichler committed
259 260
}

261
func (s *Service) swapCashoutStatusHandler(w http.ResponseWriter, r *http.Request) {
Ralph Pichler's avatar
Ralph Pichler committed
262 263 264
	addr := mux.Vars(r)["peer"]
	peer, err := swarm.ParseHexAddress(addr)
	if err != nil {
265 266
		s.logger.Debugf("debug api: cashout status peer: invalid peer address %s: %v", addr, err)
		s.logger.Errorf("debug api: cashout status peer: invalid peer address %s", addr)
267
		jsonhttp.NotFound(w, errInvalidAddress)
Ralph Pichler's avatar
Ralph Pichler committed
268 269 270
		return
	}

271
	status, err := s.swap.CashoutStatus(r.Context(), peer)
Ralph Pichler's avatar
Ralph Pichler committed
272 273
	if err != nil {
		if errors.Is(err, chequebook.ErrNoCheque) {
274 275
			s.logger.Debugf("debug api: cashout status peer: %v", addr, err)
			s.logger.Errorf("debug api: cashout status peer: %s", addr)
Ralph Pichler's avatar
Ralph Pichler committed
276 277 278 279
			jsonhttp.NotFound(w, errNoCheque)
			return
		}
		if errors.Is(err, chequebook.ErrNoCashout) {
280 281
			s.logger.Debugf("debug api: cashout status peer: %v", addr, err)
			s.logger.Errorf("debug api: cashout status peer: %s", addr)
Ralph Pichler's avatar
Ralph Pichler committed
282 283 284
			jsonhttp.NotFound(w, errNoCashout)
			return
		}
285 286
		s.logger.Debugf("debug api: cashout status peer: cannot get status %s: %v", addr, err)
		s.logger.Errorf("debug api: cashout status peer: cannot get status %s", addr)
Ralph Pichler's avatar
Ralph Pichler committed
287 288 289 290 291
		jsonhttp.InternalServerError(w, errCannotCashStatus)
		return
	}

	var result *swapCashoutStatusResult
292 293 294 295 296 297 298 299 300 301 302 303 304 305
	var txHash *common.Hash
	var chequeResponse *chequebookLastChequePeerResponse
	if status.Last != nil {
		if status.Last.Result != nil {
			result = &swapCashoutStatusResult{
				Recipient:  status.Last.Result.Recipient,
				LastPayout: status.Last.Result.TotalPayout,
				Bounced:    status.Last.Result.Bounced,
			}
		}
		chequeResponse = &chequebookLastChequePeerResponse{
			Chequebook:  status.Last.Cheque.Chequebook.String(),
			Payout:      status.Last.Cheque.CumulativePayout,
			Beneficiary: status.Last.Cheque.Beneficiary.String(),
Ralph Pichler's avatar
Ralph Pichler committed
306
		}
307
		txHash = &status.Last.TxHash
Ralph Pichler's avatar
Ralph Pichler committed
308 309 310
	}

	jsonhttp.OK(w, swapCashoutStatusResponse{
311 312 313 314 315
		Peer:            peer,
		TransactionHash: txHash,
		Cheque:          chequeResponse,
		Result:          result,
		UncashedAmount:  status.UncashedAmount,
Ralph Pichler's avatar
Ralph Pichler committed
316 317
	})
}
318 319 320 321 322

type chequebookTxResponse struct {
	TransactionHash common.Hash `json:"transactionHash"`
}

323
func (s *Service) chequebookWithdrawHandler(w http.ResponseWriter, r *http.Request) {
324 325 326
	amountStr := r.URL.Query().Get("amount")
	if amountStr == "" {
		jsonhttp.BadRequest(w, errChequebookNoAmount)
327
		s.logger.Error("debug api: no withdraw amount")
328 329 330 331 332 333
		return
	}

	amount, ok := big.NewInt(0).SetString(amountStr, 10)
	if !ok {
		jsonhttp.BadRequest(w, errChequebookNoAmount)
334
		s.logger.Error("debug api: invalid withdraw amount")
335 336 337
		return
	}

338
	txHash, err := s.chequebook.Withdraw(r.Context(), amount)
339 340
	if errors.Is(err, chequebook.ErrInsufficientFunds) {
		jsonhttp.BadRequest(w, errChequebookInsufficientFunds)
341 342
		s.logger.Debugf("debug api: chequebook withdraw: %v", err)
		s.logger.Error("debug api: cannot withdraw from chequebook")
343 344
		return
	}
345 346
	if err != nil {
		jsonhttp.InternalServerError(w, errChequebookNoWithdraw)
347 348
		s.logger.Debugf("debug api: chequebook withdraw: %v", err)
		s.logger.Error("debug api: cannot withdraw from chequebook")
349 350 351 352 353 354
		return
	}

	jsonhttp.OK(w, chequebookTxResponse{TransactionHash: txHash})
}

355
func (s *Service) chequebookDepositHandler(w http.ResponseWriter, r *http.Request) {
356 357 358
	amountStr := r.URL.Query().Get("amount")
	if amountStr == "" {
		jsonhttp.BadRequest(w, errChequebookNoAmount)
359
		s.logger.Error("debug api: no deposit amount")
360 361 362 363 364 365
		return
	}

	amount, ok := big.NewInt(0).SetString(amountStr, 10)
	if !ok {
		jsonhttp.BadRequest(w, errChequebookNoAmount)
366
		s.logger.Error("debug api: invalid deposit amount")
367 368 369
		return
	}

370
	txHash, err := s.chequebook.Deposit(r.Context(), amount)
371 372
	if errors.Is(err, chequebook.ErrInsufficientFunds) {
		jsonhttp.BadRequest(w, errChequebookInsufficientFunds)
373 374
		s.logger.Debugf("debug api: chequebook deposit: %v", err)
		s.logger.Error("debug api: cannot deposit from chequebook")
375 376
		return
	}
377 378
	if err != nil {
		jsonhttp.InternalServerError(w, errChequebookNoDeposit)
379 380
		s.logger.Debugf("debug api: chequebook deposit: %v", err)
		s.logger.Error("debug api: cannot deposit from chequebook")
381 382 383 384 385
		return
	}

	jsonhttp.OK(w, chequebookTxResponse{TransactionHash: txHash})
}