Files
atomic-swap/protocol/txsender/external_sender.go

224 lines
5.5 KiB
Go

package txsender
import (
"context"
"errors"
"fmt"
"math/big"
"sync"
"time"
"github.com/noot/atomic-swap/common"
"github.com/noot/atomic-swap/common/types"
"github.com/noot/atomic-swap/swapfactory"
"github.com/ethereum/go-ethereum/accounts/abi"
ethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
var (
errTransactionTimeout = errors.New("timed out waiting for transaction to be signed")
errNoSwapWithID = errors.New("no swap with given id")
transactionTimeout = time.Minute * 2 // arbitrary, TODO vary this based on env
)
// Transaction represents a transaction to be signed by the front-end
type Transaction struct {
To ethcommon.Address
Data string
Value string
}
type swapChs struct {
// outgoing encoded txs to be signed
out chan *Transaction
// incoming tx hashes
in chan ethcommon.Hash
}
// ExternalSender represents a transaction signer and sender that is external to the daemon (ie. a front-end)
type ExternalSender struct {
ctx context.Context
ec *ethclient.Client
abi *abi.ABI
contractAddr ethcommon.Address
sync.RWMutex
swaps map[types.Hash]*swapChs
}
// NewExternalSender returns a new ExternalSender
func NewExternalSender(ctx context.Context, ec *ethclient.Client,
contractAddr ethcommon.Address) (*ExternalSender, error) {
abi, err := swapfactory.SwapFactoryMetaData.GetAbi()
if err != nil {
return nil, err
}
return &ExternalSender{
ctx: ctx,
ec: ec,
abi: abi,
contractAddr: contractAddr,
swaps: make(map[types.Hash]*swapChs),
}, nil
}
// SetContract ...
func (s *ExternalSender) SetContract(_ *swapfactory.SwapFactory) {}
// SetContractAddress ...
func (s *ExternalSender) SetContractAddress(addr ethcommon.Address) {
s.contractAddr = addr
}
// OngoingCh returns the channel of outgoing transactions to be signed and submitted
func (s *ExternalSender) OngoingCh(id types.Hash) (<-chan *Transaction, error) {
s.RLock()
defer s.RUnlock()
chs, has := s.swaps[id]
if !has {
return nil, errNoSwapWithID
}
return chs.out, nil
}
// IncomingCh returns the channel of incoming transaction hashes that have been signed and submitted
func (s *ExternalSender) IncomingCh(id types.Hash) (chan<- ethcommon.Hash, error) {
s.RLock()
defer s.RUnlock()
chs, has := s.swaps[id]
if !has {
return nil, errNoSwapWithID
}
return chs.in, nil
}
// AddID initialises the sender with a swap w/ the given ID
func (s *ExternalSender) AddID(id types.Hash) {
s.Lock()
defer s.Unlock()
_, has := s.swaps[id]
if has {
return
}
s.swaps[id] = &swapChs{
out: make(chan *Transaction),
in: make(chan ethcommon.Hash),
}
}
// DeleteID deletes the swap w/ the given ID from the sender
func (s *ExternalSender) DeleteID(id types.Hash) {
s.Lock()
defer s.Unlock()
delete(s.swaps, id)
}
// NewSwap prompts the external sender to sign a new_swap transaction
func (s *ExternalSender) NewSwap(id types.Hash, _pubKeyClaim [32]byte, _pubKeyRefund [32]byte,
_claimer ethcommon.Address, _timeoutDuration *big.Int, _nonce *big.Int,
value *big.Int) (ethcommon.Hash, *ethtypes.Receipt, error) {
input, err := s.abi.Pack("new_swap", _pubKeyClaim, _pubKeyRefund, _claimer, _timeoutDuration, _nonce)
if err != nil {
return ethcommon.Hash{}, nil, err
}
tx := &Transaction{
To: s.contractAddr,
Data: fmt.Sprintf("0x%x", input),
Value: fmt.Sprintf("%v", common.EtherAmount(*value).AsEther()),
}
s.RLock()
defer s.RUnlock()
chs, has := s.swaps[id]
if !has {
return ethcommon.Hash{}, nil, errNoSwapWithID
}
chs.out <- tx
var txHash ethcommon.Hash
select {
case <-time.After(transactionTimeout):
return ethcommon.Hash{}, nil, errTransactionTimeout
case txHash = <-chs.in:
}
receipt, err := waitForReceipt(s.ctx, s.ec, txHash)
if err != nil {
return ethcommon.Hash{}, nil, err
}
return txHash, receipt, nil
}
// SetReady prompts the external sender to sign a set_ready transaction
func (s *ExternalSender) SetReady(id types.Hash,
_swap swapfactory.SwapFactorySwap) (ethcommon.Hash, *ethtypes.Receipt, error) {
input, err := s.abi.Pack("set_ready", _swap)
if err != nil {
return ethcommon.Hash{}, nil, err
}
return s.sendAndReceive(id, input)
}
// Claim prompts the external sender to sign a claim transaction
func (s *ExternalSender) Claim(id types.Hash, _swap swapfactory.SwapFactorySwap,
_s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
input, err := s.abi.Pack("claim", _swap, _s)
if err != nil {
return ethcommon.Hash{}, nil, err
}
return s.sendAndReceive(id, input)
}
// Refund prompts the external sender to sign a refund transaction
func (s *ExternalSender) Refund(id types.Hash, _swap swapfactory.SwapFactorySwap,
_s [32]byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
input, err := s.abi.Pack("refund", _swap, _s)
if err != nil {
return ethcommon.Hash{}, nil, err
}
return s.sendAndReceive(id, input)
}
func (s *ExternalSender) sendAndReceive(id types.Hash,
input []byte) (ethcommon.Hash, *ethtypes.Receipt, error) {
tx := &Transaction{
To: s.contractAddr,
Data: fmt.Sprintf("0x%x", input),
}
s.RLock()
defer s.RUnlock()
chs, has := s.swaps[id]
if !has {
return ethcommon.Hash{}, nil, errNoSwapWithID
}
chs.out <- tx
var txHash ethcommon.Hash
select {
case <-time.After(transactionTimeout):
return ethcommon.Hash{}, nil, errTransactionTimeout
case txHash = <-chs.in:
}
receipt, err := waitForReceipt(s.ctx, s.ec, txHash)
if err != nil {
return ethcommon.Hash{}, nil, err
}
return txHash, receipt, nil
}