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

chain+wallet: make use of the new errors from rpcclient #918

Merged
merged 2 commits into from
Mar 18, 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
2 changes: 1 addition & 1 deletion chain/neutrino.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (s *NeutrinoClient) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool)
*chainhash.Hash, error) {
err := s.CS.SendTransaction(tx)
if err != nil {
return nil, err
return nil, rpcclient.MapRPCErr(err)
}
hash := tx.TxHash()
return &hash, nil
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/btcsuite/btcwallet

require (
github.com/btcsuite/btcd v0.24.1-0.20240301210420-1a2b599bf1af
github.com/btcsuite/btcd v0.24.1-0.20240318151728-2fc99e0496d2
github.com/btcsuite/btcd/btcec/v2 v2.2.2
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcd/btcutil/psbt v1.1.8
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd v0.24.1-0.20240301210420-1a2b599bf1af h1:F60A3wst4/fy9Yr1Vn8MYmFlfn7DNLxp8o8UTvhqgBE=
github.com/btcsuite/btcd v0.24.1-0.20240301210420-1a2b599bf1af/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
github.com/btcsuite/btcd v0.24.1-0.20240318151728-2fc99e0496d2 h1:b7EiiYEZypI2id3516ptqjzhUfFAgNfF4YVtxikAg6Y=
github.com/btcsuite/btcd v0.24.1-0.20240318151728-2fc99e0496d2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.2.2 h1:5uxe5YjoCq+JeOpg0gZSNHuFgeogrocBYxvg6w9sAgc=
Expand Down
243 changes: 14 additions & 229 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
Expand All @@ -23,6 +22,7 @@ import (
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
Expand Down Expand Up @@ -3591,25 +3591,6 @@ func (e *ErrDoubleSpend) Unwrap() error {
return e.backendError
}

// ErrReplacement is an error returned from PublishTransaction in case the
// published transaction failed to propagate since it was double spending a
// replacable transaction but did not satisfy the requirements to replace it.
type ErrReplacement struct {
backendError error
}

// Error returns the string representation of ErrReplacement.
//
// NOTE: Satisfies the error interface.
func (e *ErrReplacement) Error() string {
return fmt.Sprintf("unable to replace transaction: %v", e.backendError)
}

// Unwrap returns the underlying error returned from the backend.
func (e *ErrReplacement) Unwrap() error {
return e.backendError
}

// ErrMempoolFee is an error returned from PublishTransaction in case the
// published transaction failed to propagate since it did not match the
// current mempool fee requirement.
Expand Down Expand Up @@ -3760,27 +3741,27 @@ func (w *Wallet) publishTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) {
return nil, err
}

var (
txid = tx.TxHash()
returnErr error
)

txid := tx.TxHash()
_, err = chainClient.SendRawTransaction(tx, false)
if err == nil {
return &txid, nil
}

returnErr = MapBroadcastBackendError(err)

var errInMempool *ErrInMempool
var errAlreadyConfirmed *ErrAlreadyConfirmed
// Map the error to an RPC-specific error type.
guggero marked this conversation as resolved.
Show resolved Hide resolved
//
// NOTE: all the errors returned here are mapped to an error type
// defined in `rpcclient` package, where the error strings are taken
// from bitcoind.
rpcErr := rpcclient.MapRPCErr(err)

switch {
case errors.As(returnErr, &errInMempool):
case errors.Is(rpcErr, rpcclient.ErrTxAlreadyInMempool):
log.Infof("%v: tx already in mempool", txid)
return &txid, nil

case errors.As(returnErr, &errAlreadyConfirmed):
case errors.Is(rpcErr, rpcclient.ErrTxAlreadyKnown),
errors.Is(rpcErr, rpcclient.ErrTxAlreadyConfirmed):

dbErr := walletdb.Update(w.db, func(dbTx walletdb.ReadWriteTx) error {
txmgrNs := dbTx.ReadWriteBucket(wtxmgrNamespaceKey)
txRec, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now())
Expand All @@ -3801,7 +3782,7 @@ func (w *Wallet) publishTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) {
}

// Log the causing error, even if we know how to handle it.
log.Infof("%v: broadcast failed because of: %v", txid, returnErr)
log.Infof("%v: broadcast failed because of: %v", txid, rpcErr)

// If the transaction was rejected for whatever other reason, then
// we'll remove it from the transaction store, as otherwise, we'll
Expand Down Expand Up @@ -3838,7 +3819,7 @@ func (w *Wallet) publishTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) {
}
}

return nil, returnErr
return nil, rpcErr
}

// ChainParams returns the network parameters for the blockchain the wallet
Expand Down Expand Up @@ -4052,199 +4033,3 @@ func OpenWithRetry(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks,

return w, nil
}

// matchBitcoindErr takes an error returned from bitcoind RPC client and
// matches it against the specified string. If the expected string pattern is
// found in the error passed, return true.
func matchBitcoindErr(err error, s string) bool {
// Replace all dashes found in the error string with spaces.
strippedErrStr := strings.Replace(err.Error(), "-", " ", -1)

// Replace all dashes found in the error string with spaces.
strippedMatchStr := strings.Replace(s, "-", " ", -1)

// Match against the lowercase.
return strings.Contains(
strings.ToLower(strippedErrStr),
strings.ToLower(strippedMatchStr),
)
}

