Skip to content

Commit 5dc1877

Browse files
committed
tapfreighter: add delegation key filtering for burn supply commits
This commit extends the ChainPorterConfig with DelegationKeyChecker and BurnSupplyCommitter dependencies to enable supply commitment tracking for asset burns. When processing burn events, the chain porter now filters assets to only submit supply commitment events for those where we control the delegation key. The sendBurnSupplyCommitEvents method uses functional filtering to pre-process the burn list, ensuring we only attempt to create supply commitments for assets we're authorized to manage. This prevents unnecessary processing and potential errors for assets where we lack delegation authority.
1 parent 9f03b39 commit 5dc1877

File tree

2 files changed

+132
-1
lines changed

2 files changed

+132
-1
lines changed

tapfreighter/chain_porter.go

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/lightninglabs/taproot-assets/tappsbt"
2525
"github.com/lightninglabs/taproot-assets/tapscript"
2626
"github.com/lightninglabs/taproot-assets/tapsend"
27+
"github.com/lightninglabs/taproot-assets/universe"
2728
"github.com/lightningnetwork/lnd/chainntnfs"
2829
"github.com/lightningnetwork/lnd/lnwallet"
2930
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -49,6 +50,15 @@ type ProofImporter interface {
4950
replace bool, proofs ...*proof.AnnotatedProof) error
5051
}
5152

