Skip to content

Commit

Permalink
Merge pull request #326 from ElementsProject/lwk-send-retry
Browse files Browse the repository at this point in the history
lwk: add backoff for send
  • Loading branch information
grubles authored Nov 5, 2024
2 parents 513f0c6 + 27077a8 commit df76452
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 13 deletions.
47 changes: 43 additions & 4 deletions electrum/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package electrum
import (
"context"
"crypto/tls"
"fmt"
"strings"
"time"

"github.com/cenkalti/backoff/v4"
"github.com/checksum0/go-electrum/electrum"
"github.com/elementsproject/peerswap/log"
)
Expand Down Expand Up @@ -71,11 +75,46 @@ func (c *electrumClient) GetHistory(ctx context.Context, scripthash string) ([]*
return c.client.GetHistory(ctx, scripthash)
}

// GetRawTransaction retrieves the raw transaction data for a given transaction
// and handles retries in case of a
// "missing transaction" error. It uses an exponential backoff strategy for
// retries, with a maximum of 10 retries. This is a temporary workaround for
// an issue where a missing transaction error occurs even when the UTXO exists.
// If the issue persists, the backoff strategy may need adjustment.
func (c *electrumClient) GetRawTransaction(ctx context.Context, txHash string) (string, error) {
if err := c.reconnect(ctx); err != nil {
return "", err
}
return c.client.GetRawTransaction(ctx, txHash)
var rawTx string

err := retryWithBackoff(func() error {
if err := c.reconnect(ctx); err != nil {
return err
}
var innerErr error
rawTx, innerErr = c.client.GetRawTransaction(ctx, txHash)
return innerErr
})

return rawTx, err
}

func retryWithBackoff(operation func() error) error {
const maxRetries = 10
const maxElapsedTime = 2 * time.Minute

backoffStrategy := backoff.NewExponentialBackOff()
backoffStrategy.MaxElapsedTime = maxElapsedTime

return backoff.Retry(func() error {
err := operation()
if err != nil {
log.Infof("Error during operation: %v", err)
if strings.Contains(err.Error(), "missing transaction") {
log.Infof("Retrying due to missing transaction error: %v", err)
return err
}
return backoff.Permanent(fmt.Errorf("permanent error: %w", err))
}
return nil
}, backoff.WithMaxRetries(backoffStrategy, uint64(maxRetries)))
}

func (c *electrumClient) BroadcastTransaction(ctx context.Context, rawTx string) (string, error) {
Expand Down
32 changes: 27 additions & 5 deletions lwk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import (
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/cenkalti/backoff/v4"
"github.com/elementsproject/glightning/jrpc2"
"github.com/elementsproject/peerswap/log"
)

type lwkclient struct {
Expand Down Expand Up @@ -111,13 +115,31 @@ func (s *sendRequest) Name() string {
return "wallet_send_many"
}

// send sends a request using the lwkclient and handles retries in case of a
// "missing transaction" error. It uses an exponential backoff strategy for
// retries, with a maximum of 5 retries. This is a temporary workaround for
// an issue where a missing transaction error occurs even when the UTXO exists.
// If the issue persists, the backoff strategy may need adjustment.
func (l *lwkclient) send(ctx context.Context, s *sendRequest) (*sendResponse, error) {
var resp sendResponse
err := l.request(ctx, s, &resp)
if err != nil {
return nil, err
}
return &resp, nil
// Allow configuration of maxRetries and backoff strategy
maxRetries := 5
backoffStrategy := backoff.NewExponentialBackOff()
backoffStrategy.MaxElapsedTime = 2 * time.Minute

err := backoff.Retry(func() error {
innerErr := l.request(ctx, s, &resp)
if innerErr != nil {
log.Infof("Error during send request: %v", innerErr)
if strings.Contains(innerErr.Error(), "missing transaction") {
log.Infof("Retrying due to missing transaction error: %v", innerErr)
return innerErr
}
return backoff.Permanent(fmt.Errorf("permanent error: %w", innerErr))
}
return nil
}, backoff.WithMaxRetries(backoffStrategy, uint64(maxRetries)))
return &resp, err
}

type signRequest struct {
Expand Down
9 changes: 5 additions & 4 deletions lwk/lwkwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lwk
import (
"context"
"errors"
"fmt"

"math"
"strings"
Expand Down Expand Up @@ -171,25 +172,25 @@ func (r *LWKRpcWallet) CreateAndBroadcastTransaction(swapParams *swap.OpeningPar
FeeRate: &feerate,
})
if err != nil {
return "", "", 0, err
return "", "", 0, fmt.Errorf("failed to fund transaction: %w", err)
}
signed, err := r.lwkClient.sign(ctx, &signRequest{
SignerName: r.c.GetSignerName(),
Pset: fundedTx.Pset,
})
if err != nil {
return "", "", 0, err
return "", "", 0, fmt.Errorf("failed to sign transaction: %w", err)
}
broadcasted, err := r.lwkClient.broadcast(ctx, &broadcastRequest{
WalletName: r.c.GetWalletName(),
Pset: signed.Pset,
})
if err != nil {
return "", "", 0, err
return "", "", 0, fmt.Errorf("failed to broadcast transaction: %w", err)
}
hex, err := r.electrumClient.GetRawTransaction(ctx, broadcasted.Txid)
if err != nil {
return "", "", 0, err
return "", "", 0, fmt.Errorf("failed to get raw transaction: %w", err)
}
return broadcasted.Txid, hex, 0, nil
}
Expand Down

0 comments on commit df76452

Please sign in to comment.