Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lwk: add backoff for send #326

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading