@@ -8581,29 +8581,73 @@ func (r *rpcServer) assetInvoiceAmt(ctx context.Context,
85818581func (r * rpcServer ) DecodeAssetPayReq (ctx context.Context ,
85828582 payReq * tchrpc.AssetPayReq ) (* tchrpc.AssetPayReqResponse , error ) {
85838583
8584+ tapdLog .Debugf ("Decoding asset pay req, asset_id=%x, group_key=%x," +
8585+ "pay_req=%v" , payReq .AssetId , payReq .GroupKey ,
8586+ payReq .PayReqString )
8587+
85848588 if r .cfg .PriceOracle == nil {
85858589 return nil , fmt .Errorf ("price oracle is not set" )
85868590 }
85878591
85888592 // First, we'll perform some basic input validation.
8593+ var assetID asset.ID
85898594 switch {
8590- case len (payReq .AssetId ) == 0 :
8591- return nil , fmt .Errorf ("asset ID must be specified" )
8595+ case len (payReq .AssetId ) == 0 && len (payReq .GroupKey ) == 0 :
8596+ return nil , fmt .Errorf ("either asset ID or group key must be " +
8597+ "specified" )
85928598
8593- case len (payReq .AssetId ) != 32 :
8599+ case len (payReq .AssetId ) != 0 && len (payReq .GroupKey ) != 0 :
8600+ return nil , fmt .Errorf ("cannot set both asset ID and group key" )
8601+
8602+ case len (payReq .AssetId ) != 0 && len (payReq .AssetId ) != 32 :
85948603 return nil , fmt .Errorf ("asset ID must be 32 bytes, " +
85958604 "was %d" , len (payReq .AssetId ))
85968605
8606+ case len (payReq .GroupKey ) != 0 && len (payReq .GroupKey ) != 33 &&
8607+ len (payReq .GroupKey ) != 32 :
8608+
8609+ return nil , fmt .Errorf ("group key must be 32 or 33 bytes, " +
8610+ "was %d" , len (payReq .GroupKey ))
8611+
85978612 case len (payReq .PayReqString ) == 0 :
85988613 return nil , fmt .Errorf ("payment request must be specified" )
85998614 }
86008615
8601- var (
8602- resp tchrpc.AssetPayReqResponse
8603- assetID asset.ID
8604- )
8616+ // We made sure that only one is set, so let's now use the asset ID
8617+ // or group key.
8618+ switch {
8619+ // The asset ID is easy, we can just copy the bytes.
8620+ case len (payReq .AssetId ) != 0 :
8621+ copy (assetID [:], payReq .AssetId )
8622+
8623+ // The group key is a bit more involved. We first need to sync the asset
8624+ // group, then fetch the leaves that are associated with this group key.
8625+ // From there, we can look up the asset ID of one of the group's
8626+ // tranches.
8627+ case len (payReq .GroupKey ) != 0 :
8628+ var (
8629+ groupKey * btcec.PublicKey
8630+ err error
8631+ )
8632+ if len (payReq .GroupKey ) == 32 {
8633+ groupKey , err = schnorr .ParsePubKey (payReq .GroupKey )
8634+ } else {
8635+ groupKey , err = btcec .ParsePubKey (payReq .GroupKey )
8636+ }
8637+ if err != nil {
8638+ return nil , fmt .Errorf ("error parsing group " +
8639+ "key: %w" , err )
8640+ }
8641+
8642+ assetID , err = r .syncAssetGroup (ctx , * groupKey )
8643+ if err != nil {
8644+ return nil , fmt .Errorf ("error syncing asset group: %w" ,
8645+ err )
8646+ }
86058647
8606- copy (assetID [:], payReq .AssetId )
8648+ tapdLog .Debugf ("Resolved asset ID %v for group key %x" ,
8649+ assetID .String (), groupKey .SerializeCompressed ())
8650+ }
86078651
86088652 // With the inputs validated, we'll first call out to lnd to decode the
86098653 // payment request.
@@ -8615,7 +8659,9 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
86158659 return nil , fmt .Errorf ("unable to fetch channel: %w" , err )
86168660 }
86178661
8618- resp .PayReq = payReqInfo
8662+ resp := tchrpc.AssetPayReqResponse {
8663+ PayReq : payReqInfo ,
8664+ }
86198665
86208666 // Next, we'll fetch the information for this asset ID through the addr
86218667 // book. This'll automatically fetch the asset if needed.
@@ -8625,12 +8671,17 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
86258671 "asset_id=%x: %w" , assetID [:], err )
86268672 }
86278673
8628- resp .GenesisInfo = & taprpc.GenesisInfo {
8629- GenesisPoint : assetGroup .FirstPrevOut .String (),
8630- AssetType : taprpc .AssetType (assetGroup .Type ),
8631- Name : assetGroup .Tag ,
8632- MetaHash : assetGroup .MetaHash [:],
8633- AssetId : assetID [:],
8674+ // The genesis info makes no sense in the case where we have decoded the
8675+ // invoice for a group key, since we just pick the first asset ID we
8676+ // found for a group key.
8677+ if len (payReq .GroupKey ) == 0 {
8678+ resp .GenesisInfo = & taprpc.GenesisInfo {
8679+ GenesisPoint : assetGroup .FirstPrevOut .String (),
8680+ AssetType : taprpc .AssetType (assetGroup .Type ),
8681+ Name : assetGroup .Tag ,
8682+ MetaHash : assetGroup .MetaHash [:],
8683+ AssetId : assetID [:],
8684+ }
86348685 }
86358686
86368687 // If this asset ID belongs to an asset group, then we'll display that
@@ -8690,6 +8741,97 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
86908741 return & resp , nil
86918742}
86928743
8744+ // syncAssetGroup checks if we already know any asset leaves associated with
8745+ // this group key. If not, it will sync the asset group and return the asset
8746+ // ID of the first leaf found. If there are no leaves found after syncing, an
8747+ // error is returned.
8748+ func (r * rpcServer ) syncAssetGroup (ctx context.Context ,
8749+ groupKey btcec.PublicKey ) (asset.ID , error ) {
8750+
8751+ // We first check if we already know any asset leaves associated with
8752+ // this group key.
8753+ leaf := fn.NewRight [asset.ID ](groupKey )
8754+ leaves , err := r .cfg .Multiverse .FetchLeaves (
8755+ ctx , []universe.MultiverseLeafDesc {leaf },
8756+ universe .ProofTypeIssuance ,
8757+ )
8758+ if err != nil {
8759+ return asset.ID {}, fmt .Errorf ("error fetching leaves: %w" , err )
8760+ }
8761+
8762+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8763+ len (leaves ), groupKey .SerializeCompressed ())
8764+
8765+ // If there are no leaves, then we need to sync the asset group.
8766+ if len (leaves ) == 0 {
8767+ tapdLog .Debugf ("No leaves found for group key %x, " +
8768+ "syncing asset group" , groupKey .SerializeCompressed ())
8769+ err = r .cfg .AddrBook .SyncAssetGroup (ctx , groupKey )
8770+ if err != nil {
8771+ return asset.ID {}, fmt .Errorf ("error syncing asset " +
8772+ "group: %w" , err )
8773+ }
8774+
8775+ // Now we can try again.
8776+ leaves , err = r .cfg .Multiverse .FetchLeaves (
8777+ ctx , []universe.MultiverseLeafDesc {leaf },
8778+ universe .ProofTypeIssuance ,
8779+ )
8780+ if err != nil {
8781+ return asset.ID {}, fmt .Errorf ("error fetching leaves: " +
8782+ "%w" , err )
8783+ }
8784+
8785+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8786+ len (leaves ), groupKey .SerializeCompressed ())
8787+
8788+ if len (leaves ) == 0 {
8789+ return asset.ID {}, fmt .Errorf ("no asset leaves found " +
8790+ "for group %x after sync" ,
8791+ groupKey .SerializeCompressed ())
8792+ }
8793+ }
8794+
8795+ // Since we know we have at least one leaf, we can just take leaf ID and
8796+ // query the keys with it. We just need one so we can fetch the actual
8797+ // proof to find out the asset ID. We don't really care about the order,
8798+ // so we just use the natural database order.
8799+ leafID := leaves [0 ]
8800+ leafKeys , err := r .cfg .Multiverse .UniverseLeafKeys (
8801+ ctx , universe.UniverseLeafKeysQuery {
8802+ Id : leafID .ID ,
8803+ Limit : 1 ,
8804+ },
8805+ )
8806+ if err != nil {
8807+ return asset.ID {}, fmt .Errorf ("error fetching leaf keys: %w" ,
8808+ err )
8809+ }
8810+
8811+ // We know we have a leaf, so this shouldn't happen.
8812+ if len (leafKeys ) != 1 {
8813+ return asset.ID {}, fmt .Errorf ("expected 1 leaf key, got %d" ,
8814+ len (leafKeys ))
8815+ }
8816+
8817+ proofs , err := r .cfg .Multiverse .FetchProofLeaf (
8818+ ctx , leafID .ID , leafKeys [0 ],
8819+ )
8820+ if err != nil {
8821+ return asset.ID {}, fmt .Errorf ("error fetching proof leaf: %w" ,
8822+ err )
8823+ }
8824+
8825+ // We should have a proof for the asset ID now.
8826+ if len (proofs ) != 1 {
8827+ return asset.ID {}, fmt .Errorf ("expected 1 proof, got %d" ,
8828+ len (proofs ))
8829+ }
8830+
8831+ // We can now extract the asset ID from the proof.
8832+ return proofs [0 ].Leaf .ID (), nil
8833+ }
8834+
86938835// RegisterTransfer informs the daemon about a new inbound transfer that has
86948836// happened. This is used for interactive transfers where no TAP address is
86958837// involved and the recipient is aware of the transfer through an out-of-band
0 commit comments