Skip to content

Commit 8495c51

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 2d9fa05 commit 8495c51

File tree

4 files changed

+220
-61
lines changed

4 files changed

+220
-61
lines changed

rpcserver.go

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

taprpc/tapchannelrpc/tapchannel.pb.go

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

taprpc/tapchannelrpc/tapchannel.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,16 @@ message AddInvoiceResponse {
220220

221221
message AssetPayReq {
222222
// The asset ID that will be used to resolve the invoice's satoshi amount.
223+
// Mutually exclusive to group_key.
223224
bytes asset_id = 1;
224225

225226
// The normal LN invoice that whose amount will be mapped to units of the
226227
// asset ID.
227228
string pay_req_string = 2;
229+
230+
// The group key that will be used to resolve the invoice's satoshi amount.
231+
// Mutually exclusive to asset_id.
232+
bytes group_key = 3;
228233
}
229234

230235
message AssetPayReqResponse {

taprpc/tapchannelrpc/tapchannel.swagger.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1539,11 +1539,16 @@
15391539
"asset_id": {
15401540
"type": "string",
15411541
"format": "byte",
1542-
"description": "The asset ID that will be used to resolve the invoice's satoshi amount."
1542+
"description": "The asset ID that will be used to resolve the invoice's satoshi amount.\nMutually exclusive to group_key."
15431543
},
15441544
"pay_req_string": {
15451545
"type": "string",
15461546
"description": "The normal LN invoice that whose amount will be mapped to units of the\nasset ID."
1547+
},
1548+
"group_key": {
1549+
"type": "string",
1550+
"format": "byte",
1551+
"description": "The group key that will be used to resolve the invoice's satoshi amount.\nMutually exclusive to asset_id."
15471552
}
15481553
}
15491554
},

0 commit comments

Comments
 (0)