@@ -8584,29 +8584,73 @@ func (r *rpcServer) assetInvoiceAmt(ctx context.Context,
8584
8584
func (r * rpcServer ) DecodeAssetPayReq (ctx context.Context ,
8585
8585
payReq * tchrpc.AssetPayReq ) (* tchrpc.AssetPayReqResponse , error ) {
8586
8586
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
+
8587
8591
if r .cfg .PriceOracle == nil {
8588
8592
return nil , fmt .Errorf ("price oracle is not set" )
8589
8593
}
8590
8594
8591
8595
// First, we'll perform some basic input validation.
8596
+ var assetID asset.ID
8592
8597
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" )
8595
8601
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 :
8597
8606
return nil , fmt .Errorf ("asset ID must be 32 bytes, " +
8598
8607
"was %d" , len (payReq .AssetId ))
8599
8608
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
+
8600
8615
case len (payReq .PayReqString ) == 0 :
8601
8616
return nil , fmt .Errorf ("payment request must be specified" )
8602
8617
}
8603
8618
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
+ }
8608
8650
8609
- copy (assetID [:], payReq .AssetId )
8651
+ tapdLog .Debugf ("Resolved asset ID %v for group key %x" ,
8652
+ assetID .String (), groupKey .SerializeCompressed ())
8653
+ }
8610
8654
8611
8655
// With the inputs validated, we'll first call out to lnd to decode the
8612
8656
// payment request.
@@ -8618,7 +8662,9 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
8618
8662
return nil , fmt .Errorf ("unable to fetch channel: %w" , err )
8619
8663
}
8620
8664
8621
- resp .PayReq = payReqInfo
8665
+ resp := tchrpc.AssetPayReqResponse {
8666
+ PayReq : payReqInfo ,
8667
+ }
8622
8668
8623
8669
// Next, we'll fetch the information for this asset ID through the addr
8624
8670
// book. This'll automatically fetch the asset if needed.
@@ -8628,12 +8674,17 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
8628
8674
"asset_id=%x: %w" , assetID [:], err )
8629
8675
}
8630
8676
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
+ }
8637
8688
}
8638
8689
8639
8690
// 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,
8693
8744
return & resp , nil
8694
8745
}
8695
8746
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
+
8696
8838
// RegisterTransfer informs the daemon about a new inbound transfer that has
8697
8839
// happened. This is used for interactive transfers where no TAP address is
8698
8840
// involved and the recipient is aware of the transfer through an out-of-band
0 commit comments