Skip to content

Commit 4f16d2d

Browse files
committed
tapgarden: fetch finalized batch via file archiver
In this commit, we update listBatches to fetch assets from finalized batches via the proof file archiver instead of the DB. Given the batch seedlings, we fetch issuance proofs and decode the asset in its genesis state. We also verify that we can recompute the genesis output script with the fetched assets.
1 parent 660940e commit 4f16d2d

File tree

2 files changed

+202
-18
lines changed

2 files changed

+202
-18
lines changed

tapgarden/interface.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ type MintingStore interface {
262262
FetchGroupByGroupKey(ctx context.Context,
263263
groupKey *btcec.PublicKey) (*asset.AssetGroup, error)
264264

265+
// FetchScriptKeyByTweakedKey fetches the populated script key given the
266+
// tweaked script key.
267+
FetchScriptKeyByTweakedKey(ctx context.Context,
268+
tweakedKey *btcec.PublicKey) (*asset.TweakedScriptKey, error)
269+
265270
// FetchAssetMeta fetches the meta reveal for an asset genesis.
266271
FetchAssetMeta(ctx context.Context, ID asset.ID) (*proof.MetaReveal,
267272
error)

tapgarden/planter.go

Lines changed: 197 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"slices"
89
"sync"
910
"time"
1011

@@ -724,10 +725,150 @@ func freezeMintingBatch(ctx context.Context, batchStore MintingStore,
724725
)
725726
}
726727

