diff --git a/config_spec.toml b/config_spec.toml index 61df6e6e..cc2cc3d4 100644 --- a/config_spec.toml +++ b/config_spec.toml @@ -56,3 +56,9 @@ name = "grpc_port" type = "u16" optional = true doc = "The port the grpc server will run on. Defaults to 7000." + +[[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 30s." diff --git a/proto/lndkrpc.proto b/proto/lndkrpc.proto index cf6b198a..0a9cda3c 100644 --- a/proto/lndkrpc.proto +++ b/proto/lndkrpc.proto @@ -8,6 +8,7 @@ service Offers { message PayOfferRequest { string offer = 1; optional uint64 amount = 2; + optional uint32 response_invoice_timeout = 3; } message PayOfferResponse { diff --git a/sample-lndk.conf b/sample-lndk.conf index 655ec7ba..311e7c07 100644 --- a/sample-lndk.conf +++ b/sample-lndk.conf @@ -8,3 +8,4 @@ macaroon_path="/home//.lnd/data/chain/bitcoin/regtest/admin.macaroon" # -----END CERTIFICATE-----" # # This should be a hex-encoded macaroon string. # macaroon-hex="0201036C6E6402F801030A1034F41C28A3B5190702FEA607DD4045AE1201301A160A0761646472657373120472656164120577726974651A130A04696E666F120472656164120577726974651A170A08696E766F69636573120472656164120577726974651A210A086D616361726F6F6E120867656E6572617465120472656164120577726974651A160A076D657373616765120472656164120577726974651A170A086F6666636861696E120472656164120577726974651A160A076F6E636861696E120472656164120577726974651A140A057065657273120472656164120577726974651A180A067369676E6572120867656E6572617465120472656164000006205CF3E77A97764FB2A68967832608591BE375B36F51A6269AB425AA767BC22B55" +response_invoice_timeout=10 diff --git a/src/cli.rs b/src/cli.rs index 0f5eecfd..297a1512 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -71,6 +71,11 @@ enum Commands { /// whatever the offer amount is. #[arg(required = false)] amount: Option, + + /// 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)] + response_invoice_timeout: Option, }, } @@ -98,6 +103,7 @@ async fn main() -> Result<(), ()> { Commands::PayOffer { ref offer_string, amount, + response_invoice_timeout, } => { let data_dir = home::home_dir().unwrap().join(DEFAULT_DATA_DIR); let pem = match args.cert_pem { @@ -162,6 +168,7 @@ async fn main() -> Result<(), ()> { let mut request = Request::new(PayOfferRequest { offer: offer.to_string(), amount, + response_invoice_timeout, }); add_metadata(&mut request, macaroon).map_err(|_| ())?; diff --git a/src/lib.rs b/src/lib.rs index 3b6dffdb..fd49833b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,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 = 30; #[allow(clippy::result_unit_err)] pub fn setup_logger(log_level: Option, log_dir: Option) -> Result<(), ()> { @@ -226,6 +227,9 @@ pub struct OfferHandler { pending_messages: Mutex>>, pub messenger_utils: MessengerUtilities, expanded_key: ExpandedKey, + /// Default timeout in seconds that we will wait for offer creator to respond with + /// an invoice. + pub response_invoice_timeout: u32, } #[derive(Clone)] @@ -239,10 +243,28 @@ 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, + /// The amount of time in seconds that we will wait for offer creator to respond with + /// an invoice. If not provided, we will use the default value. + pub response_invoice_timeout: Option, } impl OfferHandler { - pub fn new() -> Self { + pub fn new_default() -> Self { + Self::new(None) + } + + pub fn new(response_invoice_timeout: Option) -> Self { + let response_invoice_timeout = match response_invoice_timeout { + Some(timeout) => { + if timeout < 1 { + DEFAULT_RESPONSE_INVOICE_TIMEOUT + } else { + timeout + } + } + None => DEFAULT_RESPONSE_INVOICE_TIMEOUT, + }; + let messenger_utils = MessengerUtilities::new(); let random_bytes = messenger_utils.get_secure_random_bytes(); let expanded_key = ExpandedKey::new(&KeyMaterial(random_bytes)); @@ -253,6 +275,7 @@ impl OfferHandler { pending_messages: Mutex::new(Vec::new()), messenger_utils, expanded_key, + response_invoice_timeout, } } @@ -264,19 +287,28 @@ impl OfferHandler { ) -> Result> { let client_clone = cfg.client.clone(); let offer_id = cfg.offer.clone().to_string(); + let cfg_timeout = match cfg.response_invoice_timeout { + Some(timeout) => timeout, + None => self.response_invoice_timeout, + }; let validated_amount = self.send_invoice_request(cfg).await.map_err(|e| { let mut active_offers = self.active_offers.lock().unwrap(); active_offers.remove(&offer_id.clone()); e })?; - let invoice = match timeout(Duration::from_secs(100), self.wait_for_invoice()).await { + let invoice = match timeout( + Duration::from_secs(cfg_timeout as u64), + self.wait_for_invoice(), + ) + .await + { Ok(invoice) => invoice, Err(_) => { - error!("Did not receive invoice in 100 seconds."); + error!("Did not receive invoice in {cfg_timeout} seconds."); let mut active_offers = self.active_offers.lock().unwrap(); active_offers.remove(&offer_id.clone()); - return Err(OfferError::InvoiceTimeout); + return Err(OfferError::InvoiceTimeout(cfg_timeout)); } }; { @@ -327,7 +359,7 @@ impl OfferHandler { impl Default for OfferHandler { fn default() -> Self { - Self::new() + Self::new_default() } } diff --git a/src/lndk_offers.rs b/src/lndk_offers.rs index dca809e3..f1f3ce13 100644 --- a/src/lndk_offers.rs +++ b/src/lndk_offers.rs @@ -59,7 +59,7 @@ pub enum OfferError { /// Failed to send payment. PaymentFailure, /// Failed to receive an invoice back from offer creator before the timeout. - InvoiceTimeout, + InvoiceTimeout(u32), } impl Display for OfferError { @@ -83,7 +83,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."), } } } @@ -699,7 +699,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::new_default(); let resp = handler .create_invoice_request(signer_mock, offer, vec![], Network::Regtest, amount) .await; @@ -719,7 +719,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::new_default(); assert!(handler .create_invoice_request(signer_mock, offer, vec![], Network::Regtest, 10000,) .await @@ -744,7 +744,7 @@ mod tests { }); let offer = decode(get_offer()).unwrap(); - let handler = OfferHandler::new(); + let handler = OfferHandler::new_default(); assert!(handler .create_invoice_request(signer_mock, offer, vec![], Network::Regtest, 10000,) .await @@ -885,7 +885,7 @@ mod tests { }); let receiver_node_id = PublicKey::from_str(&get_pubkey()).unwrap(); - let handler = OfferHandler::new(); + let handler = OfferHandler::new_default(); assert!(handler .create_reply_path(connector_mock, receiver_node_id) .await @@ -902,7 +902,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::new_default(); assert!(handler .create_reply_path(connector_mock, receiver_node_id) .await @@ -918,7 +918,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::new_default(); assert!(handler .create_reply_path(connector_mock, receiver_node_id) .await @@ -953,7 +953,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::new_default(); let params = PayInvoiceParams { path: blinded_path, cltv_expiry_delta: 200, @@ -976,7 +976,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::new_default(); let params = PayInvoiceParams { path: blinded_path, cltv_expiry_delta: 200, @@ -1009,7 +1009,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::new_default(); let params = PayInvoiceParams { path: blinded_path, cltv_expiry_delta: 200, diff --git a/src/main.rs b/src/main.rs index 83e7da33..2afef7b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ async fn main() -> Result<(), ()> { signals, }; - let handler = Arc::new(OfferHandler::new()); + let handler = Arc::new(OfferHandler::new(config.response_invoice_timeout)); let messenger = LndkOnionMessenger::new(); let data_dir = diff --git a/src/server.rs b/src/server.rs index 30e6249b..08f977ac 100644 --- a/src/server.rs +++ b/src/server.rs @@ -97,6 +97,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 { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e91d2c00..3cbdfb9d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -99,7 +99,7 @@ async fn test_lndk_forwards_onion_message() { ); setup_logger(None, log_dir).unwrap(); - let handler = Arc::new(lndk::OfferHandler::new()); + let handler = Arc::new(lndk::OfferHandler::new_default()); let messenger = lndk::LndkOnionMessenger::new(); select! { val = messenger.run(lndk_cfg, Arc::clone(&handler)) => { @@ -245,7 +245,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::new_default()); let messenger = lndk::LndkOnionMessenger::new(); let pay_cfg = PayOfferParams { offer: offer.clone(), @@ -254,6 +254,7 @@ async fn test_lndk_send_invoice_request() { client: client.clone(), destination: Destination::BlindedPath(blinded_path.clone()), reply_path: Some(reply_path.clone()), + response_invoice_timeout: None, }; select! { val = messenger.run(lndk_cfg, Arc::clone(&handler)) => { @@ -290,7 +291,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::new_default()); let messenger = lndk::LndkOnionMessenger::new(); select! { val = messenger.run(lndk_cfg, Arc::clone(&handler)) => { @@ -420,7 +421,7 @@ async fn test_lndk_pay_offer() { BlindedPath::new_for_message(&[pubkey_2, lnd_pubkey], &messenger_utils, &secp_ctx).unwrap(); // Make sure lndk successfully sends the invoice_request. - let handler = Arc::new(lndk::OfferHandler::new()); + let handler = Arc::new(lndk::OfferHandler::new_default()); let messenger = lndk::LndkOnionMessenger::new(); let pay_cfg = PayOfferParams { offer, @@ -429,6 +430,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, }; let log_dir = Some( lndk_dir