Skip to content

Commit

Permalink
multi: Configurable timeout for receiving invoice
Browse files Browse the repository at this point in the history
This commit introduces a parameter in the RPC to allow users to modify
the timeout when receiving an invoice from the offer creator.

Additionally, it adds a configuration parameter for the server to set
up a default value if no parameter is used.

The default timeout value is also decreased from 100 seconds to 20
seconds.
  • Loading branch information
a-mpch authored and mrfelton committed Aug 9, 2024
1 parent 6459123 commit 856eb68
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 29 deletions.
6 changes: 6 additions & 0 deletions config_spec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ name = "skip_version_check"
type = "bool"
default = "false"
doc = "Skip checking the LND version. Otherwise, LNDK checks that the LND version is compatible with LNDK."

[[param]]
name = "response_invoice_timeout"
type = "u32"
optional = true
doc = "Amount of time in seconds that server waits for an offer creator to respond with an invoice. Defaults to 15s."
2 changes: 2 additions & 0 deletions proto/lndkrpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ message PayOfferRequest {
string offer = 1;
optional uint64 amount = 2;
optional string payer_note = 3;
optional uint32 response_invoice_timeout = 4;
}

message PayOfferResponse {
Expand All @@ -22,6 +23,7 @@ message GetInvoiceRequest {
string offer = 1;
optional uint64 amount = 2;
optional string payer_note = 3;
optional uint32 response_invoice_timeout = 4;
}

message DecodeInvoiceRequest {
Expand Down
2 changes: 2 additions & 0 deletions sample-lndk.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ macaroon_path="/home/<USERNAME>/.lnd/data/chain/bitcoin/regtest/admin.macaroon"
# -----END CERTIFICATE-----"
# # This should be a hex-encoded macaroon string.
# macaroon-hex="0201036C6E6402F801030A1034F41C28A3B5190702FEA607DD4045AE1201301A160A0761646472657373120472656164120577726974651A130A04696E666F120472656164120577726974651A170A08696E766F69636573120472656164120577726974651A210A086D616361726F6F6E120867656E6572617465120472656164120577726974651A160A076D657373616765120472656164120577726974651A170A086F6666636861696E120472656164120577726974651A160A076F6E636861696E120472656164120577726974651A140A057065657273120472656164120577726974651A180A067369676E6572120867656E6572617465120472656164000006205CF3E77A97764FB2A68967832608591BE375B36F51A6269AB425AA767BC22B55"

response_invoice_timeout=15
18 changes: 16 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use lndk::lndk_offers::decode;
use lndk::lndkrpc::offers_client::OffersClient;
use lndk::lndkrpc::{GetInvoiceRequest, PayInvoiceRequest, PayOfferRequest};
use lndk::{
Bolt12InvoiceString, DEFAULT_DATA_DIR, DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT,
TLS_CERT_FILENAME,
Bolt12InvoiceString, DEFAULT_DATA_DIR, DEFAULT_RESPONSE_INVOICE_TIMEOUT, DEFAULT_SERVER_HOST,
DEFAULT_SERVER_PORT, TLS_CERT_FILENAME,
};
use std::fs::File;
use std::io::BufReader;
Expand Down Expand Up @@ -93,6 +93,11 @@ enum Commands {
/// A payer-provided note which will be seen by the recipient.
#[arg(required = false)]
payer_note: Option<String>,

/// The amount of time in seconds that the user would like to wait for an invoice to
/// arrive. If this isn't set, we'll use the default value.
#[arg(long, global = false, required = false, default_value = DEFAULT_RESPONSE_INVOICE_TIMEOUT.to_string())]
response_invoice_timeout: Option<u32>,
},
/// GetInvoice fetch a BOLT 12 invoice, which will be returned as a hex-encoded string. It
/// fetches the invoice from a BOLT 12 offer, provided as a 'lno'-prefaced offer string.
Expand All @@ -108,6 +113,11 @@ enum Commands {
/// A payer-provided note which will be seen by the recipient.
#[arg(required = false)]
payer_note: Option<String>,

/// The amount of time in seconds that the user would like to wait for an invoice to
/// arrive. If this isn't set, we'll use the default value.
#[arg(long, global = false, required = false, default_value = DEFAULT_RESPONSE_INVOICE_TIMEOUT.to_string())]
response_invoice_timeout: Option<u32>,
},
/// PayInvoice pays a hex-encoded BOLT12 invoice.
PayInvoice {
Expand Down Expand Up @@ -162,6 +172,7 @@ async fn main() {
ref offer_string,
amount,
payer_note,
response_invoice_timeout,
} => {
let tls = read_cert_from_args(args.cert_pem, args.cert_path);
let grpc_host = args.grpc_host;
Expand Down Expand Up @@ -203,6 +214,7 @@ async fn main() {
offer: offer.to_string(),
amount,
payer_note,
response_invoice_timeout,
});
add_metadata(&mut request, macaroon).unwrap_or_else(|_| exit(1));

Expand All @@ -218,6 +230,7 @@ async fn main() {
ref offer_string,
amount,
payer_note,
response_invoice_timeout,
} => {
let tls = read_cert_from_args(args.cert_pem, args.cert_path);
let grpc_host = args.grpc_host;
Expand Down Expand Up @@ -258,6 +271,7 @@ async fn main() {
offer: offer.to_string(),
amount,
payer_note,
response_invoice_timeout,
});
add_metadata(&mut request, macaroon).unwrap_or_else(|_| exit(1));
match client.get_invoice(request).await {
Expand Down
42 changes: 30 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub const DEFAULT_DATA_DIR: &str = ".lndk";

pub const TLS_CERT_FILENAME: &str = "tls-cert.pem";
pub const TLS_KEY_FILENAME: &str = "tls-key.pem";
pub const DEFAULT_RESPONSE_INVOICE_TIMEOUT: u32 = 15;

#[allow(clippy::result_unit_err)]
pub fn setup_logger(log_level: Option<String>, log_dir: Option<String>) -> Result<(), ()> {
Expand Down Expand Up @@ -263,6 +264,9 @@ pub struct OfferHandler {
pending_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
pub messenger_utils: MessengerUtilities,
expanded_key: ExpandedKey,
/// The amount of time in seconds that we will wait for the offer creator to respond with
/// an invoice. If not provided, we will use the default value of 15 seconds.
pub response_invoice_timeout: u32,
}

pub struct PaymentInfo {
Expand All @@ -282,19 +286,25 @@ pub struct PayOfferParams {
/// The path we will send back to the offer creator, so it knows where to send back the
/// invoice.
pub reply_path: Option<BlindedPath>,
/// The amount of time in seconds that we will wait for the offer creator to respond with
/// an invoice. If not provided, we will use the default value of 15 seconds.
pub response_invoice_timeout: Option<u32>,
}

impl OfferHandler {
pub fn new() -> Self {
pub fn new(response_invoice_timeout: Option<u32>) -> Self {
let messenger_utils = MessengerUtilities::new();
let random_bytes = messenger_utils.get_secure_random_bytes();
let expanded_key = ExpandedKey::new(&KeyMaterial(random_bytes));
let response_invoice_timeout =
response_invoice_timeout.unwrap_or(DEFAULT_RESPONSE_INVOICE_TIMEOUT);

OfferHandler {
active_payments: Mutex::new(HashMap::new()),
pending_messages: Mutex::new(Vec::new()),
messenger_utils,
expanded_key,
response_invoice_timeout,
}
}

Expand Down Expand Up @@ -338,16 +348,24 @@ impl OfferHandler {
e
})?;

let invoice =
match timeout(Duration::from_secs(20), self.wait_for_invoice(payment_id)).await {
Ok(invoice) => invoice,
Err(_) => {
error!("Did not receive invoice in 100 seconds.");
let mut active_payments = self.active_payments.lock().unwrap();
active_payments.remove(&payment_id);
return Err(OfferError::InvoiceTimeout);
}
};
let cfg_timeout = cfg
.response_invoice_timeout
.unwrap_or(self.response_invoice_timeout);

let invoice = match timeout(
Duration::from_secs(cfg_timeout as u64),
self.wait_for_invoice(payment_id),
)
.await
{
Ok(invoice) => invoice,
Err(_) => {
error!("Did not receive invoice in {cfg_timeout} seconds.");
let mut active_payments = self.active_payments.lock().unwrap();
active_payments.remove(&payment_id);
return Err(OfferError::InvoiceTimeout(cfg_timeout));
}
};
{
let mut active_payments = self.active_payments.lock().unwrap();
active_payments
Expand Down Expand Up @@ -435,7 +453,7 @@ impl OfferHandler {

impl Default for OfferHandler {
fn default() -> Self {
Self::new()
Self::new(None)
}
}

Expand Down
22 changes: 11 additions & 11 deletions src/lndk_offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub enum OfferError {
/// Failed to send payment.
PaymentFailure,
/// Failed to receive an invoice back from offer creator before the timeout.
InvoiceTimeout,
InvoiceTimeout(u32),
/// Failed to find introduction node for blinded path.
IntroductionNodeNotFound,
/// Cannot fetch channel info.
Expand Down Expand Up @@ -94,7 +94,7 @@ impl Display for OfferError {
OfferError::RouteFailure(e) => write!(f, "Error routing payment: {e:?}"),
OfferError::TrackFailure(e) => write!(f, "Error tracking payment: {e:?}"),
OfferError::PaymentFailure => write!(f, "Failed to send payment"),
OfferError::InvoiceTimeout => write!(f, "Did not receive invoice in 100 seconds."),
OfferError::InvoiceTimeout(e) => write!(f, "Did not receive invoice in {e:?} seconds."),
OfferError::IntroductionNodeNotFound => write!(f, "Could not find introduction node."),
OfferError::GetChannelInfo(e) => write!(f, "Could not fetch channel info: {e:?}"),
}
Expand Down Expand Up @@ -803,7 +803,7 @@ mod tests {
.returning(move |_, _| Ok(get_invoice_request(offer.clone(), amount)));

let offer = decode(get_offer()).unwrap();
let handler = OfferHandler::new();
let handler = OfferHandler::default();
let resp = handler
.create_invoice_request(
signer_mock,
Expand All @@ -829,7 +829,7 @@ mod tests {
.returning(move |_, _| Ok(get_invoice_request(decode(get_offer()).unwrap(), 10000)));

let offer = decode(get_offer()).unwrap();
let handler = OfferHandler::new();
let handler = OfferHandler::default();
assert!(handler
.create_invoice_request(
signer_mock,
Expand Down Expand Up @@ -858,7 +858,7 @@ mod tests {
.returning(move |_, _| Err(OfferError::SignError(SignError::Signing)));

let offer = decode(get_offer()).unwrap();
let handler = OfferHandler::new();
let handler = OfferHandler::default();
assert!(handler
.create_invoice_request(
signer_mock,
Expand Down Expand Up @@ -1005,7 +1005,7 @@ mod tests {
});

let receiver_node_id = PublicKey::from_str(&get_pubkey()).unwrap();
let handler = OfferHandler::new();
let handler = OfferHandler::default();
assert!(handler
.create_reply_path(connector_mock, receiver_node_id)
.await
Expand All @@ -1022,7 +1022,7 @@ mod tests {
.returning(|| Ok(ListPeersResponse { peers: vec![] }));

let receiver_node_id = PublicKey::from_str(&get_pubkey()).unwrap();
let handler = OfferHandler::new();
let handler = OfferHandler::default();
assert!(handler
.create_reply_path(connector_mock, receiver_node_id)
.await
Expand All @@ -1038,7 +1038,7 @@ mod tests {
.returning(|| Err(Status::unknown("unknown error")));

let receiver_node_id = PublicKey::from_str(&get_pubkey()).unwrap();
let handler = OfferHandler::new();
let handler = OfferHandler::default();
assert!(handler
.create_reply_path(connector_mock, receiver_node_id)
.await
Expand Down Expand Up @@ -1073,7 +1073,7 @@ mod tests {

let blinded_path = get_blinded_path();
let payment_hash = MessengerUtilities::new().get_secure_random_bytes();
let handler = OfferHandler::new();
let handler = OfferHandler::default();
let payment_id = PaymentId(MessengerUtilities::new().get_secure_random_bytes());
let params = SendPaymentParams {
path: blinded_path,
Expand All @@ -1098,7 +1098,7 @@ mod tests {
let blinded_path = get_blinded_path();
let payment_hash = MessengerUtilities::new().get_secure_random_bytes();
let payment_id = PaymentId(MessengerUtilities::new().get_secure_random_bytes());
let handler = OfferHandler::new();
let handler = OfferHandler::default();
let params = SendPaymentParams {
path: blinded_path,
cltv_expiry_delta: 200,
Expand Down Expand Up @@ -1132,7 +1132,7 @@ mod tests {
let blinded_path = get_blinded_path();
let payment_hash = MessengerUtilities::new().get_secure_random_bytes();
let payment_id = PaymentId(MessengerUtilities::new().get_secure_random_bytes());
let handler = OfferHandler::new();
let handler = OfferHandler::default();
let params = SendPaymentParams {
path: blinded_path,
cltv_expiry_delta: 200,
Expand Down
11 changes: 10 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use lndkrpc::offers_server::OffersServer;
use log::{error, info};
use std::fs::create_dir_all;
use std::path::PathBuf;
use std::process::exit;
use std::sync::Arc;
use tokio::select;
use tonic::transport::{Server, ServerTlsConfig};
Expand Down Expand Up @@ -55,7 +56,15 @@ async fn main() -> Result<(), ()> {
skip_version_check: config.skip_version_check,
};

let handler = Arc::new(OfferHandler::new());
let response_invoice_timeout = config.response_invoice_timeout;
if let Some(timeout) = response_invoice_timeout {
if timeout == 0 {
eprintln!("Error: response_invoice_timeout must be more than 0 seconds.");
exit(1);
}
}

let handler = Arc::new(OfferHandler::new(config.response_invoice_timeout));

Check warning on line 67 in src/main.rs

View check run for this annotation

Codecov / codecov/patch

src/main.rs#L59-L67

Added lines #L59 - L67 were not covered by tests
let messenger = LndkOnionMessenger::new();

let data_dir =
Expand Down
2 changes: 2 additions & 0 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ impl Offers for LNDKServer {
client,
destination,
reply_path: Some(reply_path),
response_invoice_timeout: inner_request.response_invoice_timeout,
};

let payment = match self.offer_handler.pay_offer(cfg).await {
Expand Down Expand Up @@ -206,6 +207,7 @@ impl Offers for LNDKServer {
client,
destination,
reply_path: Some(reply_path),
response_invoice_timeout: inner_request.response_invoice_timeout,
};

let (invoice, _, payment_id) = match self.offer_handler.get_invoice(cfg).await {
Expand Down
2 changes: 1 addition & 1 deletion tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ pub async fn setup_lndk(
};

// Make sure lndk successfully sends the invoice_request.
let handler = Arc::new(lndk::OfferHandler::new());
let handler = Arc::new(lndk::OfferHandler::default());
let messenger = lndk::LndkOnionMessenger::new();

let log_dir = Some(
Expand Down
7 changes: 5 additions & 2 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ async fn create_offers(
client: client.clone(),
destination: Destination::BlindedPath(blinded_path),
reply_path: Some(reply_path),
response_invoice_timeout: None,
};

pay_cfgs.push(pay_cfg);
Expand Down Expand Up @@ -216,7 +217,7 @@ async fn test_lndk_send_invoice_request() {
setup_logger(None, log_dir).unwrap();

// Make sure lndk successfully sends the invoice_request.
let handler = Arc::new(lndk::OfferHandler::new());
let handler = Arc::new(lndk::OfferHandler::default());
let messenger = lndk::LndkOnionMessenger::new();
let (invoice_request, _, _) = handler
.create_invoice_request(
Expand Down Expand Up @@ -270,7 +271,7 @@ async fn test_lndk_send_invoice_request() {
);
setup_logger(None, log_dir).unwrap();

let handler = Arc::new(lndk::OfferHandler::new());
let handler = Arc::new(lndk::OfferHandler::default());
let messenger = lndk::LndkOnionMessenger::new();
let (invoice_request, _, _) = handler
.create_invoice_request(
Expand Down Expand Up @@ -342,6 +343,7 @@ async fn test_lndk_pay_offer() {
client: client.clone(),
destination: Destination::BlindedPath(blinded_path.clone()),
reply_path: Some(reply_path),
response_invoice_timeout: None,
};
select! {
val = messenger.run(lndk_cfg.clone(), Arc::clone(&handler)) => {
Expand Down Expand Up @@ -398,6 +400,7 @@ async fn test_lndk_pay_offer_concurrently() {
client: client.clone(),
destination: Destination::BlindedPath(blinded_path.clone()),
reply_path: Some(reply_path),
response_invoice_timeout: None,
};
// Let's also try to pay the same offer multiple times concurrently.
select! {
Expand Down

0 comments on commit 856eb68

Please sign in to comment.