// MapBroadcastBackendError maps the different backend errors when broadcasting
// a transaction to the bitcoin network to an internal error type.
func MapBroadcastBackendError(err error) error {
// Determine if this was an RPC error thrown due to the transaction
// already confirming.
var rpcTxConfirmed bool
if rpcErr, ok := err.(*btcjson.RPCError); ok {
rpcTxConfirmed = rpcErr.Code == btcjson.ErrRPCTxAlreadyInChain
}

var returnErr error

switch {
case err == nil:
return nil

// Since we have different backends that can be used with the wallet,
// we'll need to check specific errors for each one.
//
// If the transaction is already in the mempool, we can just return now.
//
// This error is returned when broadcasting/sending a transaction to a
// btcd node that already has it in their mempool.
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L953
case matchBitcoindErr(err, "already have transaction"):
fallthrough

// This error is returned when broadcasting a transaction to a bitcoind
// node that already has it in their mempool.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L590
case matchBitcoindErr(err, "txn-already-in-mempool"):
return &ErrInMempool{
backendError: err,
}

// If the transaction has already confirmed, we can safely remove it
// from the unconfirmed store as it should already exist within the
// confirmed store. We'll avoid returning an error as the broadcast was
// in a sense successful.
//
// This error is returned when sending a transaction that has already
// confirmed to a btcd/bitcoind node over RPC.
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/rpcserver.go#L3355
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/node/transaction.cpp#L36
case rpcTxConfirmed:
fallthrough

// This error is returned when broadcasting a transaction that has
// already confirmed to a btcd node over the P2P network.
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L1036
case matchBitcoindErr(err, "transaction already exists"):
fallthrough

// This error is returned when broadcasting a transaction that has
// already confirmed to a bitcoind node over the P2P network.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L648
case matchBitcoindErr(err, "txn-already-known"):
return &ErrAlreadyConfirmed{
backendError: err,
}

// If the transactions is invalid since it attempts to double spend a
// transaction already in the mempool or in the chain, we'll remove it
// from the store and return an error.
//
// This error is returned from btcd when there is already a transaction
// not signaling replacement in the mempool that spends one of the
// referenced outputs.
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L591
case matchBitcoindErr(err, "already spent"):
fallthrough

// This error is returned from btcd when a referenced output cannot be
// found, meaning it etiher has been spent or doesn't exist.
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/blockchain/chain.go#L405
case matchBitcoindErr(err, "already been spent"):
fallthrough

// This error is returned from btcd when a transaction is spending
// either output that is missing or already spent, and orphans aren't
// allowed.
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L1409
case matchBitcoindErr(err, "orphan transaction"):
fallthrough

// Error returned from bitcoind when output was spent by other
// non-replacable transaction already in the mempool.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L622
case matchBitcoindErr(err, "txn-mempool-conflict"):
fallthrough

// Returned by bitcoind on the RPC when broadcasting a transaction that
// is spending either output that is missing or already spent.
//
// https://github.com/bitcoin/bitcoin/blob/3ba8de1b704d590fa4e1975620bd21d830d11666/test/functional/mempool_accept.py#L163C1-L163C1
case matchBitcoindErr(err, "missing-inputs") ||
matchBitcoindErr(err, "bad-txns-inputs-missingorspent"):

returnErr = &ErrDoubleSpend{
backendError: err,
}

// Returned by bitcoind if the transaction spends outputs that would be
// replaced by it.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L790
case matchBitcoindErr(err, "bad-txns-spends-conflicting-tx"):
fallthrough

// Returned by bitcoind when a replacement transaction did not have
// enough fee.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L830
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L894
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L904
case matchBitcoindErr(err, "insufficient fee"):
fallthrough

// Returned by bitcoind in case the transaction would replace too many
// transaction in the mempool.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L858
case matchBitcoindErr(err, "too many potential replacements"):
fallthrough

// Returned by bitcoind if the transaction spends an output that is
// unconfimed and not spent by the transaction it replaces.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L882
case matchBitcoindErr(err, "replacement-adds-unconfirmed"):
fallthrough

// Returned by btcd when replacement transaction was rejected for
// whatever reason.
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L841
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L854
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L875
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L896
// https://github.com/btcsuite/btcd/blob/130ea5bddde33df32b06a1cdb42a6316eb73cff5/mempool/mempool.go#L913
case matchBitcoindErr(err, "replacement transaction"):
returnErr = &ErrReplacement{
backendError: err,
}

// Returned by bitcoind when a transaction does not meet the fee
// requirements to be accepted into mempool. This happens when the
// mempool reached its limits and is now purging low fee transactions.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L510
case matchBitcoindErr(err, "mempool min fee not met"):
fallthrough

// Returned by btcd when a transaction does not meet the fee
// requirements to be accepted into mempool.
// https://github.com/btcsuite/btcd/blob/9c16d23918b15c468c5647c388b9b7db3bc48dc7/mempool/mempool.go#L1151
case matchBitcoindErr(err, "fees which is under the required amount"):
fallthrough

// Returned by btcd when a transaction does not meet the fee
// requirements to be accepted into mempool. The error states priority
// but decreasing the min relay fee prevents checking for the priority
// in the first place therefore we consider this a mempool fee error.
// https://github.com/btcsuite/btcd/blob/9c16d23918b15c468c5647c388b9b7db3bc48dc7/mempool/mempool.go#L1162
case matchBitcoindErr(err, "has insufficient priority"):
fallthrough

// Returned by bitcoind when a transaction does not meet the fee
// requirements to be accepted into mempool because the policy of the
// mempool has a higher min relay fee. Default value for bitcoind is
// 1000 sat/kvbyte but this is configurable.
// https://github.com/bitcoin/bitcoin/blob/9bf5768dd628b3a7c30dd42b5ed477a92c4d3540/src/validation.cpp#L514
case matchBitcoindErr(err, "min relay fee not met"):
returnErr = &ErrMempoolFee{
backendError: err,
}

// We received an error not matching any of the above cases.
default:
returnErr = fmt.Errorf("unmatched backend error: %w", err)
}

return returnErr
}
Loading
Loading