Created
November 14, 2023 11:14
-
-
Save Retropex/a876aa4b8569737d07d98174cc7a531f to your computer and use it in GitHub Desktop.
Ordisrespector V2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From e2b535915a9c4e04daf6909ab10e68258d43df1d Mon Sep 17 00:00:00 2001 | |
From: =?UTF-8?q?L=C3=A9o=20Haf?= <[email protected]> | |
Date: Tue, 14 Nov 2023 12:06:32 +0100 | |
Subject: [PATCH] Update Ordisrespector to V2 | |
--- | |
src/policy/policy.cpp | 63 +++++++ | |
src/policy/policy.h | 5 + | |
src/script/interpreter.cpp | 8 - | |
src/script/script.cpp | 50 ++++++ | |
src/script/script.h | 7 +- | |
src/test/script_tests.cpp | 256 +++++++++++++++++++++++++++++ | |
src/validation.cpp | 4 + | |
test/functional/feature_taproot.py | 2 +- | |
8 files changed, 384 insertions(+), 11 deletions(-) | |
diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp | |
index 41b5b2d0f1898..10efe4bda8967 100644 | |
--- a/src/policy/policy.cpp | |
+++ b/src/policy/policy.cpp | |
@@ -306,3 +306,66 @@ int64_t GetVirtualTransactionInputSize(const CTxIn& txin, int64_t nSigOpCost, un | |
{ | |
return GetVirtualTransactionSize(GetTransactionInputWeight(txin), nSigOpCost, bytes_per_sigop); | |
} | |
+ | |
+std::pair<CScript, unsigned int> GetScriptForTransactionInput(CScript prevScript, const CTxIn& txin) | |
+{ | |
+ bool p2sh = false; | |
+ if (prevScript.IsPayToScriptHash()) { | |
+ std::vector <std::vector<unsigned char> > stack; | |
+ if (!EvalScript(stack, txin.scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE)) { | |
+ return std::make_pair(CScript(), 0); | |
+ } | |
+ if (stack.empty()) { | |
+ return std::make_pair(CScript(), 0); | |
+ } | |
+ prevScript = CScript(stack.back().begin(), stack.back().end()); | |
+ p2sh = true; | |
+ } | |
+ | |
+ int witnessversion = 0; | |
+ std::vector<unsigned char> witnessprogram; | |
+ | |
+ if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram)) { | |
+ // For P2SH, scriptSig is always push-only, so the actual script is only the last stack item | |
+ // For non-P2SH, prevScript is likely the real script, but not part of this transaction, and scriptSig could very well be executable, so return the latter instead | |
+ return std::make_pair(p2sh ? prevScript : txin.scriptSig, WITNESS_SCALE_FACTOR); | |
+ } | |
+ | |
+ Span stack{txin.scriptWitness.stack}; | |
+ | |
+ if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) { | |
+ if (stack.empty()) return std::make_pair(CScript(), 0); // invalid | |
+ auto& script_data = stack.back(); | |
+ prevScript = CScript(script_data.begin(), script_data.end()); | |
+ return std::make_pair(prevScript, 1); | |
+ } | |
+ | |
+ if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) { | |
+ if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { | |
+ SpanPopBack(stack); | |
+ } | |
+ if (stack.size() >= 2) { | |
+ SpanPopBack(stack); // Ignore control block | |
+ prevScript = CScript(stack.back().begin(), stack.back().end()); | |
+ return std::make_pair(prevScript, 1); | |
+ } | |
+ } | |
+ | |
+ return std::make_pair(CScript(), 0); | |
+} | |
+ | |
+size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view) | |
+{ | |
+ size_t ret{0}; | |
+ | |
+ for (const CTxIn& txin : tx.vin) { | |
+ const CTxOut &utxo = view.AccessCoin(txin.prevout).out; | |
+ auto[script, consensus_weight_per_byte] = GetScriptForTransactionInput(utxo.scriptPubKey, txin); | |
+ ret += script.DatacarrierBytes(); | |
+ } | |
+ for (const CTxOut& txout : tx.vout) { | |
+ ret += txout.scriptPubKey.DatacarrierBytes(); | |
+ } | |
+ | |
+ return ret; | |
+} | |
\ No newline at end of file | |
diff --git a/src/policy/policy.h b/src/policy/policy.h | |
index 394fb342306d8..ef05261d32f8a 100644 | |
--- a/src/policy/policy.h | |
+++ b/src/policy/policy.h | |
@@ -14,6 +14,7 @@ | |
#include <cstdint> | |
#include <string> | |
+#include <utility> | |
class CCoinsViewCache; | |
class CFeeRate; | |
@@ -148,4 +149,8 @@ static inline int64_t GetVirtualTransactionInputSize(const CTxIn& tx) | |
return GetVirtualTransactionInputSize(tx, 0, 0); | |
} | |
+std::pair<CScript, unsigned int> GetScriptForTransactionInput(CScript prevScript, const CTxIn&); | |
+ | |
+size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view); | |
+ | |
#endif // BITCOIN_POLICY_POLICY_H | |
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp | |
index 3eec7373fbd80..5f4a1aceb2ced 100644 | |
--- a/src/script/interpreter.cpp | |
+++ b/src/script/interpreter.cpp | |
@@ -479,14 +479,6 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& | |
return set_error(serror, SCRIPT_ERR_MINIMALDATA); | |
} | |
stack.push_back(vchPushValue); | |
- if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) && opcode == OP_FALSE) { | |
- auto pc_tmp = pc; | |
- opcodetype next_opcode; | |
- valtype dummy_data; | |
- if (script.GetOp(pc_tmp, next_opcode, dummy_data) && next_opcode == OP_IF) { | |
- return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS); | |
- } | |
- } | |
} else if (fExec || (OP_IF <= opcode && opcode <= OP_ENDIF)) | |
switch (opcode) | |
{ | |
diff --git a/src/script/script.cpp b/src/script/script.cpp | |
index 79d19b90853b6..38c060fb8eb3c 100644 | |
--- a/src/script/script.cpp | |
+++ b/src/script/script.cpp | |
@@ -280,6 +280,56 @@ bool CScript::HasValidOps() const | |
return true; | |
} | |
+size_t CScript::DatacarrierBytes() const | |
+{ | |
+ size_t counted{0}; | |
+ opcodetype opcode, last_opcode{OP_INVALIDOPCODE}; | |
+ std::vector<unsigned char> push_data; | |
+ unsigned int inside_noop{0}, inside_conditional{0}; | |
+ CScript::const_iterator opcode_it = begin(), data_began = begin(); | |
+ for (CScript::const_iterator it = begin(); it < end(); last_opcode = opcode) { | |
+ opcode_it = it; | |
+ if (!GetOp(it, opcode, push_data)) { | |
+ // Invalid scripts are necessarily all data | |
+ return size(); | |
+ } | |
+ | |
+ if (opcode == OP_IF || opcode == OP_NOTIF) { | |
+ ++inside_conditional; | |
+ } else if (opcode == OP_ENDIF) { | |
+ if (!inside_conditional) return size(); // invalid | |
+ --inside_conditional; | |
+ } else if (opcode == OP_RETURN && !inside_conditional) { | |
+ // unconditional OP_RETURN is unspendable | |
+ return size(); | |
+ } | |
+ | |
+ // Match OP_FALSE OP_IF | |
+ if (inside_noop) { | |
+ switch (opcode) { | |
+ case OP_IF: case OP_NOTIF: | |
+ ++inside_noop; | |
+ break; | |
+ case OP_ENDIF: | |
+ if (0 == --inside_noop) { | |
+ counted += it - data_began + 1; | |
+ } | |
+ break; | |
+ default: /* do nothing */; | |
+ } | |
+ } else if (opcode == OP_IF && last_opcode == OP_FALSE) { | |
+ inside_noop = 1; | |
+ data_began = opcode_it; | |
+ // Match <data> OP_DROP | |
+ } else if (opcode <= OP_PUSHDATA4) { | |
+ data_began = opcode_it; | |
+ } else if (opcode == OP_DROP && last_opcode <= OP_PUSHDATA4) { | |
+ counted += it - data_began; | |
+ } | |
+ } | |
+ return counted; | |
+} | |
+ | |
bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet) | |
{ | |
opcodeRet = OP_INVALIDOPCODE; | |
diff --git a/src/script/script.h b/src/script/script.h | |
index 374ae1642e92d..98ec4cadd7e5a 100644 | |
--- a/src/script/script.h | |
+++ b/src/script/script.h | |
@@ -542,15 +542,18 @@ class CScript : public CScriptBase | |
bool HasValidOps() const; | |
/** | |
- * Returns whether the script is guaranteed to fail at execution, | |
+ * Returns whether the scriptPubKey is guaranteed to fail at execution, | |
* regardless of the initial stack. This allows outputs to be pruned | |
- * instantly when entering the UTXO set. | |
+ * instantly when entering the UTXO set. Note that this is incorrect for | |
+ * witness scripts, which are not always limited by MAX_SCRIPT_SIZE. | |
*/ | |
bool IsUnspendable() const | |
{ | |
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE); | |
} | |
+ size_t DatacarrierBytes() const; | |
+ | |
void clear() | |
{ | |
// The default prevector::clear() does not release memory | |
diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp | |
index d8898743b093c..52a79efa94db5 100644 | |
--- a/src/test/script_tests.cpp | |
+++ b/src/test/script_tests.cpp | |
@@ -7,6 +7,7 @@ | |
#include <core_io.h> | |
#include <key.h> | |
+#include <policy/policy.h> | |
#include <rpc/util.h> | |
#include <script/script.h> | |
#include <script/script_error.h> | |
@@ -1466,6 +1467,261 @@ BOOST_AUTO_TEST_CASE(script_HasValidOps) | |
BOOST_CHECK(!script.HasValidOps()); | |
} | |
+BOOST_AUTO_TEST_CASE(script_DataCarrierBytes) | |
+{ | |
+ using zeros = std::vector<unsigned char>; | |
+ | |
+ // empty script | |
+ BOOST_CHECK_EQUAL(0, (CScript()).DatacarrierBytes()); | |
+ // series of pushes are not data | |
+ BOOST_CHECK_EQUAL(0, (CScript() << OP_0 << OP_0 << OP_0).DatacarrierBytes()); | |
+ // unspendable if first op is OP_RETURN, then length(1), zeros(11) | |
+ BOOST_CHECK_EQUAL(13, (CScript() << OP_RETURN << zeros(11)).DatacarrierBytes()); | |
+ // invalid script (no data following PUSHDATA) makes it all data | |
+ BOOST_CHECK_EQUAL(2, (CScript() << OP_0 << OP_PUSHDATA4).DatacarrierBytes()); | |
+ // no data here | |
+ BOOST_CHECK_EQUAL(0, (CScript() << OP_TRUE << OP_IF << OP_ENDIF).DatacarrierBytes()); | |
+ // specific data pattern, entire script is data | |
+ BOOST_CHECK_EQUAL(4, (CScript() << OP_FALSE << OP_IF << OP_7 << OP_ENDIF).DatacarrierBytes()); | |
+ // consecutive data | |
+ BOOST_CHECK_EQUAL(6, (CScript() << OP_FALSE << OP_IF << OP_ENDIF << OP_FALSE << OP_IF << OP_ENDIF).DatacarrierBytes()); | |
+ // nested data (all is data) | |
+ BOOST_CHECK_EQUAL(6, (CScript() << OP_FALSE << OP_IF << OP_TRUE << OP_IF << OP_ENDIF << OP_ENDIF).DatacarrierBytes()); | |
+ // pushing then immediately dropping is data: length(1), zero(11), OP_DROP | |
+ BOOST_CHECK_EQUAL(13, (CScript() << zeros(11) << OP_DROP).DatacarrierBytes()); | |
+} | |
+ | |
+BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) | |
+{ | |
+ using zeros = std::vector<unsigned char>; | |
+ | |
+ { // P2PK - no datacarrier bytes (tx_in doesn't matter) | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << zeros(65) << OP_CHECKSIG; | |
+ tx_in.scriptSig = CScript(); | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == tx_in.scriptSig); | |
+ BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2PKH - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << OP_DUP << OP_HASH160 << zeros(20) << OP_EQUALVERIFY << OP_CHECKSIG; | |
+ // signature, pubkey | |
+ tx_in.scriptSig = CScript() << zeros(72) << zeros(33); | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == tx_in.scriptSig); | |
+ BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2SH - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ CScript redeem_script = CScript() << OP_DROP << OP_TRUE; | |
+ prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL; | |
+ // signature, pubkey, redeem_script | |
+ tx_in.scriptSig = CScript() << OP_7 << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end()); | |
+ // this should return the redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == redeem_script); | |
+ BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2SH - with datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ // arbitrary amount of data (27 bytes) | |
+ CScript redeem_script = CScript() << OP_RETURN << zeros(27); | |
+ prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL; | |
+ // signature, pubkey, redeem_script | |
+ tx_in.scriptSig = CScript() << OP_7 << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end()); | |
+ // this should return the redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == redeem_script); | |
+ BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); | |
+ // OP_RETURN(1), length(1), zeros(27) = 29 | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 29); | |
+ } | |
+ { // P2WPKH - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ // P2WPKH is [OP_0, hash160(pubkey)] | |
+ prev_script = CScript() << OP_0 << zeros(20); | |
+ // segwit: empty scriptsig | |
+ tx_in.scriptSig = CScript(); | |
+ tx_in.scriptWitness.stack.push_back(zeros(65)); // signature | |
+ tx_in.scriptWitness.stack.push_back(zeros(33)); // pubkey | |
+ // this should return the redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ // should have no script at all since it's wrapped P2WPKH | |
+ BOOST_CHECK(ret_script == CScript()); | |
+ BOOST_CHECK_EQUAL(scale, 0); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2WSH - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << OP_0 << zeros(32); | |
+ // segwit: empty scriptsig | |
+ tx_in.scriptSig = CScript(); | |
+ tx_in.scriptWitness.stack.push_back(zeros(65)); // arbitrary value to satisfy redeem script | |
+ CScript redeem_script = CScript() << OP_0; | |
+ auto redeem_vec{std::vector<unsigned char>(redeem_script.begin(), redeem_script.end())}; | |
+ tx_in.scriptWitness.stack.push_back(redeem_vec); | |
+ // this should return the redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == redeem_script); | |
+ BOOST_CHECK_EQUAL(scale, 1); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2WSH - some datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << OP_0 << zeros(32); | |
+ // segwit: empty scriptsig | |
+ tx_in.scriptSig = CScript(); | |
+ tx_in.scriptWitness.stack.push_back(zeros(65)); // arbitrary value to satisfy redeem script | |
+ CScript redeem_script = CScript() << OP_FALSE << OP_IF << zeros(10) << OP_ENDIF; | |
+ auto redeem_vec{std::vector<unsigned char>(redeem_script.begin(), redeem_script.end())}; | |
+ tx_in.scriptWitness.stack.push_back(redeem_vec); | |
+ // this should return the redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == redeem_script); | |
+ BOOST_CHECK_EQUAL(scale, 1); | |
+ // OP_FALSE(1), OP_IF(1), length(1), zeros(10), OP_ENDIF(1) | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 14); | |
+ } | |
+ { // P2SH-P2WPKH - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ // P2WPKH is [OP_0, hash160(pubkey)] | |
+ CScript redeem_script = CScript() << OP_0 << zeros(20); | |
+ prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL; | |
+ tx_in.scriptSig = CScript() << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end()); | |
+ // this should return the redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ // should have no script at all since it's wrapped P2WPKH | |
+ BOOST_CHECK(ret_script == CScript()); | |
+ // data bytes in the witness get discounted (*1 instead of *4) | |
+ BOOST_CHECK_EQUAL(scale, 0); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2SH-P2WSH - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ // P2WSH is [OP_0, sha256(redeem_script)] | |
+ CScript redeem_script = CScript() << OP_0 << zeros(32); | |
+ prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL; | |
+ tx_in.scriptSig = CScript() << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end()); | |
+ CScript witness_redeem_script = CScript() << OP_TRUE << OP_IF << zeros(10) << OP_ENDIF; | |
+ | |
+ // in real life, one or more values (to satisfy the redeem script) would be pushed to the stack | |
+ CScript wit = CScript() << OP_7; | |
+ tx_in.scriptWitness.stack.push_back(std::vector<unsigned char>(wit.begin(), wit.end())); | |
+ // and then finally the redeem script itself (as the last stack element) | |
+ auto redeem_vec{std::vector<unsigned char>(witness_redeem_script.begin(), witness_redeem_script.end())}; | |
+ tx_in.scriptWitness.stack.push_back(redeem_vec); | |
+ | |
+ // this should return the witness redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ // should have no script at all since it's wrapped P2WPKH | |
+ BOOST_CHECK(ret_script == witness_redeem_script); | |
+ // data bytes in the witness get discounted (*1 instead of *4) | |
+ BOOST_CHECK_EQUAL(scale, 1); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2SH-P2WSH - some datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ // P2WSH is [OP_0, sha256(redeem_script)] | |
+ CScript redeem_script = CScript() << OP_0 << zeros(32); | |
+ prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL; | |
+ tx_in.scriptSig = CScript() << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end()); | |
+ CScript witness_redeem_script = CScript() << OP_FALSE << OP_IF << zeros(10) << OP_ENDIF; | |
+ | |
+ // in real life, one or more values (to satisfy the redeem script) would be pushed to the stack | |
+ CScript wit = CScript() << OP_7; | |
+ tx_in.scriptWitness.stack.push_back(std::vector<unsigned char>(wit.begin(), wit.end())); | |
+ // and then finally the redeem script itself (as the last stack element) | |
+ auto redeem_vec{std::vector<unsigned char>(witness_redeem_script.begin(), witness_redeem_script.end())}; | |
+ tx_in.scriptWitness.stack.push_back(redeem_vec); | |
+ | |
+ // this should return the witness redeem script | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ // should have no script at all since it's wrapped P2WPKH | |
+ BOOST_CHECK(ret_script == witness_redeem_script); | |
+ // data bytes in the witness get discounted (*1 instead of *4) | |
+ BOOST_CHECK_EQUAL(scale, 1); | |
+ // OP_FALSE(1), OP_IF(1), length(1), zeros(10), OP_ENDIF(1) = 14 | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 14); | |
+ } | |
+ { // P2TR keypath - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << OP_1 << zeros(32); | |
+ // segwit: empty scriptsig | |
+ tx_in.scriptSig = CScript(); | |
+ tx_in.scriptWitness.stack.push_back(zeros(65)); // signature | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == CScript()); | |
+ BOOST_CHECK_EQUAL(scale, 0); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2TR keypath - annex but no script - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << OP_1 << zeros(32); | |
+ // segwit: empty scriptsig | |
+ tx_in.scriptSig = CScript(); | |
+ tx_in.scriptWitness.stack.push_back(zeros(65)); // signature | |
+ std::vector<unsigned char> annex{0x50, 0, 0}; | |
+ tx_in.scriptWitness.stack.push_back(annex); | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == CScript()); | |
+ BOOST_CHECK_EQUAL(scale, 0); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2TR scriptpath - no datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << OP_1 << zeros(32); | |
+ // segwit: empty scriptsig | |
+ tx_in.scriptSig = CScript(); | |
+ // stack: zero or more arbitrary values (script arguments); script; control block | |
+ // (here we have two arbitrary values) | |
+ tx_in.scriptWitness.stack.push_back(zeros(85)); // arbitrary value | |
+ tx_in.scriptWitness.stack.push_back(zeros(10)); // arbitrary value | |
+ CScript script = CScript() << OP_7 << OP_8; | |
+ auto script_vec{std::vector<unsigned char>(script.begin(), script.end())}; | |
+ tx_in.scriptWitness.stack.push_back(script_vec); | |
+ tx_in.scriptWitness.stack.push_back(zeros(33)); // control block | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == script); | |
+ BOOST_CHECK_EQUAL(scale, 1); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); | |
+ } | |
+ { // P2TR scriptpath - some datacarrier bytes | |
+ CScript prev_script; // scriptPubKey | |
+ CTxIn tx_in; | |
+ prev_script = CScript() << OP_1 << zeros(32); | |
+ // segwit: empty scriptsig | |
+ tx_in.scriptSig = CScript(); | |
+ // stack: zero or more arbitrary values (script arguments); script; control block | |
+ // (here we have one arbitrary value) | |
+ tx_in.scriptWitness.stack.push_back(zeros(85)); // arbitrary value | |
+ CScript script = CScript() << OP_RETURN << OP_7 << OP_8; | |
+ auto script_vec{std::vector<unsigned char>(script.begin(), script.end())}; | |
+ tx_in.scriptWitness.stack.push_back(script_vec); | |
+ tx_in.scriptWitness.stack.push_back(zeros(33)); // control block | |
+ auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); | |
+ BOOST_CHECK(ret_script == script); | |
+ BOOST_CHECK_EQUAL(scale, 1); | |
+ BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 3); | |
+ } | |
+} | |
+ | |
static CMutableTransaction TxFromHex(const std::string& str) | |
{ | |
CMutableTransaction tx; | |
diff --git a/src/validation.cpp b/src/validation.cpp | |
index 1fd8f0e326877..a10db5a784da5 100644 | |
--- a/src/validation.cpp | |
+++ b/src/validation.cpp | |
@@ -815,6 +815,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) | |
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); | |
} | |
+ if (DatacarrierBytes(tx, m_view) > m_pool.m_max_datacarrier_bytes.value_or(0)) { | |
+ return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "txn-datacarrier-exceeded"); | |
+ } | |
+ | |
// Check for non-standard witnesses. | |
if (tx.HasWitness() && m_pool.m_require_standard && !IsWitnessStandard(tx, m_view)) { | |
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); | |
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py | |
index 8be2040d9165e..cd5dc7436f7dc 100755 | |
--- a/test/functional/feature_taproot.py | |
+++ b/test/functional/feature_taproot.py | |
@@ -738,7 +738,7 @@ def spenders_taproot_active(): | |
scripts = [ | |
("pk_codesep", CScript(random_checksig_style(pubs[1]) + bytes([OP_CODESEPARATOR]))), # codesep after checksig | |
("codesep_pk", CScript(bytes([OP_CODESEPARATOR]) + random_checksig_style(pubs[1]))), # codesep before checksig | |
- ("branched_codesep", CScript([random_bytes(random.randrange(511)), OP_DROP, OP_IF, OP_CODESEPARATOR, pubs[0], OP_ELSE, OP_CODESEPARATOR, pubs[1], OP_ENDIF, OP_CHECKSIG])), # branch dependent codesep | |
+ ("branched_codesep", CScript([random_bytes(random.randrange(75)), OP_DROP, OP_IF, OP_CODESEPARATOR, pubs[0], OP_ELSE, OP_CODESEPARATOR, pubs[1], OP_ENDIF, OP_CHECKSIG])), # branch dependent codesep | |
] | |
random.shuffle(scripts) | |
tap = taproot_construct(pubs[0], scripts) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment