Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock Minting Anchor TX Change Output for Future Universe Commitment Support #1325

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,12 +591,13 @@ func (r *rpcServer) MintAsset(ctx context.Context,
}

seedling := &tapgarden.Seedling{
AssetVersion: assetVersion,
AssetType: asset.Type(req.Asset.AssetType),
AssetName: req.Asset.Name,
Amount: req.Asset.Amount,
EnableEmission: req.Asset.NewGroupedAsset,
Meta: seedlingMeta,
AssetVersion: assetVersion,
AssetType: asset.Type(req.Asset.AssetType),
AssetName: req.Asset.Name,
Amount: req.Asset.Amount,
EnableEmission: req.Asset.NewGroupedAsset,
Meta: seedlingMeta,
EnableUniCommitment: req.Asset.EnableUniCommitment,
}

rpcsLog.Infof("[MintAsset]: version=%v, type=%v, name=%v, amt=%v, "+
Expand Down
118 changes: 118 additions & 0 deletions tapgarden/batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ type MintingBatch struct {
// reveal for that asset, if it has one.
AssetMetas AssetMetas

// EnableUniCommitment is a flag that determines whether the minting
// event supports universe commitments. When set to true, the batch must
// include only assets that share the same asset group key, which must
// also be specified.
//
// Universe commitments are minter-controlled, on-chain anchored
// attestations regarding the state of the universe.
EnableUniCommitment bool

// mintingPubKey is the top-level Taproot output key that will be used
// to commit to the Taproot Asset commitment above.
mintingPubKey *btcec.PublicKey
Expand Down Expand Up @@ -313,6 +322,115 @@ func (m *MintingBatch) HasSeedlings() bool {
return len(m.Seedlings) != 0
}

// validateUniCommitment verifies that the seedling adheres to the universe
// commitment feature restrictions in the context of the current batch state.
func (m *MintingBatch) validateUniCommitment(newSeedling Seedling) error {
// If the batch is empty, the first seedling will set the universe
// commitment flag for the batch.
if !m.HasSeedlings() {
if newSeedling.EnableUniCommitment {
// The minting batch funding step records the genesis
// transaction in the database. Additionally, the
// uni-commitment feature requires the change output to
// be locked, ensuring it can only be spent by `tapd`.
// Therefore, to leverage the uni-commitment feature,
// the batch must be populated with seedlings, with the
// uni-commitment flag correctly set before any funding
// attempt is made.
//
// As such, when adding the first seedling with
// uni-commitment support to the batch, it is essential
// to verify that the batch has not yet been funded.
if m.GenesisPacket != nil {
return fmt.Errorf("attempting to add " +
"seedling with universe commitment " +
"flag enabled to funded batch")
}

// At this point, we know the batch is empty, and the
// candidate seedling will be the first to be added.
// Consequently, if the seedling has the universe
// commitment flag enabled, it must specify a
// re-issuable asset group key.
if !newSeedling.EnableEmission {
return fmt.Errorf("the emission flag must be " +
"enabled for the first asset in a " +
"batch with the universe commitment " +
"flag enabled")
}

if !newSeedling.HasGroupKey() {
return fmt.Errorf("a group key must be " +
"specified for the first seedling in " +
"the batch when the universe " +
"commitment flag is enabled")
}
}

// No further checks are required for the first seedling in the
// batch.
return nil
}

// At this stage, it is confirmed that the batch contains seedlings, and
// the universe commitment flag for the batch should have been correctly
// updated when the existing seedlings were added.
//
// Therefore, when assessing this new candidate seedling, its universe
// commitment flag state must align with the batch's flag state.
if m.EnableUniCommitment != newSeedling.EnableUniCommitment {
return fmt.Errorf("seedling universe commitment flag does " +
"not match batch")
}

// If the universe commitment flag is disabled for both the seedling and
// the batch, no additional checks are required.
if !m.EnableUniCommitment && !newSeedling.EnableUniCommitment {
return nil
}

// At this stage, the universe commitment flag is enabled for both the
// seedling and the batch, and the batch contains at least one seedling.
//
// As a result, the candidate seedling must have a group anchor that is
// already part of the batch.
if newSeedling.GroupAnchor == nil {
return fmt.Errorf("group anchor unspecified for seedling " +
"with universe commitment flag enabled")
}

err := m.validateGroupAnchor(&newSeedling)
if err != nil {
return fmt.Errorf("group anchor validation failed: %w", err)
}

return nil
}

// AddSeedling adds a new seedling to the batch.
func (m *MintingBatch) AddSeedling(newSeedling Seedling) error {
// Ensure that the seedling adheres to the universe commitment feature
// restrictions in relation to the current batch state.
err := m.validateUniCommitment(newSeedling)
if err != nil {
return fmt.Errorf("seedling does not comply with universe "+
"commitment feature: %w", err)
}

// At this stage, the seedling has been confirmed to comply with the
// universe commitment feature restrictions. If this is the first
// seedling being added to the batch, the batch universe commitment flag
// can be set to match the seedling's flag state.
if !m.HasSeedlings() {
m.EnableUniCommitment = newSeedling.EnableUniCommitment
}

// Add the seedling to the batch.
m.Seedlings[newSeedling.AssetName] = &newSeedling

return nil
}

// ToMintingBatch creates a new MintingBatch from a VerboseBatch.
func (v *VerboseBatch) ToMintingBatch() *MintingBatch {
newBatch := v.MintingBatch.Copy()
Expand Down
30 changes: 21 additions & 9 deletions tapgarden/planter.go
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,9 @@ func (c *ChainPlanter) fundGenesisPsbt(ctx context.Context,
log.Infof("Funded GenesisPacket for batch: %x", batchKey)
log.Tracef("GenesisPacket: %v", spew.Sdump(fundedGenesisPkt))

// TODO(ffranr): Lock change output here if EnableUniCommitment set for
// batch.

return fundedGenesisPkt, nil
}

Expand Down Expand Up @@ -2351,35 +2354,44 @@ func (c *ChainPlanter) prepAssetSeedling(ctx context.Context,
// No batch, so we'll create a new one with only this seedling as part
// of the batch.
case c.pendingBatch == nil:
newBatch, err := c.newBatch()
var err error
c.pendingBatch, err = c.newBatch()
if err != nil {
return err
}

log.Infof("Adding %v to new MintingBatch", req)
log.Infof("Attempting to add a seedling to a new batch "+
"(seedling=%v)", req)

newBatch.Seedlings[req.AssetName] = req
err = c.pendingBatch.AddSeedling(*req)
if err != nil {
return fmt.Errorf("failed to add seedling to batch: %w",
err)
}

ctx, cancel := c.WithCtxQuit()
defer cancel()
err = c.cfg.Log.CommitMintingBatch(ctx, newBatch)
err = c.cfg.Log.CommitMintingBatch(ctx, c.pendingBatch)
if err != nil {
return err
}

c.pendingBatch = newBatch

// A batch already exists, so we'll add this seedling to the batch,
// committing it to disk fully before we move on.
case c.pendingBatch != nil:
log.Infof("Adding %v to existing MintingBatch", req)
log.Infof("Attempting to add a seedling to batch (seedling=%v)",
req)

c.pendingBatch.Seedlings[req.AssetName] = req
err := c.pendingBatch.AddSeedling(*req)
if err != nil {
return fmt.Errorf("failed to add seedling to batch: %w",
err)
}

// Now that we know the seedling is ok, we'll write it to disk.
ctx, cancel := c.WithCtxQuit()
defer cancel()
err := c.cfg.Log.AddSeedlingsToBatch(
err = c.cfg.Log.AddSeedlingsToBatch(
ctx, c.pendingBatch.BatchKey.PubKey, req,
)
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions tapgarden/seedling.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ type Seedling struct {
// for this asset meaning future assets linked to it can be created.
EnableEmission bool

// EnableUniCommitment indicates whether the minting event which
// will be associated with the seedling supports universe commitments.
// If set to true, the seedling can only be included in a minting batch
// where all assets share the same asset group key, which must be
// specified.
//
// Universe commitments are minter-controlled, on-chain anchored
// attestations regarding the state of the universe.
EnableUniCommitment bool

// GroupAnchor is the name of another seedling in the pending batch that
// will anchor an asset group. This seedling will be minted with the
// same group key as the anchor asset.
Expand Down
Loading
Loading