@@ -145,6 +145,13 @@ type Archiver interface {
145145 // specific fields need to be set in the Locator (e.g. the OutPoint).
146146 FetchProof (ctx context.Context , id Locator ) (Blob , error )
147147
148+ // FetchIssuanceProof fetches the issuance proof for an asset, given the
149+ // anchor point of the issuance (NOT the genesis point for the asset).
150+ //
151+ // If a proof cannot be found, then ErrProofNotFound should be returned.
152+ FetchIssuanceProof (ctx context.Context , id asset.ID ,
153+ anchorOutpoint wire.OutPoint ) (Blob , error )
154+
148155 // HasProof returns true if the proof for the given locator exists. This
149156 // is intended to be a performance optimized lookup compared to fetching
150157 // a proof and checking for ErrProofNotFound.
@@ -385,6 +392,7 @@ func lookupProofFilePath(rootPath string, loc Locator) (string, error) {
385392 assetID := hex .EncodeToString (loc .AssetID [:])
386393 scriptKey := hex .EncodeToString (loc .ScriptKey .SerializeCompressed ())
387394
395+ // TODO(jhb): Check for correct file suffix and truncated outpoint?
388396 searchPattern := filepath .Join (rootPath , assetID , scriptKey + "*" )
389397 matches , err := filepath .Glob (searchPattern )
390398 if err != nil {
@@ -529,6 +537,78 @@ func (f *FileArchiver) FetchProof(_ context.Context, id Locator) (Blob, error) {
529537 return proofFile , nil
530538}
531539
540+ // FetchIssuanceProof fetches the issuance proof for an asset, given the
541+ // anchor point of the issuance (NOT the genesis point for the asset).
542+ //
543+ // If a proof cannot be found, then ErrProofNotFound should be returned.
544+ //
545+ // NOTE: This implements the Archiver interface.
546+ func (f * FileArchiver ) FetchIssuanceProof (ctx context.Context , id asset.ID ,
547+ anchorOutpoint wire.OutPoint ) (Blob , error ) {
548+
549+ // Construct a pattern to search for the issuance proof file. We'll
550+ // leave the script key unspecified, as we don't know what the script
551+ // key was at genesis.
552+ assetID := hex .EncodeToString (id [:])
553+ scriptKeyGlob := strings .Repeat ("?" , 2 * btcec .PubKeyBytesLenCompressed )
554+ truncatedHash := anchorOutpoint .Hash .String ()[:outpointTruncateLength ]
555+
556+ fileName := fmt .Sprintf ("%s-%s-%d.%s" ,
557+ scriptKeyGlob , truncatedHash , anchorOutpoint .Index ,
558+ TaprootAssetsFileEnding )
559+
560+ searchPattern := filepath .Join (f .proofPath , assetID , fileName )
561+ matches , err := filepath .Glob (searchPattern )
562+ if err != nil {
563+ return nil , fmt .Errorf ("error listing proof files: %w" , err )
564+ }
565+ if len (matches ) == 0 {
566+ return nil , ErrProofNotFound
567+ }
568+
569+ // We expect exactly one matching proof for a specific asset ID and
570+ // outpoint. However, the proof file path uses the truncated outpoint,
571+ // so an asset transfer with a collision in the first half of the TXID
572+ // could also match. We can filter out such proof files by size.
573+ proofFiles := make ([]Blob , 0 , len (matches ))
574+ for _ , path := range matches {
575+ proofFile , err := os .ReadFile (path )
576+
577+ switch {
578+ case os .IsNotExist (err ):
579+ return nil , ErrProofNotFound
580+
581+ case err != nil :
582+ return nil , fmt .Errorf ("unable to find proof: %w" , err )
583+ }
584+
585+ proofFiles = append (proofFiles , proofFile )
586+ }
587+
588+ switch {
589+ // No proofs were read.
590+ case len (proofFiles ) == 0 :
591+ return nil , ErrProofNotFound
592+
593+ // Exactly one proof, we'll return it.
594+ case len (proofFiles ) == 1 :
595+ return proofFiles [0 ], nil
596+
597+ // Multiple proofs, return the smallest one.
598+ default :
599+ minProofIdx := 0
600+ minProofSize := len (proofFiles [minProofIdx ])
601+ for idx , proof := range proofFiles {
602+ if len (proof ) < minProofSize {
603+ minProofSize = len (proof )
604+ minProofIdx = idx
605+ }
606+ }
607+
608+ return proofFiles [minProofIdx ], nil
609+ }
610+ }
611+
532612// HasProof returns true if the proof for the given locator exists. This is
533613// intended to be a performance optimized lookup compared to fetching a proof
534614// and checking for ErrProofNotFound.
@@ -704,10 +784,13 @@ func (f *FileArchiver) RemoveSubscriber(
704784 return f .eventDistributor .RemoveSubscriber (subscriber )
705785}
706786
707- // A compile-time interface to ensure FileArchiver meets the NotifyArchiver
787+ // A compile-time assertion to ensure FileArchiver meets the NotifyArchiver
708788// interface.
709789var _ NotifyArchiver = (* FileArchiver )(nil )
710790
791+ // A compile-time assertion to ensure FileArchiver meets the Archiver interface.
792+ var _ Archiver = (* FileArchiver )(nil )
793+
711794// MultiArchiver is an archive of archives. It contains several archives and
712795// attempts to use them either as a look-aside cache, or a write through cache
713796// for all incoming requests.
@@ -763,6 +846,33 @@ func (m *MultiArchiver) FetchProof(ctx context.Context,
763846 return nil , ErrProofNotFound
764847}
765848
849+ // FetchIssuanceProof fetches the issuance proof for an asset, given the
850+ // anchor point of the issuance (NOT the genesis point for the asset).
851+ func (m * MultiArchiver ) FetchIssuanceProof (ctx context.Context ,
852+ id asset.ID , anchorOutpoint wire.OutPoint ) (Blob , error ) {
853+
854+ // Iterate through all our active backends and try to see if at least
855+ // one of them contains the proof. Either one of them will have the
856+ // proof, or we'll return an error back to the user.
857+ for _ , archive := range m .backends {
858+ proof , err := archive .FetchIssuanceProof (
859+ ctx , id , anchorOutpoint ,
860+ )
861+
862+ switch {
863+ case errors .Is (err , ErrProofNotFound ):
864+ continue
865+
866+ case err != nil :
867+ return nil , err
868+ }
869+
870+ return proof , nil
871+ }
872+
873+ return nil , ErrProofNotFound
874+ }
875+
766876// HasProof returns true if the proof for the given locator exists. This is
767877// intended to be a performance optimized lookup compared to fetching a proof
768878// and checking for ErrProofNotFound. The multi archiver only considers a proof
0 commit comments