Skip to content

Conversation

Roasbeef
Copy link
Member

@Roasbeef Roasbeef commented Jul 24, 2025

In this PR, we add some extra plumbing to actually hook up the mint and burn actions originating in the daemon into the supply commit state machine. We cap things off by adding an itest that exercises supply commit creation e2e.

Along the way, we fixed two existing bugs in related sub-sytems:

  1. We never spent the pre commitments on disk in the first place. This caused the initial itest to fail, as the state machine would be adding an extra input to the commitment transaction which was already spent.
    • While fixing this, I added a new outpoint field into the pre commitment table to make it easy to reference for spending.
  2. We weren't storing the fully key desc information on disk? This caused the PSBT finaliztion to fail.
  3. We weren't passing in the tapscript root hash into the PSBT, so lnd wasn't able to sign for it.

I've codified the knowledge needed to debug the last 2 issues in this PR: #1739.

Fixes #1648

Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a quick look, didn't go through every commit.

@guggero guggero self-requested a review August 7, 2025 14:15
@Roasbeef Roasbeef force-pushed the state-machine-hookup branch from 58d8271 to b1b0ad7 Compare August 8, 2025 03:40
@Roasbeef Roasbeef marked this pull request as ready for review August 8, 2025 03:40
Copy link
Contributor

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! LGTM pending CI fixes.

@coveralls
Copy link

coveralls commented Aug 12, 2025

Pull Request Test Coverage Report for Build 17420261431

Details

  • 414 of 531 (77.97%) changed or added relevant lines in 13 files are covered.
  • 63 unchanged lines in 12 files lost coverage.
  • Overall coverage increased (+8.2%) to 56.858%

Changes Missing Coverage Covered Lines Changed/Added Lines %
itest/assertions.go 38 40 95.0%
tapcfg/server.go 39 42 92.86%
tapdb/asset_minting.go 14 18 77.78%
universe/supplycommit/env.go 26 31 83.87%
address/book.go 19 31 61.29%
tapfreighter/chain_porter.go 77 93 82.8%
tapdb/supply_commit.go 32 49 65.31%
universe/supplycommit/transitions.go 63 85 74.12%
tapgarden/caretaker.go 80 116 68.97%
Files with Coverage Reduction New Missed Lines %
fn/context_guard.go 1 91.94%
tapdb/supply_commit.go 1 49.21%
mssmt/compacted_tree.go 2 75.19%
tapdb/multiverse.go 2 76.2%
universe/archive.go 2 80.05%
itest/assertions.go 3 89.19%
rpcserver.go 3 61.02%
universe/supplycommit/transitions.go 4 70.37%
universe/syncer.go 4 82.73%
tapfreighter/chain_porter.go 6 82.17%
Totals Coverage Status
Change from base Build 17419117724: 8.2%
Covered Lines: 61471
Relevant Lines: 108113

💛 - Coveralls

@Roasbeef Roasbeef force-pushed the state-machine-hookup branch from 236e50f to 81c687c Compare August 15, 2025 01:20
@Roasbeef
Copy link
Member Author

Pushed a series of commits fixing some bugs:

  • We weren't storing the full internal key, so PSBT signing failed.
  • We weren't setting the taproot merkle tree in the input for the supply commit output, so signing failed again.
  • We weren't spending the pre-commitment outputs in the db, leading to double spends for attempts.

After all those were fixed, the itest added passes. We mint an asset, then another one in a group, then burn an asset. Along the way we check that all the commitment information has been updated along the way.

@Roasbeef
Copy link
Member Author

Pushed up a commit to fix a rebase issue in the unit tests.

@guggero guggero force-pushed the state-machine-hookup branch from a1e821b to 0207a85 Compare August 19, 2025 07:47
@guggero
Copy link
Contributor

guggero commented Aug 19, 2025

I cleaned up after the AI to fix the linter issues and added release notes to get the CI green.

@guggero
Copy link
Contributor

guggero commented Aug 20, 2025

