Skip to content

[Taproot API Project] flatten internal representation of TapTree #808

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

apoelstra
Copy link
Member

This PR completely rewrites the TapTree module to store a taptree as a flat list of leaves and heights. For a non-ToPublicKey key this is the only useful information that we ever extract from this, so storing a big recursive structure is wasteful. We also eliminate recursive code from everything TapTree related, including the implicit Drop impl.

In the next PR we'll introduce a new tree structure that contains control block information -- but TapTree can't be this structure because TapTree doesn't assume that its keys can be serialized.

Also breaks the iter_scripts iterator API in a couple small ways.

… in Tr

This makes it impossible to construct a `TapTree` whose depth exceeds
the limit, which means that we can assume that our maximum depth is 128
in all of our code.

Also introduces a new error type for this case distinct from "maximum
recursive depth exceeded" which is supposed to be about limitations of
this library, or at least of Script, rather than about the Taproot
limit.
This constructor will be part of the new API introduced in the following
"flatten TapTree" commit. To reduce the diff of that commit, introduce it
now.
We fix the return value of Tr::tap_tree to return a Option<&T> instead
of an &Option<T>; we already do this in Descriptor::tap_tree, which is
likely to be more commonly called ... and both methods are pretty
obscure.

Also remove the iter_scripts method in favor of leaves. We did this
rename in the previous PR. However, that PR broke its API anyway by
making it yield an opaque struct rather than a tuple. Better to just do
all the breakage at once.

Maaybe we should backport the rename to 12.x as a pure rename, so that
people will update their code and get nicer error messages.
This replaces the internal representation of `TapTree` with one that
matches the BIP371 iterator: it's a vector of depths and leaves and
nothing else. It does not track its maximum height (except during
construction to return an error if 128 is exceeded); it does not track
"internal nodes"; it does not offer an API to iterate through its
"internal nodes" except by calling Liftable::lift to get a Semantic copy
of the tree.

This greatly simplifies the type and many algorithms associated with it.
It automatically eliminates recursion from display, lifting, dropping,
and so on.

As it turns out, we never use TapTrees except to iterate over the
leaves, and there is no data about internal nodes that we can even
compute, let alone store, so there's no reason to have nodes for them.

In later commit/PR we will create a second TapTree-like type, which can
be constructed from a TapTree for ToPublicKey keys, and can be used to
extract Merkle paths and control blocks. But without ToPublicKey we
can't compute these, so TapTree is not the type for that.
@apoelstra
Copy link
Member Author

Ooo, it looks like I broke our satisfaction tests somehow.

@apoelstra apoelstra marked this pull request as draft April 14, 2025 19:10
@apoelstra
Copy link
Member Author

The satisfaction tests seem to require bitcoin 22, which is a bit of a PITA for me to obtain since it needs boost 1.75 to build. I will work on updating it to a more modern version of Core in a separate PR before moving forward on this.

This gets our benchmarks to roughly where we were before we flattened
the data structure. The `_tr_oneleaf_` benchmarks appear to be
measurably faster than before but everything else is pretty-much the
same.

Since the old code was definitely slower -- using Arcs, making tons of
tiny allocations, storing internal nodes -- we can infer that actual
construction of TapTrees is just not a meaningful portion of the time
taken to parse expressions, and further optimization effort should go to
the expression parser.
@apoelstra apoelstra force-pushed the 2025-04--taproot-3 branch from fd88f16 to b25ec35 Compare April 15, 2025 14:14
@apoelstra
Copy link
Member Author

Actually I'll just do it independently. Since I was able to get the integration tests running it was easy for me to find the bug -- I had introduced an off-by-one error in our maximum Taproot depth checking.

It turns out that BIP 341 specifies valid depths from 0 to 128 inclusive so actually there are 129 depths that might be used. This is surprisingly inconvient for something that sipa designed in parallel with a C++ implementation, but it is what it is.

Anyway I extended my u128-based bitmap in TapTreeBuilder with an extra bool, changed a >= check to a >, and this should be good to go now.

TapTreeIter is now a wrapper around core::slice::Iter. So we can
propagate several traits: ExactSizeIterator, DoubleEndedIterator and
FusedIterator.

Annoyingly we cannot propagate TrustedLenIterator since that's
nightly-only, nor can we propagate Default which was introduced some
time after 1.63.0.

core::slice::Iter also implements AsRef<&[T]> which is tempting, but we
don't propagate that since it feels like it reveals too much about the
internals of TapTreeIter.
@apoelstra apoelstra force-pushed the 2025-04--taproot-3 branch from b25ec35 to adf3164 Compare April 15, 2025 14:33
@apoelstra apoelstra marked this pull request as ready for review April 15, 2025 15:49
Copy link
Member Author

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On adf3164 successfully ran local tests

@sanket1729
Copy link
Member

The satisfaction tests seem to require bitcoin 22

Interesting, is this because of some deprecated RPCs?

@apoelstra
Copy link
Member Author

It seems to be some change in how default wallets are created (or not).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants