As suggested in #25787, this PR adds tests for datacarrier
and datacarriersize
initialization options.
Close #25787.
datacarrier
and datacarriersize
options
#25792
Concept ACK
Thanks for improving test coverage! Seems like the implementation can be simplified in many ways, I’d suggest to:
ADDRESS_OP_TRUE
), since we are only concerned with modifying outputs here; there is no need to use the P2PK modeself.wallet.rescan_utxos()
(see many other tests, e.g. p2p_filter.py)create_null_data_transaction
helper by creating a zero-fee template tx with MiniWallet’s create_self_transfer
(we are not interested in setting the inputs manually), append the datacarrier output and then deduct the fee manually, e.g. something like this should be sufficient (admittedly, still a bit hacky though):0tx = self.wallet.create_self_transfer(fee_rate=0)["tx"]
1tx.vout.append(CTxOut(nValue=0, scriptPubKey=script))
2tx.vout[0].nValue -= tx.get_vsize() # simply pay 1sat/vbyte fee
3return tx
319@@ -320,6 +320,7 @@
320 'feature_unsupported_utxo_db.py',
321 'feature_logging.py',
322 'feature_anchors.py',
323+ 'feature_datacarrier.py',
47+ tx = self.create_null_data_transaction(script)
48+
49+ self.test_transaction(tx, success=True)
50+
51+ self.log.info("Testing a null data transaction larger than allowed by the default -datacarriersize value.")
52+ data = b"Hello world!" * 10
data = b"Hello world!" * 7
would have worked just as well here.
I’d recommend making these values just above the limits to detect off-by-one errors. For example, instead of composing it of “hello world” strings, use a MAX_OP_RETURN_RELAY
-length string.
Concept ACK, thanks for working on this.
To test differences between a default and a configured node, I’d recommend you create 2 nodes with different policies and submit transactions to both, instead of restarting.
37+ tx.vout[0].nValue -= tx.get_vsize() # simply pay 1sat/vbyte fee
38+ return tx
39+
40+ def run_test(self):
41+ self.node = self.nodes[0]
42+ self.wallet = MiniWallet(self.nodes[0])
nit (feel free to ignore)
0 self.wallet = MiniWallet(self.node)
self.node
is initialized in set_test_params()
, test_transaction()
can also use it.
14+from test_framework.messages import (
15+ CTxOut,
16+)
17+from test_framework.util import (
18+ assert_raises_rpc_error,
19+)
nit: I think one-line alphabetically sorted is a bit easier to read
0from test_framework.messages import CTxOut
1from test_framework.script import CScript, OP_RETURN
2from test_framework.test_framework import BitcoinTestFramework
3from test_framework.util import assert_raises_rpc_error
4from test_framework.wallet import MiniWallet
16+)
17+from test_framework.util import (
18+ assert_raises_rpc_error,
19+)
20+
21+class DataCarrierTest(BitcoinTestFramework):
25+ def test_transaction(self, tx, success):
26+ tx_hex = tx.serialize().hex()
27+
28+ if success:
29+ self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx_hex)
30+ assert(tx.rehash() in self.nodes[0].getrawmempool(True))
adding a short message can help debugging:
0 assert(tx.rehash() in self.nodes[0].getrawmempool(True), f"{tx_hex} not in mempool")
66+ self.restart_node(0, extra_args=["-datacarrier=true", "-datacarriersize=5"])
67+
68+ tx4 = self.create_null_data_transaction(script)
69+
70+ self.test_transaction(tx4, success=False)
71+
29+ self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx_hex)
30+ assert(tx.rehash() in self.nodes[0].getrawmempool(True))
31+ else:
32+ assert_raises_rpc_error(-26, "scriptpubkey", self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx_hex)
33+
34+ def create_null_data_transaction(self, script):
nit: the style guidelines suggest adding type hints
0 def create_null_data_transaction(self, script: CScript) -> CTransaction:
48+
49+ self.test_transaction(tx, success=True)
50+
51+ self.log.info("Testing a null data transaction larger than allowed by the default -datacarriersize value.")
52+ data = b"Hello world!" * 10
53+ script2 = CScript([OP_RETURN, data])
nit: I think this variable name could be a bit more descriptive (and script
could be renamed to short_script
)
0 too_long_script = CScript([OP_RETURN, data])
Approach ACK 8677781f4
Just left a few style nits
38+ MAX_OP_RETURN_DATA_SIZE = 80
39+
40+ self.wallet = MiniWallet(self.nodes[0])
41+ self.wallet.rescan_utxos()
42+
43+ data = os.urandom(MAX_OP_RETURN_DATA_SIZE)
random_bytes
helper instead (to be found in test_framework.util
). (Background: also used the urandom
approach for generating random data recently in a PR and got a review with good counter-arguments: #25625 (review))
LGTM. Some more suggestions (feel free to ignore, or do in a follow-up):
create_null_data_transaction
method would be IMHO more logical if only the null-data payload (type bytes
) is passed; the scriptPubKey with prepended OP_RETURN could then be created in there instead of the need to do that repeatedly at the caller’s sidedatacarriersize
limitMAX_OP_RETURN_RELAY
(which is 83 and not 80 though, as it denotes the serialized size of the whole scriptPubKey; “+1 for OP_RETURN, 2 for the pushdata opcodes”)The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
No conflicts as of last run.
ACK 1650f9457bc1c3cfecde554522f3c38ca18ef04c
edit: modulo MarcoFalke’s comment regarding -datacarrier=true
25+ def set_test_params(self):
26+ self.num_nodes = 3
27+ self.extra_args = [
28+ [],
29+ ["-datacarrier=false"],
30+ ["-datacarrier=true", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"]
This parses as =0
(The test fails regardless of the second option)
Probably a good time to make this an init error finally.
FYI for anyone else confused about this, see the docs of InterpretBool()
:
non-numeric string values like “foo”, return 0
so -datacarrier=false
and -datacarrier=true
all resolve to -datacarrier=0
.
As such, should be:
0 ["-datacarrier=0"],
1 ["-datacarrier=1", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"]
Good catch MarcoFalke!
0@@ -0,0 +1,79 @@
1+#!/usr/bin/env python3
2+# Copyright (c) 2020-2021 The Bitcoin Core developers
3+# Distributed under the MIT software license, see the accompanying
4+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+"""Test datacarrier functionality"""
6+from test_framework.messages import (
7+ CTxOut,
8+ CTransaction,
yocto-nit: sorted order
0 CTransaction,
1 CTxOut,
ACK 4bebb81622eb71a83db1d46ca84c09f7e1ab7f9a
I think the methods test_transaction
and create_null_data_transaction
could be merged into one (e.g. something like test_null_data_transaction
with node, data and success parameters) to reduce it to even less code at the callers side, since we don’t reuse any of the transactions anyway, but no hard feelings.
re-ACK 4bebb81 - verified changes are limited to fixing the startup arguments and adding a new test to check the happy path with a lower-than-default datacarriersize
01: 1650f9457 ! 1: 4bebb8162 test: add tests for `datacarrier` and `datacarriersize` options
1 @@ test/functional/mempool_datacarrier.py (new)
2 + self.num_nodes = 3
3 + self.extra_args = [
4 + [],
5 -+ ["-datacarrier=false"],
6 -+ ["-datacarrier=true", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"]
7 ++ ["-datacarrier=0"],
8 ++ ["-datacarrier=1", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"]
9 + ]
10 +
11 + def test_transaction(self, node: TestNode, tx: CTransaction, success: bool):
12 @@ test/functional/mempool_datacarrier.py (new)
13 + # By default, only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes).
14 + default_size_data = random_bytes(MAX_OP_RETURN_RELAY - 3)
15 + too_long_data = random_bytes(MAX_OP_RETURN_RELAY - 2)
16 ++ small_data = random_bytes(MAX_OP_RETURN_RELAY - 4)
17 +
18 + self.log.info("Testing null data transaction with default -datacarrier and -datacarriersize values.")
19 + tx = self.create_null_data_transaction(default_size_data)
20 @@ test/functional/mempool_datacarrier.py (new)
21 + tx3 = self.create_null_data_transaction(default_size_data)
22 + self.test_transaction(self.nodes[1], tx3, success=False)
23 +
24 -+ self.log.info("Testing a null data transaction with a small -datacarriersize value.")
25 ++ self.log.info("Testing a null data transaction with a size larger than accepted by -datacarriersize.")
26 + tx4 = self.create_null_data_transaction(default_size_data)
27 + self.test_transaction(self.nodes[2], tx4, success=False)
28 +
29 ++ self.log.info("Testing a null data transaction with a size smaller than accepted by -datacarriersize.")
30 ++ tx5 = self.create_null_data_transaction(small_data)
31 ++ self.test_transaction(self.nodes[2], tx5, success=True)
32 ++
33 +
34 +if __name__ == '__main__':
35 + DataCarrierTest().main()
36
37 ## test/functional/test_framework/messages.py ##
38 -@@ test/functional/test_framework/messages.py: FILTER_TYPE_BASIC = 0
39 -
40 - WITNESS_SCALE_FACTOR = 4
41 +@@ test/functional/test_framework/messages.py: WITNESS_SCALE_FACTOR = 4
42 + DEFAULT_ANCESTOR_LIMIT = 25 # default max number of in-mempool ancestors
43 + DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants
44
45 +# Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
46 +MAX_OP_RETURN_RELAY = 83
Co-authored-by: Sebastian Falbesoner <sebastian.falbesoner@gmail.com>
67@@ -68,6 +68,9 @@
68 DEFAULT_ANCESTOR_LIMIT = 25 # default max number of in-mempool ancestors
69 DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants
70
71+# Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
72+MAX_OP_RETURN_RELAY = 83
messages.py
for default values of settings. Maybe leave them in the only test that needs them for now (if there is only one), or create a new default_settings.py module (or a mempool.py one?)