diff --git a/pkg/bitcoin/electrum/electrum.go b/pkg/bitcoin/electrum/electrum.go index b02eeaa7cd..70cbc82309 100644 --- a/pkg/bitcoin/electrum/electrum.go +++ b/pkg/bitcoin/electrum/electrum.go @@ -88,7 +88,18 @@ func (c *Connection) GetTransaction( // We cannot use `GetTransaction` to get the the transaction details // as Esplora/Electrs doesn't support verbose transactions. // See: https://github.com/Blockstream/electrs/pull/36 - return client.GetRawTransaction(ctx, txID) + tx, err := client.GetRawTransaction(ctx, txID) + if err != nil { + if isTxNotFoundErr(err) { + // The transaction was not found on the chain. There is + // no point in retrying the request and losing time. + return "", nil + } + + return "", err + } + + return tx, nil }, "GetRawTransaction", ) @@ -99,6 +110,13 @@ func (c *Connection) GetTransaction( err, ) } + if len(rawTransaction) == 0 { + return nil, fmt.Errorf( + "failed to get raw transaction with ID [%s]: [%v]", + txID, + fmt.Errorf("not found"), + ) + } result, err := convertRawTransaction(rawTransaction) if err != nil { @@ -123,10 +141,21 @@ func (c *Connection) GetTransactionConfirmations( rawTransaction, err := requestWithRetry( c, func(ctx context.Context, client *electrum.Client) (string, error) { - // We cannot use `GetTransaction` to get the the transaction details + // We cannot use `GetTransaction` to get the transaction details // as Esplora/Electrs doesn't support verbose transactions. // See: https://github.com/Blockstream/electrs/pull/36 - return client.GetRawTransaction(ctx, txID) + tx, err := client.GetRawTransaction(ctx, txID) + if err != nil { + if isTxNotFoundErr(err) { + // The transaction was not found on the chain. There is + // no point in retrying the request and losing time. + return "", nil + } + + return "", err + } + + return tx, nil }, "GetRawTransaction", ) @@ -138,6 +167,13 @@ func (c *Connection) GetTransactionConfirmations( err, ) } + if len(rawTransaction) == 0 { + return 0, fmt.Errorf( + "failed to get raw transaction with ID [%s]: [%v]", + txID, + fmt.Errorf("not found"), + ) + } tx, err := decodeTransaction(rawTransaction) if err != nil { @@ -224,6 +260,24 @@ txOutLoop: return 0, nil } +func isTxNotFoundErr(err error) bool { + txNotFoundErrs := []string{ + "no such mempool or blockchain transaction", + "missing transaction", + "transaction not found", + } + + errStr := strings.ToLower(err.Error()) + + for _, txNotFoundErr := range txNotFoundErrs { + if strings.Contains(errStr, txNotFoundErr) { + return true + } + } + + return false +} + // BroadcastTransaction broadcasts the given transaction over the // network of the Bitcoin chain nodes. If the broadcast action could not be // done, this function returns an error. This function does not give any diff --git a/pkg/bitcoin/electrum/electrum_integration_test.go b/pkg/bitcoin/electrum/electrum_integration_test.go index a333af2998..5db9caea06 100644 --- a/pkg/bitcoin/electrum/electrum_integration_test.go +++ b/pkg/bitcoin/electrum/electrum_integration_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "math" + "reflect" "sort" "strings" "testing" @@ -175,15 +176,19 @@ func TestGetTransaction_Negative_Integration(t *testing.T) { _, err := electrum.GetTransaction(invalidTxID) - assertMissingTransactionError( - t, - testConfig.clientConfig, - fmt.Sprintf( - "failed to get raw transaction with ID [%s]", - invalidTxID.Hex(bitcoin.ReversedByteOrder), - ), - err, + expectedErr := fmt.Errorf( + "failed to get raw transaction with ID [%s]: [not found]", + invalidTxID.Hex(bitcoin.ReversedByteOrder), ) + if !reflect.DeepEqual(expectedErr, err) { + t.Errorf( + "unexpected error\n"+ + "expected: %v\n"+ + "actual: %v\n", + expectedErr, + err, + ) + } }) } @@ -221,15 +226,19 @@ func TestGetTransactionConfirmations_Negative_Integration(t *testing.T) { _, err := electrum.GetTransactionConfirmations(invalidTxID) - assertMissingTransactionError( - t, - testConfig.clientConfig, - fmt.Sprintf( - "failed to get raw transaction with ID [%s]", - invalidTxID.Hex(bitcoin.ReversedByteOrder), - ), - err, + expectedErr := fmt.Errorf( + "failed to get raw transaction with ID [%s]: [not found]", + invalidTxID.Hex(bitcoin.ReversedByteOrder), ) + if !reflect.DeepEqual(expectedErr, err) { + t.Errorf( + "unexpected error\n"+ + "expected: %v\n"+ + "actual: %v\n", + expectedErr, + err, + ) + } }) } @@ -607,17 +616,11 @@ func assertNumberCloseTo(t *testing.T, expected uint, actual uint, delta uint) { } type expectedErrorMessages struct { - missingTransaction []string missingBlockHeader []string missingTransactionInBlock []string } var expectedServerErrorMessages = expectedErrorMessages{ - missingTransaction: []string{ - "errNo: 0, errMsg: missing transaction", - "errNo: 2, errMsg: daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})", - "errNo: 2, errMsg: daemon error: DaemonError({'message': 'Transaction not found.', 'code': -1})", - }, missingBlockHeader: []string{ "errNo: 0, errMsg: missing header", "errNo: 1, errMsg: height 4,294,967,295 out of range", @@ -629,22 +632,6 @@ var expectedServerErrorMessages = expectedErrorMessages{ "errNo: 1, errMsg: No transaction matching the requested hash found at height 123456"}, } -func assertMissingTransactionError( - t *testing.T, - clientConfig electrum.Config, - clientErrorPrefix string, - - actualError error, -) { - assertServerError( - t, - clientConfig, - clientErrorPrefix, - expectedServerErrorMessages.missingTransaction, - actualError, - ) -} - func assertMissingBlockHeaderError( t *testing.T, clientConfig electrum.Config,