From c1d5347616151a3a992afd32cad8f7f7734931b5 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Wed, 26 Apr 2023 21:55:46 +0200 Subject: [PATCH] fix: set `GasLimit` for relayed transactions (#425) Co-authored-by: Dmitry Holodov --- net/relay.go | 4 +-- protocol/xmrmaker/claim.go | 47 ++++++++++++++++++++++++++++++--- protocol/xmrmaker/event.go | 9 +------ protocol/xmrmaker/swap_state.go | 12 ++++++--- protocol/xmrtaker/claim.go | 6 +++++ relayer/submit_transaction.go | 3 +++ 6 files changed, 64 insertions(+), 17 deletions(-) diff --git a/net/relay.go b/net/relay.go index 862b98f2..3c25c09a 100644 --- a/net/relay.go +++ b/net/relay.go @@ -60,7 +60,7 @@ func (h *Host) handleRelayStream(stream libp2pnetwork.Stream) { // (2) If the request is purportedly from a maker to a taker of a current // swap, then: // (a) The swap should exist in our swaps map - // (b) The peerID who sent us the request much match the peerID with + // (b) The peerID who sent us the request must match the peerID with // whom we are performing the swap. if req.OfferID == nil && !h.isRelayer { return @@ -120,7 +120,7 @@ func receiveRelayClaimResponse(stream libp2pnetwork.Stream) (*RelayClaimResponse // The timeout should be short enough, that the Maker can try multiple relayers // before T1 expires even if the receiving node accepts the relay request and // just sits on it without doing anything. - const relayResponseTimeout = time.Second * 45 + const relayResponseTimeout = time.Minute select { case msg := <-nextStreamMessage(stream, maxMessageSize): diff --git a/protocol/xmrmaker/claim.go b/protocol/xmrmaker/claim.go index 002257c5..de939c3b 100644 --- a/protocol/xmrmaker/claim.go +++ b/protocol/xmrmaker/claim.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum" @@ -15,9 +16,11 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" + "github.com/athanorlabs/atomic-swap/coins" "github.com/athanorlabs/atomic-swap/common" "github.com/athanorlabs/atomic-swap/common/types" "github.com/athanorlabs/atomic-swap/ethereum/block" + "github.com/athanorlabs/atomic-swap/ethereum/extethclient" "github.com/athanorlabs/atomic-swap/net/message" "github.com/athanorlabs/atomic-swap/relayer" ) @@ -39,12 +42,16 @@ func (s *swapState) claimFunds() (*ethtypes.Receipt, error) { log.Infof("balance before claim: %s %s", balance.AsStandardString(), balance.StandardSymbol()) } + hasBalanceToClaim, err := checkForMinClaimBalance(s.ctx, s.ETHClient()) + if err != nil { + return nil, err + } + var receipt *ethtypes.Receipt // call swap.Swap.Claim() w/ b.privkeys.sk, revealing XMRMaker's secret spend key - if s.offerExtra.UseRelayer || weiBalance.Decimal().IsZero() { + if s.offerExtra.UseRelayer || !hasBalanceToClaim { // relayer fee was set or we had insufficient funds to claim without a relayer - // TODO: Sufficient funds check above should be more specific receipt, err = s.claimWithRelay() if err != nil { return nil, fmt.Errorf("failed to claim using relayers: %w", err) @@ -81,6 +88,40 @@ func (s *swapState) claimFunds() (*ethtypes.Receipt, error) { return receipt, nil } +// checkForMinClaimBalance check if we have enough balance to call claim. +// return true if we do, false otherwise. +func checkForMinClaimBalance(ctx context.Context, ec extethclient.EthClient) (bool, error) { + // gas cost for ETH-claim is 42965 + // gas cost for ERC20-claim is 47138 + // add a bit of leeway to allow for sudden gas price spikes + const claimGas = 50000 + + balance, err := ec.Balance(ctx) + if err != nil { + return false, err + } + + if balance.Decimal().IsZero() { + return false, nil + } + + gasPrice, err := ec.SuggestGasPrice(ctx) + if err != nil { + return false, err + } + + txCost := new(big.Int).Mul(gasPrice, big.NewInt(claimGas)) + if balance.BigInt().Cmp(txCost) < 0 { + log.Infof("balance %s ETH is under the minimum %s ETH to call claim, using a relayer", + balance.AsEtherString(), + coins.FmtWeiAsETH(txCost), + ) + return false, nil + } + + return true, nil +} + // relayClaimWithXMRTaker relays the claim to the swap's XMR taker, who should // process the claim even if they are not relaying claims for everyone. func (s *swapState) relayClaimWithXMRTaker(request *message.RelayClaimRequest) (*ethtypes.Receipt, error) { @@ -106,7 +147,6 @@ func (s *swapState) relayClaimWithXMRTaker(request *message.RelayClaimRequest) ( } log.Infof("relayer's claim via counterparty included and validated %s", common.ReceiptInfo(receipt)) - return receipt, nil } @@ -128,6 +168,7 @@ func (s *swapState) claimWithAdvertisedRelayers(request *message.RelayClaimReque log.Debugf("skipping DHT-advertised relayer that is our swap counterparty") continue } + log.Debugf("submitting claim to relayer with peer ID %s", relayerPeerID) resp, err := s.Backend.SubmitClaimToRelayer(relayerPeerID, request) if err != nil { diff --git a/protocol/xmrmaker/event.go b/protocol/xmrmaker/event.go index c5b3a413..d2ca242e 100644 --- a/protocol/xmrmaker/event.go +++ b/protocol/xmrmaker/event.go @@ -196,16 +196,9 @@ func (s *swapState) handleEvent(event Event) { err := s.handleNotifyETHLocked(e.message) if err != nil { e.errCh <- fmt.Errorf("failed to handle EventETHLocked: %w", err) - if !s.fundsLocked { - return - } } - err = s.setNextExpectedEvent(EventContractReadyType) - if err != nil { - e.errCh <- fmt.Errorf("failed to set next expected event to EventContractReadyType: %w", err) - return - } + // nextExpectedEvent was set in s.lockFunds() case *EventContractReady: log.Infof("EventContractReady") defer close(e.errCh) diff --git a/protocol/xmrmaker/swap_state.go b/protocol/xmrmaker/swap_state.go index 816fdd8b..785a226a 100644 --- a/protocol/xmrmaker/swap_state.go +++ b/protocol/xmrmaker/swap_state.go @@ -77,8 +77,6 @@ type swapState struct { // tracks the state of the swap nextExpectedEvent EventType - // set to true once funds are locked - fundsLocked bool readyWatcher *watcher.EventFilter @@ -654,8 +652,15 @@ func (s *swapState) lockFunds(amount *coins.PiconeroAmount) error { log.Debug("total XMR balance: ", coins.FmtPiconeroAsXMR(balance.Balance)) log.Info("unlocked XMR balance: ", coins.FmtPiconeroAsXMR(balance.UnlockedBalance)) - log.Infof("Starting lock of %s XMR in address %s", amount.AsMoneroString(), swapDestAddr) + + // set next expected event here, otherwise if we restart while `Transfer` is happening, + // we won't notice that we already locked the XMR on restart. + err = s.setNextExpectedEvent(EventContractReadyType) + if err != nil { + return fmt.Errorf("failed to set next expected event to EventContractReadyType: %w", err) + } + transfer, err := s.XMRClient().Transfer(s.ctx, swapDestAddr, 0, amount, monero.MinSpendConfirmations) if err != nil { return err @@ -663,6 +668,5 @@ func (s *swapState) lockFunds(amount *coins.PiconeroAmount) error { log.Infof("Successfully locked XMR funds: txID=%s address=%s block=%d", transfer.TxID, swapDestAddr, transfer.Height) - s.fundsLocked = true return nil } diff --git a/protocol/xmrtaker/claim.go b/protocol/xmrtaker/claim.go index dee62f8b..38d66cc8 100644 --- a/protocol/xmrtaker/claim.go +++ b/protocol/xmrtaker/claim.go @@ -117,5 +117,11 @@ func (s *swapState) claimMonero(skB *mcrypto.PrivateSpendKey) (*mcrypto.Address, close(s.claimedCh) log.Infof("monero claimed and swept to original account %s", depositAddr) + go func() { + err = s.Exit() + if err != nil { + log.Warnf("failed to exit: %v", err) + } + }() return kpAB.PublicKeyPair().Address(s.Env()), nil } diff --git a/relayer/submit_transaction.go b/relayer/submit_transaction.go index 44fd98ff..494e72a8 100644 --- a/relayer/submit_transaction.go +++ b/relayer/submit_transaction.go @@ -78,6 +78,8 @@ func ValidateAndSendTransaction( return nil, err } txOpts.GasPrice = gasPrice + txOpts.GasLimit = forwarderClaimGas + log.Debugf("relaying tx with gas price %s and gas limit %d", gasPrice, txOpts.GasLimit) err = simulateExecute( ctx, @@ -101,6 +103,7 @@ func ValidateAndSendTransaction( req.Signature, ) if err != nil { + log.Errorf("failed to call execute: %s", err) return nil, err }