Skip to content

Commit 9f36962

Browse files
instagibbsajtowns
andcommitted
policy: uncap datacarrier by default
Datacarrier output script sizes and output counts are now uncapped by default. To avoid introducing another startup argument, we modify the OP_RETURN accounting to "budget" the spk sizes. If a user has set a custom default, this results in that budget being spent over the sum of all OP_RETURN outputs' scripts in the transaction, no longer capping the number of OP_RETURN outputs themselves. This should allow a superset of current behavior while respecting the passed argument in terms of total arbitrary data storage. Co-authored-by: Anthony Towns <[email protected]>
1 parent 4b1d48a commit 9f36962

File tree

10 files changed

+64
-67
lines changed

10 files changed

+64
-67
lines changed

src/init.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,8 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
632632
argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
633633
argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
634634
argsman.AddArg("-datacarriersize",
635-
strprintf("Relay and mine transactions whose data-carrying raw scriptPubKey "
636-
"is of this size or less (default: %u)",
635+
strprintf("Relay and mine transactions whose data-carrying raw scriptPubKeys in aggregate "
636+
"are of this size or less, allowing multiple outputs (default: %u)",
637637
MAX_OP_RETURN_RELAY),
638638
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
639639
argsman.AddArg("-permitbaremultisig", strprintf("Relay transactions creating non-P2SH multisig outputs (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY,

src/policy/policy.cpp

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ std::vector<uint32_t> GetDust(const CTransaction& tx, CFeeRate dust_relay_rate)
7676
return dust_outputs;
7777
}
7878

79-
bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType)
79+
bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType)
8080
{
8181
std::vector<std::vector<unsigned char> > vSolutions;
8282
whichType = Solver(scriptPubKey, vSolutions);
@@ -91,10 +91,6 @@ bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_
9191
return false;
9292
if (m < 1 || m > n)
9393
return false;
94-
} else if (whichType == TxoutType::NULL_DATA) {
95-
if (!max_datacarrier_bytes || scriptPubKey.size() > *max_datacarrier_bytes) {
96-
return false;
97-
}
9894
}
9995

10096
return true;
@@ -137,17 +133,22 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
137133
}
138134
}
139135

140-
unsigned int nDataOut = 0;
136+
unsigned int datacarrier_bytes_left = max_datacarrier_bytes.value_or(0);
141137
TxoutType whichType;
142138
for (const CTxOut& txout : tx.vout) {
143-
if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) {
139+
if (!::IsStandard(txout.scriptPubKey, whichType)) {
144140
reason = "scriptpubkey";
145141
return false;
146142
}
147143

148-
if (whichType == TxoutType::NULL_DATA)
149-
nDataOut++;
150-
else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) {
144+
if (whichType == TxoutType::NULL_DATA) {
145+
unsigned int size = txout.scriptPubKey.size();
146+
if (size > datacarrier_bytes_left) {
147+
reason = "datacarrier";
148+
return false;
149+
}
150+
datacarrier_bytes_left -= size;
151+
} else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) {
151152
reason = "bare-multisig";
152153
return false;
153154
}
@@ -159,12 +160,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
159160
return false;
160161
}
161162

162-
// only one OP_RETURN txout is permitted
163-
if (nDataOut > 1) {
164-
reason = "multi-op-return";
165-
return false;
166-
}
167-
168163
return true;
169164
}
170165

src/policy/policy.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,9 @@ static constexpr unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT_KVB{101};
7373
/** Default for -datacarrier */
7474
static const bool DEFAULT_ACCEPT_DATACARRIER = true;
7575
/**
76-
* Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN,
77-
* +2 for the pushdata opcodes.
76+
* Default setting for -datacarriersize in vbytes.
7877
*/
79-
static const unsigned int MAX_OP_RETURN_RELAY = 83;
78+
static const unsigned int MAX_OP_RETURN_RELAY = MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR;
8079
/**
8180
* An extra transaction can be added to a package, as long as it only has one
8281
* ancestor and is no larger than this. Not really any reason to make this
@@ -136,7 +135,7 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee);
136135

137136
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);
138137

139-
bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType);
138+
bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType);
140139

141140
/** Get the vout index numbers of all dust outputs */
142141
std::vector<uint32_t> GetDust(const CTransaction& tx, CFeeRate dust_relay_rate);

