@@ -8584,29 +8584,73 @@ func (r *rpcServer) assetInvoiceAmt(ctx context.Context,
85848584func (r * rpcServer ) DecodeAssetPayReq (ctx context.Context ,
85858585 payReq * tchrpc.AssetPayReq ) (* tchrpc.AssetPayReqResponse , error ) {
85868586
8587+ tapdLog .Debugf ("Decoding asset pay req, asset_id=%x, group_key=%x," +
8588+ "pay_req=%v" , payReq .AssetId , payReq .GroupKey ,
8589+ payReq .PayReqString )
8590+
85878591 if r .cfg .PriceOracle == nil {
85888592 return nil , fmt .Errorf ("price oracle is not set" )
85898593 }
85908594
85918595 // First, we'll perform some basic input validation.
8596+ var assetID asset.ID
85928597 switch {
8593- case len (payReq .AssetId ) == 0 :
8594- return nil , fmt .Errorf ("asset ID must be specified" )
8598+ case len (payReq .AssetId ) == 0 && len (payReq .GroupKey ) == 0 :
8599+ return nil , fmt .Errorf ("either asset ID or group key must be " +
8600+ "specified" )
85958601
8596- case len (payReq .AssetId ) != 32 :
8602+ case len (payReq .AssetId ) != 0 && len (payReq .GroupKey ) != 0 :
8603+ return nil , fmt .Errorf ("cannot set both asset ID and group key" )
8604+
8605+ case len (payReq .AssetId ) != 0 && len (payReq .AssetId ) != 32 :
85978606 return nil , fmt .Errorf ("asset ID must be 32 bytes, " +
85988607 "was %d" , len (payReq .AssetId ))
85998608
8609+ case len (payReq .GroupKey ) != 0 && len (payReq .GroupKey ) != 33 &&
8610+ len (payReq .GroupKey ) != 32 :
8611+
8612+ return nil , fmt .Errorf ("group key must be 32 or 33 bytes, " +
8613+ "was %d" , len (payReq .GroupKey ))
8614+
86008615 case len (payReq .PayReqString ) == 0 :
86018616 return nil , fmt .Errorf ("payment request must be specified" )
86028617 }
86038618
8604- var (
8605- resp tchrpc.AssetPayReqResponse
8606- assetID asset.ID
8607- )
8619+ // We made sure that only one is set, so let's now use the asset ID
8620+ // or group key.
8621+ switch {
8622+ // The asset ID is easy, we can just copy the bytes.
8623+ case len (payReq .AssetId ) != 0 :
8624+ copy (assetID [:], payReq .AssetId )
8625+
8626+ // The group key is a bit more involved. We first need to sync the asset
8627+ // group, then fetch the leaves that are associated with this group key.
8628+ // From there, we can look up the asset ID of one of the group's
8629+ // tranches.
8630+ case len (payReq .GroupKey ) != 0 :
8631+ var (
8632+ groupKey * btcec.PublicKey
8633+ err error
8634+ )
8635+ if len (payReq .GroupKey ) == 32 {
8636+ groupKey , err = schnorr .ParsePubKey (payReq .GroupKey )
8637+ } else {
8638+ groupKey , err = btcec .ParsePubKey (payReq .GroupKey )
8639+ }
8640+ if err != nil {
8641+ return nil , fmt .Errorf ("error parsing group " +
8642+ "key: %w" , err )
8643+ }
8644+
8645+ assetID , err = r .syncAssetGroup (ctx , * groupKey )
8646+ if err != nil {
8647+ return nil , fmt .Errorf ("error syncing asset group: %w" ,
8648+ err )
8649+ }
86088650
8609- copy (assetID [:], payReq .AssetId )
8651+ tapdLog .Debugf ("Resolved asset ID %v for group key %x" ,
8652+ assetID .String (), groupKey .SerializeCompressed ())
8653+ }
86108654
86118655 // With the inputs validated, we'll first call out to lnd to decode the
86128656 // payment request.
@@ -8618,7 +8662,9 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
86188662 return nil , fmt .Errorf ("unable to fetch channel: %w" , err )
86198663 }
86208664
8621- resp .PayReq = payReqInfo
8665+ resp := tchrpc.AssetPayReqResponse {
8666+ PayReq : payReqInfo ,
8667+ }
86228668
86238669 // Next, we'll fetch the information for this asset ID through the addr
86248670 // book. This'll automatically fetch the asset if needed.
@@ -8628,12 +8674,17 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
86288674 "asset_id=%x: %w" , assetID [:], err )
86298675 }
86308676
8631- resp .GenesisInfo = & taprpc.GenesisInfo {
8632- GenesisPoint : assetGroup .FirstPrevOut .String (),
8633- AssetType : taprpc .AssetType (assetGroup .Type ),
8634- Name : assetGroup .Tag ,
8635- MetaHash : assetGroup .MetaHash [:],
8636- AssetId : assetID [:],
8677+ // The genesis info makes no sense in the case where we have decoded the
8678+ // invoice for a group key, since we just pick the first asset ID we
8679+ // found for a group key.
8680+ if len (payReq .GroupKey ) == 0 {
8681+ resp .GenesisInfo = & taprpc.GenesisInfo {
8682+ GenesisPoint : assetGroup .FirstPrevOut .String (),
8683+ AssetType : taprpc .AssetType (assetGroup .Type ),
8684+ Name : assetGroup .Tag ,
8685+ MetaHash : assetGroup .MetaHash [:],
8686+ AssetId : assetID [:],
8687+ }
86378688 }
86388689
86398690 // If this asset ID belongs to an asset group, then we'll display that
@@ -8693,6 +8744,97 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
86938744 return & resp , nil
86948745}
86958746
8747+ // syncAssetGroup checks if we already know any asset leaves associated with
8748+ // this group key. If not, it will sync the asset group and return the asset
8749+ // ID of the first leaf found. If there are no leaves found after syncing, an
8750+ // error is returned.
8751+ func (r * rpcServer ) syncAssetGroup (ctx context.Context ,
8752+ groupKey btcec.PublicKey ) (asset.ID , error ) {
8753+
8754+ // We first check if we already know any asset leaves associated with
8755+ // this group key.
8756+ leaf := fn.NewRight [asset.ID ](groupKey )
8757+ leaves , err := r .cfg .Multiverse .FetchLeaves (
8758+ ctx , []universe.MultiverseLeafDesc {leaf },
8759+ universe .ProofTypeIssuance ,
8760+ )
8761+ if err != nil {
8762+ return asset.ID {}, fmt .Errorf ("error fetching leaves: %w" , err )
8763+ }
8764+
8765+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8766+ len (leaves ), groupKey .SerializeCompressed ())
8767+
8768+ // If there are no leaves, then we need to sync the asset group.
8769+ if len (leaves ) == 0 {
8770+ tapdLog .Debugf ("No leaves found for group key %x, " +
8771+ "syncing asset group" , groupKey .SerializeCompressed ())
8772+ err = r .cfg .AddrBook .SyncAssetGroup (ctx , groupKey )
8773+ if err != nil {
8774+ return asset.ID {}, fmt .Errorf ("error syncing asset " +
8775+ "group: %w" , err )
8776+ }
8777+
8778+ // Now we can try again.
8779+ leaves , err = r .cfg .Multiverse .FetchLeaves (
8780+ ctx , []universe.MultiverseLeafDesc {leaf },
8781+ universe .ProofTypeIssuance ,
8782+ )
8783+ if err != nil {
8784+ return asset.ID {}, fmt .Errorf ("error fetching leaves: " +
8785+ "%w" , err )
8786+ }
8787+
8788+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8789+ len (leaves ), groupKey .SerializeCompressed ())
8790+
8791+ if len (leaves ) == 0 {
8792+ return asset.ID {}, fmt .Errorf ("no asset leaves found " +
8793+ "for group %x after sync" ,
8794+ groupKey .SerializeCompressed ())
8795+ }
8796+ }
8797+
8798+ // Since we know we have at least one leaf, we can just take leaf ID and
8799+ // query the keys with it. We just need one so we can fetch the actual
8800+ // proof to find out the asset ID. We don't really care about the order,
8801+ // so we just use the natural database order.
8802+ leafID := leaves [0 ]
8803+ leafKeys , err := r .cfg .Multiverse .UniverseLeafKeys (
8804+ ctx , universe.UniverseLeafKeysQuery {
8805+ Id : leafID .ID ,
8806+ Limit : 1 ,
8807+ },
8808+ )
8809+ if err != nil {
8810+ return asset.ID {}, fmt .Errorf ("error fetching leaf keys: %w" ,
8811+ err )
8812+ }
8813+
8814+ // We know we have a leaf, so this shouldn't happen.
8815+ if len (leafKeys ) != 1 {
8816+ return asset.ID {}, fmt .Errorf ("expected 1 leaf key, got %d" ,
8817+ len (leafKeys ))
8818+ }
8819+
8820+ proofs , err := r .cfg .Multiverse .FetchProofLeaf (
8821+ ctx , leafID .ID , leafKeys [0 ],
8822+ )
8823+ if err != nil {
8824+ return asset.ID {}, fmt .Errorf ("error fetching proof leaf: %w" ,
8825+ err )
8826+ }
8827+
8828+ // We should have a proof for the asset ID now.
8829+ if len (proofs ) != 1 {
8830+ return asset.ID {}, fmt .Errorf ("expected 1 proof, got %d" ,
8831+ len (proofs ))
8832+ }
8833+
8834+ // We can now extract the asset ID from the proof.
8835+ return proofs [0 ].Leaf .ID (), nil
8836+ }
8837+
86968838// RegisterTransfer informs the daemon about a new inbound transfer that has
86978839// happened. This is used for interactive transfers where no TAP address is
86988840// involved and the recipient is aware of the transfer through an out-of-band
0 commit comments