I think you forgot to pull my linter fixes...

@Roasbeef Roasbeef force-pushed the state-machine-hookup branch from 5cb6e07 to eccebe8 Compare August 20, 2025 18:49
@Roasbeef
Copy link
Member Author

Forgot to pull down those prior chagnes before applying a fixup commit.

Should be good now...

@Roasbeef Roasbeef force-pushed the state-machine-hookup branch from eccebe8 to 2d410bf Compare August 20, 2025 22:21
Copy link
Member

@jtobin jtobin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone over these changes in detail and think they look solid. LGTM. 👍

I'd recommend adding a brief description of the changes in the OP, for posterity. I also noted a few utterly-trivial typo-related nits I just happened to pick up on while going through the code, but they don't actually need to be fixed.

@jtobin
Copy link
Member

jtobin commented Aug 27, 2025

Worth noting that this will also resolve #1648.

Copy link
Contributor

@ffranr ffranr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to include setting the block height in both burn and mint events in this PR.

Here’s an example of the change needed for burn:

Subject: [PATCH] tapfreighter: set block header and height in burn proofs for commitments
---
Index: tapfreighter/chain_porter.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tapfreighter/chain_porter.go b/tapfreighter/chain_porter.go
--- a/tapfreighter/chain_porter.go	(revision ca914afd712ea0b6be7ae155803dce527a16084c)
+++ b/tapfreighter/chain_porter.go	(revision 6db075b18a823e12290aa6e09ca35e018ac9d14a)
@@ -815,7 +815,15 @@
 		)
 	}
 
+	anchorTxBlockHeight := int32(pkg.TransferTxConfEvent.BlockHeight)
+	anchorTxBlockHeader := pkg.TransferTxConfEvent.Block.Header
+
 	// Now we scan through the VPacket for any burns.
+	//
+	// Once the anchor transaction is confirmed, we must populate the block
+	// header and block height in the proof suffixes of all outputs. Without
+	// the block height, burn events cannot be considered valid for
+	// inclusion in supply commitments.
 	var burns []*AssetBurn
 
 	for _, v := range pkg.VirtualPackets {
@@ -841,6 +849,10 @@
 				OutPoint:   op,
 			}
 
+			// Set the block height and header in the burn proof.
+			b.Proof.BlockHeight = uint32(anchorTxBlockHeight)
+			b.Proof.BlockHeader = anchorTxBlockHeader
+
 			if o.Asset.GroupKey != nil {
 				groupKey := o.Asset.GroupKey.GroupPubKey
 				b.GroupKey = groupKey.SerializeCompressed()
@@ -862,7 +874,6 @@
 	// At this point we have the confirmation signal, so we can mark the
 	// parcel delivery as completed in the database.
 	anchorTXID := pkg.OutboundPkg.AnchorTx.TxHash()
-	anchorTxBlockHeight := int32(pkg.TransferTxConfEvent.BlockHeight)
 	err = p.cfg.ExportLog.LogAnchorTxConfirm(ctx, &AssetConfirmEvent{
 		AnchorTXID:             anchorTXID,
 		BlockHash:              *pkg.TransferTxConfEvent.BlockHash,

And here's the sort of thing I think we need for mint:

Subject: [PATCH] tapgarden+supplycommit: set mint block height in NewMintEvent
---
Index: tapgarden/caretaker.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tapgarden/caretaker.go b/tapgarden/caretaker.go
--- a/tapgarden/caretaker.go	(revision 6db075b18a823e12290aa6e09ca35e018ac9d14a)
+++ b/tapgarden/caretaker.go	(revision a5e97c86a59571d10d525ea7fd516be271259258)
@@ -1550,6 +1550,7 @@
 		// Finally we send all of the above to the supply commiter.
 		err = b.cfg.MintSupplyCommitter.SendMintEvent(
 			ctx, assetSpec, uniqueLeafKey, universeLeaf,
+			leafProof.BlockHeight,
 		)
 		if err != nil {
 			return fmt.Errorf("unable to send mint event for "+
Index: tapgarden/planter.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tapgarden/planter.go b/tapgarden/planter.go
--- a/tapgarden/planter.go	(revision 6db075b18a823e12290aa6e09ca35e018ac9d14a)
+++ b/tapgarden/planter.go	(revision a5e97c86a59571d10d525ea7fd516be271259258)
@@ -37,8 +37,8 @@
 	// SendMintEvent sends a mint event to the supply commitment state
 	// machine.
 	SendMintEvent(ctx context.Context, assetSpec asset.Specifier,
-		leafKey universe.UniqueLeafKey,
-		issuanceProof universe.Leaf) error
+		leafKey universe.UniqueLeafKey, issuanceProof universe.Leaf,
+		mintBlockHeight uint32) error
 }
 
 // GardenKit holds the set of shared fundamental interfaces all sub-systems of
Index: tapgarden/supply_commit_test.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tapgarden/supply_commit_test.go b/tapgarden/supply_commit_test.go
--- a/tapgarden/supply_commit_test.go	(revision 6db075b18a823e12290aa6e09ca35e018ac9d14a)
+++ b/tapgarden/supply_commit_test.go	(revision a5e97c86a59571d10d525ea7fd516be271259258)
@@ -44,9 +44,11 @@
 // SendMintEvent implements the SupplyCommitManager interface.
 func (m *MockSupplyCommitManager) SendMintEvent(ctx context.Context,
 	assetSpec asset.Specifier, leafKey universe.UniqueLeafKey,
-	issuanceProof universe.Leaf) error {
+	issuanceProof universe.Leaf, mintBlockHeight uint32) error {
 
-	args := m.Called(ctx, assetSpec, leafKey, issuanceProof)
+	args := m.Called(
+		ctx, assetSpec, leafKey, issuanceProof, mintBlockHeight,
+	)
 	return args.Error(0)
 }
 
@@ -132,7 +134,8 @@
 					mock.AnythingOfType(
 						"universe.AssetLeafKey",
 					),
-					mock.AnythingOfType("universe.Leaf")).
+					mock.AnythingOfType("universe.Leaf"),
+					mock.AnythingOfType("uint32")).
 					Return(nil).
 					Times(tc.expectedCallCount)
 			}
