Skip to content

Commit 7e6ef7b

Browse files
committed
supplyverifier: add FetchCommitment method
Add a method to retrieve supply commitments from the verifier using a supply commitment locator. Locator supported options are: the first supply commit, the supply commit committed to at a given outpoint, and the supply commit that spends a given outpoint. A follow-up commit will remove FetchCommitment from the supply commit manager in favor of this new method.
1 parent da489a9 commit 7e6ef7b

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed

universe/supplyverifier/env.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/btcsuite/btcd/wire"
88
"github.com/lightninglabs/taproot-assets/asset"
9+
"github.com/lightninglabs/taproot-assets/fn"
910
"github.com/lightninglabs/taproot-assets/mssmt"
1011
"github.com/lightninglabs/taproot-assets/tapgarden"
1112
"github.com/lightninglabs/taproot-assets/universe/supplycommit"
@@ -63,11 +64,25 @@ type SupplyCommitView interface {
6364
leaves supplycommit.SupplyLeaves) error
6465
}
6566

67+
// SupplyTreeView is an interface that is used to look up the root (upper)
68+
// supply tree, subtrees, and leaves.
69+
//
70+
// nolint: lll
6671
type SupplyTreeView interface {
6772
// FetchSupplyTrees returns a copy of the root supply tree and subtrees
6873
// for the given asset spec.
6974
FetchSupplyTrees(ctx context.Context, spec asset.Specifier) (mssmt.Tree,
7075
*supplycommit.SupplyTrees, error)
76+
77+
// FetchSubTrees returns all the subtrees for the given asset spec.
78+
FetchSubTrees(ctx context.Context, assetSpec asset.Specifier,
79+
blockHeightEnd fn.Option[uint32]) lfn.Result[supplycommit.SupplyTrees]
80+
81+
// FetchSupplyLeavesByHeight fetches all supply leaves for a given asset
82+
// specifier within a given block height range.
83+
FetchSupplyLeavesByHeight(ctx context.Context, spec asset.Specifier,
84+
startHeight,
85+
endHeight uint32) lfn.Result[supplycommit.SupplyLeaves]
7186
}
7287

7388
// Environment is a struct that holds all the dependencies that the supply

universe/supplyverifier/manager.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"time"
88

99
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/btcsuite/btcd/wire"
1011
"github.com/lightninglabs/lndclient"
1112
"github.com/lightninglabs/taproot-assets/asset"
1213
"github.com/lightninglabs/taproot-assets/fn"
14+
"github.com/lightninglabs/taproot-assets/mssmt"
1315
"github.com/lightninglabs/taproot-assets/tapgarden"
1416
"github.com/lightninglabs/taproot-assets/universe/supplycommit"
1517
"github.com/lightningnetwork/lnd/msgmux"
@@ -248,6 +250,245 @@ func (m *Manager) InsertSupplyCommit(ctx context.Context,
248250
)
249251
}
250252

