Skip to content

tapdb+universe: implement new Universe tree for 1st party burns #1456

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 9 commits into
base: main
Choose a base branch
from

Conversation

Roasbeef
Copy link
Member

In this commit, we add a new interface, the BurnTree. This interface
will be used by a future sub-system to maintain the on-chain asset
commitment for a given asset group.

The structure of this tree is very similar to the existing issuance
tree. The main difference is that we'll only insert burn proofs into
this tree.

We then implement a concrete implementation of this interface
using the existing universe tree routines.

@Roasbeef Roasbeef requested review from guggero and GeorgeTsagk March 27, 2025 02:34
@levmi levmi moved this from 🆕 New to 🏗 In progress in Taproot-Assets Project Board Mar 27, 2025
@levmi levmi moved this from 🏗 In progress to 👀 In review in Taproot-Assets Project Board Mar 27, 2025
Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Did a first pass, very nice! Nothing major, so should be pretty close.
Nice unit tests too 💯

CI failures seem to be flakes.

@Roasbeef
Copy link
Member Author

Pushed up a commit to consolidate a lot of the duplication between the ignore and burn trees.

@Roasbeef
Copy link
Member Author

Roasbeef commented Apr 3, 2025

Pushed up a commit to solve the collision issues. We add a new interface for LeafKey, then make a new concrete BaseLeafKey implementation. This way any methods that needs the old leaf key can continue to accept it, so we're able to re-use many helper functions. We then add a UniqueLeafKey interface, along with a concrete implementation that'll be used by the burn tree.

For the ignore tree, we use the hash of the prev ID as a key, which already includes the asset ID, so we're safe there.

@guggero guggero self-requested a review April 3, 2025 13:29
@levmi levmi requested review from ffranr and removed request for GeorgeTsagk April 7, 2025 15:43
@lightninglabs-deploy
Copy link

@guggero: review reminder

Copy link
Member

@guggero guggero left a comment

Choose a reason for hiding this comment

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

