-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support group keys on SendPayment
& AddInvoice
#1423
Conversation
Pull Request Test Coverage Report for Build 14266166576Details
💛 - Coveralls |
The current LiT itests are going to pass, as they will keep using a single asset ID for payments and invoices. This LiT PR uses the new group key arguments for creating/paying invoices. |
7c660bd
to
6cf7190
Compare
9c9de09
to
b53b3a2
Compare
Rebased on |
b53b3a2
to
f13f4a0
Compare
f13f4a0
to
27faee9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a first pass.
Concerning the discussion around group key in the asset sum entry.
I failed to consider the group key send case when designing the p2p messages.
Once an HTLC is locked in, the allocation
code makes sure each HTLC has a specific, clearly defined set of asset pieces, all with IDs defined.
But as long as the HTLC is just transmitted (e.g. on the SendPayment
RPC, in ProduceHtlcExtraData
function of the aux traffic shaper, or even in the UpdateAddHtlc
wire message), we don't actually know the ID of the piece(s) that are going to be used for the HTLC.
So in those scenarios we only really check two things in the policies:
- The sum of the assets being transferred in the HTLC message
- Whether the assets match the policy
For the second check, I think it makes sense to indirectly put the group key in there. Whether we hash it first (as we do for the universe key) or whether we just use the x-coordinate of the group key doesn't matter too much IMO. But I think we need to be very clear wherever we do that and definitely add a comment on why we're doing that (feel free to copy my explanation above).
27faee9
to
99e85a7
Compare
Changed base to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice!
Super super close, just a couple of nits and improvement requests.
@@ -949,6 +951,10 @@ func (m *Manager) getAssetGroupKey(ctx context.Context, | |||
// Perform the DB query. | |||
group, err := m.cfg.GroupLookup.QueryAssetGroup(ctx, id) | |||
if err != nil { | |||
if errors.Is(err, address.ErrAssetGroupUnknown) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need this commit, given that we now check whether a given asset ID is a group key before querying the DB?
It just feels weird to return fn.None[btcec.PublicKey]()
. To me that means: the given asset ID is _not_ part of a group
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this is still valid for cases where AssetMatchesSpecifier
is called with an asset ID and a specifier which includes a group key
An actual group lookup would have to be performed for that set of inputs. If that asset doesn't belong to a group we would run into the above line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An actual group lookup would have to be performed for that set of inputs. If that asset doesn't belong to a group we would run into the above line.
Seems related to the first comment I left in this round of review. Looks like we would do a DB look up to make sure that we didn't accidentally interpret an asset ID as a group key.
return true, nil | ||
} | ||
|
||
// Now let's make an actual query to find this assetID's group, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, given that we don't call getAssetGroupKey
with a "fake" asset ID anymore, I think we can potentially remove the previous commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rpcserver.go
Outdated
return err | ||
} | ||
|
||
switch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add this switch
and the error check above as a new function asset.NewExclusiveSpecifier()
?
To make sure we use the same semantics everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above error comes from the rpc marshaller, we could make such a func for the switch itself (i.e make a specifier that only contains one of assetID/groupKey) but I'm not sure if there's a clear way of saying which one to keep? Would we default to keeping the groupKey over the assetID or vice versa?
@@ -7684,18 +7684,31 @@ func checkOverpayment(quote *rfqrpc.PeerAcceptedSellQuote, | |||
func (r *rpcServer) AddInvoice(ctx context.Context, | |||
req *tchrpc.AddInvoiceRequest) (*tchrpc.AddInvoiceResponse, error) { | |||
|
|||
if len(req.AssetId) > 0 && len(req.GroupKey) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-use potential, see my previous comment for NewExclusiveSpecifier
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, since there's a new occurrence here it makes sense to add smth for this
I guess can add a simple NewExclusiveSpecifier
that accepts both assetID/groupKey and prioritizes groupKey over assetID, given that it's more generic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check can be moved as well.
99e85a7
to
e2e8e47
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM ❄️
I dig the change to move to using the x-coord instead of the hash.
Changes look good pending the final comments from the other reviewer.
Will check out the corresponding itest changes now.
groupKey := c.AssetSpecifier.UnwrapGroupKeyToPtr() | ||
groupKeyX := schnorr.SerializePubKey(groupKey) | ||
|
||
assetID = asset.ID(groupKeyX) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is nice compared to the hash version, as this way we aren't actually losing any information.
However, it's possible that a normal asset ID, can actually be interpreted as a valid x coordinate. To avoid this confusion, when we decode on the other side, and conclude it might be a pubkey, we should check our local db to make sure it's actually a group key that we've validated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the receiver's part that you're describing
Lines 984 to 990 in e2e8e47
// Let's directly check if the ID is equal to the hash of the | |
// group key. This is used by the sender to indicate that any | |
// asset that belongs to this group may be used. | |
groupKeyX := schnorr.SerializePubKey(specifierGK) | |
if asset.ID(groupKeyX) == id { | |
return true, nil | |
} |
a receiver is always checking this "assetID" against a specifier. If that specifier contains a groupkey we'll immediately check if the X coordinates match before doing any DB lookups
we should check our local db to make sure it's actually a group key that we've validated.
That's an interesting point. Do we really care if the group key is validated?
If that group key made it into a channel or RFQ quote, then it can't be a fake or un-validated group key. If you have DoS vectors in mind, I haven't digged a lot into that.
@@ -949,6 +951,10 @@ func (m *Manager) getAssetGroupKey(ctx context.Context, | |||
// Perform the DB query. | |||
group, err := m.cfg.GroupLookup.QueryAssetGroup(ctx, id) | |||
if err != nil { | |||
if errors.Is(err, address.ErrAssetGroupUnknown) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An actual group lookup would have to be performed for that set of inputs. If that asset doesn't belong to a group we would run into the above line.
Seems related to the first comment I left in this round of review. Looks like we would do a DB look up to make sure that we didn't accidentally interpret an asset ID as a group key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last simplification request, otherwise LGTM 🎉
Pending @Roasbeef's other comments.
@@ -7684,18 +7684,31 @@ func checkOverpayment(quote *rfqrpc.PeerAcceptedSellQuote, | |||
func (r *rpcServer) AddInvoice(ctx context.Context, | |||
req *tchrpc.AddInvoiceRequest) (*tchrpc.AddInvoiceResponse, error) { | |||
|
|||
if len(req.AssetId) > 0 && len(req.GroupKey) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check can be moved as well.
We add a new interface to the HTLC SumAssetBalance method, which helps check the identifier of the asset against a specifier. This allows for checking asset inclusion in a group, which is a bit involved and not the responsibility of the HTLC model.
We extend the interface of the rfq Policy in order to allow the specifier checker to be involved. This extends certain checks, and allows us to use asset specifiers that only have a group key.
When intercepting an HTLC which is incoming from a btc channel and outgoing towards an asset channel, we need to create an HTLC record which reflects the asset related balance change. What really matter in these records is the total asset amount, but we also set the assetID field to be either equal to the specific asset we're sending, or the group of assets. This field does not dictate what the actual assets being sent are going to be, that will be decided later in the asset allocation process.
This allows us to get rid of a circular dependency issue that would occur in a follow up commit, where we import the adress package in the rfq package. Co-authored-by: Oliver Gugger <[email protected]>
We add the specifier checker interface to the AuxInvoiceManager too, as it is needed to validate incoming HTLCs which may use asset IDs that belong to a group, while the RFQ is based on a group key.
Adds some coverage to the invoice manager unit tests, which involve an RFQ quote over a group key, plus an HTLC with multiple asset balances, which may belong or not to the group.
We may be performing a group lookup on an asset that doesn't belong to a group. Instead of returning the error that originates from the ErrNoRows of sql we instead return a nil result, signalling that no group was found and no error occurred.
In some cases the sender of an asset HTLC may just encode the x part of the group key as the assetID of the balance in the custom records. This is done as a way to signal that any asset ID that belongs to this group may be picked to carry out the payment. This commit makes the specifier matcher aware of that case.
As mentioned in the previous commit, we occasionally want to just encode the hash of the group key as the asset ID of the balance that is encoded in the custom record. We make the ProduceHtlcExtraData hook aware of that case, which is triggered when the quote is made upon a group key and not a specific asset ID.
We add this new specifier creator which may accept both an asset id and group key. This is needed for callers who may have both fields available and simply want to derive an asset specifier from them. By default a specifier over the group key is prioritized as that's considered more generic over the specific asset id. This is used in a later commit.
We have taken care of the groupkey RFQ negotiation in previous commits. All we need now to support sending a payment over a group of assets, is to propagate the user specifier groupkey to the corresponding fields.
In this commit we take the user defined group key and allow the asset specifier to be created over it. All the calls that accept it as an argument are already groupkey aware.
e2e8e47
to
e9e1de8
Compare
Description
This PR takes care of the rest of the work required to create and pay invoices by using group keys.
group_key
onSendPayment
means that only channels that contain assets which belong to that group are going to be considered for sending this payment.group_key
onAddInvoice
means that only channels that contain assets which belong to that group may be considered for generating an RFQ quote and encoding it in the invoice.LiT PR for itest