Index: universe/supplycommit/manager.go
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/universe/supplycommit/manager.go b/universe/supplycommit/manager.go
--- a/universe/supplycommit/manager.go	(revision 6db075b18a823e12290aa6e09ca35e018ac9d14a)
+++ b/universe/supplycommit/manager.go	(revision a5e97c86a59571d10d525ea7fd516be271259258)
@@ -266,11 +266,13 @@
 
 // SendMintEvent sends a mint event to the supply commitment state machine.
 func (m *Manager) SendMintEvent(ctx context.Context, assetSpec asset.Specifier,
-	leafKey universe.UniqueLeafKey, issuanceProof universe.Leaf) error {
+	leafKey universe.UniqueLeafKey, issuanceProof universe.Leaf,
+	mintBlockHeight uint32) error {
 
 	mintEvent := &NewMintEvent{
 		LeafKey:       leafKey,
 		IssuanceProof: issuanceProof,
+		MintHeight:    mintBlockHeight,
 	}
 
 	return m.SendEventSync(ctx, assetSpec, mintEvent)

@github-project-automation github-project-automation bot moved this from 🏗 In progress to 👀 In review in Taproot-Assets Project Board Aug 27, 2025
…n control

This commit introduces a new DelegationKeyChecker interface that allows
verification of whether we control the delegation key for a given asset.
The interface is implemented by the address.Book, which checks if the
local key ring contains the private key corresponding to an asset's
delegation public key.

The implementation queries the asset group and metadata to extract the
delegation key, then attempts to locate the corresponding private key
in the local keystore. This verification is essential for ensuring only
authorized parties can create supply commitment proofs for assets with
delegation keys.
This commit extends the MultiStateMachineManager to implement the
MintCommitter and BurnSupplyCommitter interfaces. These new methods
provide a type-safe way to send mint and burn events to the supply
commitment state machines.

The SendMintEvent method accepts universe-specific types for minting
operations, while SendBurnEvent handles burn leaf submissions. Both
methods internally construct the appropriate event types and delegate
to the existing SendEvent infrastructure, maintaining consistency with
the state machine architecture.
This commit enhances the GardenKit configuration by adding two new
optional dependencies: DelegationKeyChecker and MintCommitter. These
additions enable the garden subsystem to verify delegation key ownership
and submit mint events to the supply commitment state machine.

The GardenKit struct serves as the central configuration hub for the
tapgarden package, and these new fields allow components like the
BatchCaretaker to conditionally enable supply commitment functionality
when the required dependencies are available.
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.
This commit enhances the BatchCaretaker to filter mint events based on
delegation key ownership. The sendSupplyCommitEvents method now uses
functional filtering to ensure supply commitment events are only sent
for assets where we control the delegation key.

Similar to the burn event filtering in tapfreighter, this change ensures
the caretaker respects delegation authority when creating supply proofs
for newly minted assets. The filtering happens early in the process to
avoid unnecessary proof construction and event submission for assets
we're not authorized to manage.
This commit completes the integration by wiring the address book's
DelegationKeyChecker implementation to both the GardenKit and
ChainPorterConfig. With this configuration, both minting and burning
operations now properly verify delegation key ownership before
submitting supply commitment events.

The address book serves as the central authority for key ownership
verification, leveraging its existing access to the key ring and
asset metadata storage. This ensures consistent delegation checking
across all supply commitment operations.
This commit introduces comprehensive test coverage for the delegation
key filtering functionality in the BatchCaretaker. The tests verify
that mint supply commitment events are only sent for assets where we
control the delegation key.

The test suite covers three key scenarios: all assets having delegation
keys, partial delegation key ownership, and no delegation keys. Mock
implementations of the MintCommitter and DelegationKeyChecker interfaces
enable isolated testing of the filtering logic without requiring actual
key management infrastructure.
This commit adds a unified test suite for burn supply commitment event
processing in the ChainPorter. The tests verify delegation key filtering,
error handling, and various burn scenarios through a table-driven
approach with a helper function for cleaner test setup.

The test coverage includes successful burn processing with mixed group
keys, delegation key filtering scenarios, error handling for both the
delegation checker and burn committer, handling of missing dependencies,
and validation of invalid group key bytes. The setupChainPorterTest
helper function eliminates repetitive mock configuration, making the
tests more maintainable and focused on the scenarios being tested.
This'll be useful to fix a bug in the next commit, where we weren't
actually able to effectivley look up which pre commitment was being
spent by a new supply commit.
The call is idempotent so not explcitily a bug, but we have two calls to
the function here. Likely was a rebase issue.
In this commit, we fix a bug where we wouldn't actually mark pre
commitmetns as spent. We'll do so now, which makes sure that when we
fetch the pre commits for a later batch, we aren't trying to include one
that might be double spent.
This fixes a bug that would cause a spend of a supply commitment output
to fail. We weren't storing the full key desc, so lnd wasn't able to
sign the PSBT, as the bip32 information didn't match up (derived wrong
key).
If this isn't set, then we can't do a spend, as lnd can't figure out how
to sign for the PSBT input.
@Roasbeef Roasbeef force-pushed the state-machine-hookup branch from 2d410bf to 0390ec1 Compare September 3, 2025 01:23
@ffranr ffranr added this pull request to the merge queue Sep 3, 2025
Any commits made after this event will not be merged.
github-merge-queue bot pushed a commit that referenced this pull request Sep 3, 2025
multi: hook up burn and mint events to the supply commit state machine
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

multi: hook up supply commitment state machine to minting and burning operations
5 participants