From 9d5583998e273bb7bb9e1106572692e34367fc53 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 13 Nov 2023 19:08:00 -0800 Subject: [PATCH] universe: update MultiverseRoot to use a single db query In this commit, we modify the `MultiverseRoot` method to use a single DB query. If we don't have anything to filter, then we'll fetch the global root for that proof type. Otherwise, we'll query the DB for the leaves that match the set of IDs, then insert those into our in-memory tree. This saves us N DB queries where N is the size of the set of Identifier. --- rpcserver.go | 9 +++-- universe/base.go | 83 +++++++++++++++++++++++-------------------- universe/interface.go | 16 +++++---- 3 files changed, 61 insertions(+), 47 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index ea610dd20..84f0b0e4d 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3005,9 +3005,12 @@ func (r *rpcServer) MultiverseRoot(ctx context.Context, err) } - return &unirpc.MultiverseRootResponse{ - MultiverseRoot: marshalMssmtNode(rootNode), - }, nil + var resp unirpc.MultiverseRootResponse + rootNode.WhenSome(func(node universe.MultiverseRoot) { + resp.MultiverseRoot = marshalMssmtNode(node) + }) + + return &resp, nil } // AssetRoots queries for the known Universe roots associated with each known diff --git a/universe/base.go b/universe/base.go index f130238d3..244e1a2d0 100644 --- a/universe/base.go +++ b/universe/base.go @@ -139,63 +139,70 @@ func (a *Archive) RootNodes(ctx context.Context, // proof type. If the given list of universe IDs is non-empty, then the root // will be calculated just for those universes. func (a *Archive) MultiverseRoot(ctx context.Context, proofType ProofType, - filterByIDs []Identifier) (mssmt.Node, error) { + filterByIDs []Identifier) (fn.Option[MultiverseRoot], error) { log.Debugf("Fetching multiverse root for proof type: %v", proofType) - leaveIDs, err := a.cfg.Multiverse.FetchLeaves(ctx, nil, nil, proofType) - if err != nil { - return nil, fmt.Errorf("unable to fetch multiverse leaves: %w", - err) - } + none := fn.None[MultiverseRoot]() - // If a filter list is provided, then we'll only include the leaves - // that are in the filter list. - includeUniverse := func(id Identifier) bool { - if len(filterByIDs) == 0 { - return true + // If we don't have any IDs, then we'll return the multiverse root for + // the given proof type. + if len(filterByIDs) == 0 { + rootNode, err := a.cfg.Multiverse.MultiverseRootNode( + ctx, proofType, + ) + if err != nil { + return none, err } - for _, filterID := range filterByIDs { - if id.IsEqual(filterID) { - return true - } + return rootNode, nil + } + + // Otherwise, we'll run the query to fetch the multiverse leaf for each + // of the specified assets. + uniTargets := make([]MultiverseLeafDesc, len(filterByIDs)) + for idx, id := range filterByIDs { + if id.GroupKey != nil { + uniTargets[idx] = fn.NewRight[asset.ID](*id.GroupKey) + } else { + uniTargets[idx] = fn.NewLeft[asset.ID, btcec.PublicKey]( + id.AssetID, + ) } + } - return false + multiverseLeaves, err := a.cfg.Multiverse.FetchLeaves( + ctx, uniTargets, proofType, + ) + if err != nil { + return none, fmt.Errorf("unable to fetch multiverse "+ + "leaves: %w", err) } + // Now that we have the leaves, we'll insert them into an in-memory + // tree, so we can obtain the root for this unique combination. memStore := mssmt.NewDefaultStore() tree := mssmt.NewCompactedTree(memStore) - for _, id := range leaveIDs { - // Only include the universe if it's in the filter list (given - // the filter list is non-empty). - if !includeUniverse(id) { - continue - } - - uniRoot, err := a.cfg.Multiverse.UniverseRootNode(ctx, id) + for _, leaf := range multiverseLeaves { + _, err = tree.Insert(ctx, leaf.ID.Bytes(), leaf.LeafNode) if err != nil { - return nil, fmt.Errorf("unable to fetch universe "+ - "root: %w", err) + return none, fmt.Errorf("unable to insert "+ + "leaf: %w", err) } + } - rootHash := uniRoot.NodeHash() - rootSum := uniRoot.NodeSum() - - if id.ProofType == ProofTypeIssuance { - rootSum = 1 - } + customRoot, err := tree.Root(ctx) + if err != nil { + return none, fmt.Errorf("unable to obtain root: %w", err) + } - uniLeaf := mssmt.NewLeafNode(rootHash[:], rootSum) - _, err = tree.Insert(ctx, id.Bytes(), uniLeaf) - if err != nil { - return nil, fmt.Errorf("unable to insert leaf: %w", err) - } + multiverseRoot := MultiverseRoot{ + ProofType: proofType, + Node: customRoot, } - return tree.Root(ctx) + return fn.Some(multiverseRoot), nil } // UpsertProofLeaf attempts to upsert a proof for an asset issuance or transfer diff --git a/universe/interface.go b/universe/interface.go index 1c7d09bab..ced57668e 100644 --- a/universe/interface.go +++ b/universe/interface.go @@ -428,12 +428,16 @@ type MultiverseArchive interface { UniverseLeafKeys(ctx context.Context, q UniverseLeafKeysQuery) ([]LeafKey, error) - // FetchLeaves returns the set of multiverse leaves for the given proof - // type, asset ID, and group key. If both asset ID and group key is nil, - // all leaves for the given proof type will be returned. - FetchLeaves(ctx context.Context, assetID *asset.ID, - groupKey *btcec.PublicKey, - proofType ProofType) ([]Identifier, error) + // FetchLeaves returns the set of multiverse leaves that satisfy the set + // of universe targets. If the set of targets is empty, all leaves for + // the given proof type will be returned. + FetchLeaves(ctx context.Context, universeTargets []MultiverseLeafDesc, + proofType ProofType) ([]MultiverseLeaf, error) + + // MultiverseRootNode returns the Multiverse root node for the given + // proof type. + MultiverseRootNode(ctx context.Context, + proofType ProofType) (fn.Option[MultiverseRoot], error) } // Registrar is an interface that allows a caller to upsert a proof leaf in a