Skip to content

Commit 05f5374

Browse files
committed
taprpc+rpcserver: add group key to DecodeAssetPayReq
Since we support group keys in all other payment related commands, we now add the same argument for the invoice decoding RPC.
1 parent 39ec043 commit 05f5374

File tree

4 files changed

+238
-70
lines changed

4 files changed

+238
-70
lines changed

rpcserver.go

Lines changed: 156 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8491,29 +8491,73 @@ func (r *rpcServer) assetInvoiceAmt(ctx context.Context,
84918491
func (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

taprpc/tapchannelrpc/tapchannel.pb.go

Lines changed: 67 additions & 52 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)