From aa2adfd901fd6f1942b4c9bdefc55ebbd8b8a1fd Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 3 Jul 2024 22:30:16 +0800 Subject: [PATCH] rpcclient: remove error matching related code This is now handled in `btcwallet/chain`. --- rpcclient/errors.go | 497 --------------------------------------- rpcclient/errors_test.go | 121 ---------- 2 files changed, 618 deletions(-) diff --git a/rpcclient/errors.go b/rpcclient/errors.go index 928881f1da..37c46f066c 100644 --- a/rpcclient/errors.go +++ b/rpcclient/errors.go @@ -2,8 +2,6 @@ package rpcclient import ( "errors" - "fmt" - "strings" ) var ( @@ -21,498 +19,3 @@ var ( // error. ErrUndefined = errors.New("undefined") ) - -// BitcoindRPCErr represents an error returned by bitcoind's RPC server. -type BitcoindRPCErr uint32 - -// This section defines all possible errors or reject reasons returned from -// bitcoind's `sendrawtransaction` or `testmempoolaccept` RPC. -const ( - // ErrMissingInputsOrSpent is returned when calling - // `sendrawtransaction` with missing inputs. - ErrMissingInputsOrSpent BitcoindRPCErr = iota - - // ErrMaxBurnExceeded is returned when calling `sendrawtransaction` - // with exceeding, falling short of, and equaling maxburnamount. - ErrMaxBurnExceeded - - // ErrMaxFeeExceeded can happen when passing a signed tx to - // `testmempoolaccept`, but the tx pays more fees than specified. - ErrMaxFeeExceeded - - // ErrTxAlreadyKnown is used in the `reject-reason` field of - // `testmempoolaccept` when a transaction is already in the blockchain. - ErrTxAlreadyKnown - - // ErrTxAlreadyConfirmed is returned as an error from - // `sendrawtransaction` when a transaction is already in the - // blockchain. - ErrTxAlreadyConfirmed - - // ErrMempoolConflict happens when RBF is not enabled yet the - // transaction conflicts with an unconfirmed tx. . - // - // NOTE: RBF rule 1. - ErrMempoolConflict - - // ErrReplacementAddsUnconfirmed is returned when a transaction adds - // new unconfirmed inputs. - // - // NOTE: RBF rule 2. - ErrReplacementAddsUnconfirmed - - // ErrInsufficientFee is returned when fee rate used or fees paid - // doesn't meet the requirements. - // - // NOTE: RBF rule 3 or 4. - ErrInsufficientFee - - // ErrTooManyReplacements is returned when a transaction causes too - // many transactions being replaced. This is set by - // `MAX_REPLACEMENT_CANDIDATES` in `bitcoind` and defaults to 100. - // - // NOTE: RBF rule 5. - ErrTooManyReplacements - - // ErrMempoolMinFeeNotMet is returned when the transaction doesn't meet - // the minimum relay fee. - ErrMempoolMinFeeNotMet - - // ErrConflictingTx is returned when a transaction that spends - // conflicting tx outputs that are rejected. - ErrConflictingTx - - // ErrEmptyOutput is returned when a transaction has no outputs. - ErrEmptyOutput - - // ErrEmptyInput is returned when a transaction has no inputs. - ErrEmptyInput - - // ErrTxTooSmall is returned when spending a tiny transaction(in - // non-witness bytes) that is disallowed. - // - // NOTE: ErrTxTooLarge must be put after ErrTxTooSmall because it's a - // subset of ErrTxTooSmall. Otherwise, if bitcoind returns - // `tx-size-small`, it will be matched to ErrTxTooLarge. - ErrTxTooSmall - - // ErrDuplicateInput is returned when a transaction has duplicate - // inputs. - ErrDuplicateInput - - // ErrEmptyPrevOut is returned when a non-coinbase transaction has - // coinbase-like outpoint. - ErrEmptyPrevOut - - // ErrBelowOutValue is returned when a transaction's output value is - // greater than its input value. - ErrBelowOutValue - - // ErrNegativeOutput is returned when a transaction has negative output - // value. - ErrNegativeOutput - - // ErrLargeOutput is returned when a transaction has too large output - // value. - ErrLargeOutput - - // ErrLargeTotalOutput is returned when a transaction has too large sum - // of output values. - ErrLargeTotalOutput - - // ErrScriptVerifyFlag is returned when there is invalid OP_IF - // construction. - ErrScriptVerifyFlag - - // ErrTooManySigOps is returned when a transaction has too many sigops. - ErrTooManySigOps - - // ErrInvalidOpcode is returned when a transaction has invalid OP - // codes. - ErrInvalidOpcode - - // ErrTxAlreadyInMempool is returned when a transaction is in the - // mempool. - ErrTxAlreadyInMempool - - // ErrMissingInputs is returned when a transaction has missing inputs, - // that never existed or only existed once in the past. - ErrMissingInputs - - // ErrOversizeTx is returned when a transaction is too large. - ErrOversizeTx - - // ErrCoinbaseTx is returned when the transaction is coinbase tx. - ErrCoinbaseTx - - // ErrNonStandardVersion is returned when the transactions are not - // standard - a version currently non-standard. - ErrNonStandardVersion - - // ErrNonStandardScript is returned when the transactions are not - // standard - non-standard script. - ErrNonStandardScript - - // ErrBareMultiSig is returned when the transactions are not standard - - // bare multisig script (2-of-3). - ErrBareMultiSig - - // ErrScriptSigNotPushOnly is returned when the transactions are not - // standard - not-pushonly scriptSig. - ErrScriptSigNotPushOnly - - // ErrScriptSigSize is returned when the transactions are not standard - // - too large scriptSig (>1650 bytes). - ErrScriptSigSize - - // ErrTxTooLarge is returned when the transactions are not standard - - // too large tx size. - ErrTxTooLarge - - // ErrDust is returned when the transactions are not standard - output - // too small. - ErrDust - - // ErrMultiOpReturn is returned when the transactions are not standard - // - muiltiple OP_RETURNs. - ErrMultiOpReturn - - // ErrNonFinal is returned when spending a timelocked transaction that - // hasn't expired yet. - ErrNonFinal - - // ErrNonBIP68Final is returned when a transaction that is locked by - // BIP68 sequence logic and not expired yet. - ErrNonBIP68Final - - // ErrSameNonWitnessData is returned when another tx with the same - // non-witness data is already in the mempool. For instance, these two - // txns share the same `txid` but different `wtxid`. - ErrSameNonWitnessData - - // ErrNonMandatoryScriptVerifyFlag is returned when passing a raw tx to - // `testmempoolaccept`, which gives the error followed by (Witness - // program hash mismatch). - ErrNonMandatoryScriptVerifyFlag - - // errSentinel is used to indicate the end of the error list. This - // should always be the last error code. - errSentinel -) - -// Error implements the error interface. It returns the error message defined -// in `bitcoind`. - -// Some of the dashes used in the original error string is removed, e.g. -// "missing-inputs" is now "missing inputs". This is ok since we will normalize -// the errors before matching. -// -// references: -// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/rpc_rawtransaction.py#L342 -// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/data/invalid_txs.py -// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_accept.py -// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_accept_wtxid.py -// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_dust.py -// - https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_limit.py -// - https://github.com/bitcoin/bitcoin/blob/master/src/validation.cpp -func (r BitcoindRPCErr) Error() string { - switch r { - case ErrMissingInputsOrSpent: - return "bad-txns-inputs-missingorspent" - - case ErrMaxBurnExceeded: - return "Unspendable output exceeds maximum configured by user (maxburnamount)" - - case ErrMaxFeeExceeded: - return "max-fee-exceeded" - - case ErrTxAlreadyKnown: - return "txn-already-known" - - case ErrTxAlreadyConfirmed: - return "Transaction already in block chain" - - case ErrMempoolConflict: - return "txn mempool conflict" - - case ErrReplacementAddsUnconfirmed: - return "replacement adds unconfirmed" - - case ErrInsufficientFee: - return "insufficient fee" - - case ErrTooManyReplacements: - return "too many potential replacements" - - case ErrMempoolMinFeeNotMet: - return "mempool min fee not met" - - case ErrConflictingTx: - return "bad txns spends conflicting tx" - - case ErrEmptyOutput: - return "bad txns vout empty" - - case ErrEmptyInput: - return "bad txns vin empty" - - case ErrTxTooSmall: - return "tx size small" - - case ErrDuplicateInput: - return "bad txns inputs duplicate" - - case ErrEmptyPrevOut: - return "bad txns prevout null" - - case ErrBelowOutValue: - return "bad txns in belowout" - - case ErrNegativeOutput: - return "bad txns vout negative" - - case ErrLargeOutput: - return "bad txns vout toolarge" - - case ErrLargeTotalOutput: - return "bad txns txouttotal toolarge" - - case ErrScriptVerifyFlag: - return "mandatory script verify flag failed" - - case ErrTooManySigOps: - return "bad txns too many sigops" - - case ErrInvalidOpcode: - return "disabled opcode" - - case ErrTxAlreadyInMempool: - return "txn already in mempool" - - case ErrMissingInputs: - return "missing inputs" - - case ErrOversizeTx: - return "bad txns oversize" - - case ErrCoinbaseTx: - return "coinbase" - - case ErrNonStandardVersion: - return "version" - - case ErrNonStandardScript: - return "scriptpubkey" - - case ErrBareMultiSig: - return "bare multisig" - - case ErrScriptSigNotPushOnly: - return "scriptsig not pushonly" - - case ErrScriptSigSize: - return "scriptsig size" - - case ErrTxTooLarge: - return "tx size" - - case ErrDust: - return "dust" - - case ErrMultiOpReturn: - return "multi op return" - - case ErrNonFinal: - return "non final" - - case ErrNonBIP68Final: - return "non BIP68 final" - - case ErrSameNonWitnessData: - return "txn-same-nonwitness-data-in-mempool" - - case ErrNonMandatoryScriptVerifyFlag: - return "non-mandatory-script-verify-flag" - } - - return "unknown error" -} - -// BtcdErrMap takes the errors returned from btcd's `testmempoolaccept` and -// `sendrawtransaction` RPCs and map them to the errors defined above, which -// are results from calling either `testmempoolaccept` or `sendrawtransaction` -// in `bitcoind`. -// -// Errors not mapped in `btcd`: -// - deployment error from `validateSegWitDeployment`. -// - the error when total inputs is higher than max allowed value from -// `CheckTransactionInputs`. -// - the error when total outputs is higher than total inputs from -// `CheckTransactionInputs`. -// - errors from `CalcSequenceLock`. -// -// NOTE: This is not an exhaustive list of errors, but it covers the -// usage case of LND. -// -//nolint:lll -var BtcdErrMap = map[string]error{ - // BIP125 related errors. - // - // When fee rate used or fees paid doesn't meet the requirements. - "replacement transaction has an insufficient fee rate": ErrInsufficientFee, - "replacement transaction has an insufficient absolute fee": ErrInsufficientFee, - - // When a transaction causes too many transactions being replaced. This - // is set by `MAX_REPLACEMENT_CANDIDATES` in `bitcoind` and defaults to - // 100. - "replacement transaction evicts more transactions than permitted": ErrTooManyReplacements, - - // When a transaction adds new unconfirmed inputs. - "replacement transaction spends new unconfirmed input": ErrReplacementAddsUnconfirmed, - - // A transaction that spends conflicting tx outputs that are rejected. - "replacement transaction spends parent transaction": ErrConflictingTx, - - // A transaction that conflicts with an unconfirmed tx. Happens when - // RBF is not enabled. - "output already spent in mempool": ErrMempoolConflict, - - // A transaction with no outputs. - "transaction has no outputs": ErrEmptyOutput, - - // A transaction with no inputs. - "transaction has no inputs": ErrEmptyInput, - - // A transaction with duplicate inputs. - "transaction contains duplicate inputs": ErrDuplicateInput, - - // A non-coinbase transaction with coinbase-like outpoint. - "transaction input refers to previous output that is null": ErrEmptyPrevOut, - - // A transaction pays too little fee. - "fees which is under the required amount": ErrMempoolMinFeeNotMet, - "has insufficient priority": ErrInsufficientFee, - "has been rejected by the rate limiter due to low fees": ErrInsufficientFee, - - // A transaction with negative output value. - "transaction output has negative value": ErrNegativeOutput, - - // A transaction with too large output value. - "transaction output value is higher than max allowed value": ErrLargeOutput, - - // A transaction with too large sum of output values. - "total value of all transaction outputs exceeds max allowed value": ErrLargeTotalOutput, - - // A transaction with too many sigops. - "sigop cost is too hight": ErrTooManySigOps, - - // A transaction already in the blockchain. - "database contains entry for spent tx output": ErrTxAlreadyKnown, - "transaction already exists in blockchain": ErrTxAlreadyConfirmed, - - // A transaction in the mempool. - // - // NOTE: For btcd v0.24.2 and beyond, the error message is "already - // have transaction in mempool". - "already have transaction": ErrTxAlreadyInMempool, - - // A transaction with missing inputs, that never existed or only - // existed once in the past. - "either does not exist or has already been spent": ErrMissingInputs, - "orphan transaction": ErrMissingInputs, - - // A really large transaction. - "serialized transaction is too big": ErrOversizeTx, - - // A coinbase transaction. - "transaction is an invalid coinbase": ErrCoinbaseTx, - - // Some nonstandard transactions - a version currently non-standard. - "transaction version": ErrNonStandardVersion, - - // Some nonstandard transactions - non-standard script. - "non-standard script form": ErrNonStandardScript, - "has a non-standard input": ErrNonStandardScript, - - // Some nonstandard transactions - bare multisig script - // (2-of-3). - "milti-signature script": ErrBareMultiSig, - - // Some nonstandard transactions - not-pushonly scriptSig. - "signature script is not push only": ErrScriptSigNotPushOnly, - - // Some nonstandard transactions - too large scriptSig (>1650 - // bytes). - "signature script size is larger than max allowed": ErrScriptSigSize, - - // Some nonstandard transactions - too large tx size. - "weight of transaction is larger than max allowed": ErrTxTooLarge, - - // Some nonstandard transactions - output too small. - "payment is dust": ErrDust, - - // Some nonstandard transactions - muiltiple OP_RETURNs. - "more than one transaction output in a nulldata script": ErrMultiOpReturn, - - // A timelocked transaction. - "transaction is not finalized": ErrNonFinal, - "tried to spend coinbase transaction output": ErrNonFinal, - - // A transaction that is locked by BIP68 sequence logic. - "transaction's sequence locks on inputs not met": ErrNonBIP68Final, - - // TODO(yy): find/return the following errors in `btcd`. - // - // A tiny transaction(in non-witness bytes) that is disallowed. - // "unmatched btcd error 1": ErrTxTooSmall, - // "unmatched btcd error 2": ErrScriptVerifyFlag, - // // A transaction with invalid OP codes. - // "unmatched btcd error 3": ErrInvalidOpcode, - // // Minimally-small transaction(in non-witness bytes) that is - // // allowed. - // "unmatched btcd error 4": ErrSameNonWitnessData, -} - -// 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` -// defined above, 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 MapRPCErr(rpcErr error) error { - // Iterate the map and find the matching error. - for btcdErr, err := range BtcdErrMap { - // Match it against btcd's error first. - if matchErrStr(rpcErr, btcdErr) { - return err - } - } - - // If not found, try to match it against bitcoind's error. - for i := uint32(0); i < uint32(errSentinel); i++ { - err := BitcoindRPCErr(i) - if matchErrStr(rpcErr, err.Error()) { - return err - } - } - - // If not matched, return the original error wrapped. - return fmt.Errorf("%w: %v", ErrUndefined, rpcErr) -} - -// matchErrStr takes an error returned from RPC client and matches it against -// the specified string. If the expected string pattern is found in the error -// passed, return true. Both the error strings are normalized before matching. -func matchErrStr(err error, s string) bool { - // Replace all dashes found in the error string with spaces. - strippedErrStr := strings.ReplaceAll(err.Error(), "-", " ") - - // Replace all dashes found in the error string with spaces. - strippedMatchStr := strings.ReplaceAll(s, "-", " ") - - // Match against the lowercase. - return strings.Contains( - strings.ToLower(strippedErrStr), - strings.ToLower(strippedMatchStr), - ) -} diff --git a/rpcclient/errors_test.go b/rpcclient/errors_test.go index e074622b11..46bd2aa21c 100644 --- a/rpcclient/errors_test.go +++ b/rpcclient/errors_test.go @@ -1,122 +1 @@ package rpcclient - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestMatchErrStr checks that `matchErrStr` can correctly replace the dashes -// with spaces and turn title cases into lowercases for a given error and match -// it against the specified string pattern. -func TestMatchErrStr(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - bitcoindErr error - matchStr string - matched bool - }{ - { - name: "error without dashes", - bitcoindErr: errors.New("missing input"), - matchStr: "missing input", - matched: true, - }, - { - name: "match str without dashes", - bitcoindErr: errors.New("missing-input"), - matchStr: "missing input", - matched: true, - }, - { - name: "error with dashes", - bitcoindErr: errors.New("missing-input"), - matchStr: "missing input", - matched: true, - }, - { - name: "match str with dashes", - bitcoindErr: errors.New("missing-input"), - matchStr: "missing-input", - matched: true, - }, - { - name: "error with title case and dash", - bitcoindErr: errors.New("Missing-Input"), - matchStr: "missing input", - matched: true, - }, - { - name: "match str with title case and dash", - bitcoindErr: errors.New("missing-input"), - matchStr: "Missing-Input", - matched: true, - }, - { - name: "unmatched error", - bitcoindErr: errors.New("missing input"), - matchStr: "missingorspent", - matched: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - matched := matchErrStr(tc.bitcoindErr, tc.matchStr) - require.Equal(t, tc.matched, matched) - }) - } -} - -// TestMapRPCErr checks that `MapRPCErr` can correctly map a given error to -// the corresponding error in the `BtcdErrMap` or `BitcoindErrors` map. -func TestMapRPCErr(t *testing.T) { - t.Parallel() - - require := require.New(t) - - // Get all known bitcoind errors. - bitcoindErrors := make([]error, 0, errSentinel) - for i := uint32(0); i < uint32(errSentinel); i++ { - err := BitcoindRPCErr(i) - bitcoindErrors = append(bitcoindErrors, err) - } - - // An unknown error should be mapped to ErrUndefined. - errUnknown := errors.New("unknown error") - err := MapRPCErr(errUnknown) - require.ErrorIs(err, ErrUndefined) - - // A known error should be mapped to the corresponding error in the - // `BtcdErrMap` or `bitcoindErrors` map. - for btcdErrStr, mappedErr := range BtcdErrMap { - err := MapRPCErr(errors.New(btcdErrStr)) - require.ErrorIs(err, mappedErr) - - err = MapRPCErr(mappedErr) - require.ErrorIs(err, mappedErr) - } - - for _, bitcoindErr := range bitcoindErrors { - err = MapRPCErr(bitcoindErr) - require.ErrorIs(err, bitcoindErr) - } -} - -// TestBitcoindErrorSentinel checks that all defined BitcoindRPCErr errors are -// added to the method `Error`. -func TestBitcoindErrorSentinel(t *testing.T) { - t.Parallel() - - rt := require.New(t) - - for i := uint32(0); i < uint32(errSentinel); i++ { - err := BitcoindRPCErr(i) - rt.NotEqualf(err.Error(), "unknown error", "error code %d is "+ - "not defined, make sure to update it inside the Error "+ - "method", i) - } -}