53+
// BurnSupplyCommitter is used by the chain porter to update the on-chain supply
54+
// commitment when burns new 1st party burns are confirmed.
55+
type BurnSupplyCommitter interface {
56+
// SendBurnEvent sends a burn event to the supply commitment state
57+
// machine.
58+
SendBurnEvent(ctx context.Context, assetSpec asset.Specifier,
59+
burnLeaf universe.BurnLeaf) error
60+
}
61+
5262
// ChainPorterConfig is the main config for the chain porter.
5363
type ChainPorterConfig struct {
5464
// ChainParams are the chain parameters for the chain porter.
@@ -100,6 +110,15 @@ type ChainPorterConfig struct {
100110
// ErrChan is the main error channel the custodian will report back
101111
// critical errors to the main server.
102112
ErrChan chan<- error
113+
114+
// BurnSupplyCommitter is used to track supply changes (burns) and
115+
// create periodic on-chain supply commitments.
116+
BurnCommitter BurnSupplyCommitter
117+
118+
// DelegationKeyChecker is used to verify that we control the delegation
119+
// key for a given asset, which is required for creating supply
120+
// commitments.
121+
DelegationKeyChecker address.DelegationKeyChecker
103122
}
104123

105124
// ChainPorter is the main sub-system of the tapfreighter package. The porter
@@ -677,6 +696,94 @@ func (p *ChainPorter) storeProofs(sendPkg *sendPackage) error {
677696
return nil
678697
}
679698

699+
// sendBurnSupplyCommitEvents sends supply commitment events for all burned
700+
// assets to track them in the supply commitment state machine.
701+
func (p *ChainPorter) sendBurnSupplyCommitEvents(ctx context.Context,
702+
burns []*AssetBurn) error {
703+
704+
// If no supply commit manager is configured, skip this step.
705+
if p.cfg.BurnCommitter == nil {
706+
return nil
707+
}
708+
709+
// If no delegation key checker is configured, skip this step. We need
710+
// it to figure out if this is an asset we created or not.
711+
if p.cfg.DelegationKeyChecker == nil {
712+
return nil
713+
}
714+
715+
delChecker := p.cfg.DelegationKeyChecker
716+
717+
// We'll use a filter predicate to filter out the burns that we didn't
718+
// do ourselves, i.e., those that don't have a delegation key.
719+
burnsWithDelegation := fn.Filter(burns, func(burn *AssetBurn) bool {
720+
var assetID asset.ID
721+
copy(assetID[:], burn.AssetID)
722+
723+
// If the asset doesn't have a group, then this will return
724+
// false.
725+
hasDelegationKey, err := delChecker.HasDelegationKey(
726+
ctx, assetID,
727+
)
728+
if err != nil {
729+
log.Debugf("Error checking delegation key for "+
730+
"asset %x: %v", assetID, err)
731+
return false
732+
}
733+
734+
if !hasDelegationKey {
735+
log.Debugf("Skipping supply commit burn event "+
736+
"for asset %x: delegation key not controlled "+
737+
"locally",
738+
assetID)
739+
}
740+
741+
return hasDelegationKey
742+
})
743+
744+
for _, burn := range burnsWithDelegation {
745+
var assetID asset.ID
746+
copy(assetID[:], burn.AssetID)
747+
748+
groupKeyBytes := burn.GroupKey
749+
groupKey, err := btcec.ParsePubKey(groupKeyBytes)
750+
if err != nil {
751+
return fmt.Errorf("unable to parse group key: %w", err)
752+
}
753+
754+
assetSpec := asset.NewSpecifierOptionalGroupPubKey(
755+
assetID, groupKey,
756+
)
757+
758+
// Create a NewBurnEvent for the supply commitment state machine.
759+
// We need to create a universe.BurnLeaf for this.
760+
burnLeaf := universe.BurnLeaf{
761+
UniverseKey: universe.AssetLeafKey{
762+
BaseLeafKey: universe.BaseLeafKey{
763+
ScriptKey: burn.ScriptKey,
764+
OutPoint: burn.OutPoint,
765+
},
766+
AssetID: assetID,
767+
},
768+
BurnProof: burn.Proof,
769+
}
770+
771+
// Send the burn event to the supply commit manager.
772+
err = p.cfg.BurnCommitter.SendBurnEvent(
773+
ctx, assetSpec, burnLeaf,
774+
)
775+
if err != nil {
776+
return fmt.Errorf("unable to send burn event for "+
777+
"asset %x: %w", assetID, err)
778+
}
779+
780+
log.Debugf("Sent supply commit burn event for asset %x",
781+
assetID)
782+
}
783+
784+
return nil
785+
}
786+
680787
// storePackageAnchorTxConf logs the on-chain confirmation of the transfer
681788
// anchor transaction for the given package.
682789
func (p *ChainPorter) storePackageAnchorTxConf(pkg *sendPackage) error {
@@ -728,6 +835,12 @@ func (p *ChainPorter) storePackageAnchorTxConf(pkg *sendPackage) error {
728835
Amount: o.Amount,
729836
AnchorTxid: pkg.OutboundPkg.AnchorTx.TxHash(),
730837
Note: pkg.Note,
838+
ScriptKey: &o.Asset.ScriptKey,
839+
Proof: o.ProofSuffix,
840+
OutPoint: wire.OutPoint{
841+
Hash: pkg.OutboundPkg.AnchorTx.TxHash(),
842+
Index: o.AnchorOutputIndex,
843+
},
731844
}
732845

733846
if o.Asset.GroupKey != nil {
@@ -739,11 +852,20 @@ func (p *ChainPorter) storePackageAnchorTxConf(pkg *sendPackage) error {
739852
}
740853
}
741854

855+
// Send supply commitment events for all burned assets before confirming
856+
// the transaction. This ensures that supply commitments are tracked
857+
// before the burn is considered complete.
858+
err := p.sendBurnSupplyCommitEvents(ctx, burns)
859+
if err != nil {
860+
return fmt.Errorf("unable to send burn supply commit "+
861+
"events: %w", err)
862+
}
863+
742864
// At this point we have the confirmation signal, so we can mark the
743865
// parcel delivery as completed in the database.
744866
anchorTXID := pkg.OutboundPkg.AnchorTx.TxHash()
745867
anchorTxBlockHeight := int32(pkg.TransferTxConfEvent.BlockHeight)
746-
err := p.cfg.ExportLog.LogAnchorTxConfirm(ctx, &AssetConfirmEvent{
868+
err = p.cfg.ExportLog.LogAnchorTxConfirm(ctx, &AssetConfirmEvent{
747869
AnchorTXID: anchorTXID,
748870
BlockHash: *pkg.TransferTxConfEvent.BlockHash,
749871
BlockHeight: anchorTxBlockHeight,

tapfreighter/interface.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ type AssetBurn struct {
7878

7979
// AnchorTxid is the txid of the transaction this burn is anchored to.
8080
AnchorTxid chainhash.Hash
81+
82+
// ScriptKey is the script key of the asset that got burnt.
83+
ScriptKey *asset.ScriptKey
84+
85+
// OutPoint is the outpoint of the asset that got burnt.
86+
OutPoint wire.OutPoint
87+
88+
// Proof is the proof that the asset was burnt.
89+
Proof *proof.Proof
8190
}
8291

8392
// String returns the string representation of the commitment constraints.

0 commit comments

Comments
 (0)