src/test/fuzz/key.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,12 @@ FUZZ_TARGET(key, .init = initialize_key)
150150
assert(fillable_signing_provider_pub.HaveKey(pubkey.GetID()));
151151

152152
TxoutType which_type_tx_pubkey;
153-
const bool is_standard_tx_pubkey = IsStandard(tx_pubkey_script, std::nullopt, which_type_tx_pubkey);
153+
const bool is_standard_tx_pubkey = IsStandard(tx_pubkey_script, which_type_tx_pubkey);
154154
assert(is_standard_tx_pubkey);
155155
assert(which_type_tx_pubkey == TxoutType::PUBKEY);
156156

157157
TxoutType which_type_tx_multisig;
158-
const bool is_standard_tx_multisig = IsStandard(tx_multisig_script, std::nullopt, which_type_tx_multisig);
158+
const bool is_standard_tx_multisig = IsStandard(tx_multisig_script, which_type_tx_multisig);
159159
assert(is_standard_tx_multisig);
160160
assert(which_type_tx_multisig == TxoutType::MULTISIG);
161161

src/test/fuzz/script.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ FUZZ_TARGET(script, .init = initialize_script)
5353
}
5454

5555
TxoutType which_type;
56-
bool is_standard_ret = IsStandard(script, std::nullopt, which_type);
56+
bool is_standard_ret = IsStandard(script, which_type);
5757
if (!is_standard_ret) {
5858
assert(which_type == TxoutType::NONSTANDARD ||
5959
which_type == TxoutType::NULL_DATA ||

src/test/multisig_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard)
144144

145145
const auto is_standard{[](const CScript& spk) {
146146
TxoutType type;
147-
bool res{::IsStandard(spk, std::nullopt, type)};
147+
bool res{::IsStandard(spk, type)};
148148
if (res) {
149149
BOOST_CHECK_EQUAL(type, TxoutType::MULTISIG);
150150
}

src/test/transaction_tests.cpp

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -797,14 +797,14 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
797797
CKey key = GenerateRandomKey();
798798
t.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
799799

800-
constexpr auto CheckIsStandard = [](const auto& t) {
800+
constexpr auto CheckIsStandard = [](const auto& t, const unsigned int max_op_return_relay = MAX_OP_RETURN_RELAY) {
801801
std::string reason;
802-
BOOST_CHECK(IsStandardTx(CTransaction{t}, MAX_OP_RETURN_RELAY, g_bare_multi, g_dust, reason));
802+
BOOST_CHECK(IsStandardTx(CTransaction{t}, max_op_return_relay, g_bare_multi, g_dust, reason));
803803
BOOST_CHECK(reason.empty());
804804
};
805-
constexpr auto CheckIsNotStandard = [](const auto& t, const std::string& reason_in) {
805+
constexpr auto CheckIsNotStandard = [](const auto& t, const std::string& reason_in, const unsigned int max_op_return_relay = MAX_OP_RETURN_RELAY) {
806806
std::string reason;
807-
BOOST_CHECK(!IsStandardTx(CTransaction{t}, MAX_OP_RETURN_RELAY, g_bare_multi, g_dust, reason));
807+
BOOST_CHECK(!IsStandardTx(CTransaction{t}, max_op_return_relay, g_bare_multi, g_dust, reason));
808808
BOOST_CHECK_EQUAL(reason_in, reason);
809809
};
810810

@@ -858,15 +858,13 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
858858
t.vout[0].scriptPubKey = CScript() << OP_1;
859859
CheckIsNotStandard(t, "scriptpubkey");
860860

861-
// MAX_OP_RETURN_RELAY-byte TxoutType::NULL_DATA (standard)
861+
// Custom 83-byte TxoutType::NULL_DATA (standard with max_op_return_relay of 83)
862862
t.vout[0].scriptPubKey = CScript() << OP_RETURN << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"_hex;
863-
BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY, t.vout[0].scriptPubKey.size());
864-
CheckIsStandard(t);
863+
BOOST_CHECK_EQUAL(83, t.vout[0].scriptPubKey.size());
864+
CheckIsStandard(t, /*max_op_return_relay=*/83);
865865

866-
// MAX_OP_RETURN_RELAY+1-byte TxoutType::NULL_DATA (non-standard)
867-
t.vout[0].scriptPubKey = CScript() << OP_RETURN << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"_hex;
868-
BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size());
869-
CheckIsNotStandard(t, "scriptpubkey");
866+
// Non-standard if max_op_return_relay datacarrier arg is one less
867+
CheckIsNotStandard(t, "datacarrier", /*max_op_return_relay=*/82);
870868

871869
// Data payload can be encoded in any way...
872870
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ""_hex;
@@ -888,21 +886,28 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
888886
t.vout[0].scriptPubKey = CScript() << OP_RETURN;
889887
CheckIsStandard(t);
890888

891-
// Only one TxoutType::NULL_DATA permitted in all cases
889+
// Multiple TxoutType::NULL_DATA are permitted
892890
t.vout.resize(2);
893891
t.vout[0].scriptPubKey = CScript() << OP_RETURN << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"_hex;
894892
t.vout[0].nValue = 0;
895893
t.vout[1].scriptPubKey = CScript() << OP_RETURN << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"_hex;
896894
t.vout[1].nValue = 0;
897-
CheckIsNotStandard(t, "multi-op-return");
895+
CheckIsStandard(t);
898896

899897
t.vout[0].scriptPubKey = CScript() << OP_RETURN << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"_hex;
900898
t.vout[1].scriptPubKey = CScript() << OP_RETURN;
901-
CheckIsNotStandard(t, "multi-op-return");
899+
CheckIsStandard(t);
902900

903901
t.vout[0].scriptPubKey = CScript() << OP_RETURN;
904902
t.vout[1].scriptPubKey = CScript() << OP_RETURN;
905-
CheckIsNotStandard(t, "multi-op-return");
903+
CheckIsStandard(t);
904+
905+
t.vout[0].scriptPubKey = CScript() << OP_RETURN << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"_hex;
906+
t.vout[1].scriptPubKey = CScript() << OP_RETURN << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"_hex;
907+
const auto datacarrier_size = t.vout[0].scriptPubKey.size() + t.vout[1].scriptPubKey.size();
908+
CheckIsStandard(t); // Default max relay should never trigger
909+
CheckIsStandard(t, /*max_op_return_relay=*/datacarrier_size);
910+
CheckIsNotStandard(t, "datacarrier", /*max_op_return_relay=*/datacarrier_size-1);
906911

907912
// Check large scriptSig (non-standard if size is >1650 bytes)
908913
t.vout.resize(1);

test/functional/mempool_accept.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -326,13 +326,6 @@ def run_test(self):
326326
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}],
327327
rawtxs=[tx.serialize().hex()],
328328
)
329-
tx = tx_from_hex(raw_tx_reference)
330-
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
331-
tx.vout = [tx.vout[0]] * 2
332-
self.check_mempool_result(
333-
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'multi-op-return'}],
334-
rawtxs=[tx.serialize().hex()],
335-
)
336329

