From 79a5d71b059d486823e2e451356fb85fd74818f7 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 cafaf9159..d04eb0cc4 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2986,9 +2986,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 c901fa2d5..80829ab0b 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 34bbede4c..5280d7954 100644 --- a/universe/interface.go +++ b/universe/interface.go @@ -348,12 +348,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