diff --git a/chain/bitcoind_client.go b/chain/bitcoind_client.go index 4997877565..a7f58b89e2 100644 --- a/chain/bitcoind_client.go +++ b/chain/bitcoind_client.go @@ -214,7 +214,27 @@ func (c *BitcoindClient) GetTxOut(txHash *chainhash.Hash, index uint32, func (c *BitcoindClient) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error) { - return c.chainConn.client.SendRawTransaction(tx, allowHighFees) + txid, err := c.chainConn.client.SendRawTransaction(tx, allowHighFees) + if err != nil { + return nil, c.MapRPCErr(err) + } + + return txid, nil +} + +// MapRPCErr takes an error returned from calling RPC methods from various +// chain backend and map it to an defined error here. +func (c *BitcoindClient) MapRPCErr(rpcErr error) error { + // Try to match it against bitcoind's error. + for i := uint32(0); i < uint32(errSentinel); i++ { + err := RPCErr(i) + if matchErrStr(rpcErr, err.Error()) { + return err + } + } + + // If not matched, return the original error wrapped. + return fmt.Errorf("%w: %v", ErrUndefined, rpcErr) } // TestMempoolAcceptCmd returns result of mempool acceptance tests indicating @@ -224,7 +244,12 @@ func (c *BitcoindClient) SendRawTransaction(tx *wire.MsgTx, func (c *BitcoindClient) TestMempoolAccept(txns []*wire.MsgTx, maxFeeRate float64) ([]*btcjson.TestMempoolAcceptResult, error) { - return c.chainConn.client.TestMempoolAccept(txns, maxFeeRate) + result, err := c.chainConn.client.TestMempoolAccept(txns, maxFeeRate) + if err != nil { + return nil, c.MapRPCErr(err) + } + + return result, nil } // Notifications returns a channel to retrieve notifications from. diff --git a/chain/btcd.go b/chain/btcd.go index 85d8013a9f..277823ddab 100644 --- a/chain/btcd.go +++ b/chain/btcd.go @@ -6,6 +6,7 @@ package chain import ( "errors" + "fmt" "sync" "time" @@ -578,3 +579,47 @@ func (c *RPCClient) LookupInputMempoolSpend(op wire.OutPoint) ( return getTxSpendingPrevOut(op, c.Client) } + +// MapRPCErr takes an error returned from calling RPC methods from various +// chain backend and map it to an defined error here. It uses the `BtcdErrMap`, +// whose keys are btcd error strings and values are errors made from bitcoind +// error strings. +func (c *RPCClient) MapRPCErr(rpcErr error) error { + // Iterate the map and find the matching error. + for btcdErr, err := range BtcdErrMap { + // Match it against btcd's error. + if matchErrStr(rpcErr, btcdErr) { + return err + } + } + + // If not matched, return the original error wrapped. + return fmt.Errorf("%w: %v", ErrUndefined, rpcErr) +} + +// TestMempoolAcceptCmd returns result of mempool acceptance tests indicating +// if raw transaction(s) would be accepted by mempool. +// +// NOTE: This is part of the chain.Interface interface. +func (c *RPCClient) TestMempoolAccept(txns []*wire.MsgTx, + maxFeeRate float64) ([]*btcjson.TestMempoolAcceptResult, error) { + + result, err := c.Client.TestMempoolAccept(txns, maxFeeRate) + if err != nil { + return nil, c.MapRPCErr(err) + } + + return result, nil +} + +// SendRawTransaction sends a raw transaction via btcd. +func (c *RPCClient) SendRawTransaction(tx *wire.MsgTx, + allowHighFees bool) (*chainhash.Hash, error) { + + txid, err := c.Client.SendRawTransaction(tx, allowHighFees) + if err != nil { + return nil, c.MapRPCErr(err) + } + + return txid, nil +} diff --git a/chain/interface.go b/chain/interface.go index 5691e792ee..9ca341dbaf 100644 --- a/chain/interface.go +++ b/chain/interface.go @@ -49,6 +49,7 @@ type Interface interface { Notifications() <-chan interface{} BackEnd() string TestMempoolAccept([]*wire.MsgTx, float64) ([]*btcjson.TestMempoolAcceptResult, error) + MapRPCErr(err error) error } // Notification types. These are defined here and processed from from reading diff --git a/chain/neutrino.go b/chain/neutrino.go index 61e4d5f428..62fe56d661 100644 --- a/chain/neutrino.go +++ b/chain/neutrino.go @@ -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, rpcclient.MapRPCErr(err) + return nil, s.MapRPCErr(err) } hash := tx.TxHash() return &hash, nil @@ -815,3 +815,22 @@ out: close(s.dequeueNotification) s.wg.Done() } + +// MapRPCErr takes an error returned from calling RPC methods from various +// chain backend and map it to an defined error here. It uses the `BtcdErrMap`, +// whose keys are btcd error strings and values are errors made from bitcoind +// error strings. +// +// NOTE: we assume neutrino shares the same error strings as btcd. +func (s *NeutrinoClient) MapRPCErr(rpcErr error) error { + // Iterate the map and find the matching error. + for btcdErr, err := range BtcdErrMap { + // Match it against btcd's error. + if matchErrStr(rpcErr, btcdErr) { + return err + } + } + + // If not matched, return the original error wrapped. + return fmt.Errorf("%w: %v", ErrUndefined, rpcErr) +} diff --git a/wallet/mock.go b/wallet/mock.go index 8c995ebb57..43c3d881bb 100644 --- a/wallet/mock.go +++ b/wallet/mock.go @@ -94,3 +94,7 @@ func (m *mockChainClient) TestMempoolAccept(txns []*wire.MsgTx, return nil, nil } + +func (m *mockChainClient) MapRPCErr(err error) error { + return nil +} diff --git a/wallet/wallet.go b/wallet/wallet.go index 362ca9b787..fb7f10419b 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -22,7 +22,6 @@ 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" @@ -3793,25 +3792,18 @@ func (w *Wallet) publishTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { } txid := tx.TxHash() - _, err = chainClient.SendRawTransaction(tx, false) - if err == nil { + _, rpcErr := chainClient.SendRawTransaction(tx, false) + if rpcErr == nil { return &txid, nil } - // Map the error to an RPC-specific error type. - // - // 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.Is(rpcErr, rpcclient.ErrTxAlreadyInMempool): + case errors.Is(rpcErr, chain.ErrTxAlreadyInMempool): log.Infof("%v: tx already in mempool", txid) return &txid, nil - case errors.Is(rpcErr, rpcclient.ErrTxAlreadyKnown), - errors.Is(rpcErr, rpcclient.ErrTxAlreadyConfirmed): + case errors.Is(rpcErr, chain.ErrTxAlreadyKnown), + errors.Is(rpcErr, chain.ErrTxAlreadyConfirmed): dbErr := walletdb.Update(w.db, func(dbTx walletdb.ReadWriteTx) error { txmgrNs := dbTx.ReadWriteBucket(wtxmgrNamespaceKey)