-
Notifications
You must be signed in to change notification settings - Fork 110
Support passing preimage for spontaneous payments #549
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
base: main
Are you sure you want to change the base?
Support passing preimage for spontaneous payments #549
Conversation
👋 I see @TheBlueMatt was un-assigned. |
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.
Just shared a few thoughts regarding the overall design of this API and its extensibility.
src/payment/spontaneous.rs
Outdated
/// Send a spontaneous with custom preimage | ||
pub fn send_with_preimage( | ||
&self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option<SendingParameters>, | ||
preimage: PaymentPreimage, |
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 wondering if we should expand the purpose of SendingParameters
a bit to allow passing of any custom parameters required for sending payments (not just routing and pathfinding parameters).
This approach would allow us to include preimage as an Option within SendingParameters
, providing a single, coherent place for all payment-sending customizations.
A significant benefit of this change would be the ability to deprecate or drop specialized functions like send_with_preimage
and send_with_custom_tlvs
(in another PR), reducing overall boilerplate for users.
What do you think about this?
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 initially hesitated about mixing routing parameters with payment content. Your point about expanding SendingParameters
to handle all payment customization makes sense for the long-term API design, and the benefit outweigh the conceptual mixing.
I'll refactor to move preimage into SendingParameters
as an Option. I can drop the send_with_preimage
and send_with_preimage_and_custom_tlvs
, then send_with_custom_tlvs
can be dropped in another PR.
Thank you.
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 wondering if we should expand the purpose of SendingParameters a bit to allow passing of any custom parameters required for sending payments (not just routing and pathfinding parameters).
Ah, no, I don't think we should do that. Note that after LDK 0.2 release (cf. #462) we'll replace SendingParameters
with LDK's RouteParametersConfig
everywhere, so really shouldn't make any changes prior to it. Also, while we might be fine to expose the preimage for keysend
payments, I don't think we'd like to allow users to override it for BOLT11/BOLT12 generally.
I'll refactor to move preimage into SendingParameters as an Option.
Please revert this. I think we can either have separate send
APIs as you previously suggested, or add the preimage as an optional field to the existing ones. Will take a look after the revert, but your previous approach might be fine.
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, no, I don't think we should do that. Note that after LDK 0.2 release (cf. #462) we'll replace
SendingParameters
with LDK'sRouteParametersConfig
everywhere, so really shouldn't make any changes prior to it. Also, while we might be fine to expose the preimage forkeysend
payments, I don't think we'd like to allow users to override it for BOLT11/BOLT12 generally.
Thanks for the clarification!
src/payment/spontaneous.rs
Outdated
self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs), None) | ||
} | ||
|
||
/// Send a spontaneous with custom preimage |
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 you missed the word "payment" here (after spontaneous) - that's if we'd still be adding this method.
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, I missed that. Thank you. I am removing the method.
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.
If we are going to use sending_parameters
to pass in the custom preimage, I think we should allow the user to set it in config or pass it directly.
src/payment/spontaneous.rs
Outdated
@@ -77,7 +77,10 @@ impl SpontaneousPayment { | |||
return Err(Error::NotRunning); | |||
} | |||
|
|||
let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes()); | |||
let payment_preimage = sending_parameters |
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.
If we are doing this, I think we'll need to use override_params
instead as that allows send_inner
to select sending_parameters
set in the config (if not passed into the send
method directly).
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.
No, I don't think we should do this, see above.
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.
As mentioned above I don't think adding this field to SendingParameters
is the way to go here (note it would also allowing setting global default preimage
if users would override the field in Config
, which is actually dangerous as preimages can never be reused). Please revert to you previous draft, will review then.
src/payment/spontaneous.rs
Outdated
@@ -77,7 +77,10 @@ impl SpontaneousPayment { | |||
return Err(Error::NotRunning); | |||
} | |||
|
|||
let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes()); | |||
let payment_preimage = sending_parameters |
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.
No, I don't think we should do this, see above.
Alright. Will do that. Thank you. |
Hello @tnull, I have reverted to the previous draft. This is ready. |
Nice work on this PR! The custom preimage functionality is implemented cleanly, and the test coverage looks good. One small enhancement suggestion: we could add verification that |
Thanks for the suggestion! I've added the end-to-end verification as requested. It also ensure the custom preimage flows correctly through the entire payment process from sender to receiver. |
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.
Basically LGTM, but now that we actually take a PaymentPreimage
from user input we should probably avoid the UniffiCustomTypeConverter
for it, and rather expose a 'proper' newtype wrapper for the bindings. See #542 for inspiration on this, for example.
@@ -213,6 +213,10 @@ interface SpontaneousPayment { | |||
PaymentId send_with_custom_tlvs(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters, sequence<CustomTlvRecord> custom_tlvs); | |||
[Throws=NodeError] | |||
void send_probes(u64 amount_msat, PublicKey node_id); | |||
[Throws=NodeError] |
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.
nit: Let's move these new entries to the correct location w.r.t. API, i.e. below send_with_custom_tlvs
.
src/payment/spontaneous.rs
Outdated
/// Send a spontaneous payment with custom preimage | ||
pub fn send_with_preimage( | ||
&self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option<SendingParameters>, | ||
preimage: PaymentPreimage, |
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.
If we add this as a new required parameter, let's move it before the optional parameters, i.e., Option<SendingParameters>
(here and below).
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.
Alright, will fix it. Thank you.
Thank you for the feedback, I will fix it |
2ffdc53
to
5194ccc
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.
Generally looks good to me - would appreciate some clarity on why we have maybe_wrap
and maybe_wrap_arc
.
pub fn maybe_wrap_arc<T>(ldk_type: impl Into<T>) -> std::sync::Arc<T> { | ||
std::sync::Arc::new(ldk_type.into()) | ||
} | ||
|
||
#[cfg(feature = "uniffi")] | ||
pub fn maybe_wrap<T>(ldk_type: impl Into<T>) -> T { | ||
ldk_type.into() | ||
} | ||
|
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 probably missing something here but I'm wondering why we need to "split" maybe_wrap
into these 2 functions.
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.
maybe_wrap
and maybe_wrap_arc
is to handle different type wrapping requirements depending on whether the uniffi feature is enabled. maybe_wrap_arc
wraps the input type in a std::sync::Arc<T>
. maybe_wrap
performs a simple into() conversion without additional wrapping, used in cases where the type doesn’t need to be wrapped in an Arc.
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.
From my understanding, I thought maybe_wrap
(as it was before) already handled wrapping depending on whether the uniFFI feature flag is enabled - if enabled wrap in an Arc, if not return same value as passed in?
I noticed maybe_wrap
(this new version) is used to conditionally wrap the preimage - does this mean the preimage never needs to be wrapped in an Arc even when the uniFFI feature flag is enabled?
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.
In #542, the wrapper types introduced were for structs with a bit more complexity and state than PaymentPreimage
. From my understanding, it makes sense to allocate them on the heap because they are also expensive to clone. PaymentPreimage
is simpler, wraps 32 bytes, and should be cheaper to clone and doesn't need to be Arc
'ed.
Also, when compiled with uniffi
, there was a bit of conflict about converting maybe_wrap
into a macro so it can handle the different cases (i.e. types needing heap allocation vs types that do not), however I think @tnull may not like the idea for reasons stated in #526.
This change is easily revertible though if Arc
ing is preferred.
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.
In #542, the wrapper types introduced were for structs with a bit more complexity and state than
PaymentPreimage
. From my understanding, it makes sense to allocate them on the heap because they are also expensive to clone.PaymentPreimage
is simpler, wraps 32 bytes, and should be cheaper to clone and doesn't need to beArc
'ed.
Thanks for the clarification! This makes sense.
#[cfg(not(feature = "uniffi"))] | ||
pub fn maybe_wrap_arc<T>(value: T) -> T { | ||
value | ||
} | ||
|
||
#[cfg(not(feature = "uniffi"))] | ||
pub fn maybe_wrap<T>(value: T) -> T { | ||
value | ||
} |
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.
These 2 fns are basically the same, please is there a reason why we need both of them?
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.
both maybe_wrap_arc
and maybe_wrap
are identical, simply returning the input value. The reason for keeping them as separate functions is to maintain a consistent API that aligns with their distinct behaviors when the uniffi
feature is enabled
pub fn maybe_wrap_arc<T>(ldk_type: impl Into<T>) -> std::sync::Arc<T> { | ||
std::sync::Arc::new(ldk_type.into()) | ||
} | ||
|
||
#[cfg(feature = "uniffi")] | ||
pub fn maybe_wrap<T>(ldk_type: impl Into<T>) -> T { | ||
ldk_type.into() | ||
} | ||
|
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.
In #542, the wrapper types introduced were for structs with a bit more complexity and state than PaymentPreimage
. From my understanding, it makes sense to allocate them on the heap because they are also expensive to clone. PaymentPreimage
is simpler, wraps 32 bytes, and should be cheaper to clone and doesn't need to be Arc
'ed.
Also, when compiled with uniffi
, there was a bit of conflict about converting maybe_wrap
into a macro so it can handle the different cases (i.e. types needing heap allocation vs types that do not), however I think @tnull may not like the idea for reasons stated in #526.
This change is easily revertible though if Arc
ing is preferred.
@@ -446,6 +450,10 @@ dictionary PaymentDetails { | |||
u64 latest_update_timestamp; | |||
}; | |||
|
|||
dictionary PaymentPreimage { | |||
bytes inner; |
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 wonder about what would be the most appropriate description for [u8;N]
: bytes
vs sequence<u8>
?
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.
bytes
represents fixed-size binary data like preimage better
@aagbotemi |
- integration test for passing preimage for spontaneous payment - bindings for passing preimage for spontaneous payment - refactor: move preimage from method parameter to SendingParameters - revert to parameter-based approach - verify node_b recieved payment for end-to-end verification - expose a proper newtype wrapper for the PaymentPreimage bindings - trigger CI
68a141d
to
c33d291
Compare
This PR adds support for specifying custom preimages when sending spontaneous (keysend) payments.
Solution
send_with_preimage()
methodsend_with_preimage_and_custom_tlvs()
method for advanced use casessend_inner()
to accept optional preimage parameter and generate random preimage only when custom preimage is not provided.An integration test is added to verify logic, and the new methods are added to the bindings.
Fixes #535