Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ std::pair<size_t, size_t> CScript::DatacarrierBytes(const size_t remaining_outpu
opcodetype opcode, last_opcode{OP_INVALIDOPCODE};
std::vector<unsigned char> push_data;
unsigned int inside_noop{0}, inside_conditional{0};
auto is_push = [](opcodetype op) { return op <= OP_16 && op != OP_RESERVED; }; // OP_0..OP_16 push data/numbers
CScript::const_iterator opcode_it = begin(), data_began = begin();
for (CScript::const_iterator it = begin(); it < end(); last_opcode = opcode) {
opcode_it = it;
Expand Down Expand Up @@ -406,10 +407,10 @@ std::pair<size_t, size_t> CScript::DatacarrierBytes(const size_t remaining_outpu
} 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) {
// Match <data> OP_DROP, or a run of <data>/pushnums ... OP_DROP/OP_2DROP (bare/BIP-110 envelope)
} else if (is_push(opcode)) {
if (!is_push(last_opcode)) data_began = opcode_it; // only reset at the start of a push run
} else if ((opcode == OP_DROP || opcode == OP_2DROP) && is_push(last_opcode)) {
counted += it - data_began;
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/test/script_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,10 @@ BOOST_AUTO_TEST_CASE(script_DataCarrierBytes)
BOOST_CHECK_EQUAL("0+6", DatacarrierBytesStr(CScript() << OP_FALSE << OP_IF << OP_TRUE << OP_IF << OP_ENDIF << OP_ENDIF));
// pushing then immediately dropping is data: length(1), zero(11), OP_DROP
BOOST_CHECK_EQUAL("0+13", DatacarrierBytesStr(CScript() << zeros(11) << OP_DROP));
// bare/BIP-110 envelope: a run of pushes balanced by OP_2DROP is all data
BOOST_CHECK_EQUAL("0+25", DatacarrierBytesStr(CScript() << zeros(11) << zeros(11) << OP_2DROP));
// pushnums interleaved in the run don't strand earlier pushes: 12 + 1 (OP_7) + 12 + 1 (OP_2DROP)
BOOST_CHECK_EQUAL("0+26", DatacarrierBytesStr(CScript() << zeros(11) << OP_7 << zeros(11) << OP_2DROP));
// OLGA data obfuscated as p2wsh
const auto olga_header = CScript() << OP_0 << "003e7374616d703a000000000000000000000000000000000000000000000000"_hex;
BOOST_CHECK_EQUAL("0+82", DatacarrierBytesStr(olga_header, 2));
Expand Down
8 changes: 5 additions & 3 deletions test/functional/feature_taproot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,8 +1037,10 @@ def big_spend_inputs(ctx):
add_spender(spenders, "tapscript/emptysigs/checksig", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t13"}, **ERR_UNKNOWN_PUBKEY)
add_spender(spenders, "tapscript/emptysigs/nochecksigverify", leaf="t12", **common, inputs=[b'', getter("sign")], failure={"leaf": "t20"}, **ERR_UNKNOWN_PUBKEY)
add_spender(spenders, "tapscript/emptysigs/checksigadd", leaf="t14", **common, inputs=[b'', getter("sign")], failure={"leaf": "t15"}, **ERR_UNKNOWN_PUBKEY)
# Test that scripts over 10000 bytes (and over 201 non-push ops) are acceptable.
add_spender(spenders, "tapscript/no10000limit", leaf="t19", **SINGLE_SIG, **common)
# Test that scripts over 10000 bytes (and over 201 non-push ops) are acceptable by consensus.
# The leaf opens with 10001 repeats of OP_0 OP_0 OP_2DROP, which datacarrier policy counts as
# a data envelope, so it is only spendable in a block rather than through the mempool.
add_spender(spenders, "tapscript/no10000limit", leaf="t19", standard=False, **SINGLE_SIG, **common)
# Test that a stack size of 1000 elements is permitted, but 1001 isn't.
add_spender(spenders, "tapscript/1000stack", leaf="t21", **SINGLE_SIG, **common, failure={"leaf": "t22"}, **ERR_STACK_SIZE)
# Test that an input stack size of 1000 elements is permitted, but 1001 isn't.
Expand All @@ -1061,7 +1063,7 @@ def big_spend_inputs(ctx):
# n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGVERIFY.
lambda n, pk: (CScript([OP_DROP, pk, OP_0, OP_IF, OP_2DUP, OP_CHECKSIGVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_2, OP_SWAP, OP_CHECKSIGADD, OP_3, OP_EQUAL]), n + 1),
# n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIG.
lambda n, pk: (CScript([random.randbytes(220), OP_2DROP, pk, OP_1, OP_NOTIF, OP_2DUP, OP_CHECKSIG, OP_VERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_4, OP_SWAP, OP_CHECKSIGADD, OP_5, OP_EQUAL]), n + 1),
lambda n, pk: (CScript([random.randbytes(75), OP_2DROP, pk, OP_1, OP_NOTIF, OP_2DUP, OP_CHECKSIG, OP_VERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_4, OP_SWAP, OP_CHECKSIGADD, OP_5, OP_EQUAL]), n + 1),
# n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD.
lambda n, pk: (CScript([OP_DROP, pk, OP_1, OP_IF, OP_ELSE, OP_2DUP, OP_6, OP_SWAP, OP_CHECKSIGADD, OP_7, OP_EQUALVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_8, OP_SWAP, OP_CHECKSIGADD, OP_9, OP_EQUAL]), n + 1),
# n+1 OP_CHECKSIGs, but also one OP_CHECKSIG with an empty signature.
Expand Down
56 changes: 56 additions & 0 deletions test/functional/mempool_datacarrier.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CScript,
OP_1,
OP_2DROP,
OP_7,
OP_DROP,
OP_RETURN,
taproot_construct,
Expand Down Expand Up @@ -92,6 +93,50 @@ def test_opnet_transaction(self, node: TestNode, success: bool) -> None:
self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex)


def test_bare_envelope(self, node: TestNode, data_len: int, success: bool, interleave_pushnum: bool = False) -> None:
# A bare inscription envelope: <marker> <data>... balanced by OP_2DROP,
# with no OP_IF wrapper (the shape ordinals/ord#4545 uses under BIP-110).
# A pushnum interleaved in the run (which ord's parser accepts as payload)
# must not strand the large push from datacarrier counting.
if interleave_pushnum:
envelope_script = CScript([randbytes(data_len), OP_7, randbytes(1), OP_2DROP, OP_DROP, OP_1])
else:
envelope_script = CScript([b'ord', randbytes(data_len), OP_2DROP, OP_1])
internal_key = b'\x01' * 32
tap = taproot_construct(internal_key, [("leaf", envelope_script), ("dummy", CScript([OP_1]))])
leaf = tap.leaves["leaf"]
control_block = bytes([leaf.version | tap.negflag]) + tap.internal_pubkey + leaf.merklebranch
assert len(control_block) == 65

utxo = self.wallet.get_utxo()
funding_tx = CTransaction()
funding_tx.vin = [CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout']))]
funding_value = int(utxo['value'] * 100_000_000) - 1000
funding_tx.vout = [CTxOut(funding_value, tap.scriptPubKey)]
funding_tx.version = 2
self.wallet.sign_tx(funding_tx)
funding_tx.rehash()
self.nodes[0].sendrawtransaction(funding_tx.serialize().hex())
self.generate(self.nodes[0], 1, sync_fun=self.sync_blocks)

spend_tx = CTransaction()
spend_tx.version = 2
spend_tx.vin = [CTxIn(COutPoint(int(funding_tx.hash, 16), 0))]
spend_tx.vout = [CTxOut(funding_value - 1000, tap.scriptPubKey)]
spend_tx.wit.vtxinwit = [CTxInWitness()]
spend_tx.wit.vtxinwit[0].scriptWitness.stack = [
bytes(envelope_script), # tapscript
control_block, # control block (65 bytes)
]
tx_hex = spend_tx.serialize().hex()

if success:
self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_hex)
assert spend_tx.rehash() in node.getrawmempool(True)
else:
assert_raises_rpc_error(-26, "txn-datacarrier-exceeded",
self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex)

def run_test(self):
self.wallet = MiniWallet(self.nodes[0])

Expand Down Expand Up @@ -141,6 +186,17 @@ def run_test(self):
self.log.info("Testing an OPNet transaction (just pushing 'op') with -datacarriersize=2.")
self.test_opnet_transaction(node=self.nodes[3], success=False)

self.log.info("Testing a bare OP_2DROP envelope larger than the default -datacarriersize.")
self.test_bare_envelope(node=self.nodes[0], data_len=MAX_OP_RETURN_RELAY, success=False)

self.log.info("Testing a bare OP_2DROP envelope smaller than the default -datacarriersize.")
# data_len >= 2 keeps the payload a minimal push; a 1-byte push of 1..16 or 0x81 would be
# rejected by MINIMALDATA in script verification rather than accepted by the datacarrier check.
self.test_bare_envelope(node=self.nodes[0], data_len=2, success=True)

self.log.info("Testing a bare envelope with a pushnum interleaved in the push run.")
self.test_bare_envelope(node=self.nodes[0], data_len=MAX_OP_RETURN_RELAY, success=False, interleave_pushnum=True)


if __name__ == '__main__':
DataCarrierTest(__file__).main()
Loading