253+
// SupplyCommitSnapshot packages the on-chain state of a supply commitment at a
254+
// specific block height: the root commitment, the supply tree,
255+
// the subtrees at that height, the new leaves since the previous commitment,
256+
// and the chain proof that links the leaves to the root.
257+
//
258+
// TODO(guggero): Replace call sites that pass three separate params with
259+
// this struct.
260+
type SupplyCommitSnapshot struct {
261+
// Commitment is the root supply commitment that commits to all supply
262+
// leaves up to the block height recorded in CommitmentBlock.
263+
Commitment supplycommit.RootCommitment
264+
265+
// SupplyTree is the upper supply tree as of CommitmentBlock.
266+
SupplyTree mssmt.Tree
267+
268+
// Subtrees are the supply subtrees as of CommitmentBlock.
269+
Subtrees supplycommit.SupplyTrees
270+
271+
// Leaves are the supply leaves added after the previous commitment's
272+
// block height (exclusive) and up to this commitment's block height
273+
// (inclusive).
274+
Leaves supplycommit.SupplyLeaves
275+
}
276+
277+
// LocatorType is an enum that indicates the type of locator used to identify
278+
// a supply commitment in the database.
279+
type LocatorType uint8
280+
281+
const (
282+
// LocatorTypeOutpoint indicates that the locator type is the outpoint
283+
// of a supply commitment transaction output.
284+
LocatorTypeOutpoint LocatorType = 0
285+
286+
// LocatorTypeSpentOutpoint indicates that the locator type is the
287+
// outpoint spent by a supply commitment transaction.
288+
LocatorTypeSpentOutpoint LocatorType = 1
289+
290+
// LocatorTypeVeryFirst indicates that the locator type is the very
291+
// first supply commitment transaction output for an asset group.
292+
LocatorTypeVeryFirst LocatorType = 2
293+
)
294+
295+
// CommitLocator is used to locate a supply commitment in the database based on
296+
// its on-chain characteristics.
297+
type CommitLocator struct {
298+
// LocatorType indicates the type of locator used to identify the
299+
// supply commitment.
300+
LocatorType LocatorType
301+
302+
// Outpoint is the outpoint used to locate a supply commitment.
303+
// Depending on the LocatorType, this may be the outpoint created by a
304+
// supply commitment, the outpoint spent by a supply commitment, or an
305+
// empty outpoint for the very first supply commitment of an asset
306+
// group.
307+
Outpoint wire.OutPoint
308+
}
309+
310+
// BlockHeightRange represents a range of block heights, inclusive of both
311+
// start and end.
312+
type BlockHeightRange struct {
313+
// Start is the starting block height of the range.
314+
Start uint32
315+
316+
// End is the ending block height of the range.
317+
End uint32
318+
}
319+
320+
// fetchCommitmentBlockRange returns the block height range for fetching supply
321+
// leaves for the given commitment.
322+
//
323+
// The range starts from the block height of the previous commitment
324+
// (exclusive) to the block height of the given commitment (inclusive). If
325+
// there is no previous commitment, the range starts from block height zero.
326+
func (m *Manager) fetchCommitmentBlockRange(ctx context.Context,
327+
assetSpec asset.Specifier,
328+
commitment supplycommit.RootCommitment) (BlockHeightRange, error) {
329+
330+
var (
331+
zero BlockHeightRange
332+
view = m.cfg.SupplyCommitView
333+
)
334+
335+
commitmentBlock, err := commitment.CommitmentBlock.UnwrapOrErr(
336+
supplycommit.ErrNoBlockInfo,
337+
)
338+
if err != nil {
339+
return zero, fmt.Errorf("unable to fetch commitment block: %w",
340+
err)
341+
}
342+
343+
// Determine the block height range for fetching supply leaves.
344+
//
345+
// If there is no preceding commitment, the block height range starts
346+
// from zero.
347+
if commitment.SpentCommitment.IsNone() {
348+
heightRange := BlockHeightRange{
349+
Start: 0,
350+
End: commitmentBlock.Height,
351+
}
352+
353+
return heightRange, nil
354+
}
355+
356+
// Otherwise, we need to fetch the previous commitment to determine
357+
// the starting block height.
358+
prevCommitmentOutPoint, err := commitment.SpentCommitment.UnwrapOrErr(
359+
fmt.Errorf("supply commitment unexpectedly has no spent " +
360+
"outpoint"),
361+
)
362+
if err != nil {
363+
return zero, err
364+
}
365+
366+
spentCommitment, err := view.FetchCommitmentByOutpoint(
367+
ctx, assetSpec, prevCommitmentOutPoint,
368+
)
369+
if err != nil {
370+
return zero, fmt.Errorf("unable to fetch commitment by "+
371+
"outpoint: %w", err)
372+
}
373+
374+
spentCommitmentBlock, err := spentCommitment.CommitmentBlock.
375+
UnwrapOrErr(supplycommit.ErrNoBlockInfo)
376+
if err != nil {
377+
return zero, fmt.Errorf("unable to fetch spent commitment "+
378+
"block: %w", err)
379+
}
380+
381+
return BlockHeightRange{
382+
Start: spentCommitmentBlock.Height,
383+
End: commitmentBlock.Height,
384+
}, nil
385+
}
386+
387+
// FetchCommitment fetches the commitment with the given locator from the local
388+
// database view.
389+
func (m *Manager) FetchCommitment(ctx context.Context,
390+
assetSpec asset.Specifier, locator CommitLocator) (SupplyCommitSnapshot,
391+
error) {
392+
393+
var (
394+
zero SupplyCommitSnapshot
395+
err error
396+
397+
view = m.cfg.SupplyCommitView
398+
commitment *supplycommit.RootCommitment
399+
)
400+
switch locator.LocatorType {
401+
case LocatorTypeOutpoint:
402+
commitment, err = view.FetchCommitmentByOutpoint(
403+
ctx, assetSpec, locator.Outpoint,
404+
)
405+
if err != nil {
406+
return zero, fmt.Errorf("unable to fetch commitment "+
407+
"by outpoint: %w", err)
408+
}
409+
410+
case LocatorTypeSpentOutpoint:
411+
commitment, err = view.FetchCommitmentBySpentOutpoint(
412+
ctx, assetSpec, locator.Outpoint,
413+
)
414+
if err != nil {
415+
return zero, fmt.Errorf("unable to fetch commitment "+
416+
"by spent outpoint: %w", err)
417+
}
418+
419+
case LocatorTypeVeryFirst:
420+
commitment, err = view.FetchStartingCommitment(ctx, assetSpec)
421+
if err != nil {
422+
return zero, fmt.Errorf("unable to fetch starting "+
423+
"commitment: %w", err)
424+
}
425+
426+
default:
427+
return zero, fmt.Errorf("unknown supply commit locator "+
428+
"type: %d", locator.LocatorType)
429+
}
430+
431+
// Fetch block height range for fetching supply leaves.
432+
blockHeightRange, err := m.fetchCommitmentBlockRange(
433+
ctx, assetSpec, *commitment,
434+
)
435+
if err != nil {
436+
return zero, fmt.Errorf("unable to fetch block height "+
437+
"range: %w", err)
438+
}
439+
440+
leaves, err := m.cfg.SupplyTreeView.FetchSupplyLeavesByHeight(
441+
ctx, assetSpec, blockHeightRange.Start, blockHeightRange.End,
442+
).Unpack()
443+
if err != nil {
444+
return zero, fmt.Errorf("unable to fetch supply leaves for "+
445+
"asset specifier %s: %w", assetSpec.String(), err)
446+
}
447+
448+
// Fetch supply subtrees at block height.
449+
subtrees, err := m.cfg.SupplyTreeView.FetchSubTrees(
450+
ctx, assetSpec, fn.Some(blockHeightRange.End),
451+
).Unpack()
452+
if err != nil {
453+
return zero, fmt.Errorf("unable to fetch supply subtrees for "+
454+
"asset specifier %s: %w", assetSpec.String(), err)
455+
}
456+
457+
// Formulate supply tree at correct height from subtrees.
458+
bareSupplyTree := mssmt.NewCompactedTree(mssmt.NewDefaultStore())
459+
supplyTree, err := supplycommit.UpdateRootSupplyTree(
460+
ctx, bareSupplyTree, subtrees,
461+
)
462+
if err != nil {
463+
return zero, fmt.Errorf("unable to formulate supply tree "+
464+
"for asset specifier %s: %w", assetSpec.String(), err)
465+
}
466+
467+
// Sanity check that the derived upper supply tree root matches the
468+
// commitment.
469+
expectedSupplyRoot, err := supplyTree.Root(ctx)
470+
if err != nil {
471+
return zero, fmt.Errorf("unable to fetch upper supply tree "+
472+
"root for asset specifier %s: %w",
473+
assetSpec.String(), err)
474+
}
475+
476+
expectedRootHash := expectedSupplyRoot.NodeHash()
477+
actualRootHash := commitment.SupplyRoot.NodeHash()
478+
if expectedRootHash != actualRootHash {
479+
return zero, fmt.Errorf("supply root mismatch for asset "+
480+
"specifier %s: expected %s, got %s",
481+
assetSpec.String(), expectedRootHash, actualRootHash)
482+
}
483+
484+
return SupplyCommitSnapshot{
485+
Commitment: *commitment,
486+
SupplyTree: supplyTree,
487+
Subtrees: subtrees,
488+
Leaves: leaves,
489+
}, nil
490+
}
491+
251492
// CanHandle determines if the state machine associated with the given asset
252493
// specifier can handle the given message. If a state machine for the asset
253494
// group does not exist, it will be created and started.

0 commit comments

Comments
 (0)