Skip to content
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

Trampoline payments #1034

Merged
merged 8 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions libs/sdk-bindings/src/breez_sdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ dictionary LnUrlPayErrorData {
dictionary LnUrlPayRequest {
LnUrlPayRequestData data;
u64 amount_msat;
boolean use_trampoline;
string? comment = null;
string? payment_label = null;
boolean? validate_success_action_url = null;
Expand Down Expand Up @@ -766,6 +767,7 @@ dictionary RedeemOnchainFundsResponse {

dictionary SendPaymentRequest {
string bolt11;
boolean use_trampoline;
u64? amount_msat = null;
string? label = null;
};
Expand Down
4 changes: 4 additions & 0 deletions libs/sdk-common/src/lnurl/specs/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub mod model {
pub data: LnUrlPayRequestData,
/// The amount in millisatoshis for this payment
pub amount_msat: u64,
/// Trampoline payments outsource pathfinding to the LSP. Trampoline payments can improve
/// payment performance, but are generally more expensive in terms of fees and they
/// compromise on privacy.
pub use_trampoline: bool,
/// An optional comment for this payment
pub comment: Option<String>,
/// The external label or identifier of the [Payment]
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ hex = { workspace = true }
# The switch to 0.2 will happen with https://github.com/breez/breez-sdk/pull/724
gl-client = { git = "https://github.com/Blockstream/greenlight.git", features = [
"permissive",
], rev = "b9d1ecb9ea7325b041d08ddc171486fdad646a63" }
], rev = "43f02207182e28a4d62e7aa1c79cd098f9b300ba" }
zbase32 = "0.1.2"
base64 = { workspace = true }
chrono = "0.4"
Expand Down
1 change: 1 addition & 0 deletions libs/sdk-core/src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ pub struct _RouteHintHop {
pub struct _LnUrlPayRequest {
pub data: LnUrlPayRequestData,
pub amount_msat: u64,
pub use_trampoline: bool,
pub comment: Option<String>,
pub payment_label: Option<String>,
pub validate_success_action_url: Option<bool>,
Expand Down
192 changes: 137 additions & 55 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,33 +289,105 @@ impl BreezServices {
}
};

match self
if self
.persister
.get_completed_payment_by_hash(&parsed_invoice.payment_hash)?
.is_some()
{
Some(_) => Err(SendPaymentError::AlreadyPaid),
return Err(SendPaymentError::AlreadyPaid);
}

// If there is an lsp, the invoice route hint does not contain the
// lsp in the hint, and trampoline payments are requested, attempt a
// trampoline payment.
let maybe_trampoline_id = self.get_trampoline_id(&req, &parsed_invoice)?;

self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;

// If trampoline is an option, try trampoline first.
let trampoline_result = if let Some(trampoline_id) = maybe_trampoline_id {
debug!("attempting trampoline payment");
match self
.node_api
.send_trampoline_payment(
parsed_invoice.bolt11.clone(),
amount_msat,
req.label.clone(),
trampoline_id,
)
.await
{
Ok(res) => Some(res),
Err(e) => {
warn!("trampoline payment failed: {:?}", e);
None
}
}
} else {
debug!("not attempting trampoline payment");
None
};

// If trampoline failed or didn't happen, fall back to regular payment.
let payment_res = match trampoline_result {
Some(res) => Ok(res),
None => {
self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?;
let payment_res = self
.node_api
debug!("attempting normal payment");
self.node_api
.send_payment(
parsed_invoice.bolt11.clone(),
req.amount_msat,
req.label.clone(),
)
.map_err(Into::into)
.await;
let payment = self
.on_payment_completed(
parsed_invoice.payee_pubkey.clone(),
Some(parsed_invoice),
req.label,
payment_res,
)
.await?;
Ok(SendPaymentResponse { payment })
.await
}
};

let payment = self
.on_payment_completed(
parsed_invoice.payee_pubkey.clone(),
Some(parsed_invoice),
req.label,
payment_res,
)
.await?;
Ok(SendPaymentResponse { payment })
}

fn get_trampoline_id(
&self,
req: &SendPaymentRequest,
invoice: &LNInvoice,
) -> Result<Option<Vec<u8>>, SendPaymentError> {
// If trampoline is turned off, return immediately
if !req.use_trampoline {
return Ok(None);
}

// Get the persisted LSP id. If no LSP, return early.
let lsp_pubkey = match self.persister.get_lsp_pubkey()? {
Some(lsp_pubkey) => lsp_pubkey,
None => return Ok(None),
};

// If the LSP is in the routing hint, don't use trampoline, but rather
// pay directly to the destination.
if invoice.routing_hints.iter().any(|hint| {
hint.hops
.last()
.map(|hop| hop.src_node_id == lsp_pubkey)
.unwrap_or(false)
}) {
return Ok(None);
}

// If ended up here, this payment will attempt trampoline.
Ok(Some(hex::decode(lsp_pubkey).map_err(|_| {
SendPaymentError::Generic {
err: "failed to decode lsp pubkey".to_string(),
}
})?))
}

/// Pay directly to a node id using keysend
Expand Down Expand Up @@ -365,6 +437,7 @@ impl BreezServices {
let pay_req = SendPaymentRequest {
bolt11: cb.pr.clone(),
amount_msat: None,
use_trampoline: req.use_trampoline,
label: req.payment_label,
};
let invoice = parse_invoice(cb.pr.as_str())?;
Expand Down Expand Up @@ -673,19 +746,21 @@ impl BreezServices {

/// Select the LSP to be used and provide inbound liquidity
pub async fn connect_lsp(&self, lsp_id: String) -> SdkResult<()> {
match self.list_lsps().await?.iter().any(|lsp| lsp.id == lsp_id) {
true => {
self.persister.set_lsp_id(lsp_id)?;
self.sync().await?;
if let Some(webhook_url) = self.persister.get_webhook_url()? {
self.register_payment_notifications(webhook_url).await?
}
Ok(())
let lsp_pubkey = match self.list_lsps().await?.iter().find(|lsp| lsp.id == lsp_id) {
Some(lsp) => lsp.pubkey.clone(),
None => {
return Err(SdkError::Generic {
err: format!("Unknown LSP: {lsp_id}"),
})
}
false => Err(SdkError::Generic {
err: format!("Unknown LSP: {lsp_id}"),
}),
};

self.persister.set_lsp(lsp_id, Some(lsp_pubkey))?;
self.sync().await?;
if let Some(webhook_url) = self.persister.get_webhook_url()? {
self.register_payment_notifications(webhook_url).await?
}
Ok(())
}

/// Get the current LSP's ID
Expand Down Expand Up @@ -1171,32 +1246,39 @@ impl BreezServices {
/// If not or no LSP is selected, it selects the first LSP in [`list_lsps`].
async fn connect_lsp_peer(&self, node_pubkey: String) -> SdkResult<()> {
let lsps = self.lsp_api.list_lsps(node_pubkey).await?;
if let Some(lsp) = self
let lsp = match self
.persister
.get_lsp_id()?
.and_then(|lsp_id| lsps.clone().into_iter().find(|lsp| lsp.id == lsp_id))
.or_else(|| lsps.first().cloned())
.and_then(|lsp_id| lsps.iter().find(|lsp| lsp.id == lsp_id))
.or_else(|| lsps.first())
{
self.persister.set_lsp_id(lsp.id)?;
if let Ok(node_state) = self.node_info() {
let node_id = lsp.pubkey;
let address = lsp.host;
let lsp_connected = node_state
.connected_peers
.iter()
.any(|e| e == node_id.as_str());
if !lsp_connected {
debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
self.node_api
.connect_peer(node_id.clone(), address.clone())
.await
.map_err(|e| SdkError::ServiceConnectivity {
err: format!("(LSP: {node_id}) Failed to connect: {e}"),
})?;
}
debug!("connected to lsp {node_id}@{address}");
}
Some(lsp) => lsp.clone(),
None => return Ok(()),
};

self.persister.set_lsp(lsp.id, Some(lsp.pubkey.clone()))?;
let node_state = match self.node_info() {
Ok(node_state) => node_state,
Err(_) => return Ok(()),
};

let node_id = lsp.pubkey;
let address = lsp.host;
let lsp_connected = node_state
.connected_peers
.iter()
.any(|e| e == node_id.as_str());
if !lsp_connected {
debug!("connecting to lsp {}@{}", node_id.clone(), address.clone());
self.node_api
.connect_peer(node_id.clone(), address.clone())
.await
.map_err(|e| SdkError::ServiceConnectivity {
err: format!("(LSP: {node_id}) Failed to connect: {e}"),
})?;
debug!("connected to lsp {node_id}@{address}");
}

Ok(())
}

Expand Down Expand Up @@ -1401,15 +1483,15 @@ impl BreezServices {
.await;

// Sync node state
let sync_breez_services = self.clone();
match sync_breez_services.persister.get_node_state()? {
match self.persister.get_node_state()? {
Some(node) => {
info!("Starting existing node {}", node.id)
info!("Starting existing node {}", node.id);
self.connect_lsp_peer(node.id).await?;
}
None => {
// In case it is a first run we sync in foreground to get the node state.
info!("First run, syncing in foreground");
sync_breez_services.sync().await?;
self.sync().await?;
info!("First run, finished running syncing in foreground");
}
}
Expand Down Expand Up @@ -2325,7 +2407,7 @@ impl BreezServicesBuilder {

let current_lsp_id = persister.get_lsp_id()?;
if current_lsp_id.is_none() && self.config.default_lsp_id.is_some() {
persister.set_lsp_id(self.config.default_lsp_id.clone().unwrap())?;
persister.set_lsp(self.config.default_lsp_id.clone().unwrap(), None)?;
}

let payment_receiver = Arc::new(PaymentReceiver {
Expand Down Expand Up @@ -3149,7 +3231,7 @@ pub(crate) mod tests {
let node_api = Arc::new(MockNodeAPI::new(dummy_node_state.clone()));

let breez_server = Arc::new(MockBreezServer {});
persister.set_lsp_id(breez_server.lsp_id()).unwrap();
persister.set_lsp(breez_server.lsp_id(), None).unwrap();
persister.set_node_state(&dummy_node_state).unwrap();

let receiver: Arc<dyn Receiver> = Arc::new(PaymentReceiver {
Expand Down Expand Up @@ -3256,7 +3338,7 @@ pub(crate) mod tests {
let persister = Arc::new(create_test_persister(test_config.clone()));
persister.init()?;
persister.insert_or_update_payments(&known_payments, false)?;
persister.set_lsp_id(MockBreezServer {}.lsp_id())?;
persister.set_lsp(MockBreezServer {}.lsp_id(), None)?;

let mut builder = BreezServicesBuilder::new(test_config.clone());
let breez_services = builder
Expand Down
6 changes: 6 additions & 0 deletions libs/sdk-core/src/bridge_generated.io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,7 @@ impl Wire2Api<LnUrlPayRequest> for wire_LnUrlPayRequest {
LnUrlPayRequest {
data: self.data.wire2api(),
amount_msat: self.amount_msat.wire2api(),
use_trampoline: self.use_trampoline.wire2api(),
comment: self.comment.wire2api(),
payment_label: self.payment_label.wire2api(),
validate_success_action_url: self.validate_success_action_url.wire2api(),
Expand Down Expand Up @@ -1082,6 +1083,7 @@ impl Wire2Api<SendPaymentRequest> for wire_SendPaymentRequest {
fn wire2api(self) -> SendPaymentRequest {
SendPaymentRequest {
bolt11: self.bolt11.wire2api(),
use_trampoline: self.use_trampoline.wire2api(),
amount_msat: self.amount_msat.wire2api(),
label: self.label.wire2api(),
}
Expand Down Expand Up @@ -1238,6 +1240,7 @@ pub struct wire_LnUrlAuthRequestData {
pub struct wire_LnUrlPayRequest {
data: wire_LnUrlPayRequestData,
amount_msat: u64,
use_trampoline: bool,
comment: *mut wire_uint_8_list,
payment_label: *mut wire_uint_8_list,
validate_success_action_url: *mut bool,
Expand Down Expand Up @@ -1402,6 +1405,7 @@ pub struct wire_SendOnchainRequest {
#[derive(Clone)]
pub struct wire_SendPaymentRequest {
bolt11: *mut wire_uint_8_list,
use_trampoline: bool,
amount_msat: *mut u64,
label: *mut wire_uint_8_list,
}
Expand Down Expand Up @@ -1647,6 +1651,7 @@ impl NewWithNullPtr for wire_LnUrlPayRequest {
Self {
data: Default::default(),
amount_msat: Default::default(),
use_trampoline: Default::default(),
comment: core::ptr::null_mut(),
payment_label: core::ptr::null_mut(),
validate_success_action_url: core::ptr::null_mut(),
Expand Down Expand Up @@ -2011,6 +2016,7 @@ impl NewWithNullPtr for wire_SendPaymentRequest {
fn new_with_null_ptr() -> Self {
Self {
bolt11: core::ptr::null_mut(),
use_trampoline: Default::default(),
amount_msat: core::ptr::null_mut(),
label: core::ptr::null_mut(),
}
Expand Down
Loading
Loading