diff --git a/src/net_processing.cpp b/src/net_processing.cpp index e09b56a6318..a5b1ea50475 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1807,7 +1807,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr std::list lRemovedTxn; - if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, true, &fMissingInputs, &lRemovedTxn)) { + if (!AlreadyHave(inv) && AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn)) { mempool.check(pcoinsTip); RelayTransaction(tx, connman); for (unsigned int i = 0; i < tx.vout.size(); i++) { @@ -1845,7 +1845,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (setMisbehaving.count(fromPeer)) continue; - if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, true, &fMissingInputs2, &lRemovedTxn)) { + if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, &fMissingInputs2, &lRemovedTxn)) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); RelayTransaction(orphanTx, connman); for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 605e3e06968..4d670373f2a 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -69,6 +69,9 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) * script can be anything; an attacker could use a very * expensive-to-check-upon-redemption script like: * DUP CHECKSIG DROP ... repeated 100 times... OP_1 + * + * Note this must assign whichType even if returning false, in case + * IsStandardTx ignores the "scriptpubkey" rejection. */ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled) @@ -96,71 +99,94 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool w return whichType != TX_NONSTANDARD; } -bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled) -{ - if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) { - reason = "version"; - return false; +namespace { + inline bool MaybeReject_(std::string& out_reason, const std::string& reason, const std::string& reason_prefix, const ignore_rejects_type& ignore_rejects) { + if (ignore_rejects.count(reason_prefix + reason)) { + return false; + } + + out_reason = reason_prefix + reason; + return true; } +} - // Extremely large transactions with lots of inputs can cost the network - // almost as much to process as they cost the sender in fees, because - // computing signature hashes is O(ninputs*txsize). Limiting transactions - // to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks. - unsigned int sz = GetTransactionWeight(tx); - if (sz >= MAX_STANDARD_TX_WEIGHT) { - reason = "tx-size"; - return false; +#define MaybeReject(reason) do { \ + if (MaybeReject_(out_reason, reason, reason_prefix, ignore_rejects)) { \ + return false; \ + } \ +} while(0) + +bool IsStandardTx(const CTransaction& tx, std::string& out_reason, const bool witnessEnabled, const ignore_rejects_type& ignore_rejects) +{ + const std::string reason_prefix; + + if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) { + MaybeReject("version"); } - for (const CTxIn& txin : tx.vin) - { - // Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed - // keys (remember the 520 byte limit on redeemScript size). That works - // out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627 - // bytes of scriptSig, which we round off to 1650 bytes for some minor - // future-proofing. That's also enough to spend a 20-of-20 - // CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not - // considered standard. - if (txin.scriptSig.size() > 1650) { - reason = "scriptsig-size"; - return false; - } - if (!txin.scriptSig.IsPushOnly()) { - reason = "scriptsig-not-pushonly"; + if (!ignore_rejects.count("tx-size")) { + // Extremely large transactions with lots of inputs can cost the network + // almost as much to process as they cost the sender in fees, because + // computing signature hashes is O(ninputs*txsize). Limiting transactions + // to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks. + unsigned int sz = GetTransactionWeight(tx); + if (sz >= MAX_STANDARD_TX_WEIGHT) { + out_reason = "tx-size"; return false; } } - unsigned int nDataOut = 0; - txnouttype whichType; - for (const CTxOut& txout : tx.vout) { - if (!::IsStandard(txout.scriptPubKey, whichType, witnessEnabled)) { - reason = "scriptpubkey"; - return false; + bool fCheckPushOnly = !ignore_rejects.count("scriptsig-not-pushonly"); + if ((!ignore_rejects.count("scriptsig-size")) || fCheckPushOnly) { + for (const CTxIn& txin : tx.vin) + { + // Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed + // keys (remember the 520 byte limit on redeemScript size). That works + // out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627 + // bytes of scriptSig, which we round off to 1650 bytes for some minor + // future-proofing. That's also enough to spend a 20-of-20 + // CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not + // considered standard. + if (txin.scriptSig.size() > 1650) { + MaybeReject("scriptsig-size"); + } + if (fCheckPushOnly && !txin.scriptSig.IsPushOnly()) { + out_reason = "scriptsig-not-pushonly"; + return false; + } } + } - if (whichType == TX_NULL_DATA) - nDataOut++; - else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { - reason = "bare-multisig"; - return false; - } else if (IsDust(txout, ::dustRelayFee)) { - reason = "dust"; - return false; + if (!(ignore_rejects.count("scriptpubkey") && ignore_rejects.count("bare-multisig") && ignore_rejects.count("dust") && ignore_rejects.count("multi-op-return"))) { + unsigned int nDataOut = 0; + txnouttype whichType; + for (const CTxOut& txout : tx.vout) { + if (!::IsStandard(txout.scriptPubKey, whichType, witnessEnabled)) { + MaybeReject("scriptpubkey"); + } + + if (whichType == TX_NULL_DATA) + nDataOut++; + else { + if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { + MaybeReject("bare-multisig"); + } + if (IsDust(txout, ::dustRelayFee)) { + MaybeReject("dust"); + } + } } - } - // only one OP_RETURN txout is permitted - if (nDataOut > 1) { - reason = "multi-op-return"; - return false; + // only one OP_RETURN txout is permitted + if (nDataOut > 1) { + MaybeReject("multi-op-return"); + } } return true; } -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) +bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects) { if (tx.IsCoinBase()) return true; // Coinbases don't use vin normally @@ -173,20 +199,33 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) txnouttype whichType; // get the scriptPubKey corresponding to this input: const CScript& prevScript = prev.scriptPubKey; - if (!Solver(prevScript, whichType, vSolutions)) - return false; + if (!Solver(prevScript, whichType, vSolutions)) { + MaybeReject("script-unknown"); + } if (whichType == TX_SCRIPTHASH) { + if (!tx.vin[i].scriptSig.IsPushOnly()) { + // The only way we got this far, is if the user ignored scriptsig-not-pushonly. + // However, this case is invalid, and will be caught later on. + // But for now, we don't want to run the [possibly expensive] script here. + continue; + } std::vector > stack; // convert the scriptSig into a stack, so we can inspect the redeemScript - if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE)) + if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE)) { + // This case is also invalid or a bug + out_reason = reason_prefix + "scriptsig-failure"; return false; - if (stack.empty()) + } + if (stack.empty()) { + // Also invalid + out_reason = reason_prefix + "scriptcheck-missing"; return false; + } CScript subscript(stack.back().begin(), stack.back().end()); if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) { - return false; + MaybeReject("scriptcheck-sigops"); } } } @@ -194,7 +233,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) return true; } -bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) +bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects) { if (tx.IsCoinBase()) return true; // Coinbases are skipped @@ -217,9 +256,15 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // into a stack. We do not check IsPushOnly nor compare the hash as these will be done later anyway. // If the check fails at this stage, we know that this txid must be a bad one. if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE)) + { + out_reason = reason_prefix + "scriptsig-failure"; return false; + } if (stack.empty()) + { + out_reason = reason_prefix + "scriptcheck-missing"; return false; + } prevScript = CScript(stack.back().begin(), stack.back().end()); } @@ -228,18 +273,27 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // Non-witness program must not be associated with any witness if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram)) + { + out_reason = reason_prefix + "nonwitness-input"; return false; + } // Check P2WSH standard limits if (witnessversion == 0 && witnessprogram.size() == 32) { if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) - return false; + { + MaybeReject("script-size"); + } size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1; if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS) - return false; + { + MaybeReject("stackitem-count"); + } for (unsigned int j = 0; j < sizeWitnessStack; j++) { if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE) - return false; + { + MaybeReject("stackitem-size"); + } } } } diff --git a/src/policy/policy.h b/src/policy/policy.h index c06820f84e6..19018cef183 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -12,6 +12,7 @@ #include "script/standard.h" #include +#include class CCoinsViewCache; class CTxOut; @@ -74,6 +75,9 @@ static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_ static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | LOCKTIME_MEDIAN_TIME_PAST; +typedef std::unordered_set ignore_rejects_type; +static const ignore_rejects_type empty_ignore_rejects; + CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee); @@ -83,19 +87,25 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool w * Check for standard transaction types * @return True if all outputs (scriptPubKeys) use only standard transaction forms */ -bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled = false); +bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled = false, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects); /** * Check for standard transaction types * @param[in] mapInputs Map of previous transactions that have outputs we're spending * @return True if all inputs (scriptSigs) use only standard transaction forms */ -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); +bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects); + +inline bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) { + std::string reason; + return AreInputsStandard(tx, mapInputs, reason, reason); +} + /** * Check if the transaction is over standard P2WSH resources limit: * 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements * These limits are adequate for multi-signature up to n-of-100 using OP_CHECKSIG, OP_ADD, and OP_EQUAL, */ -bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); +bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects); extern CFeeRate incrementalRelayFee; extern CFeeRate dustRelayFee; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 41794537824..180f81d623d 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -95,6 +95,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createrawtransaction", 3, "replaceable" }, { "signrawtransaction", 1, "prevtxs" }, { "signrawtransaction", 2, "privkeys" }, + { "sendrawtransaction", 1, "ignore_rejects" }, { "sendrawtransaction", 1, "allowhighfees" }, { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 81005118541..52e813eebba 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -892,12 +892,12 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "sendrawtransaction \"hexstring\" ( allowhighfees )\n" + "sendrawtransaction \"hexstring\" ( [\"ignore_reject\",...] )\n" "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n" "\nAlso see createrawtransaction and signrawtransaction calls.\n" "\nArguments:\n" "1. \"hexstring\" (string, required) The hex string of the raw transaction)\n" - "2. allowhighfees (boolean, optional, default=false) Allow high fees\n" + "2. \"ignore_reject\" (string, optional) Rejection conditions to ignore, eg 'absurdly-high-fee'\n" "\nResult:\n" "\"hex\" (string) The transaction hash in hex\n" "\nExamples:\n" @@ -912,7 +912,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) ); LOCK(cs_main); - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); + RPCTypeCheck(request.params, {UniValue::VSTR}); // parse hex string from parameter CMutableTransaction mtx; @@ -922,8 +922,24 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) const uint256& hashTx = tx->GetHash(); CAmount nMaxRawTxFee = maxTxFee; - if (request.params.size() > 1 && request.params[1].get_bool()) - nMaxRawTxFee = 0; + + ignore_rejects_type ignore_rejects; + const UniValue& json_ign_rejs = request.params[1]; + if (!json_ign_rejs.isNull()) { + if (json_ign_rejs.isBool()) { + // This parameter used to be boolean allowhighfees + if (json_ign_rejs.isTrue()) { + ignore_rejects.insert(rejectmsg_absurdfee); + } + } else { + RPCTypeCheckArgument(json_ign_rejs, UniValue::VARR); + + for (size_t i = 0; i < json_ign_rejs.size(); ++i) { + const UniValue& json_ign_rej = json_ign_rejs[i]; + ignore_rejects.insert(json_ign_rej.get_str()); + } + } + } CCoinsViewCache &view = *pcoinsTip; bool fHaveChain = false; @@ -936,8 +952,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) // push to local node and sync with wallets CValidationState state; bool fMissingInputs; - bool fLimitFree = true; - if (!AcceptToMemoryPool(mempool, state, std::move(tx), fLimitFree, &fMissingInputs, nullptr, false, nMaxRawTxFee)) { + if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, nullptr, nMaxRawTxFee, ignore_rejects)) { if (state.IsInvalid()) { throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason())); } else { @@ -968,7 +983,7 @@ static const CRPCCommand commands[] = { "rawtransactions", "createrawtransaction", &createrawtransaction, true, {"inputs","outputs","locktime","replaceable"} }, { "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} }, { "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} }, - { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} }, + { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","ignore_rejects|allowhighfees"} }, { "rawtransactions", "combinerawtransaction", &combinerawtransaction, true, {"txs"} }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 2d25cb96c80..424c4310a15 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -29,7 +29,7 @@ ToMemPool(CMutableTransaction& tx) LOCK(cs_main); CValidationState state; - return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), false, nullptr, nullptr, true, 0); + return AcceptToMemoryPool(mempool, state, MakeTransactionRef(tx), nullptr, nullptr, 0, ignore_rejects_type({rejectmsg_mempoolfull, rejectmsg_gratis})); } BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup) diff --git a/src/validation.cpp b/src/validation.cpp index 8bd23a0f1d7..c8b6efd4bd6 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -378,7 +378,7 @@ void UpdateMempoolForReorg(DisconnectedBlockTransactions &disconnectpool, bool f while (it != disconnectpool.queuedTx.get().rend()) { // ignore validation errors in resurrected transactions CValidationState stateDummy; - if (!fAddToMempool || (*it)->IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, *it, false, nullptr, nullptr, true)) { + if (!fAddToMempool || (*it)->IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, *it, nullptr, nullptr, 0, ignore_rejects_type({rejectmsg_mempoolfull, rejectmsg_gratis}))) { // If the transaction doesn't make it in to the mempool, remove any // transactions that depend on it (which would now be orphans). mempool.removeRecursive(**it, MemPoolRemovalReason::REORG); @@ -437,9 +437,28 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt return CheckInputs(tx, state, view, true, flags, cacheSigStore, true, txdata); } -static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree, +namespace { + inline bool MaybeReject_(unsigned int reject_code, const std::string& reason, bool corruption_possible, const std::string& debug_msg, const ignore_rejects_type& ignore_rejects, CValidationState& state) { + if (ignore_rejects.count(reason)) { + return false; + } + + return state.DoS(0, true, reject_code, reason, corruption_possible, debug_msg); + } +} + +#define MaybeRejectDbg(reject_code, reason, corruption_possible, debug_msg) do { \ + if (MaybeReject_(reject_code, reason, corruption_possible, debug_msg, ignore_rejects, state)) { \ + return false; \ + } \ +} while(0) + +#define MaybeReject(reject_code, reason) MaybeRejectDbg(reject_code, reason, false, "") + +static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool* pfMissingInputs, int64_t nAcceptTime, std::list* plTxnReplaced, - bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector& coins_to_uncache) + const CAmount& nAbsurdFee, const ignore_rejects_type& ignore_rejects, + std::vector& coins_to_uncache) { const CTransaction& tx = *ptx; const uint256 hash = tx.GetHash(); @@ -457,19 +476,20 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Reject transactions with witness before segregated witness activates (override with -prematurewitness) bool witnessEnabled = IsWitnessEnabled(chainActive.Tip(), chainparams.GetConsensus()); if (!gArgs.GetBoolArg("-prematurewitness", false) && tx.HasWitness() && !witnessEnabled) { - return state.DoS(0, false, REJECT_NONSTANDARD, "no-witness-yet", true); + MaybeRejectDbg(REJECT_NONSTANDARD, "no-witness-yet", true, ""); } // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; - if (fRequireStandard && !IsStandardTx(tx, reason, witnessEnabled)) + if (fRequireStandard && !IsStandardTx(tx, reason, witnessEnabled, ignore_rejects)) { return state.DoS(0, false, REJECT_NONSTANDARD, reason); + } // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. if (!CheckFinalTx(tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) - return state.DoS(0, false, REJECT_NONSTANDARD, "non-final"); + MaybeReject(REJECT_NONSTANDARD, "non-final"); // is it already in the memory pool? if (pool.exists(hash)) { @@ -488,6 +508,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool const CTransaction *ptxConflicting = itConflicting->second; if (!setConflicts.count(ptxConflicting->GetHash())) { + if (!ignore_rejects.count("txn-mempool-conflict")) { + // Allow opt-out of transaction replacement by setting // nSequence > MAX_BIP125_RBF_SEQUENCE (SEQUENCE_FINAL-2) on all inputs. // @@ -516,6 +538,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool return state.Invalid(false, REJECT_DUPLICATE, "txn-mempool-conflict"); } + } // ignore_rejects + setConflicts.insert(ptxConflicting->GetHash()); } } @@ -567,17 +591,20 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // be mined yet. // Must keep pool.cs for this unless we change CheckSequenceLocks to take a // CoinsViewCache instead of create its own + // NOTE: The miner doesn't check this again, so for now it may not be overridden. if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); } // Check for non-standard pay-to-script-hash in inputs - if (fRequireStandard && !AreInputsStandard(tx, view)) - return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); + if (fRequireStandard && !AreInputsStandard(tx, view, "bad-txns-input-", reason, ignore_rejects)) { + return state.Invalid(false, REJECT_NONSTANDARD, reason); + } // Check for non-standard witness in P2WSH - if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, view)) - return state.DoS(0, false, REJECT_NONSTANDARD, "bad-witness-nonstandard", true); + if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, view, "bad-witness-", reason, ignore_rejects)) { + return state.DoS(0, false, REJECT_NONSTANDARD, reason, true); + } int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS); @@ -608,23 +635,21 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // MAX_BLOCK_SIGOPS; we still consider this an invalid rather than // merely non-standard transaction. if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) - return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false, + MaybeRejectDbg(REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false, strprintf("%d", nSigOpsCost)); CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize); if (mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) { - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nFees, mempoolRejectFee)); + MaybeRejectDbg(REJECT_INSUFFICIENTFEE, "fee-too-low-for-mempool", false, strprintf("%d < %d", nFees, mempoolRejectFee)); } // No transactions are allowed below minRelayTxFee except from disconnected blocks - if (fLimitFree && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) { - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "min relay fee not met"); + if (nModifiedFees < ::minRelayTxFee.GetFee(nSize)) { + MaybeReject(REJECT_INSUFFICIENTFEE, rejectmsg_gratis); } if (nAbsurdFee && nFees > nAbsurdFee) - return state.Invalid(false, - REJECT_HIGHFEE, "absurdly-high-fee", - strprintf("%d > %d", nFees, nAbsurdFee)); + MaybeRejectDbg(REJECT_HIGHFEE, rejectmsg_absurdfee, false, strprintf("%d > %d", nFees, nAbsurdFee)); // Calculate in-mempool ancestors, up to a limit. CTxMemPool::setEntries setAncestors; @@ -632,6 +657,9 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000; size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; + if (ignore_rejects.count("too-long-mempool-chain")) { + nLimitAncestors = nLimitAncestorSize = nLimitDescendants = nLimitDescendantSize = std::numeric_limits::max(); + } std::string errString; if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString); @@ -700,8 +728,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize()); if (newFeeRate <= oldFeeRate) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + MaybeRejectDbg( + REJECT_INSUFFICIENTFEE, "fee-too-low-for-replacement", false, strprintf("rejecting replacement %s; new feerate %s <= old feerate %s", hash.ToString(), newFeeRate.ToString(), @@ -718,7 +746,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // This potentially overestimates the number of actual descendants // but we just want to be conservative to avoid doing too much // work. - if (nConflictingCount <= maxDescendantsToVisit) { + if (nConflictingCount <= maxDescendantsToVisit || ignore_rejects.count("too-many-replacements")) { // If not too many to replace, then calculate the set of // transactions that would have to be evicted for (CTxMemPool::txiter it : setIterConflicting) { @@ -730,13 +758,15 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } } else { return state.DoS(0, false, - REJECT_NONSTANDARD, "too many potential replacements", false, + REJECT_NONSTANDARD, "too-many-replacements", false, strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n", hash.ToString(), nConflictingCount, maxDescendantsToVisit)); } + if (!ignore_rejects.count("replacement-adds-unconfirmed")) { + for (unsigned int j = 0; j < tx.vin.size(); j++) { // We don't want to accept replacements that require low @@ -756,13 +786,15 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } } + } // ignore_rejects + // The replacement must pay greater fees than the transactions it // replaces - if we did the bandwidth used by those conflicting // transactions would not be paid for. if (nModifiedFees < nConflictingFees) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + MaybeRejectDbg( + REJECT_INSUFFICIENTFEE, "fee-too-low-for-replacement", false, strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s", hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees))); } @@ -772,8 +804,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CAmount nDeltaFees = nModifiedFees - nConflictingFees; if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize)) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + MaybeRejectDbg( + REJECT_INSUFFICIENTFEE, "fee-too-low-for-replacement", false, strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s", hash.ToString(), FormatMoney(nDeltaFees), @@ -859,10 +891,10 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool pool.addUnchecked(hash, entry, setAncestors, validForFeeEstimation); // trim mempool and check if tx was trimmed - if (!fOverrideMempoolLimit) { + if (!ignore_rejects.count(rejectmsg_mempoolfull)) { LimitMempoolSize(pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60); if (!pool.exists(hash)) - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool full"); + return state.DoS(0, false, REJECT_INSUFFICIENTFEE, rejectmsg_mempoolfull); } } @@ -872,12 +904,12 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } /** (try to) add transaction to memory pool with a specified acceptance time **/ -static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree, +static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool* pfMissingInputs, int64_t nAcceptTime, std::list* plTxnReplaced, - bool fOverrideMempoolLimit, const CAmount nAbsurdFee) + const CAmount nAbsurdFee, const ignore_rejects_type& ignore_rejects) { std::vector coins_to_uncache; - bool res = AcceptToMemoryPoolWorker(chainparams, pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache); + bool res = AcceptToMemoryPoolWorker(chainparams, pool, state, tx, pfMissingInputs, nAcceptTime, plTxnReplaced, nAbsurdFee, ignore_rejects, coins_to_uncache); if (!res) { for (const COutPoint& hashTx : coins_to_uncache) pcoinsTip->Uncache(hashTx); @@ -888,12 +920,12 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo return res; } -bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree, +bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool* pfMissingInputs, std::list* plTxnReplaced, - bool fOverrideMempoolLimit, const CAmount nAbsurdFee) + const CAmount nAbsurdFee, const ignore_rejects_type& ignore_rejects) { const CChainParams& chainparams = Params(); - return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, fLimitFree, pfMissingInputs, GetTime(), plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee); + return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, pfMissingInputs, GetTime(), plTxnReplaced, nAbsurdFee, ignore_rejects); } /** Return transaction in txOut, and if it was found inside a block, its hash is placed in hashBlock */ @@ -4299,7 +4331,7 @@ bool LoadMempool(void) CValidationState state; if (nTime + nExpiryTimeout > nNow) { LOCK(cs_main); - AcceptToMemoryPoolWithTime(chainparams, mempool, state, tx, true, nullptr, nTime, nullptr, false, 0); + AcceptToMemoryPoolWithTime(chainparams, mempool, state, tx, nullptr, nTime, nullptr, 0, empty_ignore_rejects); if (state.IsValid()) { ++count; } else { diff --git a/src/validation.h b/src/validation.h index edb8eab894b..630acf805dd 100644 --- a/src/validation.h +++ b/src/validation.h @@ -15,6 +15,7 @@ #include "fs.h" #include "protocol.h" // For CMessageHeader::MessageStartChars #include "policy/feerate.h" +#include "policy/policy.h" #include "script/script_error.h" #include "sync.h" #include "versionbits.h" @@ -299,9 +300,13 @@ void PruneBlockFilesManual(int nManualPruneHeight); /** (try to) add transaction to memory pool * plTxnReplaced will be appended to with all transactions replaced from mempool **/ -bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool fLimitFree, +bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx, bool* pfMissingInputs, std::list* plTxnReplaced = nullptr, - bool fOverrideMempoolLimit=false, const CAmount nAbsurdFee=0); + const CAmount nAbsurdFee=0, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects); + +static const std::string rejectmsg_absurdfee = "absurdly-high-fee"; +static const std::string rejectmsg_gratis = "fee-too-low-for-relay"; +static const std::string rejectmsg_mempoolfull = "fee-too-low-for-mempool-full"; /** Convert CValidationState to a human-readable message for logging */ std::string FormatStateMessage(const CValidationState &state); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 599e74149c5..ef4756f0199 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4333,7 +4333,7 @@ int CMerkleTx::GetBlocksToMaturity() const } -bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) +bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state, const ignore_rejects_type& ignore_rejects) { - return ::AcceptToMemoryPool(mempool, state, tx, true, nullptr, nullptr, false, nAbsurdFee); + return ::AcceptToMemoryPool(mempool, state, tx, nullptr, nullptr, nAbsurdFee, ignore_rejects); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f97a99d82a1..9573afea285 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -8,6 +8,7 @@ #include "amount.h" #include "policy/feerate.h" +#include "policy/policy.h" #include "streams.h" #include "tinyformat.h" #include "ui_interface.h" @@ -253,7 +254,7 @@ class CMerkleTx bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; } int GetBlocksToMaturity() const; /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ - bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state); + bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects); bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } void setAbandoned() { hashBlock = ABANDON_HASH; } diff --git a/test/functional/p2p-segwit.py b/test/functional/p2p-segwit.py index 63dfbb8ae6e..980373ac26f 100755 --- a/test/functional/p2p-segwit.py +++ b/test/functional/p2p-segwit.py @@ -1819,13 +1819,13 @@ def test_non_standard_witness(self): # Testing native P2WSH # Witness stack size, excluding witnessScript, over 100 is non-standard p2wsh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] - self.std_node.test_transaction_acceptance(p2wsh_txs[0], True, False, b'bad-witness-nonstandard') + self.std_node.test_transaction_acceptance(p2wsh_txs[0], True, False, b'bad-witness-stackitem-count') # Non-standard nodes should accept self.test_node.test_transaction_acceptance(p2wsh_txs[0], True, True) # Stack element size over 80 bytes is non-standard p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] - self.std_node.test_transaction_acceptance(p2wsh_txs[1], True, False, b'bad-witness-nonstandard') + self.std_node.test_transaction_acceptance(p2wsh_txs[1], True, False, b'bad-witness-stackitem-size') # Non-standard nodes should accept self.test_node.test_transaction_acceptance(p2wsh_txs[1], True, True) # Standard nodes should accept if element size is not over 80 bytes @@ -1839,16 +1839,16 @@ def test_non_standard_witness(self): # witnessScript size at 3601 bytes is non-standard p2wsh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] - self.std_node.test_transaction_acceptance(p2wsh_txs[3], True, False, b'bad-witness-nonstandard') + self.std_node.test_transaction_acceptance(p2wsh_txs[3], True, False, b'bad-witness-script-size') # Non-standard nodes should accept self.test_node.test_transaction_acceptance(p2wsh_txs[3], True, True) # Repeating the same tests with P2SH-P2WSH p2sh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] - self.std_node.test_transaction_acceptance(p2sh_txs[0], True, False, b'bad-witness-nonstandard') + self.std_node.test_transaction_acceptance(p2sh_txs[0], True, False, b'bad-witness-stackitem-count') self.test_node.test_transaction_acceptance(p2sh_txs[0], True, True) p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] - self.std_node.test_transaction_acceptance(p2sh_txs[1], True, False, b'bad-witness-nonstandard') + self.std_node.test_transaction_acceptance(p2sh_txs[1], True, False, b'bad-witness-stackitem-size') self.test_node.test_transaction_acceptance(p2sh_txs[1], True, True) p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]] self.std_node.test_transaction_acceptance(p2sh_txs[1], True, True) @@ -1856,7 +1856,7 @@ def test_non_standard_witness(self): self.test_node.test_transaction_acceptance(p2sh_txs[2], True, True) self.std_node.test_transaction_acceptance(p2sh_txs[2], True, True) p2sh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] - self.std_node.test_transaction_acceptance(p2sh_txs[3], True, False, b'bad-witness-nonstandard') + self.std_node.test_transaction_acceptance(p2sh_txs[3], True, False, b'bad-witness-script-size') self.test_node.test_transaction_acceptance(p2sh_txs[3], True, True) self.nodes[0].generate(1) # Mine and clean up the mempool of non-standard node diff --git a/test/functional/prioritise_transaction.py b/test/functional/prioritise_transaction.py index 4fc03d25474..34fe4634e48 100755 --- a/test/functional/prioritise_transaction.py +++ b/test/functional/prioritise_transaction.py @@ -103,7 +103,7 @@ def run_test(self): tx_id = self.nodes[0].decoderawtransaction(tx_hex)["txid"] # This will raise an exception due to min relay fee not being met - assert_raises_jsonrpc(-26, "66: min relay fee not met", self.nodes[0].sendrawtransaction, tx_hex) + assert_raises_jsonrpc(-26, "66: fee-too-low-for-relay", self.nodes[0].sendrawtransaction, tx_hex) assert(tx_id not in self.nodes[0].getrawmempool()) # This is a less than 1000-byte transaction, so just set the fee diff --git a/test/functional/replace-by-fee.py b/test/functional/replace-by-fee.py index bc676549875..717571e46ba 100755 --- a/test/functional/replace-by-fee.py +++ b/test/functional/replace-by-fee.py @@ -124,7 +124,7 @@ def test_simple_doublespend(self): tx1b_hex = txToHex(tx1b) # This will raise an exception due to insufficient fee - assert_raises_jsonrpc(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, True) + assert_raises_jsonrpc(-26, "fee-too-low-for-replacement", self.nodes[0].sendrawtransaction, tx1b_hex, True) # Extra 0.1 BTC fee tx1b = CTransaction() @@ -167,7 +167,7 @@ def test_doublespend_chain(self): dbl_tx_hex = txToHex(dbl_tx) # This will raise an exception due to insufficient fee - assert_raises_jsonrpc(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) + assert_raises_jsonrpc(-26, "fee-too-low-for-replacement", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) # Accepted with sufficient fee dbl_tx = CTransaction() @@ -228,7 +228,7 @@ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _tota dbl_tx.vout = [CTxOut(initial_nValue - fee*n, CScript([1]))] dbl_tx_hex = txToHex(dbl_tx) # This will raise an exception due to insufficient fee - assert_raises_jsonrpc(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) + assert_raises_jsonrpc(-26, "fee-too-low-for-replacement", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) # 1 BTC fee is enough dbl_tx = CTransaction() @@ -256,7 +256,7 @@ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _tota dbl_tx.vout = [CTxOut(initial_nValue - 2*fee*n, CScript([1]))] dbl_tx_hex = txToHex(dbl_tx) # This will raise an exception - assert_raises_jsonrpc(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) + assert_raises_jsonrpc(-26, "too-many-replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, True) for tx in tree_txs: tx.rehash() @@ -280,7 +280,7 @@ def test_replacement_feeperkb(self): tx1b_hex = txToHex(tx1b) # This will raise an exception due to insufficient fee - assert_raises_jsonrpc(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, True) + assert_raises_jsonrpc(-26, "fee-too-low-for-replacement", self.nodes[0].sendrawtransaction, tx1b_hex, True) def test_spends_of_conflicting_outputs(self): """Replacements that spend conflicting tx outputs are rejected""" @@ -385,7 +385,7 @@ def test_too_many_replacements(self): double_tx_hex = txToHex(double_tx) # This will raise an exception - assert_raises_jsonrpc(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, True) + assert_raises_jsonrpc(-26, "too-many-replacements", self.nodes[0].sendrawtransaction, double_tx_hex, True) # If we remove an input, it should pass double_tx = CTransaction() @@ -482,7 +482,7 @@ def test_prioritised_transactions(self): tx1b_hex = txToHex(tx1b) # Verify tx1b cannot replace tx1a. - assert_raises_jsonrpc(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, True) + assert_raises_jsonrpc(-26, "fee-too-low-for-replacement", self.nodes[0].sendrawtransaction, tx1b_hex, True) # Use prioritisetransaction to set tx1a's fee to 0. self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1*COIN)) @@ -509,7 +509,7 @@ def test_prioritised_transactions(self): tx2b_hex = txToHex(tx2b) # Verify tx2b cannot replace tx2a. - assert_raises_jsonrpc(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, True) + assert_raises_jsonrpc(-26, "fee-too-low-for-replacement", self.nodes[0].sendrawtransaction, tx2b_hex, True) # Now prioritise tx2b to have a higher modified fee self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1*COIN))