--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use lightning::util::base32;
+
+use crate::utils::test_logger;
+
+#[inline]
+pub fn do_test(data: &[u8]) {
+ if let Ok(s) = std::str::from_utf8(data) {
+ let first_decoding = base32::Alphabet::RFC4648 { padding: true }.decode(s);
+ if let Ok(first_decoding) = first_decoding {
+ let encoding_response = base32::Alphabet::RFC4648 { padding: true }.encode(&first_decoding);
+ assert_eq!(encoding_response, s.to_ascii_uppercase());
+ let second_decoding = base32::Alphabet::RFC4648 { padding: true }.decode(&encoding_response).unwrap();
+ assert_eq!(first_decoding, second_decoding);
+ }
+ }
+
+ if let Ok(s) = std::str::from_utf8(data) {
+ let first_decoding = base32::Alphabet::RFC4648 { padding: false }.decode(s);
+ if let Ok(first_decoding) = first_decoding {
+ let encoding_response = base32::Alphabet::RFC4648 { padding: false }.encode(&first_decoding);
+ assert_eq!(encoding_response, s.to_ascii_uppercase());
+ let second_decoding = base32::Alphabet::RFC4648 { padding: false }.decode(&encoding_response).unwrap();
+ assert_eq!(first_decoding, second_decoding);
+ }
+ }
+
+ let encode_response = base32::Alphabet::RFC4648 { padding: false }.encode(&data);
+ let decode_response = base32::Alphabet::RFC4648 { padding: false }.decode(&encode_response).unwrap();
+ assert_eq!(data, decode_response);
+
+ let encode_response = base32::Alphabet::RFC4648 { padding: true }.encode(&data);
+ let decode_response = base32::Alphabet::RFC4648 { padding: true }.decode(&encode_response).unwrap();
+ assert_eq!(data, decode_response);
+}
+
+pub fn base32_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ do_test(data);
+}
+
+#[no_mangle]
+pub extern "C" fn base32_run(data: *const u8, datalen: usize) {
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) });
+}
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on target_template.txt
+// To modify it, modify target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::base32::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ base32_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ base32_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ base32_run(data.as_ptr(), data.len());
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+ use std::io::Read;
+
+ let mut data = Vec::with_capacity(8192);
+ std::io::stdin().read_to_end(&mut data).unwrap();
+ base32_run(data.as_ptr(), data.len());
+}
+
+#[test]
+fn run_test_cases() {
+ use std::fs;
+ use std::io::Read;
+ use lightning_fuzz::utils::test_logger::StringBuffer;
+
+ use std::sync::{atomic, Arc};
+ {
+ let data: Vec<u8> = vec![0];
+ base32_run(data.as_ptr(), data.len());
+ }
+ let mut threads = Vec::new();
+ let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+ if let Ok(tests) = fs::read_dir("test_cases/base32") {
+ for test in tests {
+ let mut data: Vec<u8> = Vec::new();
+ let path = test.unwrap().path();
+ fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
+ threads_running.fetch_add(1, atomic::Ordering::AcqRel);
+
+ let thread_count_ref = Arc::clone(&threads_running);
+ let main_thread_ref = std::thread::current();
+ threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
+ std::thread::spawn(move || {
+ let string_logger = StringBuffer::new();
+
+ let panic_logger = string_logger.clone();
+ let res = if ::std::panic::catch_unwind(move || {
+ base32_test(&data, panic_logger);
+ }).is_err() {
+ Some(string_logger.into_string())
+ } else { None };
+ thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
+ main_thread_ref.unpark();
+ res
+ })
+ ));
+ while threads_running.load(atomic::Ordering::Acquire) > 32 {
+ std::thread::park();
+ }
+ }
+ }
+ let mut failed_outputs = Vec::new();
+ for (test, thread) in threads.drain(..) {
+ if let Some(output) = thread.join().unwrap() {
+ println!("\nOutput of {}:\n{}\n", test, output);
+ failed_outputs.push(test);
+ }
+ }
+ if !failed_outputs.is_empty() {
+ println!("Test cases which failed: ");
+ for case in failed_outputs {
+ println!("{}", case);
+ }
+ panic!();
+ }
+}
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on target_template.txt
+// To modify it, modify target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::fromstr_to_netaddress::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+ use std::io::Read;
+
+ let mut data = Vec::with_capacity(8192);
+ std::io::stdin().read_to_end(&mut data).unwrap();
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+}
+
+#[test]
+fn run_test_cases() {
+ use std::fs;
+ use std::io::Read;
+ use lightning_fuzz::utils::test_logger::StringBuffer;
+
+ use std::sync::{atomic, Arc};
+ {
+ let data: Vec<u8> = vec![0];
+ fromstr_to_netaddress_run(data.as_ptr(), data.len());
+ }
+ let mut threads = Vec::new();
+ let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+ if let Ok(tests) = fs::read_dir("test_cases/fromstr_to_netaddress") {
+ for test in tests {
+ let mut data: Vec<u8> = Vec::new();
+ let path = test.unwrap().path();
+ fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
+ threads_running.fetch_add(1, atomic::Ordering::AcqRel);
+
+ let thread_count_ref = Arc::clone(&threads_running);
+ let main_thread_ref = std::thread::current();
+ threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
+ std::thread::spawn(move || {
+ let string_logger = StringBuffer::new();
+
+ let panic_logger = string_logger.clone();
+ let res = if ::std::panic::catch_unwind(move || {
+ fromstr_to_netaddress_test(&data, panic_logger);
+ }).is_err() {
+ Some(string_logger.into_string())
+ } else { None };
+ thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
+ main_thread_ref.unpark();
+ res
+ })
+ ));
+ while threads_running.load(atomic::Ordering::Acquire) > 32 {
+ std::thread::park();
+ }
+ }
+ }
+ let mut failed_outputs = Vec::new();
+ for (test, thread) in threads.drain(..) {
+ if let Some(output) = thread.join().unwrap() {
+ println!("\nOutput of {}:\n{}\n", test, output);
+ failed_outputs.push(test);
+ }
+ }
+ if !failed_outputs.is_empty() {
+ println!("Test cases which failed: ");
+ for case in failed_outputs {
+ println!("{}", case);
+ }
+ panic!();
+ }
+}
GEN_TEST zbase32
GEN_TEST indexedmap
GEN_TEST onion_hop_data
+GEN_TEST base32
+GEN_TEST fromstr_to_netaddress
GEN_TEST msg_accept_channel msg_targets::
GEN_TEST msg_announcement_signatures msg_targets::
fee_msat: amt,
cltv_expiry_delta: 200,
}], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
check_payment_err(err, amt > max_value_sendable || amt < min_value_sendable);
false
channel_features: middle.channel_features(),
fee_msat: first_hop_fee,
cltv_expiry_delta: 100,
- },RouteHop {
+ }, RouteHop {
pubkey: dest.get_our_node_id(),
node_features: dest.node_features(),
short_channel_id: dest_chan_id,
fee_msat: amt,
cltv_expiry_delta: 200,
}], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
let sent_amt = amt + first_hop_fee;
check_payment_err(err, sent_amt < min_value_sendable || sent_amt > max_value_sendable);
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use lightning::ln::msgs::NetAddress;
+use core::str::FromStr;
+
+use crate::utils::test_logger;
+
+#[inline]
+pub fn do_test(data: &[u8]) {
+ if let Ok(s) = std::str::from_utf8(data) {
+ let _ = NetAddress::from_str(s);
+ }
+
+}
+
+pub fn fromstr_to_netaddress_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ do_test(data);
+}
+
+#[no_mangle]
+pub extern "C" fn fromstr_to_netaddress_run(data: *const u8, datalen: usize) {
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) });
+}
+
4 => {
let final_value_msat = slice_to_be24(get_slice!(3)) as u64;
let payment_params = PaymentParameters::from_node_id(get_pubkey!(), 42);
- let params = RouteParameters {
- payment_params,
- final_value_msat,
- };
+ let params = RouteParameters::from_payment_params_and_value(
+ payment_params, final_value_msat);
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0[0..8].copy_from_slice(&be64_to_array(payments_sent));
let mut sha = Sha256::engine();
15 => {
let final_value_msat = slice_to_be24(get_slice!(3)) as u64;
let payment_params = PaymentParameters::from_node_id(get_pubkey!(), 42);
- let params = RouteParameters {
- payment_params,
- final_value_msat,
- };
+ let params = RouteParameters::from_payment_params_and_value(
+ payment_params, final_value_msat);
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0[0..8].copy_from_slice(&be64_to_array(payments_sent));
let mut sha = Sha256::engine();
pub mod router;
pub mod zbase32;
pub mod onion_hop_data;
+pub mod base32;
+pub mod fromstr_to_netaddress;
pub mod msg_targets;
let mut last_hops = Vec::new();
last_hops!(last_hops);
find_routes!(first_hops, node_pks.iter(), |final_amt, final_delta, target: &PublicKey| {
- RouteParameters {
- payment_params: PaymentParameters::from_node_id(*target, final_delta)
+ RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(*target, final_delta)
.with_route_hints(last_hops.clone()).unwrap(),
- final_value_msat: final_amt,
- }
+ final_amt)
});
},
x => {
let mut features = Bolt12InvoiceFeatures::empty();
features.set_basic_mpp_optional();
find_routes!(first_hops, vec![dummy_pk].iter(), |final_amt, _, _| {
- RouteParameters {
- payment_params: PaymentParameters::blinded(last_hops.clone())
- .with_bolt12_features(features.clone()).unwrap(),
- final_value_msat: final_amt,
- }
+ RouteParameters::from_payment_params_and_value(PaymentParameters::blinded(last_hops.clone())
+ .with_bolt12_features(features.clone()).unwrap(),
+ final_amt)
});
}
}
// You may not use this file except in accordance with one or both of these
// licenses.
-use lightning::util::zbase32;
+use lightning::util::base32;
use crate::utils::test_logger;
#[inline]
pub fn do_test(data: &[u8]) {
- let res = zbase32::encode(data);
- assert_eq!(&zbase32::decode(&res).unwrap()[..], data);
+ let res = base32::Alphabet::ZBase32.encode(data);
+ assert_eq!(&base32::Alphabet::ZBase32.decode(&res).unwrap()[..], data);
if let Ok(s) = std::str::from_utf8(data) {
- if let Ok(decoded) = zbase32::decode(s) {
- assert_eq!(&zbase32::encode(&decoded), &s.to_ascii_lowercase());
+ let res = base32::Alphabet::ZBase32.decode(s);
+ if let Ok(decoded) = res {
+ assert_eq!(&base32::Alphabet::ZBase32.encode(&decoded), &s.to_ascii_lowercase());
}
}
}
void zbase32_run(const unsigned char* data, size_t data_len);
void indexedmap_run(const unsigned char* data, size_t data_len);
void onion_hop_data_run(const unsigned char* data, size_t data_len);
+void base32_run(const unsigned char* data, size_t data_len);
+void fromstr_to_netaddress_run(const unsigned char* data, size_t data_len);
void msg_accept_channel_run(const unsigned char* data, size_t data_len);
void msg_announcement_signatures_run(const unsigned char* data, size_t data_len);
void msg_channel_reestablish_run(const unsigned char* data, size_t data_len);
invoice.min_final_cltv_expiry_delta() as u32)
.with_bolt11_features(invoice.features().unwrap().clone()).unwrap()
.with_route_hints(invoice.route_hints()).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: invoice.amount_milli_satoshis().unwrap(),
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, invoice.amount_milli_satoshis().unwrap());
let payment_event = {
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
invoice.min_final_cltv_expiry_delta() as u32)
.with_bolt11_features(invoice.features().unwrap().clone()).unwrap()
.with_route_hints(invoice.route_hints()).unwrap();
- let params = RouteParameters {
- payment_params,
- final_value_msat: invoice.amount_milli_satoshis().unwrap(),
- };
+ let params = RouteParameters::from_payment_params_and_value(
+ payment_params, invoice.amount_milli_satoshis().unwrap());
let (payment_event, fwd_idx) = {
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
monitor_state.last_chain_persist_height.load(Ordering::Acquire) + LATENCY_GRACE_PERIOD_BLOCKS as usize
> self.highest_chain_height.load(Ordering::Acquire)
{
- log_info!(self.logger, "A Channel Monitor sync is still in progress, refusing to provide monitor events!");
+ log_debug!(self.logger, "A Channel Monitor sync is still in progress, refusing to provide monitor events!");
} else {
if monitor_state.channel_perm_failed.load(Ordering::Acquire) {
// If a `UpdateOrigin::ChainSync` persistence failed with `PermanantFailure`,
/// The node_id of the node which should receive this message
node_id: PublicKey,
/// The message which should be sent.
- msg: msgs::TxAddInput,
+ msg: msgs::TxAbort,
},
/// Used to indicate that a channel_ready message should be sent to the peer with the given node_id.
SendChannelReady {
/// exceeding this age limit will be force-closed and purged from memory.
pub(crate) const UNFUNDED_CHANNEL_AGE_LIMIT_TICKS: usize = 60;
+/// Number of blocks needed for an output from a coinbase transaction to be spendable.
+pub(crate) const COINBASE_MATURITY: u32 = 100;
+
struct PendingChannelMonitorUpdate {
update: ChannelMonitorUpdate,
}
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
} else {
if self.context.is_outbound() {
- for input in tx.input.iter() {
- if input.witness.is_empty() {
- // We generated a malleable funding transaction, implying we've
- // just exposed ourselves to funds loss to our counterparty.
- #[cfg(not(fuzzing))]
- panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
+ if !tx.is_coin_base() {
+ for input in tx.input.iter() {
+ if input.witness.is_empty() {
+ // We generated a malleable funding transaction, implying we've
+ // just exposed ourselves to funds loss to our counterparty.
+ #[cfg(not(fuzzing))]
+ panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
+ }
}
}
}
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
}
}
+ // If this is a coinbase transaction and not a 0-conf channel
+ // we should update our min_depth to 100 to handle coinbase maturity
+ if tx.is_coin_base() &&
+ self.context.minimum_depth.unwrap_or(0) > 0 &&
+ self.context.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
+ self.context.minimum_depth = Some(COINBASE_MATURITY);
+ }
}
// If we allow 1-conf funding, we may need to check for channel_ready here and
// send it immediately instead of waiting for a best_block_updated call (which
self.context.channel_state = ChannelState::FundingCreated as u32;
self.context.channel_id = funding_txo.to_channel_id();
+
+ // If the funding transaction is a coinbase transaction, we need to set the minimum depth to 100.
+ // We can skip this if it is a zero-conf channel.
+ if funding_transaction.is_coin_base() &&
+ self.context.minimum_depth.unwrap_or(0) > 0 &&
+ self.context.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
+ self.context.minimum_depth = Some(COINBASE_MATURITY);
+ }
+
self.context.funding_transaction = Some(funding_transaction);
let channel = Channel {
pub fn funding_transaction_generated(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
- for inp in funding_transaction.input.iter() {
- if inp.witness.is_empty() {
- return Err(APIError::APIMisuseError {
- err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
- });
+ if !funding_transaction.is_coin_base() {
+ for inp in funding_transaction.input.iter() {
+ if inp.witness.is_empty() {
+ return Err(APIError::APIMisuseError {
+ err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
+ });
+ }
}
}
{
// But if the channel is behind of the monitor, close the channel:
log_error!(args.logger, "A ChannelManager is stale compared to the current ChannelMonitor!");
log_error!(args.logger, " The channel will be force-closed and the latest commitment transaction from the ChannelMonitor broadcast.");
- log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} but the ChannelManager is at update_id {}.",
- &channel.context.channel_id(), monitor.get_latest_update_id(), channel.context.get_latest_monitor_update_id());
+ if channel.context.get_latest_monitor_update_id() < monitor.get_latest_update_id() {
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at update_id {} but the ChannelManager is at update_id {}.",
+ &channel.context.channel_id(), monitor.get_latest_update_id(), channel.context.get_latest_monitor_update_id());
+ }
+ if channel.get_cur_holder_commitment_transaction_number() > monitor.get_cur_holder_commitment_number() {
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at holder commitment number {} but the ChannelManager is at holder commitment number {}.",
+ &channel.context.channel_id(), monitor.get_cur_holder_commitment_number(), channel.get_cur_holder_commitment_transaction_number());
+ }
+ if channel.get_revoked_counterparty_commitment_transaction_number() > monitor.get_min_seen_secret() {
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at revoked counterparty transaction number {} but the ChannelManager is at revoked counterparty transaction number {}.",
+ &channel.context.channel_id(), monitor.get_min_seen_secret(), channel.get_revoked_counterparty_commitment_transaction_number());
+ }
+ if channel.get_cur_counterparty_commitment_transaction_number() > monitor.get_cur_counterparty_commitment_number() {
+ log_error!(args.logger, " The ChannelMonitor for channel {} is at counterparty commitment transaction number {} but the ChannelManager is at counterparty commitment transaction number {}.",
+ &channel.context.channel_id(), monitor.get_cur_counterparty_commitment_number(), channel.get_cur_counterparty_commitment_transaction_number());
+ }
let (monitor_update, mut new_failed_htlcs) = channel.context.force_shutdown(true);
if let Some((counterparty_node_id, funding_txo, update)) = monitor_update {
close_background_events.push(BackgroundEvent::MonitorUpdateRegeneratedOnStartup {
let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &expected_route, 100_000);
// Next, attempt a keysend payment and make sure it fails.
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV, false),
- final_value_msat: 100_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(),
+ TEST_FINAL_CLTV, false), 100_000);
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph,
None, nodes[0].logger, &scorer, &(), &random_seed_bytes
pass_along_path(&nodes[0], &path, 100_000, payment_hash, None, event, true, Some(payment_preimage));
// Next, attempt a keysend payment and make sure it fails.
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV, false),
- final_value_msat: 100_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV, false),
+ 100_000
+ );
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph,
None, nodes[0].logger, &scorer, &(), &random_seed_bytes
let payee_pubkey = nodes[1].node.get_our_node_id();
let _chan = create_chan_between_nodes(&nodes[0], &nodes[1]);
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
- final_value_msat: 10_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, false), 10_000);
let network_graph = nodes[0].network_graph.clone();
let first_hops = nodes[0].node.list_usable_channels();
let scorer = test_utils::TestScorer::new();
let payee_pubkey = nodes[1].node.get_our_node_id();
let _chan = create_chan_between_nodes(&nodes[0], &nodes[1]);
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
- final_value_msat: 10_000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, false), 10_000);
let network_graph = nodes[0].network_graph.clone();
let first_hops = nodes[0].node.list_usable_channels();
let scorer = test_utils::TestScorer::new();
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
$node_a.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
- PaymentId(payment_hash.0), RouteParameters {
- payment_params, final_value_msat: 10_000,
- }, Retry::Attempts(0)).unwrap();
+ PaymentId(payment_hash.0),
+ RouteParameters::from_payment_params_and_value(payment_params, 10_000),
+ Retry::Attempts(0)).unwrap();
let payment_event = SendEvent::from_event($node_a.get_and_clear_pending_msg_events().pop().unwrap());
$node_b.handle_update_add_htlc(&$node_a.get_our_node_id(), &payment_event.msgs[0]);
$node_b.handle_commitment_signed(&$node_a.get_our_node_id(), &payment_event.commitment_msg);
true
}
+ /// Sets a required feature bit. Errors if `bit` is outside the feature range as defined
+ /// by [BOLT 9].
+ ///
+ /// Note: Required bits are even. If an odd bit is given, then the corresponding even bit will
+ /// be set instead (i.e., `bit - 1`).
+ ///
+ /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md
+ pub fn set_required_feature_bit(&mut self, bit: usize) -> Result<(), ()> {
+ self.set_feature_bit(bit - (bit % 2))
+ }
+
+ /// Sets an optional feature bit. Errors if `bit` is outside the feature range as defined
+ /// by [BOLT 9].
+ ///
+ /// Note: Optional bits are odd. If an even bit is given, then the corresponding odd bit will be
+ /// set instead (i.e., `bit + 1`).
+ ///
+ /// [BOLT 9]: https://github.com/lightning/bolts/blob/master/09-features.md
+ pub fn set_optional_feature_bit(&mut self, bit: usize) -> Result<(), ()> {
+ self.set_feature_bit(bit + (1 - (bit % 2)))
+ }
+
+ fn set_feature_bit(&mut self, bit: usize) -> Result<(), ()> {
+ if bit > 255 {
+ return Err(());
+ }
+ self.set_bit(bit, false)
+ }
+
/// Sets a required custom feature bit. Errors if `bit` is outside the custom range as defined
/// by [bLIP 2] or if it is a known `T` feature.
///
if bit < 256 {
return Err(());
}
+ self.set_bit(bit, true)
+ }
+ fn set_bit(&mut self, bit: usize, custom: bool) -> Result<(), ()> {
let byte_offset = bit / 8;
let mask = 1 << (bit - 8 * byte_offset);
- if byte_offset < T::KNOWN_FEATURE_MASK.len() {
+ if byte_offset < T::KNOWN_FEATURE_MASK.len() && custom {
if (T::KNOWN_FEATURE_MASK[byte_offset] & mask) != 0 {
return Err(());
}
assert!(!features.requires_basic_mpp());
assert!(features.requires_payment_secret());
assert!(features.supports_payment_secret());
+
+ // Set flags manually
+ let mut features = NodeFeatures::empty();
+ assert!(features.set_optional_feature_bit(55).is_ok());
+ assert!(features.supports_keysend());
+ assert!(features.set_optional_feature_bit(255).is_ok());
+ assert!(features.set_required_feature_bit(256).is_err());
}
#[test]
use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::channelmanager::{self, AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
-use crate::routing::router::{self, PaymentParameters, Route};
+use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
use crate::ln::features::InitFeatures;
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler,RoutingMessageHandler};
use crate::sync::{Arc, Mutex, LockTestExt, RwLock};
use core::mem;
use core::iter::repeat;
-use bitcoin::{PackedLockTime, TxMerkleNode};
+use bitcoin::{PackedLockTime, TxIn, TxMerkleNode};
pub const CHAN_CONFIRM_DEPTH: u32 = 10;
};
}
-pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128) -> (ChannelId, Transaction, OutPoint) {
+pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
+ expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128)
+ -> (ChannelId, Transaction, OutPoint)
+{
+ internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, false)
+}
+
+pub fn create_coinbase_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
+ expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128)
+ -> (ChannelId, Transaction, OutPoint)
+{
+ internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, true)
+}
+
+fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
+ expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128,
+ coinbase: bool) -> (ChannelId, Transaction, OutPoint) {
let chan_id = *node.network_chan_count.borrow();
let events = node.node.get_and_clear_pending_events();
assert_eq!(*channel_value_satoshis, expected_chan_value);
assert_eq!(user_channel_id, expected_user_chan_id);
- let tx = Transaction { version: chan_id as i32, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: vec![TxOut {
+ let input = if coinbase {
+ vec![TxIn {
+ previous_output: bitcoin::OutPoint::null(),
+ ..Default::default()
+ }]
+ } else {
+ Vec::new()
+ };
+
+ let tx = Transaction { version: chan_id as i32, lock_time: PackedLockTime::ZERO, input, output: vec![TxOut {
value: *channel_value_satoshis, script_pubkey: output_script.clone(),
}]};
let funding_outpoint = OutPoint { txid: tx.txid(), index: 0 };
_ => panic!("Unexpected event"),
}
}
+
pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: ChannelId) -> Transaction {
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, &node_b.node.get_our_node_id(), channel_value, 42);
assert_eq!(temporary_channel_id, expected_temporary_channel_id);
}
/// Gets a route from the given sender to the node described in `payment_params`.
-pub fn get_route(send_node: &Node, payment_params: &PaymentParameters, recv_value: u64) -> Result<Route, msgs::LightningError> {
+pub fn get_route(send_node: &Node, route_params: &RouteParameters) -> Result<Route, msgs::LightningError> {
let scorer = TestScorer::new();
let keys_manager = TestKeysInterface::new(&[0u8; 32], bitcoin::network::constants::Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
router::get_route(
- &send_node.node.get_our_node_id(), payment_params, &send_node.network_graph.read_only(),
+ &send_node.node.get_our_node_id(), route_params, &send_node.network_graph.read_only(),
Some(&send_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
- recv_value, send_node.logger, &scorer, &(), &random_seed_bytes
+ send_node.logger, &scorer, &(), &random_seed_bytes
)
}
/// Don't use this, use the identically-named function instead.
#[macro_export]
macro_rules! get_route {
- ($send_node: expr, $payment_params: expr, $recv_value: expr) => {
- $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value)
- }
+ ($send_node: expr, $payment_params: expr, $recv_value: expr) => {{
+ let route_params = $crate::routing::router::RouteParameters::from_payment_params_and_value($payment_params, $recv_value);
+ $crate::ln::functional_test_utils::get_route(&$send_node, &route_params)
+ }}
}
#[cfg(test)]
$crate::get_route_and_payment_hash!($send_node, $recv_node, payment_params, $recv_value)
}};
($send_node: expr, $recv_node: expr, $payment_params: expr, $recv_value: expr) => {{
+ let route_params = $crate::routing::router::RouteParameters::from_payment_params_and_value($payment_params, $recv_value);
let (payment_preimage, payment_hash, payment_secret) =
$crate::ln::functional_test_utils::get_payment_preimage_hash(&$recv_node, Some($recv_value), None);
- let route = $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value);
+ let route = $crate::ln::functional_test_utils::get_route(&$send_node, &route_params);
(route.unwrap(), payment_hash, payment_preimage, payment_secret)
}}
}
pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret) {
let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV)
.with_bolt11_features(expected_route.last().unwrap().node.invoice_features()).unwrap();
- let route = get_route(origin_node, &payment_params, recv_value).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value);
+ let route = get_route(origin_node, &route_params).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), expected_route.len());
for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) {
let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV)
.with_bolt11_features(expected_route.last().unwrap().node.invoice_features()).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value);
let network_graph = origin_node.network_graph.read_only();
let scorer = test_utils::TestScorer::new();
let seed = [0u8; 32];
let keys_manager = test_utils::TestKeysInterface::new(&seed, Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = router::get_route(
- &origin_node.node.get_our_node_id(), &payment_params, &network_graph,
- None, recv_value, origin_node.logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let route = router::get_route(&origin_node.node.get_our_node_id(), &route_params, &network_graph,
+ None, origin_node.logger, &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), expected_route.len());
for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
use crate::sign::{ChannelSigner, EcdsaChannelSigner, EntropySource, SignerProvider};
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason};
use crate::ln::{ChannelId, PaymentPreimage, PaymentSecret, PaymentHash};
-use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel};
+use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel, COINBASE_MATURITY};
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA};
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError};
use crate::ln::{chan_utils, onion_utils};
use crate::ln::chan_utils::{OFFERED_HTLC_SCRIPT_WEIGHT, htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment};
use crate::routing::gossip::{NetworkGraph, NetworkUpdate};
-use crate::routing::router::{Path, PaymentParameters, Route, RouteHop, get_route};
+use crate::routing::router::{Path, PaymentParameters, Route, RouteHop, get_route, RouteParameters};
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
});
hops[1].fee_msat = chan_4.1.contents.fee_base_msat as u64 + chan_4.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
hops[0].fee_msat = chan_3.0.contents.fee_base_msat as u64 + chan_3.0.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
- let payment_preimage_1 = send_along_route(&nodes[1], Route { paths: vec![Path { hops, blinded_tail: None }], payment_params: None }, &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
+ let payment_preimage_1 = send_along_route(&nodes[1],
+ Route { paths: vec![Path { hops, blinded_tail: None }], route_params: None },
+ &vec!(&nodes[2], &nodes[3], &nodes[1])[..], 1000000).0;
let mut hops = Vec::with_capacity(3);
hops.push(RouteHop {
});
hops[1].fee_msat = chan_2.1.contents.fee_base_msat as u64 + chan_2.1.contents.fee_proportional_millionths as u64 * hops[2].fee_msat as u64 / 1000000;
hops[0].fee_msat = chan_3.1.contents.fee_base_msat as u64 + chan_3.1.contents.fee_proportional_millionths as u64 * hops[1].fee_msat as u64 / 1000000;
- let payment_hash_2 = send_along_route(&nodes[1], Route { paths: vec![Path { hops, blinded_tail: None }], payment_params: None }, &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
+ let payment_hash_2 = send_along_route(&nodes[1],
+ Route { paths: vec![Path { hops, blinded_tail: None }], route_params: None },
+ &vec!(&nodes[3], &nodes[2], &nodes[1])[..], 1000000).1;
// Claim the rebalances...
fail_payment(&nodes[1], &vec!(&nodes[3], &nodes[2], &nodes[1])[..], payment_hash_2);
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
- let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV).with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
- let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None, 10_000, nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(),
+ TEST_FINAL_CLTV).with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 10_000);
+ let route = get_route(&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph.read_only(),
+ None, nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
let (_, our_payment_hash, _) = get_payment_preimage_hash!(nodes[0]);
let our_payment_secret = nodes[1].node.create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200, None).unwrap();
nodes[0].node.send_payment_with_route(&route, our_payment_hash,
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), 50).with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
- let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None,
- 3_000_000, nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 3_000_000);
+ let route = get_route(&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph.read_only(), None,
+ nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
let payment_preimage = send_along_route(&nodes[0], route, &[&nodes[1]], 3_000_000).0;
let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), 50).with_bolt11_features(nodes[0].node.invoice_features()).unwrap();
- let route = get_route(&nodes[1].node.get_our_node_id(), &payment_params, &nodes[1].network_graph.read_only(), None,
- 3_000_000, nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 3_000_000);
+ let route = get_route(&nodes[1].node.get_our_node_id(), &route_params, &nodes[1].network_graph.read_only(), None,
+ nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
send_along_route(&nodes[1], route, &[&nodes[0]], 3_000_000);
let revoked_local_txn = get_local_commitment_txn!(nodes[1], chan.2);
mine_transaction(&nodes[1], &spend_tx);
}
+#[test]
+fn test_coinbase_funding_tx() {
+ // Miners are able to fund channels directly from coinbase transactions, however
+ // by consensus rules, outputs of a coinbase transaction are encumbered by a 100
+ // block maturity timelock. To ensure that a (non-0conf) channel like this is enforceable
+ // on-chain, the minimum depth is updated to 100 blocks for coinbase funding transactions.
+ //
+ // Note that 0conf channels with coinbase funding transactions are unaffected and are
+ // immediately operational after opening.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
+ let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
+ let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
+
+ // Create the coinbase funding transaction.
+ let (temporary_channel_id, tx, _) = create_coinbase_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42);
+
+ nodes[0].node.funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap();
+ check_added_monitors!(nodes[0], 0);
+ let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created);
+ check_added_monitors!(nodes[1], 1);
+ expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
+
+ let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed);
+ check_added_monitors!(nodes[0], 1);
+
+ expect_channel_pending_event(&nodes[0], &nodes[1].node.get_our_node_id());
+ assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+
+ // Starting at height 0, we "confirm" the coinbase at height 1.
+ confirm_transaction_at(&nodes[0], &tx, 1);
+ // We connect 98 more blocks to have 99 confirmations for the coinbase transaction.
+ connect_blocks(&nodes[0], COINBASE_MATURITY - 2);
+ // Check that we have no pending message events (we have not queued a `channel_ready` yet).
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ // Now connect one more block which results in 100 confirmations of the coinbase transaction.
+ connect_blocks(&nodes[0], 1);
+ // There should now be a `channel_ready` which can be handled.
+ let _ = &nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &get_event_msg!(&nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id()));
+
+ confirm_transaction_at(&nodes[1], &tx, 1);
+ connect_blocks(&nodes[1], COINBASE_MATURITY - 2);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ connect_blocks(&nodes[1], 1);
+ expect_channel_ready_event(&nodes[1], &nodes[0].node.get_our_node_id());
+ create_chan_between_nodes_with_value_confirm_second(&nodes[0], &nodes[1]);
+}
+
fn do_test_tx_confirmed_skipping_blocks_immediate_broadcast(test_height_before_timelock: bool) {
// In the first version of the chain::Confirm interface, after a refactor was made to not
// broadcast CSV-locked transactions until their CSV lock is up, we wouldn't reliably broadcast
use crate::onion_message;
use crate::prelude::*;
+use core::convert::TryFrom;
use core::fmt;
use core::fmt::Debug;
+use core::str::FromStr;
use crate::io::{self, Read};
use crate::io_extras::read_to_end;
use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
use crate::util::logger;
use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize};
+use crate::util::base32;
use crate::routing::gossip::{NodeAlias, NodeId};
}
}
+/// [`NetAddress`] error variants
+#[derive(Debug, Eq, PartialEq, Clone)]
+pub enum NetAddressParseError {
+ /// Socket address (IPv4/IPv6) parsing error
+ SocketAddrParse,
+ /// Invalid input format
+ InvalidInput,
+ /// Invalid port
+ InvalidPort,
+ /// Invalid onion v3 address
+ InvalidOnionV3,
+}
+
+impl fmt::Display for NetAddressParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ NetAddressParseError::SocketAddrParse => write!(f, "Socket address (IPv4/IPv6) parsing error"),
+ NetAddressParseError::InvalidInput => write!(f, "Invalid input format. \
+ Expected: \"<ipv4>:<port>\", \"[<ipv6>]:<port>\", \"<onion address>.onion:<port>\" or \"<hostname>:<port>\""),
+ NetAddressParseError::InvalidPort => write!(f, "Invalid port"),
+ NetAddressParseError::InvalidOnionV3 => write!(f, "Invalid onion v3 address"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddrV4> for NetAddress {
+ fn from(addr: std::net::SocketAddrV4) -> Self {
+ NetAddress::IPv4 { addr: addr.ip().octets(), port: addr.port() }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddrV6> for NetAddress {
+ fn from(addr: std::net::SocketAddrV6) -> Self {
+ NetAddress::IPv6 { addr: addr.ip().octets(), port: addr.port() }
+ }
+}
+
+#[cfg(feature = "std")]
+impl From<std::net::SocketAddr> for NetAddress {
+ fn from(addr: std::net::SocketAddr) -> Self {
+ match addr {
+ std::net::SocketAddr::V4(addr) => addr.into(),
+ std::net::SocketAddr::V6(addr) => addr.into(),
+ }
+ }
+}
+
+fn parse_onion_address(host: &str, port: u16) -> Result<NetAddress, NetAddressParseError> {
+ if host.ends_with(".onion") {
+ let domain = &host[..host.len() - ".onion".len()];
+ if domain.len() != 56 {
+ return Err(NetAddressParseError::InvalidOnionV3);
+ }
+ let onion = base32::Alphabet::RFC4648 { padding: false }.decode(&domain).map_err(|_| NetAddressParseError::InvalidOnionV3)?;
+ if onion.len() != 35 {
+ return Err(NetAddressParseError::InvalidOnionV3);
+ }
+ let version = onion[0];
+ let first_checksum_flag = onion[1];
+ let second_checksum_flag = onion[2];
+ let mut ed25519_pubkey = [0; 32];
+ ed25519_pubkey.copy_from_slice(&onion[3..35]);
+ let checksum = u16::from_be_bytes([first_checksum_flag, second_checksum_flag]);
+ return Ok(NetAddress::OnionV3 { ed25519_pubkey, checksum, version, port });
+
+ } else {
+ return Err(NetAddressParseError::InvalidInput);
+ }
+}
+
+#[cfg(feature = "std")]
+impl FromStr for NetAddress {
+ type Err = NetAddressParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match std::net::SocketAddr::from_str(s) {
+ Ok(addr) => Ok(addr.into()),
+ Err(_) => {
+ let trimmed_input = match s.rfind(":") {
+ Some(pos) => pos,
+ None => return Err(NetAddressParseError::InvalidInput),
+ };
+ let host = &s[..trimmed_input];
+ let port: u16 = s[trimmed_input + 1..].parse().map_err(|_| NetAddressParseError::InvalidPort)?;
+ if host.ends_with(".onion") {
+ return parse_onion_address(host, port);
+ };
+ if let Ok(hostname) = Hostname::try_from(s[..trimmed_input].to_string()) {
+ return Ok(NetAddress::Hostname { hostname, port });
+ };
+ return Err(NetAddressParseError::SocketAddrParse)
+ },
+ }
+ }
+}
+
/// Represents the set of gossip messages that require a signature from a node's identity key.
pub enum UnsignedGossipMessage<'a> {
/// An unsigned channel announcement.
/// Handle an incoming `channel_ready` message from the given peer.
fn handle_channel_ready(&self, their_node_id: &PublicKey, msg: &ChannelReady);
- // Channl close:
+ // Channel close:
/// Handle an incoming `shutdown` message from the given peer.
fn handle_shutdown(&self, their_node_id: &PublicKey, msg: &Shutdown);
/// Handle an incoming `closing_signed` message from the given peer.
#[cfg(test)]
mod tests {
+ use std::convert::TryFrom;
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::{Transaction, PackedLockTime, TxIn, Script, Sequence, Witness, TxOut};
use hex;
use crate::ln::ChannelId;
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket};
+ use crate::ln::msgs::NetAddress;
use crate::routing::gossip::{NodeAlias, NodeId};
use crate::util::ser::{Writeable, Readable, Hostname, TransactionU16LenLimited};
use crate::io::{self, Cursor};
use crate::prelude::*;
- use core::convert::TryFrom;
use core::str::FromStr;
-
use crate::chain::transaction::OutPoint;
+ #[cfg(feature = "std")]
+ use std::net::{Ipv4Addr, Ipv6Addr};
+ use crate::ln::msgs::NetAddressParseError;
+
#[test]
fn encoding_channel_reestablish() {
let public_key = {
};
let mut addresses = Vec::new();
if ipv4 {
- addresses.push(msgs::NetAddress::IPv4 {
+ addresses.push(NetAddress::IPv4 {
addr: [255, 254, 253, 252],
port: 9735
});
}
if ipv6 {
- addresses.push(msgs::NetAddress::IPv6 {
+ addresses.push(NetAddress::IPv6 {
addr: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240],
port: 9735
});
}
if onionv2 {
- addresses.push(msgs::NetAddress::OnionV2(
+ addresses.push(NetAddress::OnionV2(
[255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]
));
}
if onionv3 {
- addresses.push(msgs::NetAddress::OnionV3 {
+ addresses.push(NetAddress::OnionV3 {
ed25519_pubkey: [255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224],
checksum: 32,
version: 16,
});
}
if hostname {
- addresses.push(msgs::NetAddress::Hostname {
+ addresses.push(NetAddress::Hostname {
hostname: Hostname::try_from(String::from("host")).unwrap(),
port: 9735,
});
let shutdown = msgs::Shutdown {
channel_id: ChannelId::from_bytes([2; 32]),
scriptpubkey:
- if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey() }
+ if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey() }
else if script_type == 2 { Address::p2sh(&script, Network::Testnet).unwrap().script_pubkey() }
else if script_type == 3 { Address::p2wpkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).unwrap().script_pubkey() }
- else { Address::p2wsh(&script, Network::Testnet).script_pubkey() },
+ else { Address::p2wsh(&script, Network::Testnet).script_pubkey() },
};
let encoded_value = shutdown.encode();
let mut target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap();
}.encode(), hex::decode("00000000014001010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202").unwrap());
let init_msg = msgs::Init { features: InitFeatures::from_le_bytes(vec![]),
networks: Some(vec![mainnet_hash]),
- remote_network_address: Some(msgs::NetAddress::IPv4 {
+ remote_network_address: Some(NetAddress::IPv4 {
addr: [127, 0, 0, 1],
port: 1000,
}),
}
Ok(encoded_payload)
}
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn test_net_address_from_str() {
+ assert_eq!(NetAddress::IPv4 {
+ addr: Ipv4Addr::new(127, 0, 0, 1).octets(),
+ port: 1234,
+ }, NetAddress::from_str("127.0.0.1:1234").unwrap());
+
+ assert_eq!(NetAddress::IPv6 {
+ addr: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).octets(),
+ port: 1234,
+ }, NetAddress::from_str("[0:0:0:0:0:0:0:1]:1234").unwrap());
+ assert_eq!(
+ NetAddress::Hostname {
+ hostname: Hostname::try_from("lightning-node.mydomain.com".to_string()).unwrap(),
+ port: 1234,
+ }, NetAddress::from_str("lightning-node.mydomain.com:1234").unwrap());
+ assert_eq!(
+ NetAddress::Hostname {
+ hostname: Hostname::try_from("example.com".to_string()).unwrap(),
+ port: 1234,
+ }, NetAddress::from_str("example.com:1234").unwrap());
+ assert_eq!(NetAddress::OnionV3 {
+ ed25519_pubkey: [37, 24, 75, 5, 25, 73, 117, 194, 139, 102, 182, 107, 4, 105, 247, 246, 85,
+ 111, 177, 172, 49, 137, 167, 155, 64, 221, 163, 47, 31, 33, 71, 3],
+ checksum: 48326,
+ version: 121,
+ port: 1234
+ }, NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:1234").unwrap());
+ assert_eq!(Err(NetAddressParseError::InvalidOnionV3), NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6.onion:1234"));
+ assert_eq!(Err(NetAddressParseError::InvalidInput), NetAddress::from_str("127.0.0.1@1234"));
+ assert_eq!(Err(NetAddressParseError::InvalidInput), "".parse::<NetAddress>());
+ assert!(NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:9735:94").is_err());
+ assert!(NetAddress::from_str("wrong$%#.com:1234").is_err());
+ assert_eq!(Err(NetAddressParseError::InvalidPort), NetAddress::from_str("example.com:wrong"));
+ assert!("localhost".parse::<NetAddress>().is_err());
+ assert!("localhost:invalid-port".parse::<NetAddress>().is_err());
+ assert!( "invalid-onion-v3-hostname.onion:8080".parse::<NetAddress>().is_err());
+ assert!("b32.example.onion:invalid-port".parse::<NetAddress>().is_err());
+ assert!("invalid-address".parse::<NetAddress>().is_err());
+ assert!(NetAddress::from_str("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion.onion:1234").is_err());
+ }
}
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId, RecipientOnionFields};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
-use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
+use crate::routing::router::{get_route, PaymentParameters, Route, RouteParameters, RouteHint, RouteHintHop};
use crate::ln::features::{InitFeatures, Bolt11InvoiceFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, ChannelUpdate};
])]).unwrap();
let scorer = test_utils::TestScorer::new();
let network_graph = $nodes[0].network_graph.read_only();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, $amt);
(get_route(
- &$nodes[0].node.get_our_node_id(), &payment_params, &network_graph,
+ &$nodes[0].node.get_our_node_id(), &route_params, &network_graph,
Some(&$nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
- $amt, $nodes[0].logger, &scorer, &(), &[0u8; 32]
+ $nodes[0].logger, &scorer, &(), &[0u8; 32]
).unwrap(), phantom_route_hint.phantom_scid)
}
}}
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0 // We fill in the payloads manually instead of generating them from RouteHops.
},
], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
let onion_keys = super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()).unwrap();
}))
}
- let route = Route { paths: vec![path], payment_params: None };
+ let route = Route { paths: vec![path], route_params: None };
let onion_session_privs = self.add_new_pending_payment(payment_hash,
RecipientOnionFields::spontaneous_empty(), payment_id, None, &route, None, None,
entropy_source, best_block_height)?;
results,
payment_id,
failed_paths_retry: if pending_amt_unsent != 0 {
- if let Some(payment_params) = &route.payment_params {
+ if let Some(payment_params) = route.route_params.as_ref().map(|p| p.payment_params.clone()) {
Some(RouteParameters {
- payment_params: payment_params.clone(),
+ payment_params: payment_params,
final_value_msat: pending_amt_unsent,
})
} else { None }
PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()),
0
).with_expiry_time(past_expiry_time);
- let expired_route_params = RouteParameters {
- payment_params,
- final_value_msat: 0,
- };
+ let expired_route_params = RouteParameters::from_payment_params_and_value(payment_params, 0);
let pending_events = Mutex::new(VecDeque::new());
if on_retry {
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(),
- PaymentId([0; 32]), None, &Route { paths: vec![], payment_params: None },
+ PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None },
Some(Retry::Attempts(1)), Some(expired_route_params.payment_params.clone()),
&&keys_manager, 0).unwrap();
outbound_payments.retry_payment_internal(
let payment_params = PaymentParameters::from_node_id(
PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()), 0);
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: 0,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 0);
router.expect_find_route(route_params.clone(),
Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }));
let pending_events = Mutex::new(VecDeque::new());
if on_retry {
outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(),
- PaymentId([0; 32]), None, &Route { paths: vec![], payment_params: None },
+ PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None },
Some(Retry::Attempts(1)), Some(route_params.payment_params.clone()),
&&keys_manager, 0).unwrap();
outbound_payments.retry_payment_internal(
let sender_pk = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let receiver_pk = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
let payment_params = PaymentParameters::from_node_id(sender_pk, 0);
- let route_params = RouteParameters {
- payment_params: payment_params.clone(),
- final_value_msat: 0,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params.clone(), 0);
let failed_scid = 42;
let route = Route {
paths: vec![Path { hops: vec![RouteHop {
fee_msat: 0,
cltv_expiry_delta: 0,
}], blinded_tail: None }],
- payment_params: Some(payment_params),
+ route_params: Some(route_params.clone()),
};
router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut route_params_w_failed_scid = route_params.clone();
// Initiate the MPP payment.
let payment_id = PaymentId(payment_hash.0);
- let mut route_params = RouteParameters {
- payment_params: route.payment_params.clone().unwrap(),
- final_value_msat: amt_msat,
- };
+ let mut route_params = route.route_params.clone().unwrap();
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
}
let payer_pubkey = nodes[0].node.get_our_node_id();
let payee_pubkey = nodes[1].node.get_our_node_id();
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
- final_value_msat: 10000,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, false), 10000);
let network_graph = nodes[0].network_graph.clone();
let channels = nodes[0].node.list_usable_channels();
let payer_pubkey = nodes[0].node.get_our_node_id();
let payee_pubkey = nodes[3].node.get_our_node_id();
let recv_value = 15_000_000;
- let route_params = RouteParameters {
- payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, true),
- final_value_msat: recv_value,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::for_keysend(payee_pubkey, 40, true), recv_value);
let scorer = test_utils::TestScorer::new();
let random_seed_bytes = chanmon_cfgs[0].keys_manager.get_secure_random_bytes();
let route = find_route(&payer_pubkey, &route_params, &network_graph, None, nodes[0].logger,
let amt_msat = 1_000_000;
let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
let (payment_preimage_1, payment_hash_1, _, payment_id_1) = send_along_route(&nodes[0], route.clone(), &[&nodes[1], &nodes[2]], 1_000_000);
- let route_params = RouteParameters {
- payment_params: route.payment_params.clone().unwrap(),
- final_value_msat: amt_msat,
- };
+ let route_params = route.route_params.unwrap().clone();
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
check_added_monitors!(nodes[0], 1);
let scorer = test_utils::TestScorer::new();
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = get_route(
- &nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(),
- Some(&nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
- amt_msat, nodes[0].logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
+ let route = get_route( &nodes[0].node.get_our_node_id(), &route_params,
+ &nodes[0].network_graph.read_only(),
+ Some(&nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()), nodes[0].logger,
+ &scorer, &(), &random_seed_bytes).unwrap();
nodes[0].node.send_payment_with_route(&route, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42);
- let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], &payment_params, 9_998_000);
+ let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], payment_params, 9_998_000);
let (payment_hash, payment_id) = nodes[0].node.send_probe(route.paths[0].clone()).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 42);
// Send a dust HTLC, which will be treated as if it timed out once the channel hits the chain.
- let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], &payment_params, 1_000);
+ let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], payment_params, 1_000);
let (payment_hash, payment_id) = nodes[0].node.send_probe(route.paths[0].clone()).unwrap();
// node[0] -- update_add_htlcs -> node[1]
}])
]).unwrap()
.with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
- let route = get_route(
- &nodes[0].node.get_our_node_id(), &route_params.payment_params,
- &nodes[0].network_graph.read_only(), None, route_params.final_value_msat,
- nodes[0].logger, &scorer, &(), &random_seed_bytes,
- ).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat,);
+ let route = get_route( &nodes[0].node.get_our_node_id(), &route_params,
+ &nodes[0].network_graph.read_only(), None, nodes[0].logger, &scorer, &(),
+ &random_seed_bytes,).unwrap();
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
nodes[0].node.send_payment_with_route(&route, payment_hash,
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_route_hints(route_hints).unwrap()
.with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let (_, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
macro_rules! pass_failed_attempt_with_retry_along_path {
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
// Ensure the first monitor update (for the initial send path1 over chan_1) succeeds, but the
// second (for the initial send path2 over chan_2) fails.
cltv_expiry_delta: 100,
}], blinded_tail: None },
],
- payment_params: Some(route_params.payment_params.clone()),
+ route_params: Some(route_params.clone()),
};
let retry_1_route = Route {
paths: vec![
cltv_expiry_delta: 100,
}], blinded_tail: None },
],
- payment_params: Some(route_params.payment_params.clone()),
+ route_params: Some(route_params.clone()),
};
let retry_2_route = Route {
paths: vec![
cltv_expiry_delta: 100,
}], blinded_tail: None },
],
- payment_params: Some(route_params.payment_params.clone()),
+ route_params: Some(route_params.clone()),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(send_route));
let mut payment_params = route_params.payment_params.clone();
payment_params.previously_failed_channels.push(chan_2_id);
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params, final_value_msat: amt_msat / 2,
- }, Ok(retry_1_route));
+ nodes[0].router.expect_find_route(
+ RouteParameters::from_payment_params_and_value(payment_params, amt_msat / 2),
+ Ok(retry_1_route));
let mut payment_params = route_params.payment_params.clone();
payment_params.previously_failed_channels.push(chan_3_id);
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params, final_value_msat: amt_msat / 4,
- }, Ok(retry_2_route));
+ nodes[0].router.expect_find_route(
+ RouteParameters::from_payment_params_and_value(payment_params, amt_msat / 4),
+ Ok(retry_2_route));
// Send a payment that will partially fail on send, then partially fail on retry, then succeed.
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params: payment_params.clone(),
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), amt_msat);
let chans = nodes[0].node.list_usable_channels();
let mut route = Route {
cltv_expiry_delta: 100,
}], blinded_tail: None },
],
- payment_params: Some(payment_params),
+ route_params: Some(route_params.clone()),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
// On retry, split the payment across both channels.
route.paths[0].hops[0].fee_msat = 50_000_001;
route.paths[1].hops[0].fee_msat = 50_000_000;
- let mut pay_params = route.payment_params.clone().unwrap();
+ let mut pay_params = route.route_params.clone().unwrap().payment_params;
pay_params.previously_failed_channels.push(chans[1].short_channel_id.unwrap());
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params: pay_params,
- // Note that the second request here requests the amount we originally failed to send,
- // not the amount remaining on the full payment, which should be changed.
- final_value_msat: 100_000_001,
- }, Ok(route.clone()));
+ nodes[0].router.expect_find_route(
+ // Note that the second request here requests the amount we originally failed to send,
+ // not the amount remaining on the full payment, which should be changed.
+ RouteParameters::from_payment_params_and_value(pay_params, 100_000_001),
+ Ok(route.clone()));
{
let scorer = chanmon_cfgs[0].scorer.read().unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let chans = nodes[0].node.list_usable_channels();
let mut route = Route {
cltv_expiry_delta: 100,
}], blinded_tail: None },
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ route_params: Some(RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV),
+ 100_000_001)),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
// On retry, split the payment across both channels.
route.paths[1].hops[0].fee_msat = 50_000_001;
let mut pay_params = route_params.payment_params.clone();
pay_params.previously_failed_channels.push(chans[0].short_channel_id.unwrap());
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params: pay_params, final_value_msat: amt_msat,
- }, Ok(route.clone()));
+ nodes[0].router.expect_find_route(
+ RouteParameters::from_payment_params_and_value(pay_params, amt_msat),
+ Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let mut route = Route {
paths: vec![
cltv_expiry_delta: 100,
}], blinded_tail: None }
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ route_params: Some(RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV),
+ 100_000_000)),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut second_payment_params = route_params.payment_params.clone();
// On retry, we'll only return one path
route.paths.remove(1);
route.paths[0].hops[1].fee_msat = amt_msat;
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params: second_payment_params,
- final_value_msat: amt_msat,
- }, Ok(route.clone()));
+ nodes[0].router.expect_find_route(
+ RouteParameters::from_payment_params_and_value(second_payment_params, amt_msat),
+ Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let mut route = Route {
paths: vec![
cltv_expiry_delta: 100,
}], blinded_tail: None }
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ route_params: Some(RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV),
+ 100_000_000)),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut second_payment_params = route_params.payment_params.clone();
second_payment_params.previously_failed_channels = vec![chan_2_scid];
// On retry, we'll only be asked for one path (or 100k sats)
route.paths.remove(0);
- nodes[0].router.expect_find_route(RouteParameters {
- payment_params: second_payment_params,
- final_value_msat: amt_msat / 2,
- }, Ok(route.clone()));
+ nodes[0].router.expect_find_route(
+ RouteParameters::from_payment_params_and_value(second_payment_params, amt_msat / 2),
+ Ok(route.clone()));
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_expiry_time(payment_expiry_secs as u64)
.with_bolt11_features(invoice_features).unwrap();
- let mut route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
let mut route = Route {
paths: vec![
cltv_expiry_delta: 100,
}], blinded_tail: None }
],
- payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)),
+ route_params: Some(RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV),
+ amt_msat - amt_msat / 1000)),
};
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
create_announced_chan_between_nodes(&nodes, 2, 3);
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[3]);
- let mut route_params = RouteParameters {
- payment_params: PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
+ let mut route_params = RouteParameters::from_payment_params_and_value(
+ PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_bolt11_features(nodes[1].node.invoice_features()).unwrap(),
- final_value_msat: 10_000_000,
- };
+ 10_000_000);
let mut route = nodes[0].router.find_route(&nodes[0].node.get_our_node_id(), &route_params,
None, nodes[0].node.compute_inflight_htlcs()).unwrap();
// Make sure the route is ordered as the B->D path before C->D
// Initiate the payment
let payment_id = PaymentId(payment_hash.0);
- let mut route_params = RouteParameters {
- payment_params: route.payment_params.clone().unwrap(),
- final_value_msat: amt_msat,
- };
+ let mut route_params = route.route_params.clone().unwrap();
let custom_tlvs = vec![((1 << 16) + 1, vec![0x42u8; 16])];
let onion_fields = RecipientOnionFields::secret_only(payment_secret);
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
- let mut route_params = RouteParameters {
- payment_params,
- final_value_msat: amt_msat,
- };
+ let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat);
// Send the MPP payment, delivering the updated commitment state to nodes[1].
nodes[0].node.send_payment(payment_hash, RecipientOnionFields {
use crate::chain::transaction::OutPoint;
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use crate::ln::channelmanager::{self, PaymentSendFailure, PaymentId, RecipientOnionFields, ChannelShutdownState, ChannelDetails};
-use crate::routing::router::{PaymentParameters, get_route};
+use crate::routing::router::{PaymentParameters, get_route, RouteParameters};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, ErrorAction};
use crate::ln::script::ShutdownScript;
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[0]);
let payment_params_1 = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id(), TEST_FINAL_CLTV).with_bolt11_features(nodes[1].node.invoice_features()).unwrap();
- let route_1 = get_route(&nodes[0].node.get_our_node_id(), &payment_params_1, &nodes[0].network_graph.read_only(), None, 100000, &logger, &scorer, &(), &random_seed_bytes).unwrap();
- let payment_params_2 = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(), TEST_FINAL_CLTV).with_bolt11_features(nodes[0].node.invoice_features()).unwrap();
- let route_2 = get_route(&nodes[1].node.get_our_node_id(), &payment_params_2, &nodes[1].network_graph.read_only(), None, 100000, &logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params_1, 100_000);
+ let route_1 = get_route(&nodes[0].node.get_our_node_id(), &route_params,
+ &nodes[0].network_graph.read_only(), None, &logger, &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params_2 = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id(),
+ TEST_FINAL_CLTV).with_bolt11_features(nodes[0].node.invoice_features()).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params_2, 100_000);
+ let route_2 = get_route(&nodes[1].node.get_our_node_id(), &route_params,
+ &nodes[1].network_graph.read_only(), None, &logger, &scorer, &(), &random_seed_bytes).unwrap();
unwrap_send_err!(nodes[0].node.send_payment_with_route(&route_1, payment_hash,
RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)
), true, APIError::ChannelUnavailable {..}, {});
/// [`BlindedTail`]s are present, then the pubkey of the last [`RouteHop`] in each path must be
/// the same.
pub paths: Vec<Path>,
- /// The `payment_params` parameter passed via [`RouteParameters`] to [`find_route`].
+ /// The `route_params` parameter passed to [`find_route`].
///
/// This is used by `ChannelManager` to track information which may be required for retries.
- pub payment_params: Option<PaymentParameters>,
+ ///
+ /// Will be `None` for objects serialized with LDK versions prior to 0.0.117.
+ pub route_params: Option<RouteParameters>,
}
impl Route {
/// Returns the total amount of fees paid on this [`Route`].
///
- /// This doesn't include any extra payment made to the recipient, which can happen in excess of
- /// the amount passed to [`find_route`]'s `route_params.final_value_msat`.
+ /// For objects serialized with LDK 0.0.117 and after, this includes any extra payment made to
+ /// the recipient, which can happen in excess of the amount passed to [`find_route`] via
+ /// [`RouteParameters::final_value_msat`], if we had to reach the [`htlc_minimum_msat`] limits.
+ ///
+ /// [`htlc_minimum_msat`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
pub fn get_total_fees(&self) -> u64 {
- self.paths.iter().map(|path| path.fee_msat()).sum()
+ let overpaid_value_msat = self.route_params.as_ref()
+ .map_or(0, |p| self.get_total_amount().saturating_sub(p.final_value_msat));
+ overpaid_value_msat + self.paths.iter().map(|path| path.fee_msat()).sum::<u64>()
}
- /// Returns the total amount paid on this [`Route`], excluding the fees. Might be more than
- /// requested if we had to reach htlc_minimum_msat.
+ /// Returns the total amount paid on this [`Route`], excluding the fees.
+ ///
+ /// Might be more than requested as part of the given [`RouteParameters::final_value_msat`] if
+ /// we had to reach the [`htlc_minimum_msat`] limits.
+ ///
+ /// [`htlc_minimum_msat`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
pub fn get_total_amount(&self) -> u64 {
self.paths.iter().map(|path| path.final_value_msat()).sum()
}
}
}
write_tlv_fields!(writer, {
- (1, self.payment_params, option),
+ // For compatibility with LDK versions prior to 0.0.117, we take the individual
+ // RouteParameters' fields and reconstruct them on read.
+ (1, self.route_params.as_ref().map(|p| &p.payment_params), option),
(2, blinded_tails, optional_vec),
+ (3, self.route_params.as_ref().map(|p| p.final_value_msat), option),
});
Ok(())
}
_init_and_read_len_prefixed_tlv_fields!(reader, {
(1, payment_params, (option: ReadableArgs, min_final_cltv_expiry_delta)),
(2, blinded_tails, optional_vec),
+ (3, final_value_msat, option),
});
let blinded_tails = blinded_tails.unwrap_or(Vec::new());
if blinded_tails.len() != 0 {
path.blinded_tail = blinded_tail_opt;
}
}
- Ok(Route { paths, payment_params })
+
+ // If we previously wrote the corresponding fields, reconstruct RouteParameters.
+ let route_params = match (payment_params, final_value_msat) {
+ (Some(payment_params), Some(final_value_msat)) => {
+ Some(RouteParameters { payment_params, final_value_msat })
+ }
+ _ => None,
+ };
+
+ Ok(Route { paths, route_params })
}
}
/// Parameters needed to find a [`Route`].
///
/// Passed to [`find_route`] and [`build_route_from_hops`].
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RouteParameters {
/// The parameters of the failed payment path.
pub payment_params: PaymentParameters,
pub final_value_msat: u64,
}
+impl RouteParameters {
+ /// Constructs [`RouteParameters`] from the given [`PaymentParameters`] and a payment amount.
+ pub fn from_payment_params_and_value(payment_params: PaymentParameters, final_value_msat: u64) -> Self {
+ Self { payment_params, final_value_msat }
+ }
+}
+
impl Writeable for RouteParameters {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_tlv_fields!(writer, {
) -> Result<Route, LightningError>
where L::Target: Logger, GL::Target: Logger {
let graph_lock = network_graph.read_only();
- let mut route = get_route(our_node_pubkey, &route_params.payment_params, &graph_lock, first_hops,
- route_params.final_value_msat, logger, scorer, score_params,
- random_seed_bytes)?;
+ let mut route = get_route(our_node_pubkey, &route_params, &graph_lock, first_hops, logger,
+ scorer, score_params, random_seed_bytes)?;
add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
Ok(route)
}
pub(crate) fn get_route<L: Deref, S: ScoreLookUp>(
- our_node_pubkey: &PublicKey, payment_params: &PaymentParameters, network_graph: &ReadOnlyNetworkGraph,
- first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, logger: L, scorer: &S, score_params: &S::ScoreParams,
+ our_node_pubkey: &PublicKey, route_params: &RouteParameters, network_graph: &ReadOnlyNetworkGraph,
+ first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S, score_params: &S::ScoreParams,
_random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger {
+
+ let payment_params = &route_params.payment_params;
+ let final_value_msat = route_params.final_value_msat;
// If we're routing to a blinded recipient, we won't have their node id. Therefore, keep the
// unblinded payee id as an option. We also need a non-optional "payee id" for path construction,
// so use a dummy id for this in the blinded case.
}
}
- let route = Route { paths, payment_params: Some(payment_params.clone()) };
+ let route = Route { paths, route_params: Some(route_params.clone()) };
log_info!(logger, "Got route: {}", log_route!(route));
Ok(route)
}
) -> Result<Route, LightningError>
where L::Target: Logger, GL::Target: Logger {
let graph_lock = network_graph.read_only();
- let mut route = build_route_from_hops_internal(
- our_node_pubkey, hops, &route_params.payment_params, &graph_lock,
- route_params.final_value_msat, logger, random_seed_bytes)?;
+ let mut route = build_route_from_hops_internal(our_node_pubkey, hops, &route_params,
+ &graph_lock, logger, random_seed_bytes)?;
add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
Ok(route)
}
fn build_route_from_hops_internal<L: Deref>(
- our_node_pubkey: &PublicKey, hops: &[PublicKey], payment_params: &PaymentParameters,
- network_graph: &ReadOnlyNetworkGraph, final_value_msat: u64, logger: L,
- random_seed_bytes: &[u8; 32]
+ our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters,
+ network_graph: &ReadOnlyNetworkGraph, logger: L, random_seed_bytes: &[u8; 32],
) -> Result<Route, LightningError> where L::Target: Logger {
struct HopScorer {
let scorer = HopScorer { our_node_id, hop_ids };
- get_route(our_node_pubkey, payment_params, network_graph, None, final_value_msat,
- logger, &scorer, &(), random_seed_bytes)
+ get_route(our_node_pubkey, route_params, network_graph, None, logger, &scorer, &(), random_seed_bytes)
}
#[cfg(test)]
use crate::routing::utxo::UtxoResult;
use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
- DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE};
+ DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE, RouteParameters};
use crate::routing::scoring::{ChannelUsage, FixedPenaltyScorer, ScoreLookUp, ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters};
use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel};
use crate::chain::transaction::OutPoint;
// Simple route to 2 via 1
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 0, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Cannot send a payment of 0 msat");
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 0);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes) {
+ assert_eq!(err, "Cannot send a payment of 0 msat");
} else { panic!(); }
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
let our_chans = vec![get_channel_details(Some(2), our_id, InitFeatures::from_le_bytes(vec![0b11]), 100000)];
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) =
- get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "First hop cannot have our_node_pubkey as a destination.");
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()),
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "First hop cannot have our_node_pubkey as a destination.");
} else { panic!(); }
-
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
}
});
// Not possible to send 199_999_999, because the minimum on channel=2 is 200_000_000.
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 199_999_999, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a path to the given destination");
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 199_999_999);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// Lift the restriction on the first hop.
});
// A payment above the minimum should pass
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 199_999_999, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
}
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 60_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
// Overpay fees to hit htlc_minimum_msat.
let overpaid_fees = route.paths[0].hops[0].fee_msat + route.paths[1].hops[0].fee_msat;
// TODO: this could be better balanced to overpay 10k and not 15k.
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
// Fine to overpay for htlc_minimum_msat if it allows us to save fee.
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops[0].short_channel_id, 12);
let fees = route.paths[0].hops[0].fee_msat;
assert_eq!(fees, 5_000);
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 50_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
// Not fine to overpay for htlc_minimum_msat if it requires paying more than fee on
// the other channel.
assert_eq!(route.paths.len(), 1);
});
// If all the channels require some features we don't understand, route should fail
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a path to the given destination");
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// If we specify a channel to node7, that overrides our local channel view and that gets used
- let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
+ InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
add_or_update_node(&gossip_sync, &secp_ctx, &privkeys[7], unknown_features.clone(), 1);
// If all nodes require some features we don't understand, route should fail
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a path to the given destination");
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// If we specify a channel to node7, that overrides our local channel view and that gets used
- let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
+ InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
// Route to 1 via 2 and 3 because our channel to 1 is disabled
let payment_params = PaymentParameters::from_node_id(nodes[0], 42);
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 3);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
// If we specify a channel to node7, that overrides our local channel view and that gets used
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
- let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(),
+ InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[7]);
let mut invalid_last_hops = last_hops_multi_private_channels(&nodes);
invalid_last_hops.push(invalid_last_hop);
{
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(invalid_last_hops).unwrap();
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Route hint cannot have the payee as the source.");
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(invalid_last_hops).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
+ &route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes) {
+ assert_eq!(err, "Route hint cannot have the payee as the source.");
} else { panic!(); }
}
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Test handling of an empty RouteHint passed in Invoice.
-
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &[42u8; 32]).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &[42u8; 32]).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
// This test shows that public routes can be present in the invoice
// which would be handled in the same manner.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
// Simple test with outbound channel to 4 to test that last_hops and first_hops connect
let our_chans = vec![get_channel_details(Some(42), nodes[3].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
let mut last_hops = last_hops(&nodes);
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops.clone()).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(last_hops.clone()).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 2);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[3]);
last_hops[0].0[0].fees.base_msat = 1000;
// Revert to via 6 as the fee on 8 goes up
- let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
+ .with_route_hints(last_hops).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 4);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
// ...but still use 8 for larger payments as 6 has a variable feerate
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 2000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 2000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].hops.len(), 5);
assert_eq!(route.paths[0].hops[0].pubkey, nodes[1]);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let logger = ln_test_utils::TestLogger::new();
let network_graph = NetworkGraph::new(Network::Testnet, &logger);
- let route = get_route(&source_node_id, &payment_params, &network_graph.read_only(),
- Some(&our_chans.iter().collect::<Vec<_>>()), route_val, &logger, &scorer, &(), &random_seed_bytes);
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, route_val);
+ let route = get_route(&source_node_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), &logger, &scorer, &(),
+ &random_seed_bytes);
route
}
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 250_000_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 250_000_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 250_000_000, Arc::clone(&logger), &scorer, &(),&random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 250_000_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(),&random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 200_000_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer,
+ &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 200_000_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 15_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 15_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 15_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 10_001);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 10_001, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 10_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 10_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.hops.len(), 2);
});
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 60_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 60_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 49 sats (just a bit below the capacity).
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 49_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 49_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route an exact amount is also fine
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 50_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
});
{
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 50_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 300_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 300_000,
+ &our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
{
// Attempt to route while setting max_path_count to 0 results in a failure.
let zero_payment_params = payment_params.clone().with_max_path_count(0);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ zero_payment_params, 100);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &zero_payment_params, &network_graph.read_only(), None, 100,
+ &our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
assert_eq!(err, "Can't find a route with no paths allowed.");
} else { panic!(); }
// This is the case because the minimal_value_contribution_msat would require each path
// to account for 1/3 of the total value, which is violated by 2 out of 3 paths.
let fail_payment_params = payment_params.clone().with_max_path_count(3);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ fail_payment_params, 250_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &fail_payment_params, &network_graph.read_only(), None, 250_000,
+ &our_id, &route_params, &network_graph.read_only(), None,
Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
{
// Now, attempt to route 250 sats (just a bit below the capacity).
// Our algorithm should provide us with these 3 paths.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
- 250_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 250_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route an exact amount is also fine
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
- 290_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 290_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let config = UserConfig::default();
- let payment_params = PaymentParameters::from_node_id(nodes[3], 42).with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+ let payment_params = PaymentParameters::from_node_id(nodes[3], 42)
+ .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
// We need a route consisting of 3 paths:
// From our node to node3 via {node0, node2}, {node7, node2, node4} and {node7, node2}.
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 350_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 350_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 300 sats (exact amount we can route).
// Our algorithm should provide us with these 3 paths, 100 sats each.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 300_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 300_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
{
// Now, attempt to route 180 sats.
// Our algorithm should provide us with these 2 paths.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 180_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 180_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_value_transferred_msat = 0;
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 210_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 210_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 200 sats (exact amount we can route).
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 200_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 200_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
// Get a route for 100 sats and check that we found the MPP route no problem and didn't
// overpay at all.
- let mut route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100_000);
+ let mut route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
route.paths.sort_by_key(|path| path.hops[0].short_channel_id);
// Paths are manually ordered ordered by SCID, so:
{
// Attempt to route more than available results in a failure.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 150_000);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 150_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
- assert_eq!(err, "Failed to find a sufficient route to the given destination");
+ &our_id, &route_params, &network_graph.read_only(), None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 125 sats (just a bit below the capacity of 3 channels).
// Our algorithm should provide us with these 3 paths.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 125_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 125_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route without the last small cheap channel
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 90_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Now ensure the route flows simply over nodes 1 and 4 to 6.
- let route = get_route(&our_id, &payment_params, &network.read_only(), None, 10_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 10_000);
+ let route = get_route(&our_id, &route_params, &network.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 3);
{
// Now, attempt to route 90 sats, which is exactly 90 sats at the last hop, plus the
// 200% fee charged channel 13 in the 1-to-2 direction.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 90_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
// Now, attempt to route 90 sats, hitting the htlc_minimum on channel 4, but
// overshooting the htlc_maximum on channel 2. Thus, we should pick the (absurdly
// expensive) channels 12-13 path.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 90_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
{
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 200_000),
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 10_000),
- ]), 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ ]), Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[0].hops[0].fee_msat, 100_000);
}
{
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000),
- ]), 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ ]), Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert_eq!(route.paths[0].hops.len(), 1);
assert_eq!(route.paths[1].hops.len(), 1);
// If we have several options above the 3xpayment value threshold, we should pick the
// smallest of them, avoiding further fragmenting our available outbound balance to
// this node.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(2), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(3), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(5), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(8), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(9), nodes[0], channelmanager::provided_init_features(&config), 50_000),
&get_channel_details(Some(4), nodes[0], channelmanager::provided_init_features(&config), 1_000_000),
- ]), 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ ]), Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 1);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let route = get_route( &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 100);
// Applying a 100 msat penalty to each hop results in taking channels 7 and 10 to nodes[6]
// from nodes[2] rather than channel 6, 11, and 8, even though the longer path is cheaper.
let scorer = FixedPenaltyScorer::with_penalty(100);
- let route = get_route(
- &our_id, &payment_params, &network_graph.read_only(), None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100);
+ let route = get_route( &our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 300);
let scorer = ln_test_utils::TestScorer::new();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = get_route(
- &our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100);
+ let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 100);
// A different path to nodes[6] exists if channel 6 cannot be routed over.
let scorer = BadChannelScorer { short_channel_id: 6 };
- let route = get_route(
- &our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ).unwrap();
+ let route = get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_eq!(route.get_total_fees(), 300);
// A path to nodes[6] does not exist if nodes[2] cannot be routed through.
let scorer = BadNodeScorer { node_id: NodeId::from_pubkey(&nodes[2]) };
- match get_route(
- &our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes
- ) {
- Err(LightningError { err, .. } ) => {
- assert_eq!(err, "Failed to find a path to the given destination");
- },
- Ok(_) => panic!("Expected error"),
+ match get_route( &our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes) {
+ Err(LightningError { err, .. } ) => {
+ assert_eq!(err, "Failed to find a path to the given destination");
+ },
+ Ok(_) => panic!("Expected error"),
}
}
short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0
},
], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
assert_eq!(route.get_total_fees(), 250);
short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
},
], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
assert_eq!(route.get_total_fees(), 200);
// In an earlier version of `Route::get_total_fees` and `Route::get_total_amount`, they
// would both panic if the route was completely empty. We test to ensure they return 0
// here, even though its somewhat nonsensical as a route.
- let route = Route { paths: Vec::new(), payment_params: None };
+ let route = Route { paths: Vec::new(), route_params: None };
assert_eq!(route.get_total_fees(), 0);
assert_eq!(route.get_total_amount(), 0);
.with_max_total_cltv_expiry_delta(feasible_max_total_cltv_delta);
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ feasible_payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_ne!(path.len(), 0);
let fail_max_total_cltv_delta = 23;
let fail_payment_params = PaymentParameters::from_node_id(nodes[6], 0).with_route_hints(last_hops(&nodes)).unwrap()
.with_max_total_cltv_expiry_delta(fail_max_total_cltv_delta);
- match get_route(&our_id, &fail_payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(
+ fail_payment_params, 100);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &(), &random_seed_bytes)
{
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
// We should be able to find a route initially, and then after we fail a few random
// channels eventually we won't be able to any longer.
- assert!(get_route(&our_id, &payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).is_ok());
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ assert!(get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).is_ok());
loop {
- if let Ok(route) = get_route(&our_id, &payment_params, &network_graph, None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes) {
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ if let Ok(route) = get_route(&our_id, &route_params, &network_graph, None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes)
+ {
for chan in route.paths[0].hops.iter() {
assert!(!payment_params.previously_failed_channels.contains(&chan.short_channel_id));
}
// First check we can actually create a long route on this graph.
let feasible_payment_params = PaymentParameters::from_node_id(nodes[18], 0);
- let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ feasible_payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).unwrap();
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert!(path.len() == MAX_PATH_LENGTH_ESTIMATE.into());
// But we can't create a path surpassing the MAX_PATH_LENGTH_ESTIMATE limit.
let fail_payment_params = PaymentParameters::from_node_id(nodes[19], 0);
- match get_route(&our_id, &fail_payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(
+ fail_payment_params, 100);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &(), &random_seed_bytes)
{
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
let payment_params = PaymentParameters::from_node_id(nodes[6], 42).with_route_hints(last_hops(&nodes)).unwrap();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let cltv_expiry_deltas_before = route.paths[0].hops.iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
let keys_manager = ln_test_utils::TestKeysInterface::new(&[4u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
- let mut route = get_route(&our_id, &payment_params, &network_graph, None, 100,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), 100);
+ let mut route = get_route(&our_id, &route_params, &network_graph, None,
+ Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
add_random_cltv_offset(&mut route, &payment_params, &network_graph, &random_seed_bytes);
let mut path_plausibility = vec![];
let payment_params = PaymentParameters::from_node_id(nodes[3], 0);
let hops = [nodes[1], nodes[2], nodes[4], nodes[3]];
- let route = build_route_from_hops_internal(&our_id, &hops, &payment_params,
- &network_graph, 100, Arc::clone(&logger), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
+ let route = build_route_from_hops_internal(&our_id, &hops, &route_params, &network_graph,
+ Arc::clone(&logger), &random_seed_bytes).unwrap();
let route_hop_pubkeys = route.paths[0].hops.iter().map(|hop| hop.pubkey).collect::<Vec<_>>();
assert_eq!(hops.len(), route.paths[0].hops.len());
for (idx, hop_pubkey) in hops.iter().enumerate() {
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
// 100,000 sats is less than the available liquidity on each channel, set above.
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000_000, Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100_000_000);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!((route.paths[0].hops[1].short_channel_id == 4 && route.paths[1].hops[1].short_channel_id == 13) ||
(route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13));
// Then check we can get a normal route
let payment_params = PaymentParameters::from_node_id(nodes[10], 42);
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 100);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
assert!(route.is_ok());
// Then check that we can't get a route if we ban an intermediate node.
scorer_params.add_banned(&NodeId::from_pubkey(&nodes[3]));
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
assert!(route.is_err());
// Finally make sure we can route again, when we remove the ban.
scorer_params.remove_banned(&NodeId::from_pubkey(&nodes[3]));
- let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, Arc::clone(&logger), &scorer, &scorer_params,&random_seed_bytes);
+ let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
+ Arc::clone(&logger), &scorer, &scorer_params, &random_seed_bytes);
assert!(route.is_ok());
}
// Make sure we'll error if our route hints don't have enough liquidity according to their
// htlc_maximum_msat.
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, max_htlc_msat + 1);
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
- &payment_params, &netgraph, None, max_htlc_msat + 1, Arc::clone(&logger), &scorer, &(),
+ &route_params, &netgraph, None, Arc::clone(&logger), &scorer, &(),
&random_seed_bytes)
{
assert_eq!(err, "Failed to find a sufficient route to the given destination");
let payment_params = PaymentParameters::from_node_id(dest_node_id, 42)
.with_route_hints(vec![route_hint_1, route_hint_2]).unwrap()
.with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
- let route = get_route(&our_id, &payment_params, &netgraph, None, max_htlc_msat + 1,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, max_htlc_msat + 1);
+ let route = get_route(&our_id, &route_params, &netgraph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
.with_route_hints(vec![route_hint_1, route_hint_2]).unwrap()
.with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
- let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
- Some(&first_hop.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+ let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
+ Some(&first_hop.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
&random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
get_channel_details(Some(42), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10),
get_channel_details(Some(43), intermed_node_id, InitFeatures::from_le_bytes(vec![0b11]), amt_msat - 10),
];
- let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
- Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
+ let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
&random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
(blinded_payinfo.clone(), blinded_path.clone()),
(blinded_payinfo.clone(), blinded_path.clone())])
.with_bolt12_features(bolt12_features).unwrap();
- let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
- Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+ let route = get_route(&our_node_id, &route_params, &network_graph.read_only(),
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
&random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
fee_msat: 100,
cltv_expiry_delta: 0,
}], blinded_tail: None }],
- payment_params: None,
+ route_params: None,
};
let encoded_route = route.encode();
let decoded_route: Route = Readable::read(&mut Cursor::new(&encoded_route[..])).unwrap();
excess_final_cltv_expiry_delta: 0,
final_value_msat: 200,
}),
- }], payment_params: None};
+ }], route_params: None};
let payment_params = PaymentParameters::from_node_id(ln_test_utils::pubkey(47), 18);
let (_, network_graph, _, _, _) = build_line_graph();
features: BlindedHopFeatures::empty(),
};
- let final_amt_msat = 1001;
let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())]);
- let route = get_route(&our_id, &payment_params, &network_graph, None, final_amt_msat , Arc::clone(&logger),
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, 1001);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].hops.len(), 2);
let payment_params = PaymentParameters::blinded(vec![
(blinded_payinfo.clone(), invalid_blinded_path.clone()),
(blinded_payinfo.clone(), invalid_blinded_path_2)]);
- match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
&scorer, &(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
invalid_blinded_path.introduction_node_id = our_id;
let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]);
- match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
- &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "Cannot generate a route to blinded paths if we are the introduction node to all of them");
invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46);
invalid_blinded_path.blinded_hops.clear();
let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]);
- match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
- &scorer, &(), &random_seed_bytes)
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001);
+ match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,
+ &(), &random_seed_bytes)
{
Err(LightningError { err, .. }) => {
assert_eq!(err, "0-hop blinded path provided");
let payment_params = PaymentParameters::blinded(blinded_hints.clone())
.with_bolt12_features(bolt12_features.clone()).unwrap();
- let route = get_route(&our_id, &payment_params, &network_graph, None,
- 100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100_000);
+ let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
+ &scorer, &(), &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in route.paths.into_iter() {
let payment_params = PaymentParameters::blinded(blinded_hints.clone());
let netgraph = network_graph.read_only();
- if let Err(LightningError { err, .. }) = get_route(&nodes[0], &payment_params, &netgraph,
- Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params.clone(), amt_msat);
+ if let Err(LightningError { err, .. }) = get_route(&nodes[0], &route_params, &netgraph,
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
&random_seed_bytes) {
- assert_eq!(err, "Failed to find a path to the given destination");
+ assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!("Expected error") }
// Sending an exact amount accounting for the blinded path fee works.
let amt_minus_blinded_path_fee = amt_msat - blinded_payinfo.fee_base_msat as u64;
- let route = get_route(&nodes[0], &payment_params, &netgraph,
- Some(&first_hops.iter().collect::<Vec<_>>()), amt_minus_blinded_path_fee,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_minus_blinded_path_fee);
+ let route = get_route(&nodes[0], &route_params, &netgraph,
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
assert_eq!(route.get_total_amount(), amt_minus_blinded_path_fee);
}
.with_bolt12_features(bolt12_features.clone()).unwrap();
let netgraph = network_graph.read_only();
- let route = get_route(&nodes[0], &payment_params, &netgraph,
- Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat,
- Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+ let route_params = RouteParameters::from_payment_params_and_value(
+ payment_params, amt_msat);
+ let route = get_route(&nodes[0], &route_params, &netgraph,
+ Some(&first_hops.iter().collect::<Vec<_>>()), Arc::clone(&logger), &scorer, &(),
+ &random_seed_bytes).unwrap();
assert_eq!(route.get_total_fees(), blinded_payinfo.fee_base_msat as u64);
assert_eq!(route.get_total_amount(), amt_msat);
}
let params = PaymentParameters::from_node_id(dst, 42)
.with_bolt11_features(features.clone()).unwrap();
let first_hop = first_hop(src);
- let amt = starting_amount + seed % 1_000_000;
+ let amt_msat = starting_amount + seed % 1_000_000;
+ let route_params = RouteParameters::from_payment_params_and_value(
+ params.clone(), amt_msat);
let path_exists =
- get_route(&payer, ¶ms, &graph.read_only(), Some(&[&first_hop]),
- amt, &TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok();
+ get_route(&payer, &route_params, &graph.read_only(), Some(&[&first_hop]),
+ &TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok();
if path_exists {
// ...and seed the scorer with success and failure data...
seed = seed.overflowing_mul(6364136223846793005).0.overflowing_add(1).0;
let mpp_features = channelmanager::provided_invoice_features(&UserConfig::default());
let params = PaymentParameters::from_node_id(dst, 42)
.with_bolt11_features(mpp_features).unwrap();
-
- let route_res = get_route(&payer, ¶ms, &graph.read_only(),
- Some(&[&first_hop]), score_amt, &TestLogger::new(), scorer,
- score_params, &random_seed_bytes);
+ let route_params = RouteParameters::from_payment_params_and_value(
+ params.clone(), score_amt);
+ let route_res = get_route(&payer, &route_params, &graph.read_only(),
+ Some(&[&first_hop]), &TestLogger::new(), scorer, score_params,
+ &random_seed_bytes);
if let Ok(route) = route_res {
for path in route.paths {
if seed & 0x80 == 0 {
score_amt /= 100;
}
- route_endpoints.push((first_hop, params, amt));
+ route_endpoints.push((first_hop, params, amt_msat));
break;
}
}
// Because we've changed channel scores, it's possible we'll take different routes to the
// selected destinations, possibly causing us to fail because, eg, the newly-selected path
// requires a too-high CLTV delta.
- route_endpoints.retain(|(first_hop, params, amt)| {
- get_route(&payer, params, &graph.read_only(), Some(&[first_hop]), *amt,
+ route_endpoints.retain(|(first_hop, params, amt_msat)| {
+ let route_params = RouteParameters::from_payment_params_and_value(
+ params.clone(), *amt_msat);
+ get_route(&payer, &route_params, &graph.read_only(), Some(&[first_hop]),
&TestLogger::new(), scorer, score_params, &random_seed_bytes).is_ok()
});
route_endpoints.truncate(route_count);
let mut idx = 0;
bench.bench_function(bench_name, |b| b.iter(|| {
let (first_hop, params, amt) = &route_endpoints[idx % route_endpoints.len()];
- assert!(get_route(&payer, params, &graph.read_only(), Some(&[first_hop]), *amt,
+ let route_params = RouteParameters::from_payment_params_and_value(params.clone(), *amt);
+ assert!(get_route(&payer, &route_params, &graph.read_only(), Some(&[first_hop]),
&DummyLogger{}, &scorer, score_params, &random_seed_bytes).is_ok());
idx += 1;
}));
--- /dev/null
+// This is a modification of base32 encoding to support the zbase32 alphabet.
+// The original piece of software can be found at https://crates.io/crates/base32(v0.4.0)
+// The original portions of this software are Copyright (c) 2015 The base32 Developers
+
+// This file is licensed under either of
+// Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or
+// MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option.
+
+
+use crate::prelude::*;
+
+/// RFC4648 encoding table
+const RFC4648_ALPHABET: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+/// Zbase encoding alphabet
+const ZBASE_ALPHABET: &'static [u8] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
+
+/// RFC4648 decoding table
+const RFC4648_INV_ALPHABET: [i8; 43] = [
+ -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+];
+
+/// Zbase decoding table
+const ZBASE_INV_ALPHABET: [i8; 43] = [
+ -1, 18, -1, 25, 26, 27, 30, 29, 7, 31, -1, -1, -1, -1, -1, -1, -1, 24, 1, 12, 3, 8, 5, 6, 28,
+ 21, 9, 10, -1, 11, 2, 16, 13, 14, 4, 22, 17, 19, -1, 20, 15, 0, 23,
+];
+
+/// Alphabet used for encoding and decoding.
+#[derive(Copy, Clone)]
+pub enum Alphabet {
+ /// RFC4648 encoding.
+ RFC4648 {
+ /// Whether to use padding.
+ padding: bool
+ },
+ /// Zbase32 encoding.
+ ZBase32
+}
+
+impl Alphabet {
+ /// Encode bytes into a base32 string.
+ pub fn encode(&self, data: &[u8]) -> String {
+ // output_length is calculated as follows:
+ // / 5 divides the data length by the number of bits per chunk (5),
+ // * 8 multiplies the result by the number of characters per chunk (8).
+ // + 4 rounds up to the nearest character.
+ let output_length = (data.len() * 8 + 4) / 5;
+ let mut ret = match self {
+ Self::RFC4648 { padding } => {
+ let mut ret = Self::encode_data(data, RFC4648_ALPHABET);
+ if *padding {
+ let len = ret.len();
+ for i in output_length..len {
+ ret[i] = b'=';
+ }
+
+ return String::from_utf8(ret).expect("Invalid UTF-8");
+ }
+ ret
+ },
+ Self::ZBase32 => {
+ Self::encode_data(data, ZBASE_ALPHABET)
+ },
+ };
+ ret.truncate(output_length);
+
+ #[cfg(fuzzing)]
+ assert_eq!(ret.capacity(), (data.len() + 4) / 5 * 8);
+
+ String::from_utf8(ret).expect("Invalid UTF-8")
+ }
+
+ /// Decode a base32 string into a byte vector.
+ pub fn decode(&self, data: &str) -> Result<Vec<u8>, ()> {
+ let data = data.as_bytes();
+ let (data, alphabet) = match self {
+ Self::RFC4648 { padding } => {
+ let mut unpadded_data_length = data.len();
+ if *padding {
+ if data.len() % 8 != 0 { return Err(()); }
+ data.iter().rev().take(6).for_each(|&c| {
+ if c == b'=' {
+ unpadded_data_length -= 1;
+ }
+ });
+ }
+ (&data[..unpadded_data_length], RFC4648_INV_ALPHABET)
+ },
+ Self::ZBase32 => {
+ (data, ZBASE_INV_ALPHABET)
+ }
+ };
+ // If the string has more characters than are required to alphabet_encode the number of bytes
+ // decodable, treat the string as invalid.
+ match data.len() % 8 { 1|3|6 => return Err(()), _ => {} }
+ Ok(Self::decode_data(data, alphabet)?)
+ }
+
+ /// Encode a byte slice into a base32 string.
+ fn encode_data(data: &[u8], alphabet: &'static [u8]) -> Vec<u8> {
+ // cap is calculated as follows:
+ // / 5 divides the data length by the number of bits per chunk (5),
+ // * 8 multiplies the result by the number of characters per chunk (8).
+ // + 4 rounds up to the nearest character.
+ let cap = (data.len() + 4) / 5 * 8;
+ let mut ret = Vec::with_capacity(cap);
+ for chunk in data.chunks(5) {
+ let mut buf = [0u8; 5];
+ for (i, &b) in chunk.iter().enumerate() {
+ buf[i] = b;
+ }
+ ret.push(alphabet[((buf[0] & 0xF8) >> 3) as usize]);
+ ret.push(alphabet[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize]);
+ ret.push(alphabet[((buf[1] & 0x3E) >> 1) as usize]);
+ ret.push(alphabet[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize]);
+ ret.push(alphabet[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize]);
+ ret.push(alphabet[((buf[3] & 0x7C) >> 2) as usize]);
+ ret.push(alphabet[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize]);
+ ret.push(alphabet[(buf[4] & 0x1F) as usize]);
+ }
+ #[cfg(fuzzing)]
+ assert_eq!(ret.capacity(), cap);
+
+ ret
+ }
+
+ fn decode_data(data: &[u8], alphabet: [i8; 43]) -> Result<Vec<u8>, ()> {
+ // cap is calculated as follows:
+ // / 8 divides the data length by the number of characters per chunk (8),
+ // * 5 multiplies the result by the number of bits per chunk (5),
+ // + 7 rounds up to the nearest byte.
+ let cap = (data.len() + 7) / 8 * 5;
+ let mut ret = Vec::with_capacity(cap);
+ for chunk in data.chunks(8) {
+ let mut buf = [0u8; 8];
+ for (i, &c) in chunk.iter().enumerate() {
+ match alphabet.get(c.to_ascii_uppercase().wrapping_sub(b'0') as usize) {
+ Some(&-1) | None => return Err(()),
+ Some(&value) => buf[i] = value as u8,
+ };
+ }
+ ret.push((buf[0] << 3) | (buf[1] >> 2));
+ ret.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4));
+ ret.push((buf[3] << 4) | (buf[4] >> 1));
+ ret.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3));
+ ret.push((buf[6] << 5) | buf[7]);
+ }
+ let output_length = data.len() * 5 / 8;
+ for c in ret.drain(output_length..) {
+ if c != 0 {
+ // If the original string had any bits set at positions outside of the encoded data,
+ // treat the string as invalid.
+ return Err(());
+ }
+ }
+
+ // Check that our capacity calculation doesn't under-shoot in fuzzing
+ #[cfg(fuzzing)]
+ assert_eq!(ret.capacity(), cap);
+ Ok(ret)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const ZBASE32_TEST_DATA: &[(&str, &[u8])] = &[
+ ("", &[]),
+ ("yy", &[0x00]),
+ ("oy", &[0x80]),
+ ("tqrey", &[0x8b, 0x88, 0x80]),
+ ("6n9hq", &[0xf0, 0xbf, 0xc7]),
+ ("4t7ye", &[0xd4, 0x7a, 0x04]),
+ ("6im5sdy", &[0xf5, 0x57, 0xbb, 0x0c]),
+ ("ybndrfg8ejkmcpqxot1uwisza345h769", &[0x00, 0x44, 0x32, 0x14, 0xc7, 0x42, 0x54, 0xb6,
+ 0x35, 0xcf, 0x84, 0x65, 0x3a, 0x56, 0xd7, 0xc6,
+ 0x75, 0xbe, 0x77, 0xdf])
+ ];
+
+ #[test]
+ fn test_zbase32_encode() {
+ for &(zbase32, data) in ZBASE32_TEST_DATA {
+ assert_eq!(Alphabet::ZBase32.encode(data), zbase32);
+ }
+ }
+
+ #[test]
+ fn test_zbase32_decode() {
+ for &(zbase32, data) in ZBASE32_TEST_DATA {
+ assert_eq!(Alphabet::ZBase32.decode(zbase32).unwrap(), data);
+ }
+ }
+
+ #[test]
+ fn test_decode_wrong() {
+ const WRONG_DATA: &[&str] = &["00", "l1", "?", "="];
+ for &data in WRONG_DATA {
+ match Alphabet::ZBase32.decode(data) {
+ Ok(_) => assert!(false, "Data shouldn't be decodable"),
+ Err(_) => assert!(true),
+ }
+ }
+ }
+
+ const RFC4648_NON_PADDED_TEST_VECTORS: &[(&[u8], &[u8])] = &[
+ (&[0xF8, 0x3E, 0x7F, 0x83, 0xE7], b"7A7H7A7H"),
+ (&[0x77, 0xC1, 0xF7, 0x7C, 0x1F], b"O7A7O7A7"),
+ (&[0xF8, 0x3E, 0x7F, 0x83, 0xE7], b"7A7H7A7H"),
+ (&[0x77, 0xC1, 0xF7, 0x7C, 0x1F], b"O7A7O7A7"),
+ ];
+
+ const RFC4648_TEST_VECTORS: &[(&[u8], &str)] = &[
+ (b"", ""),
+ (b"f", "MY======"),
+ (b"fo", "MZXQ===="),
+ (b"foo", "MZXW6==="),
+ (b"foob", "MZXW6YQ="),
+ (b"fooba", "MZXW6YTB"),
+ (b"foobar", "MZXW6YTBOI======"),
+ (&[0xF8, 0x3E, 0x7F, 0x83], "7A7H7AY="),
+ ];
+
+ #[test]
+ fn test_rfc4648_encode() {
+ for (input, encoded) in RFC4648_TEST_VECTORS {
+ assert_eq!(&Alphabet::RFC4648 { padding: true }.encode(input), encoded);
+ }
+
+ for (input, encoded) in RFC4648_NON_PADDED_TEST_VECTORS {
+ assert_eq!(&Alphabet::RFC4648 { padding: false }.encode(input).as_bytes(), encoded);
+ }
+ }
+
+ #[test]
+ fn test_rfc4648_decode() {
+ for (input, encoded) in RFC4648_TEST_VECTORS {
+ let res = &Alphabet::RFC4648 { padding: true }.decode(encoded).unwrap();
+ assert_eq!(&res[..], &input[..]);
+ }
+
+ for (input, encoded) in RFC4648_NON_PADDED_TEST_VECTORS {
+ let res = &Alphabet::RFC4648 { padding: false }.decode(std::str::from_utf8(encoded).unwrap()).unwrap();
+ assert_eq!(&res[..], &input[..]);
+ }
+ }
+
+ #[test]
+ fn padding() {
+ let num_padding = [0, 6, 4, 3, 1];
+ for i in 1..6 {
+ let encoded = Alphabet::RFC4648 { padding: true }.encode(
+ (0..(i as u8)).collect::<Vec<u8>>().as_ref()
+ );
+ assert_eq!(encoded.len(), 8);
+ for j in 0..(num_padding[i % 5]) {
+ assert_eq!(encoded.as_bytes()[encoded.len() - j - 1], b'=');
+ }
+ for j in 0..(8 - num_padding[i % 5]) {
+ assert!(encoded.as_bytes()[j] != b'=');
+ }
+ }
+ }
+
+ #[test]
+ fn test_decode_rfc4648_errors() {
+ assert!(Alphabet::RFC4648 { padding: false }.decode("abc2def===").is_err()); // Invalid char because padding is disabled
+ assert!(Alphabet::RFC4648 { padding: true }.decode("abc2def===").is_err()); // Invalid length
+ assert!(Alphabet::RFC4648 { padding: true }.decode("MZX=6YTB").is_err()); // Invalid char
+ }
+}
//! <https://api.lightning.community/#signmessage>
use crate::prelude::*;
-use crate::util::zbase32;
+use crate::util::base32;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
use bitcoin::secp256k1::{Error, Message, PublicKey, Secp256k1, SecretKey};
static LN_MESSAGE_PREFIX: &[u8] = b"Lightning Signed Message:";
fn sigrec_encode(sig_rec: RecoverableSignature) -> Vec<u8> {
- let (rid, rsig) = sig_rec.serialize_compact();
- let prefix = rid.to_i32() as u8 + 31;
+ let (rid, rsig) = sig_rec.serialize_compact();
+ let prefix = rid.to_i32() as u8 + 31;
- [&[prefix], &rsig[..]].concat()
+ [&[prefix], &rsig[..]].concat()
}
fn sigrec_decode(sig_rec: Vec<u8>) -> Result<RecoverableSignature, Error> {
- // Signature must be 64 + 1 bytes long (compact signature + recovery id)
- if sig_rec.len() != 65 {
- return Err(Error::InvalidSignature);
- }
-
- let rsig = &sig_rec[1..];
- let rid = sig_rec[0] as i32 - 31;
-
- match RecoveryId::from_i32(rid) {
- Ok(x) => RecoverableSignature::from_compact(rsig, x),
- Err(e) => Err(e)
- }
+ // Signature must be 64 + 1 bytes long (compact signature + recovery id)
+ if sig_rec.len() != 65 {
+ return Err(Error::InvalidSignature);
+ }
+
+ let rsig = &sig_rec[1..];
+ let rid = sig_rec[0] as i32 - 31;
+
+ match RecoveryId::from_i32(rid) {
+ Ok(x) => RecoverableSignature::from_compact(rsig, x),
+ Err(e) => Err(e)
+ }
}
/// Creates a digital signature of a message given a SecretKey, like the node's secret.
/// A receiver knowing the PublicKey (e.g. the node's id) and the message can be sure that the signature was generated by the caller.
/// Signatures are EC recoverable, meaning that given the message and the signature the PublicKey of the signer can be extracted.
pub fn sign(msg: &[u8], sk: &SecretKey) -> Result<String, Error> {
- let secp_ctx = Secp256k1::signing_only();
- let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
+ let secp_ctx = Secp256k1::signing_only();
+ let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
- let sig = secp_ctx.sign_ecdsa_recoverable(&Message::from_slice(&msg_hash)?, sk);
- Ok(zbase32::encode(&sigrec_encode(sig)))
+ let sig = secp_ctx.sign_ecdsa_recoverable(&Message::from_slice(&msg_hash)?, sk);
+ Ok(base32::Alphabet::ZBase32.encode(&sigrec_encode(sig)))
}
/// Recovers the PublicKey of the signer of the message given the message and the signature.
pub fn recover_pk(msg: &[u8], sig: &str) -> Result<PublicKey, Error> {
- let secp_ctx = Secp256k1::verification_only();
- let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
-
- match zbase32::decode(&sig) {
- Ok(sig_rec) => {
- match sigrec_decode(sig_rec) {
- Ok(sig) => secp_ctx.recover_ecdsa(&Message::from_slice(&msg_hash)?, &sig),
- Err(e) => Err(e)
- }
- },
- Err(_) => Err(Error::InvalidSignature)
- }
+ let secp_ctx = Secp256k1::verification_only();
+ let msg_hash = sha256d::Hash::hash(&[LN_MESSAGE_PREFIX, msg].concat());
+
+ match base32::Alphabet::ZBase32.decode(&sig) {
+ Ok(sig_rec) => {
+ match sigrec_decode(sig_rec) {
+ Ok(sig) => secp_ctx.recover_ecdsa(&Message::from_slice(&msg_hash)?, &sig),
+ Err(e) => Err(e)
+ }
+ },
+ Err(_) => Err(Error::InvalidSignature)
+ }
}
/// Verifies a message was signed by a PrivateKey that derives to a given PublicKey, given a message, a signature,
/// and the PublicKey.
pub fn verify(msg: &[u8], sig: &str, pk: &PublicKey) -> bool {
- match recover_pk(msg, sig) {
- Ok(x) => x == *pk,
- Err(_) => false
- }
+ match recover_pk(msg, sig) {
+ Ok(x) => x == *pk,
+ Err(_) => false
+ }
}
#[cfg(test)]
mod test {
- use core::str::FromStr;
- use crate::util::message_signing::{sign, recover_pk, verify};
- use bitcoin::secp256k1::ONE_KEY;
- use bitcoin::secp256k1::{PublicKey, Secp256k1};
-
- #[test]
- fn test_sign() {
- let message = "test message";
- let zbase32_sig = sign(message.as_bytes(), &ONE_KEY);
-
- assert_eq!(zbase32_sig.unwrap(), "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e")
- }
-
- #[test]
- fn test_recover_pk() {
- let message = "test message";
- let sig = "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e";
- let pk = recover_pk(message.as_bytes(), sig);
-
- assert_eq!(pk.unwrap(), PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY))
- }
-
- #[test]
- fn test_verify() {
- let message = "another message";
- let sig = sign(message.as_bytes(), &ONE_KEY).unwrap();
- let pk = PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY);
-
- assert!(verify(message.as_bytes(), &sig, &pk))
- }
-
- #[test]
- fn test_verify_ground_truth_ish() {
- // There are no standard tests vectors for Sign/Verify, using the same tests vectors as c-lightning to see if they are compatible.
- // Taken from https://github.com/ElementsProject/lightning/blob/1275af6fbb02460c8eb2f00990bb0ef9179ce8f3/tests/test_misc.py#L1925-L1938
-
- let corpus = [
- ["@bitconner",
- "is this compatible?",
- "rbgfioj114mh48d8egqx8o9qxqw4fmhe8jbeeabdioxnjk8z3t1ma1hu1fiswpakgucwwzwo6ofycffbsqusqdimugbh41n1g698hr9t",
- "02b80cabdf82638aac86948e4c06e82064f547768dcef977677b9ea931ea75bab5"],
- ["@duck1123",
- "hi",
- "rnrphcjswusbacjnmmmrynh9pqip7sy5cx695h6mfu64iac6qmcmsd8xnsyczwmpqp9shqkth3h4jmkgyqu5z47jfn1q7gpxtaqpx4xg",
- "02de60d194e1ca5947b59fe8e2efd6aadeabfb67f2e89e13ae1a799c1e08e4a43b"],
- ["@jochemin",
- "hi",
- "ry8bbsopmduhxy3dr5d9ekfeabdpimfx95kagdem7914wtca79jwamtbw4rxh69hg7n6x9ty8cqk33knbxaqftgxsfsaeprxkn1k48p3",
- "022b8ece90ee891cbcdac0c1cc6af46b73c47212d8defbce80265ac81a6b794931"],
- ];
-
- for c in &corpus {
- assert!(verify(c[1].as_bytes(), c[2], &PublicKey::from_str(c[3]).unwrap()))
- }
- }
+ use core::str::FromStr;
+ use crate::util::message_signing::{sign, recover_pk, verify};
+ use bitcoin::secp256k1::ONE_KEY;
+ use bitcoin::secp256k1::{PublicKey, Secp256k1};
+
+ #[test]
+ fn test_sign() {
+ let message = "test message";
+ let zbase32_sig = sign(message.as_bytes(), &ONE_KEY);
+
+ assert_eq!(zbase32_sig.unwrap(), "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e")
+ }
+
+ #[test]
+ fn test_recover_pk() {
+ let message = "test message";
+ let sig = "d9tibmnic9t5y41hg7hkakdcra94akas9ku3rmmj4ag9mritc8ok4p5qzefs78c9pqfhpuftqqzhydbdwfg7u6w6wdxcqpqn4sj4e73e";
+ let pk = recover_pk(message.as_bytes(), sig);
+
+ assert_eq!(pk.unwrap(), PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY))
+ }
+
+ #[test]
+ fn test_verify() {
+ let message = "another message";
+ let sig = sign(message.as_bytes(), &ONE_KEY).unwrap();
+ let pk = PublicKey::from_secret_key(&Secp256k1::signing_only(), &ONE_KEY);
+
+ assert!(verify(message.as_bytes(), &sig, &pk))
+ }
+
+ #[test]
+ fn test_verify_ground_truth_ish() {
+ // There are no standard tests vectors for Sign/Verify, using the same tests vectors as c-lightning to see if they are compatible.
+ // Taken from https://github.com/ElementsProject/lightning/blob/1275af6fbb02460c8eb2f00990bb0ef9179ce8f3/tests/test_misc.py#L1925-L1938
+
+ let corpus = [
+ ["@bitconner",
+ "is this compatible?",
+ "rbgfioj114mh48d8egqx8o9qxqw4fmhe8jbeeabdioxnjk8z3t1ma1hu1fiswpakgucwwzwo6ofycffbsqusqdimugbh41n1g698hr9t",
+ "02b80cabdf82638aac86948e4c06e82064f547768dcef977677b9ea931ea75bab5"],
+ ["@duck1123",
+ "hi",
+ "rnrphcjswusbacjnmmmrynh9pqip7sy5cx695h6mfu64iac6qmcmsd8xnsyczwmpqp9shqkth3h4jmkgyqu5z47jfn1q7gpxtaqpx4xg",
+ "02de60d194e1ca5947b59fe8e2efd6aadeabfb67f2e89e13ae1a799c1e08e4a43b"],
+ ["@jochemin",
+ "hi",
+ "ry8bbsopmduhxy3dr5d9ekfeabdpimfx95kagdem7914wtca79jwamtbw4rxh69hg7n6x9ty8cqk33knbxaqftgxsfsaeprxkn1k48p3",
+ "022b8ece90ee891cbcdac0c1cc6af46b73c47212d8defbce80265ac81a6b794931"],
+ ];
+
+ for c in &corpus {
+ assert!(verify(c[1].as_bytes(), c[2], &PublicKey::from_str(c[3]).unwrap()))
+ }
+ }
}
+
pub mod persist;
pub mod string;
pub mod wakers;
+#[cfg(fuzzing)]
+pub mod base32;
+#[cfg(not(fuzzing))]
+pub(crate) mod base32;
pub(crate) mod atomic_counter;
pub(crate) mod byte_utils;
pub(crate) mod chacha20;
-#[cfg(fuzzing)]
-pub mod zbase32;
-#[cfg(not(fuzzing))]
-pub(crate) mod zbase32;
#[cfg(not(fuzzing))]
pub(crate) mod poly1305;
pub(crate) mod chacha20poly1305rfc;
+++ /dev/null
-// This is a modification of base32 encoding to support the zbase32 alphabet.
-// The original piece of software can be found at https://github.com/andreasots/base32
-// The original portions of this software are Copyright (c) 2015 The base32 Developers
-
-/* This file is licensed under either of
- * Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or
- * MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
- * at your option.
-*/
-
-use crate::prelude::*;
-
-const ALPHABET: &'static [u8] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
-
-/// Encodes some bytes as a zbase32 string
-pub fn encode(data: &[u8]) -> String {
- let mut ret = Vec::with_capacity((data.len() + 4) / 5 * 8);
-
- for chunk in data.chunks(5) {
- let buf = {
- let mut buf = [0u8; 5];
- for (i, &b) in chunk.iter().enumerate() {
- buf[i] = b;
- }
- buf
- };
-
- ret.push(ALPHABET[((buf[0] & 0xF8) >> 3) as usize]);
- ret.push(ALPHABET[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xC0) >> 6)) as usize]);
- ret.push(ALPHABET[((buf[1] & 0x3E) >> 1) as usize]);
- ret.push(ALPHABET[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xF0) >> 4)) as usize]);
- ret.push(ALPHABET[(((buf[2] & 0x0F) << 1) | (buf[3] >> 7)) as usize]);
- ret.push(ALPHABET[((buf[3] & 0x7C) >> 2) as usize]);
- ret.push(ALPHABET[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xE0) >> 5)) as usize]);
- ret.push(ALPHABET[(buf[4] & 0x1F) as usize]);
- }
-
- ret.truncate((data.len() * 8 + 4) / 5);
-
- // Check that our capacity calculation doesn't under-shoot in fuzzing
- #[cfg(fuzzing)]
- assert_eq!(ret.capacity(), (data.len() + 4) / 5 * 8);
-
- String::from_utf8(ret).unwrap()
-}
-
-// ASCII 0-Z
-const INV_ALPHABET: [i8; 43] = [
- -1, 18, -1, 25, 26, 27, 30, 29, 7, 31, -1, -1, -1, -1, -1, -1, -1, 24, 1, 12, 3, 8, 5, 6, 28,
- 21, 9, 10, -1, 11, 2, 16, 13, 14, 4, 22, 17, 19, -1, 20, 15, 0, 23,
-];
-
-/// Decodes a zbase32 string to the original bytes, failing if the string was not encoded by a
-/// proper zbase32 encoder.
-pub fn decode(data: &str) -> Result<Vec<u8>, ()> {
- if !data.is_ascii() {
- return Err(());
- }
-
- let data = data.as_bytes();
- let output_length = data.len() * 5 / 8;
- if data.len() > (output_length * 8 + 4) / 5 {
- // If the string has more charachters than are required to encode the number of bytes
- // decodable, treat the string as invalid.
- return Err(());
- }
-
- let mut ret = Vec::with_capacity((data.len() + 7) / 8 * 5);
-
- for chunk in data.chunks(8) {
- let buf = {
- let mut buf = [0u8; 8];
- for (i, &c) in chunk.iter().enumerate() {
- match INV_ALPHABET.get(c.to_ascii_uppercase().wrapping_sub(b'0') as usize) {
- Some(&-1) | None => return Err(()),
- Some(&value) => buf[i] = value as u8,
- };
- }
- buf
- };
- ret.push((buf[0] << 3) | (buf[1] >> 2));
- ret.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4));
- ret.push((buf[3] << 4) | (buf[4] >> 1));
- ret.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3));
- ret.push((buf[6] << 5) | buf[7]);
- }
- for c in ret.drain(output_length..) {
- if c != 0 {
- // If the original string had any bits set at positions outside of the encoded data,
- // treat the string as invalid.
- return Err(());
- }
- }
-
- // Check that our capacity calculation doesn't under-shoot in fuzzing
- #[cfg(fuzzing)]
- assert_eq!(ret.capacity(), (data.len() + 7) / 8 * 5);
-
- Ok(ret)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- const TEST_DATA: &[(&str, &[u8])] = &[
- ("", &[]),
- ("yy", &[0x00]),
- ("oy", &[0x80]),
- ("tqrey", &[0x8b, 0x88, 0x80]),
- ("6n9hq", &[0xf0, 0xbf, 0xc7]),
- ("4t7ye", &[0xd4, 0x7a, 0x04]),
- ("6im5sdy", &[0xf5, 0x57, 0xbb, 0x0c]),
- ("ybndrfg8ejkmcpqxot1uwisza345h769", &[0x00, 0x44, 0x32, 0x14, 0xc7, 0x42, 0x54, 0xb6,
- 0x35, 0xcf, 0x84, 0x65, 0x3a, 0x56, 0xd7, 0xc6,
- 0x75, 0xbe, 0x77, 0xdf])
- ];
-
- #[test]
- fn test_encode() {
- for &(zbase32, data) in TEST_DATA {
- assert_eq!(encode(data), zbase32);
- }
- }
-
- #[test]
- fn test_decode() {
- for &(zbase32, data) in TEST_DATA {
- assert_eq!(decode(zbase32).unwrap(), data);
- }
- }
-
- #[test]
- fn test_decode_wrong() {
- const WRONG_DATA: &[&str] = &["00", "l1", "?", "="];
-
- for &data in WRONG_DATA {
- match decode(data) {
- Ok(_) => assert!(false, "Data shouldn't be decodable"),
- Err(_) => assert!(true),
- }
- }
- }
-}
--- /dev/null
+# Backwards Compatibility
+
+- `Route` objects written with LDK versions prior to 0.0.117 won't be retryable after being deserialized with LDK 0.0.117 or above.