337330
self.log.info('A timelocked transaction')
338331
tx = tx_from_hex(raw_tx_reference)

test/functional/mempool_datacarrier.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818

1919
from random import randbytes
2020

21+
# The historical maximum, now used to test coverage
22+
CUSTOM_DATACARRIER_ARG = 83
2123

2224
class DataCarrierTest(BitcoinTestFramework):
2325
def set_test_params(self):
2426
self.num_nodes = 4
2527
self.extra_args = [
26-
[],
27-
["-datacarrier=0"],
28-
["-datacarrier=1", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"],
28+
[], # default is uncapped
29+
["-datacarrier=0"], # no relay of datacarrier
30+
["-datacarrier=1", f"-datacarriersize={CUSTOM_DATACARRIER_ARG}"],
2931
["-datacarrier=1", "-datacarriersize=2"],
3032
]
3133

@@ -41,32 +43,33 @@ def test_null_data_transaction(self, node: TestNode, data, success: bool) -> Non
4143
self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_hex)
4244
assert tx.rehash() in node.getrawmempool(True), f'{tx_hex} not in mempool'
4345
else:
44-
assert_raises_rpc_error(-26, "scriptpubkey", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex)
46+
assert_raises_rpc_error(-26, "datacarrier", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex)
4547

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

