-
Notifications
You must be signed in to change notification settings - Fork 131
Add Multi-RFQ Send #1613
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
Add Multi-RFQ Send #1613
Conversation
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.
Looks pretty good! Have a couple of questions and suggestions, nothing major though.
rfqmsg/records.go
Outdated
if err != nil { | ||
return err | ||
} | ||
list := make([]ID, num) |
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.
We allocate memory from a number we received over the wire. Need to check and error out the length before to make sure we limit the number of bytes we allocate.
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.
Good point, so far the total length of available RFQ IDs is open ended.
We should introduce a static limit (which would also limit the max number of quotes we acquire) and enforce it everywhere.
2bb2369
to
df8ce52
Compare
Pull Request Test Coverage Report for Build 16603360913Details
💛 - Coveralls |
Please update taproot-assets/taprpc/tapchannelrpc/tapchannel.proto Lines 125 to 130 in df8ce52
group_key in addition to asset_id in there.
|
What is this expected to do with an invoice with no amount specified? |
taproot-assets/taprpc/tapchannelrpc/tapchannel.proto Lines 139 to 144 in df8ce52
but with multi-rfq send, I would expect the ability to provide an array. Not sure that you want to fix that in this PR, but maybe it should be a separate issue? I did not make this comment for multi-rfq receive because of #1442 . |
df8ce52
to
aa55b42
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 another pass, getting very close here too.
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'm up-to-date with this PR. I see that some of Oli's comments are unresolved. I don't have anything to add otherwise. I can review quickly once Oli's comments are addressed.
@@ -17,14 +17,17 @@ service TaprootAssetChannels { | |||
rpc FundChannel (FundChannelRequest) returns (FundChannelResponse); | |||
|
|||
/* | |||
Deprecated. |
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.
We should consider adding a comment here to guide the user on the appropriate alternative action.
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'm not sure there even is an alternative action? We're deprecating this because as far as we know nobody is using this anymore
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, was this RPC endpoint only used internally? And since we don’t think it’s useful to users, we’re removing it—is that the idea?
d90193a
to
40bd091
Compare
rpcserver.go
Outdated
|
||
err = stream.Send(&tchrpc.SendPaymentResponse{ | ||
Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{ | ||
AcceptedSellOrder: quote, |
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 streaming multiple quotes to the user is probably not the best idea, since there is no way for the user to know when they're done reading (other than the stream being closed).
So my idea was to add a second field to this RPC message, AcceptedSellOrders
that is a slice of quotes. Then we can deprecate the old field (and update its documentation to say it will only ever contain the first quote and that clients must update to receive the full list).
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 streaming multiple quotes to the user is probably not the best idea, since there is no way for the user to know when they're done reading (other than the stream being closed). So my idea was to add a second field to this RPC message,
AcceptedSellOrders
that is a slice of quotes. Then we can deprecate the old field (and update its documentation to say it will only ever contain the first quote and that clients must update to receive the full list).
yeah, summary was here #1613 (comment) . not sure if @GeorgeTsagk was part of that conversation though??
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 the clients know when to terminate the stream once they receive the payment updates (since the RFQ responses precede the payment update)
Regardless, I went ahead with this change and it's added in the new diff
// We make sure to always write the result to a channel before | ||
// returning. This way the collector can expect a certain number | ||
// of items via the channels. | ||
go func(peer route.Vertex) { |
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, you're right, if you want all errors then the errgroup.Group
isn't useful.
But I think we already have a helper for exactly what you need, that does all the mutex locking and stuff: fn.ParSliceErrCollect()
.
40bd091
to
ad546c9
Compare
Changed the RPC response stream to now return all the quotes in an array. |
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 🎉
// We make sure to always write the result to a channel before | ||
// returning. This way the collector can expect a certain number | ||
// of items via the channels. | ||
go func(peer route.Vertex) { |
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, perhaps something like this?
// ResultFunc is a type def for a function that takes a context (to allow early
// cancellation) and a series of value returning a Result. This is typically
// used a closure to perform concurrent work over a homogeneous slice of
// values.
type ResultFunc[V any, R fn.Result[any]] func(context.Context, V) R
// ParSliceResultCollect can be used to execute a function on each element of a
// slice in parallel. This function is fully blocking and will wait for all
// goroutines to finish (subject to context cancellation/timeout). Any results
// will be collected and returned as a map of slice element index to Result.
// Active goroutines limited with number of CPU.
func ParSliceResultCollect[V any, R fn.Result[any]](ctx context.Context, s []V,
f ResultFunc[V, R]) (map[int]R, error) {
errGroup, ctx := errgroup.WithContext(ctx)
errGroup.SetLimit(runtime.GOMAXPROCS(0))
var instanceResultsMutex sync.Mutex
instanceResults := make(map[int]R, len(s))
for idx := range s {
errGroup.Go(func() error {
result := f(ctx, s[idx])
instanceResultsMutex.Lock()
instanceResults[idx] = result
instanceResultsMutex.Unlock()
// Avoid returning an error here, as that would cancel
// the errGroup and terminate all slice element
// processing instances. Instead, collect the error and
// return it later.
return nil
})
}
// Now we will wait/block for all goroutines to finish.
//
// The goroutines that are executing in parallel should not return an
// error, but the Wait call may return an error if the context is
// canceled or timed out.
err := errGroup.Wait()
if err != nil {
return nil, fmt.Errorf("failed to wait on error group in "+
"ParSliceErrorCollect: %w", err)
}
return instanceResults, nil
}
// We make sure to always write the result to a channel before | ||
// returning. This way the collector can expect a certain number | ||
// of items via the channels. | ||
go func(peer route.Vertex) { |
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.
But can do in a follow-up PR.
return nil | ||
// We now stream the full array of negotiated quotes to the response | ||
// stream. | ||
return stream.Send(&tchrpc.SendPaymentResponse{ |
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.
👍 looks good. This will require a small adjustment in litcli
as well, just as a reminder.
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.
Took a look at this from the beginning once more. Noticed a few things we might want to change. But all trivial. I can approve quickly in a followup review.
We add a new field to rfqmsg.Htlc which expresses the available quotes that may be used to send out this HTLC. This is done to allow LND to store an array of RFQ IDs to use later via the AuxTrafficShaper interface in order to query asset bandwidth and produce the correct asset related records for outgoing HTLCs.
This commit performs a small refactor to the paymentBandwidth helper. Since we now have multiple candidate RFQ IDs, we extract the main logic of calculating the bandwidth into a helper, and call it once for each of the available RFQ IDs.
When LND queries the PaymentBandwidth there's no way to signal back which RFQ ID ended up being used for that bandwidth calculation. We rely on the assumption that one quote is established per peer within the scope of a payment. This way, the AuxTrafficShaper methods spot the quote that it needs to use by matching the peer of the quote with the peer that LND is going to send this HTLC to.
The RPC method now uses all of the introduced features. Instead of acquiring just one quote we now extract that logic into a helper and call it once for each valid peer. We then encode the array of available RFQ IDs into the first hop records and hand it over to LND.
0466670
to
028af11
Compare
Description
Allows payments to use multiple quotes across multiple peers in order to pay an invoice. Previously we had to define a specific peer to negotiate a quote with, with this PR this is no longer required (but still supported) as Tapd will automatically scan our peers and establish quotes with all valid ones for the asset/amount of this payment.
The signature of
ProduceHtlcExtraData
had to be changed, as it's not possible to distinguish which of the quotes in therfqmsg.Htlc
should be used. We now provide the pubkey of the peer this HTLC is being sent to, in order to help Tapd extract the corresponding quote and calculate the correct amount of asset units.Closes #1358
Depends on: lightningnetwork/lnd#9980