@@ -8418,29 +8418,73 @@ func (r *rpcServer) assetInvoiceAmt(ctx context.Context,
84188418func (r * rpcServer ) DecodeAssetPayReq (ctx context.Context ,
84198419 payReq * tchrpc.AssetPayReq ) (* tchrpc.AssetPayReqResponse , error ) {
84208420
8421+ tapdLog .Debugf ("Decoding asset pay req, asset_id=%x, group_key=%x," +
8422+ "pay_req=%v" , payReq .AssetId , payReq .GroupKey ,
8423+ payReq .PayReqString )
8424+
84218425 if r .cfg .PriceOracle == nil {
84228426 return nil , fmt .Errorf ("price oracle is not set" )
84238427 }
84248428
84258429 // First, we'll perform some basic input validation.
8430+ var assetID asset.ID
84268431 switch {
8427- case len (payReq .AssetId ) == 0 :
8428- return nil , fmt .Errorf ("asset ID must be specified" )
8432+ case len (payReq .AssetId ) == 0 && len (payReq .GroupKey ) == 0 :
8433+ return nil , fmt .Errorf ("either asset ID or group key must be " +
8434+ "specified" )
84298435
8430- case len (payReq .AssetId ) != 32 :
8436+ case len (payReq .AssetId ) != 0 && len (payReq .GroupKey ) != 0 :
8437+ return nil , fmt .Errorf ("cannot set both asset ID and group key" )
8438+
8439+ case len (payReq .AssetId ) != 0 && len (payReq .AssetId ) != 32 :
84318440 return nil , fmt .Errorf ("asset ID must be 32 bytes, " +
84328441 "was %d" , len (payReq .AssetId ))
84338442
8443+ case len (payReq .GroupKey ) != 0 && len (payReq .GroupKey ) != 33 &&
8444+ len (payReq .GroupKey ) != 32 :
8445+
8446+ return nil , fmt .Errorf ("group key must be 32 or 33 bytes, " +
8447+ "was %d" , len (payReq .GroupKey ))
8448+
84348449 case len (payReq .PayReqString ) == 0 :
84358450 return nil , fmt .Errorf ("payment request must be specified" )
84368451 }
84378452
8438- var (
8439- resp tchrpc.AssetPayReqResponse
8440- assetID asset.ID
8441- )
8453+ // We made sure that only one is set, so let's now use the asset ID
8454+ // or group key.
8455+ switch {
8456+ // The asset ID is easy, we can just copy the bytes.
8457+ case len (payReq .AssetId ) != 0 :
8458+ copy (assetID [:], payReq .AssetId )
8459+
8460+ // The group key is a bit more involved. We first need to sync the asset
8461+ // group, then fetch the leaves that are associated with this group key.
8462+ // From there, we can look up the asset ID of one of the group's
8463+ // tranches.
8464+ case len (payReq .GroupKey ) != 0 :
8465+ var (
8466+ groupKey * btcec.PublicKey
8467+ err error
8468+ )
8469+ if len (payReq .GroupKey ) == 32 {
8470+ groupKey , err = schnorr .ParsePubKey (payReq .GroupKey )
8471+ } else {
8472+ groupKey , err = btcec .ParsePubKey (payReq .GroupKey )
8473+ }
8474+ if err != nil {
8475+ return nil , fmt .Errorf ("error parsing group " +
8476+ "key: %w" , err )
8477+ }
8478+
8479+ assetID , err = r .syncAssetGroup (ctx , groupKey )
8480+ if err != nil {
8481+ return nil , fmt .Errorf ("error syncing asset group: %w" ,
8482+ err )
8483+ }
84428484
8443- copy (assetID [:], payReq .AssetId )
8485+ tapdLog .Debugf ("Resolved asset ID %v for group key %x" ,
8486+ assetID .String (), groupKey .SerializeCompressed ())
8487+ }
84448488
84458489 // With the inputs validated, we'll first call out to lnd to decode the
84468490 // payment request.
@@ -8452,7 +8496,9 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
84528496 return nil , fmt .Errorf ("unable to fetch channel: %w" , err )
84538497 }
84548498
8455- resp .PayReq = payReqInfo
8499+ resp := tchrpc.AssetPayReqResponse {
8500+ PayReq : payReqInfo ,
8501+ }
84568502
84578503 // Next, we'll fetch the information for this asset ID through the addr
84588504 // book. This'll automatically fetch the asset if needed.
@@ -8527,6 +8573,96 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
85278573 return & resp , nil
85288574}
85298575
8576+ // syncAssetGroup checks if we already know any asset leaves associated with
8577+ // this group key. If not, it will sync the asset group and return the asset
8578+ // ID of the first leaf found. If there are no leaves found after syncing, an
8579+ // error is returned.
8580+ func (r * rpcServer ) syncAssetGroup (ctx context.Context ,
8581+ groupKey * btcec.PublicKey ) (asset.ID , error ) {
8582+
8583+ // We first check if we already know any asset leaves associated with
8584+ // this group key.
8585+ leaf := fn.NewRight [asset.ID ](* groupKey )
8586+ leaves , err := r .cfg .Multiverse .FetchLeaves (
8587+ ctx , []universe.MultiverseLeafDesc {leaf },
8588+ universe .ProofTypeIssuance ,
8589+ )
8590+ if err != nil {
8591+ return asset.ID {}, fmt .Errorf ("error fetching leaves: %w" , err )
8592+ }
8593+
8594+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8595+ len (leaves ), groupKey .SerializeCompressed ())
8596+
8597+ // If there are no leaves, then we need to sync the asset group.
8598+ if len (leaves ) == 0 {
8599+ tapdLog .Debugf ("No leaves found for group key %x, " +
8600+ "syncing asset group" , groupKey .SerializeCompressed ())
8601+ err = r .cfg .AddrBook .SyncAssetGroup (ctx , groupKey )
8602+ if err != nil {
8603+ return asset.ID {}, fmt .Errorf ("error syncing asset " +
8604+ "group: %w" , err )
8605+ }
8606+
8607+ // Now we can try again.
8608+ leaves , err = r .cfg .Multiverse .FetchLeaves (
8609+ ctx , []universe.MultiverseLeafDesc {leaf },
8610+ universe .ProofTypeIssuance ,
8611+ )
8612+ if err != nil {
8613+ return asset.ID {}, fmt .Errorf ("error fetching leaves: " +
8614+ "%w" , err )
8615+ }
8616+
8617+ tapdLog .Tracef ("Found %d leaves for group key %x" ,
8618+ len (leaves ), groupKey .SerializeCompressed ())
8619+
8620+ if len (leaves ) == 0 {
8621+ return asset.ID {}, fmt .Errorf ("no asset leaves found " +
8622+ "for group %x after sync" ,
8623+ groupKey .SerializeCompressed ())
8624+ }
8625+ }
8626+
8627+ // Since we know we have at least one leaf, we can just take leaf ID and
8628+ // query the keys with it. We just need one so we can fetch the actual
8629+ // proof to find out the asset ID.
8630+ leafID := leaves [0 ]
8631+ leafKeys , err := r .cfg .Multiverse .UniverseLeafKeys (
8632+ ctx , universe.UniverseLeafKeysQuery {
8633+ Id : leafID .ID ,
8634+ Limit : 1 ,
8635+ },
8636+ )
8637+ if err != nil {
8638+ return asset.ID {}, fmt .Errorf ("error fetching leaf keys: %w" ,
8639+ err )
8640+ }
8641+
8642+ // We know we have a leaf, so this shouldn't happen.
8643+ if len (leafKeys ) != 1 {
8644+ return asset.ID {}, fmt .Errorf ("expected 1 leaf key, got %d" ,
8645+ len (leafKeys ))
8646+ }
8647+
8648+ proofs , err := r .cfg .Multiverse .FetchProofLeaf (
8649+ ctx , leafID .ID , leafKeys [0 ],
8650+ )
8651+ if err != nil {
8652+ return asset.ID {}, fmt .Errorf ("error fetching proof leaf: %w" ,
8653+ err )
8654+ }
8655+
8656+ // We should have a proof for the asset ID now.
8657+ if len (proofs ) != 1 {
8658+ return asset.ID {}, fmt .Errorf ("expected 1 proof, got %d" ,
8659+ len (proofs ))
8660+ }
8661+
8662+ // We can now extract the asset ID from the proof.
8663+ return proofs [0 ].Leaf .ID (), nil
8664+ }
8665+
85308666// RegisterTransfer informs the daemon about a new inbound transfer that has
85318667// happened. This is used for interactive transfers where no TAP address is
85328668// involved and the recipient is aware of the transfer through an out-of-band
0 commit comments