Skip to content

Instantly share code, notes, and snippets.

@Retropex
Created November 14, 2023 11:14
Show Gist options
  • Save Retropex/a876aa4b8569737d07d98174cc7a531f to your computer and use it in GitHub Desktop.
Save Retropex/a876aa4b8569737d07d98174cc7a531f to your computer and use it in GitHub Desktop.
Ordisrespector V2
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