49-
# By default, only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes).
50-
default_size_data = randbytes(MAX_OP_RETURN_RELAY - 3)
51-
too_long_data = randbytes(MAX_OP_RETURN_RELAY - 2)
52-
small_data = randbytes(MAX_OP_RETURN_RELAY - 4)
51+
# By default, any size is allowed.
52+
53+
# If it is custom set to 83, the historical value,
54+
# only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes).
55+
custom_size_data = randbytes(CUSTOM_DATACARRIER_ARG - 3)
56+
too_long_data = randbytes(CUSTOM_DATACARRIER_ARG - 2)
57+
extremely_long_data = randbytes(MAX_OP_RETURN_RELAY - 200)
5358
one_byte = randbytes(1)
5459
zero_bytes = randbytes(0)
5560

56-
self.log.info("Testing null data transaction with default -datacarrier and -datacarriersize values.")
57-
self.test_null_data_transaction(node=self.nodes[0], data=default_size_data, success=True)
58-
59-
self.log.info("Testing a null data transaction larger than allowed by the default -datacarriersize value.")
60-
self.test_null_data_transaction(node=self.nodes[0], data=too_long_data, success=False)
61+
self.log.info("Testing a null data transaction succeeds for default arg regardless of size.")
62+
self.test_null_data_transaction(node=self.nodes[0], data=too_long_data, success=True)
63+
self.test_null_data_transaction(node=self.nodes[0], data=extremely_long_data, success=True)
6164

6265
self.log.info("Testing a null data transaction with -datacarrier=false.")
63-
self.test_null_data_transaction(node=self.nodes[1], data=default_size_data, success=False)
66+
self.test_null_data_transaction(node=self.nodes[1], data=custom_size_data, success=False)
6467

6568
self.log.info("Testing a null data transaction with a size larger than accepted by -datacarriersize.")
66-
self.test_null_data_transaction(node=self.nodes[2], data=default_size_data, success=False)
69+
self.test_null_data_transaction(node=self.nodes[2], data=too_long_data, success=False)
6770

68-
self.log.info("Testing a null data transaction with a size smaller than accepted by -datacarriersize.")
69-
self.test_null_data_transaction(node=self.nodes[2], data=small_data, success=True)
71+
self.log.info("Testing a null data transaction with a size equal to -datacarriersize.")
72+
self.test_null_data_transaction(node=self.nodes[2], data=custom_size_data, success=True)
7073

7174
self.log.info("Testing a null data transaction with no data.")
7275
self.test_null_data_transaction(node=self.nodes[0], data=None, success=True)

test/functional/test_framework/messages.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@
7373
DEFAULT_ANCESTOR_LIMIT = 25 # default max number of in-mempool ancestors
7474
DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants
7575

76-
# Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
77-
MAX_OP_RETURN_RELAY = 83
76+
77+
# Default setting for -datacarriersize.
78+
MAX_OP_RETURN_RELAY = 100_000
79+
7880

7981
DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours
8082

0 commit comments

Comments
 (0)