@@ -8491,29 +8491,73 @@ func (r *rpcServer) assetInvoiceAmt(ctx context.Context,
84918491func (r * rpcServer ) DecodeAssetPayReq (ctx context.Context ,
84928492 payReq * tchrpc.AssetPayReq ) (* tchrpc.AssetPayReqResponse , error ) {
84938493
8494+ tapdLog .Debugf ("Decoding asset pay req, asset_id=%x, group_key=%x," +
8495+ "pay_req=%v" , payReq .AssetId , payReq .GroupKey ,
8496+ payReq .PayReqString )
8497+
84948498 if r .cfg .PriceOracle == nil {
84958499 return nil , fmt .Errorf ("price oracle is not set" )
84968500 }
84978501
84988502 // First, we'll perform some basic input validation.
8503+ var assetID asset.ID
84998504 switch {
8500- case len (payReq .AssetId ) == 0 :
8501- return nil , fmt .Errorf ("asset ID must be specified" )
8505+ case len (payReq .AssetId ) == 0 && len (payReq .GroupKey ) == 0 :
8506+ return nil , fmt .Errorf ("either asset ID or group key must be " +
8507+ "specified" )
85028508
8503- case len (payReq .AssetId ) != 32 :
8509+ case len (payReq .AssetId ) != 0 && len (payReq .GroupKey ) != 0 :
8510+ return nil , fmt .Errorf ("cannot set both asset ID and group key" )
8511+
8512+ case len (payReq .AssetId ) != 0 && len (payReq .AssetId ) != 32 :
85048513 return nil , fmt .Errorf ("asset ID must be 32 bytes, " +
85058514 "was %d" , len (payReq .AssetId ))
85068515
8516+ case len (payReq .GroupKey ) != 0 && len (payReq .GroupKey ) != 33 &&
8517+ len (payReq .GroupKey ) != 32 :
8518+
8519+ return nil , fmt .Errorf ("group key must be 32 or 33 bytes, " +
8520+ "was %d" , len (payReq .GroupKey ))
8521+
85078522 case len (payReq .PayReqString ) == 0 :
85088523 return nil , fmt .Errorf ("payment request must be specified" )
85098524 }
85108525
8511- var (
8512- resp tchrpc.AssetPayReqResponse
8513- assetID asset.ID
8514- )
8526+ // We made sure that only one is set, so let's now use the asset ID
8527+ // or group key.
8528+ switch {
8529+ // The asset ID is easy, we can just copy the bytes.
8530+ case len (payReq .AssetId ) != 0 :
8531+ copy (assetID [:], payReq .AssetId )
8532+
8533+ // The group key is a bit more involved. We first need to sync the asset
8534+ // group, then fetch the leaves that are associated with this group key.
8535+ // From there, we can look up the asset ID of one of the group's
8536+ // tranches.
8537+ case len (payReq .GroupKey ) != 0 :
8538+ var (
8539+ groupKey * btcec.PublicKey
8540+ err error
8541+ )
8542+ if len (payReq .GroupKey ) == 32 {
8543+ groupKey , err = schnorr .ParsePubKey (payReq .GroupKey )
8544+ } else {
8545+ groupKey , err = btcec .ParsePubKey (payReq .GroupKey )
8546+ }
8547+ if err != nil {
8548+ return nil , fmt .Errorf ("error parsing group " +
8549+ "key: %w" , err )
8550+ }
8551+
8552+ assetID , err = r .syncAssetGroup (ctx , groupKey )
8553+ if err != nil {
8554+ return nil , fmt .Errorf ("error syncing asset group: %w" ,
8555+ err )
8556+ }
85158557
8516- copy (assetID [:], payReq .AssetId )
8558+ tapdLog .Debugf ("Resolved asset ID %v for group key %x" ,
8559+ assetID .String (), groupKey .SerializeCompressed ())
8560+ }
85178561
85188562 // With the inputs validated, we'll first call out to lnd to decode the
85198563 // payment request.
@@ -8525,7 +8569,9 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
85258569 return nil , fmt .Errorf ("unable to fetch channel: %w" , err )
85268570 }
85278571
8528- resp .PayReq = payReqInfo
8572+ resp := tchrpc.AssetPayReqResponse {
8573+ PayReq : payReqInfo ,
8574+ }
85298575
85308576 // Next, we'll fetch the information for this asset ID through the addr
85318577 // book. This'll automatically fetch the asset if needed.
@@ -8535,12 +8581,17 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
85358581 "asset_id=%x: %w" , assetID [:], err )
85368582 }
85378583
8538- resp .GenesisInfo = & taprpc.GenesisInfo {
8539- GenesisPoint : assetGroup .FirstPrevOut .String (),
8540- AssetType : taprpc .AssetType (assetGroup .Type ),
8541- Name : assetGroup .Tag ,
8542- MetaHash : assetGroup .MetaHash [:],
8543- AssetId : assetID [:],
8584+ // The genesis info makes no sense in the case where we have decoded the
8585+ // invoice for a group key, since we just pick the first asset ID we
8586+ // found for a group key.
8587+ if len (payReq .GroupKey ) == 0 {
8588+ resp .GenesisInfo = & taprpc.GenesisInfo {
8589+ GenesisPoint : assetGroup .FirstPrevOut .String (),
8590+ AssetType : taprpc .AssetType (assetGroup .Type ),
8591+ Name : assetGroup .Tag ,
8592+ MetaHash : assetGroup .MetaHash [:],
8593+ AssetId : assetID [:],
8594+ }
85448595 }
85458596
85468597 // If this asset ID belongs to an asset group, then we'll display that
@@ -8600,6 +8651,96 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
86008651 return & resp , nil
86018652}
86028653
8654+ // syncAssetGroup checks if we already know any asset leaves associated with
8655+ // this group key. If not, it will sync the asset group and return the asset
8656+ // ID of the first leaf found. If there are no leaves found after syncing, an
8657+ // error is returned.
8658+ func (r * rpcServer ) syncAssetGroup (ctx context.Context ,
8659+ groupKey * btcec.PublicKey ) (asset.ID , error ) {
8660+
8661+ // We first check if we already know any asset leaves associated with
8662+ // this group key.
8663+ leaf := fn.NewRight [asset.ID ](* groupKey )
8664+ leaves , err := r .cfg .Multiverse .FetchLeaves (
8665+ ctx , []universe.MultiverseLeafDesc {leaf },
8666+ universe .ProofTypeIssuance ,
8667+ )
8668+ if err != nil {
8669+ return asset.ID {}, fmt .Errorf ("error fetching leaves: %w" , err )
8670+ }
8671+
8672+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8673+ len (leaves ), groupKey .SerializeCompressed ())
8674+
8675+ // If there are no leaves, then we need to sync the asset group.
8676+ if len (leaves ) == 0 {
8677+ tapdLog .Debugf ("No leaves found for group key %x, " +
8678+ "syncing asset group" , groupKey .SerializeCompressed ())
8679+ err = r .cfg .AddrBook .SyncAssetGroup (ctx , groupKey )
8680+ if err != nil {
8681+ return asset.ID {}, fmt .Errorf ("error syncing asset " +
8682+ "group: %w" , err )
8683+ }
8684+
8685+ // Now we can try again.
8686+ leaves , err = r .cfg .Multiverse .FetchLeaves (
8687+ ctx , []universe.MultiverseLeafDesc {leaf },
8688+ universe .ProofTypeIssuance ,
8689+ )
8690+ if err != nil {
8691+ return asset.ID {}, fmt .Errorf ("error fetching leaves: " +
8692+ "%w" , err )
8693+ }
8694+
8695+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8696+ len (leaves ), groupKey .SerializeCompressed ())
8697+
8698+ if len (leaves ) == 0 {
8699+ return asset.ID {}, fmt .Errorf ("no asset leaves found " +
8700+ "for group %x after sync" ,
8701+ groupKey .SerializeCompressed ())
8702+ }
8703+ }
8704+
8705+ // Since we know we have at least one leaf, we can just take leaf ID and
8706+ // query the keys with it. We just need one so we can fetch the actual
8707+ // proof to find out the asset ID.
8708+ leafID := leaves [0 ]
8709+ leafKeys , err := r .cfg .Multiverse .UniverseLeafKeys (
8710+ ctx , universe.UniverseLeafKeysQuery {
8711+ Id : leafID .ID ,
8712+ Limit : 1 ,
8713+ },
8714+ )
8715+ if err != nil {
8716+ return asset.ID {}, fmt .Errorf ("error fetching leaf keys: %w" ,
8717+ err )
8718+ }
8719+
8720+ // We know we have a leaf, so this shouldn't happen.
8721+ if len (leafKeys ) != 1 {
8722+ return asset.ID {}, fmt .Errorf ("expected 1 leaf key, got %d" ,
8723+ len (leafKeys ))
8724+ }
8725+
8726+ proofs , err := r .cfg .Multiverse .FetchProofLeaf (
8727+ ctx , leafID .ID , leafKeys [0 ],
8728+ )
8729+ if err != nil {
8730+ return asset.ID {}, fmt .Errorf ("error fetching proof leaf: %w" ,
8731+ err )
8732+ }
8733+
8734+ // We should have a proof for the asset ID now.
8735+ if len (proofs ) != 1 {
8736+ return asset.ID {}, fmt .Errorf ("expected 1 proof, got %d" ,
8737+ len (proofs ))
8738+ }
8739+
8740+ // We can now extract the asset ID from the proof.
8741+ return proofs [0 ].Leaf .ID (), nil
8742+ }
8743+
86038744// RegisterTransfer informs the daemon about a new inbound transfer that has
86048745// happened. This is used for interactive transfers where no TAP address is
86058746// involved and the recipient is aware of the transfer through an out-of-band
0 commit comments