Skip to content
This repository has been archived by the owner on Aug 2, 2021. It is now read-only.

Commit

Permalink
swap, swap/txqueue, contract: add transaction queue
Browse files Browse the repository at this point in the history
  • Loading branch information
ralph-pichler committed Feb 5, 2020
1 parent a638f1b commit 6481f28
Show file tree
Hide file tree
Showing 11 changed files with 1,257 additions and 150 deletions.
7 changes: 4 additions & 3 deletions contracts/swap/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
chequebookFactory "github.com/ethersphere/go-sw3/contracts-v0-2-0/simpleswapfactory"
"github.com/ethersphere/swarm/swap/txqueue"
)

var (
Expand All @@ -26,7 +27,7 @@ var (
type simpleSwapFactory struct {
instance *chequebookFactory.SimpleSwapFactory
address common.Address
backend Backend
backend txqueue.Backend
}

// SimpleSwapFactory interface defines the methods available for a factory contract for SimpleSwap
Expand All @@ -40,7 +41,7 @@ type SimpleSwapFactory interface {
}

// FactoryAt creates a SimpleSwapFactory instance for the given address and backend
func FactoryAt(address common.Address, backend Backend) (SimpleSwapFactory, error) {
func FactoryAt(address common.Address, backend txqueue.Backend) (SimpleSwapFactory, error) {
simple, err := chequebookFactory.NewSimpleSwapFactory(address, backend)
if err != nil {
return nil, err
Expand Down Expand Up @@ -83,7 +84,7 @@ func (sf simpleSwapFactory) DeploySimpleSwap(auth *bind.TransactOpts, issuer com
return nil, err
}

receipt, err := WaitFunc(auth.Context, sf.backend, tx)
receipt, err := txqueue.WaitFunc(auth.Context, sf.backend, tx.Hash())
if err != nil {
return nil, err
}
Expand Down
58 changes: 5 additions & 53 deletions contracts/swap/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package swap

import (
"context"
"errors"
"fmt"
"math/big"
Expand All @@ -29,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
contract "github.com/ethersphere/go-sw3/contracts-v0-2-0/erc20simpleswap"
"github.com/ethersphere/swarm/swap/txqueue"
"github.com/ethersphere/swarm/uint256"
)

Expand All @@ -37,13 +37,6 @@ var (
ErrTransactionReverted = errors.New("Transaction reverted")
)

// Backend wraps all methods required for contract deployment.
type Backend interface {
bind.ContractBackend
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error)
}

// Contract interface defines the methods exported from the underlying go-bindings for the smart contract
type Contract interface {
// Withdraw attempts to withdraw ERC20-token from the chequebook
Expand Down Expand Up @@ -89,13 +82,13 @@ type Params struct {
type simpleContract struct {
instance *contract.ERC20SimpleSwap
address common.Address
backend Backend
backend txqueue.Backend
}

// InstanceAt creates a new instance of a contract at a specific address.
// It assumes that there is an existing contract instance at the given address, or an error is returned
// This function is needed to communicate with remote Swap contracts (e.g. sending a cheque)
func InstanceAt(address common.Address, backend Backend) (Contract, error) {
func InstanceAt(address common.Address, backend txqueue.Backend) (Contract, error) {
instance, err := contract.NewERC20SimpleSwap(address, backend)
if err != nil {
return nil, err
Expand All @@ -110,7 +103,7 @@ func (s simpleContract) Withdraw(auth *bind.TransactOpts, amount *big.Int) (*typ
if err != nil {
return nil, err
}
return WaitFunc(auth.Context, s.backend, tx)
return txqueue.WaitFunc(auth.Context, s.backend, tx.Hash())
}

// Deposit sends an amount in ERC20 token to the chequebook and blocks until the transaction is mined
Expand Down Expand Up @@ -140,7 +133,7 @@ func (s simpleContract) Deposit(auth *bind.TransactOpts, amount *big.Int) (*type
if err != nil {
return nil, err
}
return WaitFunc(auth.Context, s.backend, tx)
return txqueue.WaitFunc(auth.Context, s.backend, tx.Hash())
}

// CashChequeBeneficiaryStart sends the transaction to cash a cheque as the beneficiary
Expand Down Expand Up @@ -224,44 +217,3 @@ func (s simpleContract) Issuer(opts *bind.CallOpts) (common.Address, error) {
func (s simpleContract) PaidOut(opts *bind.CallOpts, addr common.Address) (*big.Int, error) {
return s.instance.PaidOut(opts, addr)
}

// WaitFunc is the default function to wait for transactions
// We can overwrite this in tests so that we don't need to wait for mining
var WaitFunc = waitForTx

// waitForTx waits for transaction to be mined and returns the receipt
func waitForTx(ctx context.Context, backend Backend, tx *types.Transaction) (*types.Receipt, error) {
// it blocks here until tx is mined
receipt, err := bind.WaitMined(ctx, backend, tx)
if err != nil {
return nil, err
}
// indicate whether the transaction did not revert
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, ErrTransactionReverted
}
return receipt, nil
}

// WaitForTransactionByHash waits for a transaction to by mined by hash
func WaitForTransactionByHash(ctx context.Context, backend Backend, txHash common.Hash) (*types.Receipt, error) {
tx, pending, err := backend.TransactionByHash(ctx, txHash)
if err != nil {
return nil, err
}

var receipt *types.Receipt
if pending {
receipt, err = WaitFunc(ctx, backend, tx)
if err != nil {
return nil, err
}
} else {
receipt, err = backend.TransactionReceipt(ctx, txHash)
if err != nil {
return nil, err
}
}

return receipt, nil
}
136 changes: 86 additions & 50 deletions swap/cashout.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ package swap
import (
"context"
"crypto/ecdsa"
"errors"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/metrics"
contract "github.com/ethersphere/swarm/contracts/swap"
"github.com/ethersphere/swarm/swap/txqueue"
"github.com/ethersphere/swarm/uint256"
)

Expand All @@ -31,8 +33,9 @@ const CashChequeBeneficiaryTransactionCost = 50000

// CashoutProcessor holds all relevant fields needed for processing cashouts
type CashoutProcessor struct {
backend contract.Backend // ethereum backend to use
privateKey *ecdsa.PrivateKey // private key to use
backend txqueue.Backend // ethereum backend to use
transactionQueue *txqueue.TxQueue // transaction queue to use
chequeCashedChan chan *CashoutRequest
}

// CashoutRequest represents a request for a cashout operation
Expand All @@ -41,42 +44,26 @@ type CashoutRequest struct {
Destination common.Address // destination for the payout
}

// ActiveCashout stores the necessary information for a cashout in progess
type ActiveCashout struct {
Request CashoutRequest // the request that caused this cashout
TransactionHash common.Hash // the hash of the current transaction for this request
}

// newCashoutProcessor creates a new instance of CashoutProcessor
func newCashoutProcessor(backend contract.Backend, privateKey *ecdsa.PrivateKey) *CashoutProcessor {
return &CashoutProcessor{
backend: backend,
privateKey: privateKey,
func newCashoutProcessor(transactionQueue *txqueue.TxQueue, backend txqueue.Backend, privateKey *ecdsa.PrivateKey) *CashoutProcessor {
c := &CashoutProcessor{
backend: backend,
transactionQueue: transactionQueue,
}

transactionQueue.SetComponent("cashout", c)
return c
}

func (c *CashoutProcessor) SetChequeCashedChan(chequeCashedChan chan *CashoutRequest) {
c.chequeCashedChan = chequeCashedChan
}

// cashCheque tries to cash the cheque specified in the request
// after the transaction is sent it waits on its success
func (c *CashoutProcessor) cashCheque(ctx context.Context, request *CashoutRequest) error {
cheque := request.Cheque
opts := bind.NewKeyedTransactor(c.privateKey)
opts.Context = ctx

otherSwap, err := contract.InstanceAt(cheque.Contract, c.backend)
if err != nil {
return err
}

tx, err := otherSwap.CashChequeBeneficiaryStart(opts, request.Destination, cheque.CumulativePayout, cheque.Signature)
if err != nil {
return err
}

// this blocks until the cashout has been successfully processed
return c.waitForAndProcessActiveCashout(&ActiveCashout{
Request: *request,
TransactionHash: tx.Hash(),
})
_, err := c.transactionQueue.QueueRequest("cashout", "CashoutRequest", request)
return err
}

// estimatePayout estimates the payout for a given cheque as well as the transaction cost
Expand Down Expand Up @@ -123,30 +110,79 @@ func (c *CashoutProcessor) estimatePayout(ctx context.Context, cheque *Cheque) (
return expectedPayout, transactionCosts, nil
}

// waitForAndProcessActiveCashout waits for activeCashout to complete
func (c *CashoutProcessor) waitForAndProcessActiveCashout(activeCashout *ActiveCashout) error {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTransactionTimeout)
defer cancel()

receipt, err := contract.WaitForTransactionByHash(ctx, c.backend, activeCashout.TransactionHash)
if err != nil {
return err
func (*CashoutProcessor) GetTypeInstance(requestType string) txqueue.TransactionRequest {
switch requestType {
case "CashoutRequest":
return &CashoutRequest{}
default:
return nil
}
}

otherSwap, err := contract.InstanceAt(activeCashout.Request.Cheque.Contract, c.backend)
if err != nil {
return err
}
func (*CashoutProcessor) SendTransactionRequest(id uint64, request txqueue.TransactionRequest, backend txqueue.Backend, opts *bind.TransactOpts) (common.Hash, error) {
switch request := request.(type) {
case *CashoutRequest:
cheque := request.Cheque

result := otherSwap.CashChequeBeneficiaryResult(receipt)
otherSwap, err := contract.InstanceAt(cheque.Contract, backend)
if err != nil {
return common.Hash{}, err
}

metrics.GetOrRegisterCounter("swap.cheques.cashed.honey", nil).Inc(result.TotalPayout.Int64())
tx, err := otherSwap.CashChequeBeneficiaryStart(opts, request.Destination, cheque.CumulativePayout, cheque.Signature)
if err != nil {
return common.Hash{}, err
}

if result.Bounced {
metrics.GetOrRegisterCounter("swap.cheques.cashed.bounced", nil).Inc(1)
swapLog.Warn("cheque bounced", "tx", receipt.TxHash)
return tx.Hash(), nil
default:
return common.Hash{}, errors.New("unknown type")
}
}

swapLog.Info("cheque cashed", "honey", activeCashout.Request.Cheque.Honey)
return nil
func (c *CashoutProcessor) HandleNotification(id uint64, notification interface{}) error {
switch notification.(type) {
case *txqueue.TransactionReceiptNotification:
notification := notification.(*txqueue.TransactionReceiptNotification)
requestInfo, err := c.transactionQueue.GetRequestInfo(id)
if err != nil {
return err
}

cashoutRequest, ok := requestInfo.Request.(*CashoutRequest)
if !ok {
return nil
}
otherSwap, err := contract.InstanceAt(cashoutRequest.Cheque.Contract, c.backend)
if err != nil {
return err
}

receipt := &notification.Receipt

if receipt.Status == 0 {
swapLog.Error("cheque cashing transaction reverted", "tx", receipt.TxHash)
return nil
}

result := otherSwap.CashChequeBeneficiaryResult(receipt)

metrics.GetOrRegisterCounter("swap.cheques.cashed.honey", nil).Inc(result.TotalPayout.Int64())

if result.Bounced {
metrics.GetOrRegisterCounter("swap.cheques.cashed.bounced", nil).Inc(1)
swapLog.Warn("cheque bounced", "tx", receipt.TxHash)
}

swapLog.Info("cheque cashed", "honey", cashoutRequest.Cheque.Honey)

select {
case c.chequeCashedChan <- cashoutRequest:
default:
}

return nil
default:
return nil
}
}
Loading

0 comments on commit 6481f28

Please sign in to comment.