Very nice refactor, code re-use and collision avoidance!
Code looks good to me (apart from a few mostly code style related nits, Aider still seems to struggle with our preferred code style. Or I struggle with its decisions, however you want to see it 😅 )

}
// decodeAndBuildAuthBurnLeaf decodes the raw leaf, reconstructs the key, and
// builds the AuthenticatedBurnLeaf.
func decodeAndBuildAuthBurnLeaf(dbLeaf UniverseLeaf) (
Copy link
Member

Choose a reason for hiding this comment

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

So here we're using the:

func myFuncName(param, param) (
	returnVal, returnVal) {

style, while further down we're using

func myFuncName(param, param
) (returnVal, returnVal) {

Can we please at least get consistency in terms of this decision? I way prefer the first one, for reasons previously stated.

type LeafKey struct {
// LeafKey is an interface that allows us to obtain the universe key for a leaf
// within a universe.
type LeafKey interface {
Copy link
Member

Choose a reason for hiding this comment

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

Very nice solution! Awesome to be able to leave behind the universe collision issue for new trees 💯

In this commit, we add a new interface, the BurnTree. This interface
will be used by a future sub-system to maintain the on-chain asset
commitment for a given asset group.

The structure of this tree is very similar to the existing issuance
tree. The main difference is that we'll only insert burn proofs into
this tree.
This is a bug fix to ensure that randProof makes proofs that have unique
outpoints. Otherwise, it's possbile that the outpoints collide if used
as a map key.
In this commit, we add an option to skip multi-verse insertion. This is
useful as for the new burn and ignore trees, we won't put them in a
multi-verse of all the other ignore/burn trees. Instead, they'll go in a
special tree for a given group, which contains these trees as sub-trees.
We're able to re-use a lot of code for the insertion routines here as
the tree structure is identical to the issuance tree.
…rnTree

Previously, the `BurnUniverseTree` and `IgnoreUniverseTree` implementations
contained significant duplicated logic for common operations such as:
- Deriving a universe identifier from an asset specifier.
- Calculating the total sum of leaf values in a tree.
- Querying specific leaves and generating their MS-SMT inclusion proofs.
- Listing all leaves within a specific universe namespace.

This commit introduces several generic helper functions within
`tapdb/universe.go` to centralize this logic:
- `specifierToIdentifier`: Creates a universe identifier for a given
  specifier and proof type.
- `getUniverseTreeSum`: Calculates the sum of a universe tree root.
- `queryUniverseLeavesAndProofs`: A generic function to query leaves,
  decode them, fetch MS-SMT proofs, and build authenticated results.
- `listUniverseLeaves`: A generic function to list and decode all leaves
  in a namespace.

The `BurnUniverseTree` and `IgnoreUniverseTree` methods (`Sum`,
`QueryBurns`, `ListBurns`, `QueryTuples`, `ListTuples`) have been
refactored to utilize these new helpers. This significantly reduces
code duplication and improves the maintainability of universe tree
interactions.

The `IgnoreTree` interface and related tests were updated to reflect
changes in the `ListTuples` return type.
This is a prep for fixing an issue with Universe keys that have an
identical 2-tuple, but distinct 3-tuple.
We then also add a new concrete implementation of this leaf key. The
interface will allow us to pass it in anywhere that the normal LeafKey
is expected.
This ensures that we avoid collision issues when an output has multiple
assets that have the same script key and same group key. The asset ID
will now be the tie breaker here.
@Roasbeef
Copy link
Member Author

Pushed up latest iteration. This is dependent on the ignore tree PR.

@ffranr ffranr changed the base branch from ignore-tree-impl to main April 16, 2025 15:40
@ffranr
Copy link
Contributor

ffranr commented Apr 16, 2025

I've changed base branch to main now that ignore-tree-impl PR is merged.


// BurnTree sum is the response to a query of the total amount burned in a given
// burn tree.
type BurnTreeSum = SumQueryResp
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably consider generalising the doc for SumQueryResp in this PR, because it's specific to ignore right now.

Comment on lines +1247 to +1248
// BurnProof is the burn proof that is stored within the burn leaf.
BurnProof *proof.Proof
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this field needs to be a pointer type.

type AuthenticatedBurnLeaf struct {
*BurnLeaf

// BurnTreeRoot is the root of the ignore tree that the burn leaf
Copy link
Contributor

Choose a reason for hiding this comment

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

ignore tree -> burn tree

Comment on lines +251 to +253
// decodeAndBuildAuthIgnoreTuple decodes the raw leaf, reconstructs the key,
// and builds the AuthenticatedIgnoreTuple.
func decodeAndBuildAuthIgnoreTuple(dbLeaf UniverseLeaf,
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe function name could be: parseDbSignedIgnoreTuple

}

// buildAuthIgnoreTuple constructs the final AuthenticatedIgnoreTuple.
func buildAuthIgnoreTuple(decodedLeaf *universe.SignedIgnoreTuple,
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps this can become a universe.AuthenticatedIgnoreTuple constructor called NewAuthIgnoreTuple

Copy link
Contributor

Choose a reason for hiding this comment

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

BTW, I'm getting an error in my IDE because decodedLeaf is a pointer where as field SignedIgnoreTuple is not.

@@ -5276,7 +5276,7 @@ func (r *rpcServer) AssetLeaves(ctx context.Context,

// unmarshalLeafKey un-marshals a leaf key from the RPC form.
func unmarshalLeafKey(key *unirpc.AssetKey) (universe.LeafKey, error) {
var leafKey universe.LeafKey
var leafKey universe.BaseLeafKey
Copy link
Contributor

@ffranr ffranr Apr 17, 2025

Choose a reason for hiding this comment

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

Perhaps we can follow the same pattern we used for the GroupKeyReveal upgrade from V0 to V1. That would mean introducing a new interface, renaming universe.LeafKey to universe.LeafKeyV0, and adding a new universe.LeafKeyV1, rather than layering changes on top of a shared base. Might help keep the original V0 implementation clearly identifiable.

And then in the doc for V0 we can explain why it's been depreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

4 participants