RPC: sendrawtransaction: Allow the user to ignore/override specific rejections #7533

Open
wants to merge 6 commits into
from
View
@@ -1902,13 +1902,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
@@ -1922,24 +1922,24 @@ 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)
p2sh_txs[2].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, scripts[2]]
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
@@ -58,6 +58,7 @@ static void CCoinsCaching(benchmark::State& state)
CBasicKeyStore keystore;
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
+ std::string reason;
std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
CMutableTransaction t1;
@@ -77,7 +78,7 @@ static void CCoinsCaching(benchmark::State& state)
// Benchmark.
while (state.KeepRunning()) {
- bool success = AreInputsStandard(t1, coins);
+ bool success = AreInputsStandard(t1, coins, reason);
assert(success);
CAmount value = coins.GetValueIn(t1);
assert(value == (50 + 21 + 22) * CENT);
View
@@ -29,6 +29,9 @@
* 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)
@@ -56,71 +59,95 @@ 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";
+static inline bool IsStandardTx_Rejection_(std::string& reasonOut, const std::string& reason, const std::string& reasonPrefix, const std::set<std::string>& setIgnoreRejects=std::set<std::string>()) {
+ if (setIgnoreRejects.find(reasonPrefix + reason) != setIgnoreRejects.end())
return false;
- }
- // 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;
+ reasonOut = reason;
+ return true;
+}
+
+#define IsStandardTx_Rejection(reasonIn) do { \
+ if (IsStandardTx_Rejection_(reason, reasonIn, "", setIgnoreRejects)) { \
+ return false; \
+ } \
+} while(0)
+
+bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled, const std::set<std::string>& setIgnoreRejects)
+{
+ if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) {
+ IsStandardTx_Rejection("version");
}
- BOOST_FOREACH(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 (setIgnoreRejects.find("tx-size") == setIgnoreRejects.end()) {
+ // 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;
}
}
- unsigned int nDataOut = 0;
- txnouttype whichType;
- BOOST_FOREACH(const CTxOut& txout, tx.vout) {
- if (!::IsStandard(txout.scriptPubKey, whichType, witnessEnabled)) {
- reason = "scriptpubkey";
- return false;
+ bool fCheckPushOnly = (setIgnoreRejects.find("scriptsig-not-pushonly") == setIgnoreRejects.end());
+ if (setIgnoreRejects.find("scriptsig-size") == setIgnoreRejects.end() || fCheckPushOnly) {
+ BOOST_FOREACH(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) {
+ IsStandardTx_Rejection("scriptsig-size");
+ }
+ if (fCheckPushOnly && !txin.scriptSig.IsPushOnly()) {
+ 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 (txout.IsDust(dustRelayFee)) {
- reason = "dust";
- return false;
+ if (setIgnoreRejects.find("scriptpubkey") == setIgnoreRejects.end() || setIgnoreRejects.find("bare-multisig") == setIgnoreRejects.end() || setIgnoreRejects.find("dust") == setIgnoreRejects.end() || setIgnoreRejects.find("multi-op-return") == setIgnoreRejects.end()) {
+ unsigned int nDataOut = 0;
+ txnouttype whichType;
+ BOOST_FOREACH(const CTxOut& txout, tx.vout) {
+ if (!::IsStandard(txout.scriptPubKey, whichType, witnessEnabled)) {
+ IsStandardTx_Rejection("scriptpubkey");
+ }
+
+ if (whichType == TX_NULL_DATA)
+ nDataOut++;
+ else {
+ if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) {
+ IsStandardTx_Rejection("bare-multisig");
+ }
+ if (txout.IsDust(dustRelayFee)) {
+ IsStandardTx_Rejection("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) {
+ IsStandardTx_Rejection("multi-op-return");
+ }
}
return true;
}
-bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
+#define AreInputsStandard_Rejection(reasonIn) do { \
+ if (IsStandardTx_Rejection_(reason, reasonIn, "bad-txns-input-", setIgnoreRejects)) { \
+ return false; \
+ } \
+} while(0)
+
+bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, std::string& reason, const std::set<std::string>& setIgnoreRejects)
{
if (tx.IsCoinBase())
return true; // Coinbases don't use vin normally
@@ -133,28 +160,47 @@ 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)) {
+ AreInputsStandard_Rejection("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<std::vector<unsigned char> > 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
+ reason = "scriptsig-failure";
return false;
- if (stack.empty())
+ }
+ if (stack.empty()) {
+ // Also invalid
+ reason = "scriptcheck-missing";
return false;
+ }
CScript subscript(stack.back().begin(), stack.back().end());
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
- return false;
+ AreInputsStandard_Rejection("scriptcheck-sigops");
}
}
}
return true;
}
-bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
+#define IsWitnessStandard_Rejection(reasonIn) do { \
+ if (IsStandardTx_Rejection_(reason, reasonIn, "bad-witness-", setIgnoreRejects)) { \
+ return false; \
+ } \
+} while(0)
+
+bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, std::string& reason, const std::set<std::string>& setIgnoreRejects)
{
if (tx.IsCoinBase())
return true; // Coinbases are skipped
@@ -177,9 +223,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))
+ {
+ reason = "scriptsig-failure";
return false;
+ }
if (stack.empty())
+ {
+ reason = "scriptcheck-missing";
return false;
+ }
prevScript = CScript(stack.back().begin(), stack.back().end());
}
@@ -188,18 +240,21 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// Non-witness program must not be associated with any witness
if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram))
+ {
+ reason = "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;
+ IsWitnessStandard_Rejection("script-size");
size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1;
if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS)
- return false;
+ IsWitnessStandard_Rejection("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;
+ IsWitnessStandard_Rejection("stackitem-size");
}
}
}
View
@@ -79,19 +79,19 @@ 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 std::set<std::string>& setIgnoreRejects=std::set<std::string>());
/**
* 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, std::string& reason, const std::set<std::string>& setIgnoreRejects=std::set<std::string>());
/**
* 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, std::string& reason, const std::set<std::string>& setIgnoreRejects=std::set<std::string>());
extern CFeeRate incrementalRelayFee;
extern CFeeRate dustRelayFee;
View
@@ -861,12 +861,12 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw runtime_error(
- "sendrawtransaction \"hexstring\" ( allowhighfees )\n"
+ "sendrawtransaction \"hexstring\" ( [\"ignoreReject\",...] )\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. \"ignoreReject\" (string, optional) Rejection conditions to ignore, eg 'absurdly-high-fee'\n"
"\nResult:\n"
"\"hex\" (string) The transaction hash in hex\n"
"\nExamples:\n"
@@ -881,7 +881,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
);
LOCK(cs_main);
- RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL));
+ RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR));
// parse hex string from parameter
CMutableTransaction mtx;
@@ -892,8 +892,20 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
bool fLimitFree = false;
CAmount nMaxRawTxFee = maxTxFee;
- if (request.params.size() > 1 && request.params[1].get_bool())
- nMaxRawTxFee = 0;
+
+ std::set<std::string> setIgnoreRejects;
+ if (request.params.size() > 1) {
+ if (request.params[1].type() == UniValue::VBOOL) {
+ // This parameter used to be boolean allowhighfees
+ setIgnoreRejects.insert(strRejectMsg_AbsurdFee);
+ } else {
+ UniValue ignRejs = request.params[1].get_array();
+ for (unsigned int i = 0; i < ignRejs.size(); ++i) {
+ const UniValue& ignRej = ignRejs[i];
+ setIgnoreRejects.insert(ignRej.get_str());
+ }
+ }
+ }
CCoinsViewCache &view = *pcoinsTip;
const CCoins* existingCoins = view.AccessCoins(hashTx);
@@ -903,7 +915,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
// push to local node and sync with wallets
CValidationState state;
bool fMissingInputs;
- if (!AcceptToMemoryPool(mempool, state, std::move(tx), fLimitFree, &fMissingInputs, NULL, false, nMaxRawTxFee)) {
+ if (!AcceptToMemoryPool(mempool, state, std::move(tx), fLimitFree, &fMissingInputs, NULL, nMaxRawTxFee, setIgnoreRejects)) {
if (state.IsInvalid()) {
throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
} else {
Oops, something went wrong.