728+
// filterFinalizedBatches separates a set of batches into two sets based on
729+
// their batch state.
730+
func filterFinalizedBatches(batches []*MintingBatch) ([]*MintingBatch,
731+
[]*MintingBatch) {
732+
733+
finalized := []*MintingBatch{}
734+
nonFinalized := []*MintingBatch{}
735+
736+
fn.ForEach(batches, func(batch *MintingBatch) {
737+
switch batch.State() {
738+
case BatchStateFinalized:
739+
finalized = append(finalized, batch)
740+
default:
741+
nonFinalized = append(nonFinalized, batch)
742+
}
743+
})
744+
745+
return finalized, nonFinalized
746+
}
747+
748+
// fetchFinalizedBatch fetches the assets of a batch in their genesis state,
749+
// given a batch populated with seedlings.
750+
func fetchFinalizedBatch(ctx context.Context, batchStore MintingStore,
751+
archiver proof.Archiver, batch *MintingBatch) (*MintingBatch, error) {
752+
753+
// Collect genesis TX information from the batch to build the proof
754+
// locators.
755+
anchorOutputIndex := extractAnchorOutputIndex(batch.GenesisPacket)
756+
signedTx, err := psbt.Extract(batch.GenesisPacket.Pkt)
757+
if err != nil {
758+
return nil, err
759+
}
760+
761+
genOutpoint := extractGenesisOutpoint(signedTx)
762+
genScript := signedTx.TxOut[anchorOutputIndex].PkScript
763+
anchorOutpoint := wire.OutPoint{
764+
Hash: signedTx.TxHash(),
765+
Index: anchorOutputIndex,
766+
}
767+
768+
batchAssets := make([]*asset.Asset, 0, len(batch.Seedlings))
769+
assetMetas := make(AssetMetas)
770+
for _, seedling := range batch.Seedlings {
771+
gen := seedling.Genesis(genOutpoint, anchorOutputIndex)
772+
issuanceProof, err := archiver.FetchIssuanceProof(
773+
ctx, gen.ID(), anchorOutpoint,
774+
)
775+
if err != nil {
776+
return nil, err
777+
}
778+
779+
proofFile, err := issuanceProof.AsFile()
780+
if err != nil {
781+
return nil, err
782+
}
783+
784+
if proofFile.NumProofs() != 1 {
785+
return nil, fmt.Errorf("expected single proof for " +
786+
"issuance proof")
787+
}
788+
789+
rawProof, err := proofFile.RawLastProof()
790+
if err != nil {
791+
return nil, err
792+
}
793+
794+
// Decode the sprouted asset from the issuance proof.
795+
var sproutedAsset asset.Asset
796+
assetRecord := proof.AssetLeafRecord(&sproutedAsset)
797+
err = proof.SparseDecode(bytes.NewReader(rawProof), assetRecord)
798+
if err != nil {
799+
return nil, fmt.Errorf("unable to decode issuance "+
800+
"proof: %w", err)
801+
}
802+
803+
if !sproutedAsset.IsGenesisAsset() {
804+
return nil, fmt.Errorf("decoded asset is not a " +
805+
"genesis asset")
806+
}
807+
808+
// Populate the key info for the script key and group key.
809+
if sproutedAsset.ScriptKey.PubKey == nil {
810+
return nil, fmt.Errorf("decoded asset is missing " +
811+
"script key")
812+
}
813+
814+
tweakedScriptKey, err := batchStore.FetchScriptKeyByTweakedKey(
815+
ctx, sproutedAsset.ScriptKey.PubKey,
816+
)
817+
if err != nil {
818+
return nil, err
819+
}
820+
821+
sproutedAsset.ScriptKey.TweakedScriptKey = tweakedScriptKey
822+
if sproutedAsset.GroupKey != nil {
823+
assetGroup, err := batchStore.FetchGroupByGroupKey(
824+
ctx, &sproutedAsset.GroupKey.GroupPubKey,
825+
)
826+
if err != nil {
827+
return nil, err
828+
}
829+
830+
sproutedAsset.GroupKey = assetGroup.GroupKey
831+
}
832+
833+
batchAssets = append(batchAssets, &sproutedAsset)
834+
scriptKey := asset.ToSerialized(sproutedAsset.ScriptKey.PubKey)
835+
assetMetas[scriptKey] = seedling.Meta
836+
}
837+
838+
// Verify that we can reconstruct the genesis output script used in the
839+
// anchor TX.
840+
batchSibling := batch.TapSibling()
841+
var tapSibling *chainhash.Hash
842+
if len(batchSibling) != 0 {
843+
var err error
844+
tapSibling, err = chainhash.NewHash(batchSibling)
845+
if err != nil {
846+
return nil, err
847+
}
848+
}
849+
850+
tapCommitment, err := VerifyOutputScript(
851+
batch.BatchKey.PubKey, tapSibling, genScript, batchAssets,
852+
)
853+
854+
if err != nil {
855+
return nil, err
856+
}
857+
858+
// With the batch assets validated, construct the populated finalized
859+
// batch.
860+
batch.Seedlings = nil
861+
finalizedBatch := batch.Copy()
862+
finalizedBatch.RootAssetCommitment = tapCommitment
863+
finalizedBatch.AssetMetas = assetMetas
864+
865+
return finalizedBatch, nil
866+
}
867+
727868
// ListBatches returns the single batch specified by the batch key, or the set
728869
// of batches not yet finalized on disk.
729870
func listBatches(ctx context.Context, batchStore MintingStore,
730-
genBuilder asset.GenesisTxBuilder,
871+
archiver proof.Archiver, genBuilder asset.GenesisTxBuilder,
731872
params ListBatchesParams) ([]*VerboseBatch, error) {
732873

733874
var (
@@ -747,12 +888,54 @@ func listBatches(ctx context.Context, batchStore MintingStore,
747888
return nil, err
748889
}
749890

750-
verboseBatches := fn.Map(batches, func(b *MintingBatch) *VerboseBatch {
751-
return &VerboseBatch{
752-
MintingBatch: b,
753-
UnsealedSeedlings: nil,
891+
var (
892+
finalBatches, nonFinalBatches = filterFinalizedBatches(batches)
893+
verboseBatches []*VerboseBatch
894+
)
895+
896+
switch {
897+
case len(finalBatches) == 0:
898+
verboseBatches = fn.Map(batches,
899+
func(b *MintingBatch) *VerboseBatch {
900+
return &VerboseBatch{
901+
MintingBatch: b,
902+
UnsealedSeedlings: nil,
903+
}
904+
},
905+
)
906+
907+
// For finalized batches, we need to fetch the assets from the proof
908+
// archiver, not the DB.
909+
default:
910+
finalizedBatches := make([]*MintingBatch, 0, len(finalBatches))
911+
for _, batch := range finalBatches {
912+
finalizedBatch, err := fetchFinalizedBatch(
913+
ctx, batchStore, archiver, batch,
914+
)
915+
if err != nil {
916+
return nil, err
917+
}
918+
919+
finalizedBatches = append(
920+
finalizedBatches, finalizedBatch,
921+
)
754922
}
755-
})
923+
924+
// Re-sort the batches by creation time for consistent display.
925+
allBatches := append(nonFinalBatches, finalizedBatches...)
926+
slices.SortFunc(allBatches, func(a, b *MintingBatch) int {
927+
return a.CreationTime.Compare(b.CreationTime)
928+
})
929+
930+
verboseBatches = fn.Map(allBatches,
931+
func(b *MintingBatch) *VerboseBatch {
932+
return &VerboseBatch{
933+
MintingBatch: b,
934+
UnsealedSeedlings: nil,
935+
}
936+
},
937+
)
938+
}
756939

757940
// Return the batches without any extra asset group info.
758941
if !params.Verbose {
@@ -787,11 +970,9 @@ func listBatches(ctx context.Context, batchStore MintingStore,
787970
// Before we can build the group key requests for each seedling,
788971
// we must fetch the genesis point and anchor index for the
789972
// batch.
790-
anchorOutputIndex := uint32(0)
791-
if currentBatch.GenesisPacket.ChangeOutputIndex == 0 {
792-
anchorOutputIndex = 1
793-
}
794-
973+
anchorOutputIndex := extractAnchorOutputIndex(
974+
currentBatch.GenesisPacket,
975+
)
795976
genesisPoint := extractGenesisOutpoint(
796977
currentBatch.GenesisPacket.Pkt.UnsignedTx,
797978
)
@@ -1030,8 +1211,8 @@ func (c *ChainPlanter) gardener() {
10301211

10311212
ctx, cancel := c.WithCtxQuit()
10321213
batches, err := listBatches(
1033-
ctx, c.cfg.Log, c.cfg.GenTxBuilder,
1034-
*listBatchesParams,
1214+
ctx, c.cfg.Log, c.cfg.ProofFiles,
1215+
c.cfg.GenTxBuilder, *listBatchesParams,
10351216
)
10361217
cancel()
10371218
if err != nil {
@@ -1311,11 +1492,9 @@ func (c *ChainPlanter) sealBatch(ctx context.Context, params SealParams,
13111492

13121493
// Before we can build the group key requests for each seedling, we must
13131494
// fetch the genesis point and anchor index for the batch.
1314-
anchorOutputIndex := uint32(0)
1315-
if workingBatch.GenesisPacket.ChangeOutputIndex == 0 {
1316-
anchorOutputIndex = 1
1317-
}
1318-
1495+
anchorOutputIndex := extractAnchorOutputIndex(
1496+
workingBatch.GenesisPacket,
1497+
)
13191498
genesisPoint := extractGenesisOutpoint(
13201499
workingBatch.GenesisPacket.Pkt.UnsignedTx,
13211500
)

0 commit comments

Comments
 (0)