-
Notifications
You must be signed in to change notification settings - Fork 7
Add annex data carrier option behind -annexcarrier option #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -213,8 +213,15 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) | |
| return true; | ||
| } | ||
|
|
||
| bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) | ||
| bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, bool allow_annex_data) | ||
| { | ||
| // For now allow 0 to MAX_ANNEX_DATA bytes push to allow drop-in replacement via BIP PR#1381 | ||
| // but we allow it spread over any number of inputs based on this budget: | ||
| // 1(witness stack element size) + 1(annex marker) + 1(payload len) + MAX_ANNEX_DATA(payload) | ||
| size_t annex_bytes_left{1 + 1 + 1 + MAX_ANNEX_DATA}; | ||
|
|
||
| if (!allow_annex_data) annex_bytes_left = 0; | ||
|
|
||
| if (tx.IsCoinBase()) | ||
| return true; // Coinbases are skipped | ||
|
|
||
|
|
@@ -266,13 +273,18 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) | |
|
|
||
| // Check policy limits for Taproot spends: | ||
| // - MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit for stack item size | ||
| // - No annexes | ||
| // - Limited annex format | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the 0x50 <1 byte data len> start to be used by more experimental applications on inquisition, of course we might have conflicts with assigning consensus semantic for taproot annex, maybe annexcarrier could be its own annex "tag" by introducing a marker value before the len for a v0.2 of annexcarrier. I definitely can see a "annexcarrier" generic data payload where there is no meaning attached to the record itself, for client-reserved value (like nSequence was used sometimes in the past) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per bitcoin/bips#1381 |
||
| if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) { | ||
| // Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341) | ||
| Span stack{tx.vin[i].scriptWitness.stack}; | ||
| if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { | ||
| // Annexes are nonstandard as long as no semantics are defined for them. | ||
| return false; | ||
| const auto& annex = stack.back(); | ||
|
|
||
| if (annex.size() >= annex_bytes_left) return false; | ||
| annex_bytes_left -= annex.size() + 1; // bip141 witness stack element size included | ||
|
|
||
| // limit annex format to allow for future expansion | ||
| if (annex.size() < 2 || annex.size() != static_cast<size_t>(annex[1]) + 2) return false; | ||
| } | ||
| if (stack.size() >= 2) { | ||
| // Script path spend (2 or more stack elements after removing optional annex) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,9 +132,9 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) | |
| * 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements | ||
| * These limits are adequate for multisignatures up to n-of-100 using OP_CHECKSIG, OP_ADD, and OP_EQUAL. | ||
| * | ||
| * Also enforce a maximum stack item size limit and no annexes for tapscript spends. | ||
| * Also enforce a maximum stack item size limit and limited annexes for tapscript spends. | ||
| */ | ||
| bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); | ||
| bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, bool allow_annex_data); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it would be better to pull the annex check out of here, into its own function that takes a set of flags like
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Future Work(TM), agreed |
||
|
|
||
| /** Compute the virtual transaction size (weight reinterpreted as bytes). */ | ||
| int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| #include <variant> | ||
|
|
||
| static const bool DEFAULT_ACCEPT_DATACARRIER = true; | ||
| static const bool DEFAULT_ACCEPT_ANNEXDATA = true; | ||
|
|
||
| class CKeyID; | ||
| class CScript; | ||
|
|
@@ -38,6 +39,17 @@ class CScriptID : public BaseHash<uint160> | |
| */ | ||
| static const unsigned int MAX_OP_RETURN_RELAY = 83; | ||
|
|
||
| /** | ||
| * Allows up to one annex data to be embedded in a transaction, counting towards the | ||
| * same data carrier limit as OP_RETURN values | ||
| */ | ||
| extern bool accept_annex_data; | ||
|
|
||
| /** | ||
| * Static maximum of annex data per transaction | ||
| */ | ||
| static const unsigned int MAX_ANNEX_DATA = 126; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it wasn't for this limitation, inscriptions could also use the annex and with that become more chain-space efficient because there's no commit/reveal scheme needed anymore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it may be worth to make this policy change so that you can at least have a single large data block too. There are other applications besides inscriptions that require more than 126 bytes.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This limitation is specifically in place so I don't have to rely on a more complex encoding ala #9 to start experimentation with useful size annexes. It's not a principled limitation with respect to DoS concerns or anything. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TLV is nice, but also it doesn't seem quite clear how any of this is going to be validated, if ever. The basic case of just storing data though is immediately useful. What would you think of using
for this basic case, and reserve other values of the second byte for a future tlv format? This is also more compact for lengths > 126, because the length itself doesn't need to be encoded.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For larger sizes it can be a bit annoying, since currently taproot checksigs don't cover other inputs annexes. Limiting total annex to 126 bytes means a griefing counter-party can only do so much griefing. I'm agnostic to what the specific format is; I'm looking to experiment with small sizes. |
||
|
|
||
| /** | ||
| * Mandatory script verification flags that all new blocks must comply with for | ||
| * them to be valid. (but old blocks may not comply with) Currently just P2SH, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| """Test datacarrier functionality""" | ||
| from test_framework.messages import ( | ||
| CTxOut, | ||
| MAX_ANNEX_DATA, | ||
| MAX_OP_RETURN_RELAY, | ||
| ) | ||
| from test_framework.script import ( | ||
|
|
@@ -25,7 +26,7 @@ def set_test_params(self): | |
| self.num_nodes = 3 | ||
| self.extra_args = [ | ||
| [], | ||
| ["-datacarrier=0"], | ||
| ["-datacarrier=0", "-annexcarrier=0"], | ||
| ["-datacarrier=1", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"] | ||
| ] | ||
|
|
||
|
|
@@ -42,6 +43,20 @@ def test_null_data_transaction(self, node: TestNode, data: bytes, success: bool) | |
| else: | ||
| assert_raises_rpc_error(-26, "scriptpubkey", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex) | ||
|
|
||
| def test_annex_data_transaction(self, node: TestNode, data: bytes, success: bool) -> None: | ||
| tx = self.wallet.create_self_transfer(fee_rate=0)["tx"] | ||
| tx.wit.vtxinwit[0].scriptWitness.stack = tx.wit.vtxinwit[0].scriptWitness.stack + [bytes.fromhex("50") + len(data).to_bytes(1, 'little') + bytes.fromhex("ff"*len(data))] | ||
| tx.vout[0].nValue -= tx.get_vsize() # simply pay 1sat/vbyte fee | ||
|
|
||
| tx_hex = tx.serialize().hex() | ||
|
|
||
| if success: | ||
| self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_hex) | ||
| assert tx.rehash() in node.getrawmempool(True), f'{tx_hex} not in mempool' | ||
| else: | ||
| assert_raises_rpc_error(-26, "bad-witness-nonstandard", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex) | ||
|
|
||
|
|
||
| def run_test(self): | ||
| self.wallet = MiniWallet(self.nodes[0]) | ||
| self.wallet.rescan_utxos() | ||
|
|
@@ -51,6 +66,20 @@ def run_test(self): | |
| too_long_data = random_bytes(MAX_OP_RETURN_RELAY - 2) | ||
| small_data = random_bytes(MAX_OP_RETURN_RELAY - 4) | ||
|
|
||
| small_annex = random_bytes(0) | ||
| large_annex = random_bytes(MAX_ANNEX_DATA) | ||
| oversize_annex = random_bytes(127) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe |
||
|
|
||
| self.log.info("Testing annex data transaction with default -annexcarrier value.") | ||
| self.test_annex_data_transaction(node=self.nodes[0], data=small_annex, success=True) | ||
| self.test_annex_data_transaction(node=self.nodes[0], data=large_annex, success=True) | ||
| self.test_annex_data_transaction(node=self.nodes[0], data=oversize_annex, success=False) | ||
|
|
||
| self.log.info("Testing annex data transaction with false -annexcarrier value.") | ||
| self.test_annex_data_transaction(node=self.nodes[1], data=small_annex, success=False) | ||
| self.test_annex_data_transaction(node=self.nodes[1], data=large_annex, success=False) | ||
| self.test_annex_data_transaction(node=self.nodes[1], data=oversize_annex, success=False) | ||
|
|
||
| self.log.info("Testing null data transaction with default -datacarrier and -datacarriersize values.") | ||
| self.test_null_data_transaction(node=self.nodes[0], data=default_size_data, success=True) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Payload budget spread over any number of inputs might not work well for non-interactive combination of unordered pair of inputs/outputs under
SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, and such batching might still exist with eltoo LN, no ?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are larger issues with annex budgeting we have to solve I think due to the deficiency in sighashes over the annex. Let's continue thinking on this,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if you want to do a non-interactive combination of inputs/outputs it's best to do it by having multiple transactions -- ie Ai to Ao, Bi to Bo, Ci to Co1 and Co2, would be three transactions each paying 0 fees and each with an ephemeral anchor output, than a fourth transaction D which pays fees for all of them and spends all their ephemeral anchors. That minimises malleability so you can spend "Ao" without having to worry about its txid changing due to a fee bump, and I think reduces the complexity of dealing with partial rbfs etc. Presuming things work out that way, each of Ai, Bi, Ci naturally get their own budget for annex data.