@@ -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