Improvements to taproot script iterator#924
Improvements to taproot script iterator#924apoelstra merged 9 commits intorust-bitcoin:masterfrom BP-WG:taproot/iter-fix
Conversation
|
@dr-orlovsky, I mentioned earlier that this will delay the release even more. Some things like documentation fixes are easy to review, but others take time. I think I also made a mistake in reviewing #901 (due to a bug in psbt TapTree iterator). This caused a bug while implementing #901, because here iterator only needs to iterate leaves, but also hidden branches. I repeat some of these things are non-trivial and will take time for careful review. I don't think any of the above should be required for any use cases. If you want to add op_return commitments, that is already possible using iterator API if we fix it also output the leaf version. I will make a PR to address the bug fixes and constrain the code so that we don't allow hidden branches in this release. I only think we should restrict ourselves to bug fixes, there should be a code freeze and that is all. People have been waiting for far too long for this release. We will make another release soon after improving the APIs, adding hidden branches. |
|
From my understanding this PR makes iterator better than in the current master. The issues you referenced are unrelated to this PR. I am happy to review your PR fixing hidden branches disallowed in PSBT, but not sure I understand how this can speed up the release. I am happy to have the release ASAP even without this PR - as much as I am happy to see release cycle not happening once or just twice a year. Please let me know what can I do to speed up this process. |
|
@dr-orlovsky, sorry for being unclear. I was partly frustrated with myself for not catching the bug about hidden branches in the iterator PR. I ended up blaming it on the push for the PR, but I should not have ACKed in rust in the first place. I think that will considerably change the way the iterator PR #901 should have been done. It was not an issue for Psbt, but the spec does not support hidden branches. But the goal of the iterator PR was to iterate so that users like you can re-create the same spendinfo using the builder API. However, this is not possible if there are hidden leaves. builder = builder.add_hidden(depth, hash);
// But when we iterate using the API, we don't traverse this tree completely because we skip the hidden nodes.
I would like to do this cleanly as follows, but that would change everything in the taproot module. Don't know if we want to do such a change at this point. But without this #901 remains buggy. // Internally used structure to store information about taproot leaf node
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LeafInfo {
// tap leaf (explicit script or hidden)
pub(crate) tap_leaf : TapLeaf,
// The merkle proof(hashing partners) to get this node
pub(crate) merkle_branch: TaprootMerkleBranch,
}
/// Leaves of a taproot tree. Leaves are either the specified with [`Script`] and
/// corresponding [`LeafVersion`] or with a hidden [`TapBranchHash`].
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) enum TapLeaf {
/// [`Script`] with it's [`LeafVersion`]
Script(Script, LeafVersion),
/// Hidden node with a given [`TapBranchHash`]
Hidden(TapBranchHash),
}We have two options:
I am leaning towards 2) because 1) will be hard to review quickly and do correctly.
Regardless of what we do in this PR, I think we should work on a release notes PR/ changelog PR. |
|
@dr-orlovsky, here is something that I think we should end up doing. I don't have my usual setup and am using cafe internet, can't code properly. But here is something rough that I think we should be doing. Let me know what you about this? sanket1729@a5bd3db. Would it be possible to update this PR so that we do something as suggested there? This was partly my hesitation to ACK this PR |
|
Thank you for explaining details. I am really sorry for missing the part with the hidden branches when I was working on the original iterator implementation. I think there is a third way of solving this issue: iterator should return |
|
@sanket1729 Thinking more, I see that the real bug is not the way we iterate
So, to address the problem we should introduce |
|
Filed an issue: #928 |
|
Ah, sorry @sanket1729, the whole discussion above all is a non-issue: |
sanket1729
left a comment
There was a problem hiding this comment.
Some comments, plus I think the major remark from the previous review still stands. We also need to update the iterator to yield hidden nodes.
src/util/taproot.rs
Outdated
There was a problem hiding this comment.
I am not super comfortable with this. I think we should not expose any constructors.
There was a problem hiding this comment.
LeafInfo can be constructed correctly using NodeInfo constructors(new_leaf or new_leaf_with_ver). Moreover, I don't see any use in exposing this.
This is possibly dangerous because the user needs to make sure that Merkle proof path is correct.
There was a problem hiding this comment.
I just need a type having version, script and merkle path bound together downstream. I need it all the way around. If we can't have it here, I will not be able to reconstruct it, since everything here is private.
There was a problem hiding this comment.
You can call control_block() method on TaprootSpendInfo to get it. The iterator also outputs LeafInfo that you can use downstream. All its members have accessor functions.
My point is that we should only expose these after they are finalized. I don't want users creating misc leaves with Merkle proofs that don't verify. Let the high level take care of building it.
There was a problem hiding this comment.
Calling control_block() requires finalization, requiring internal key and secp object. Very annoying and destroying any normal API downstream.
There was a problem hiding this comment.
The iterator also outputs LeafInfo that you can use downstream
The iterator works right and it has public accessor functions? You can fetch all the information from there and combine it in a custom type downstream.
If we have a new constructor that does not check if the leaf would be used correctly, we might as well expose all members as public fields.
There was a problem hiding this comment.
I had to develop my own tree types required for doing OP_RETURN taproot commitments, and those types also have iterators. I can't construct and return LeafInfo from that iterators without it having constructors. Ok, I probably need to do another type for that and just provide convertor for it from this LeafInfo. I agree that exposing it to others may be risky.
src/util/taproot.rs
Outdated
There was a problem hiding this comment.
As mentioned previously, I don't like constructors here. I think we should let the internal algorithms do the proof construction. Plus, there is no API in rust-bitcoin that uses LeafInfo as an argument.
Sorry, not getting it. We do not have hidden nodes in |
This is a fine solution for now. But eventually(post-release), I think we should have it. When we have it, we cannot use the iterator to re-create taproot trees if there are hidden leaves. The logic with my comment #895 (comment) won't work if there are partial descriptors. |
Unfortunately I found it impossible to make deterministic OP_RETURN commits in the last DFS leaf, since adding a leaf will change ordering of the nodes in the parent and with >80% probability "last" DFS leaf will not be last leaf anymore for a tree with >2 levels. So I had to change the schema to use the level 0 or level 1 leaf (+proof that the other sibling does not have OP_RETURN commitment). Which required A LOT of coding, re-building the whole tree into a tree-like representation and allowing arbitrary "plastic surgery" on it: https://github.com/LNP-BP/descriptor-wallet/blob/master/scripts/src/taproot.rs (probably the most interesting part is in tests showcasing the use of the API)
I see no reason of iterating hidden leafs with the same iterator which iterates the scripts. It can be a separate iterator - and the script iterator must always run only on complete trees (and fail to be constructed on incomplete one). We may have iterator over both (to be able to iterate when not all scripts are known), but this should be a dedicated third iterator. |
The last leaf will remain last in the DFS walk no matter what. That is the point of DFS ordering. If you use the algorithm that I mentioned in the comment, it is guaranteed to happen. To me, the point of the iterator API was so that users could deterministically get the same tree again using the builder. |
Sorry for not explaining properly and messing with DFS: I meant the fact that if you have a partial tree (and in my case for proving the commitment you'd like not to store the whole tree with all the scripts on the client side) you can't prove that the node is last in DFS order and you need the node to be always on the right side of each merkle path to it (in order to be able to prove that there is no alternative commitment in the whole tree) - which is impossible to have. DFS order is simply inapplicable here.
Ok, that is a different iterator. The one I need should iterate only through the known scripts. |
Yes, you can! Why not? The Merkle proof should not consist of any element that has a hashing partner on the right-hand side? The last node in DSF is guaranteed to be this property. |
No, it does not guarantee. I can easily construct the tree where DFS-last node will not be right-hand side in consensus ordering. |
This one |
|
I see: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-8. This is really what is causing the issue. There is no ordering of leaves guaranteed because multiple trees have the same merkle-root. We cannot assert anything about the position of leaves, we only assert the depth and the that are committed. Now, this seems like a very hard problem to solve. Is same as |
|
Not because of that. There is nothing that will guarantee the |
|
@sanket1729 I have addressed your comments |
sanket1729
left a comment
There was a problem hiding this comment.
ACK 435c44f04a82f7c17b0865e2c97ba9de9a479a51. Thanks for addressing all the comments, I am happy with the current state.
Don't have any strong opinions on ScriptLeaf vs LeafInfo.
|
@sanket1729 thank you for taking your time for all these reviews cycles! Regarding the name, I thought that when we will add support for the hidden nodes in |
tcharding
left a comment
There was a problem hiding this comment.
FWIW, all bar one nit, looks good to me.
src/util/taproot.rs
Outdated
There was a problem hiding this comment.
nit; The comment and the return type do not match.
There was a problem hiding this comment.
I will leave it for a post-merge fix PR not to discard existing review, if you are ok with that
Kixunil
left a comment
There was a problem hiding this comment.
While I'm not sure about some things, at least I don't see any obvious bugs.
src/util/taproot.rs
Outdated
There was a problem hiding this comment.
"Computes" sounds like this is an expensive operation which it is not.
src/util/taproot.rs
Outdated
There was a problem hiding this comment.
Since the depth is guaranteed to be <127 by the TaprootBuilder type
There was a problem hiding this comment.
For follow up fixup: add a comment about this or find a cleaner design.
There was a problem hiding this comment.
Cleaner design requires MSRV bump and TryInto trait
src/util/taproot.rs
Outdated
|
@Kixunil thank you for reviewing. Since except a single comment these seems to be nits - and I wrote my proposal on the first comment, can we please have this PR merged to get to the taproot release without the need to have next major version right after the release? |
|
I really don't like releasing poorly designed APIs just because of deadline announced a few days ahead. We're not shitcoiners. If we want to compromise then conservatively hiding the type is better compromise because exposing it is semver-compatible, the reverse is not. It's also a trivial change. Note that I'd also suggest being conservative around methods/trait impls. |
|
Tried to address all your comments, @Kixunil. @sanket1729 this requires your re-ACK, if you have some time |
Kixunil
left a comment
There was a problem hiding this comment.
Almost acked but noticed IntoIterator and not having iter() at the same time. I guess IntoIterator should be removed if TapTree is not an obvious collection of leaves. (I suspect it's not because leaves exclude intermediate nodes but I'm not sure, BTreeMap also returns only leaves.)
src/util/taproot.rs
Outdated
There was a problem hiding this comment.
Perhaps for RC fix consider impl From<TapLeafhash> for Hash
There was a problem hiding this comment.
This is not a topic of unrelated separate discussions. All hashes in taptree are tagged. I think we will need a newtype over sha256 hash TapNodeHash, which will mean "any type of taptree node hash", but not other sha256 hash - and then have From TapLeaf/BranchHash to TapNodeHash. But this is for the next release probably.
src/util/psbt/map/output.rs
Outdated
There was a problem hiding this comment.
Should we really implement IntoIterator if we renamed iter to script_leaves?
There was a problem hiding this comment.
Added commit on top removing this implementation.
Kixunil
left a comment
There was a problem hiding this comment.
ACK 565a4c135871db74ad8bd806bcf300d83c29bf0a
sanket1729
left a comment
There was a problem hiding this comment.
ACK 565a4c135871db74ad8bd806bcf300d83c29bf0a
LeafInfo structure is a useful form of representing leaf script information (script, leaf version and merkle proof).
Previously used depth and script tuple missed information about the leaf version. All three comprises already existing type `LeafInfo` which was made public in previous commits.
Without this method computation of the leaf depth requires cloning due to the requirements of merkle_branch.into_inner()
In the future, TapTree may iterate over different node types, and that's why it does not have `iter()` function; using instead `script_leafs`. Thus, we should not have IntoIterator implementation as well
| leaf_iter: root.leaves.iter() | ||
| } | ||
| } | ||
| // This should be unreachable as we Taptree is already finalized |
There was a problem hiding this comment.
Sorry, I saw this last time I reviewed and somehow must not have commented on it.
| // This should be unreachable as we Taptree is already finalized | |
| // This should be unreachable as the Taptree is already finalized. |
| #[inline] | ||
| pub fn depth(&self) -> u8 { | ||
| // The depth is guaranteed to be < 127 by the TaprootBuilder type. | ||
| // TODO: Following MSRV bump implement via `try_into().expect("")`. |
There was a problem hiding this comment.
Just bringing to your attention #952 but I'm not fussed, don't hold up merged for this comment.
sanket1729
left a comment
There was a problem hiding this comment.
ACK 3c59897. Reviewed the range-diff with the post that I previously ACKed
|
I think @Kixunil's ACK also counts here as this was just a rebase with a minor variable name change from his ACK. This brings us to 3 ACKs and this is the only release-blocking PR. cc @apoelstra |
This PR makes existing taproot script iterator to iterate
LeafScriptvalues instead of constructed(u8, &Script). First, this is more idiomatic (iterator should not construct value but iterate through real internal representation); second information about merkle path of the scripts is required for me downstream to implement OP_RETURN taproot commitments.The PR also removes unnecessary iterator type, replacing it with a slice iterator type from the core rust library.
I am asking to include this PR into RC fix scope, since it is required downstream.