id: cache-graph
uses: actions/cache@v3
with:
- path: lightning/net_graph-2021-05-31.bin
- key: ldk-net_graph-v0.0.15-2021-05-31.bin
+ path: lightning/net_graph-2023-01-18.bin
+ key: ldk-net_graph-v0.0.113-2023-01-18.bin
- name: Fetch routing graph snapshot
if: steps.cache-graph.outputs.cache-hit != 'true'
run: |
- curl --verbose -L -o lightning/net_graph-2021-05-31.bin https://bitcoin.ninja/ldk-net_graph-v0.0.15-2021-05-31.bin
- echo "Sha sum: $(sha256sum lightning/net_graph-2021-05-31.bin | awk '{ print $1 }')"
- if [ "$(sha256sum lightning/net_graph-2021-05-31.bin | awk '{ print $1 }')" != "${EXPECTED_ROUTING_GRAPH_SNAPSHOT_SHASUM}" ]; then
+ curl --verbose -L -o lightning/net_graph-2023-01-18.bin https://bitcoin.ninja/ldk-net_graph-v0.0.113-2023-01-18.bin
+ echo "Sha sum: $(sha256sum lightning/net_graph-2023-01-18.bin | awk '{ print $1 }')"
+ if [ "$(sha256sum lightning/net_graph-2023-01-18.bin | awk '{ print $1 }')" != "${EXPECTED_ROUTING_GRAPH_SNAPSHOT_SHASUM}" ]; then
echo "Bad hash"
exit 1
fi
env:
- EXPECTED_ROUTING_GRAPH_SNAPSHOT_SHASUM: 05a5361278f68ee2afd086cc04a1f927a63924be451f3221d380533acfacc303
+ EXPECTED_ROUTING_GRAPH_SNAPSHOT_SHASUM: da6066f2bddcddbe7d8a6debbd53545697137b310bbb8c4911bc8c81fc5ff48c
- name: Fetch rapid graph sync reference input
run: |
curl --verbose -L -o lightning-rapid-gossip-sync/res/full_graph.lngossip https://bitcoin.ninja/ldk-compressed_graph-285cb27df79-2022-07-21.bin
said, we deeply welcome people contributing for the first time to an open source
project or pick up Rust while contributing. Don't be shy, you'll learn.
+For the project Code of Conduct, see our [website](https://lightningdevkit.org/code_of_conduct).
+
Communication Channels
-----------------------
contributions but rather understand the project culture before investing an
asymmetric number of hours on development compared to your merged work.
-Browsing through the [meeting minutes](https://github.com/lightningdevkit/rust-lightning/wiki/Meetings)
+Browsing through the [meeting minutes](https://github.com/lightningdevkit/rust-lightning/wiki/Meeting-Notes)
is a good first step. You will learn who is working on what, how releases are
drafted, what are the pending tasks to deliver, where you can contribute review
bandwidth, etc.
stdin_fuzz = []
[dependencies]
-afl = { version = "0.4", optional = true }
-lightning = { path = "../lightning", features = ["regex"] }
+lightning = { path = "../lightning", features = ["regex", "hashbrown", "_test_utils"] }
lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" }
bitcoin = { version = "0.29.0", features = ["secp-lowmemory"] }
hex = "0.3"
+hashbrown = "0.8"
+
+afl = { version = "0.12", optional = true }
honggfuzz = { version = "0.5", optional = true, default-features = false }
libfuzzer-sys = { version = "0.4", optional = true }
[profile.release]
lto = true
codegen-units = 1
+debug-assertions = true
+overflow-checks = true
# When testing a large fuzz corpus, -O1 offers a nice speedup
[profile.dev]
GEN_TEST process_network_graph
GEN_TEST router
GEN_TEST zbase32
+GEN_TEST indexedmap
GEN_TEST msg_accept_channel msg_targets::
GEN_TEST msg_announcement_signatures msg_targets::
--- /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::indexedmap::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ indexedmap_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ indexedmap_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ indexedmap_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();
+ indexedmap_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];
+ indexedmap_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/indexedmap") {
+ 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 || {
+ indexedmap_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::msg_targets::msg_channel_details::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+ fuzz!(|data| {
+ msg_channel_details_run(data.as_ptr(), data.len());
+ });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+ loop {
+ fuzz!(|data| {
+ msg_channel_details_run(data.as_ptr(), data.len());
+ });
+ }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+ msg_channel_details_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();
+ msg_channel_details_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];
+ msg_channel_details_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/msg_channel_details") {
+ 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 || {
+ msg_channel_details_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!();
+ }
+}
use bitcoin::hashes::Hash as TraitImport;
use bitcoin::hashes::sha256::Hash as Sha256;
+use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::{BlockHash, WPubkeyHash};
use lightning::chain;
use crate::utils::test_logger::{self, Output};
use crate::utils::test_persister::TestPersister;
-use bitcoin::secp256k1::{PublicKey, SecretKey, Scalar};
+use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::ecdh::SharedSecret;
-use bitcoin::secp256k1::ecdsa::RecoverableSignature;
-use bitcoin::secp256k1::Secp256k1;
+use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use std::mem;
use std::cmp::{self, Ordering};
-use std::collections::{HashSet, hash_map, HashMap};
+use hashbrown::{HashSet, hash_map, HashMap};
use std::sync::{Arc,Mutex};
use std::sync::atomic;
use std::io::Cursor;
self.chain_monitor.watch_channel(funding_txo, monitor)
}
- fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> chain::ChannelMonitorUpdateStatus {
+ fn update_channel(&self, funding_txo: OutPoint, update: &channelmonitor::ChannelMonitorUpdate) -> chain::ChannelMonitorUpdateStatus {
let mut map_lock = self.latest_monitors.lock().unwrap();
let mut map_entry = match map_lock.entry(funding_txo) {
hash_map::Entry::Occupied(entry) => entry,
};
let deserialized_monitor = <(BlockHash, channelmonitor::ChannelMonitor<EnforcingSigner>)>::
read(&mut Cursor::new(&map_entry.get().1), (&*self.keys, &*self.keys)).unwrap().1;
- deserialized_monitor.update_monitor(&update, &&TestBroadcaster{}, &FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }, &self.logger).unwrap();
+ deserialized_monitor.update_monitor(update, &&TestBroadcaster{}, &FuzzEstimator { ret_val: atomic::AtomicU32::new(253) }, &self.logger).unwrap();
let mut ser = VecWriter(Vec::new());
deserialized_monitor.write(&mut ser).unwrap();
map_entry.insert((update.update_id, ser.0));
}
struct KeyProvider {
- node_id: u8,
+ node_secret: SecretKey,
rand_bytes_id: atomic::AtomicU32,
enforcement_states: Mutex<HashMap<[u8;32], Arc<Mutex<EnforcementState>>>>,
}
impl EntropySource for KeyProvider {
fn get_secure_random_bytes(&self) -> [u8; 32] {
let id = self.rand_bytes_id.fetch_add(1, atomic::Ordering::Relaxed);
- let mut res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, self.node_id];
+ let mut res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, self.node_secret[31]];
res[30-4..30].copy_from_slice(&id.to_le_bytes());
res
}
}
impl NodeSigner for KeyProvider {
- fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> {
- Ok(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap())
- }
-
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
- let secp_ctx = Secp256k1::signing_only();
- Ok(PublicKey::from_secret_key(&secp_ctx, &self.get_node_secret(recipient)?))
+ let node_secret = match recipient {
+ Recipient::Node => Ok(&self.node_secret),
+ Recipient::PhantomNode => Err(())
+ }?;
+ Ok(PublicKey::from_secret_key(&Secp256k1::signing_only(), node_secret))
}
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>) -> Result<SharedSecret, ()> {
- let mut node_secret = self.get_node_secret(recipient)?;
+ let mut node_secret = match recipient {
+ Recipient::Node => Ok(self.node_secret.clone()),
+ Recipient::PhantomNode => Err(())
+ }?;
if let Some(tweak) = tweak {
- node_secret = node_secret.mul_tweak(tweak).unwrap();
+ node_secret = node_secret.mul_tweak(tweak).map_err(|_| ())?;
}
Ok(SharedSecret::new(other_key, &node_secret))
}
fn get_inbound_payment_key_material(&self) -> KeyMaterial {
- KeyMaterial([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id])
+ KeyMaterial([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_secret[31]])
}
fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
unreachable!()
}
+
+ fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
+ let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
+ let secp_ctx = Secp256k1::signing_only();
+ Ok(secp_ctx.sign_ecdsa(&msg_hash, &self.node_secret))
+ }
}
impl SignerProvider for KeyProvider {
let id = channel_keys_id[0];
let keys = InMemorySigner::new(
&secp_ctx,
- self.get_node_secret(Recipient::Node).unwrap(),
- SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, self.node_id]).unwrap(),
- SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, self.node_id]).unwrap(),
- SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, self.node_id]).unwrap(),
- SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, self.node_id]).unwrap(),
- SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, self.node_id]).unwrap(),
- [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, self.node_id],
+ SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, self.node_secret[31]]).unwrap(),
+ SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, self.node_secret[31]]).unwrap(),
+ SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, self.node_secret[31]]).unwrap(),
+ SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, self.node_secret[31]]).unwrap(),
+ SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, self.node_secret[31]]).unwrap(),
+ [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, self.node_secret[31]],
channel_value_satoshis,
channel_keys_id,
);
fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::Signer, DecodeError> {
let mut reader = std::io::Cursor::new(buffer);
- let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret(Recipient::Node).unwrap())?;
+ let inner: InMemorySigner = Readable::read(&mut reader)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);
Ok(EnforcingSigner {
fn get_destination_script(&self) -> Script {
let secp_ctx = Secp256k1::signing_only();
- let channel_monitor_claim_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, self.node_id]).unwrap();
+ let channel_monitor_claim_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, self.node_secret[31]]).unwrap();
let our_channel_monitor_claim_key_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &channel_monitor_claim_key).serialize());
Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&our_channel_monitor_claim_key_hash[..]).into_script()
}
fn get_shutdown_scriptpubkey(&self) -> ShutdownScript {
let secp_ctx = Secp256k1::signing_only();
- let secret_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, self.node_id]).unwrap();
+ let secret_key = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, self.node_secret[31]]).unwrap();
let pubkey_hash = WPubkeyHash::hash(&PublicKey::from_secret_key(&secp_ctx, &secret_key).serialize());
ShutdownScript::new_p2wpkh(&pubkey_hash)
}
let mut payment_hash;
for _ in 0..256 {
payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).into_inner());
- if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600) {
+ if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600, None) {
return Some((payment_secret, payment_hash));
}
*payment_id = payment_id.wrapping_add(1);
macro_rules! make_node {
($node_id: expr, $fee_estimator: expr) => { {
let logger: Arc<dyn Logger> = Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone()));
- let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(HashMap::new()) });
+ let node_secret = SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, $node_id]).unwrap();
+ let keys_manager = Arc::new(KeyProvider { node_secret, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(HashMap::new()) });
let monitor = Arc::new(TestChainMonitor::new(broadcast.clone(), logger.clone(), $fee_estimator.clone(),
Arc::new(TestPersister {
update_ret: Mutex::new(ChannelMonitorUpdateStatus::Completed)
} else { panic!("Wrong event type"); }
};
- $dest.handle_open_channel(&$source.get_our_node_id(), $source.init_features(), &open_channel);
+ $dest.handle_open_channel(&$source.get_our_node_id(), &open_channel);
let accept_channel = {
let events = $dest.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
} else { panic!("Wrong event type"); }
};
- $source.handle_accept_channel(&$dest.get_our_node_id(), $dest.init_features(), &accept_channel);
+ $source.handle_accept_channel(&$dest.get_our_node_id(), &accept_channel);
let funding_output;
{
let events = $source.get_and_clear_pending_events();
use bitcoin::hashes::Hash as TraitImport;
use bitcoin::hashes::HashEngine as TraitImportEngine;
use bitcoin::hashes::sha256::Hash as Sha256;
+use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash};
use lightning::chain;
use lightning::util::events::Event;
use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
use lightning::util::logger::Logger;
-use lightning::util::ser::ReadableArgs;
+use lightning::util::ser::{Readable, Writeable};
use crate::utils::test_logger;
use crate::utils::test_persister::TestPersister;
-use bitcoin::secp256k1::{PublicKey, SecretKey, Scalar};
+use bitcoin::secp256k1::{Message, PublicKey, SecretKey, Scalar, Secp256k1};
use bitcoin::secp256k1::ecdh::SharedSecret;
-use bitcoin::secp256k1::ecdsa::RecoverableSignature;
-use bitcoin::secp256k1::Secp256k1;
+use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
use std::cell::RefCell;
-use std::collections::{HashMap, hash_map};
+use hashbrown::{HashMap, hash_map};
use std::convert::TryInto;
use std::cmp;
use std::sync::{Arc, Mutex};
type ChannelMan<'a> = ChannelManager<
Arc<chainmonitor::ChainMonitor<EnforcingSigner, Arc<dyn chain::Filter>, Arc<TestBroadcaster>, Arc<FuzzEstimator>, Arc<dyn Logger>, Arc<TestPersister>>>,
Arc<TestBroadcaster>, Arc<KeyProvider>, Arc<KeyProvider>, Arc<KeyProvider>, Arc<FuzzEstimator>, &'a FuzzRouter, Arc<dyn Logger>>;
-type PeerMan<'a> = PeerManager<Peer<'a>, Arc<ChannelMan<'a>>, Arc<P2PGossipSync<Arc<NetworkGraph<Arc<dyn Logger>>>, Arc<dyn chain::Access>, Arc<dyn Logger>>>, IgnoringMessageHandler, Arc<dyn Logger>, IgnoringMessageHandler>;
+type PeerMan<'a> = PeerManager<Peer<'a>, Arc<ChannelMan<'a>>, Arc<P2PGossipSync<Arc<NetworkGraph<Arc<dyn Logger>>>, Arc<dyn chain::Access>, Arc<dyn Logger>>>, IgnoringMessageHandler, Arc<dyn Logger>, IgnoringMessageHandler, Arc<KeyProvider>>;
struct MoneyLossDetector<'a> {
manager: Arc<ChannelMan<'a>>,
}
impl NodeSigner for KeyProvider {
- fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> {
- Ok(self.node_secret.clone())
- }
-
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
- let secp_ctx = Secp256k1::signing_only();
- Ok(PublicKey::from_secret_key(&secp_ctx, &self.get_node_secret(recipient)?))
+ let node_secret = match recipient {
+ Recipient::Node => Ok(&self.node_secret),
+ Recipient::PhantomNode => Err(())
+ }?;
+ Ok(PublicKey::from_secret_key(&Secp256k1::signing_only(), node_secret))
}
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>) -> Result<SharedSecret, ()> {
- let mut node_secret = self.get_node_secret(recipient)?;
+ let mut node_secret = match recipient {
+ Recipient::Node => Ok(self.node_secret.clone()),
+ Recipient::PhantomNode => Err(())
+ }?;
if let Some(tweak) = tweak {
- node_secret = node_secret.mul_tweak(tweak).unwrap();
+ node_secret = node_secret.mul_tweak(tweak).map_err(|_| ())?;
}
Ok(SharedSecret::new(other_key, &node_secret))
}
fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
unreachable!()
}
+
+ fn sign_gossip_message(&self, msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
+ let msg_hash = Message::from_slice(&Sha256dHash::hash(&msg.encode()[..])[..]).map_err(|_| ())?;
+ let secp_ctx = Secp256k1::signing_only();
+ Ok(secp_ctx.sign_ecdsa(&msg_hash, &self.node_secret))
+ }
}
impl SignerProvider for KeyProvider {
EnforcingSigner::new_with_revoked(if inbound {
InMemorySigner::new(
&secp_ctx,
- self.node_secret.clone(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ctr]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, ctr]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, ctr]).unwrap(),
} else {
InMemorySigner::new(
&secp_ctx,
- self.node_secret.clone(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, ctr]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, ctr]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, ctr]).unwrap(),
}
fn read_chan_signer(&self, mut data: &[u8]) -> Result<EnforcingSigner, DecodeError> {
- let inner: InMemorySigner = ReadableArgs::read(&mut data, self.node_secret.clone())?;
+ let inner: InMemorySigner = Readable::read(&mut data)?;
let state = Arc::new(Mutex::new(EnforcementState::new()));
Ok(EnforcingSigner::new_with_revoked(
// keys subsequently generated in this test. Rather than regenerating all the messages manually,
// it's easier to just increment the counter here so the keys don't change.
keys_manager.counter.fetch_sub(3, Ordering::AcqRel);
- let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret(Recipient::Node).unwrap());
+ let our_id = &keys_manager.get_node_id(Recipient::Node).unwrap();
let network_graph = Arc::new(NetworkGraph::new(genesis_block(network).block_hash(), Arc::clone(&logger)));
let gossip_sync = Arc::new(P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger)));
let scorer = FixedPenaltyScorer::with_penalty(0);
chan_handler: channelmanager.clone(),
route_handler: gossip_sync.clone(),
onion_message_handler: IgnoringMessageHandler {},
- }, our_network_key, 0, &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0], Arc::clone(&logger), IgnoringMessageHandler{}));
+ }, 0, &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0], Arc::clone(&logger), IgnoringMessageHandler{}, keys_manager.clone()));
let mut should_forward = false;
let mut payments_received: Vec<PaymentHash> = Vec::new();
let payment_hash = PaymentHash(Sha256::from_engine(sha).into_inner());
// Note that this may fail - our hashes may collide and we'll end up trying to
// double-register the same payment_hash.
- let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1);
+ let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1, None);
},
9 => {
for payment in payments_received.drain(..) {
// It's possible the channel has been closed in the mean time, but any other
// failure may be a bug.
if let APIError::ChannelUnavailable { err } = e {
- assert_eq!(err, "No such channel");
+ if !err.starts_with("Can't find a peer matching the passed counterparty node_id ") {
+ assert_eq!(err, "No such channel");
+ }
} else { panic!(); }
}
pending_funding_signatures.insert(funding_output, tx);
--- /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::indexed_map::{IndexedMap, self};
+use std::collections::{BTreeMap, btree_map};
+use hashbrown::HashSet;
+
+use crate::utils::test_logger;
+
+fn check_eq(btree: &BTreeMap<u8, u8>, indexed: &IndexedMap<u8, u8>) {
+ assert_eq!(btree.len(), indexed.len());
+ assert_eq!(btree.is_empty(), indexed.is_empty());
+
+ let mut btree_clone = btree.clone();
+ assert!(btree_clone == *btree);
+ let mut indexed_clone = indexed.clone();
+ assert!(indexed_clone == *indexed);
+
+ for k in 0..=255 {
+ assert_eq!(btree.contains_key(&k), indexed.contains_key(&k));
+ assert_eq!(btree.get(&k), indexed.get(&k));
+
+ let btree_entry = btree_clone.entry(k);
+ let indexed_entry = indexed_clone.entry(k);
+ match btree_entry {
+ btree_map::Entry::Occupied(mut bo) => {
+ if let indexed_map::Entry::Occupied(mut io) = indexed_entry {
+ assert_eq!(bo.get(), io.get());
+ assert_eq!(bo.get_mut(), io.get_mut());
+ } else { panic!(); }
+ },
+ btree_map::Entry::Vacant(_) => {
+ if let indexed_map::Entry::Vacant(_) = indexed_entry {
+ } else { panic!(); }
+ }
+ }
+ }
+
+ const STRIDE: u8 = 16;
+ for k in 0..=255/STRIDE {
+ let lower_bound = k * STRIDE;
+ let upper_bound = lower_bound + (STRIDE - 1);
+ let mut btree_iter = btree.range(lower_bound..=upper_bound);
+ let mut indexed_iter = indexed.range(lower_bound..=upper_bound);
+ loop {
+ let b_v = btree_iter.next();
+ let i_v = indexed_iter.next();
+ assert_eq!(b_v, i_v);
+ if b_v.is_none() { break; }
+ }
+ }
+
+ let mut key_set = HashSet::with_capacity(256);
+ for k in indexed.unordered_keys() {
+ assert!(key_set.insert(*k));
+ assert!(btree.contains_key(k));
+ }
+ assert_eq!(key_set.len(), btree.len());
+
+ key_set.clear();
+ for (k, v) in indexed.unordered_iter() {
+ assert!(key_set.insert(*k));
+ assert_eq!(btree.get(k).unwrap(), v);
+ }
+ assert_eq!(key_set.len(), btree.len());
+
+ key_set.clear();
+ for (k, v) in indexed_clone.unordered_iter_mut() {
+ assert!(key_set.insert(*k));
+ assert_eq!(btree.get(k).unwrap(), v);
+ }
+ assert_eq!(key_set.len(), btree.len());
+}
+
+#[inline]
+pub fn do_test(data: &[u8]) {
+ if data.len() % 2 != 0 { return; }
+ let mut btree = BTreeMap::new();
+ let mut indexed = IndexedMap::new();
+
+ // Read in k-v pairs from the input and insert them into the maps then check that the maps are
+ // equivalent in every way we can read them.
+ for tuple in data.windows(2) {
+ let prev_value_b = btree.insert(tuple[0], tuple[1]);
+ let prev_value_i = indexed.insert(tuple[0], tuple[1]);
+ assert_eq!(prev_value_b, prev_value_i);
+ }
+ check_eq(&btree, &indexed);
+
+ // Now, modify the maps in all the ways we have to do so, checking that the maps remain
+ // equivalent as we go.
+ for (k, v) in indexed.unordered_iter_mut() {
+ *v = *k;
+ *btree.get_mut(k).unwrap() = *k;
+ }
+ check_eq(&btree, &indexed);
+
+ for k in 0..=255 {
+ match btree.entry(k) {
+ btree_map::Entry::Occupied(mut bo) => {
+ if let indexed_map::Entry::Occupied(mut io) = indexed.entry(k) {
+ if k < 64 {
+ *io.get_mut() ^= 0xff;
+ *bo.get_mut() ^= 0xff;
+ } else if k < 128 {
+ *io.into_mut() ^= 0xff;
+ *bo.get_mut() ^= 0xff;
+ } else {
+ assert_eq!(bo.remove_entry(), io.remove_entry());
+ }
+ } else { panic!(); }
+ },
+ btree_map::Entry::Vacant(bv) => {
+ if let indexed_map::Entry::Vacant(iv) = indexed.entry(k) {
+ bv.insert(k);
+ iv.insert(k);
+ } else { panic!(); }
+ },
+ }
+ }
+ check_eq(&btree, &indexed);
+}
+
+pub fn indexedmap_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+ do_test(data);
+}
+
+#[no_mangle]
+pub extern "C" fn indexedmap_run(data: *const u8, datalen: usize) {
+ do_test(unsafe { std::slice::from_raw_parts(data, datalen) });
+}
pub mod chanmon_deser;
pub mod chanmon_consistency;
pub mod full_stack;
+pub mod indexedmap;
pub mod onion_message;
pub mod peer_crypt;
pub mod process_network_graph;
}
impl NodeSigner for KeyProvider {
- fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> {
- Ok(self.node_secret.clone())
- }
-
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
- let secp_ctx = Secp256k1::signing_only();
- Ok(PublicKey::from_secret_key(&secp_ctx, &self.get_node_secret(recipient)?))
+ let node_secret = match recipient {
+ Recipient::Node => Ok(&self.node_secret),
+ Recipient::PhantomNode => Err(())
+ }?;
+ Ok(PublicKey::from_secret_key(&Secp256k1::signing_only(), node_secret))
}
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>) -> Result<SharedSecret, ()> {
- let mut node_secret = self.get_node_secret(recipient)?;
+ let mut node_secret = match recipient {
+ Recipient::Node => Ok(self.node_secret.clone()),
+ Recipient::PhantomNode => Err(())
+ }?;
if let Some(tweak) = tweak {
node_secret = node_secret.mul_tweak(tweak).map_err(|_| ())?;
}
fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
unreachable!()
}
+
+ fn sign_gossip_message(&self, _msg: lightning::ln::msgs::UnsignedGossipMessage) -> Result<bitcoin::secp256k1::ecdsa::Signature, ()> {
+ unreachable!()
+ }
}
impl SignerProvider for KeyProvider {
// licenses.
use lightning::ln::peer_channel_encryptor::PeerChannelEncryptor;
+use lightning::util::test_utils::TestNodeSigner;
use bitcoin::secp256k1::{Secp256k1, PublicKey, SecretKey};
Ok(key) => key,
Err(_) => return,
};
+ let node_signer = TestNodeSigner::new(our_network_key);
let ephemeral_key = match SecretKey::from_slice(get_slice!(32)) {
Ok(key) => key,
Err(_) => return,
};
let mut crypter = PeerChannelEncryptor::new_outbound(their_pubkey, ephemeral_key);
crypter.get_act_one(&secp_ctx);
- match crypter.process_act_two(get_slice!(50), &our_network_key, &secp_ctx) {
+ match crypter.process_act_two(get_slice!(50), &&node_signer) {
Ok(_) => {},
Err(_) => return,
}
assert!(crypter.is_ready_for_encryption());
crypter
} else {
- let mut crypter = PeerChannelEncryptor::new_inbound(&our_network_key, &secp_ctx);
- match crypter.process_act_one_with_keys(get_slice!(50), &our_network_key, ephemeral_key, &secp_ctx) {
+ let mut crypter = PeerChannelEncryptor::new_inbound(&&node_signer);
+ match crypter.process_act_one_with_keys(get_slice!(50), &&node_signer, ephemeral_key, &secp_ctx) {
Ok(_) => {},
Err(_) => return,
}
use crate::utils::test_logger;
use std::convert::TryInto;
-use std::collections::HashSet;
+use hashbrown::HashSet;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
self.update_ret.lock().unwrap().clone()
}
- fn update_persisted_channel(&self, _funding_txo: OutPoint, _update: &Option<channelmonitor::ChannelMonitorUpdate>, _data: &channelmonitor::ChannelMonitor<EnforcingSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
+ fn update_persisted_channel(&self, _funding_txo: OutPoint, _update: Option<&channelmonitor::ChannelMonitorUpdate>, _data: &channelmonitor::ChannelMonitor<EnforcingSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
self.update_ret.lock().unwrap().clone()
}
}
void process_network_graph_run(const unsigned char* data, size_t data_len);
void router_run(const unsigned char* data, size_t data_len);
void zbase32_run(const unsigned char* data, size_t data_len);
+void indexedmap_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);
[features]
futures = [ "futures-util" ]
+std = ["lightning/std", "lightning-rapid-gossip-sync/std"]
+
+default = ["std"]
[dependencies]
-bitcoin = "0.29.0"
-lightning = { version = "0.0.113", path = "../lightning", features = ["std"] }
-lightning-rapid-gossip-sync = { version = "0.0.113", path = "../lightning-rapid-gossip-sync" }
+bitcoin = { version = "0.29.0", default-features = false }
+lightning = { version = "0.0.113", path = "../lightning", default-features = false }
+lightning-rapid-gossip-sync = { version = "0.0.113", path = "../lightning-rapid-gossip-sync", default-features = false }
futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true }
[dev-dependencies]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
+
+#[cfg(any(test, feature = "std"))]
+extern crate core;
+
#[macro_use] extern crate lightning;
extern crate lightning_rapid_gossip_sync;
use lightning::util::logger::Logger;
use lightning::util::persist::Persister;
use lightning_rapid_gossip_sync::RapidGossipSync;
+use lightning::io;
+
+use core::ops::Deref;
+use core::time::Duration;
+
+#[cfg(feature = "std")]
use std::sync::Arc;
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::thread;
-use std::thread::JoinHandle;
-use std::time::{Duration, Instant};
-use std::ops::Deref;
+#[cfg(feature = "std")]
+use core::sync::atomic::{AtomicBool, Ordering};
+#[cfg(feature = "std")]
+use std::thread::{self, JoinHandle};
+#[cfg(feature = "std")]
+use std::time::Instant;
#[cfg(feature = "futures")]
-use futures_util::{select_biased, future::FutureExt};
+use futures_util::{select_biased, future::FutureExt, task};
/// `BackgroundProcessor` takes care of tasks that (1) need to happen periodically to keep
/// Rust-Lightning running properly, and (2) either can or should be run in the background. Its
///
/// [`ChannelMonitor`]: lightning::chain::channelmonitor::ChannelMonitor
/// [`Event`]: lightning::util::events::Event
+#[cfg(feature = "std")]
#[must_use = "BackgroundProcessor will immediately stop on drop. It should be stored until shutdown."]
pub struct BackgroundProcessor {
stop_thread: Arc<AtomicBool>,
($persister: ident, $chain_monitor: ident, $process_chain_monitor_events: expr,
$channel_manager: ident, $process_channel_manager_events: expr,
$gossip_sync: ident, $peer_manager: ident, $logger: ident, $scorer: ident,
- $loop_exit_check: expr, $await: expr)
+ $loop_exit_check: expr, $await: expr, $get_timer: expr, $timer_elapsed: expr)
=> { {
log_trace!($logger, "Calling ChannelManager's timer_tick_occurred on startup");
$channel_manager.timer_tick_occurred();
- let mut last_freshness_call = Instant::now();
- let mut last_ping_call = Instant::now();
- let mut last_prune_call = Instant::now();
- let mut last_scorer_persist_call = Instant::now();
+ let mut last_freshness_call = $get_timer(FRESHNESS_TIMER);
+ let mut last_ping_call = $get_timer(PING_TIMER);
+ let mut last_prune_call = $get_timer(FIRST_NETWORK_PRUNE_TIMER);
+ let mut last_scorer_persist_call = $get_timer(SCORER_PERSIST_TIMER);
let mut have_pruned = false;
loop {
// We wait up to 100ms, but track how long it takes to detect being put to sleep,
// see `await_start`'s use below.
- let await_start = Instant::now();
+ let mut await_start = $get_timer(1);
let updates_available = $await;
- let await_time = await_start.elapsed();
+ let await_slow = $timer_elapsed(&mut await_start, 1);
if updates_available {
log_trace!($logger, "Persisting ChannelManager...");
log_trace!($logger, "Terminating background processor.");
break;
}
- if last_freshness_call.elapsed().as_secs() > FRESHNESS_TIMER {
+ if $timer_elapsed(&mut last_freshness_call, FRESHNESS_TIMER) {
log_trace!($logger, "Calling ChannelManager's timer_tick_occurred");
$channel_manager.timer_tick_occurred();
- last_freshness_call = Instant::now();
+ last_freshness_call = $get_timer(FRESHNESS_TIMER);
}
- if await_time > Duration::from_secs(1) {
+ if await_slow {
// On various platforms, we may be starved of CPU cycles for several reasons.
// E.g. on iOS, if we've been in the background, we will be entirely paused.
// Similarly, if we're on a desktop platform and the device has been asleep, we
// peers.
log_trace!($logger, "100ms sleep took more than a second, disconnecting peers.");
$peer_manager.disconnect_all_peers();
- last_ping_call = Instant::now();
- } else if last_ping_call.elapsed().as_secs() > PING_TIMER {
+ last_ping_call = $get_timer(PING_TIMER);
+ } else if $timer_elapsed(&mut last_ping_call, PING_TIMER) {
log_trace!($logger, "Calling PeerManager's timer_tick_occurred");
$peer_manager.timer_tick_occurred();
- last_ping_call = Instant::now();
+ last_ping_call = $get_timer(PING_TIMER);
}
// Note that we want to run a graph prune once not long after startup before
// falling back to our usual hourly prunes. This avoids short-lived clients never
// pruning their network graph. We run once 60 seconds after startup before
// continuing our normal cadence.
- if last_prune_call.elapsed().as_secs() > if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER } {
+ if $timer_elapsed(&mut last_prune_call, if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER }) {
// The network graph must not be pruned while rapid sync completion is pending
if let Some(network_graph) = $gossip_sync.prunable_network_graph() {
- log_trace!($logger, "Pruning and persisting network graph.");
- network_graph.remove_stale_channels_and_tracking();
+ #[cfg(feature = "std")] {
+ log_trace!($logger, "Pruning and persisting network graph.");
+ network_graph.remove_stale_channels_and_tracking();
+ }
+ #[cfg(not(feature = "std"))] {
+ log_warn!($logger, "Not pruning network graph, consider enabling `std` or doing so manually with remove_stale_channels_and_tracking_with_time.");
+ log_trace!($logger, "Persisting network graph.");
+ }
if let Err(e) = $persister.persist_graph(network_graph) {
log_error!($logger, "Error: Failed to persist network graph, check your disk and permissions {}", e)
}
- last_prune_call = Instant::now();
+ last_prune_call = $get_timer(NETWORK_PRUNE_TIMER);
have_pruned = true;
}
}
- if last_scorer_persist_call.elapsed().as_secs() > SCORER_PERSIST_TIMER {
+ if $timer_elapsed(&mut last_scorer_persist_call, SCORER_PERSIST_TIMER) {
if let Some(ref scorer) = $scorer {
log_trace!($logger, "Persisting scorer");
if let Err(e) = $persister.persist_scorer(&scorer) {
log_error!($logger, "Error: Failed to persist scorer, check your disk and permissions {}", e)
}
}
- last_scorer_persist_call = Instant::now();
+ last_scorer_persist_call = $get_timer(SCORER_PERSIST_TIMER);
}
}
/// future which outputs true, the loop will exit and this function's future will complete.
///
/// See [`BackgroundProcessor::start`] for information on which actions this handles.
+///
+/// Requires the `futures` feature. Note that while this method is available without the `std`
+/// feature, doing so will skip calling [`NetworkGraph::remove_stale_channels_and_tracking`],
+/// you should call [`NetworkGraph::remove_stale_channels_and_tracking_with_time`] regularly
+/// manually instead.
#[cfg(feature = "futures")]
pub async fn process_events_async<
'a,
PGS: 'static + Deref<Target = P2PGossipSync<G, CA, L>> + Send + Sync,
RGS: 'static + Deref<Target = RapidGossipSync<G, L>> + Send,
UMH: 'static + Deref + Send + Sync,
- PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH>> + Send + Sync,
+ PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH, NS>> + Send + Sync,
S: 'static + Deref<Target = SC> + Send + Sync,
SC: WriteableScore<'a>,
- SleepFuture: core::future::Future<Output = bool>,
+ SleepFuture: core::future::Future<Output = bool> + core::marker::Unpin,
Sleeper: Fn(Duration) -> SleepFuture
>(
persister: PS, event_handler: EventHandler, chain_monitor: M, channel_manager: CM,
gossip_sync: GossipSync<PGS, RGS, G, CA, L>, peer_manager: PM, logger: L, scorer: Option<S>,
sleeper: Sleeper,
-) -> Result<(), std::io::Error>
+) -> Result<(), io::Error>
where
CA::Target: 'static + chain::Access,
CF::Target: 'static + chain::Filter,
false
}
}
+ }, |t| sleeper(Duration::from_secs(t)),
+ |fut: &mut SleepFuture, _| {
+ let mut waker = task::noop_waker();
+ let mut ctx = task::Context::from_waker(&mut waker);
+ core::pin::Pin::new(fut).poll(&mut ctx).is_ready()
})
}
+#[cfg(feature = "std")]
impl BackgroundProcessor {
/// Start a background thread that takes care of responsibilities enumerated in the [top-level
/// documentation].
PGS: 'static + Deref<Target = P2PGossipSync<G, CA, L>> + Send + Sync,
RGS: 'static + Deref<Target = RapidGossipSync<G, L>> + Send,
UMH: 'static + Deref + Send + Sync,
- PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH>> + Send + Sync,
+ PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH, NS>> + Send + Sync,
S: 'static + Deref<Target = SC> + Send + Sync,
SC: WriteableScore<'a>,
>(
define_run_body!(persister, chain_monitor, chain_monitor.process_pending_events(&event_handler),
channel_manager, channel_manager.process_pending_events(&event_handler),
gossip_sync, peer_manager, logger, scorer, stop_thread.load(Ordering::Acquire),
- channel_manager.await_persistable_update_timeout(Duration::from_millis(100)))
+ channel_manager.await_persistable_update_timeout(Duration::from_millis(100)),
+ |_| Instant::now(), |time: &Instant, dur| time.elapsed().as_secs() > dur)
});
Self { stop_thread: stop_thread_clone, thread_handle: Some(handle) }
}
}
}
+#[cfg(feature = "std")]
impl Drop for BackgroundProcessor {
fn drop(&mut self) {
self.stop_and_join_thread().unwrap();
}
}
-#[cfg(test)]
+#[cfg(all(feature = "std", test))]
mod tests {
use bitcoin::blockdata::block::BlockHeader;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::network::constants::Network;
use lightning::chain::{BestBlock, Confirm, chainmonitor};
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
- use lightning::chain::keysinterface::{InMemorySigner, Recipient, EntropySource, KeysManager, NodeSigner};
+ use lightning::chain::keysinterface::{InMemorySigner, EntropySource, KeysManager};
use lightning::chain::transaction::OutPoint;
use lightning::get_event_msg;
use lightning::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChainParameters, ChannelManager, SimpleArcChannelManager};
node: Arc<SimpleArcChannelManager<ChainMonitor, test_utils::TestBroadcaster, test_utils::TestFeeEstimator, test_utils::TestLogger>>,
p2p_gossip_sync: PGS,
rapid_gossip_sync: RGS,
- peer_manager: Arc<PeerManager<TestDescriptor, Arc<test_utils::TestChannelMessageHandler>, Arc<test_utils::TestRoutingMessageHandler>, IgnoringMessageHandler, Arc<test_utils::TestLogger>, IgnoringMessageHandler>>,
+ peer_manager: Arc<PeerManager<TestDescriptor, Arc<test_utils::TestChannelMessageHandler>, Arc<test_utils::TestRoutingMessageHandler>, IgnoringMessageHandler, Arc<test_utils::TestLogger>, IgnoringMessageHandler, Arc<KeysManager>>>,
chain_monitor: Arc<ChainMonitor>,
persister: Arc<FilesystemPersister>,
tx_broadcaster: Arc<test_utils::TestBroadcaster>,
let p2p_gossip_sync = Arc::new(P2PGossipSync::new(network_graph.clone(), Some(chain_source.clone()), logger.clone()));
let rapid_gossip_sync = Arc::new(RapidGossipSync::new(network_graph.clone()));
let msg_handler = MessageHandler { chan_handler: Arc::new(test_utils::TestChannelMessageHandler::new()), route_handler: Arc::new(test_utils::TestRoutingMessageHandler::new()), onion_message_handler: IgnoringMessageHandler{}};
- let peer_manager = Arc::new(PeerManager::new(msg_handler, keys_manager.get_node_secret(Recipient::Node).unwrap(), 0, &seed, logger.clone(), IgnoringMessageHandler{}));
+ let peer_manager = Arc::new(PeerManager::new(msg_handler, 0, &seed, logger.clone(), IgnoringMessageHandler{}, keys_manager.clone()));
let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, persister, tx_broadcaster, network_graph, logger, best_block, scorer };
nodes.push(node);
}
macro_rules! begin_open_channel {
($node_a: expr, $node_b: expr, $channel_value: expr) => {{
$node_a.node.create_channel($node_b.node.get_our_node_id(), $channel_value, 100, 42, None).unwrap();
- $node_b.node.handle_open_channel(&$node_a.node.get_our_node_id(), $node_a.node.init_features(), &get_event_msg!($node_a, MessageSendEvent::SendOpenChannel, $node_b.node.get_our_node_id()));
- $node_a.node.handle_accept_channel(&$node_b.node.get_our_node_id(), $node_b.node.init_features(), &get_event_msg!($node_b, MessageSendEvent::SendAcceptChannel, $node_a.node.get_our_node_id()));
+ $node_b.node.handle_open_channel(&$node_a.node.get_our_node_id(), &get_event_msg!($node_a, MessageSendEvent::SendOpenChannel, $node_b.node.get_our_node_id()));
+ $node_a.node.handle_accept_channel(&$node_b.node.get_our_node_id(), &get_event_msg!($node_b, MessageSendEvent::SendAcceptChannel, $node_a.node.get_our_node_id()));
}}
}
// Initiate the background processors to watch each node.
let data_dir = nodes[0].persister.get_data_dir();
let persister = Arc::new(Persister::new(data_dir));
- let router = DefaultRouter::new(Arc::clone(&nodes[0].network_graph), Arc::clone(&nodes[0].logger), random_seed_bytes, Arc::clone(&nodes[0].scorer));
+ let router = Arc::new(DefaultRouter::new(Arc::clone(&nodes[0].network_graph), Arc::clone(&nodes[0].logger), random_seed_bytes, Arc::clone(&nodes[0].scorer)));
let invoice_payer = Arc::new(InvoicePayer::new(Arc::clone(&nodes[0].node), router, Arc::clone(&nodes[0].logger), |_: _| {}, Retry::Attempts(2)));
let event_handler = Arc::clone(&invoice_payer);
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
chunked_transfer = { version = "1.4", optional = true }
[dev-dependencies]
+lightning = { version = "0.0.113", path = "../lightning", features = ["_test_utils"] }
tokio = { version = "~1.14", features = [ "macros", "rt" ] }
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
use secp256k1::PublicKey;
-use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiry, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
+use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
SemanticError, PrivateRoute, ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawInvoice,
constants, SignedRawInvoice, RawDataPart, InvoiceFeatures};
Ok(TaggedField::DescriptionHash(Sha256::from_base32(field_data)?)),
constants::TAG_EXPIRY_TIME =>
Ok(TaggedField::ExpiryTime(ExpiryTime::from_base32(field_data)?)),
- constants::TAG_MIN_FINAL_CLTV_EXPIRY =>
- Ok(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry::from_base32(field_data)?)),
+ constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA =>
+ Ok(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta::from_base32(field_data)?)),
constants::TAG_FALLBACK =>
Ok(TaggedField::Fallback(Fallback::from_base32(field_data)?)),
constants::TAG_PRIVATE_ROUTE =>
}
}
-impl FromBase32 for MinFinalCltvExpiry {
+impl FromBase32 for MinFinalCltvExpiryDelta {
type Err = ParseError;
- fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiry, ParseError> {
+ fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiryDelta, ParseError> {
let expiry = parse_int_be::<u64, u5>(field_data, 32);
if let Some(expiry) = expiry {
- Ok(MinFinalCltvExpiry(expiry))
+ Ok(MinFinalCltvExpiryDelta(expiry))
} else {
Err(ParseError::IntegerOverflowError)
}
}
#[test]
- fn test_parse_min_final_cltv_expiry() {
- use crate::MinFinalCltvExpiry;
+ fn test_parse_min_final_cltv_expiry_delta() {
+ use crate::MinFinalCltvExpiryDelta;
use bech32::FromBase32;
let input = from_bech32("pr".as_bytes());
- let expected = Ok(MinFinalCltvExpiry(35));
+ let expected = Ok(MinFinalCltvExpiryDelta(35));
- assert_eq!(MinFinalCltvExpiry::from_base32(&input), expected);
+ assert_eq!(MinFinalCltvExpiryDelta::from_base32(&input), expected);
}
#[test]
/// Default minimum final CLTV expiry as defined by [BOLT 11].
///
/// Note that this is *not* the same value as rust-lightning's minimum CLTV expiry, which is
-/// provided in [`MIN_FINAL_CLTV_EXPIRY`].
+/// provided in [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
///
/// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
-/// [`MIN_FINAL_CLTV_EXPIRY`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY
-pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY: u64 = 18;
+/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
+pub const DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA: u64 = 18;
/// Builder for `Invoice`s. It's the most convenient and advised way to use this library. It ensures
/// that only a semantically and syntactically correct Invoice can be built using it.
/// .payment_hash(payment_hash)
/// .payment_secret(payment_secret)
/// .current_timestamp()
-/// .min_final_cltv_expiry(144)
+/// .min_final_cltv_expiry_delta(144)
/// .build_signed(|hash| {
/// Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)
/// })
PayeePubKey(PayeePubKey),
DescriptionHash(Sha256),
ExpiryTime(ExpiryTime),
- MinFinalCltvExpiry(MinFinalCltvExpiry),
+ MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta),
Fallback(Fallback),
PrivateRoute(PrivateRoute),
PaymentSecret(PaymentSecret),
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct ExpiryTime(Duration);
-/// `min_final_cltv_expiry` to use for the last HTLC in the route
+/// `min_final_cltv_expiry_delta` to use for the last HTLC in the route
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
-pub struct MinFinalCltvExpiry(pub u64);
+pub struct MinFinalCltvExpiryDelta(pub u64);
// TODO: better types instead onf byte arrays
/// Fallback address in case no LN payment is possible
pub const TAG_PAYEE_PUB_KEY: u8 = 19;
pub const TAG_DESCRIPTION_HASH: u8 = 23;
pub const TAG_EXPIRY_TIME: u8 = 6;
- pub const TAG_MIN_FINAL_CLTV_EXPIRY: u8 = 24;
+ pub const TAG_MIN_FINAL_CLTV_EXPIRY_DELTA: u8 = 24;
pub const TAG_FALLBACK: u8 = 9;
pub const TAG_PRIVATE_ROUTE: u8 = 3;
pub const TAG_PAYMENT_SECRET: u8 = 16;
}
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, T, tb::False, S> {
- /// Sets `min_final_cltv_expiry`.
- pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
- self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry)));
+ /// Sets `min_final_cltv_expiry_delta`.
+ pub fn min_final_cltv_expiry_delta(mut self, min_final_cltv_expiry_delta: u64) -> InvoiceBuilder<D, H, T, tb::True, S> {
+ self.tagged_fields.push(TaggedField::MinFinalCltvExpiryDelta(MinFinalCltvExpiryDelta(min_final_cltv_expiry_delta)));
self.set_flags()
}
}
find_extract!(self.known_tagged_fields(), TaggedField::ExpiryTime(ref x), x)
}
- pub fn min_final_cltv_expiry(&self) -> Option<&MinFinalCltvExpiry> {
- find_extract!(self.known_tagged_fields(), TaggedField::MinFinalCltvExpiry(ref x), x)
+ pub fn min_final_cltv_expiry_delta(&self) -> Option<&MinFinalCltvExpiryDelta> {
+ find_extract!(self.known_tagged_fields(), TaggedField::MinFinalCltvExpiryDelta(ref x), x)
}
pub fn payment_secret(&self) -> Option<&PaymentSecret> {
.unwrap_or_else(|| Duration::new(u64::max_value(), 1_000_000_000 - 1)) < at_time
}
- /// Returns the invoice's `min_final_cltv_expiry` time, if present, otherwise
- /// [`DEFAULT_MIN_FINAL_CLTV_EXPIRY`].
- pub fn min_final_cltv_expiry(&self) -> u64 {
- self.signed_invoice.min_final_cltv_expiry()
+ /// Returns the invoice's `min_final_cltv_expiry_delta` time, if present, otherwise
+ /// [`DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA`].
+ pub fn min_final_cltv_expiry_delta(&self) -> u64 {
+ self.signed_invoice.min_final_cltv_expiry_delta()
.map(|x| x.0)
- .unwrap_or(DEFAULT_MIN_FINAL_CLTV_EXPIRY)
+ .unwrap_or(DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA)
}
/// Returns a list of all fallback addresses
TaggedField::PayeePubKey(_) => constants::TAG_PAYEE_PUB_KEY,
TaggedField::DescriptionHash(_) => constants::TAG_DESCRIPTION_HASH,
TaggedField::ExpiryTime(_) => constants::TAG_EXPIRY_TIME,
- TaggedField::MinFinalCltvExpiry(_) => constants::TAG_MIN_FINAL_CLTV_EXPIRY,
+ TaggedField::MinFinalCltvExpiryDelta(_) => constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA,
TaggedField::Fallback(_) => constants::TAG_FALLBACK,
TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE,
TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
///
/// [phantom invoices]: crate::utils::create_phantom_invoice
MissingRouteHints,
+
+ /// The provided `min_final_cltv_expiry_delta` was less than [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
+ ///
+ /// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
+ MinFinalCltvExpiryDeltaTooShort,
}
impl Display for CreationError {
CreationError::TimestampOutOfBounds => f.write_str("The Unix timestamp of the supplied date is less than zero or greater than 35-bits"),
CreationError::InvalidAmount => f.write_str("The supplied millisatoshi amount was greater than the total bitcoin supply"),
CreationError::MissingRouteHints => f.write_str("The invoice required route hints and they weren't provided"),
+ CreationError::MinFinalCltvExpiryDeltaTooShort => f.write_str(
+ "The supplied final CLTV expiry delta was less than LDK's `MIN_FINAL_CLTV_EXPIRY_DELTA`"),
}
}
}
let builder = InvoiceBuilder::new(Currency::Bitcoin)
.payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
.duration_since_epoch(Duration::from_secs(1234567))
- .min_final_cltv_expiry(144);
+ .min_final_cltv_expiry_delta(144);
let too_long_string = String::from_iter(
(0..1024).map(|_| '?')
.duration_since_epoch(Duration::from_secs(1234567))
.payee_pub_key(public_key.clone())
.expiry_time(Duration::from_secs(54321))
- .min_final_cltv_expiry(144)
+ .min_final_cltv_expiry_delta(144)
.fallback(Fallback::PubKeyHash([0;20]))
.private_route(route_1.clone())
.private_route(route_2.clone())
);
assert_eq!(invoice.payee_pub_key(), Some(&public_key));
assert_eq!(invoice.expiry_time(), Duration::from_secs(54321));
- assert_eq!(invoice.min_final_cltv_expiry(), 144);
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), 144);
assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]);
assert_eq!(invoice.private_routes(), vec![&PrivateRoute(route_1), &PrivateRoute(route_2)]);
assert_eq!(
.unwrap();
let invoice = Invoice::from_signed(signed_invoice).unwrap();
- assert_eq!(invoice.min_final_cltv_expiry(), DEFAULT_MIN_FINAL_CLTV_EXPIRY);
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), DEFAULT_MIN_FINAL_CLTV_EXPIRY_DELTA);
assert_eq!(invoice.expiry_time(), Duration::from_secs(DEFAULT_EXPIRY_TIME));
assert!(!invoice.would_expire(Duration::from_secs(1234568)));
}
//! # let router = FakeRouter {};
//! # let scorer = RefCell::new(FakeScorer {});
//! # let logger = FakeLogger {};
-//! let invoice_payer = InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+//! let invoice_payer = InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
//!
//! let invoice = "...";
//! if let Ok(invoice) = invoice.parse::<Invoice>() {
/// (C-not exported) generally all users should use the [`InvoicePayer`] type alias.
pub struct InvoicePayerUsingTime<
P: Deref,
- R: Router,
+ R: Deref,
L: Deref,
E: sealed::BaseEventHandler,
T: Time
> where
P::Target: Payer,
+ R::Target: Router,
L::Target: Logger,
{
payer: P,
Sending(PaymentSendFailure),
}
-impl<P: Deref, R: Router, L: Deref, E: sealed::BaseEventHandler, T: Time>
+impl<P: Deref, R: Deref, L: Deref, E: sealed::BaseEventHandler, T: Time>
InvoicePayerUsingTime<P, R, L, E, T>
where
P::Target: Payer,
+ R::Target: Router,
L::Target: Logger,
{
/// Creates an invoice payer that retries failed payment paths.
let route_params = RouteParameters {
payment_params,
final_value_msat: invoice.amount_milli_satoshis().or(amount_msats).unwrap(),
- final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32,
+ final_cltv_expiry_delta: invoice.min_final_cltv_expiry_delta() as u32,
};
let send_payment = |route: &Route| {
} else { false }
}
-impl<P: Deref, R: Router, L: Deref, E: sealed::BaseEventHandler, T: Time>
+impl<P: Deref, R: Deref, L: Deref, E: sealed::BaseEventHandler, T: Time>
InvoicePayerUsingTime<P, R, L, E, T>
where
P::Target: Payer,
+ R::Target: Router,
L::Target: Logger,
{
/// Returns a bool indicating whether the processed event should be forwarded to a user-provided
}
}
-impl<P: Deref, R: Router, L: Deref, E: EventHandler, T: Time>
+impl<P: Deref, R: Deref, L: Deref, E: EventHandler, T: Time>
EventHandler for InvoicePayerUsingTime<P, R, L, E, T>
where
P::Target: Payer,
+ R::Target: Router,
L::Target: Logger,
{
fn handle_event(&self, event: Event) {
}
}
-impl<P: Deref, R: Router, L: Deref, T: Time, F: Future, H: Fn(Event) -> F>
+impl<P: Deref, R: Deref, L: Deref, T: Time, F: Future, H: Fn(Event) -> F>
InvoicePayerUsingTime<P, R, L, H, T>
where
P::Target: Payer,
+ R::Target: Router,
L::Target: Logger,
{
/// Intercepts events required by the [`InvoicePayer`] and forwards them to the underlying event
use secp256k1::{SecretKey, PublicKey, Secp256k1};
use std::cell::RefCell;
use std::collections::VecDeque;
- use std::ops::DerefMut;
use std::time::{SystemTime, Duration};
use crate::time_utils::tests::SinceEpoch;
use crate::DEFAULT_EXPIRY_TIME;
.payment_hash(payment_hash)
.payment_secret(PaymentSecret([0; 32]))
.duration_since_epoch(duration_since_epoch())
- .min_final_cltv_expiry(144)
+ .min_final_cltv_expiry_delta(144)
.amount_milli_satoshis(128)
.build_signed(|hash| {
Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)
.payment_hash(payment_hash)
.payment_secret(PaymentSecret([0; 32]))
.duration_since_epoch(duration_since_epoch())
- .min_final_cltv_expiry(144)
+ .min_final_cltv_expiry_delta(144)
.build_signed(|hash| {
Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)
})
.payment_hash(payment_hash)
.payment_secret(PaymentSecret([0; 32]))
.duration_since_epoch(duration)
- .min_final_cltv_expiry(144)
+ .min_final_cltv_expiry_delta(144)
.amount_milli_satoshis(128)
.build_signed(|hash| {
Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(0));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
assert!(invoice_payer.pay_invoice(&invoice).is_ok());
}
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(PaymentId([1; 32]));
let event = Event::PaymentPathFailed {
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
type InvoicePayerUsingSinceEpoch <P, R, L, E> = InvoicePayerUsingTime::<P, R, L, E, SinceEpoch>;
let invoice_payer =
- InvoicePayerUsingSinceEpoch::new(&payer, router, &logger, event_handler, Retry::Timeout(Duration::from_secs(120)));
+ InvoicePayerUsingSinceEpoch::new(&payer, &router, &logger, event_handler, Retry::Timeout(Duration::from_secs(120)));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_preimage = PaymentPreimage([1; 32]);
let invoice = expired_invoice(payment_preimage);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
assert_eq!(*payer.attempts.borrow(), 1);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(0));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
let router = FailingRouter {};
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, |_: Event| {}, Retry::Attempts(0));
+ InvoicePayer::new(&payer, &router, &logger, |_: Event| {}, Retry::Attempts(0));
let payment_preimage = PaymentPreimage([1; 32]);
let invoice = invoice(payment_preimage);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, |_: Event| {}, Retry::Attempts(0));
+ InvoicePayer::new(&payer, &router, &logger, |_: Event| {}, Retry::Attempts(0));
match invoice_payer.pay_invoice(&invoice) {
Err(PaymentError::Sending(_)) => {},
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(0));
let payment_id =
Some(invoice_payer.pay_zero_value_invoice(&invoice, final_value_msat).unwrap());
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(0));
let payment_preimage = PaymentPreimage([1; 32]);
let invoice = invoice(payment_preimage);
let router = TestRouter::new(TestScorer::new());
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_pubkey(
pubkey, payment_preimage, final_value_msat, final_cltv_expiry_delta
let router = TestRouter::new(scorer);
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
let event = Event::PaymentPathFailed {
let router = TestRouter::new(scorer);
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
let payment_id = invoice_payer.pay_invoice(&invoice).unwrap();
let event = Event::PaymentPathSuccessful {
let router = TestRouter::new(scorer);
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(0));
// Make first invoice payment.
invoice_payer.pay_invoice(&payment_invoice).unwrap();
let router = TestRouter::new(scorer);
let logger = TestLogger::new();
let invoice_payer =
- InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+ InvoicePayer::new(&payer, &router, &logger, event_handler, Retry::Attempts(2));
// Fail 1st path, leave 2nd path inflight
let payment_id = Some(invoice_payer.pay_invoice(&payment_invoice).unwrap());
RouteParameters {
payment_params,
final_value_msat,
- final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32,
+ final_cltv_expiry_delta: invoice.min_final_cltv_expiry_delta() as u32,
}
}
}
router.expect_find_route(Ok(route.clone()));
let event_handler = |_: Event| { panic!(); };
- let invoice_payer = InvoicePayer::new(nodes[0].node, router, nodes[0].logger, event_handler, Retry::Attempts(1));
+ let invoice_payer = InvoicePayer::new(nodes[0].node, &router, nodes[0].logger, event_handler, Retry::Attempts(1));
assert!(invoice_payer.pay_invoice(&create_invoice_from_channelmanager_and_duration_since_epoch(
&nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::Bitcoin,
- Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600).unwrap())
+ Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600, None).unwrap())
.is_ok());
let htlc_msgs = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(htlc_msgs.len(), 2);
router.expect_find_route(Ok(route.clone()));
let event_handler = |_: Event| { panic!(); };
- let invoice_payer = InvoicePayer::new(nodes[0].node, router, nodes[0].logger, event_handler, Retry::Attempts(1));
+ let invoice_payer = InvoicePayer::new(nodes[0].node, &router, nodes[0].logger, event_handler, Retry::Attempts(1));
assert!(invoice_payer.pay_invoice(&create_invoice_from_channelmanager_and_duration_since_epoch(
&nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::Bitcoin,
- Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600).unwrap())
+ Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600, None).unwrap())
.is_ok());
let htlc_msgs = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(htlc_msgs.len(), 2);
let event_checker = expected_events.borrow_mut().pop_front().unwrap();
event_checker(event);
};
- let invoice_payer = InvoicePayer::new(nodes[0].node, router, nodes[0].logger, event_handler, Retry::Attempts(1));
+ let invoice_payer = InvoicePayer::new(nodes[0].node, &router, nodes[0].logger, event_handler, Retry::Attempts(1));
assert!(invoice_payer.pay_invoice(&create_invoice_from_channelmanager_and_duration_since_epoch(
&nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::Bitcoin,
- Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600).unwrap())
+ Some(100_010_000), "Invoice".to_string(), duration_since_epoch(), 3600, None).unwrap())
.is_ok());
let htlc_updates = SendEvent::from_node(&nodes[0]);
check_added_monitors!(nodes[0], 1);
use bech32::{ToBase32, u5, WriteBase32, Base32Len};
use crate::prelude::*;
-use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiry, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
+use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiryDelta, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawInvoice, RawDataPart};
/// Converts a stream of bytes written to it to base32. On finalization the according padding will
}
}
-impl ToBase32 for MinFinalCltvExpiry {
+impl ToBase32 for MinFinalCltvExpiryDelta {
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
writer.write(&encode_int_be_base32(self.0))
}
}
-impl Base32Len for MinFinalCltvExpiry {
+impl Base32Len for MinFinalCltvExpiryDelta {
fn base32_len(&self) -> usize {
encoded_int_be_base32_size(self.0)
}
TaggedField::ExpiryTime(ref duration) => {
write_tagged_field(writer, constants::TAG_EXPIRY_TIME, duration)
},
- TaggedField::MinFinalCltvExpiry(ref expiry) => {
- write_tagged_field(writer, constants::TAG_MIN_FINAL_CLTV_EXPIRY, expiry)
+ TaggedField::MinFinalCltvExpiryDelta(ref expiry) => {
+ write_tagged_field(writer, constants::TAG_MIN_FINAL_CLTV_EXPIRY_DELTA, expiry)
},
TaggedField::Fallback(ref fallback_address) => {
write_tagged_field(writer, constants::TAG_FALLBACK, fallback_address)
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::chain::keysinterface::{Recipient, NodeSigner, SignerProvider, EntropySource};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY};
-#[cfg(feature = "std")]
+use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY_DELTA};
use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA};
use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey};
use lightning::routing::gossip::RoutingFees;
use core::ops::Deref;
use core::time::Duration;
-#[cfg(feature = "std")]
/// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
/// See [`PhantomKeysManager`] for more information on phantom node payments.
///
///
/// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for
/// in excess of the current time.
+///
+/// 'duration_since_epoch' is the current time since epoch in seconds.
+///
+/// ['std::time::SystemTime'] has been removed to allow this function to be used in a 'no_std' environment,
+/// where [`std::time::SystemTime`] is not available and the current time is supplied by the caller.
+///
+/// You can specify a custom `min_final_cltv_expiry_delta`, or let LDK default it to
+/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. The provided expiry must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`] - 3.
+/// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block
+/// confirmations during routing.
///
/// Note that the provided `keys_manager`'s `NodeSigner` implementation must support phantom
/// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
/// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment
/// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash
/// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
+/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
pub fn create_phantom_invoice<ES: Deref, NS: Deref, L: Deref>(
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: String,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
- node_signer: NS, logger: L, network: Currency,
+ node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
) -> Result<Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
let description = InvoiceDescription::Direct(&description,);
_create_phantom_invoice::<ES, NS, L>(
amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints,
- entropy_source, node_signer, logger, network,
+ entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch,
)
}
-#[cfg(feature = "std")]
/// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
/// See [`PhantomKeysManager`] for more information on phantom node payments.
///
///
/// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for
/// in excess of the current time.
+///
+/// 'duration_since_epoch' is the current time since epoch in seconds.
+///
+/// ['std::time::SystemTime'] has been removed to allow this function to be used in a 'no_std' environment,
+/// where [`std::time::SystemTime`] is not available and the current time is supplied by the caller.
///
/// Note that the provided `keys_manager`'s `NodeSigner` implementation must support phantom
/// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
pub fn create_phantom_invoice_with_description_hash<ES: Deref, NS: Deref, L: Deref>(
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, invoice_expiry_delta_secs: u32,
description_hash: Sha256, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
- node_signer: NS, logger: L, network: Currency
+ node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
) -> Result<Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
_create_phantom_invoice::<ES, NS, L>(
amt_msat, payment_hash, InvoiceDescription::Hash(&description_hash),
invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network,
+ min_final_cltv_expiry_delta, duration_since_epoch,
)
}
-#[cfg(feature = "std")]
fn _create_phantom_invoice<ES: Deref, NS: Deref, L: Deref>(
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: InvoiceDescription,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
- node_signer: NS, logger: L, network: Currency,
+ node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
) -> Result<Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
L::Target: Logger,
{
- use std::time::{SystemTime, UNIX_EPOCH};
if phantom_route_hints.len() == 0 {
return Err(SignOrCreationError::CreationError(
));
}
+ if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap().saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA {
+ return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
+ }
+
let invoice = match description {
InvoiceDescription::Direct(description) => {
InvoiceBuilder::new(network).description(description.0.clone())
amt_msat,
payment_hash,
invoice_expiry_delta_secs,
- SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .expect("Time must be > 1970")
+ duration_since_epoch
.as_secs(),
+ min_final_cltv_expiry_delta,
)
.map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
(payment_hash, payment_secret)
amt_msat,
invoice_expiry_delta_secs,
&entropy_source,
- SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .expect("Time must be > 1970")
+ duration_since_epoch
.as_secs(),
+ min_final_cltv_expiry_delta,
)
.map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?
};
phantom_route_hints.len(), log_bytes!(payment_hash.0));
let mut invoice = invoice
- .current_timestamp()
+ .duration_since_epoch(duration_since_epoch)
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
.payment_secret(payment_secret)
- .min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into())
+ .min_final_cltv_expiry_delta(
+ // Add a buffer of 3 to the delta if present, otherwise use LDK's minimum.
+ min_final_cltv_expiry_delta.map(|x| x.saturating_add(3)).unwrap_or(MIN_FINAL_CLTV_EXPIRY_DELTA).into())
.expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into()));
if let Some(amt) = amt_msat {
invoice = invoice.amount_milli_satoshis(amt);
///
/// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for
/// in excess of the current time.
+///
+/// You can specify a custom `min_final_cltv_expiry_delta`, or let LDK default it to
+/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. The provided expiry must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
+/// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block
+/// confirmations during routing.
+///
+/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
pub fn create_invoice_from_channelmanager<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
- network: Currency, amt_msat: Option<u64>, description: String, invoice_expiry_delta_secs: u32
+ network: Currency, amt_msat: Option<u64>, description: String, invoice_expiry_delta_secs: u32,
+ min_final_cltv_expiry_delta: Option<u16>,
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
.expect("for the foreseeable future this shouldn't happen");
create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- description, duration, invoice_expiry_delta_secs
+ description, duration, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
)
}
///
/// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for
/// in excess of the current time.
+///
+/// You can specify a custom `min_final_cltv_expiry_delta`, or let LDK default it to
+/// [`MIN_FINAL_CLTV_EXPIRY_DELTA`]. The provided expiry must be at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
+/// Note that LDK will add a buffer of 3 blocks to the delta to allow for up to a few new block
+/// confirmations during routing.
+///
+/// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA
pub fn create_invoice_from_channelmanager_with_description_hash<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
- invoice_expiry_delta_secs: u32
+ invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- description_hash, duration, invoice_expiry_delta_secs
+ description_hash, duration, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
)
}
pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
- duration_since_epoch: Duration, invoice_expiry_delta_secs: u32
+ duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
_create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
InvoiceDescription::Hash(&description_hash),
- duration_since_epoch, invoice_expiry_delta_secs
+ duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
)
}
pub fn create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
- invoice_expiry_delta_secs: u32
+ invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
- duration_since_epoch, invoice_expiry_delta_secs
+ duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
)
}
fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: InvoiceDescription,
- duration_since_epoch: Duration, invoice_expiry_delta_secs: u32
+ duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
R::Target: Router,
L::Target: Logger,
{
+ if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap().saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA {
+ return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
+ }
+
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
// supply.
let (payment_hash, payment_secret) = channelmanager
- .create_inbound_payment(amt_msat, invoice_expiry_delta_secs)
+ .create_inbound_payment(amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta)
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
- channelmanager, node_signer, logger, network, amt_msat, description, duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret)
+ channelmanager, node_signer, logger, network, amt_msat, description, duration_since_epoch,
+ invoice_expiry_delta_secs, payment_hash, payment_secret, min_final_cltv_expiry_delta)
}
/// See [`create_invoice_from_channelmanager_and_duration_since_epoch`]
pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
- invoice_expiry_delta_secs: u32, payment_hash: PaymentHash
+ invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option<u16>,
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
L::Target: Logger,
{
let payment_secret = channelmanager
- .create_inbound_payment_for_hash(payment_hash,amt_msat, invoice_expiry_delta_secs)
+ .create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs,
+ min_final_cltv_expiry_delta)
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
channelmanager, node_signer, logger, network, amt_msat,
InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
- duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret
+ duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret,
+ min_final_cltv_expiry_delta,
)
}
fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: InvoiceDescription, duration_since_epoch: Duration,
- invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret
+ invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret,
+ min_final_cltv_expiry_delta: Option<u16>,
) -> Result<Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
let our_node_pubkey = channelmanager.get_our_node_id();
let channels = channelmanager.list_channels();
+ if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap().saturating_add(3) < MIN_FINAL_CLTV_EXPIRY_DELTA {
+ return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
+ }
+
log_trace!(logger, "Creating invoice with payment hash {}", log_bytes!(payment_hash.0));
let invoice = match description {
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
.payment_secret(payment_secret)
.basic_mpp()
- .min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into())
+ .min_final_cltv_expiry_delta(
+ // Add a buffer of 3 to the delta if present, otherwise use LDK's minimum.
+ min_final_cltv_expiry_delta.map(|x| x.saturating_add(3)).unwrap_or(MIN_FINAL_CLTV_EXPIRY_DELTA).into())
.expiry_time(Duration::from_secs(invoice_expiry_delta_secs.into()));
if let Some(amt) = amt_msat {
invoice = invoice.amount_milli_satoshis(amt);
#[cfg(test)]
mod test {
use core::time::Duration;
- use crate::{Currency, Description, InvoiceDescription};
+ use crate::{Currency, Description, InvoiceDescription, SignOrCreationError, CreationError};
use bitcoin_hashes::{Hash, sha256};
use bitcoin_hashes::sha256::Hash as Sha256;
use lightning::chain::keysinterface::{EntropySource, PhantomKeysManager};
use lightning::ln::{PaymentPreimage, PaymentHash};
- use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY, PaymentId};
+ use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId};
use lightning::ln::functional_test_utils::*;
use lightning::ln::msgs::ChannelMessageHandler;
use lightning::routing::router::{PaymentParameters, RouteParameters, find_route};
let invoice = create_invoice_from_channelmanager_and_duration_since_epoch(
&nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet,
Some(10_000), "test".to_string(), Duration::from_secs(1234567),
- non_default_invoice_expiry_secs).unwrap();
+ non_default_invoice_expiry_secs, None).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
- assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
+ // If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`.
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
let route_params = RouteParameters {
payment_params,
final_value_msat: invoice.amount_milli_satoshis().unwrap(),
- final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32,
+ final_cltv_expiry_delta: invoice.min_final_cltv_expiry_delta() as u32,
};
let first_hops = nodes[0].node.list_usable_channels();
let network_graph = &node_cfgs[0].network_graph;
assert_eq!(events.len(), 2);
}
+ fn do_create_invoice_min_final_cltv_delta(with_custom_delta: bool) {
+ 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);
+ let custom_min_final_cltv_expiry_delta = Some(50);
+
+ let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch(
+ &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet,
+ Some(10_000), "".into(), Duration::from_secs(1234567), 3600,
+ if with_custom_delta { custom_min_final_cltv_expiry_delta } else { None },
+ ).unwrap();
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), if with_custom_delta {
+ custom_min_final_cltv_expiry_delta.unwrap() + 3 /* Buffer */} else { MIN_FINAL_CLTV_EXPIRY_DELTA } as u64);
+ }
+
+ #[test]
+ fn test_create_invoice_custom_min_final_cltv_delta() {
+ do_create_invoice_min_final_cltv_delta(true);
+ do_create_invoice_min_final_cltv_delta(false);
+ }
+
+ #[test]
+ fn create_invoice_min_final_cltv_delta_equals_htlc_fail_buffer() {
+ 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);
+ let custom_min_final_cltv_expiry_delta = Some(21);
+
+ let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch(
+ &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet,
+ Some(10_000), "".into(), Duration::from_secs(1234567), 3600,
+ custom_min_final_cltv_expiry_delta,
+ ).unwrap();
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
+ }
+
#[test]
fn test_create_invoice_with_description_hash() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let description_hash = crate::Sha256(Hash::hash("Testing description_hash".as_bytes()));
let invoice = crate::utils::create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
&nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet,
- Some(10_000), description_hash, Duration::from_secs(1234567), 3600
+ Some(10_000), description_hash, Duration::from_secs(1234567), 3600, None,
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
- assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
}
let invoice = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
&nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet,
Some(10_000), "test".to_string(), Duration::from_secs(1234567), 3600,
- payment_hash
+ payment_hash, None,
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
- assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap());
}
private_chan_cfg.channel_handshake_config.announced_channel = false;
let temporary_channel_id = nodes[2].node.create_channel(nodes[0].node.get_our_node_id(), 1_000_000, 500_000_000, 42, Some(private_chan_cfg)).unwrap();
let open_channel = get_event_msg!(nodes[2], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_open_channel(&nodes[2].node.get_our_node_id(), nodes[2].node.init_features(), &open_channel);
+ nodes[0].node.handle_open_channel(&nodes[2].node.get_our_node_id(), &open_channel);
let accept_channel = get_event_msg!(nodes[0], MessageSendEvent::SendAcceptChannel, nodes[2].node.get_our_node_id());
- nodes[2].node.handle_accept_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &accept_channel);
+ nodes[2].node.handle_accept_channel(&nodes[0].node.get_our_node_id(), &accept_channel);
let tx = sign_funding_transaction(&nodes[2], &nodes[0], 1_000_000, temporary_channel_id);
let invoice = create_invoice_from_channelmanager_and_duration_since_epoch(
&invoice_node.node, invoice_node.keys_manager, invoice_node.logger,
Currency::BitcoinTestnet, invoice_amt, "test".to_string(), Duration::from_secs(1234567),
- 3600).unwrap();
+ 3600, None).unwrap();
let hints = invoice.private_routes();
for hint in hints {
let invoice =
crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(
Some(payment_amt), payment_hash, "test".to_string(), non_default_invoice_expiry_secs,
- route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, Currency::BitcoinTestnet
+ route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger,
+ Currency::BitcoinTestnet, None, Duration::from_secs(1234567)
).unwrap();
let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().into_inner()), *invoice.payment_secret());
let payment_preimage = if user_generated_pmt_hash {
nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap()
};
- assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.route_hints().len(), 2);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
let params = RouteParameters {
payment_params,
final_value_msat: invoice.amount_milli_satoshis().unwrap(),
- final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32,
+ final_cltv_expiry_delta: invoice.min_final_cltv_expiry_delta() as u32,
};
let first_hops = nodes[0].node.list_usable_channels();
let network_graph = &node_cfgs[0].network_graph;
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001);
let payment_amt = 20_000;
- let (payment_hash, _payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
+ let (payment_hash, _payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600, None).unwrap();
let route_hints = vec![
nodes[1].node.get_phantom_route_hints(),
nodes[2].node.get_phantom_route_hints(),
];
- let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), Some(payment_hash), "test".to_string(), 3600, route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, Currency::BitcoinTestnet).unwrap();
+ let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface,
+ &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), Some(payment_hash),
+ "test".to_string(), 3600, route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager,
+ &nodes[1].logger, Currency::BitcoinTestnet, None, Duration::from_secs(1234567)).unwrap();
let chan_0_1 = &nodes[1].node.list_usable_channels()[0];
assert_eq!(invoice.route_hints()[0].0[0].htlc_minimum_msat, chan_0_1.inbound_htlc_minimum_msat);
&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger,
>(
Some(payment_amt), None, non_default_invoice_expiry_secs, description_hash,
- route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger, Currency::BitcoinTestnet
+ route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager, &nodes[1].logger,
+ Currency::BitcoinTestnet, None, Duration::from_secs(1234567),
)
.unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(200_000));
- assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes()))));
}
+ #[test]
+ #[cfg(feature = "std")]
+ fn create_phantom_invoice_with_custom_payment_hash_and_custom_min_final_cltv_delta() {
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let payment_amt = 20_000;
+ let route_hints = vec![
+ nodes[1].node.get_phantom_route_hints(),
+ nodes[2].node.get_phantom_route_hints(),
+ ];
+ let user_payment_preimage = PaymentPreimage([1; 32]);
+ let payment_hash = Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).into_inner()));
+ let non_default_invoice_expiry_secs = 4200;
+ let min_final_cltv_expiry_delta = Some(100);
+ let duration_since_epoch = Duration::from_secs(1234567);
+ let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface,
+ &test_utils::TestKeysInterface, &test_utils::TestLogger>(Some(payment_amt), payment_hash,
+ "".to_string(), non_default_invoice_expiry_secs, route_hints, &nodes[1].keys_manager, &nodes[1].keys_manager,
+ &nodes[1].logger, Currency::BitcoinTestnet, min_final_cltv_expiry_delta, duration_since_epoch).unwrap();
+ assert_eq!(invoice.amount_pico_btc(), Some(200_000));
+ assert_eq!(invoice.min_final_cltv_expiry_delta(), (min_final_cltv_expiry_delta.unwrap() + 3) as u64);
+ assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
+ }
+
#[test]
#[cfg(feature = "std")]
fn test_multi_node_hints_includes_single_channels_to_participating_nodes() {
private_chan_cfg.channel_handshake_config.announced_channel = false;
let temporary_channel_id = nodes[1].node.create_channel(nodes[3].node.get_our_node_id(), 1_000_000, 500_000_000, 42, Some(private_chan_cfg)).unwrap();
let open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[3].node.get_our_node_id());
- nodes[3].node.handle_open_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &open_channel);
+ nodes[3].node.handle_open_channel(&nodes[1].node.get_our_node_id(), &open_channel);
let accept_channel = get_event_msg!(nodes[3], MessageSendEvent::SendAcceptChannel, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_accept_channel(&nodes[3].node.get_our_node_id(), nodes[3].node.init_features(), &accept_channel);
+ nodes[1].node.handle_accept_channel(&nodes[3].node.get_our_node_id(), &accept_channel);
let tx = sign_funding_transaction(&nodes[1], &nodes[3], 1_000_000, temporary_channel_id);
.map(|route_hint| route_hint.phantom_scid)
.collect::<HashSet<u64>>();
- let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(invoice_amt, None, "test".to_string(), 3600, phantom_route_hints, &invoice_node.keys_manager, &invoice_node.keys_manager, &invoice_node.logger, Currency::BitcoinTestnet).unwrap();
+ let invoice = crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface,
+ &test_utils::TestKeysInterface, &test_utils::TestLogger>(invoice_amt, None, "test".to_string(),
+ 3600, phantom_route_hints, &invoice_node.keys_manager, &invoice_node.keys_manager,
+ &invoice_node.logger, Currency::BitcoinTestnet, None, Duration::from_secs(1234567)).unwrap();
let invoice_hints = invoice.private_routes();
}
assert!(chan_ids_to_match.is_empty(), "Unmatched short channel ids: {:?}", chan_ids_to_match);
}
+
+ #[test]
+ fn test_create_invoice_fails_with_invalid_custom_min_final_cltv_expiry_delta() {
+ 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);
+ let result = crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch(
+ &nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet,
+ Some(10_000), "Some description".into(), Duration::from_secs(1234567), 3600, Some(MIN_FINAL_CLTV_EXPIRY_DELTA - 4),
+ );
+ match result {
+ Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort)) => {},
+ _ => panic!(),
+ }
+ }
}
"462264ede7e14047e9b249da94fefc47f41f7d02ee9b091815a5506bc8abf75f"
).unwrap())
.expiry_time(Duration::from_secs(604800))
- .min_final_cltv_expiry(10)
+ .min_final_cltv_expiry_delta(10)
.description("Blockstream Store: 88.85 USD for Blockstream Ledger Nano S x 1, \"Back In My Day\" Sticker x 2, \"I Got Lightning Working\" Sticker x 2 and 1 more items".to_owned())
.private_route(RouteHint(vec![RouteHintHop {
src_node_id: PublicKey::from_slice(&hex::decode(
[dev-dependencies]
tokio = { version = "~1.14", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
+lightning = { version = "0.0.113", path = "../lightning", features = ["_test_utils"] }
//! type TxBroadcaster = dyn lightning::chain::chaininterface::BroadcasterInterface + Send + Sync;
//! type FeeEstimator = dyn lightning::chain::chaininterface::FeeEstimator + Send + Sync;
//! type Logger = dyn lightning::util::logger::Logger + Send + Sync;
+//! type NodeSigner = dyn lightning::chain::keysinterface::NodeSigner + Send + Sync;
//! type ChainAccess = dyn lightning::chain::Access + Send + Sync;
//! type ChainFilter = dyn lightning::chain::Filter + Send + Sync;
//! type DataPersister = dyn lightning::chain::chainmonitor::Persist<lightning::chain::keysinterface::InMemorySigner> + Send + Sync;
use tokio::sync::mpsc;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
+use lightning::chain::keysinterface::NodeSigner;
use lightning::ln::peer_handler;
use lightning::ln::peer_handler::SocketDescriptor as LnSocketTrait;
use lightning::ln::peer_handler::CustomMessageHandler;
id: u64,
}
impl Connection {
- async fn poll_event_process<CMH, RMH, OMH, L, UMH>(peer_manager: Arc<peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH>>, mut event_receiver: mpsc::Receiver<()>) where
+ async fn poll_event_process<PM, CMH, RMH, OMH, L, UMH, NS>(
+ peer_manager: PM,
+ mut event_receiver: mpsc::Receiver<()>,
+ ) where
+ PM: Deref<Target = peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH, NS>> + 'static + Send + Sync,
CMH: Deref + 'static + Send + Sync,
RMH: Deref + 'static + Send + Sync,
OMH: Deref + 'static + Send + Sync,
L: Deref + 'static + Send + Sync,
UMH: Deref + 'static + Send + Sync,
+ NS: Deref + 'static + Send + Sync,
CMH::Target: ChannelMessageHandler + Send + Sync,
RMH::Target: RoutingMessageHandler + Send + Sync,
OMH::Target: OnionMessageHandler + Send + Sync,
L::Target: Logger + Send + Sync,
UMH::Target: CustomMessageHandler + Send + Sync,
- {
+ NS::Target: NodeSigner + Send + Sync,
+ {
loop {
if event_receiver.recv().await.is_none() {
return;
}
}
- async fn schedule_read<CMH, RMH, OMH, L, UMH>(peer_manager: Arc<peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH>>, us: Arc<Mutex<Self>>, mut reader: io::ReadHalf<TcpStream>, mut read_wake_receiver: mpsc::Receiver<()>, mut write_avail_receiver: mpsc::Receiver<()>) where
+ async fn schedule_read<PM, CMH, RMH, OMH, L, UMH, NS>(
+ peer_manager: PM,
+ us: Arc<Mutex<Self>>,
+ mut reader: io::ReadHalf<TcpStream>,
+ mut read_wake_receiver: mpsc::Receiver<()>,
+ mut write_avail_receiver: mpsc::Receiver<()>,
+ ) where
+ PM: Deref<Target = peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH, NS>> + 'static + Send + Sync + Clone,
CMH: Deref + 'static + Send + Sync,
RMH: Deref + 'static + Send + Sync,
OMH: Deref + 'static + Send + Sync,
L: Deref + 'static + Send + Sync,
UMH: Deref + 'static + Send + Sync,
+ NS: Deref + 'static + Send + Sync,
CMH::Target: ChannelMessageHandler + 'static + Send + Sync,
RMH::Target: RoutingMessageHandler + 'static + Send + Sync,
OMH::Target: OnionMessageHandler + 'static + Send + Sync,
L::Target: Logger + 'static + Send + Sync,
UMH::Target: CustomMessageHandler + 'static + Send + Sync,
- {
+ NS::Target: NodeSigner + 'static + Send + Sync,
+ {
// Create a waker to wake up poll_event_process, above
let (event_waker, event_receiver) = mpsc::channel(1);
- tokio::spawn(Self::poll_event_process(Arc::clone(&peer_manager), event_receiver));
+ tokio::spawn(Self::poll_event_process(peer_manager.clone(), event_receiver));
// 8KB is nice and big but also should never cause any issues with stack overflowing.
let mut buf = [0; 8192];
/// The returned future will complete when the peer is disconnected and associated handling
/// futures are freed, though, because all processing futures are spawned with tokio::spawn, you do
/// not need to poll the provided future in order to make progress.
-pub fn setup_inbound<CMH, RMH, OMH, L, UMH>(peer_manager: Arc<peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH>>, stream: StdTcpStream) -> impl std::future::Future<Output=()> where
+pub fn setup_inbound<PM, CMH, RMH, OMH, L, UMH, NS>(
+ peer_manager: PM,
+ stream: StdTcpStream,
+) -> impl std::future::Future<Output=()> where
+ PM: Deref<Target = peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH, NS>> + 'static + Send + Sync + Clone,
CMH: Deref + 'static + Send + Sync,
RMH: Deref + 'static + Send + Sync,
OMH: Deref + 'static + Send + Sync,
L: Deref + 'static + Send + Sync,
UMH: Deref + 'static + Send + Sync,
+ NS: Deref + 'static + Send + Sync,
CMH::Target: ChannelMessageHandler + Send + Sync,
RMH::Target: RoutingMessageHandler + Send + Sync,
OMH::Target: OnionMessageHandler + Send + Sync,
L::Target: Logger + Send + Sync,
UMH::Target: CustomMessageHandler + Send + Sync,
+ NS::Target: NodeSigner + Send + Sync,
{
let remote_addr = get_addr_from_stream(&stream);
let (reader, write_receiver, read_receiver, us) = Connection::new(stream);
- #[cfg(debug_assertions)]
+ #[cfg(test)]
let last_us = Arc::clone(&us);
let handle_opt = if let Ok(_) = peer_manager.new_inbound_connection(SocketDescriptor::new(us.clone()), remote_addr) {
// socket shutdown(). Still, as a check during testing, to make sure tokio doesn't
// keep too many wakers around, this makes sense. The race should be rare (we do
// some work after shutdown()) and an error would be a major memory leak.
- #[cfg(debug_assertions)]
- assert!(Arc::try_unwrap(last_us).is_ok());
+ #[cfg(test)]
+ debug_assert!(Arc::try_unwrap(last_us).is_ok());
}
}
}
/// The returned future will complete when the peer is disconnected and associated handling
/// futures are freed, though, because all processing futures are spawned with tokio::spawn, you do
/// not need to poll the provided future in order to make progress.
-pub fn setup_outbound<CMH, RMH, OMH, L, UMH>(peer_manager: Arc<peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH>>, their_node_id: PublicKey, stream: StdTcpStream) -> impl std::future::Future<Output=()> where
+pub fn setup_outbound<PM, CMH, RMH, OMH, L, UMH, NS>(
+ peer_manager: PM,
+ their_node_id: PublicKey,
+ stream: StdTcpStream,
+) -> impl std::future::Future<Output=()> where
+ PM: Deref<Target = peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH, NS>> + 'static + Send + Sync + Clone,
CMH: Deref + 'static + Send + Sync,
RMH: Deref + 'static + Send + Sync,
OMH: Deref + 'static + Send + Sync,
L: Deref + 'static + Send + Sync,
UMH: Deref + 'static + Send + Sync,
+ NS: Deref + 'static + Send + Sync,
CMH::Target: ChannelMessageHandler + Send + Sync,
RMH::Target: RoutingMessageHandler + Send + Sync,
OMH::Target: OnionMessageHandler + Send + Sync,
L::Target: Logger + Send + Sync,
UMH::Target: CustomMessageHandler + Send + Sync,
+ NS::Target: NodeSigner + Send + Sync,
{
let remote_addr = get_addr_from_stream(&stream);
let (reader, mut write_receiver, read_receiver, us) = Connection::new(stream);
- #[cfg(debug_assertions)]
+ #[cfg(test)]
let last_us = Arc::clone(&us);
let handle_opt = if let Ok(initial_send) = peer_manager.new_outbound_connection(their_node_id, SocketDescriptor::new(us.clone()), remote_addr) {
Some(tokio::spawn(async move {
// socket shutdown(). Still, as a check during testing, to make sure tokio doesn't
// keep too many wakers around, this makes sense. The race should be rare (we do
// some work after shutdown()) and an error would be a major memory leak.
- #[cfg(debug_assertions)]
- assert!(Arc::try_unwrap(last_us).is_ok());
+ #[cfg(test)]
+ debug_assert!(Arc::try_unwrap(last_us).is_ok());
}
}
}
/// disconnected and associated handling futures are freed, though, because all processing in said
/// futures are spawned with tokio::spawn, you do not need to poll the second future in order to
/// make progress.
-pub async fn connect_outbound<CMH, RMH, OMH, L, UMH>(peer_manager: Arc<peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH>>, their_node_id: PublicKey, addr: SocketAddr) -> Option<impl std::future::Future<Output=()>> where
+pub async fn connect_outbound<PM, CMH, RMH, OMH, L, UMH, NS>(
+ peer_manager: PM,
+ their_node_id: PublicKey,
+ addr: SocketAddr,
+) -> Option<impl std::future::Future<Output=()>> where
+ PM: Deref<Target = peer_handler::PeerManager<SocketDescriptor, CMH, RMH, OMH, L, UMH, NS>> + 'static + Send + Sync + Clone,
CMH: Deref + 'static + Send + Sync,
RMH: Deref + 'static + Send + Sync,
OMH: Deref + 'static + Send + Sync,
L: Deref + 'static + Send + Sync,
UMH: Deref + 'static + Send + Sync,
+ NS: Deref + 'static + Send + Sync,
CMH::Target: ChannelMessageHandler + Send + Sync,
RMH::Target: RoutingMessageHandler + Send + Sync,
OMH::Target: OnionMessageHandler + Send + Sync,
L::Target: Logger + Send + Sync,
UMH::Target: CustomMessageHandler + Send + Sync,
+ NS::Target: NodeSigner + Send + Sync,
{
if let Ok(Ok(stream)) = time::timeout(Duration::from_secs(10), async { TcpStream::connect(&addr).await.map(|s| s.into_std().unwrap()) }).await {
Some(setup_outbound(peer_manager, their_node_id, stream))
use lightning::ln::peer_handler::{MessageHandler, PeerManager};
use lightning::ln::features::NodeFeatures;
use lightning::util::events::*;
+ use lightning::util::test_utils::TestNodeSigner;
use bitcoin::secp256k1::{Secp256k1, SecretKey, PublicKey};
use tokio::sync::mpsc;
fn provided_init_features(&self, _their_node_id: &PublicKey) -> InitFeatures { InitFeatures::empty() }
}
impl ChannelMessageHandler for MsgHandler {
- fn handle_open_channel(&self, _their_node_id: &PublicKey, _their_features: InitFeatures, _msg: &OpenChannel) {}
- fn handle_accept_channel(&self, _their_node_id: &PublicKey, _their_features: InitFeatures, _msg: &AcceptChannel) {}
+ fn handle_open_channel(&self, _their_node_id: &PublicKey, _msg: &OpenChannel) {}
+ fn handle_accept_channel(&self, _their_node_id: &PublicKey, _msg: &AcceptChannel) {}
fn handle_funding_created(&self, _their_node_id: &PublicKey, _msg: &FundingCreated) {}
fn handle_funding_signed(&self, _their_node_id: &PublicKey, _msg: &FundingSigned) {}
fn handle_channel_ready(&self, _their_node_id: &PublicKey, _msg: &ChannelReady) {}
- fn handle_shutdown(&self, _their_node_id: &PublicKey, _their_features: &InitFeatures, _msg: &Shutdown) {}
+ fn handle_shutdown(&self, _their_node_id: &PublicKey, _msg: &Shutdown) {}
fn handle_closing_signed(&self, _their_node_id: &PublicKey, _msg: &ClosingSigned) {}
fn handle_update_add_htlc(&self, _their_node_id: &PublicKey, _msg: &UpdateAddHTLC) {}
fn handle_update_fulfill_htlc(&self, _their_node_id: &PublicKey, _msg: &UpdateFulfillHTLC) {}
chan_handler: Arc::clone(&a_handler),
route_handler: Arc::clone(&a_handler),
onion_message_handler: Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{}),
- }, a_key.clone(), 0, &[1; 32], Arc::new(TestLogger()), Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{})));
+ }, 0, &[1; 32], Arc::new(TestLogger()), Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{}), Arc::new(TestNodeSigner::new(a_key))));
let (b_connected_sender, mut b_connected) = mpsc::channel(1);
let (b_disconnected_sender, mut b_disconnected) = mpsc::channel(1);
chan_handler: Arc::clone(&b_handler),
route_handler: Arc::clone(&b_handler),
onion_message_handler: Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{}),
- }, b_key.clone(), 0, &[2; 32], Arc::new(TestLogger()), Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{})));
+ }, 0, &[2; 32], Arc::new(TestLogger()), Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{}), Arc::new(TestNodeSigner::new(b_key))));
// We bind on localhost, hoping the environment is properly configured with a local
// address. This may not always be the case in containers and the like, so if this test is
chan_handler: Arc::new(lightning::ln::peer_handler::ErroringMessageHandler::new()),
onion_message_handler: Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{}),
route_handler: Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{}),
- }, a_key, 0, &[1; 32], Arc::new(TestLogger()), Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{})));
+ }, 0, &[1; 32], Arc::new(TestLogger()), Arc::new(lightning::ln::peer_handler::IgnoringMessageHandler{}), Arc::new(TestNodeSigner::new(a_key))));
// Make two connections, one for an inbound and one for an outbound connection
let conn_a = {
/// An enum that represents the speed at which we want a transaction to confirm used for feerate
/// estimation.
-#[derive(Clone, Copy, Hash, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ConfirmationTarget {
/// We are happy with this transaction confirming slowly when feerate drops some.
Background,
use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Balance, MonitorEvent, TransactionOutputs, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::chain::transaction::{OutPoint, TransactionData};
-use crate::chain::keysinterface::Sign;
+use crate::chain::keysinterface::WriteableEcdsaChannelSigner;
use crate::util::atomic_counter::AtomicCounter;
use crate::util::logger::Logger;
use crate::util::errors::APIError;
pub(crate) fn from_monitor_update(update: &ChannelMonitorUpdate) -> Self {
Self { contents: UpdateOrigin::OffChain(update.update_id) }
}
- pub(crate) fn from_new_monitor<ChannelSigner: Sign>(monitor: &ChannelMonitor<ChannelSigner>) -> Self {
+ pub(crate) fn from_new_monitor<ChannelSigner: WriteableEcdsaChannelSigner>(monitor: &ChannelMonitor<ChannelSigner>) -> Self {
Self { contents: UpdateOrigin::OffChain(monitor.get_latest_update_id()) }
}
}
/// [`ChannelMonitorUpdateStatus::PermanentFailure`], in which case the channel will likely be
/// closed without broadcasting the latest state. See
/// [`ChannelMonitorUpdateStatus::PermanentFailure`] for more details.
-pub trait Persist<ChannelSigner: Sign> {
+pub trait Persist<ChannelSigner: WriteableEcdsaChannelSigner> {
/// Persist a new channel's data in response to a [`chain::Watch::watch_channel`] call. This is
/// called by [`ChannelManager`] for new channels, or may be called directly, e.g. on startup.
///
/// [`ChannelMonitorUpdateStatus`] for requirements when returning errors.
///
/// [`Writeable::write`]: crate::util::ser::Writeable::write
- fn update_persisted_channel(&self, channel_id: OutPoint, update: &Option<ChannelMonitorUpdate>, data: &ChannelMonitor<ChannelSigner>, update_id: MonitorUpdateId) -> ChannelMonitorUpdateStatus;
+ fn update_persisted_channel(&self, channel_id: OutPoint, update: Option<&ChannelMonitorUpdate>, data: &ChannelMonitor<ChannelSigner>, update_id: MonitorUpdateId) -> ChannelMonitorUpdateStatus;
}
-struct MonitorHolder<ChannelSigner: Sign> {
+struct MonitorHolder<ChannelSigner: WriteableEcdsaChannelSigner> {
monitor: ChannelMonitor<ChannelSigner>,
/// The full set of pending monitor updates for this Channel.
///
last_chain_persist_height: AtomicUsize,
}
-impl<ChannelSigner: Sign> MonitorHolder<ChannelSigner> {
+impl<ChannelSigner: WriteableEcdsaChannelSigner> MonitorHolder<ChannelSigner> {
fn has_pending_offchain_updates(&self, pending_monitor_updates_lock: &MutexGuard<Vec<MonitorUpdateId>>) -> bool {
pending_monitor_updates_lock.iter().any(|update_id|
if let UpdateOrigin::OffChain(_) = update_id.contents { true } else { false })
///
/// Note that this holds a mutex in [`ChainMonitor`] and may block other events until it is
/// released.
-pub struct LockedChannelMonitor<'a, ChannelSigner: Sign> {
+pub struct LockedChannelMonitor<'a, ChannelSigner: WriteableEcdsaChannelSigner> {
lock: RwLockReadGuard<'a, HashMap<OutPoint, MonitorHolder<ChannelSigner>>>,
funding_txo: OutPoint,
}
-impl<ChannelSigner: Sign> Deref for LockedChannelMonitor<'_, ChannelSigner> {
+impl<ChannelSigner: WriteableEcdsaChannelSigner> Deref for LockedChannelMonitor<'_, ChannelSigner> {
type Target = ChannelMonitor<ChannelSigner>;
fn deref(&self) -> &ChannelMonitor<ChannelSigner> {
&self.lock.get(&self.funding_txo).expect("Checked at construction").monitor
///
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
/// [module-level documentation]: crate::chain::chainmonitor
-pub struct ChainMonitor<ChannelSigner: Sign, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
+pub struct ChainMonitor<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
where C::Target: chain::Filter,
T::Target: BroadcasterInterface,
F::Target: FeeEstimator,
highest_chain_height: AtomicUsize,
}
-impl<ChannelSigner: Sign, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref> ChainMonitor<ChannelSigner, C, T, F, L, P>
+impl<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref> ChainMonitor<ChannelSigner, C, T, F, L, P>
where C::Target: chain::Filter,
T::Target: BroadcasterInterface,
F::Target: FeeEstimator,
}
log_trace!(self.logger, "Syncing Channel Monitor for channel {}", log_funding_info!(monitor));
- match self.persister.update_persisted_channel(*funding_outpoint, &None, monitor, update_id) {
+ match self.persister.update_persisted_channel(*funding_outpoint, None, monitor, update_id) {
ChannelMonitorUpdateStatus::Completed =>
log_trace!(self.logger, "Finished syncing Channel Monitor for channel {}", log_funding_info!(monitor)),
ChannelMonitorUpdateStatus::PermanentFailure => {
}
}
-impl<ChannelSigner: Sign, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
+impl<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
chain::Listen for ChainMonitor<ChannelSigner, C, T, F, L, P>
where
C::Target: chain::Filter,
}
}
-impl<ChannelSigner: Sign, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
+impl<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
chain::Confirm for ChainMonitor<ChannelSigner, C, T, F, L, P>
where
C::Target: chain::Filter,
}
}
-impl<ChannelSigner: Sign, C: Deref , T: Deref , F: Deref , L: Deref , P: Deref >
+impl<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref , T: Deref , F: Deref , L: Deref , P: Deref >
chain::Watch<ChannelSigner> for ChainMonitor<ChannelSigner, C, T, F, L, P>
where C::Target: chain::Filter,
T::Target: BroadcasterInterface,
/// Note that we persist the given `ChannelMonitor` update while holding the
/// `ChainMonitor` monitors lock.
- fn update_channel(&self, funding_txo: OutPoint, update: ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus {
+ fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus {
// Update the monitor that watches the channel referred to by the given outpoint.
let monitors = self.monitors.read().unwrap();
match monitors.get(&funding_txo) {
Some(monitor_state) => {
let monitor = &monitor_state.monitor;
log_trace!(self.logger, "Updating ChannelMonitor for channel {}", log_funding_info!(monitor));
- let update_res = monitor.update_monitor(&update, &self.broadcaster, &*self.fee_estimator, &self.logger);
+ let update_res = monitor.update_monitor(update, &self.broadcaster, &*self.fee_estimator, &self.logger);
if update_res.is_err() {
log_error!(self.logger, "Failed to update ChannelMonitor for channel {}.", log_funding_info!(monitor));
}
// Even if updating the monitor returns an error, the monitor's state will
// still be changed. So, persist the updated monitor despite the error.
- let update_id = MonitorUpdateId::from_monitor_update(&update);
+ let update_id = MonitorUpdateId::from_monitor_update(update);
let mut pending_monitor_updates = monitor_state.pending_monitor_updates.lock().unwrap();
- let persist_res = self.persister.update_persisted_channel(funding_txo, &Some(update), monitor, update_id);
+ let persist_res = self.persister.update_persisted_channel(funding_txo, Some(update), monitor, update_id);
match persist_res {
ChannelMonitorUpdateStatus::InProgress => {
pending_monitor_updates.push(update_id);
}
}
-impl<ChannelSigner: Sign, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref> events::EventsProvider for ChainMonitor<ChannelSigner, C, T, F, L, P>
+impl<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref> events::EventsProvider for ChainMonitor<ChannelSigner, C, T, F, L, P>
where C::Target: chain::Filter,
T::Target: BroadcasterInterface,
F::Target: FeeEstimator,
use crate::chain::{BestBlock, WatchedOutput};
use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator, LowerBoundedFeeEstimator};
use crate::chain::transaction::{OutPoint, TransactionData};
-use crate::chain::keysinterface::{SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, Sign, SignerProvider, EntropySource};
+use crate::chain::keysinterface::{SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, WriteableEcdsaChannelSigner, SignerProvider, EntropySource};
#[cfg(anchors)]
use crate::chain::onchaintx::ClaimEvent;
use crate::chain::onchaintx::OnchainTxHandler;
/// the "reorg path" (ie disconnecting blocks until you find a common ancestor from both the
/// returned block hash and the the current chain and then reconnecting blocks to get to the
/// best chain) upon deserializing the object!
-pub struct ChannelMonitor<Signer: Sign> {
+pub struct ChannelMonitor<Signer: WriteableEcdsaChannelSigner> {
#[cfg(test)]
pub(crate) inner: Mutex<ChannelMonitorImpl<Signer>>,
#[cfg(not(test))]
inner: Mutex<ChannelMonitorImpl<Signer>>,
}
-pub(crate) struct ChannelMonitorImpl<Signer: Sign> {
+#[derive(PartialEq)]
+pub(crate) struct ChannelMonitorImpl<Signer: WriteableEcdsaChannelSigner> {
latest_update_id: u64,
commitment_transaction_number_obscure_factor: u64,
/// The node_id of our counterparty
counterparty_node_id: Option<PublicKey>,
-
- secp_ctx: Secp256k1<secp256k1::All>, //TODO: dedup this a bit...
}
/// Transaction outputs to watch for on-chain spends.
pub type TransactionOutputs = (Txid, Vec<(u32, TxOut)>);
-#[cfg(any(test, fuzzing, feature = "_test_utils"))]
-/// Used only in testing and fuzzing to check serialization roundtrips don't change the underlying
-/// object
-impl<Signer: Sign> PartialEq for ChannelMonitor<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> PartialEq for ChannelMonitor<Signer> where Signer: PartialEq {
fn eq(&self, other: &Self) -> bool {
let inner = self.inner.lock().unwrap();
let other = other.inner.lock().unwrap();
}
}
-#[cfg(any(test, fuzzing, feature = "_test_utils"))]
-/// Used only in testing and fuzzing to check serialization roundtrips don't change the underlying
-/// object
-impl<Signer: Sign> PartialEq for ChannelMonitorImpl<Signer> {
- fn eq(&self, other: &Self) -> bool {
- if self.latest_update_id != other.latest_update_id ||
- self.commitment_transaction_number_obscure_factor != other.commitment_transaction_number_obscure_factor ||
- self.destination_script != other.destination_script ||
- self.broadcasted_holder_revokable_script != other.broadcasted_holder_revokable_script ||
- self.counterparty_payment_script != other.counterparty_payment_script ||
- self.channel_keys_id != other.channel_keys_id ||
- self.holder_revocation_basepoint != other.holder_revocation_basepoint ||
- self.funding_info != other.funding_info ||
- self.current_counterparty_commitment_txid != other.current_counterparty_commitment_txid ||
- self.prev_counterparty_commitment_txid != other.prev_counterparty_commitment_txid ||
- self.counterparty_commitment_params != other.counterparty_commitment_params ||
- self.funding_redeemscript != other.funding_redeemscript ||
- self.channel_value_satoshis != other.channel_value_satoshis ||
- self.their_cur_per_commitment_points != other.their_cur_per_commitment_points ||
- self.on_holder_tx_csv != other.on_holder_tx_csv ||
- self.commitment_secrets != other.commitment_secrets ||
- self.counterparty_claimable_outpoints != other.counterparty_claimable_outpoints ||
- self.counterparty_commitment_txn_on_chain != other.counterparty_commitment_txn_on_chain ||
- self.counterparty_hash_commitment_number != other.counterparty_hash_commitment_number ||
- self.prev_holder_signed_commitment_tx != other.prev_holder_signed_commitment_tx ||
- self.current_counterparty_commitment_number != other.current_counterparty_commitment_number ||
- self.current_holder_commitment_number != other.current_holder_commitment_number ||
- self.current_holder_commitment_tx != other.current_holder_commitment_tx ||
- self.payment_preimages != other.payment_preimages ||
- self.pending_monitor_events != other.pending_monitor_events ||
- self.pending_events.len() != other.pending_events.len() || // We trust events to round-trip properly
- self.onchain_events_awaiting_threshold_conf != other.onchain_events_awaiting_threshold_conf ||
- self.outputs_to_watch != other.outputs_to_watch ||
- self.lockdown_from_offchain != other.lockdown_from_offchain ||
- self.holder_tx_signed != other.holder_tx_signed ||
- self.funding_spend_seen != other.funding_spend_seen ||
- self.funding_spend_confirmed != other.funding_spend_confirmed ||
- self.confirmed_commitment_tx_counterparty_output != other.confirmed_commitment_tx_counterparty_output ||
- self.htlcs_resolved_on_chain != other.htlcs_resolved_on_chain
- {
- false
- } else {
- true
- }
- }
-}
-
-impl<Signer: Sign> Writeable for ChannelMonitor<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> Writeable for ChannelMonitor<Signer> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
self.inner.lock().unwrap().write(writer)
}
const SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 1;
-impl<Signer: Sign> Writeable for ChannelMonitorImpl<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
}
}
-impl<Signer: Sign> ChannelMonitor<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
/// For lockorder enforcement purposes, we need to have a single site which constructs the
/// `inner` mutex, otherwise cases where we lock two monitors at the same time (eg in our
/// PartialEq implementation) we may decide a lockorder violation has occurred.
let onchain_tx_handler =
OnchainTxHandler::new(destination_script.clone(), keys,
- channel_parameters.clone(), initial_holder_commitment_tx, secp_ctx.clone());
+ channel_parameters.clone(), initial_holder_commitment_tx, secp_ctx);
let mut outputs_to_watch = HashMap::new();
outputs_to_watch.insert(funding_info.0.txid, vec![(funding_info.0.index as u32, funding_info.1.clone())]);
best_block,
counterparty_node_id: Some(counterparty_node_id),
-
- secp_ctx,
})
}
}
}
-impl<Signer: Sign> ChannelMonitorImpl<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
/// Helper for get_claimable_balances which does the work for an individual HTLC, generating up
/// to one `Balance` for the HTLC.
fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, holder_commitment: bool,
}
}
-impl<Signer: Sign> ChannelMonitor<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
/// Gets the balances in this channel which are either claimable by us if we were to
/// force-close the channel now or which are claimable on-chain (possibly awaiting
/// confirmation).
vec![Vec::new(), Vec::new(), Vec::new(), Vec::new(), deliberately_bogus_accepted_htlc_witness_program().into()].into()
}
-impl<Signer: Sign> ChannelMonitorImpl<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
/// Inserts a revocation secret into this channel monitor. Prunes old preimages if neither
/// needed by holder commitment transactions HTCLs nor by counterparty ones. Unless we haven't already seen
/// counterparty commitment transaction's secret, they are de facto pruned (we can use revocation key).
if commitment_number >= self.get_min_seen_secret() {
let secret = self.get_secret(commitment_number).unwrap();
let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret));
- let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
- let revocation_pubkey = chan_utils::derive_public_revocation_key(&self.secp_ctx, &per_commitment_point, &self.holder_revocation_basepoint);
- let delayed_key = chan_utils::derive_public_key(&self.secp_ctx, &PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key), &self.counterparty_commitment_params.counterparty_delayed_payment_base_key);
+ let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key);
+ let revocation_pubkey = chan_utils::derive_public_revocation_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_point, &self.holder_revocation_basepoint);
+ let delayed_key = chan_utils::derive_public_key(&self.onchain_tx_handler.secp_ctx, &PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key), &self.counterparty_commitment_params.counterparty_delayed_payment_base_key);
let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key);
let revokeable_p2wsh = revokeable_redeemscript.to_v0_p2wsh();
if let Some(transaction) = tx {
let revocation_pubkey = chan_utils::derive_public_revocation_key(
- &self.secp_ctx, &per_commitment_point, &self.holder_revocation_basepoint);
- let delayed_key = chan_utils::derive_public_key(&self.secp_ctx,
+ &self.onchain_tx_handler.secp_ctx, &per_commitment_point, &self.holder_revocation_basepoint);
+ let delayed_key = chan_utils::derive_public_key(&self.onchain_tx_handler.secp_ctx,
&per_commitment_point,
&self.counterparty_commitment_params.counterparty_delayed_payment_base_key);
let revokeable_p2wsh = chan_utils::get_revokeable_redeemscript(&revocation_pubkey,
Ok(key) => key,
Err(_) => return (Vec::new(), None)
};
- let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
+ let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key);
let htlc_txid = tx.txid();
let mut claimable_outpoints = vec![];
}
}
-impl<Signer: Sign, T: Deref, F: Deref, L: Deref> chain::Listen for (ChannelMonitor<Signer>, T, F, L)
+impl<Signer: WriteableEcdsaChannelSigner, T: Deref, F: Deref, L: Deref> chain::Listen for (ChannelMonitor<Signer>, T, F, L)
where
T::Target: BroadcasterInterface,
F::Target: FeeEstimator,
}
}
-impl<Signer: Sign, T: Deref, F: Deref, L: Deref> chain::Confirm for (ChannelMonitor<Signer>, T, F, L)
+impl<Signer: WriteableEcdsaChannelSigner, T: Deref, F: Deref, L: Deref> chain::Confirm for (ChannelMonitor<Signer>, T, F, L)
where
T::Target: BroadcasterInterface,
F::Target: FeeEstimator,
(13, spendable_txids_confirmed, vec_type),
});
- let mut secp_ctx = Secp256k1::new();
- secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
-
Ok((best_block.block_hash(), ChannelMonitor::from_impl(ChannelMonitorImpl {
latest_update_id,
commitment_transaction_number_obscure_factor,
best_block,
counterparty_node_id,
-
- secp_ctx,
})))
}
}
SecretKey::from_slice(&[41; 32]).unwrap(),
SecretKey::from_slice(&[41; 32]).unwrap(),
SecretKey::from_slice(&[41; 32]).unwrap(),
- SecretKey::from_slice(&[41; 32]).unwrap(),
[41; 32],
0,
[0; 32],
use bitcoin::bech32::u5;
use bitcoin::hashes::{Hash, HashEngine};
-use bitcoin::hashes::sha256::HashEngine as Sha256State;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::WPubkeyHash;
use crate::util::transaction_utils;
use crate::util::crypto::{hkdf_extract_expand_twice, sign};
-use crate::util::ser::{Writeable, Writer, Readable, ReadableArgs};
+use crate::util::ser::{Writeable, Writer, Readable};
#[cfg(anchors)]
use crate::util::events::HTLCDescriptor;
use crate::chain::transaction::OutPoint;
use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
use crate::ln::{chan_utils, PaymentPreimage};
use crate::ln::chan_utils::{HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, HolderCommitmentTransaction, ChannelTransactionParameters, CommitmentTransaction, ClosingTransaction};
-use crate::ln::msgs::UnsignedChannelAnnouncement;
+use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage};
use crate::ln::script::ShutdownScript;
use crate::prelude::*;
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::io::{self, Error};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
+use crate::util::atomic_counter::AtomicCounter;
+use crate::util::chacha20::ChaCha20;
use crate::util::invoice::construct_invoice_preimage;
/// Used as initial key material, to be expanded into multiple secret keys (but not to be used
/// The revocation point specific to the commitment transaction which was broadcast. Used to
/// derive the witnessScript for this output.
pub revocation_pubkey: PublicKey,
- /// Arbitrary identification information returned by a call to [`BaseSign::channel_keys_id`].
+ /// Arbitrary identification information returned by a call to [`ChannelSigner::channel_keys_id`].
/// This may be useful in re-deriving keys used in the channel to spend the output.
pub channel_keys_id: [u8; 32],
/// The value of the channel which this output originated from, possibly indirectly.
pub outpoint: OutPoint,
/// The output which is referenced by the given outpoint.
pub output: TxOut,
- /// Arbitrary identification information returned by a call to [`BaseSign::channel_keys_id`].
+ /// Arbitrary identification information returned by a call to [`ChannelSigner::channel_keys_id`].
/// This may be useful in re-deriving keys used in the channel to spend the output.
pub channel_keys_id: [u8; 32],
/// The value of the channel which this transactions spends.
///
/// To derive the delayed payment key which is used to sign this input, you must pass the
/// holder [`InMemorySigner::delayed_payment_base_key`] (i.e., the private key which corresponds to the
- /// [`ChannelPublicKeys::delayed_payment_basepoint`] in [`BaseSign::pubkeys`]) and the provided
+ /// [`ChannelPublicKeys::delayed_payment_basepoint`] in [`ChannelSigner::pubkeys`]) and the provided
/// [`DelayedPaymentOutputDescriptor::per_commitment_point`] to [`chan_utils::derive_private_key`]. The public key can be
/// generated without the secret key using [`chan_utils::derive_public_key`] and only the
- /// [`ChannelPublicKeys::delayed_payment_basepoint`] which appears in [`BaseSign::pubkeys`].
+ /// [`ChannelPublicKeys::delayed_payment_basepoint`] which appears in [`ChannelSigner::pubkeys`].
///
/// To derive the [`DelayedPaymentOutputDescriptor::revocation_pubkey`] provided here (which is
/// used in the witness script generation), you must pass the counterparty
/// [`ChannelPublicKeys::revocation_basepoint`] (which appears in the call to
- /// [`BaseSign::provide_channel_parameters`]) and the provided
+ /// [`ChannelSigner::provide_channel_parameters`]) and the provided
/// [`DelayedPaymentOutputDescriptor::per_commitment_point`] to
/// [`chan_utils::derive_public_revocation_key`].
///
/// [`chan_utils::get_revokeable_redeemscript`].
DelayedPaymentOutput(DelayedPaymentOutputDescriptor),
/// An output to a P2WPKH, spendable exclusively by our payment key (i.e., the private key
- /// which corresponds to the `payment_point` in [`BaseSign::pubkeys`]). The witness
+ /// which corresponds to the `payment_point` in [`ChannelSigner::pubkeys`]). The witness
/// in the spending input is, thus, simply:
/// ```bitcoin
/// <BIP 143 signature> <payment key>
(2, StaticPaymentOutput),
);
-/// A trait to sign Lightning channel transactions as described in
-/// [BOLT 3](https://github.com/lightning/bolts/blob/master/03-transactions.md).
-///
-/// Signing services could be implemented on a hardware wallet and should implement signing
-/// policies in order to be secure. Please refer to the [VLS Policy
-/// Controls](https://gitlab.com/lightning-signer/validating-lightning-signer/-/blob/main/docs/policy-controls.md)
-/// for an example of such policies.
-pub trait BaseSign {
+/// A trait to handle Lightning channel key material without concretizing the channel type or
+/// the signature mechanism.
+pub trait ChannelSigner {
/// Gets the per-commitment point for a specific commitment number
///
/// Note that the commitment number starts at `(1 << 48) - 1` and counts backwards.
fn get_per_commitment_point(&self, idx: u64, secp_ctx: &Secp256k1<secp256k1::All>) -> PublicKey;
+
/// Gets the commitment secret for a specific commitment number as part of the revocation process
///
/// An external signer implementation should error here if the commitment was already signed
/// Note that the commitment number starts at `(1 << 48) - 1` and counts backwards.
// TODO: return a Result so we can signal a validation error
fn release_commitment_secret(&self, idx: u64) -> [u8; 32];
+
/// Validate the counterparty's signatures on the holder commitment transaction and HTLCs.
///
/// This is required in order for the signer to make sure that releasing a commitment
/// irrelevant or duplicate preimages.
fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction,
preimages: Vec<PaymentPreimage>) -> Result<(), ()>;
+
/// Returns the holder's channel public keys and basepoints.
fn pubkeys(&self) -> &ChannelPublicKeys;
+
/// Returns an arbitrary identifier describing the set of keys which are provided back to you in
/// some [`SpendableOutputDescriptor`] types. This should be sufficient to identify this
- /// [`BaseSign`] object uniquely and lookup or re-derive its keys.
+ /// [`EcdsaChannelSigner`] object uniquely and lookup or re-derive its keys.
fn channel_keys_id(&self) -> [u8; 32];
+
+ /// Set the counterparty static channel data, including basepoints,
+ /// `counterparty_selected`/`holder_selected_contest_delay` and funding outpoint.
+ ///
+ /// This data is static, and will never change for a channel once set. For a given [`ChannelSigner`]
+ /// instance, LDK will call this method exactly once - either immediately after construction
+ /// (not including if done via [`SignerProvider::read_chan_signer`]) or when the funding
+ /// information has been generated.
+ ///
+ /// channel_parameters.is_populated() MUST be true.
+ fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters);
+}
+
+/// A trait to sign Lightning channel transactions as described in
+/// [BOLT 3](https://github.com/lightning/bolts/blob/master/03-transactions.md).
+///
+/// Signing services could be implemented on a hardware wallet and should implement signing
+/// policies in order to be secure. Please refer to the [VLS Policy
+/// Controls](https://gitlab.com/lightning-signer/validating-lightning-signer/-/blob/main/docs/policy-controls.md)
+/// for an example of such policies.
+pub trait EcdsaChannelSigner: ChannelSigner {
/// Create a signature for a counterparty's commitment transaction and associated HTLC transactions.
///
/// Note that if signing fails or is rejected, the channel will be force-closed.
fn sign_holder_anchor_input(
&self, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()>;
- /// Signs a channel announcement message with our funding key and our node secret key (aka
- /// node_id or network_key), proving it comes from one of the channel participants.
+ /// Signs a channel announcement message with our funding key proving it comes from one of the
+ /// channel participants.
///
- /// The first returned signature should be from our node secret key, the second from our
- /// funding key.
+ /// Channel announcements also require a signature from each node's network key. Our node
+ /// signature is computed through [`NodeSigner::sign_gossip_message`].
///
/// Note that if this fails or is rejected, the channel will not be publicly announced and
/// our counterparty may (though likely will not) close the channel on us for violating the
/// protocol.
- fn sign_channel_announcement(&self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>)
- -> Result<(Signature, Signature), ()>;
- /// Set the counterparty static channel data, including basepoints,
- /// `counterparty_selected`/`holder_selected_contest_delay` and funding outpoint.
- ///
- /// This data is static, and will never change for a channel once set. For a given [`BaseSign`]
- /// instance, LDK will call this method exactly once - either immediately after construction
- /// (not including if done via [`SignerProvider::read_chan_signer`]) or when the funding
- /// information has been generated.
- ///
- /// channel_parameters.is_populated() MUST be true.
- fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters);
+ fn sign_channel_announcement_with_funding_key(
+ &self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>
+ ) -> Result<Signature, ()>;
}
/// A writeable signer.
///
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
/// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor
-pub trait Sign: BaseSign + Writeable {}
+pub trait WriteableEcdsaChannelSigner: EcdsaChannelSigner + Writeable {}
/// Specifies the recipient of an invoice.
///
/// A trait that can handle cryptographic operations at the scope level of a node.
pub trait NodeSigner {
- /// Get node secret key based on the provided [`Recipient`].
- ///
- /// The `node_id`/`network_key` is the public key that corresponds to this secret key.
- ///
- /// This method must return the same value each time it is called with a given [`Recipient`]
- /// parameter.
- ///
- /// Errors if the [`Recipient`] variant is not supported by the implementation.
- fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()>;
-
/// Get secret key material as bytes for use in encrypting and decrypting inbound payment data.
///
/// If the implementor of this trait supports [phantom node payments], then every node that is
/// [phantom node payments]: PhantomKeysManager
fn get_inbound_payment_key_material(&self) -> KeyMaterial;
- /// Get node id based on the provided [`Recipient`]. This public key corresponds to the secret in
- /// [`get_node_secret`].
+ /// Get node id based on the provided [`Recipient`].
///
/// This method must return the same value each time it is called with a given [`Recipient`]
/// parameter.
///
/// Errors if the [`Recipient`] variant is not supported by the implementation.
- ///
- /// [`get_node_secret`]: Self::get_node_secret
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()>;
- /// Gets the ECDH shared secret of our [`node secret`] and `other_key`, multiplying by `tweak` if
+ /// Gets the ECDH shared secret of our node secret and `other_key`, multiplying by `tweak` if
/// one is provided. Note that this tweak can be applied to `other_key` instead of our node
/// secret, though this is less efficient.
///
- /// Errors if the [`Recipient`] variant is not supported by the implementation.
+ /// Note that if this fails while attempting to forward an HTLC, LDK will panic. The error
+ /// should be resolved to allow LDK to resume forwarding HTLCs.
///
- /// [`node secret`]: Self::get_node_secret
+ /// Errors if the [`Recipient`] variant is not supported by the implementation.
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>) -> Result<SharedSecret, ()>;
/// Sign an invoice.
///
/// Errors if the [`Recipient`] variant is not supported by the implementation.
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()>;
+
+ /// Sign a gossip message.
+ ///
+ /// Note that if this fails, LDK may panic and the message will not be broadcast to the network
+ /// or a possible channel counterparty. If LDK panics, the error should be resolved to allow the
+ /// message to be broadcast, as otherwise it may prevent one from receiving funds over the
+ /// corresponding channel.
+ fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()>;
}
/// A trait that can return signer instances for individual channels.
pub trait SignerProvider {
- /// A type which implements [`Sign`] which will be returned by [`Self::derive_channel_signer`].
- type Signer : Sign;
+ /// A type which implements [`WriteableEcdsaChannelSigner`] which will be returned by [`Self::derive_channel_signer`].
+ type Signer : WriteableEcdsaChannelSigner;
/// Generates a unique `channel_keys_id` that can be used to obtain a [`Self::Signer`] through
/// [`SignerProvider::derive_channel_signer`]. The `user_channel_id` is provided to allow
/// To derive a new `Signer`, a fresh `channel_keys_id` should be obtained through
/// [`SignerProvider::generate_channel_keys_id`]. Otherwise, an existing `Signer` can be
/// re-derived from its `channel_keys_id`, which can be obtained through its trait method
- /// [`BaseSign::channel_keys_id`].
+ /// [`ChannelSigner::channel_keys_id`].
fn derive_channel_signer(&self, channel_value_satoshis: u64, channel_keys_id: [u8; 32]) -> Self::Signer;
/// Reads a [`Signer`] for this [`SignerProvider`] from the given input stream.
/// This is only called during deserialization of other objects which contain
- /// [`Sign`]-implementing objects (i.e., [`ChannelMonitor`]s and [`ChannelManager`]s).
+ /// [`WriteableEcdsaChannelSigner`]-implementing objects (i.e., [`ChannelMonitor`]s and [`ChannelManager`]s).
/// The bytes are exactly those which `<Self::Signer as Writeable>::write()` writes, and
/// contain no versioning scheme. You may wish to include your own version prefix and ensure
/// you've read all of the provided bytes to ensure no corruption occurred.
}
#[derive(Clone)]
-/// A simple implementation of [`Sign`] that just keeps the private keys in memory.
+/// A simple implementation of [`WriteableEcdsaChannelSigner`] that just keeps the private keys in memory.
///
/// This implementation performs no policy checks and is insufficient by itself as
/// a secure external signer.
pub commitment_seed: [u8; 32],
/// Holder public keys and basepoints.
pub(crate) holder_channel_pubkeys: ChannelPublicKeys,
- /// Private key of our node secret, used for signing channel announcements.
- node_secret: SecretKey,
/// Counterparty public keys and counterparty/holder `selected_contest_delay`, populated on channel acceptance.
channel_parameters: Option<ChannelTransactionParameters>,
/// The total value of this channel.
/// Creates a new [`InMemorySigner`].
pub fn new<C: Signing>(
secp_ctx: &Secp256k1<C>,
- node_secret: SecretKey,
funding_key: SecretKey,
revocation_base_key: SecretKey,
payment_key: SecretKey,
delayed_payment_base_key,
htlc_base_key,
commitment_seed,
- node_secret,
channel_value_satoshis,
holder_channel_pubkeys,
channel_parameters: None,
/// Returns the counterparty's pubkeys.
///
- /// Will panic if [`BaseSign::provide_channel_parameters`] has not been called before.
+ /// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
pub fn counterparty_pubkeys(&self) -> &ChannelPublicKeys { &self.get_channel_parameters().counterparty_parameters.as_ref().unwrap().pubkeys }
/// Returns the `contest_delay` value specified by our counterparty and applied on holder-broadcastable
/// transactions, i.e., the amount of time that we have to wait to recover our funds if we
/// broadcast a transaction.
///
- /// Will panic if [`BaseSign::provide_channel_parameters`] has not been called before.
+ /// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
pub fn counterparty_selected_contest_delay(&self) -> u16 { self.get_channel_parameters().counterparty_parameters.as_ref().unwrap().selected_contest_delay }
/// Returns the `contest_delay` value specified by us and applied on transactions broadcastable
/// by our counterparty, i.e., the amount of time that they have to wait to recover their funds
/// if they broadcast a transaction.
///
- /// Will panic if [`BaseSign::provide_channel_parameters`] has not been called before.
+ /// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
pub fn holder_selected_contest_delay(&self) -> u16 { self.get_channel_parameters().holder_selected_contest_delay }
/// Returns whether the holder is the initiator.
///
- /// Will panic if [`BaseSign::provide_channel_parameters`] has not been called before.
+ /// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
pub fn is_outbound(&self) -> bool { self.get_channel_parameters().is_outbound_from_holder }
/// Funding outpoint
///
- /// Will panic if [`BaseSign::provide_channel_parameters`] has not been called before.
+ /// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
pub fn funding_outpoint(&self) -> &OutPoint { self.get_channel_parameters().funding_outpoint.as_ref().unwrap() }
/// Returns a [`ChannelTransactionParameters`] for this channel, to be used when verifying or
/// building transactions.
///
- /// Will panic if [`BaseSign::provide_channel_parameters`] has not been called before.
+ /// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
pub fn get_channel_parameters(&self) -> &ChannelTransactionParameters {
self.channel_parameters.as_ref().unwrap()
}
/// Returns whether anchors should be used.
///
- /// Will panic if [`BaseSign::provide_channel_parameters`] has not been called before.
+ /// Will panic if [`ChannelSigner::provide_channel_parameters`] has not been called before.
pub fn opt_anchors(&self) -> bool {
self.get_channel_parameters().opt_anchors.is_some()
}
}
}
-impl BaseSign for InMemorySigner {
+impl ChannelSigner for InMemorySigner {
fn get_per_commitment_point(&self, idx: u64, secp_ctx: &Secp256k1<secp256k1::All>) -> PublicKey {
let commitment_secret = SecretKey::from_slice(&chan_utils::build_commitment_secret(&self.commitment_seed, idx)).unwrap();
PublicKey::from_secret_key(secp_ctx, &commitment_secret)
fn channel_keys_id(&self) -> [u8; 32] { self.channel_keys_id }
+ fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) {
+ assert!(self.channel_parameters.is_none() || self.channel_parameters.as_ref().unwrap() == channel_parameters);
+ if self.channel_parameters.is_some() {
+ // The channel parameters were already set and they match, return early.
+ return;
+ }
+ assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated");
+ self.channel_parameters = Some(channel_parameters.clone());
+ }
+}
+
+impl EcdsaChannelSigner for InMemorySigner {
fn sign_counterparty_commitment(&self, commitment_tx: &CommitmentTransaction, _preimages: Vec<PaymentPreimage>, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
let trusted_tx = commitment_tx.trust();
let keys = trusted_tx.keys();
Ok(sign(secp_ctx, &hash_to_message!(&sighash[..]), &self.funding_key))
}
- fn sign_channel_announcement(&self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>)
- -> Result<(Signature, Signature), ()> {
+ fn sign_channel_announcement_with_funding_key(
+ &self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>
+ ) -> Result<Signature, ()> {
let msghash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
- Ok((sign(secp_ctx, &msghash, &self.node_secret), sign(secp_ctx, &msghash, &self.funding_key)))
- }
-
- fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) {
- assert!(self.channel_parameters.is_none() || self.channel_parameters.as_ref().unwrap() == channel_parameters);
- if self.channel_parameters.is_some() {
- // The channel parameters were already set and they match, return early.
- return;
- }
- assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated");
- self.channel_parameters = Some(channel_parameters.clone());
+ Ok(sign(secp_ctx, &msghash, &self.funding_key))
}
}
const MIN_SERIALIZATION_VERSION: u8 = 1;
-impl Sign for InMemorySigner {}
+impl WriteableEcdsaChannelSigner for InMemorySigner {}
impl Writeable for InMemorySigner {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
}
}
-impl ReadableArgs<SecretKey> for InMemorySigner {
- fn read<R: io::Read>(reader: &mut R, node_secret: SecretKey) -> Result<Self, DecodeError> {
+impl Readable for InMemorySigner {
+ fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION);
let funding_key = Readable::read(reader)?;
payment_key,
delayed_payment_base_key,
htlc_base_key,
- node_secret,
commitment_seed,
channel_value_satoshis,
holder_channel_pubkeys,
channel_master_key: ExtendedPrivKey,
channel_child_index: AtomicUsize,
- rand_bytes_master_key: ExtendedPrivKey,
- rand_bytes_child_index: AtomicUsize,
- rand_bytes_unique_start: Sha256State,
+ rand_bytes_unique_start: [u8; 32],
+ rand_bytes_index: AtomicCounter,
seed: [u8; 32],
starting_time_secs: u64,
Err(_) => panic!("Your RNG is busted"),
};
let channel_master_key = master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx(3).unwrap()).expect("Your RNG is busted");
- let rand_bytes_master_key = master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx(4).unwrap()).expect("Your RNG is busted");
let inbound_payment_key: SecretKey = master_key.ckd_priv(&secp_ctx, ChildNumber::from_hardened_idx(5).unwrap()).expect("Your RNG is busted").private_key;
let mut inbound_pmt_key_bytes = [0; 32];
inbound_pmt_key_bytes.copy_from_slice(&inbound_payment_key[..]);
- let mut rand_bytes_unique_start = Sha256::engine();
- rand_bytes_unique_start.input(&starting_time_secs.to_be_bytes());
- rand_bytes_unique_start.input(&starting_time_nanos.to_be_bytes());
- rand_bytes_unique_start.input(seed);
+ let mut rand_bytes_engine = Sha256::engine();
+ rand_bytes_engine.input(&starting_time_secs.to_be_bytes());
+ rand_bytes_engine.input(&starting_time_nanos.to_be_bytes());
+ rand_bytes_engine.input(seed);
+ rand_bytes_engine.input(b"LDK PRNG Seed");
+ let rand_bytes_unique_start = Sha256::from_engine(rand_bytes_engine).into_inner();
let mut res = KeysManager {
secp_ctx,
channel_master_key,
channel_child_index: AtomicUsize::new(0),
- rand_bytes_master_key,
- rand_bytes_child_index: AtomicUsize::new(0),
rand_bytes_unique_start,
+ rand_bytes_index: AtomicCounter::new(),
seed: *seed,
starting_time_secs,
Err(_) => panic!("Your rng is busted"),
}
}
- /// Derive an old [`Sign`] containing per-channel secrets based on a key derivation parameters.
+ /// Derive an old [`WriteableEcdsaChannelSigner`] containing per-channel secrets based on a key derivation parameters.
pub fn derive_channel_keys(&self, channel_value_satoshis: u64, params: &[u8; 32]) -> InMemorySigner {
let chan_id = u64::from_be_bytes(params[0..8].try_into().unwrap());
let mut unique_start = Sha256::engine();
InMemorySigner::new(
&self.secp_ctx,
- self.node_secret,
funding_key,
revocation_base_key,
payment_key,
impl EntropySource for KeysManager {
fn get_secure_random_bytes(&self) -> [u8; 32] {
- let mut sha = self.rand_bytes_unique_start.clone();
-
- let child_ix = self.rand_bytes_child_index.fetch_add(1, Ordering::AcqRel);
- let child_privkey = self.rand_bytes_master_key.ckd_priv(&self.secp_ctx, ChildNumber::from_hardened_idx(child_ix as u32).expect("key space exhausted")).expect("Your RNG is busted");
- sha.input(&child_privkey.private_key[..]);
-
- sha.input(b"Unique Secure Random Bytes Salt");
- Sha256::from_engine(sha).into_inner()
+ let index = self.rand_bytes_index.get_increment();
+ let mut nonce = [0u8; 16];
+ nonce[..8].copy_from_slice(&index.to_be_bytes());
+ ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce)
}
}
impl NodeSigner for KeysManager {
- fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
- match recipient {
- Recipient::Node => Ok(self.node_secret.clone()),
- Recipient::PhantomNode => Err(())
- }
- }
-
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
match recipient {
Recipient::Node => Ok(self.node_id.clone()),
}
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>) -> Result<SharedSecret, ()> {
- let mut node_secret = self.get_node_secret(recipient)?;
+ let mut node_secret = match recipient {
+ Recipient::Node => Ok(self.node_secret.clone()),
+ Recipient::PhantomNode => Err(())
+ }?;
if let Some(tweak) = tweak {
node_secret = node_secret.mul_tweak(tweak).map_err(|_| ())?;
}
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> {
let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data);
let secret = match recipient {
- Recipient::Node => self.get_node_secret(Recipient::Node)?,
- Recipient::PhantomNode => return Err(()),
- };
- Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &secret))
+ Recipient::Node => Ok(&self.node_secret),
+ Recipient::PhantomNode => Err(())
+ }?;
+ Ok(self.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
+ }
+
+ fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
+ let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
+ Ok(sign(&self.secp_ctx, &msg_hash, &self.node_secret))
}
}
}
fn read_chan_signer(&self, reader: &[u8]) -> Result<Self::Signer, DecodeError> {
- InMemorySigner::read(&mut io::Cursor::new(reader), self.node_secret.clone())
+ InMemorySigner::read(&mut io::Cursor::new(reader))
}
fn get_destination_script(&self) -> Script {
}
impl NodeSigner for PhantomKeysManager {
- fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
- match recipient {
- Recipient::Node => self.inner.get_node_secret(Recipient::Node),
- Recipient::PhantomNode => Ok(self.phantom_secret.clone()),
- }
- }
-
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
match recipient {
Recipient::Node => self.inner.get_node_id(Recipient::Node),
}
fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>) -> Result<SharedSecret, ()> {
- let mut node_secret = self.get_node_secret(recipient)?;
+ let mut node_secret = match recipient {
+ Recipient::Node => self.inner.node_secret.clone(),
+ Recipient::PhantomNode => self.phantom_secret.clone(),
+ };
if let Some(tweak) = tweak {
node_secret = node_secret.mul_tweak(tweak).map_err(|_| ())?;
}
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> {
let preimage = construct_invoice_preimage(&hrp_bytes, &invoice_data);
- let secret = self.get_node_secret(recipient)?;
- Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), &secret))
+ let secret = match recipient {
+ Recipient::Node => &self.inner.node_secret,
+ Recipient::PhantomNode => &self.phantom_secret,
+ };
+ Ok(self.inner.secp_ctx.sign_ecdsa_recoverable(&hash_to_message!(&Sha256::hash(&preimage)), secret))
+ }
+
+ fn sign_gossip_message(&self, msg: UnsignedGossipMessage) -> Result<Signature, ()> {
+ self.inner.sign_gossip_message(msg)
}
}
}
}
-// Ensure that BaseSign can have a vtable
+// Ensure that EcdsaChannelSigner can have a vtable
#[test]
pub fn dyn_sign() {
- let _signer: Box<dyn BaseSign>;
+ let _signer: Box<dyn EcdsaChannelSigner>;
+}
+
+#[cfg(all(test, feature = "_bench_unstable", not(feature = "no-std")))]
+mod benches {
+ use std::sync::{Arc, mpsc};
+ use std::sync::mpsc::TryRecvError;
+ use std::thread;
+ use std::time::Duration;
+ use bitcoin::blockdata::constants::genesis_block;
+ use bitcoin::Network;
+ use crate::chain::keysinterface::{EntropySource, KeysManager};
+
+ use test::Bencher;
+
+ #[bench]
+ fn bench_get_secure_random_bytes(bench: &mut Bencher) {
+ let seed = [0u8; 32];
+ let now = Duration::from_secs(genesis_block(Network::Testnet).header.time as u64);
+ let keys_manager = Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_micros()));
+
+ let mut handles = Vec::new();
+ let mut stops = Vec::new();
+ for _ in 1..5 {
+ let keys_manager_clone = Arc::clone(&keys_manager);
+ let (stop_sender, stop_receiver) = mpsc::channel();
+ let handle = thread::spawn(move || {
+ loop {
+ keys_manager_clone.get_secure_random_bytes();
+ match stop_receiver.try_recv() {
+ Ok(_) | Err(TryRecvError::Disconnected) => {
+ println!("Terminating.");
+ break;
+ }
+ Err(TryRecvError::Empty) => {}
+ }
+ }
+ });
+ handles.push(handle);
+ stops.push(stop_sender);
+ }
+
+ bench.iter(|| {
+ for _ in 1..100 {
+ keys_manager.get_secure_random_bytes();
+ }
+ });
+
+ for stop in stops {
+ let _ = stop.send(());
+ }
+ for handle in handles {
+ handle.join().unwrap();
+ }
+ }
+
}
use bitcoin::secp256k1::PublicKey;
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, MonitorEvent};
-use crate::chain::keysinterface::Sign;
+use crate::chain::keysinterface::WriteableEcdsaChannelSigner;
use crate::chain::transaction::{OutPoint, TransactionData};
use crate::prelude::*;
/// multiple instances.
///
/// [`PermanentFailure`]: ChannelMonitorUpdateStatus::PermanentFailure
-pub trait Watch<ChannelSigner: Sign> {
+pub trait Watch<ChannelSigner: WriteableEcdsaChannelSigner> {
/// Watches a channel identified by `funding_txo` using `monitor`.
///
/// Implementations are responsible for watching the chain for the funding transaction along
/// [`ChannelMonitorUpdateStatus`] for invariants around returning an error.
///
/// [`update_monitor`]: channelmonitor::ChannelMonitor::update_monitor
- fn update_channel(&self, funding_txo: OutPoint, update: ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus;
+ fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus;
/// Returns any monitor events since the last call. Subsequent calls must only return new
/// events.
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1;
-use crate::chain::keysinterface::{BaseSign, EntropySource, SignerProvider};
+use crate::chain::keysinterface::{ChannelSigner, EntropySource, SignerProvider};
use crate::ln::msgs::DecodeError;
use crate::ln::PaymentPreimage;
#[cfg(anchors)]
use crate::chain::chaininterface::ConfirmationTarget;
use crate::chain::chaininterface::{FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator};
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER};
-use crate::chain::keysinterface::Sign;
+use crate::chain::keysinterface::WriteableEcdsaChannelSigner;
#[cfg(anchors)]
use crate::chain::package::PackageSolvingData;
use crate::chain::package::PackageTemplate;
/// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and
/// do RBF bumping if possible.
-pub struct OnchainTxHandler<ChannelSigner: Sign> {
+#[derive(PartialEq)]
+pub struct OnchainTxHandler<ChannelSigner: WriteableEcdsaChannelSigner> {
destination_script: Script,
holder_commitment: HolderCommitmentTransaction,
// holder_htlc_sigs and prev_holder_htlc_sigs are in the order as they appear in the commitment
const SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 1;
-impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
+impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
pub(crate) fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
}
}
-impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
+impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
pub(crate) fn new(destination_script: Script, signer: ChannelSigner, channel_parameters: ChannelTransactionParameters, holder_commitment: HolderCommitmentTransaction, secp_ctx: Secp256k1<secp256k1::All>) -> Self {
OnchainTxHandler {
destination_script,
// remove it once it reaches the confirmation threshold, or to generate a new claim if the
// transaction is reorged out.
let mut all_inputs_have_confirmed_spend = true;
- for outpoint in &request_outpoints {
- if let Some(first_claim_txid_height) = self.claimable_outpoints.get(outpoint) {
+ for outpoint in request_outpoints.iter() {
+ if let Some(first_claim_txid_height) = self.claimable_outpoints.get(*outpoint) {
// We check for outpoint spends within claims individually rather than as a set
// since requests can have outpoints split off.
if !self.onchain_events_awaiting_threshold_conf.iter()
for outpoint in request.outpoints() {
log_debug!(logger, "Removing claim tracking for {} due to maturation of claim package {}.",
outpoint, log_bytes!(package_id));
- self.claimable_outpoints.remove(&outpoint);
+ self.claimable_outpoints.remove(outpoint);
#[cfg(anchors)]
self.pending_claim_events.remove(&package_id);
}
OnchainEvent::ContentiousOutpoint { package } => {
log_debug!(logger, "Removing claim tracking due to maturation of claim tx for outpoints:");
log_debug!(logger, " {:?}", package.outpoints());
- self.claimable_outpoints.remove(&package.outpoints()[0]);
+ self.claimable_outpoints.remove(package.outpoints()[0]);
}
}
} else {
//- resurect outpoint back in its claimable set and regenerate tx
match entry.event {
OnchainEvent::ContentiousOutpoint { package } => {
- if let Some(ancestor_claimable_txid) = self.claimable_outpoints.get(&package.outpoints()[0]) {
+ if let Some(ancestor_claimable_txid) = self.claimable_outpoints.get(package.outpoints()[0]) {
if let Some(request) = self.pending_claim_requests.get_mut(&ancestor_claimable_txid.0) {
request.merge_package(package);
// Using a HashMap guarantee us than if we have multiple outpoints getting
use crate::ln::chan_utils;
use crate::ln::msgs::DecodeError;
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
-use crate::chain::keysinterface::Sign;
+use crate::chain::keysinterface::WriteableEcdsaChannelSigner;
#[cfg(anchors)]
use crate::chain::onchaintx::ExternalHTLCClaim;
use crate::chain::onchaintx::OnchainTxHandler;
_ => { mem::discriminant(self) == mem::discriminant(&input) }
}
}
- fn finalize_input<Signer: Sign>(&self, bumped_tx: &mut Transaction, i: usize, onchain_handler: &mut OnchainTxHandler<Signer>) -> bool {
+ fn finalize_input<Signer: WriteableEcdsaChannelSigner>(&self, bumped_tx: &mut Transaction, i: usize, onchain_handler: &mut OnchainTxHandler<Signer>) -> bool {
match self {
PackageSolvingData::RevokedOutput(ref outp) => {
let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint);
}
true
}
- fn get_finalized_tx<Signer: Sign>(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler<Signer>) -> Option<Transaction> {
+ fn get_finalized_tx<Signer: WriteableEcdsaChannelSigner>(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler<Signer>) -> Option<Transaction> {
match self {
PackageSolvingData::HolderHTLCOutput(ref outp) => {
debug_assert!(!outp.opt_anchors());
inputs_weight + witnesses_weight + transaction_weight + output_weight
}
#[cfg(anchors)]
- pub(crate) fn construct_malleable_package_with_external_funding<Signer: Sign>(
+ pub(crate) fn construct_malleable_package_with_external_funding<Signer: WriteableEcdsaChannelSigner>(
&self, onchain_handler: &mut OnchainTxHandler<Signer>,
) -> Option<Vec<ExternalHTLCClaim>> {
debug_assert!(self.requires_external_funding());
}
htlcs
}
- pub(crate) fn finalize_malleable_package<L: Deref, Signer: Sign>(
+ pub(crate) fn finalize_malleable_package<L: Deref, Signer: WriteableEcdsaChannelSigner>(
&self, onchain_handler: &mut OnchainTxHandler<Signer>, value: u64, destination_script: Script, logger: &L
) -> Option<Transaction> where L::Target: Logger {
debug_assert!(self.is_malleable());
log_debug!(logger, "Finalized transaction {} ready to broadcast", bumped_tx.txid());
Some(bumped_tx)
}
- pub(crate) fn finalize_untractable_package<L: Deref, Signer: Sign>(
+ pub(crate) fn finalize_untractable_package<L: Deref, Signer: WriteableEcdsaChannelSigner>(
&self, onchain_handler: &mut OnchainTxHandler<Signer>, logger: &L,
) -> Option<Transaction> where L::Target: Logger {
debug_assert!(!self.is_malleable());
use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
use crate::util::test_utils;
- use crate::chain::keysinterface::{BaseSign, SignerProvider};
+ use crate::chain::keysinterface::{ChannelSigner, SignerProvider};
use bitcoin::{Network, Txid};
use bitcoin::hashes::Hash;
use crate::ln::PaymentHash;
// Check that even though the persister is returning a InProgress,
// because the update is bogus, ultimately the error that's returned
// should be a PermanentFailure.
- if let ChannelMonitorUpdateStatus::PermanentFailure = chain_mon.chain_monitor.update_channel(outpoint, update.clone()) {} else { panic!("Expected monitor error to be permanent"); }
+ if let ChannelMonitorUpdateStatus::PermanentFailure = chain_mon.chain_monitor.update_channel(outpoint, &update) {} else { panic!("Expected monitor error to be permanent"); }
logger.assert_log_regex("lightning::chain::chainmonitor".to_string(), regex::Regex::new("Persistence of ChannelMonitorUpdate for channel [0-9a-f]* in progress").unwrap(), 1);
- assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, update), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
} else { assert!(false); }
}
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 43, None).unwrap();
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
let (temporary_channel_id, funding_tx, funding_output) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 43);
chanmon_cfgs[1].persister.set_update_ret(ChannelMonitorUpdateStatus::InProgress);
nodes[0].node.close_channel(&channel_id, &nodes[1].node.get_our_node_id()).unwrap();
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id()));
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id()));
check_added_monitors!(nodes[1], 1);
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id()));
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id()));
check_added_monitors!(nodes[0], 1);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[0].node.close_channel(&channel_id, &nodes[1].node.get_our_node_id()).is_ok());
let shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &shutdown);
check_closed_broadcast!(nodes[1], true);
check_added_monitors!(nodes[1], 2);
check_closed_event!(nodes[1], 1, ClosureReason::ProcessingError { err: "ChannelMonitor storage failure".to_string() });
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 43, None).unwrap();
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &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(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
let events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
_ => panic!("Unexpected event"),
};
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &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(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
let (temporary_channel_id, funding_tx, ..) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 43);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 43, None).unwrap();
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &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(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
let events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
_ => panic!("Unexpected event"),
};
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &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(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
let (temporary_channel_id, funding_tx, ..) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 43);
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator};
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::chain::transaction::{OutPoint, TransactionData};
-use crate::chain::keysinterface::{Sign, EntropySource, BaseSign, SignerProvider};
+use crate::chain::keysinterface::{WriteableEcdsaChannelSigner, EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient};
use crate::util::events::ClosureReason;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, VecWriter};
use crate::util::logger::Logger;
//
// Holder designates channel data owned for the benefice of the user client.
// Counterparty designates channel data owned by the another channel participant entity.
-pub(super) struct Channel<Signer: Sign> {
+pub(super) struct Channel<Signer: ChannelSigner> {
config: LegacyChannelConfig,
// Track the previous `ChannelConfig` so that we can continue forwarding HTLCs that were
};
}
-impl<Signer: Sign> Channel<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
/// Returns the value to use for `holder_max_htlc_value_in_flight_msat` as a percentage of the
/// `channel_value_satoshis` in msat, set through
/// [`ChannelHandshakeConfig::max_inbound_htlc_value_in_flight_percent_of_channel`]
self.channel_transaction_parameters.opt_anchors.is_some()
}
- fn get_initial_channel_type(config: &UserConfig) -> ChannelTypeFeatures {
+ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) -> ChannelTypeFeatures {
// The default channel type (ie the first one we try) depends on whether the channel is
// public - if it is, we just go with `only_static_remotekey` as it's the only option
// available. If it's private, we first try `scid_privacy` as it provides better privacy
- // with no other changes, and fall back to `only_static_remotekey`
+ // with no other changes, and fall back to `only_static_remotekey`.
let mut ret = ChannelTypeFeatures::only_static_remote_key();
- if !config.channel_handshake_config.announced_channel && config.channel_handshake_config.negotiate_scid_privacy {
+ if !config.channel_handshake_config.announced_channel &&
+ config.channel_handshake_config.negotiate_scid_privacy &&
+ their_features.supports_scid_privacy() {
ret.set_scid_privacy_required();
}
+
+ // Optionally, if the user would like to negotiate the `anchors_zero_fee_htlc_tx` option, we
+ // set it now. If they don't understand it, we'll fall back to our default of
+ // `only_static_remotekey`.
+ #[cfg(anchors)]
+ { // Attributes are not allowed on if expressions on our current MSRV of 1.41.
+ if config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx &&
+ their_features.supports_anchors_zero_fee_htlc_tx() {
+ ret.set_anchors_zero_fee_htlc_tx_required();
+ }
+ }
+
ret
}
// We've exhausted our options
return Err(());
}
- self.channel_type = ChannelTypeFeatures::only_static_remote_key(); // We only currently support two types
+ // We support opening a few different types of channels. Try removing our additional
+ // features one by one until we've either arrived at our default or the counterparty has
+ // accepted one.
+ //
+ // Due to the order below, we may not negotiate `option_anchors_zero_fee_htlc_tx` if the
+ // counterparty doesn't support `option_scid_privacy`. Since `get_initial_channel_type`
+ // checks whether the counterparty supports every feature, this would only happen if the
+ // counterparty is advertising the feature, but rejecting channels proposing the feature for
+ // whatever reason.
+ if self.channel_type.supports_anchors_zero_fee_htlc_tx() {
+ self.channel_type.clear_anchors_zero_fee_htlc_tx();
+ assert!(self.channel_transaction_parameters.opt_non_zero_fee_anchors.is_none());
+ self.channel_transaction_parameters.opt_anchors = None;
+ } else if self.channel_type.supports_scid_privacy() {
+ self.channel_type.clear_scid_privacy();
+ } else {
+ self.channel_type = ChannelTypeFeatures::only_static_remote_key();
+ }
Ok(self.get_open_channel(chain_hash))
}
SP::Target: SignerProvider<Signer = Signer>,
F::Target: FeeEstimator,
{
- let opt_anchors = false; // TODO - should be based on features
-
let holder_selected_contest_delay = config.channel_handshake_config.our_to_self_delay;
let channel_keys_id = signer_provider.generate_channel_keys_id(false, channel_value_satoshis, user_id);
let holder_signer = signer_provider.derive_channel_signer(channel_value_satoshis, channel_keys_id);
return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) });
}
+ let channel_type = Self::get_initial_channel_type(&config, their_features);
+ debug_assert!(channel_type.is_subset(&channelmanager::provided_channel_type_features(&config)));
+
let feerate = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::Normal);
let value_to_self_msat = channel_value_satoshis * 1000 - push_msat;
- let commitment_tx_fee = Self::commit_tx_fee_msat(feerate, MIN_AFFORDABLE_HTLC_COUNT, opt_anchors);
+ let commitment_tx_fee = Self::commit_tx_fee_msat(feerate, MIN_AFFORDABLE_HTLC_COUNT, channel_type.requires_anchors_zero_fee_htlc_tx());
if value_to_self_msat < commitment_tx_fee {
return Err(APIError::APIMisuseError{ err: format!("Funding amount ({}) can't even pay fee for initial commitment transaction fee of {}.", value_to_self_msat / 1000, commitment_tx_fee / 1000) });
}
is_outbound_from_holder: true,
counterparty_parameters: None,
funding_outpoint: None,
- opt_anchors: if opt_anchors { Some(()) } else { None },
+ opt_anchors: if channel_type.requires_anchors_zero_fee_htlc_tx() { Some(()) } else { None },
opt_non_zero_fee_anchors: None
},
funding_transaction: None,
#[cfg(any(test, fuzzing))]
historical_inbound_htlc_fulfills: HashSet::new(),
- channel_type: Self::get_initial_channel_type(&config),
+ channel_type,
channel_keys_id,
})
}
/// Creates a new channel from a remote sides' request for one.
/// Assumes chain_hash has already been checked and corresponds with what we expect!
pub fn new_from_req<ES: Deref, SP: Deref, F: Deref, L: Deref>(
- fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP, counterparty_node_id: PublicKey, their_features: &InitFeatures,
- msg: &msgs::OpenChannel, user_id: u128, config: &UserConfig, current_chain_height: u32, logger: &L,
- outbound_scid_alias: u64
+ fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
+ counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures,
+ their_features: &InitFeatures, msg: &msgs::OpenChannel, user_id: u128, config: &UserConfig,
+ current_chain_height: u32, logger: &L, outbound_scid_alias: u64
) -> Result<Channel<Signer>, ChannelError>
where ES::Target: EntropySource,
SP::Target: SignerProvider<Signer = Signer>,
F::Target: FeeEstimator,
L::Target: Logger,
{
- let opt_anchors = false; // TODO - should be based on features
let announced_channel = if (msg.channel_flags & 1) == 1 { true } else { false };
// First check the channel type is known, failing before we do anything else if we don't
return Err(ChannelError::Close("Channel Type field contained optional bits - this is not allowed".to_owned()));
}
- if channel_type.requires_unknown_bits() {
- return Err(ChannelError::Close("Channel Type field contains unknown bits".to_owned()));
+ // We only support the channel types defined by the `ChannelManager` in
+ // `provided_channel_type_features`. The channel type must always support
+ // `static_remote_key`.
+ if !channel_type.requires_static_remote_key() {
+ return Err(ChannelError::Close("Channel Type was not understood - we require static remote key".to_owned()));
}
-
- // We currently only allow four channel types, so write it all out here - we allow
- // `only_static_remote_key` or `static_remote_key | zero_conf` in all contexts, and
- // further allow `static_remote_key | scid_privacy` or
- // `static_remote_key | scid_privacy | zero_conf`, if the channel is not
- // publicly announced.
- if *channel_type != ChannelTypeFeatures::only_static_remote_key() {
- if !channel_type.requires_scid_privacy() && !channel_type.requires_zero_conf() {
- return Err(ChannelError::Close("Channel Type was not understood".to_owned()));
- }
-
- if channel_type.requires_scid_privacy() && announced_channel {
- return Err(ChannelError::Close("SCID Alias/Privacy Channel Type cannot be set on a public channel".to_owned()));
- }
+ // Make sure we support all of the features behind the channel type.
+ if !channel_type.is_subset(our_supported_features) {
+ return Err(ChannelError::Close("Channel Type contains unsupported features".to_owned()));
+ }
+ if channel_type.requires_scid_privacy() && announced_channel {
+ return Err(ChannelError::Close("SCID Alias/Privacy Channel Type cannot be set on a public channel".to_owned()));
}
channel_type.clone()
} else {
- ChannelTypeFeatures::from_counterparty_init(&their_features)
+ let channel_type = ChannelTypeFeatures::from_init(&their_features);
+ if channel_type != ChannelTypeFeatures::only_static_remote_key() {
+ return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned()));
+ }
+ channel_type
};
- if !channel_type.supports_static_remote_key() {
- return Err(ChannelError::Close("Channel Type was not understood - we require static remote key".to_owned()));
- }
+ let opt_anchors = channel_type.supports_anchors_zero_fee_htlc_tx();
let channel_keys_id = signer_provider.generate_channel_keys_id(true, msg.funding_satoshis, user_id);
let holder_signer = signer_provider.derive_channel_signer(msg.funding_satoshis, channel_keys_id);
} else if their_features.supports_channel_type() {
// Assume they've accepted the channel type as they said they understand it.
} else {
- self.channel_type = ChannelTypeFeatures::from_counterparty_init(&their_features)
+ let channel_type = ChannelTypeFeatures::from_init(&their_features);
+ if channel_type != ChannelTypeFeatures::only_static_remote_key() {
+ return Err(ChannelError::Close("Only static_remote_key is supported for non-negotiated channel types".to_owned()));
+ }
+ self.channel_type = channel_type;
}
let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() {
/// Handles a channel_ready message from our peer. If we've already sent our channel_ready
/// and the channel is now usable (and public), this may generate an announcement_signatures to
/// reply with.
- pub fn channel_ready<L: Deref>(&mut self, msg: &msgs::ChannelReady, node_pk: PublicKey, genesis_block_hash: BlockHash, user_config: &UserConfig, best_block: &BestBlock, logger: &L) -> Result<Option<msgs::AnnouncementSignatures>, ChannelError> where L::Target: Logger {
+ pub fn channel_ready<NS: Deref, L: Deref>(
+ &mut self, msg: &msgs::ChannelReady, node_signer: &NS, genesis_block_hash: BlockHash,
+ user_config: &UserConfig, best_block: &BestBlock, logger: &L
+ ) -> Result<Option<msgs::AnnouncementSignatures>, ChannelError>
+ where
+ NS::Target: NodeSigner,
+ L::Target: Logger
+ {
if self.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
self.workaround_lnd_bug_4006 = Some(msg.clone());
return Err(ChannelError::Ignore("Peer sent channel_ready when we needed a channel_reestablish. The peer is likely lnd, see https://github.com/lightningnetwork/lnd/issues/4006".to_owned()));
log_info!(logger, "Received channel_ready from peer for channel {}", log_bytes!(self.channel_id()));
- Ok(self.get_announcement_sigs(node_pk, genesis_block_hash, user_config, best_block.height(), logger))
+ Ok(self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block.height(), logger))
}
/// Returns transaction if there is pending funding transaction that is yet to broadcast
/// Indicates that the latest ChannelMonitor update has been committed by the client
/// successfully and we should restore normal operation. Returns messages which should be sent
/// to the remote side.
- pub fn monitor_updating_restored<L: Deref>(&mut self, logger: &L, node_pk: PublicKey, genesis_block_hash: BlockHash, user_config: &UserConfig, best_block_height: u32) -> MonitorRestoreUpdates where L::Target: Logger {
+ pub fn monitor_updating_restored<L: Deref, NS: Deref>(
+ &mut self, logger: &L, node_signer: &NS, genesis_block_hash: BlockHash,
+ user_config: &UserConfig, best_block_height: u32
+ ) -> MonitorRestoreUpdates
+ where
+ L::Target: Logger,
+ NS::Target: NodeSigner
+ {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateInProgress as u32, ChannelState::MonitorUpdateInProgress as u32);
self.channel_state &= !(ChannelState::MonitorUpdateInProgress as u32);
})
} else { None };
- let announcement_sigs = self.get_announcement_sigs(node_pk, genesis_block_hash, user_config, best_block_height, logger);
+ let announcement_sigs = self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block_height, logger);
let mut accepted_htlcs = Vec::new();
mem::swap(&mut accepted_htlcs, &mut self.monitor_pending_forwards);
/// `cargo doc --document-private-items`):
/// [`super::channelmanager::ChannelManager::force_close_without_broadcasting_txn`] and
/// [`super::channelmanager::ChannelManager::force_close_all_channels_without_broadcasting_txn`].
- pub fn channel_reestablish<L: Deref>(&mut self, msg: &msgs::ChannelReestablish, logger: &L,
- node_pk: PublicKey, genesis_block_hash: BlockHash, user_config: &UserConfig, best_block: &BestBlock)
- -> Result<ReestablishResponses, ChannelError> where L::Target: Logger {
+ pub fn channel_reestablish<L: Deref, NS: Deref>(
+ &mut self, msg: &msgs::ChannelReestablish, logger: &L, node_signer: &NS,
+ genesis_block_hash: BlockHash, user_config: &UserConfig, best_block: &BestBlock
+ ) -> Result<ReestablishResponses, ChannelError>
+ where
+ L::Target: Logger,
+ NS::Target: NodeSigner
+ {
if self.channel_state & (ChannelState::PeerDisconnected as u32) == 0 {
// While BOLT 2 doesn't indicate explicitly we should error this channel here, it
// almost certainly indicates we are going to end up out-of-sync in some way, so we
})
} else { None };
- let announcement_sigs = self.get_announcement_sigs(node_pk, genesis_block_hash, user_config, best_block.height(), logger);
+ let announcement_sigs = self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, best_block.height(), logger);
if self.channel_state & (ChannelState::FundingSent as u32) == ChannelState::FundingSent as u32 {
// If we're waiting on a monitor update, we shouldn't re-send any channel_ready's.
/// When a transaction is confirmed, we check whether it is or spends the funding transaction
/// In the first case, we store the confirmation height and calculating the short channel id.
/// In the second, we simply return an Err indicating we need to be force-closed now.
- pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
- txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, user_config: &UserConfig, logger: &L)
- -> Result<(Option<msgs::ChannelReady>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
+ pub fn transactions_confirmed<NS: Deref, L: Deref>(
+ &mut self, block_hash: &BlockHash, height: u32, txdata: &TransactionData,
+ genesis_block_hash: BlockHash, node_signer: &NS, user_config: &UserConfig, logger: &L
+ ) -> Result<(Option<msgs::ChannelReady>, Option<msgs::AnnouncementSignatures>), ClosureReason>
+ where
+ NS::Target: NodeSigner,
+ L::Target: Logger
+ {
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// Check if the transaction is the expected funding transaction, and if it is,
// may have already happened for this block).
if let Some(channel_ready) = self.check_get_channel_ready(height) {
log_info!(logger, "Sending a channel_ready to our peer for channel {}", log_bytes!(self.channel_id));
- let announcement_sigs = self.get_announcement_sigs(node_pk, genesis_block_hash, user_config, height, logger);
+ let announcement_sigs = self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, height, logger);
return Ok((Some(channel_ready), announcement_sigs));
}
}
///
/// May return some HTLCs (and their payment_hash) which have timed out and should be failed
/// back.
- pub fn best_block_updated<L: Deref>(&mut self, height: u32, highest_header_time: u32, genesis_block_hash: BlockHash, node_pk: PublicKey, user_config: UserConfig, logger: &L)
- -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
- self.do_best_block_updated(height, highest_header_time, Some((genesis_block_hash, node_pk, user_config)), logger)
+ pub fn best_block_updated<NS: Deref, L: Deref>(
+ &mut self, height: u32, highest_header_time: u32, genesis_block_hash: BlockHash,
+ node_signer: &NS, user_config: &UserConfig, logger: &L
+ ) -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason>
+ where
+ NS::Target: NodeSigner,
+ L::Target: Logger
+ {
+ self.do_best_block_updated(height, highest_header_time, Some((genesis_block_hash, node_signer, user_config)), logger)
}
- fn do_best_block_updated<L: Deref>(&mut self, height: u32, highest_header_time: u32, genesis_node_pk: Option<(BlockHash, PublicKey, UserConfig)>, logger: &L)
- -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
+ fn do_best_block_updated<NS: Deref, L: Deref>(
+ &mut self, height: u32, highest_header_time: u32,
+ genesis_node_signer: Option<(BlockHash, &NS, &UserConfig)>, logger: &L
+ ) -> Result<(Option<msgs::ChannelReady>, Vec<(HTLCSource, PaymentHash)>, Option<msgs::AnnouncementSignatures>), ClosureReason>
+ where
+ NS::Target: NodeSigner,
+ L::Target: Logger
+ {
let mut timed_out_htlcs = Vec::new();
// This mirrors the check in ChannelManager::decode_update_add_htlc_onion, refusing to
// forward an HTLC when our counterparty should almost certainly just fail it for expiring
self.update_time_counter = cmp::max(self.update_time_counter, highest_header_time);
if let Some(channel_ready) = self.check_get_channel_ready(height) {
- let announcement_sigs = if let Some((genesis_block_hash, node_pk, user_config)) = genesis_node_pk {
- self.get_announcement_sigs(node_pk, genesis_block_hash, &user_config, height, logger)
+ let announcement_sigs = if let Some((genesis_block_hash, node_signer, user_config)) = genesis_node_signer {
+ self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, height, logger)
} else { None };
log_info!(logger, "Sending a channel_ready to our peer for channel {}", log_bytes!(self.channel_id));
return Ok((Some(channel_ready), timed_out_htlcs, announcement_sigs));
return Err(ClosureReason::FundingTimedOut);
}
- let announcement_sigs = if let Some((genesis_block_hash, node_pk, user_config)) = genesis_node_pk {
- self.get_announcement_sigs(node_pk, genesis_block_hash, &user_config, height, logger)
+ let announcement_sigs = if let Some((genesis_block_hash, node_signer, user_config)) = genesis_node_signer {
+ self.get_announcement_sigs(node_signer, genesis_block_hash, user_config, height, logger)
} else { None };
Ok((None, timed_out_htlcs, announcement_sigs))
}
// larger. If we don't know that time has moved forward, we can just set it to the last
// time we saw and it will be ignored.
let best_time = self.update_time_counter;
- match self.do_best_block_updated(reorg_height, best_time, None, logger) {
+ match self.do_best_block_updated(reorg_height, best_time, None::<(BlockHash, &&NodeSigner, &UserConfig)>, logger) {
Ok((channel_ready, timed_out_htlcs, announcement_sigs)) => {
assert!(channel_ready.is_none(), "We can't generate a funding with 0 confirmations?");
assert!(timed_out_htlcs.is_empty(), "We can't have accepted HTLCs with a timeout before our funding confirmation?");
/// closing).
///
/// This will only return ChannelError::Ignore upon failure.
- fn get_channel_announcement(&self, node_id: PublicKey, chain_hash: BlockHash, user_config: &UserConfig) -> Result<msgs::UnsignedChannelAnnouncement, ChannelError> {
+ fn get_channel_announcement<NS: Deref>(
+ &self, node_signer: &NS, chain_hash: BlockHash, user_config: &UserConfig,
+ ) -> Result<msgs::UnsignedChannelAnnouncement, ChannelError> where NS::Target: NodeSigner {
if !self.config.announced_channel {
return Err(ChannelError::Ignore("Channel is not available for public announcements".to_owned()));
}
return Err(ChannelError::Ignore("Cannot get a ChannelAnnouncement if the channel is not currently usable".to_owned()));
}
+ let node_id = node_signer.get_node_id(Recipient::Node)
+ .map_err(|_| ChannelError::Ignore("Failed to retrieve own public key".to_owned()))?;
let were_node_one = node_id.serialize()[..] < self.counterparty_node_id.serialize()[..];
let msg = msgs::UnsignedChannelAnnouncement {
Ok(msg)
}
- fn get_announcement_sigs<L: Deref>(&mut self, node_pk: PublicKey, genesis_block_hash: BlockHash, user_config: &UserConfig, best_block_height: u32, logger: &L)
- -> Option<msgs::AnnouncementSignatures> where L::Target: Logger {
+ fn get_announcement_sigs<NS: Deref, L: Deref>(
+ &mut self, node_signer: &NS, genesis_block_hash: BlockHash, user_config: &UserConfig,
+ best_block_height: u32, logger: &L
+ ) -> Option<msgs::AnnouncementSignatures>
+ where
+ NS::Target: NodeSigner,
+ L::Target: Logger
+ {
if self.funding_tx_confirmation_height == 0 || self.funding_tx_confirmation_height + 5 > best_block_height {
return None;
}
}
log_trace!(logger, "Creating an announcement_signatures message for channel {}", log_bytes!(self.channel_id()));
- let announcement = match self.get_channel_announcement(node_pk, genesis_block_hash, user_config) {
+ let announcement = match self.get_channel_announcement(node_signer, genesis_block_hash, user_config) {
Ok(a) => a,
- Err(_) => {
- log_trace!(logger, "Cannot create an announcement_signatures as channel is not public.");
+ Err(e) => {
+ log_trace!(logger, "{:?}", e);
return None;
}
};
- let (our_node_sig, our_bitcoin_sig) = match self.holder_signer.sign_channel_announcement(&announcement, &self.secp_ctx) {
+ let our_node_sig = match node_signer.sign_gossip_message(msgs::UnsignedGossipMessage::ChannelAnnouncement(&announcement)) {
+ Err(_) => {
+ log_error!(logger, "Failed to generate node signature for channel_announcement. Channel will not be announced!");
+ return None;
+ },
+ Ok(v) => v
+ };
+ let our_bitcoin_sig = match self.holder_signer.sign_channel_announcement_with_funding_key(&announcement, &self.secp_ctx) {
Err(_) => {
log_error!(logger, "Signer rejected channel_announcement signing. Channel will not be announced!");
return None;
/// Signs the given channel announcement, returning a ChannelError::Ignore if no keys are
/// available.
- fn sign_channel_announcement(&self, our_node_id: PublicKey, announcement: msgs::UnsignedChannelAnnouncement) -> Result<msgs::ChannelAnnouncement, ChannelError> {
+ fn sign_channel_announcement<NS: Deref>(
+ &self, node_signer: &NS, announcement: msgs::UnsignedChannelAnnouncement
+ ) -> Result<msgs::ChannelAnnouncement, ChannelError> where NS::Target: NodeSigner {
if let Some((their_node_sig, their_bitcoin_sig)) = self.announcement_sigs {
- let were_node_one = announcement.node_id_1 == our_node_id;
+ let our_node_key = node_signer.get_node_id(Recipient::Node)
+ .map_err(|_| ChannelError::Ignore("Signer failed to retrieve own public key".to_owned()))?;
+ let were_node_one = announcement.node_id_1 == our_node_key;
- let (our_node_sig, our_bitcoin_sig) = self.holder_signer.sign_channel_announcement(&announcement, &self.secp_ctx)
+ let our_node_sig = node_signer.sign_gossip_message(msgs::UnsignedGossipMessage::ChannelAnnouncement(&announcement))
+ .map_err(|_| ChannelError::Ignore("Failed to generate node signature for channel_announcement".to_owned()))?;
+ let our_bitcoin_sig = self.holder_signer.sign_channel_announcement_with_funding_key(&announcement, &self.secp_ctx)
.map_err(|_| ChannelError::Ignore("Signer rejected channel_announcement".to_owned()))?;
Ok(msgs::ChannelAnnouncement {
node_signature_1: if were_node_one { our_node_sig } else { their_node_sig },
/// Processes an incoming announcement_signatures message, providing a fully-signed
/// channel_announcement message which we can broadcast and storing our counterparty's
/// signatures for later reconstruction/rebroadcast of the channel_announcement.
- pub fn announcement_signatures(&mut self, our_node_id: PublicKey, chain_hash: BlockHash, best_block_height: u32, msg: &msgs::AnnouncementSignatures, user_config: &UserConfig) -> Result<msgs::ChannelAnnouncement, ChannelError> {
- let announcement = self.get_channel_announcement(our_node_id.clone(), chain_hash, user_config)?;
+ pub fn announcement_signatures<NS: Deref>(
+ &mut self, node_signer: &NS, chain_hash: BlockHash, best_block_height: u32,
+ msg: &msgs::AnnouncementSignatures, user_config: &UserConfig
+ ) -> Result<msgs::ChannelAnnouncement, ChannelError> where NS::Target: NodeSigner {
+ let announcement = self.get_channel_announcement(node_signer, chain_hash, user_config)?;
let msghash = hash_to_message!(&Sha256d::hash(&announcement.encode()[..])[..]);
"Got announcement_signatures prior to the required six confirmations - we may not have received a block yet that our peer has".to_owned()));
}
- self.sign_channel_announcement(our_node_id, announcement)
+ self.sign_channel_announcement(node_signer, announcement)
}
/// Gets a signed channel_announcement for this channel, if we previously received an
/// announcement_signatures from our counterparty.
- pub fn get_signed_channel_announcement(&self, our_node_id: PublicKey, chain_hash: BlockHash, best_block_height: u32, user_config: &UserConfig) -> Option<msgs::ChannelAnnouncement> {
+ pub fn get_signed_channel_announcement<NS: Deref>(
+ &self, node_signer: &NS, chain_hash: BlockHash, best_block_height: u32, user_config: &UserConfig
+ ) -> Option<msgs::ChannelAnnouncement> where NS::Target: NodeSigner {
if self.funding_tx_confirmation_height == 0 || self.funding_tx_confirmation_height + 5 > best_block_height {
return None;
}
- let announcement = match self.get_channel_announcement(our_node_id.clone(), chain_hash, user_config) {
+ let announcement = match self.get_channel_announcement(node_signer, chain_hash, user_config) {
Ok(res) => res,
Err(_) => return None,
};
- match self.sign_channel_announcement(our_node_id, announcement) {
+ match self.sign_channel_announcement(node_signer, announcement) {
Ok(res) => Some(res),
Err(_) => None,
}
}
}
-impl<Signer: Sign> Writeable for Channel<Signer> {
+impl<Signer: WriteableEcdsaChannelSigner> Writeable for Channel<Signer> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// Note that we write out as if remove_uncommitted_htlcs_and_mark_paused had just been
// called.
}
const MAX_ALLOC_SIZE: usize = 64*1024;
-impl<'a, 'b, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32)> for Channel<<SP::Target as SignerProvider>::Signer>
+impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c ChannelTypeFeatures)> for Channel<<SP::Target as SignerProvider>::Signer>
where
ES::Target: EntropySource,
SP::Target: SignerProvider
{
- fn read<R : io::Read>(reader: &mut R, args: (&'a ES, &'b SP, u32)) -> Result<Self, DecodeError> {
- let (entropy_source, signer_provider, serialized_height) = args;
+ fn read<R : io::Read>(reader: &mut R, args: (&'a ES, &'b SP, u32, &'c ChannelTypeFeatures)) -> Result<Self, DecodeError> {
+ let (entropy_source, signer_provider, serialized_height, our_supported_features) = args;
let ver = read_ver_prefix!(reader, SERIALIZATION_VERSION);
// `user_id` used to be a single u64 value. In order to remain backwards compatible with
}
let chan_features = channel_type.as_ref().unwrap();
- if chan_features.supports_unknown_bits() || chan_features.requires_unknown_bits() {
+ if !chan_features.is_subset(our_supported_features) {
// If the channel was written by a new version and negotiated with features we don't
// understand yet, refuse to read it.
return Err(DecodeError::UnknownRequiredFeature);
}
- if channel_parameters.opt_anchors.is_some() {
- // Relax this check when ChannelTypeFeatures supports anchors.
- return Err(DecodeError::InvalidValue);
- }
-
let mut secp_ctx = Secp256k1::new();
secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());
use hex;
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
+ #[cfg(anchors)]
+ use crate::ln::channel::InitFeatures;
use crate::ln::channel::{Channel, InboundHTLCOutput, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator};
use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
use crate::ln::features::ChannelTypeFeatures;
use crate::ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight};
use crate::chain::BestBlock;
use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget};
- use crate::chain::keysinterface::{BaseSign, InMemorySigner, Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
+ use crate::chain::keysinterface::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider};
use crate::chain::transaction::OutPoint;
use crate::util::config::UserConfig;
use crate::util::enforcing_trait_impls::EnforcingSigner;
use crate::util::errors::APIError;
use crate::util::test_utils;
use crate::util::test_utils::OnGetShutdownScriptpubkey;
- use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature, Scalar};
+ use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
use bitcoin::secp256k1::ffi::Signature as FFISignature;
use bitcoin::secp256k1::{SecretKey,PublicKey};
- use bitcoin::secp256k1::ecdh::SharedSecret;
- use bitcoin::secp256k1::ecdsa::RecoverableSignature;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use bitcoin::hash_types::WPubkeyHash;
- use bitcoin::bech32::u5;
use bitcoin::PackedLockTime;
use bitcoin::util::address::WitnessVersion;
use crate::prelude::*;
fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] }
}
- impl NodeSigner for Keys {
- fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> { panic!(); }
-
- fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
- let secp_ctx = Secp256k1::signing_only();
- Ok(PublicKey::from_secret_key(&secp_ctx, &self.get_node_secret(recipient)?))
- }
-
- fn ecdh(&self, _recipient: Recipient, _other_key: &PublicKey, _tweak: Option<&Scalar>) -> Result<SharedSecret, ()> { panic!(); }
-
- fn get_inbound_payment_key_material(&self) -> KeyMaterial { panic!(); }
-
- fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> { panic!(); }
- }
-
impl SignerProvider for Keys {
type Signer = InMemorySigner;
// Make sure A's dust limit is as we expect.
let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
- let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, 42).unwrap();
+ let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, 42).unwrap();
// Node B --> Node A: accept channel, explicitly setting B's dust limit.
let mut accept_channel_msg = node_b_chan.accept_inbound_channel(0);
// Create Node B's channel by receiving Node A's open_channel message
let open_channel_msg = node_a_chan.get_open_channel(chain_hash);
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
- let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, 42).unwrap();
+ let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, 42).unwrap();
// Node B --> Node A: accept channel
let accept_channel_msg = node_b_chan.accept_inbound_channel(0);
// Test that `new_from_req` creates a channel with the correct value for
// `holder_max_htlc_value_in_flight_msat`, when configured with a valid percentage value,
// which is set to the lower bound - 1 (2%) of the `channel_value`.
- let chan_3 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_init_features(&config_2_percent), &chan_1_open_channel_msg, 7, &config_2_percent, 0, &&logger, 42).unwrap();
+ let chan_3 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_2_percent), &channelmanager::provided_init_features(&config_2_percent), &chan_1_open_channel_msg, 7, &config_2_percent, 0, &&logger, 42).unwrap();
let chan_3_value_msat = chan_3.channel_value_satoshis * 1000;
assert_eq!(chan_3.holder_max_htlc_value_in_flight_msat, (chan_3_value_msat as f64 * 0.02) as u64);
// Test with the upper bound - 1 of valid values (99%).
- let chan_4 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_init_features(&config_99_percent), &chan_1_open_channel_msg, 7, &config_99_percent, 0, &&logger, 42).unwrap();
+ let chan_4 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_99_percent), &channelmanager::provided_init_features(&config_99_percent), &chan_1_open_channel_msg, 7, &config_99_percent, 0, &&logger, 42).unwrap();
let chan_4_value_msat = chan_4.channel_value_satoshis * 1000;
assert_eq!(chan_4.holder_max_htlc_value_in_flight_msat, (chan_4_value_msat as f64 * 0.99) as u64);
// Test that `new_from_req` uses the lower bound of the configurable percentage values (1%)
// if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a value less than 1.
- let chan_7 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_init_features(&config_0_percent), &chan_1_open_channel_msg, 7, &config_0_percent, 0, &&logger, 42).unwrap();
+ let chan_7 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_0_percent), &channelmanager::provided_init_features(&config_0_percent), &chan_1_open_channel_msg, 7, &config_0_percent, 0, &&logger, 42).unwrap();
let chan_7_value_msat = chan_7.channel_value_satoshis * 1000;
assert_eq!(chan_7.holder_max_htlc_value_in_flight_msat, (chan_7_value_msat as f64 * 0.01) as u64);
// Test that `new_from_req` uses the upper bound of the configurable percentage values
// (100%) if `max_inbound_htlc_value_in_flight_percent_of_channel` is set to a larger value
// than 100.
- let chan_8 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_init_features(&config_101_percent), &chan_1_open_channel_msg, 7, &config_101_percent, 0, &&logger, 42).unwrap();
+ let chan_8 = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&config_101_percent), &channelmanager::provided_init_features(&config_101_percent), &chan_1_open_channel_msg, 7, &config_101_percent, 0, &&logger, 42).unwrap();
let chan_8_value_msat = chan_8.channel_value_satoshis * 1000;
assert_eq!(chan_8.holder_max_htlc_value_in_flight_msat, chan_8_value_msat);
}
inbound_node_config.channel_handshake_config.their_channel_reserve_proportional_millionths = (inbound_selected_channel_reserve_perc * 1_000_000.0) as u32;
if outbound_selected_channel_reserve_perc + inbound_selected_channel_reserve_perc < 1.0 {
- let chan_inbound_node = Channel::<EnforcingSigner>::new_from_req(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, 42).unwrap();
+ let chan_inbound_node = Channel::<EnforcingSigner>::new_from_req(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, 42).unwrap();
let expected_inbound_selected_chan_reserve = cmp::max(MIN_THEIR_CHAN_RESERVE_SATOSHIS, (chan.channel_value_satoshis as f64 * inbound_selected_channel_reserve_perc) as u64);
assert_eq!(chan_inbound_node.counterparty_selected_channel_reserve_satoshis.unwrap(), expected_outbound_selected_chan_reserve);
} else {
// Channel Negotiations failed
- let result = Channel::<EnforcingSigner>::new_from_req(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, 42);
+ let result = Channel::<EnforcingSigner>::new_from_req(&&fee_est, &&keys_provider, &&keys_provider, inbound_node_id, &channelmanager::provided_channel_type_features(&inbound_node_config), &channelmanager::provided_init_features(&outbound_node_config), &chan_open_channel_msg, 7, &inbound_node_config, 0, &&logger, 42);
assert!(result.is_err());
}
}
use bitcoin::hashes::hex::FromHex;
use bitcoin::hash_types::Txid;
use bitcoin::secp256k1::Message;
- use crate::chain::keysinterface::BaseSign;
+ use crate::chain::keysinterface::EcdsaChannelSigner;
use crate::ln::PaymentPreimage;
use crate::ln::channel::{HTLCOutputInCommitment ,TxCreationKeys};
use crate::ln::chan_utils::{ChannelPublicKeys, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
let mut signer = InMemorySigner::new(
&secp_ctx,
- SecretKey::from_slice(&hex::decode("4242424242424242424242424242424242424242424242424242424242424242").unwrap()[..]).unwrap(),
SecretKey::from_slice(&hex::decode("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749").unwrap()[..]).unwrap(),
SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap(),
open_channel_msg.channel_type = Some(channel_type_features);
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
let res = Channel::<EnforcingSigner>::new_from_req(&feeest, &&keys_provider, &&keys_provider,
- node_b_node_id, &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, 42);
+ node_b_node_id, &channelmanager::provided_channel_type_features(&config),
+ &channelmanager::provided_init_features(&config), &open_channel_msg, 7, &config, 0, &&logger, 42);
assert!(res.is_ok());
}
+
+ #[cfg(anchors)]
+ #[test]
+ fn test_supports_anchors_zero_htlc_tx_fee() {
+ // Tests that if both sides support and negotiate `anchors_zero_fee_htlc_tx`, it is the
+ // resulting `channel_type`.
+ let secp_ctx = Secp256k1::new();
+ let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
+ let network = Network::Testnet;
+ let keys_provider = test_utils::TestKeysInterface::new(&[42; 32], network);
+ let logger = test_utils::TestLogger::new();
+
+ let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
+ let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());
+
+ let mut config = UserConfig::default();
+ config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+
+ // It is not enough for just the initiator to signal `option_anchors_zero_fee_htlc_tx`, both
+ // need to signal it.
+ let channel_a = Channel::<EnforcingSigner>::new_outbound(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
+ &channelmanager::provided_init_features(&UserConfig::default()), 10000000, 100000, 42,
+ &config, 0, 42
+ ).unwrap();
+ assert!(!channel_a.channel_type.supports_anchors_zero_fee_htlc_tx());
+
+ let mut expected_channel_type = ChannelTypeFeatures::empty();
+ expected_channel_type.set_static_remote_key_required();
+ expected_channel_type.set_anchors_zero_fee_htlc_tx_required();
+
+ let channel_a = Channel::<EnforcingSigner>::new_outbound(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
+ &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42
+ ).unwrap();
+
+ let open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+ let channel_b = Channel::<EnforcingSigner>::new_from_req(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
+ &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config),
+ &open_channel_msg, 7, &config, 0, &&logger, 42
+ ).unwrap();
+
+ assert_eq!(channel_a.channel_type, expected_channel_type);
+ assert_eq!(channel_b.channel_type, expected_channel_type);
+ }
+
+ #[cfg(anchors)]
+ #[test]
+ fn test_rejects_implicit_simple_anchors() {
+ // Tests that if `option_anchors` is being negotiated implicitly through the intersection of
+ // each side's `InitFeatures`, it is rejected.
+ let secp_ctx = Secp256k1::new();
+ let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
+ let network = Network::Testnet;
+ let keys_provider = test_utils::TestKeysInterface::new(&[42; 32], network);
+ let logger = test_utils::TestLogger::new();
+
+ let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
+ let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());
+
+ let config = UserConfig::default();
+
+ // See feature bit assignments: https://github.com/lightning/bolts/blob/master/09-features.md
+ let static_remote_key_required: u64 = 1 << 12;
+ let simple_anchors_required: u64 = 1 << 20;
+ let raw_init_features = static_remote_key_required | simple_anchors_required;
+ let init_features_with_simple_anchors = InitFeatures::from_le_bytes(raw_init_features.to_le_bytes().to_vec());
+
+ let channel_a = Channel::<EnforcingSigner>::new_outbound(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
+ &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42
+ ).unwrap();
+
+ // Set `channel_type` to `None` to force the implicit feature negotiation.
+ let mut open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+ open_channel_msg.channel_type = None;
+
+ // Since A supports both `static_remote_key` and `option_anchors`, but B only accepts
+ // `static_remote_key`, it will fail the channel.
+ let channel_b = Channel::<EnforcingSigner>::new_from_req(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
+ &channelmanager::provided_channel_type_features(&config), &init_features_with_simple_anchors,
+ &open_channel_msg, 7, &config, 0, &&logger, 42
+ );
+ assert!(channel_b.is_err());
+ }
+
+ #[cfg(anchors)]
+ #[test]
+ fn test_rejects_simple_anchors_channel_type() {
+ // Tests that if `option_anchors` is being negotiated through the `channel_type` feature,
+ // it is rejected.
+ let secp_ctx = Secp256k1::new();
+ let fee_estimator = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000});
+ let network = Network::Testnet;
+ let keys_provider = test_utils::TestKeysInterface::new(&[42; 32], network);
+ let logger = test_utils::TestLogger::new();
+
+ let node_id_a = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[1; 32]).unwrap());
+ let node_id_b = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[2; 32]).unwrap());
+
+ let config = UserConfig::default();
+
+ // See feature bit assignments: https://github.com/lightning/bolts/blob/master/09-features.md
+ let static_remote_key_required: u64 = 1 << 12;
+ let simple_anchors_required: u64 = 1 << 20;
+ let simple_anchors_raw_features = static_remote_key_required | simple_anchors_required;
+ let simple_anchors_init = InitFeatures::from_le_bytes(simple_anchors_raw_features.to_le_bytes().to_vec());
+ let simple_anchors_channel_type = ChannelTypeFeatures::from_le_bytes(simple_anchors_raw_features.to_le_bytes().to_vec());
+ assert!(simple_anchors_init.requires_unknown_bits());
+ assert!(simple_anchors_channel_type.requires_unknown_bits());
+
+ // First, we'll try to open a channel between A and B where A requests a channel type for
+ // the original `option_anchors` feature (non zero fee htlc tx). This should be rejected by
+ // B as it's not supported by LDK.
+ let channel_a = Channel::<EnforcingSigner>::new_outbound(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_b,
+ &channelmanager::provided_init_features(&config), 10000000, 100000, 42, &config, 0, 42
+ ).unwrap();
+
+ let mut open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+ open_channel_msg.channel_type = Some(simple_anchors_channel_type.clone());
+
+ let res = Channel::<EnforcingSigner>::new_from_req(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
+ &channelmanager::provided_channel_type_features(&config), &simple_anchors_init,
+ &open_channel_msg, 7, &config, 0, &&logger, 42
+ );
+ assert!(res.is_err());
+
+ // Then, we'll try to open another channel where A requests a channel type for
+ // `anchors_zero_fee_htlc_tx`. B is malicious and tries to downgrade the channel type to the
+ // original `option_anchors` feature, which should be rejected by A as it's not supported by
+ // LDK.
+ let mut channel_a = Channel::<EnforcingSigner>::new_outbound(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_b, &simple_anchors_init,
+ 10000000, 100000, 42, &config, 0, 42
+ ).unwrap();
+
+ let open_channel_msg = channel_a.get_open_channel(genesis_block(network).header.block_hash());
+
+ let channel_b = Channel::<EnforcingSigner>::new_from_req(
+ &fee_estimator, &&keys_provider, &&keys_provider, node_id_a,
+ &channelmanager::provided_channel_type_features(&config), &channelmanager::provided_init_features(&config),
+ &open_channel_msg, 7, &config, 0, &&logger, 42
+ ).unwrap();
+
+ let mut accept_channel_msg = channel_b.get_accept_channel_message();
+ accept_channel_msg.channel_type = Some(simple_anchors_channel_type.clone());
+
+ let res = channel_a.accept_channel(
+ &accept_channel_msg, &config.channel_handshake_limits, &simple_anchors_init
+ );
+ assert!(res.is_err());
+ }
}
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hash_types::{BlockHash, Txid};
use bitcoin::secp256k1::{SecretKey,PublicKey};
use bitcoin::secp256k1::Secp256k1;
-use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::{LockTime, secp256k1, Sequence};
use crate::chain;
#[cfg(any(feature = "_test_utils", test))]
use crate::ln::features::InvoiceFeatures;
use crate::routing::gossip::NetworkGraph;
-use crate::routing::router::{DefaultRouter, InFlightHtlcs, PaymentParameters, Route, RouteHop, RoutePath, Router};
+use crate::routing::router::{DefaultRouter, InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
use crate::routing::scoring::ProbabilisticScorer;
use crate::ln::msgs;
use crate::ln::onion_utils;
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT};
#[cfg(test)]
use crate::ln::outbound_payment;
-use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment};
+use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, Retry};
use crate::ln::wire::Encode;
-use crate::chain::keysinterface::{EntropySource, KeysManager, NodeSigner, Recipient, Sign, SignerProvider};
+use crate::chain::keysinterface::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, ChannelSigner};
use crate::util::config::{UserConfig, ChannelConfig};
use crate::util::events::{Event, EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination};
use crate::util::events;
msg: &'static str,
}
+/// This enum is used to specify which error data to send to peers when failing back an HTLC
+/// using [`ChannelManager::fail_htlc_backwards_with_reason`].
+///
+/// For more info on failure codes, see <https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages>.
+#[derive(Clone, Copy)]
+pub enum FailureCode {
+ /// We had a temporary error processing the payment. Useful if no other error codes fit
+ /// and you want to indicate that the payer may want to retry.
+ TemporaryNodeFailure = 0x2000 | 2,
+ /// We have a required feature which was not in this onion. For example, you may require
+ /// some additional metadata that was not provided with this payment.
+ RequiredNodeFeatureMissing = 0x4000 | 0x2000 | 3,
+ /// You may wish to use this when a `payment_preimage` is unknown, or the CLTV expiry of
+ /// the HTLC is too close to the current block height for safe handling.
+ /// Using this failure code in [`ChannelManager::fail_htlc_backwards_with_reason`] is
+ /// equivalent to calling [`ChannelManager::fail_htlc_backwards`].
+ IncorrectOrUnknownPaymentDetails = 0x4000 | 15,
+}
+
type ShutdownResult = (Option<(OutPoint, ChannelMonitorUpdate)>, Vec<(HTLCSource, PaymentHash, PublicKey, [u8; 32])>);
/// Error type returned across the peer_state mutex boundary. When an Err is generated for a
/// Event::PendingHTLCsForwardable for the API guidelines indicating how long should be waited).
/// This provides some limited amount of privacy. Ideally this would range from somewhere like one
/// second to 30 seconds, but people expect lightning to be, you know, kinda fast, sadly.
-const MIN_HTLC_RELAY_HOLDING_CELL_MILLIS: u64 = 100;
+pub(super) const MIN_HTLC_RELAY_HOLDING_CELL_MILLIS: u64 = 100;
/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
}
/// State we hold per-peer.
-pub(super) struct PeerState<Signer: Sign> {
+pub(super) struct PeerState<Signer: ChannelSigner> {
/// `temporary_channel_id` or `channel_id` -> `channel`.
///
/// Holds all channels where the peer is the counterparty. Once a channel has been assigned a
// | |
// | |__`pending_intercepted_htlcs`
// |
-// |__`pending_inbound_payments`
+// |__`per_peer_state`
// | |
-// | |__`claimable_payments`
-// | |
-// | |__`pending_outbound_payments` // This field's struct contains a map of pending outbounds
+// | |__`pending_inbound_payments`
+// | |
+// | |__`claimable_payments`
// | |
-// | |__`per_peer_state`
+// | |__`pending_outbound_payments` // This field's struct contains a map of pending outbounds
// | |
// | |__`peer_state`
// | |
#[cfg(not(test))]
short_to_chan_info: FairRwLock<HashMap<u64, (PublicKey, [u8; 32])>>,
- our_network_key: SecretKey,
our_network_pubkey: PublicKey,
inbound_payment_key: inbound_payment::ExpandedKey,
pub(super) const CLTV_FAR_FAR_AWAY: u32 = 14 * 24 * 6;
/// Minimum CLTV difference between the current block height and received inbound payments.
-/// Invoices generated for payment to us must set their `min_final_cltv_expiry` field to at least
+/// Invoices generated for payment to us must set their `min_final_cltv_expiry_delta` field to at least
/// this value.
// Note that we fail if exactly HTLC_FAIL_BACK_BUFFER + 1 was used, so we need to add one for
// any payments to succeed. Further, we don't want payments to fail if a block was found while
// a payment was being routed, so we add an extra block to be safe.
-pub const MIN_FINAL_CLTV_EXPIRY: u32 = HTLC_FAIL_BACK_BUFFER + 3;
+pub const MIN_FINAL_CLTV_EXPIRY_DELTA: u16 = HTLC_FAIL_BACK_BUFFER as u16 + 3;
// Check that our CLTV_EXPIRY is at least CLTV_CLAIM_BUFFER + ANTI_REORG_DELAY + LATENCY_GRACE_PERIOD_BLOCKS,
// ie that if the next-hop peer fails the HTLC within
match $internal {
Ok(msg) => Ok(msg),
Err(MsgHandleErrInternal { err, chan_id, shutdown_finish }) => {
- #[cfg(debug_assertions)]
+ #[cfg(any(feature = "_test_utils", test))]
{
// In testing, ensure there are no deadlocks where the lock is already held upon
// entering the macro.
- assert!($self.pending_events.try_lock().is_ok());
- assert!($self.per_peer_state.try_write().is_ok());
+ debug_assert!($self.pending_events.try_lock().is_ok());
+ debug_assert!($self.per_peer_state.try_write().is_ok());
}
let mut msg_events = Vec::with_capacity(2);
let mut peer_state = peer_state_mutex.lock().unwrap();
peer_state.pending_msg_events.append(&mut msg_events);
}
- #[cfg(debug_assertions)]
+ #[cfg(any(feature = "_test_utils", test))]
{
if let None = per_peer_state.get(&$counterparty_node_id) {
// This shouldn't occour in tests unless an unkown counterparty_node_id
=> {
assert_eq!(*data, expected_error_str);
if let Some((err_channel_id, _user_channel_id)) = chan_id {
- assert_eq!(*channel_id, err_channel_id);
+ debug_assert_eq!(*channel_id, err_channel_id);
}
}
- _ => panic!("Unexpected event"),
+ _ => debug_assert!(false, "Unexpected event"),
}
}
}
id_to_peer: Mutex::new(HashMap::new()),
short_to_chan_info: FairRwLock::new(HashMap::new()),
- our_network_key: node_signer.get_node_secret(Recipient::Node).unwrap(),
- our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &node_signer.get_node_secret(Recipient::Node).unwrap()),
+ our_network_pubkey: node_signer.get_node_id(Recipient::Node).unwrap(),
secp_ctx,
inbound_payment_key: expanded_inbound_key,
// Update the monitor with the shutdown script if necessary.
if let Some(monitor_update) = monitor_update {
- let update_res = self.chain_monitor.update_channel(chan_entry.get().get_funding_txo().unwrap(), monitor_update);
+ let update_res = self.chain_monitor.update_channel(chan_entry.get().get_funding_txo().unwrap(), &monitor_update);
let (result, is_permanent) =
handle_monitor_update_res!(self, update_res, chan_entry.get_mut(), RAACommitmentOrder::CommitmentFirst, chan_entry.key(), NO_UPDATE);
if is_permanent {
// force-closing. The monitor update on the required in-memory copy should broadcast
// the latest local state, which is the best we can do anyway. Thus, it is safe to
// ignore the result here.
- let _ = self.chain_monitor.update_channel(funding_txo, monitor_update);
+ let _ = self.chain_monitor.update_channel(funding_txo, &monitor_update);
}
}
// final_expiry_too_soon
// We have to have some headroom to broadcast on chain if we have the preimage, so make sure
// we have at least HTLC_FAIL_BACK_BUFFER blocks to go.
+ //
// Also, ensure that, in the case of an unknown preimage for the received payment hash, our
// payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
// channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
return_malformed_err!("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6);
}
- let shared_secret = SharedSecret::new(&msg.onion_routing_packet.public_key.unwrap(), &self.our_network_key).secret_bytes();
+ let shared_secret = self.node_signer.ecdh(
+ Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), None
+ ).unwrap().secret_bytes();
if msg.onion_routing_packet.version != 0 {
//TODO: Spec doesn't indicate if we should only hash hop_data here (and in other
// short_channel_id is non-0 in any ::Forward.
if let &PendingHTLCRouting::Forward { ref short_channel_id, .. } = routing {
if let Some((err, mut code, chan_update)) = loop {
- let id_option = self.short_to_chan_info.read().unwrap().get(&short_channel_id).cloned();
+ let id_option = self.short_to_chan_info.read().unwrap().get(short_channel_id).cloned();
let forwarding_chan_info_opt = match id_option {
None => { // unknown_next_peer
// Note that this is likely a timing oracle for detecting whether an scid is a
}
fn get_channel_update_for_onion(&self, short_channel_id: u64, chan: &Channel<<SP::Target as SignerProvider>::Signer>) -> Result<msgs::ChannelUpdate, LightningError> {
log_trace!(self.logger, "Generating channel update for channel {}", log_bytes!(chan.channel_id()));
- let were_node_one = PublicKey::from_secret_key(&self.secp_ctx, &self.our_network_key).serialize()[..] < chan.get_counterparty_node_id().serialize()[..];
+ let were_node_one = self.our_network_pubkey.serialize()[..] < chan.get_counterparty_node_id().serialize()[..];
let unsigned = msgs::UnsignedChannelUpdate {
chain_hash: self.genesis_hash,
fee_proportional_millionths: chan.get_fee_proportional_millionths(),
excess_data: Vec::new(),
};
-
- let msg_hash = Sha256dHash::hash(&unsigned.encode()[..]);
- let sig = self.secp_ctx.sign_ecdsa(&hash_to_message!(&msg_hash[..]), &self.our_network_key);
+ // Panic on failure to signal LDK should be restarted to retry signing the `ChannelUpdate`.
+ // If we returned an error and the `node_signer` cannot provide a signature for whatever
+ // reason`, we wouldn't be able to receive inbound payments through the corresponding
+ // channel.
+ let sig = self.node_signer.sign_gossip_message(msgs::UnsignedGossipMessage::ChannelUpdate(&unsigned)).unwrap();
Ok(msgs::ChannelUpdate {
signature: sig,
chan)
} {
Some((update_add, commitment_signed, monitor_update)) => {
- let update_err = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), monitor_update);
+ let update_err = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), &monitor_update);
let chan_id = chan.get().channel_id();
match (update_err,
handle_monitor_update_res!(self, update_err, chan,
self.send_payment_along_path(path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv))
}
+ /// Similar to [`ChannelManager::send_payment`], but will automatically find a route based on
+ /// `route_params` and retry failed payment paths based on `retry_strategy`.
+ pub fn send_payment_with_retry(&self, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<(), PaymentSendFailure> {
+ let best_block_height = self.best_block.read().unwrap().height();
+ self.pending_outbound_payments
+ .send_payment(payment_hash, payment_secret, payment_id, retry_strategy, route_params,
+ &self.router, self.list_usable_channels(), self.compute_inflight_htlcs(),
+ &self.entropy_source, &self.node_signer, best_block_height,
+ |path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv|
+ self.send_payment_along_path(path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+ }
+
#[cfg(test)]
fn test_send_payment_internal(&self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>, onion_session_privs: Vec<[u8; 32]>) -> Result<(), PaymentSendFailure> {
let best_block_height = self.best_block.read().unwrap().height();
#[cfg(test)]
pub(crate) fn test_add_new_pending_payment(&self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId, route: &Route) -> Result<Vec<[u8; 32]>, PaymentSendFailure> {
let best_block_height = self.best_block.read().unwrap().height();
- self.pending_outbound_payments.test_add_new_pending_payment(payment_hash, payment_secret, payment_id, route, &self.entropy_source, best_block_height)
+ self.pending_outbound_payments.test_add_new_pending_payment(payment_hash, payment_secret, payment_id, route, Retry::Attempts(0), &self.entropy_source, best_block_height)
}
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
if let None = peer_state_mutex_opt {
- return Err(APIError::APIMisuseError { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })
+ return Err(APIError::ChannelUnavailable { err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id) })
}
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
}
}
if let PendingHTLCRouting::Forward { onion_packet, .. } = routing {
- let phantom_secret_res = self.node_signer.get_node_secret(Recipient::PhantomNode);
- if phantom_secret_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.genesis_hash) {
- let phantom_shared_secret = SharedSecret::new(&onion_packet.public_key.unwrap(), &phantom_secret_res.unwrap()).secret_bytes();
+ let phantom_pubkey_res = self.node_signer.get_node_id(Recipient::PhantomNode);
+ if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.genesis_hash) {
+ let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
let next_hop = match onion_utils::decode_next_payment_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
match claimable_htlc.onion_payload {
OnionPayload::Invoice { .. } => {
let payment_data = payment_data.unwrap();
- let payment_preimage = match inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
- Ok(payment_preimage) => payment_preimage,
+ let (payment_preimage, min_final_cltv_expiry_delta) = match inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
+ Ok(result) => result,
Err(()) => {
+ log_trace!(self.logger, "Failing new HTLC with payment_hash {} as payment verification failed", log_bytes!(payment_hash.0));
fail_htlc!(claimable_htlc, payment_hash);
continue
}
};
+ if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
+ let expected_min_expiry_height = (self.current_best_block().height() + min_final_cltv_expiry_delta as u32) as u64;
+ if (cltv_expiry as u64) < expected_min_expiry_height {
+ log_trace!(self.logger, "Failing new HTLC with payment_hash {} as its CLTV expiry was too soon (had {}, earliest expected {})",
+ log_bytes!(payment_hash.0), cltv_expiry, expected_min_expiry_height);
+ fail_htlc!(claimable_htlc, payment_hash);
+ continue;
+ }
+ }
check_total_value!(payment_data, payment_preimage);
},
OnionPayload::Spontaneous(preimage) => {
}
}
+ let best_block_height = self.best_block.read().unwrap().height();
+ self.pending_outbound_payments.check_retry_payments(&self.router, || self.list_usable_channels(),
+ || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, best_block_height, &self.logger,
+ |path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv|
+ self.send_payment_along_path(path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv));
+
for (htlc_source, payment_hash, failure_reason, destination) in failed_forwards.drain(..) {
self.fail_htlc_backwards_internal(&htlc_source, &payment_hash, &failure_reason, destination);
}
BackgroundEvent::ClosingMonitorUpdate((funding_txo, update)) => {
// The channel has already been closed, so no use bothering to care about the
// monitor updating completing.
- let _ = self.chain_monitor.update_channel(funding_txo, update);
+ let _ = self.chain_monitor.update_channel(funding_txo, &update);
},
}
}
/// [`events::Event::PaymentClaimed`] events even for payments you intend to fail, especially on
/// startup during which time claims that were in-progress at shutdown may be replayed.
pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) {
+ self.fail_htlc_backwards_with_reason(payment_hash, &FailureCode::IncorrectOrUnknownPaymentDetails);
+ }
+
+ /// This is a variant of [`ChannelManager::fail_htlc_backwards`] that allows you to specify the
+ /// reason for the failure.
+ ///
+ /// See [`FailureCode`] for valid failure codes.
+ pub fn fail_htlc_backwards_with_reason(&self, payment_hash: &PaymentHash, failure_code: &FailureCode) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
let removed_source = self.claimable_payments.lock().unwrap().claimable_htlcs.remove(payment_hash);
if let Some((_, mut sources)) = removed_source {
for htlc in sources.drain(..) {
- let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
- htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
+ let reason = self.get_htlc_fail_reason_from_failure_code(failure_code, &htlc);
let source = HTLCSource::PreviousHopData(htlc.prev_hop);
- let reason = HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data);
let receiver = HTLCDestination::FailedPayment { payment_hash: *payment_hash };
self.fail_htlc_backwards_internal(&source, &payment_hash, &reason, receiver);
}
}
}
+ /// Gets error data to form an [`HTLCFailReason`] given a [`FailureCode`] and [`ClaimableHTLC`].
+ fn get_htlc_fail_reason_from_failure_code(&self, failure_code: &FailureCode, htlc: &ClaimableHTLC) -> HTLCFailReason {
+ match failure_code {
+ FailureCode::TemporaryNodeFailure => HTLCFailReason::from_failure_code(*failure_code as u16),
+ FailureCode::RequiredNodeFeatureMissing => HTLCFailReason::from_failure_code(*failure_code as u16),
+ FailureCode::IncorrectOrUnknownPaymentDetails => {
+ let mut htlc_msat_height_data = htlc.value.to_be_bytes().to_vec();
+ htlc_msat_height_data.extend_from_slice(&self.best_block.read().unwrap().height().to_be_bytes());
+ HTLCFailReason::reason(*failure_code as u16, htlc_msat_height_data)
+ }
+ }
+ }
+
/// Gets an HTLC onion failure code and error data for an `UPDATE` error, given the error code
/// that we want to return and a channel.
///
/// Fails an HTLC backwards to the sender of it to us.
/// Note that we do not assume that channels corresponding to failed HTLCs are still available.
fn fail_htlc_backwards_internal(&self, source: &HTLCSource, payment_hash: &PaymentHash, onion_error: &HTLCFailReason, destination: HTLCDestination) {
- #[cfg(debug_assertions)]
+ #[cfg(any(feature = "_test_utils", test))]
{
// Ensure that no peer state channel storage lock is not held when calling this
// function.
// This ensures that future code doesn't introduce a lock_order requirement for
- // `forward_htlcs` to be locked after the `per_peer_state` locks, which calling this
- // function with the `per_peer_state` aquired would.
- assert!(self.per_peer_state.try_write().is_ok());
+ // `forward_htlcs` to be locked after the `per_peer_state` peer locks, which calling
+ // this function with any `per_peer_state` peer lock aquired would.
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ for (_, peer) in per_peer_state.iter() {
+ debug_assert!(peer.try_lock().is_ok());
+ }
}
//TODO: There is a timing attack here where if a node fails an HTLC back to us they can
match chan.get_mut().get_update_fulfill_htlc_and_commit(prev_hop.htlc_id, payment_preimage, &self.logger) {
Ok(msgs_monitor_option) => {
if let UpdateFulfillCommitFetch::NewClaim { msgs, htlc_value_msat, monitor_update } = msgs_monitor_option {
- match self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), monitor_update) {
+ match self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), &monitor_update) {
ChannelMonitorUpdateStatus::Completed => {},
e => {
log_given_level!(self.logger, if e == ChannelMonitorUpdateStatus::PermanentFailure { Level::Error } else { Level::Debug },
}
},
Err((e, monitor_update)) => {
- match self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), monitor_update) {
+ match self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), &monitor_update) {
ChannelMonitorUpdateStatus::Completed => {},
e => {
// TODO: This needs to be handled somehow - if we receive a monitor update
};
// We update the ChannelMonitor on the backward link, after
// receiving an `update_fulfill_htlc` from the forward link.
- let update_res = self.chain_monitor.update_channel(prev_hop.outpoint, preimage_update);
+ let update_res = self.chain_monitor.update_channel(prev_hop.outpoint, &preimage_update);
if update_res != ChannelMonitorUpdateStatus::Completed {
// TODO: This needs to be handled somehow - if we receive a monitor update
// with a preimage we *must* somehow manage to propagate it to the upstream
return;
}
- let updates = channel.get_mut().monitor_updating_restored(&self.logger, self.get_our_node_id(), self.genesis_hash, &self.default_configuration, self.best_block.read().unwrap().height());
+ let updates = channel.get_mut().monitor_updating_restored(&self.logger, &self.node_signer, self.genesis_hash, &self.default_configuration, self.best_block.read().unwrap().height());
let channel_update = if updates.channel_ready.is_some() && channel.get().is_usable() {
// We only send a channel_update in the case where we are just now sending a
// channel_ready and the channel is in a usable state. We may re-send a
Ok(())
}
- fn internal_open_channel(&self, counterparty_node_id: &PublicKey, their_features: InitFeatures, msg: &msgs::OpenChannel) -> Result<(), MsgHandleErrInternal> {
+ fn internal_open_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannel) -> Result<(), MsgHandleErrInternal> {
if msg.chain_hash != self.genesis_hash {
return Err(MsgHandleErrInternal::send_err_msg_no_close("Unknown genesis block hash".to_owned(), msg.temporary_channel_id.clone()));
}
let user_channel_id = u128::from_be_bytes(random_bytes);
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
+ let per_peer_state = self.per_peer_state.read().unwrap();
+ let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
+ if let None = peer_state_mutex_opt {
+ return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id.clone()))
+ }
+ let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
+ let peer_state = &mut *peer_state_lock;
let mut channel = match Channel::new_from_req(&self.fee_estimator, &self.entropy_source, &self.signer_provider,
- counterparty_node_id.clone(), &their_features, msg, user_channel_id, &self.default_configuration,
+ counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, &self.default_configuration,
self.best_block.read().unwrap().height(), &self.logger, outbound_scid_alias)
{
Err(e) => {
},
Ok(res) => res
};
- let per_peer_state = self.per_peer_state.read().unwrap();
- let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
- if let None = peer_state_mutex_opt {
- return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), msg.temporary_channel_id.clone()))
- }
- let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
- let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(channel.channel_id()) {
hash_map::Entry::Occupied(_) => {
self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias);
Ok(())
}
- fn internal_accept_channel(&self, counterparty_node_id: &PublicKey, their_features: InitFeatures, msg: &msgs::AcceptChannel) -> Result<(), MsgHandleErrInternal> {
+ fn internal_accept_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannel) -> Result<(), MsgHandleErrInternal> {
let (value, output_script, user_id) = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex_opt = per_peer_state.get(counterparty_node_id);
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.temporary_channel_id) {
hash_map::Entry::Occupied(mut chan) => {
- try_chan_entry!(self, chan.get_mut().accept_channel(&msg, &self.default_configuration.channel_handshake_limits, &their_features), chan);
+ try_chan_entry!(self, chan.get_mut().accept_channel(&msg, &self.default_configuration.channel_handshake_limits, &peer_state.latest_features), chan);
(chan.get().get_value_satoshis(), chan.get().get_funding_redeemscript().to_v0_p2wsh(), chan.get().get_user_id())
},
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.temporary_channel_id))
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
- let announcement_sigs_opt = try_chan_entry!(self, chan.get_mut().channel_ready(&msg, self.get_our_node_id(),
+ let announcement_sigs_opt = try_chan_entry!(self, chan.get_mut().channel_ready(&msg, &self.node_signer,
self.genesis_hash.clone(), &self.default_configuration, &self.best_block.read().unwrap(), &self.logger), chan);
if let Some(announcement_sigs) = announcement_sigs_opt {
log_trace!(self.logger, "Sending announcement_signatures for channel {}", log_bytes!(chan.get().channel_id()));
}
}
- fn internal_shutdown(&self, counterparty_node_id: &PublicKey, their_features: &InitFeatures, msg: &msgs::Shutdown) -> Result<(), MsgHandleErrInternal> {
+ fn internal_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) -> Result<(), MsgHandleErrInternal> {
let mut dropped_htlcs: Vec<(HTLCSource, PaymentHash)>;
let result: Result<(), _> = loop {
let per_peer_state = self.per_peer_state.read().unwrap();
if chan_entry.get().sent_shutdown() { " after we initiated shutdown" } else { "" });
}
- let (shutdown, monitor_update, htlcs) = try_chan_entry!(self, chan_entry.get_mut().shutdown(&self.signer_provider, &their_features, &msg), chan_entry);
+ let (shutdown, monitor_update, htlcs) = try_chan_entry!(self, chan_entry.get_mut().shutdown(&self.signer_provider, &peer_state.latest_features, &msg), chan_entry);
dropped_htlcs = htlcs;
// Update the monitor with the shutdown script if necessary.
if let Some(monitor_update) = monitor_update {
- let update_res = self.chain_monitor.update_channel(chan_entry.get().get_funding_txo().unwrap(), monitor_update);
+ let update_res = self.chain_monitor.update_channel(chan_entry.get().get_funding_txo().unwrap(), &monitor_update);
let (result, is_permanent) =
handle_monitor_update_res!(self, update_res, chan_entry.get_mut(), RAACommitmentOrder::CommitmentFirst, chan_entry.key(), NO_UPDATE);
if is_permanent {
Err((None, e)) => try_chan_entry!(self, Err(e), chan),
Err((Some(update), e)) => {
assert!(chan.get().is_awaiting_monitor_update());
- let _ = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), update);
+ let _ = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), &update);
try_chan_entry!(self, Err(e), chan);
unreachable!();
},
Ok(res) => res
};
- let update_res = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), monitor_update);
+ let update_res = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), &monitor_update);
if let Err(e) = handle_monitor_update_res!(self, update_res, chan, RAACommitmentOrder::RevokeAndACKFirst, true, commitment_signed.is_some()) {
return Err(e);
}
let raa_updates = break_chan_entry!(self,
chan.get_mut().revoke_and_ack(&msg, &self.logger), chan);
htlcs_to_fail = raa_updates.holding_cell_failed_htlcs;
- let update_res = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), raa_updates.monitor_update);
+ let update_res = self.chain_monitor.update_channel(chan.get().get_funding_txo().unwrap(), &raa_updates.monitor_update);
if was_paused_for_mon_update {
assert!(update_res != ChannelMonitorUpdateStatus::Completed);
assert!(raa_updates.commitment_update.is_none());
peer_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
msg: try_chan_entry!(self, chan.get_mut().announcement_signatures(
- self.get_our_node_id(), self.genesis_hash.clone(),
- self.best_block.read().unwrap().height(), msg, &self.default_configuration
+ &self.node_signer, self.genesis_hash.clone(), self.best_block.read().unwrap().height(),
+ msg, &self.default_configuration
), chan),
// Note that announcement_signatures fails if the channel cannot be announced,
// so get_channel_update_for_broadcast will never fail by the time we get here.
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
- msg, &self.logger, self.our_network_pubkey.clone(), self.genesis_hash,
+ msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
));
}
if let Some((commitment_update, monitor_update)) = commitment_opt {
- match self.chain_monitor.update_channel(chan.get_funding_txo().unwrap(), monitor_update) {
+ match self.chain_monitor.update_channel(chan.get_funding_txo().unwrap(), &monitor_update) {
ChannelMonitorUpdateStatus::Completed => {
pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs {
node_id: chan.get_counterparty_node_id(),
///
/// Errors if `min_value_msat` is greater than total bitcoin supply.
///
+ /// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
+ /// on versions of LDK prior to 0.0.114.
+ ///
/// [`claim_funds`]: Self::claim_funds
/// [`PaymentClaimable`]: events::Event::PaymentClaimable
/// [`PaymentClaimable::payment_preimage`]: events::Event::PaymentClaimable::payment_preimage
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
- pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<(PaymentHash, PaymentSecret), ()> {
- inbound_payment::create(&self.inbound_payment_key, min_value_msat, invoice_expiry_delta_secs, &self.entropy_source, self.highest_seen_timestamp.load(Ordering::Acquire) as u64)
+ pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32,
+ min_final_cltv_expiry_delta: Option<u16>) -> Result<(PaymentHash, PaymentSecret), ()> {
+ inbound_payment::create(&self.inbound_payment_key, min_value_msat, invoice_expiry_delta_secs,
+ &self.entropy_source, self.highest_seen_timestamp.load(Ordering::Acquire) as u64,
+ min_final_cltv_expiry_delta)
}
/// Legacy version of [`create_inbound_payment`]. Use this method if you wish to share
/// If you need exact expiry semantics, you should enforce them upon receipt of
/// [`PaymentClaimable`].
///
- /// Note that invoices generated for inbound payments should have their `min_final_cltv_expiry`
- /// set to at least [`MIN_FINAL_CLTV_EXPIRY`].
+ /// Note that invoices generated for inbound payments should have their `min_final_cltv_expiry_delta`
+ /// set to at least [`MIN_FINAL_CLTV_EXPIRY_DELTA`].
///
/// Note that a malicious eavesdropper can intuit whether an inbound payment was created by
/// `create_inbound_payment` or `create_inbound_payment_for_hash` based on runtime.
///
/// Errors if `min_value_msat` is greater than total bitcoin supply.
///
+ /// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
+ /// on versions of LDK prior to 0.0.114.
+ ///
/// [`create_inbound_payment`]: Self::create_inbound_payment
/// [`PaymentClaimable`]: events::Event::PaymentClaimable
- pub fn create_inbound_payment_for_hash(&self, payment_hash: PaymentHash, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<PaymentSecret, ()> {
- inbound_payment::create_from_hash(&self.inbound_payment_key, min_value_msat, payment_hash, invoice_expiry_delta_secs, self.highest_seen_timestamp.load(Ordering::Acquire) as u64)
+ pub fn create_inbound_payment_for_hash(&self, payment_hash: PaymentHash, min_value_msat: Option<u64>,
+ invoice_expiry_delta_secs: u32, min_final_cltv_expiry: Option<u16>) -> Result<PaymentSecret, ()> {
+ inbound_payment::create_from_hash(&self.inbound_payment_key, min_value_msat, payment_hash,
+ invoice_expiry_delta_secs, self.highest_seen_timestamp.load(Ordering::Acquire) as u64,
+ min_final_cltv_expiry)
}
/// Legacy version of [`create_inbound_payment_for_hash`]. Use this method if you wish to share
*best_block = BestBlock::new(header.prev_blockhash, new_height)
}
- self.do_chain_event(Some(new_height), |channel| channel.best_block_updated(new_height, header.time, self.genesis_hash.clone(), self.get_our_node_id(), self.default_configuration.clone(), &self.logger));
+ self.do_chain_event(Some(new_height), |channel| channel.best_block_updated(new_height, header.time, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger));
}
}
log_trace!(self.logger, "{} transactions included in block {} at height {} provided", txdata.len(), block_hash, height);
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
- self.do_chain_event(Some(height), |channel| channel.transactions_confirmed(&block_hash, height, txdata, self.genesis_hash.clone(), self.get_our_node_id(), &self.default_configuration, &self.logger)
+ self.do_chain_event(Some(height), |channel| channel.transactions_confirmed(&block_hash, height, txdata, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger)
.map(|(a, b)| (a, Vec::new(), b)));
let last_best_block_height = self.best_block.read().unwrap().height();
if height < last_best_block_height {
let timestamp = self.highest_seen_timestamp.load(Ordering::Acquire);
- self.do_chain_event(Some(last_best_block_height), |channel| channel.best_block_updated(last_best_block_height, timestamp as u32, self.genesis_hash.clone(), self.get_our_node_id(), self.default_configuration.clone(), &self.logger));
+ self.do_chain_event(Some(last_best_block_height), |channel| channel.best_block_updated(last_best_block_height, timestamp as u32, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger));
}
}
*self.best_block.write().unwrap() = BestBlock::new(block_hash, height);
- self.do_chain_event(Some(height), |channel| channel.best_block_updated(height, header.time, self.genesis_hash.clone(), self.get_our_node_id(), self.default_configuration.clone(), &self.logger));
+ self.do_chain_event(Some(height), |channel| channel.best_block_updated(height, header.time, self.genesis_hash.clone(), &self.node_signer, &self.default_configuration, &self.logger));
macro_rules! max_time {
($timestamp: expr) => {
msg: announcement_sigs,
});
if let Some(height) = height_opt {
- if let Some(announcement) = channel.get_signed_channel_announcement(self.get_our_node_id(), self.genesis_hash, height, &self.default_configuration) {
+ if let Some(announcement) = channel.get_signed_channel_announcement(&self.node_signer, self.genesis_hash, height, &self.default_configuration) {
pending_msg_events.push(events::MessageSendEvent::BroadcastChannelAnnouncement {
msg: announcement,
// Note that announcement_signatures fails if the channel cannot be announced,
R::Target: Router,
L::Target: Logger,
{
- fn handle_open_channel(&self, counterparty_node_id: &PublicKey, their_features: InitFeatures, msg: &msgs::OpenChannel) {
+ fn handle_open_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::OpenChannel) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
- let _ = handle_error!(self, self.internal_open_channel(counterparty_node_id, their_features, msg), *counterparty_node_id);
+ let _ = handle_error!(self, self.internal_open_channel(counterparty_node_id, msg), *counterparty_node_id);
}
- fn handle_accept_channel(&self, counterparty_node_id: &PublicKey, their_features: InitFeatures, msg: &msgs::AcceptChannel) {
+ fn handle_accept_channel(&self, counterparty_node_id: &PublicKey, msg: &msgs::AcceptChannel) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
- let _ = handle_error!(self, self.internal_accept_channel(counterparty_node_id, their_features, msg), *counterparty_node_id);
+ let _ = handle_error!(self, self.internal_accept_channel(counterparty_node_id, msg), *counterparty_node_id);
}
fn handle_funding_created(&self, counterparty_node_id: &PublicKey, msg: &msgs::FundingCreated) {
let _ = handle_error!(self, self.internal_channel_ready(counterparty_node_id, msg), *counterparty_node_id);
}
- fn handle_shutdown(&self, counterparty_node_id: &PublicKey, their_features: &InitFeatures, msg: &msgs::Shutdown) {
+ fn handle_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
- let _ = handle_error!(self, self.internal_shutdown(counterparty_node_id, their_features, msg), *counterparty_node_id);
+ let _ = handle_error!(self, self.internal_shutdown(counterparty_node_id, msg), *counterparty_node_id);
}
fn handle_closing_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::ClosingSigned) {
}
} else { true };
if retain && chan.get_counterparty_node_id() != *counterparty_node_id {
- if let Some(msg) = chan.get_signed_channel_announcement(self.get_our_node_id(), self.genesis_hash.clone(), self.best_block.read().unwrap().height(), &self.default_configuration) {
+ if let Some(msg) = chan.get_signed_channel_announcement(&self.node_signer, self.genesis_hash.clone(), self.best_block.read().unwrap().height(), &self.default_configuration) {
if let Ok(update_msg) = self.get_channel_update_for_broadcast(chan) {
pending_msg_events.push(events::MessageSendEvent::SendChannelAnnouncement {
node_id: *counterparty_node_id,
/// Fetches the set of [`ChannelTypeFeatures`] flags which are provided by or required by
/// [`ChannelManager`].
pub(crate) fn provided_channel_type_features(config: &UserConfig) -> ChannelTypeFeatures {
- ChannelTypeFeatures::from_counterparty_init(&provided_init_features(config))
+ ChannelTypeFeatures::from_init(&provided_init_features(config))
}
/// Fetches the set of [`InitFeatures`] flags which are provided by or required by
features.set_channel_type_optional();
features.set_scid_privacy_optional();
features.set_zero_conf_optional();
+ #[cfg(anchors)]
+ { // Attributes are not allowed on if expressions on our current MSRV of 1.41.
+ if _config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx {
+ features.set_anchors_zero_fee_htlc_tx_optional();
+ }
+ }
features
}
}
}
+ let per_peer_state = self.per_peer_state.write().unwrap();
+
let pending_inbound_payments = self.pending_inbound_payments.lock().unwrap();
let claimable_payments = self.claimable_payments.lock().unwrap();
let pending_outbound_payments = self.pending_outbound_payments.pending_outbound_payments.lock().unwrap();
htlc_purposes.push(purpose);
}
- let per_peer_state = self.per_peer_state.write().unwrap();
(per_peer_state.len() as u64).write(writer)?;
for (peer_pubkey, peer_state_mutex) in per_peer_state.iter() {
peer_pubkey.write(writer)?;
let mut short_to_chan_info = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
let mut channel_closures = Vec::new();
for _ in 0..channel_count {
- let mut channel: Channel<<SP::Target as SignerProvider>::Signer> = Channel::read(reader, (&args.entropy_source, &args.signer_provider, best_block_height))?;
+ let mut channel: Channel<<SP::Target as SignerProvider>::Signer> = Channel::read(reader, (
+ &args.entropy_source, &args.signer_provider, best_block_height, &provided_channel_type_features(&args.default_config)
+ ))?;
let funding_txo = channel.get_funding_txo().ok_or(DecodeError::InvalidValue)?;
funding_txo_set.insert(funding_txo.clone());
if let Some(ref mut monitor) = args.channel_monitors.get_mut(&funding_txo) {
}
}
- for (ref funding_txo, ref mut monitor) in args.channel_monitors.iter_mut() {
+ for (funding_txo, monitor) in args.channel_monitors.iter_mut() {
if !funding_txo_set.contains(funding_txo) {
log_info!(args.logger, "Broadcasting latest holder commitment transaction for closed channel {}", log_bytes!(funding_txo.to_channel_id()));
monitor.broadcast_latest_holder_commitment_txn(&args.tx_broadcaster, &args.logger);
hash_map::Entry::Vacant(entry) => {
let path_fee = path.get_path_fees();
entry.insert(PendingOutboundPayment::Retryable {
+ retry_strategy: Retry::Attempts(0),
+ attempts: PaymentAttempts::new(),
+ route_params: None,
session_privs: [session_priv_bytes].iter().map(|a| *a).collect(),
payment_hash: htlc.payment_hash,
payment_secret,
payment_preimage: match pending_inbound_payments.get(&payment_hash) {
Some(inbound_payment) => inbound_payment.payment_preimage,
None => match inbound_payment::verify(payment_hash, &hop_data, 0, &expanded_inbound_key, &args.logger) {
- Ok(payment_preimage) => payment_preimage,
+ Ok((payment_preimage, _)) => payment_preimage,
Err(()) => {
log_error!(args.logger, "Failed to read claimable payment data for HTLC with payment hash {} - was not a pending inbound payment and didn't match our payment key", log_bytes!(payment_hash.0));
return Err(DecodeError::InvalidValue);
pending_events_read.append(&mut channel_closures);
}
- let our_network_key = match args.node_signer.get_node_secret(Recipient::Node) {
+ let our_network_pubkey = match args.node_signer.get_node_id(Recipient::Node) {
Ok(key) => key,
Err(()) => return Err(DecodeError::InvalidValue)
};
- let our_network_pubkey = PublicKey::from_secret_key(&secp_ctx, &our_network_key);
if let Some(network_pubkey) = received_network_pubkey {
if network_pubkey != our_network_pubkey {
log_error!(args.logger, "Key that was generated does not match the existing key.");
probing_cookie_secret: probing_cookie_secret.unwrap(),
- our_network_key,
our_network_pubkey,
secp_ctx,
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1_000_000, 500_000_000, 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(), nodes[0].node.init_features(), &open_channel);
+ 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(), nodes[1].node.init_features(), &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, _funding_output) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
let channel_id = &tx.txid().into_inner();
update_nodes_with_chan_announce(&nodes, 0, 1, &announcement, &nodes_0_update, &nodes_1_update);
nodes[0].node.close_channel(channel_id, &nodes[1].node.get_our_node_id()).unwrap();
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id()));
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id()));
let nodes_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &nodes_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes_1_shutdown);
let closing_signed_node_0 = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &closing_signed_node_0);
// creating dummy ones.
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None).unwrap();
let open_channel_msg = 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(), nodes[0].node.init_features(), &open_channel_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
let accept_channel_msg = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
// Dummy values
// Test the API functions and message handlers.
check_not_connected_to_peer_error(nodes[0].node.create_channel(unkown_public_key, 1_000_000, 500_000_000, 42, None), unkown_public_key);
- nodes[1].node.handle_open_channel(&unkown_public_key, nodes[0].node.init_features(), &open_channel_msg);
+ nodes[1].node.handle_open_channel(&unkown_public_key, &open_channel_msg);
- nodes[0].node.handle_accept_channel(&unkown_public_key, nodes[1].node.init_features(), &accept_channel_msg);
+ nodes[0].node.handle_accept_channel(&unkown_public_key, &accept_channel_msg);
check_unkown_peer_error(nodes[0].node.accept_inbound_channel(&open_channel_msg.temporary_channel_id, &unkown_public_key, 42), unkown_public_key);
check_unkown_peer_error(nodes[0].node.update_channel_config(&unkown_public_key, &[channel_id], &ChannelConfig::default()), unkown_public_key);
- nodes[0].node.handle_shutdown(&unkown_public_key, &nodes[1].node.init_features(), &shutdown_msg);
+ nodes[0].node.handle_shutdown(&unkown_public_key, &shutdown_msg);
nodes[1].node.handle_closing_signed(&unkown_public_key, &closing_signed_msg);
nodes[1].node.handle_update_fee(&unkown_public_key, &update_fee_msg);
}
+
+ #[cfg(anchors)]
+ #[test]
+ fn test_anchors_zero_fee_htlc_tx_fallback() {
+ // Tests that if both nodes support anchors, but the remote node does not want to accept
+ // anchor channels at the moment, an error it sent to the local node such that it can retry
+ // the channel without the anchors feature.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let mut anchors_config = test_default_channel_config();
+ anchors_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ anchors_config.manually_accept_inbound_channels = true;
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(anchors_config.clone()), Some(anchors_config.clone())]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 0, None).unwrap();
+ let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+ assert!(open_channel_msg.channel_type.as_ref().unwrap().supports_anchors_zero_fee_htlc_tx());
+
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
+ let events = nodes[1].node.get_and_clear_pending_events();
+ match events[0] {
+ Event::OpenChannelRequest { temporary_channel_id, .. } => {
+ nodes[1].node.force_close_broadcasting_latest_txn(&temporary_channel_id, &nodes[0].node.get_our_node_id()).unwrap();
+ }
+ _ => panic!("Unexpected event"),
+ }
+
+ let error_msg = get_err_msg!(nodes[1], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &error_msg);
+
+ let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+ assert!(!open_channel_msg.channel_type.unwrap().supports_anchors_zero_fee_htlc_tx());
+
+ check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
+ }
}
#[cfg(all(any(test, feature = "_test_utils"), feature = "_bench_unstable"))]
node_a.peer_connected(&node_b.get_our_node_id(), &Init { features: node_b.init_features(), remote_network_address: None }).unwrap();
node_b.peer_connected(&node_a.get_our_node_id(), &Init { features: node_a.init_features(), remote_network_address: None }).unwrap();
node_a.create_channel(node_b.get_our_node_id(), 8_000_000, 100_000_000, 42, None).unwrap();
- node_b.handle_open_channel(&node_a.get_our_node_id(), node_a.init_features(), &get_event_msg!(node_a_holder, MessageSendEvent::SendOpenChannel, node_b.get_our_node_id()));
- node_a.handle_accept_channel(&node_b.get_our_node_id(), node_b.init_features(), &get_event_msg!(node_b_holder, MessageSendEvent::SendAcceptChannel, node_a.get_our_node_id()));
+ node_b.handle_open_channel(&node_a.get_our_node_id(), &get_event_msg!(node_a_holder, MessageSendEvent::SendOpenChannel, node_b.get_our_node_id()));
+ node_a.handle_accept_channel(&node_b.get_our_node_id(), &get_event_msg!(node_b_holder, MessageSendEvent::SendAcceptChannel, node_a.get_our_node_id()));
let tx;
if let Event::FundingGenerationReady { temporary_channel_id, output_script, .. } = get_event!(node_a_holder, Event::FundingGenerationReady) {
payment_preimage.0[0..8].copy_from_slice(&payment_count.to_le_bytes());
payment_count += 1;
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
- let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200).unwrap();
+ let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
$node_a.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
let payment_event = SendEvent::from_event($node_a.get_and_clear_pending_msg_events().pop().unwrap());
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information).
//! - `Keysend` - send funds to a node without an invoice
//! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information).
+//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs
+//! and HTLC transactions are pre-signed with zero fee (see
+//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
+//! information).
//!
//! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md
//! [messages]: crate::ln::msgs
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
- BasicMPP | Wumbo,
+ BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
- BasicMPP | Wumbo,
+ BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
// Byte 1
StaticRemoteKey,
// Byte 2
- ,
+ AnchorsZeroFeeHtlcTx,
// Byte 3
,
// Byte 4
define_feature!(19, Wumbo, [InitContext, NodeContext],
"Feature flags for `option_support_large_channel` (aka wumbo channels).", set_wumbo_optional, set_wumbo_required,
supports_wumbo, requires_wumbo);
+ define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext],
+ "Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional,
+ set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx);
define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext],
"Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional,
set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit);
}
impl ChannelTypeFeatures {
- /// Constructs the implicit channel type based on the common supported types between us and our
- /// counterparty
- pub(crate) fn from_counterparty_init(counterparty_init: &InitFeatures) -> Self {
- let mut ret = counterparty_init.to_context_internal();
+ // Maps the relevant `InitFeatures` to `ChannelTypeFeatures`. Any unknown features to
+ // `ChannelTypeFeatures` are not included in the result.
+ pub(crate) fn from_init(init: &InitFeatures) -> Self {
+ let mut ret = init.to_context_internal();
// ChannelTypeFeatures must only contain required bits, so we OR the required forms of all
// optional bits and then AND out the optional ones.
for byte in ret.flags.iter_mut() {
(byte & unknown_features) != 0
})
}
+
+ // Returns true if the features within `self` are a subset of the features within `other`.
+ pub(crate) fn is_subset(&self, other: &Self) -> bool {
+ for (idx, byte) in self.flags.iter().enumerate() {
+ if let Some(other_byte) = other.flags.get(idx) {
+ if byte & other_byte != *byte {
+ // `self` has bits set that `other` doesn't.
+ return false;
+ }
+ } else {
+ if *byte > 0 {
+ // `self` has a non-zero byte that `other` doesn't.
+ return false;
+ }
+ }
+ }
+ true
+ }
}
impl<T: sealed::UpfrontShutdownScript> Features<T> {
}
}
+impl<T: sealed::SCIDPrivacy> Features<T> {
+ pub(crate) fn clear_scid_privacy(&mut self) {
+ <T as sealed::SCIDPrivacy>::clear_bits(&mut self.flags);
+ }
+}
+
+impl<T: sealed::AnchorsZeroFeeHtlcTx> Features<T> {
+ pub(crate) fn clear_anchors_zero_fee_htlc_tx(&mut self) {
+ <T as sealed::AnchorsZeroFeeHtlcTx>::clear_bits(&mut self.flags);
+ }
+}
+
#[cfg(test)]
impl<T: sealed::UnknownFeature> Features<T> {
pub(crate) fn unknown() -> Self {
init_features.set_channel_type_optional();
init_features.set_scid_privacy_optional();
init_features.set_zero_conf_optional();
+ init_features.set_anchors_zero_fee_htlc_tx_optional();
assert!(init_features.initial_routing_sync());
assert!(!init_features.supports_upfront_shutdown_script());
assert_eq!(node_features.flags.len(), 7);
assert_eq!(node_features.flags[0], 0b00000010);
assert_eq!(node_features.flags[1], 0b01010001);
- assert_eq!(node_features.flags[2], 0b00001010);
+ assert_eq!(node_features.flags[2], 0b10001010);
assert_eq!(node_features.flags[3], 0b00001000);
assert_eq!(node_features.flags[4], 0b10000000);
assert_eq!(node_features.flags[5], 0b10100000);
// required-StaticRemoteKey ChannelTypeFeatures.
let mut init_features = InitFeatures::empty();
init_features.set_static_remote_key_optional();
- let converted_features = ChannelTypeFeatures::from_counterparty_init(&init_features);
+ let converted_features = ChannelTypeFeatures::from_init(&init_features);
assert_eq!(converted_features, ChannelTypeFeatures::only_static_remote_key());
assert!(!converted_features.supports_any_optional_bits());
assert!(converted_features.requires_static_remote_key());
connect_blocks(node, CHAN_CONFIRM_DEPTH - 1);
scid
}
-/// Mine a signle block containing the given transaction
+/// Mine a single block containing the given transaction
pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) {
let height = node.best_block_info().1 + 1;
confirm_transaction_at(node, tx, height);
}
+/// Mine a single block containing the given transactions
+pub fn mine_transactions<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, txn: &[&Transaction]) {
+ let height = node.best_block_info().1 + 1;
+ confirm_transactions_at(node, txn, height);
+}
/// Mine the given transaction at the given height, mining blocks as required to build to that
/// height
///
/// Returns the SCID a channel confirmed in the given transaction will have, assuming the funding
/// output is the 1st output in the transaction.
-pub fn confirm_transaction_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) -> u64 {
+pub fn confirm_transactions_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, txn: &[&Transaction], conf_height: u32) -> u64 {
let first_connect_height = node.best_block_info().1 + 1;
assert!(first_connect_height <= conf_height);
if conf_height > first_connect_height {
for _ in 0..*node.network_chan_count.borrow() { // Make sure we don't end up with channels at the same short id by offsetting by chan_count
block.txdata.push(Transaction { version: 0, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() });
}
- block.txdata.push(tx.clone());
+ for tx in txn {
+ block.txdata.push((*tx).clone());
+ }
connect_block(node, &block);
scid_utils::scid_from_parts(conf_height as u64, block.txdata.len() as u64 - 1, 0).unwrap()
}
+pub fn confirm_transaction_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) -> u64 {
+ confirm_transactions_at(node, &[tx], conf_height)
+}
/// The possible ways we may notify a ChannelManager of a new block
#[derive(Clone, Copy, Debug, PartialEq)]
initiator.node.create_channel(receiver.node.get_our_node_id(), 100_000, 10_001, 42, initiator_config).unwrap();
let open_channel = get_event_msg!(initiator, MessageSendEvent::SendOpenChannel, receiver.node.get_our_node_id());
- receiver.node.handle_open_channel(&initiator.node.get_our_node_id(), initiator.node.init_features(), &open_channel);
+ receiver.node.handle_open_channel(&initiator.node.get_our_node_id(), &open_channel);
let events = receiver.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
let accept_channel = get_event_msg!(receiver, MessageSendEvent::SendAcceptChannel, initiator.node.get_our_node_id());
assert_eq!(accept_channel.minimum_depth, 0);
- initiator.node.handle_accept_channel(&receiver.node.get_our_node_id(), receiver.node.init_features(), &accept_channel);
+ initiator.node.handle_accept_channel(&receiver.node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, _) = create_funding_transaction(&initiator, &receiver.node.get_our_node_id(), 100_000, 42);
initiator.node.funding_transaction_generated(&temporary_channel_id, &receiver.node.get_our_node_id(), tx.clone()).unwrap();
let open_channel_msg = get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id());
assert_eq!(open_channel_msg.temporary_channel_id, create_chan_id);
assert_eq!(node_a.node.list_channels().iter().find(|channel| channel.channel_id == create_chan_id).unwrap().user_channel_id, 42);
- let a_flags = node_a.override_init_features.borrow().clone().unwrap_or_else(|| node_a.node.init_features());
- let b_flags = node_b.override_init_features.borrow().clone().unwrap_or_else(|| node_b.node.init_features());
- node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), a_flags, &open_channel_msg);
+ node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), &open_channel_msg);
let accept_channel_msg = get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id());
assert_eq!(accept_channel_msg.temporary_channel_id, create_chan_id);
- node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), b_flags, &accept_channel_msg);
+ node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), &accept_channel_msg);
assert_ne!(node_b.node.list_channels().iter().find(|channel| channel.channel_id == create_chan_id).unwrap().user_channel_id, 0);
sign_funding_transaction(node_a, node_b, channel_value, create_chan_id)
no_announce_cfg.channel_handshake_config.announced_channel = false;
nodes[a].node.create_channel(nodes[b].node.get_our_node_id(), channel_value, push_msat, 42, Some(no_announce_cfg)).unwrap();
let open_channel = get_event_msg!(nodes[a], MessageSendEvent::SendOpenChannel, nodes[b].node.get_our_node_id());
- let a_flags = nodes[a].override_init_features.borrow().clone().unwrap_or_else(|| nodes[a].node.init_features());
- let b_flags = nodes[b].override_init_features.borrow().clone().unwrap_or_else(|| nodes[b].node.init_features());
- nodes[b].node.handle_open_channel(&nodes[a].node.get_our_node_id(), a_flags, &open_channel);
+ nodes[b].node.handle_open_channel(&nodes[a].node.get_our_node_id(), &open_channel);
let accept_channel = get_event_msg!(nodes[b], MessageSendEvent::SendAcceptChannel, nodes[a].node.get_our_node_id());
- nodes[a].node.handle_accept_channel(&nodes[b].node.get_our_node_id(), b_flags, &accept_channel);
+ nodes[a].node.handle_accept_channel(&nodes[b].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[a], &nodes[b].node.get_our_node_id(), channel_value, 42);
nodes[a].node.funding_transaction_generated(&temporary_channel_id, &nodes[b].node.get_our_node_id(), tx.clone()).unwrap();
let (tx_a, tx_b);
node_a.close_channel(channel_id, &node_b.get_our_node_id()).unwrap();
- node_b.handle_shutdown(&node_a.get_our_node_id(), &node_a.init_features(), &get_event_msg!(struct_a, MessageSendEvent::SendShutdown, node_b.get_our_node_id()));
+ node_b.handle_shutdown(&node_a.get_our_node_id(), &get_event_msg!(struct_a, MessageSendEvent::SendShutdown, node_b.get_our_node_id()));
let events_1 = node_b.get_and_clear_pending_msg_events();
assert!(events_1.len() >= 1);
})
};
- node_a.handle_shutdown(&node_b.get_our_node_id(), &node_b.init_features(), &shutdown_b);
+ node_a.handle_shutdown(&node_b.get_our_node_id(), &shutdown_b);
let (as_update, bs_update) = if close_inbound_first {
assert!(node_a.get_and_clear_pending_msg_events().is_empty());
node_a.handle_closing_signed(&node_b.get_our_node_id(), &closing_signed_b.unwrap());
}
};
($dest_node: expr, $min_value_msat: expr) => {
+ {
+ crate::get_payment_preimage_hash!($dest_node, $min_value_msat, None)
+ }
+ };
+ ($dest_node: expr, $min_value_msat: expr, $min_final_cltv_expiry_delta: expr) => {
{
use bitcoin::hashes::Hash as _;
let mut payment_count = $dest_node.network_payment_count.borrow_mut();
*payment_count += 1;
let payment_hash = $crate::ln::PaymentHash(
bitcoin::hashes::sha256::Hash::hash(&payment_preimage.0[..]).into_inner());
- let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200).unwrap();
+ let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200, $min_final_cltv_expiry_delta).unwrap();
(payment_preimage, payment_hash, payment_secret)
}
- }
+ };
}
#[macro_export]
use crate::chain::channelmonitor;
use crate::chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
use crate::chain::transaction::OutPoint;
-use crate::chain::keysinterface::{BaseSign, EntropySource};
+use crate::chain::keysinterface::{ChannelSigner, EcdsaChannelSigner, EntropySource};
use crate::ln::{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};
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, PaymentSendFailure, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA};
// Test helper that asserts we get the correct error string given a mutator
// that supposedly makes the channel open message insane
let insane_open_helper = |expected_error_str: &str, message_mutator: fn(msgs::OpenChannel) -> msgs::OpenChannel| {
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &message_mutator(open_channel_message.clone()));
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &message_mutator(open_channel_message.clone()));
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1);
let expected_regex = regex::Regex::new(expected_error_str).unwrap();
open_channel_message.channel_reserve_satoshis = 0;
open_channel_message.max_htlc_value_in_flight_msat = 100_000_000;
}
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_channel_message);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message);
// Extract the channel accept message from node1 to node0
let mut accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
accept_channel_message.channel_reserve_satoshis = 0;
accept_channel_message.max_htlc_value_in_flight_msat = 100_000_000;
}
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &accept_channel_message);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
{
let sender_node = if send_from_initiator { &nodes[1] } else { &nodes[0] };
let counterparty_node = if send_from_initiator { &nodes[0] } else { &nodes[1] };
let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
if steps & 0x0f == 1 { return; }
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_channel);
+ 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());
if steps & 0x0f == 2 { return; }
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42);
let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 900_000);
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], 800_000);
- let node_a_payment_secret = nodes[0].node.create_inbound_payment_for_hash(payment_hash, None, 7200).unwrap();
+ let node_a_payment_secret = nodes[0].node.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[0]]], 800_000, payment_hash, node_a_payment_secret);
// Provide preimage to node 0 by claiming payment
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, push_amt, 42, None).unwrap();
let mut open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
open_channel_msg.push_msat += 1;
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_channel_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), 1);
// broadcasted, even though it's created by `nodes[0]`.
let expected_temporary_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1_000_000, 500_000_000, 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(), nodes[0].node.init_features(), &open_channel);
+ 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(), nodes[1].node.init_features(), &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, _funding_output) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
assert_eq!(temporary_channel_id, expected_temporary_channel_id);
let (our_payment_preimage, duplicate_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 900_000);
- let payment_secret = nodes[3].node.create_inbound_payment_for_hash(duplicate_payment_hash, None, 7200).unwrap();
+ let payment_secret = nodes[3].node.create_inbound_payment_for_hash(duplicate_payment_hash, None, 7200, None).unwrap();
// We reduce the final CLTV here by a somewhat arbitrary constant to keep it under the one-byte
// script push size limit so that the below script length checks match
// ACCEPTED_HTLC_SCRIPT_WEIGHT.
let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id())
.with_features(nodes[3].node.invoice_features());
- let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[3], payment_params, 900000, TEST_FINAL_CLTV - 40);
- send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[2], &nodes[3]]], 900000, duplicate_payment_hash, payment_secret);
+ let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[3], payment_params, 800_000, TEST_FINAL_CLTV - 40);
+ send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[2], &nodes[3]]], 800_000, duplicate_payment_hash, payment_secret);
let commitment_txn = get_local_commitment_txn!(nodes[2], chan_2.2);
assert_eq!(commitment_txn[0].input.len(), 1);
check_spends!(node_txn[0], commitment_txn[0]);
assert_eq!(node_txn[0].input.len(), 1);
+ assert_eq!(node_txn[0].output.len(), 1);
if node_txn.len() > 2 {
check_spends!(node_txn[1], commitment_txn[0]);
assert_eq!(node_txn[1].input.len(), 1);
+ assert_eq!(node_txn[1].output.len(), 1);
assert_eq!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
check_spends!(node_txn[2], commitment_txn[0]);
+ assert_eq!(node_txn[2].input.len(), 1);
+ assert_eq!(node_txn[2].output.len(), 1);
assert_ne!(node_txn[0].input[0].previous_output, node_txn[2].input[0].previous_output);
} else {
check_spends!(node_txn[1], commitment_txn[0]);
+ assert_eq!(node_txn[1].input.len(), 1);
+ assert_eq!(node_txn[1].output.len(), 1);
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
}
assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
+ // Assign htlc_timeout_tx to the forwarded HTLC (with value ~800 sats). The received HTLC
+ // (with value 900 sats) will be claimed in the below `claim_funds` call.
if node_txn.len() > 2 {
assert_eq!(node_txn[2].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
+ htlc_timeout_tx = if node_txn[2].output[0].value < 900 { node_txn[2].clone() } else { node_txn[0].clone() };
+ } else {
+ htlc_timeout_tx = if node_txn[0].output[0].value < 900 { node_txn[1].clone() } else { node_txn[0].clone() };
}
- htlc_timeout_tx = node_txn[0].clone();
}
nodes[2].node.claim_funds(our_payment_preimage);
expect_payment_failed_with_update!(nodes[0], duplicate_payment_hash, false, chan_2.0.contents.short_channel_id, true);
// Solve 2nd HTLC by broadcasting on B's chain HTLC-Success Tx from C
- // Note that the fee paid is effectively double as the HTLC value (including the nodes[1] fee
- // and nodes[2] fee) is rounded down and then claimed in full.
mine_transaction(&nodes[1], &htlc_success_txn[1]);
- expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(196*2), true, true);
+ expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(196), true, true);
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
assert!(updates.update_add_htlcs.is_empty());
assert!(updates.update_fail_htlcs.is_empty());
let (_, payment_hash_2, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], ds_dust_limit*1000);
// 2nd HTLC:
- send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_1, nodes[5].node.create_inbound_payment_for_hash(payment_hash_1, None, 7200).unwrap()); // not added < dust limit + HTLC tx fee
+ send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_1, nodes[5].node.create_inbound_payment_for_hash(payment_hash_1, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
// 3rd HTLC:
- send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_2, nodes[5].node.create_inbound_payment_for_hash(payment_hash_2, None, 7200).unwrap()); // not added < dust limit + HTLC tx fee
+ send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_2, nodes[5].node.create_inbound_payment_for_hash(payment_hash_2, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
// 4th HTLC:
let (_, payment_hash_3, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
// 5th HTLC:
let (_, payment_hash_4, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], 1000000);
// 6th HTLC:
- send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_3, nodes[5].node.create_inbound_payment_for_hash(payment_hash_3, None, 7200).unwrap());
+ send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_3, nodes[5].node.create_inbound_payment_for_hash(payment_hash_3, None, 7200, None).unwrap());
// 7th HTLC:
- send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_4, nodes[5].node.create_inbound_payment_for_hash(payment_hash_4, None, 7200).unwrap());
+ send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_4, nodes[5].node.create_inbound_payment_for_hash(payment_hash_4, None, 7200, None).unwrap());
// 8th HTLC:
let (_, payment_hash_5, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
// 9th HTLC:
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], ds_dust_limit*1000);
- send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_5, nodes[5].node.create_inbound_payment_for_hash(payment_hash_5, None, 7200).unwrap()); // not added < dust limit + HTLC tx fee
+ send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_5, nodes[5].node.create_inbound_payment_for_hash(payment_hash_5, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
// 10th HTLC:
let (_, payment_hash_6, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
// 11th HTLC:
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], 1000000);
- send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_6, nodes[5].node.create_inbound_payment_for_hash(payment_hash_6, None, 7200).unwrap());
+ send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_6, nodes[5].node.create_inbound_payment_for_hash(payment_hash_6, None, 7200, None).unwrap());
// Double-check that six of the new HTLC were added
// We now have six HTLCs pending over the dust limit and six HTLCs under the dust limit (ie,
let push_msat=10001;
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), channel_value_satoshis, push_msat, 42, None).unwrap();
let node0_to_1_send_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(), nodes[0].node.init_features(), &node0_to_1_send_open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &node0_to_1_send_open_channel);
get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
// Create a second channel with the same random values. This used to panic due to a colliding
node0_to_1_send_open_channel.dust_limit_satoshis = 547;
node0_to_1_send_open_channel.channel_reserve_satoshis = 100001;
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &node0_to_1_send_open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &node0_to_1_send_open_channel);
let events = nodes[1].node.get_and_clear_pending_msg_events();
let err_msg = match events[0] {
MessageSendEvent::HandleError { action: ErrorAction::SendErrorMessage { ref msg }, node_id: _ } => {
let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id());
open_channel.to_self_delay = 200;
if let Err(error) = Channel::new_from_req(&LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }),
- &nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &open_channel, 0,
+ &nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[0].node.channel_type_features(), &nodes[1].node.init_features(), &open_channel, 0,
&low_our_to_self_config, 0, &nodes[0].logger, 42)
{
match error {
// We test msg.to_self_delay <= config.their_to_self_delay is enforced in Chanel::accept_channel()
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1000000, 1000000, 42, None).unwrap();
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &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(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
let mut accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
accept_channel.to_self_delay = 200;
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[0].node.init_features(), &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let reason_msg;
if let MessageSendEvent::HandleError { ref action, .. } = nodes[0].node.get_and_clear_pending_msg_events()[0] {
match action {
let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id());
open_channel.to_self_delay = 200;
if let Err(error) = Channel::new_from_req(&LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }),
- &nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &open_channel, 0,
+ &nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[0].node.channel_type_features(), &nodes[1].node.init_features(), &open_channel, 0,
&high_their_to_self_config, 0, &nodes[0].logger, 42)
{
match error {
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id()).with_features(nodes[1].node.invoice_features());
let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None, 10_000, TEST_FINAL_CLTV, 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).unwrap();
+ 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(&route, our_payment_hash, &Some(our_payment_secret), PaymentId(our_payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);
assert_ne!(node_txn[0].input[0].previous_output, node_txn[2].input[0].previous_output);
assert_ne!(node_txn[1].input[0].previous_output, node_txn[2].input[0].previous_output);
- assert_eq!(node_txn[0].input[0].previous_output, revoked_htlc_txn[0].input[0].previous_output);
- assert_eq!(node_txn[1].input[0].previous_output, revoked_htlc_txn[1].input[0].previous_output);
+ assert_eq!(node_txn[0].input[0].previous_output, revoked_htlc_txn[1].input[0].previous_output);
+ assert_eq!(node_txn[1].input[0].previous_output, revoked_htlc_txn[0].input[0].previous_output);
// node_txn[3] spends the revoked outputs from the revoked_htlc_txn (which only have one
// output, checked above).
let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
assert_eq!(res.htlc_minimum_msat, 1);
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &res);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &res);
let res = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
assert_eq!(res.htlc_minimum_msat, 1);
}
let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, Some(manually_accept_conf)).unwrap();
let res = 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(), nodes[0].node.init_features(), &res);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &res);
// Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before
// accepting the inbound channel request.
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, Some(manually_accept_conf)).unwrap();
let res = 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(), nodes[0].node.init_features(), &res);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &res);
// Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before
// rejecting the inbound channel request.
let res = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
let temp_channel_id = res.temporary_channel_id;
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &res);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &res);
// Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in the `msg_events`.
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let channel = get_channel_ref!(&nodes[1], nodes[0], node_1_per_peer_lock, node_1_peer_state_lock, temp_channel_id);
channel.get_accept_channel_message()
};
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &accept_chan_msg);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_chan_msg);
let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42);
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, Some(manually_accept_conf)).unwrap();
let res = 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(), nodes[0].node.init_features(), &res);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &res);
// Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in `msg_events` before
// accepting the inbound channel request.
create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
{
- let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 7200).unwrap();
+ let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 7200, None).unwrap();
let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
check_added_monitors!(nodes[0], 1);
let random_payment_hash = PaymentHash([42; 32]);
let random_payment_secret = PaymentSecret([43; 32]);
- let (our_payment_hash, our_payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 2).unwrap();
+ let (our_payment_hash, our_payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 2, None).unwrap();
let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
// All the below cases should end up being handled exactly identically, so we macro the
let mut node_0_peer_state_lock;
let mut channel = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1.2);
if let Ok((_, _, update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
- assert_eq!(watchtower.chain_monitor.update_channel(outpoint, update.clone()), ChannelMonitorUpdateStatus::PermanentFailure);
- assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, update), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(watchtower.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::PermanentFailure);
+ assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
} else { assert!(false); }
}
// Our local monitor is in-sync and hasn't processed yet timeout
let mut channel = get_channel_ref!(nodes[0], nodes[1], node_0_per_peer_lock, node_0_peer_state_lock, chan_1.2);
if let Ok((_, _, update)) = channel.commitment_signed(&updates.commitment_signed, &node_cfgs[0].logger) {
// Watchtower Alice should already have seen the block and reject the update
- assert_eq!(watchtower_alice.chain_monitor.update_channel(outpoint, update.clone()), ChannelMonitorUpdateStatus::PermanentFailure);
- assert_eq!(watchtower_bob.chain_monitor.update_channel(outpoint, update.clone()), ChannelMonitorUpdateStatus::Completed);
- assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, update), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(watchtower_alice.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::PermanentFailure);
+ assert_eq!(watchtower_bob.chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
+ assert_eq!(nodes[0].chain_monitor.update_channel(outpoint, &update), ChannelMonitorUpdateStatus::Completed);
} else { assert!(false); }
}
// Our local monitor is in-sync and hasn't processed yet timeout
// Create an initial channel
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
let mut open_chan_msg = 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(), nodes[0].node.init_features(), &open_chan_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_chan_msg);
let accept_chan_msg = 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(), nodes[1].node.init_features(), &accept_chan_msg);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_chan_msg);
// Move the first channel through the funding flow...
let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42);
// Assert that `nodes[0]` can accept both `OpenChannel` requests, even though they use the same
// `temporary_channel_id` as they are from different peers.
- nodes[0].node.handle_open_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &open_chan_msg_chan_1_0);
+ nodes[0].node.handle_open_channel(&nodes[1].node.get_our_node_id(), &open_chan_msg_chan_1_0);
{
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
}
}
- nodes[0].node.handle_open_channel(&nodes[2].node.get_our_node_id(), nodes[2].node.init_features(), &open_chan_msg_chan_2_0);
+ nodes[0].node.handle_open_channel(&nodes[2].node.get_our_node_id(), &open_chan_msg_chan_2_0);
{
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
// Create an initial channel
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
let mut open_chan_msg = 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(), nodes[0].node.init_features(), &open_chan_msg);
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_chan_msg);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
// Try to create a second channel with the same temporary_channel_id as the first and check
// that it is rejected.
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_chan_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_chan_msg);
{
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
// Technically this is allowed by the spec, but we don't support it and there's little reason
// to. Still, it shouldn't cause any other issues.
open_chan_msg.temporary_channel_id = channel_id;
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_chan_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_chan_msg);
{
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
// Now try to create a second channel which has a duplicate funding output.
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
let open_chan_2_msg = 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(), nodes[0].node.init_features(), &open_chan_2_msg);
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_chan_2_msg);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42); // Get and check the FundingGenerationReady event
let funding_created = {
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 10_000, 42, None).unwrap();
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
let (temporary_channel_id, mut tx, _) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100_000, 42);
if on_holder_tx {
open_channel.dust_limit_satoshis = 546;
}
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
let mut 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(), nodes[1].node.init_features(), &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let opt_anchors = false;
let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
let open_channel_message = 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(), nodes[0].node.init_features(), &open_channel_message);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_message);
let accept_channel_message = 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(), nodes[1].node.init_features(), &accept_channel_message);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel_message);
let best_height = nodes[0].node.best_block.read().unwrap().height();
_ => panic!("Unexpected event"),
};
}
+
+fn do_payment_with_custom_min_final_cltv_expiry(valid_delta: bool, use_user_hash: bool) {
+ let mut 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);
+ let min_final_cltv_expiry_delta = 120;
+ let final_cltv_expiry_delta = if valid_delta { min_final_cltv_expiry_delta + 2 } else {
+ min_final_cltv_expiry_delta - 2 };
+ let recv_value = 100_000;
+
+ create_chan_between_nodes(&nodes[0], &nodes[1]);
+
+ let payment_parameters = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id());
+ let (payment_hash, payment_preimage, payment_secret) = if use_user_hash {
+ let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1],
+ Some(recv_value), Some(min_final_cltv_expiry_delta));
+ (payment_hash, payment_preimage, payment_secret)
+ } else {
+ let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(recv_value), 7200, Some(min_final_cltv_expiry_delta)).unwrap();
+ (payment_hash, nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap(), payment_secret)
+ };
+ let route = get_route!(nodes[0], payment_parameters, recv_value, final_cltv_expiry_delta as u32).unwrap();
+ nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let mut payment_event = SendEvent::from_event(events.pop().unwrap());
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
+ expect_pending_htlcs_forwardable!(nodes[1]);
+
+ if valid_delta {
+ expect_payment_claimable!(nodes[1], payment_hash, payment_secret, recv_value, if use_user_hash {
+ None } else { Some(payment_preimage) }, nodes[1].node.get_our_node_id());
+
+ claim_payment(&nodes[0], &vec!(&nodes[1])[..], payment_preimage);
+ } else {
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash }]);
+
+ check_added_monitors!(nodes[1], 1);
+
+ let fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_updates.update_fail_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[1], fail_updates.commitment_signed, false, true);
+
+ expect_payment_failed!(nodes[0], payment_hash, true);
+ }
+}
+
+#[test]
+fn test_payment_with_custom_min_cltv_expiry_delta() {
+ do_payment_with_custom_min_final_cltv_expiry(false, false);
+ do_payment_with_custom_min_final_cltv_expiry(false, true);
+ do_payment_with_custom_min_final_cltv_expiry(true, false);
+ do_payment_with_custom_min_final_cltv_expiry(true, true);
+}
enum Method {
LdkPaymentHash = 0,
UserPaymentHash = 1,
+ LdkPaymentHashCustomFinalCltv = 2,
+ UserPaymentHashCustomFinalCltv = 3,
}
impl Method {
match bits {
bits if bits == Method::LdkPaymentHash as u8 => Ok(Method::LdkPaymentHash),
bits if bits == Method::UserPaymentHash as u8 => Ok(Method::UserPaymentHash),
+ bits if bits == Method::LdkPaymentHashCustomFinalCltv as u8 => Ok(Method::LdkPaymentHashCustomFinalCltv),
+ bits if bits == Method::UserPaymentHashCustomFinalCltv as u8 => Ok(Method::UserPaymentHashCustomFinalCltv),
unknown => Err(unknown),
}
}
}
+fn min_final_cltv_expiry_delta_from_metadata(bytes: [u8; METADATA_LEN]) -> u16 {
+ let expiry_bytes = &bytes[AMT_MSAT_LEN..];
+ u16::from_be_bytes([expiry_bytes[0], expiry_bytes[1]])
+}
+
/// Equivalent to [`crate::ln::channelmanager::ChannelManager::create_inbound_payment`], but no
/// `ChannelManager` is required. Useful for generating invoices for [phantom node payments] without
/// a `ChannelManager`.
///
/// `current_time` is a Unix timestamp representing the current time.
///
+/// Note that if `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
+/// on versions of LDK prior to 0.0.114.
+///
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
/// [`NodeSigner::get_inbound_payment_key_material`]: crate::chain::keysinterface::NodeSigner::get_inbound_payment_key_material
-pub fn create<ES: Deref>(keys: &ExpandedKey, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32, entropy_source: &ES, current_time: u64) -> Result<(PaymentHash, PaymentSecret), ()>
+pub fn create<ES: Deref>(keys: &ExpandedKey, min_value_msat: Option<u64>,
+ invoice_expiry_delta_secs: u32, entropy_source: &ES, current_time: u64,
+ min_final_cltv_expiry_delta: Option<u16>) -> Result<(PaymentHash, PaymentSecret), ()>
where ES::Target: EntropySource
{
- let metadata_bytes = construct_metadata_bytes(min_value_msat, Method::LdkPaymentHash, invoice_expiry_delta_secs, current_time)?;
+ let metadata_bytes = construct_metadata_bytes(min_value_msat, if min_final_cltv_expiry_delta.is_some() {
+ Method::LdkPaymentHashCustomFinalCltv
+ } else {
+ Method::LdkPaymentHash
+ }, invoice_expiry_delta_secs, current_time, min_final_cltv_expiry_delta)?;
let mut iv_bytes = [0 as u8; IV_LEN];
let rand_bytes = entropy_source.get_secure_random_bytes();
///
/// See [`create`] for information on the `keys` and `current_time` parameters.
///
+/// Note that if `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
+/// on versions of LDK prior to 0.0.114.
+///
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
-pub fn create_from_hash(keys: &ExpandedKey, min_value_msat: Option<u64>, payment_hash: PaymentHash, invoice_expiry_delta_secs: u32, current_time: u64) -> Result<PaymentSecret, ()> {
- let metadata_bytes = construct_metadata_bytes(min_value_msat, Method::UserPaymentHash, invoice_expiry_delta_secs, current_time)?;
+pub fn create_from_hash(keys: &ExpandedKey, min_value_msat: Option<u64>, payment_hash: PaymentHash,
+ invoice_expiry_delta_secs: u32, current_time: u64, min_final_cltv_expiry_delta: Option<u16>) -> Result<PaymentSecret, ()> {
+ let metadata_bytes = construct_metadata_bytes(min_value_msat, if min_final_cltv_expiry_delta.is_some() {
+ Method::UserPaymentHashCustomFinalCltv
+ } else {
+ Method::UserPaymentHash
+ }, invoice_expiry_delta_secs, current_time, min_final_cltv_expiry_delta)?;
let mut hmac = HmacEngine::<Sha256>::new(&keys.user_pmt_hash_key);
hmac.input(&metadata_bytes);
Ok(construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key))
}
-fn construct_metadata_bytes(min_value_msat: Option<u64>, payment_type: Method, invoice_expiry_delta_secs: u32, highest_seen_timestamp: u64) -> Result<[u8; METADATA_LEN], ()> {
+fn construct_metadata_bytes(min_value_msat: Option<u64>, payment_type: Method,
+ invoice_expiry_delta_secs: u32, highest_seen_timestamp: u64, min_final_cltv_expiry_delta: Option<u16>) -> Result<[u8; METADATA_LEN], ()> {
if min_value_msat.is_some() && min_value_msat.unwrap() > MAX_VALUE_MSAT {
return Err(());
}
// than two hours in the future. Thus, we add two hours here as a buffer to ensure we
// absolutely never fail a payment too early.
// Note that we assume that received blocks have reasonably up-to-date timestamps.
- let expiry_bytes = (highest_seen_timestamp + invoice_expiry_delta_secs as u64 + 7200).to_be_bytes();
+ let expiry_timestamp = highest_seen_timestamp + invoice_expiry_delta_secs as u64 + 7200;
+ let mut expiry_bytes = expiry_timestamp.to_be_bytes();
+
+ // `min_value_msat` should fit in (64 bits - 3 payment type bits =) 61 bits as an unsigned integer.
+ // This should leave us with a maximum value greater than the 21M BTC supply cap anyway.
+ if min_value_msat.is_some() && min_value_msat.unwrap() > ((1u64 << 61) - 1) { return Err(()); }
+
+ // `expiry_timestamp` should fit in (64 bits - 2 delta bytes =) 48 bits as an unsigned integer.
+ // Bitcoin's block header timestamps are actually `u32`s, so we're technically already limited to
+ // the much smaller maximum timestamp of `u32::MAX` for now, but we check the u64 `expiry_timestamp`
+ // for future-proofing.
+ if min_final_cltv_expiry_delta.is_some() && expiry_timestamp > ((1u64 << 48) - 1) { return Err(()); }
+
+ if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
+ let bytes = min_final_cltv_expiry_delta.to_be_bytes();
+ expiry_bytes[0] |= bytes[0];
+ expiry_bytes[1] |= bytes[1];
+ }
let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
+
metadata_bytes[..AMT_MSAT_LEN].copy_from_slice(&min_amt_msat_bytes);
metadata_bytes[AMT_MSAT_LEN..].copy_from_slice(&expiry_bytes);
/// secret (and, if supplied by LDK, our payment preimage) to include encrypted metadata about the
/// payment.
///
-/// The metadata is constructed as:
+/// For payments without a custom `min_final_cltv_expiry_delta`, the metadata is constructed as:
/// payment method (3 bits) || payment amount (8 bytes - 3 bits) || expiry (8 bytes)
-/// and encrypted using a key derived from [`NodeSigner::get_inbound_payment_key_material`].
+///
+/// For payments including a custom `min_final_cltv_expiry_delta`, the metadata is constructed as:
+/// payment method (3 bits) || payment amount (8 bytes - 3 bits) || min_final_cltv_expiry_delta (2 bytes) || expiry (6 bytes)
+///
+/// In both cases the result is then encrypted using a key derived from [`NodeSigner::get_inbound_payment_key_material`].
///
/// Then on payment receipt, we verify in this method that the payment preimage and payment secret
/// match what was constructed.
/// [`NodeSigner::get_inbound_payment_key_material`]: crate::chain::keysinterface::NodeSigner::get_inbound_payment_key_material
/// [`create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment
/// [`create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash
-pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData, highest_seen_timestamp: u64, keys: &ExpandedKey, logger: &L) -> Result<Option<PaymentPreimage>, ()>
+pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData,
+ highest_seen_timestamp: u64, keys: &ExpandedKey, logger: &L) -> Result<
+ (Option<PaymentPreimage>, Option<u16>), ()>
where L::Target: Logger
{
let (iv_bytes, metadata_bytes) = decrypt_metadata(payment_data.payment_secret, keys);
let payment_type_res = Method::from_bits((metadata_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET);
let mut amt_msat_bytes = [0; AMT_MSAT_LEN];
+ let mut expiry_bytes = [0; METADATA_LEN - AMT_MSAT_LEN];
amt_msat_bytes.copy_from_slice(&metadata_bytes[..AMT_MSAT_LEN]);
+ expiry_bytes.copy_from_slice(&metadata_bytes[AMT_MSAT_LEN..]);
// Zero out the bits reserved to indicate the payment type.
amt_msat_bytes[0] &= 0b00011111;
- let min_amt_msat: u64 = u64::from_be_bytes(amt_msat_bytes.into());
- let expiry = u64::from_be_bytes(metadata_bytes[AMT_MSAT_LEN..].try_into().unwrap());
+ let mut min_final_cltv_expiry_delta = None;
- // Make sure to check to check the HMAC before doing the other checks below, to mitigate timing
- // attacks.
+ // Make sure to check the HMAC before doing the other checks below, to mitigate timing attacks.
let mut payment_preimage = None;
+
match payment_type_res {
- Ok(Method::UserPaymentHash) => {
+ Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => {
let mut hmac = HmacEngine::<Sha256>::new(&keys.user_pmt_hash_key);
hmac.input(&metadata_bytes[..]);
hmac.input(&payment_hash.0);
return Err(())
}
},
- Ok(Method::LdkPaymentHash) => {
+ Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => {
match derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys) {
Ok(preimage) => payment_preimage = Some(preimage),
Err(bad_preimage_bytes) => {
}
}
+ match payment_type_res {
+ Ok(Method::UserPaymentHashCustomFinalCltv) | Ok(Method::LdkPaymentHashCustomFinalCltv) => {
+ min_final_cltv_expiry_delta = Some(min_final_cltv_expiry_delta_from_metadata(metadata_bytes));
+ // Zero out first two bytes of expiry reserved for `min_final_cltv_expiry_delta`.
+ expiry_bytes[0] &= 0;
+ expiry_bytes[1] &= 0;
+ }
+ _ => {}
+ }
+
+ let min_amt_msat: u64 = u64::from_be_bytes(amt_msat_bytes.into());
+ let expiry = u64::from_be_bytes(expiry_bytes.try_into().unwrap());
+
if payment_data.total_msat < min_amt_msat {
log_trace!(logger, "Failing HTLC with payment_hash {} due to total_msat {} being less than the minimum amount of {} msat", log_bytes!(payment_hash.0), payment_data.total_msat, min_amt_msat);
return Err(())
return Err(())
}
- Ok(payment_preimage)
+ Ok((payment_preimage, min_final_cltv_expiry_delta))
}
pub(super) fn get_payment_preimage(payment_hash: PaymentHash, payment_secret: PaymentSecret, keys: &ExpandedKey) -> Result<PaymentPreimage, APIError> {
let (iv_bytes, metadata_bytes) = decrypt_metadata(payment_secret, keys);
match Method::from_bits((metadata_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET) {
- Ok(Method::LdkPaymentHash) => {
+ Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => {
derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys)
.map_err(|bad_preimage_bytes| APIError::APIMisuseError {
err: format!("Payment hash {} did not match decoded preimage {}", log_bytes!(payment_hash.0), log_bytes!(bad_preimage_bytes))
})
},
- Ok(Method::UserPaymentHash) => Err(APIError::APIMisuseError {
+ Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => Err(APIError::APIMisuseError {
err: "Expected payment type to be LdkPaymentHash, instead got UserPaymentHash".to_string()
}),
Err(other) => Err(APIError::APIMisuseError { err: format!("Unknown payment type: {}", other) }),
//! Further functional tests which test blockchain reorganizations.
+#[cfg(anchors)]
+use crate::chain::keysinterface::BaseSign;
+#[cfg(anchors)]
+use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, Balance};
use crate::chain::transaction::OutPoint;
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
use crate::ln::channel;
+#[cfg(anchors)]
+use crate::ln::chan_utils;
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, PaymentId};
use crate::ln::msgs::ChannelMessageHandler;
+#[cfg(anchors)]
+use crate::util::config::UserConfig;
+#[cfg(anchors)]
+use crate::util::events::BumpTransactionEvent;
use crate::util::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason, HTLCDestination};
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::opcodes;
use bitcoin::secp256k1::Secp256k1;
+#[cfg(anchors)]
+use bitcoin::{Amount, Script, TxIn, TxOut, PackedLockTime};
use bitcoin::Transaction;
use crate::prelude::*;
nodes[0].node.close_channel(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
assert!(nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances().is_empty());
}
+
+#[cfg(anchors)]
+#[test]
+fn test_yield_anchors_events() {
+ // Tests that two parties supporting anchor outputs can open a channel, route payments over
+ // it, and finalize its resolution uncooperatively. Once the HTLCs are locked in, one side will
+ // force close once the HTLCs expire. The force close should stem from an event emitted by LDK,
+ // allowing the consumer to provide additional fees to the commitment transaction to be
+ // broadcast. Once the commitment transaction confirms, events for the HTLC resolution should be
+ // emitted by LDK, such that the consumer can attach fees to the zero fee HTLC transactions.
+ let secp = Secp256k1::new();
+ let mut chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let mut anchors_config = UserConfig::default();
+ anchors_config.channel_handshake_config.announced_channel = true;
+ anchors_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(anchors_config), Some(anchors_config)]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let chan_id = create_announced_chan_between_nodes_with_value(
+ &nodes, 0, 1, 1_000_000, 500_000_000
+ ).2;
+ route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
+ let (payment_preimage, payment_hash, _) = route_payment(&nodes[1], &[&nodes[0]], 1_000_000);
+
+ assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
+
+ connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
+ check_closed_broadcast!(&nodes[0], true);
+ assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
+
+ get_monitor!(nodes[0], chan_id).provide_payment_preimage(
+ &payment_hash, &payment_preimage, &node_cfgs[0].tx_broadcaster,
+ &LowerBoundedFeeEstimator::new(node_cfgs[0].fee_estimator), &nodes[0].logger
+ );
+
+ let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+ assert_eq!(holder_events.len(), 1);
+ let (commitment_tx, anchor_tx) = match holder_events.pop().unwrap() {
+ Event::BumpTransaction(BumpTransactionEvent::ChannelClose { commitment_tx, anchor_descriptor, .. }) => {
+ assert_eq!(commitment_tx.input.len(), 1);
+ assert_eq!(commitment_tx.output.len(), 6);
+ let mut anchor_tx = Transaction {
+ version: 2,
+ lock_time: PackedLockTime::ZERO,
+ input: vec![
+ TxIn { previous_output: anchor_descriptor.outpoint, ..Default::default() },
+ TxIn { ..Default::default() },
+ ],
+ output: vec![TxOut {
+ value: Amount::ONE_BTC.to_sat(),
+ script_pubkey: Script::new_op_return(&[]),
+ }],
+ };
+ let signer = nodes[0].keys_manager.derive_channel_keys(
+ anchor_descriptor.channel_value_satoshis, &anchor_descriptor.channel_keys_id,
+ );
+ let funding_sig = signer.sign_holder_anchor_input(&mut anchor_tx, 0, &secp).unwrap();
+ anchor_tx.input[0].witness = chan_utils::build_anchor_input_witness(
+ &signer.pubkeys().funding_pubkey, &funding_sig
+ );
+ (commitment_tx, anchor_tx)
+ },
+ _ => panic!("Unexpected event"),
+ };
+
+ mine_transactions(&nodes[0], &[&commitment_tx, &anchor_tx]);
+ check_added_monitors!(nodes[0], 1);
+
+ let mut holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+ // Certain block `ConnectStyle`s cause an extra `ChannelClose` event to be emitted since the
+ // best block is being updated prior to the confirmed transactions.
+ match *nodes[0].connect_style.borrow() {
+ ConnectStyle::BestBlockFirst|ConnectStyle::BestBlockFirstReorgsOnlyTip|ConnectStyle::BestBlockFirstSkippingBlocks => {
+ assert_eq!(holder_events.len(), 3);
+ if let Event::BumpTransaction(BumpTransactionEvent::ChannelClose { .. }) = holder_events.remove(0) {}
+ else { panic!("unexpected event"); }
+
+ },
+ _ => assert_eq!(holder_events.len(), 2),
+ };
+ let mut htlc_txs = Vec::with_capacity(2);
+ for event in holder_events {
+ match event {
+ Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { htlc_descriptors, .. }) => {
+ assert_eq!(htlc_descriptors.len(), 1);
+ let htlc_descriptor = &htlc_descriptors[0];
+ let signer = nodes[0].keys_manager.derive_channel_keys(
+ htlc_descriptor.channel_value_satoshis, &htlc_descriptor.channel_keys_id
+ );
+ let per_commitment_point = signer.get_per_commitment_point(htlc_descriptor.per_commitment_number, &secp);
+ let mut htlc_tx = Transaction {
+ version: 2,
+ lock_time: if htlc_descriptor.htlc.offered {
+ PackedLockTime(htlc_descriptor.htlc.cltv_expiry)
+ } else {
+ PackedLockTime::ZERO
+ },
+ input: vec![
+ htlc_descriptor.unsigned_tx_input(), // HTLC input
+ TxIn { ..Default::default() } // Fee input
+ ],
+ output: vec![
+ htlc_descriptor.tx_output(&per_commitment_point, &secp), // HTLC output
+ TxOut { // Fee input change
+ value: Amount::ONE_BTC.to_sat(),
+ script_pubkey: Script::new_op_return(&[]),
+ }
+ ]
+ };
+ let our_sig = signer.sign_holder_htlc_transaction(&mut htlc_tx, 0, htlc_descriptor, &secp).unwrap();
+ let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &secp);
+ htlc_tx.input[0].witness = htlc_descriptor.tx_input_witness(&our_sig, &witness_script);
+ htlc_txs.push(htlc_tx);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ }
+
+ mine_transactions(&nodes[0], &[&htlc_txs[0], &htlc_txs[1]]);
+ connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
+
+ assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
+
+ connect_blocks(&nodes[0], BREAKDOWN_TIMEOUT as u32);
+
+ let holder_events = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+ assert_eq!(holder_events.len(), 3);
+ for event in holder_events {
+ match event {
+ Event::SpendableOutputs { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+ }
+
+ // Clear the remaining events as they're not relevant to what we're testing.
+ nodes[0].node.get_and_clear_pending_events();
+}
}
}
+/// Represents the set of gossip messages that require a signature from a node's identity key.
+pub enum UnsignedGossipMessage<'a> {
+ /// An unsigned channel announcement.
+ ChannelAnnouncement(&'a UnsignedChannelAnnouncement),
+ /// An unsigned channel update.
+ ChannelUpdate(&'a UnsignedChannelUpdate),
+ /// An unsigned node announcement.
+ NodeAnnouncement(&'a UnsignedNodeAnnouncement)
+}
+
+impl<'a> Writeable for UnsignedGossipMessage<'a> {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ match self {
+ UnsignedGossipMessage::ChannelAnnouncement(ref msg) => msg.write(writer),
+ UnsignedGossipMessage::ChannelUpdate(ref msg) => msg.write(writer),
+ UnsignedGossipMessage::NodeAnnouncement(ref msg) => msg.write(writer),
+ }
+ }
+}
/// The unsigned part of a [`node_announcement`] message.
///
pub short_channel_ids: Vec<u64>,
}
-/// A [`reply_short_channel_ids_end message`] is sent as a reply to a
+/// A [`reply_short_channel_ids_end`] message is sent as a reply to a
/// message. The query recipient makes a best
/// effort to respond based on their local network view which may not be
/// a perfect view of the network.
pub trait ChannelMessageHandler : MessageSendEventsProvider {
// Channel init:
/// Handle an incoming `open_channel` message from the given peer.
- fn handle_open_channel(&self, their_node_id: &PublicKey, their_features: InitFeatures, msg: &OpenChannel);
+ fn handle_open_channel(&self, their_node_id: &PublicKey, msg: &OpenChannel);
/// Handle an incoming `accept_channel` message from the given peer.
- fn handle_accept_channel(&self, their_node_id: &PublicKey, their_features: InitFeatures, msg: &AcceptChannel);
+ fn handle_accept_channel(&self, their_node_id: &PublicKey, msg: &AcceptChannel);
/// Handle an incoming `funding_created` message from the given peer.
fn handle_funding_created(&self, their_node_id: &PublicKey, msg: &FundingCreated);
/// Handle an incoming `funding_signed` message from the given peer.
// Channl close:
/// Handle an incoming `shutdown` message from the given peer.
- fn handle_shutdown(&self, their_node_id: &PublicKey, their_features: &InitFeatures, msg: &Shutdown);
+ fn handle_shutdown(&self, their_node_id: &PublicKey, msg: &Shutdown);
/// Handle an incoming `closing_signed` message from the given peer.
fn handle_closing_signed(&self, their_node_id: &PublicKey, msg: &ClosingSigned);
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentSecret};
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
-use crate::ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
+use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId};
use crate::ln::onion_utils;
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1;
-use bitcoin::secp256k1::Secp256k1;
-use bitcoin::secp256k1::{PublicKey, SecretKey};
+use bitcoin::secp256k1::{Secp256k1, SecretKey};
use crate::io;
use crate::prelude::*;
}
}
+fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {
+
+ 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);
+
+ create_announced_chan_between_nodes(&nodes, 0, 1);
+
+ let payment_amount = 100_000;
+ let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_amount);
+ nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ let mut payment_event = SendEvent::from_event(events.pop().unwrap());
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
+
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ expect_payment_claimable!(nodes[1], payment_hash, payment_secret, payment_amount);
+ nodes[1].node.fail_htlc_backwards_with_reason(&payment_hash, &failure_code);
+
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash: payment_hash }]);
+ check_added_monitors!(nodes[1], 1);
+
+ let events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let (update_fail_htlc, commitment_signed) = match events[0] {
+ MessageSendEvent::UpdateHTLCs { node_id: _ , updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
+ assert!(update_add_htlcs.is_empty());
+ assert!(update_fulfill_htlcs.is_empty());
+ assert_eq!(update_fail_htlcs.len(), 1);
+ assert!(update_fail_malformed_htlcs.is_empty());
+ assert!(update_fee.is_none());
+ (update_fail_htlcs[0].clone(), commitment_signed)
+ },
+ _ => panic!("Unexpected event"),
+ };
+
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &update_fail_htlc);
+ commitment_signed_dance!(nodes[0], nodes[1], commitment_signed, false, true);
+
+ let failure_data = match failure_code {
+ FailureCode::TemporaryNodeFailure => vec![],
+ FailureCode::RequiredNodeFeatureMissing => vec![],
+ FailureCode::IncorrectOrUnknownPaymentDetails => {
+ let mut htlc_msat_height_data = (payment_amount as u64).to_be_bytes().to_vec();
+ htlc_msat_height_data.extend_from_slice(&CHAN_CONFIRM_DEPTH.to_be_bytes());
+ htlc_msat_height_data
+ }
+ };
+
+ let failure_code = failure_code as u16;
+ let permanent_flag = 0x4000;
+ let permanent_fail = (failure_code & permanent_flag) != 0;
+ expect_payment_failed!(nodes[0], payment_hash, permanent_fail, failure_code, failure_data);
+
+}
+
+#[test]
+fn test_fail_htlc_backwards_with_reason() {
+ do_test_fail_htlc_backwards_with_reason(FailureCode::TemporaryNodeFailure);
+ do_test_fail_htlc_backwards_with_reason(FailureCode::RequiredNodeFeatureMissing);
+ do_test_fail_htlc_backwards_with_reason(FailureCode::IncorrectOrUnknownPaymentDetails);
+}
+
macro_rules! get_phantom_route {
($nodes: expr, $amt: expr, $channel: expr) => {{
- let secp_ctx = Secp256k1::new();
- let phantom_secret = $nodes[1].keys_manager.get_node_secret(Recipient::PhantomNode).unwrap();
- let phantom_pubkey = PublicKey::from_secret_key(&secp_ctx, &phantom_secret);
+ let phantom_pubkey = $nodes[1].keys_manager.get_node_id(Recipient::PhantomNode).unwrap();
let phantom_route_hint = $nodes[1].node.get_phantom_route_hints();
let payment_params = PaymentParameters::from_node_id(phantom_pubkey)
.with_features($nodes[1].node.invoice_features())
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use crate::ln::channelmanager::{HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, PaymentId};
+use crate::ln::channelmanager::{ChannelDetails, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils::HTLCFailReason;
-use crate::routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, RoutePath};
+use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
use crate::util::errors::APIError;
use crate::util::events;
use crate::util::logger::Logger;
+use crate::util::time::Time;
+#[cfg(all(not(feature = "no-std"), test))]
+use crate::util::time::tests::SinceEpoch;
use core::cmp;
+use core::fmt::{self, Display, Formatter};
use core::ops::Deref;
+use core::time::Duration;
+
use crate::prelude::*;
use crate::sync::Mutex;
session_privs: HashSet<[u8; 32]>,
},
Retryable {
+ retry_strategy: Retry,
+ attempts: PaymentAttempts,
+ route_params: Option<RouteParameters>,
session_privs: HashSet<[u8; 32]>,
payment_hash: PaymentHash,
payment_secret: Option<PaymentSecret>,
}
impl PendingOutboundPayment {
+ fn increment_attempts(&mut self) {
+ if let PendingOutboundPayment::Retryable { attempts, .. } = self {
+ attempts.count += 1;
+ }
+ }
+ fn is_retryable_now(&self) -> bool {
+ if let PendingOutboundPayment::Retryable { retry_strategy, attempts, .. } = self {
+ return retry_strategy.is_retryable_now(&attempts)
+ }
+ false
+ }
+ pub fn insert_previously_failed_scid(&mut self, scid: u64) {
+ if let PendingOutboundPayment::Retryable { route_params: Some(params), .. } = self {
+ params.payment_params.previously_failed_channels.push(scid);
+ }
+ }
pub(super) fn is_fulfilled(&self) -> bool {
match self {
PendingOutboundPayment::Fulfilled { .. } => true,
}
}
+/// Strategies available to retry payment path failures.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum Retry {
+ /// Max number of attempts to retry payment.
+ ///
+ /// Note that this is the number of *path* failures, not full payment retries. For multi-path
+ /// payments, if this is less than the total number of paths, we will never even retry all of the
+ /// payment's paths.
+ Attempts(usize),
+ #[cfg(not(feature = "no-std"))]
+ /// Time elapsed before abandoning retries for a payment.
+ Timeout(core::time::Duration),
+}
+
+impl Retry {
+ pub(crate) fn is_retryable_now(&self, attempts: &PaymentAttempts) -> bool {
+ match (self, attempts) {
+ (Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => {
+ max_retry_count > count
+ },
+ #[cfg(all(not(feature = "no-std"), not(test)))]
+ (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
+ *max_duration >= std::time::Instant::now().duration_since(*first_attempted_at),
+ #[cfg(all(not(feature = "no-std"), test))]
+ (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
+ *max_duration >= SinceEpoch::now().duration_since(*first_attempted_at),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+pub(super) fn has_expired(route_params: &RouteParameters) -> bool {
+ if let Some(expiry_time) = route_params.payment_params.expiry_time {
+ if let Ok(elapsed) = std::time::SystemTime::UNIX_EPOCH.elapsed() {
+ return elapsed > core::time::Duration::from_secs(expiry_time)
+ }
+ }
+ false
+}
+
+pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime<ConfiguredTime>;
+
+/// Storing minimal payment attempts information required for determining if a outbound payment can
+/// be retried.
+pub(crate) struct PaymentAttemptsUsingTime<T: Time> {
+ /// This count will be incremented only after the result of the attempt is known. When it's 0,
+ /// it means the result of the first attempt is not known yet.
+ pub(crate) count: usize,
+ /// This field is only used when retry is `Retry::Timeout` which is only build with feature std
+ first_attempted_at: T
+}
+
+#[cfg(not(any(feature = "no-std", test)))]
+type ConfiguredTime = std::time::Instant;
+#[cfg(feature = "no-std")]
+type ConfiguredTime = crate::util::time::Eternity;
+#[cfg(all(not(feature = "no-std"), test))]
+type ConfiguredTime = SinceEpoch;
+
+impl<T: Time> PaymentAttemptsUsingTime<T> {
+ pub(crate) fn new() -> Self {
+ PaymentAttemptsUsingTime {
+ count: 0,
+ first_attempted_at: T::now()
+ }
+ }
+}
+
+impl<T: Time> Display for PaymentAttemptsUsingTime<T> {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+ #[cfg(feature = "no-std")]
+ return write!(f, "attempts: {}", self.count);
+ #[cfg(not(feature = "no-std"))]
+ return write!(
+ f,
+ "attempts: {}, duration: {}s",
+ self.count,
+ T::now().duration_since(self.first_attempted_at).as_secs()
+ );
+ }
+}
+
/// If a payment fails to send, it can be in one of several states. This enum is returned as the
/// Err() type describing which state the payment is in, see the description of individual enum
/// states for more.
}
}
+ pub(super) fn send_payment<R: Deref, ES: Deref, NS: Deref, F>(
+ &self, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, payment_id: PaymentId,
+ retry_strategy: Retry, route_params: RouteParameters, router: &R,
+ first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
+ node_signer: &NS, best_block_height: u32, send_payment_along_path: F
+ ) -> Result<(), PaymentSendFailure>
+ where
+ R::Target: Router,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+ u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+ {
+ self.pay_internal(payment_id, Some((payment_hash, payment_secret, retry_strategy)),
+ route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer,
+ best_block_height, &send_payment_along_path)
+ .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
+ }
+
pub(super) fn send_payment_with_route<ES: Deref, NS: Deref, F>(
&self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
{
- let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, route, entropy_source, best_block_height)?;
- self.send_payment_internal(route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path)
+ let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, route, Retry::Attempts(0), None, entropy_source, best_block_height)?;
+ self.pay_route_internal(route, payment_hash, payment_secret, None, payment_id, None,
+ onion_session_privs, node_signer, best_block_height, &send_payment_along_path)
+ .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
}
pub(super) fn send_spontaneous_payment<ES: Deref, NS: Deref, F>(
None => PaymentPreimage(entropy_source.get_secure_random_bytes()),
};
let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
- let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, entropy_source, best_block_height)?;
+ let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, Retry::Attempts(0), None, entropy_source, best_block_height)?;
- match self.send_payment_internal(route, payment_hash, &None, Some(preimage), payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) {
+ match self.pay_route_internal(route, payment_hash, &None, Some(preimage), payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path) {
Ok(()) => Ok(payment_hash),
- Err(e) => Err(e)
+ Err(e) => {
+ self.remove_outbound_if_all_failed(payment_id, &e);
+ Err(e)
+ }
+ }
+ }
+
+ pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
+ &self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
+ best_block_height: u32, logger: &L, send_payment_along_path: SP,
+ )
+ where
+ R::Target: Router,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ SP: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+ u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+ IH: Fn() -> InFlightHtlcs,
+ FH: Fn() -> Vec<ChannelDetails>,
+ L::Target: Logger,
+ {
+ loop {
+ let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+ let mut retry_id_route_params = None;
+ for (pmt_id, pmt) in outbounds.iter_mut() {
+ if pmt.is_retryable_now() {
+ if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, route_params: Some(params), .. } = pmt {
+ if pending_amt_msat < total_msat {
+ retry_id_route_params = Some((*pmt_id, params.clone()));
+ pmt.increment_attempts();
+ break
+ }
+ }
+ }
+ }
+ if let Some((payment_id, route_params)) = retry_id_route_params {
+ core::mem::drop(outbounds);
+ if let Err(e) = self.pay_internal(payment_id, None, route_params, router, first_hops(), inflight_htlcs(), entropy_source, node_signer, best_block_height, &send_payment_along_path) {
+ log_trace!(logger, "Errored retrying payment: {:?}", e);
+ }
+ } else { break }
+ }
+ }
+
+ fn pay_internal<R: Deref, NS: Deref, ES: Deref, F>(
+ &self, payment_id: PaymentId,
+ initial_send_info: Option<(PaymentHash, &Option<PaymentSecret>, Retry)>,
+ route_params: RouteParameters, router: &R, first_hops: Vec<ChannelDetails>,
+ inflight_htlcs: InFlightHtlcs, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
+ send_payment_along_path: &F
+ ) -> Result<(), PaymentSendFailure>
+ where
+ R::Target: Router,
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+ F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+ u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+ {
+ #[cfg(feature = "std")] {
+ if has_expired(&route_params) {
+ return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
+ err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
+ }))
+ }
+ }
+
+ let route = router.find_route(
+ &node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
+ Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs
+ ).map_err(|e| PaymentSendFailure::ParameterError(APIError::APIMisuseError {
+ err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
+ }))?;
+
+ let res = if let Some((payment_hash, payment_secret, retry_strategy)) = initial_send_info {
+ let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, &route, retry_strategy, Some(route_params.clone()), entropy_source, best_block_height)?;
+ self.pay_route_internal(&route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path)
+ } else {
+ self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path)
+ };
+ match res {
+ Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
+ let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+ if let Some(payment) = outbounds.get_mut(&payment_id) {
+ let retryable = payment.is_retryable_now();
+ if retryable {
+ payment.increment_attempts();
+ } else { return res }
+ } else { return res }
+ core::mem::drop(outbounds);
+ self.pay_internal(payment_id, None, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path)
+ },
+ Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. }) => {
+ let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+ if let Some(payment) = outbounds.get_mut(&payment_id) {
+ let retryable = payment.is_retryable_now();
+ if retryable {
+ payment.increment_attempts();
+ } else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
+ } else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
+ core::mem::drop(outbounds);
+
+ // Some paths were sent, even if we failed to send the full MPP value our recipient may
+ // misbehave and claim the funds, at which point we have to consider the payment sent, so
+ // return `Ok()` here, ignoring any retry errors.
+ let _ = self.pay_internal(payment_id, None, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path);
+ Ok(())
+ },
+ Err(PaymentSendFailure::PartialFailure { failed_paths_retry: None, .. }) => {
+ // This may happen if we send a payment and some paths fail, but only due to a temporary
+ // monitor failure or the like, implying they're really in-flight, but we haven't sent the
+ // initial HTLC-Add messages yet.
+ Ok(())
+ },
+ res => res,
}
}
})),
}
};
- self.send_payment_internal(route, payment_hash, &payment_secret, None, payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height, send_payment_along_path)
+ self.pay_route_internal(route, payment_hash, &payment_secret, None, payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height, &send_payment_along_path)
}
pub(super) fn send_probe<ES: Deref, NS: Deref, F>(
}
let route = Route { paths: vec![hops], payment_params: None };
- let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, entropy_source, best_block_height)?;
+ let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, Retry::Attempts(0), None, entropy_source, best_block_height)?;
- match self.send_payment_internal(&route, payment_hash, &None, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) {
+ match self.pay_route_internal(&route, payment_hash, &None, None, payment_id, None, onion_session_privs, node_signer, best_block_height, &send_payment_along_path) {
Ok(()) => Ok((payment_hash, payment_id)),
- Err(e) => Err(e)
+ Err(e) => {
+ self.remove_outbound_if_all_failed(payment_id, &e);
+ Err(e)
+ }
}
}
#[cfg(test)]
pub(super) fn test_add_new_pending_payment<ES: Deref>(
&self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId,
- route: &Route, entropy_source: &ES, best_block_height: u32
+ route: &Route, retry_strategy: Retry, entropy_source: &ES, best_block_height: u32
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
- self.add_new_pending_payment(payment_hash, payment_secret, payment_id, route, entropy_source, best_block_height)
+ self.add_new_pending_payment(payment_hash, payment_secret, payment_id, route, retry_strategy, None, entropy_source, best_block_height)
}
pub(super) fn add_new_pending_payment<ES: Deref>(
&self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId,
- route: &Route, entropy_source: &ES, best_block_height: u32
+ route: &Route, retry_strategy: Retry, route_params: Option<RouteParameters>,
+ entropy_source: &ES, best_block_height: u32
) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
let mut onion_session_privs = Vec::with_capacity(route.paths.len());
for _ in 0..route.paths.len() {
hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment),
hash_map::Entry::Vacant(entry) => {
let payment = entry.insert(PendingOutboundPayment::Retryable {
+ retry_strategy,
+ attempts: PaymentAttempts::new(),
+ route_params,
session_privs: HashSet::new(),
pending_amt_msat: 0,
pending_fee_msat: Some(0),
}
}
- fn send_payment_internal<NS: Deref, F>(
+ fn pay_route_internal<NS: Deref, F>(
&self, route: &Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, payment_id: PaymentId, recv_value_msat: Option<u64>,
onion_session_privs: Vec<[u8; 32]>, node_signer: &NS, best_block_height: u32,
- send_payment_along_path: F
+ send_payment_along_path: &F
) -> Result<(), PaymentSendFailure>
where
NS::Target: NodeSigner,
} else { None },
})
} else if has_err {
- // If we failed to send any paths, we should remove the new PaymentId from the
- // `pending_outbound_payments` map, as the user isn't expected to `abandon_payment`.
- let removed = self.pending_outbound_payments.lock().unwrap().remove(&payment_id).is_some();
- debug_assert!(removed, "We should always have a pending payment to remove here");
Err(PaymentSendFailure::AllFailedResendSafe(results.drain(..).map(|r| r.unwrap_err()).collect()))
} else {
Ok(())
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
{
- self.send_payment_internal(route, payment_hash, payment_secret, keysend_preimage, payment_id,
+ self.pay_route_internal(route, payment_hash, payment_secret, keysend_preimage, payment_id,
recv_value_msat, onion_session_privs, node_signer, best_block_height,
- send_payment_along_path)
+ &send_payment_along_path)
+ .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
+ }
+
+ // If we failed to send any paths, we should remove the new PaymentId from the
+ // `pending_outbound_payments` map, as the user isn't expected to `abandon_payment`.
+ fn remove_outbound_if_all_failed(&self, payment_id: PaymentId, err: &PaymentSendFailure) {
+ if let &PaymentSendFailure::AllFailedResendSafe(_) = err {
+ let removed = self.pending_outbound_payments.lock().unwrap().remove(&payment_id).is_some();
+ debug_assert!(removed, "We should always have a pending payment to remove here");
+ }
}
pub(super) fn claim_htlc<L: Deref>(
payment_params: &Option<PaymentParameters>, probing_cookie_secret: [u8; 32],
secp_ctx: &Secp256k1<secp256k1::All>, pending_events: &Mutex<Vec<events::Event>>, logger: &L
) where L::Target: Logger {
+ #[cfg(test)]
+ let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
+ #[cfg(not(test))]
+ let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
+
let mut session_priv_bytes = [0; 32];
session_priv_bytes.copy_from_slice(&session_priv[..]);
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
let mut all_paths_failed = false;
let mut full_failure_ev = None;
- if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) {
+ let mut pending_retry_ev = None;
+ let attempts_remaining = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) {
if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) {
log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
return
log_trace!(logger, "Received failure of HTLC with payment_hash {} after payment completion", log_bytes!(payment_hash.0));
return
}
+ let is_retryable_now = payment.get().is_retryable_now();
+ if let Some(scid) = short_channel_id {
+ payment.get_mut().insert_previously_failed_scid(scid);
+ }
if payment.get().remaining_parts() == 0 {
all_paths_failed = true;
if payment.get().abandoned() {
payment.remove();
}
}
+ is_retryable_now
} else {
log_trace!(logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
return
- }
+ };
+ core::mem::drop(outbounds);
let mut retry = if let Some(payment_params_data) = payment_params {
let path_last_hop = path.last().expect("Outbound payments must have had a valid path");
Some(RouteParameters {
log_trace!(logger, "Failing outbound payment HTLC with payment_hash {}", log_bytes!(payment_hash.0));
let path_failure = {
- #[cfg(test)]
- let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
- #[cfg(not(test))]
- let (network_update, short_channel_id, payment_retryable, _, _) = onion_error.decode_onion_failure(secp_ctx, logger, &source);
-
if payment_is_probe(payment_hash, &payment_id, probing_cookie_secret) {
if !payment_retryable {
events::Event::ProbeSuccessful {
if let Some(scid) = short_channel_id {
retry.as_mut().map(|r| r.payment_params.previously_failed_channels.push(scid));
}
+ if payment_retryable && attempts_remaining && retry.is_some() {
+ debug_assert!(full_failure_ev.is_none());
+ pending_retry_ev = Some(events::Event::PendingHTLCsForwardable {
+ time_forwardable: Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS),
+ });
+ }
events::Event::PaymentPathFailed {
payment_id: Some(*payment_id),
payment_hash: payment_hash.clone(),
let mut pending_events = pending_events.lock().unwrap();
pending_events.push(path_failure);
if let Some(ev) = full_failure_ev { pending_events.push(ev); }
+ if let Some(ev) = pending_retry_ev { pending_events.push(ev); }
}
pub(super) fn abandon_payment(&self, payment_id: PaymentId) -> Option<events::Event> {
(0, session_privs, required),
(1, pending_fee_msat, option),
(2, payment_hash, required),
+ (not_written, retry_strategy, (static_value, Retry::Attempts(0))),
(4, payment_secret, option),
+ (not_written, attempts, (static_value, PaymentAttempts::new())),
(6, total_msat, required),
+ (not_written, route_params, (static_value, None)),
(8, pending_amt_msat, required),
(10, starting_block_height, required),
},
(2, payment_hash, required),
},
);
+
+#[cfg(test)]
+mod tests {
+ use bitcoin::blockdata::constants::genesis_block;
+ use bitcoin::network::constants::Network;
+ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
+
+ use crate::ln::PaymentHash;
+ use crate::ln::channelmanager::{PaymentId, PaymentSendFailure};
+ use crate::ln::msgs::{ErrorAction, LightningError};
+ use crate::ln::outbound_payment::{OutboundPayments, Retry};
+ use crate::routing::gossip::NetworkGraph;
+ use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteParameters};
+ use crate::sync::Arc;
+ use crate::util::errors::APIError;
+ use crate::util::test_utils;
+
+ #[test]
+ #[cfg(feature = "std")]
+ fn fails_paying_after_expiration() {
+ do_fails_paying_after_expiration(false);
+ do_fails_paying_after_expiration(true);
+ }
+ #[cfg(feature = "std")]
+ fn do_fails_paying_after_expiration(on_retry: bool) {
+ let outbound_payments = OutboundPayments::new();
+ let logger = test_utils::TestLogger::new();
+ let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
+ let network_graph = Arc::new(NetworkGraph::new(genesis_hash, &logger));
+ let router = test_utils::TestRouter::new(network_graph);
+ let secp_ctx = Secp256k1::new();
+ let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
+
+ let past_expiry_time = std::time::SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() - 2;
+ let payment_params = PaymentParameters::from_node_id(
+ PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()))
+ .with_expiry_time(past_expiry_time);
+ let expired_route_params = RouteParameters {
+ payment_params,
+ final_value_msat: 0,
+ final_cltv_expiry_delta: 0,
+ };
+ let err = if on_retry {
+ outbound_payments.pay_internal(
+ PaymentId([0; 32]), None, expired_route_params, &&router, vec![], InFlightHtlcs::new(),
+ &&keys_manager, &&keys_manager, 0, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+ } else {
+ outbound_payments.send_payment(
+ PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), expired_route_params,
+ &&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+ };
+ if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
+ assert!(err.contains("Invoice expired"));
+ } else { panic!("Unexpected error"); }
+ }
+
+ #[test]
+ fn find_route_error() {
+ do_find_route_error(false);
+ do_find_route_error(true);
+ }
+ fn do_find_route_error(on_retry: bool) {
+ let outbound_payments = OutboundPayments::new();
+ let logger = test_utils::TestLogger::new();
+ let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
+ let network_graph = Arc::new(NetworkGraph::new(genesis_hash, &logger));
+ let router = test_utils::TestRouter::new(network_graph);
+ let secp_ctx = Secp256k1::new();
+ let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
+
+ router.expect_find_route(Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }));
+
+ let payment_params = PaymentParameters::from_node_id(
+ PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()));
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: 0,
+ final_cltv_expiry_delta: 0,
+ };
+ let err = if on_retry {
+ outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), None, PaymentId([0; 32]),
+ &Route { paths: vec![], payment_params: None }, Retry::Attempts(1), Some(route_params.clone()),
+ &&keys_manager, 0).unwrap();
+ outbound_payments.pay_internal(
+ PaymentId([0; 32]), None, route_params, &&router, vec![], InFlightHtlcs::new(),
+ &&keys_manager, &&keys_manager, 0, &|_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+ } else {
+ outbound_payments.send_payment(
+ PaymentHash([0; 32]), &None, PaymentId([0; 32]), Retry::Attempts(0), route_params,
+ &&router, vec![], InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, |_, _, _, _, _, _, _, _, _| Ok(())).unwrap_err()
+ };
+ if let PaymentSendFailure::ParameterError(APIError::APIMisuseError { err }) = err {
+ assert!(err.contains("Failed to find a route"));
+ } else { panic!("Unexpected error"); }
+ }
+}
use crate::chain::transaction::OutPoint;
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
+use crate::ln::features::InvoiceFeatures;
use crate::ln::msgs;
use crate::ln::msgs::ChannelMessageHandler;
+use crate::ln::outbound_payment::Retry;
use crate::routing::gossip::RoutingFees;
-use crate::routing::router::{get_route, PaymentParameters, RouteHint, RouteHintHop, RouteParameters};
+use crate::routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RouteParameters};
use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
use crate::util::test_utils;
use crate::util::errors::APIError;
use crate::ln::functional_test_utils::*;
use crate::routing::gossip::NodeId;
+#[cfg(feature = "std")]
+use {
+ crate::util::time::tests::SinceEpoch,
+ std::time::{SystemTime, Duration}
+};
#[test]
fn retry_single_path_payment() {
let amt_msat = 60_000;
let expiry_secs = 60 * 60;
- let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(amt_msat), expiry_secs).unwrap();
+ let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(amt_msat), expiry_secs, None).unwrap();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
.with_features(nodes[1].node.invoice_features());
route_params.final_cltv_expiry_delta, nodes[0].logger, &scorer, &random_seed_bytes
).unwrap();
- let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60).unwrap();
+ let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
let payment_event = {
{
assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {} not found", log_bytes!(intercept_id.0)) });
}
}
+
+#[derive(PartialEq)]
+enum AutoRetry {
+ Success,
+ FailAttempts,
+ FailTimeout,
+ FailOnRestart,
+}
+
+#[test]
+fn automatic_retries() {
+ do_automatic_retries(AutoRetry::Success);
+ do_automatic_retries(AutoRetry::FailAttempts);
+ do_automatic_retries(AutoRetry::FailTimeout);
+ do_automatic_retries(AutoRetry::FailOnRestart);
+}
+fn do_automatic_retries(test: AutoRetry) {
+ // Test basic automatic payment retries in ChannelManager. See individual `test` variant comments
+ // below.
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+
+ let persister;
+ let new_chain_monitor;
+ let node_0_deserialized;
+
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+ let channel_id_1 = create_announced_chan_between_nodes(&nodes, 0, 1).2;
+ let channel_id_2 = create_announced_chan_between_nodes(&nodes, 2, 1).2;
+
+ // Marshall data to send the payment
+ #[cfg(feature = "std")]
+ let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
+ #[cfg(not(feature = "std"))]
+ let payment_expiry_secs = 60 * 60;
+ let amt_msat = 1000;
+ let mut invoice_features = InvoiceFeatures::empty();
+ invoice_features.set_variable_length_onion_required();
+ invoice_features.set_payment_secret_required();
+ invoice_features.set_basic_mpp_optional();
+ let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id())
+ .with_expiry_time(payment_expiry_secs as u64)
+ .with_features(invoice_features);
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ final_cltv_expiry_delta: TEST_FINAL_CLTV,
+ };
+ 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 {
+ ($failing_channel_id: expr, $expect_pending_htlcs_forwardable: expr) => {
+ // Send a payment attempt that fails due to lack of liquidity on the second hop
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1],
+ vec![HTLCDestination::NextHopChannel {
+ node_id: Some(nodes[2].node.get_our_node_id()),
+ channel_id: $failing_channel_id,
+ }]);
+ nodes[1].node.process_pending_htlc_forwards();
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(&nodes[1], 1);
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the attempt fails and a new PendingHTLCsForwardable event is generated for the retry
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ match events[0] {
+ Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => {
+ assert_eq!(payment_hash, ev_payment_hash);
+ assert_eq!(payment_failed_permanently, false);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ if $expect_pending_htlcs_forwardable {
+ assert_eq!(events.len(), 2);
+ match events[1] {
+ Event::PendingHTLCsForwardable { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+ } else { assert_eq!(events.len(), 1) }
+ }
+ }
+
+ if test == AutoRetry::Success {
+ // Test that we can succeed on the first retry.
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
+ pass_failed_attempt_with_retry_along_path!(channel_id_2, true);
+
+ // Open a new channel with liquidity on the second hop so we can find a route for the retry
+ // attempt, since the initial second hop channel will be excluded from pathfinding
+ create_announced_chan_between_nodes(&nodes, 1, 2);
+
+ // We retry payments in `process_pending_htlc_forwards`
+ nodes[0].node.process_pending_htlc_forwards();
+ check_added_monitors!(nodes[0], 1);
+ let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 1);
+ pass_along_path(&nodes[0], &[&nodes[1], &nodes[2]], amt_msat, payment_hash, Some(payment_secret), msg_events.pop().unwrap(), true, None);
+ claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
+ } else if test == AutoRetry::FailAttempts {
+ // Ensure ChannelManager will not retry a payment if it has run out of payment attempts.
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
+ pass_failed_attempt_with_retry_along_path!(channel_id_2, true);
+
+ // Open a new channel with no liquidity on the second hop so we can find a (bad) route for
+ // the retry attempt, since the initial second hop channel will be excluded from pathfinding
+ let channel_id_3 = create_announced_chan_between_nodes(&nodes, 2, 1).2;
+
+ // We retry payments in `process_pending_htlc_forwards`
+ nodes[0].node.process_pending_htlc_forwards();
+ pass_failed_attempt_with_retry_along_path!(channel_id_3, false);
+
+ // Ensure we won't retry a second time.
+ nodes[0].node.process_pending_htlc_forwards();
+ let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 0);
+
+ nodes[0].node.abandon_payment(PaymentId(payment_hash.0));
+ let events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id } => {
+ assert_eq!(payment_hash, *ev_payment_hash);
+ assert_eq!(PaymentId(payment_hash.0), *ev_payment_id);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ } else if test == AutoRetry::FailTimeout {
+ #[cfg(not(feature = "no-std"))] {
+ // Ensure ChannelManager will not retry a payment if it times out due to Retry::Timeout.
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Timeout(Duration::from_secs(60))).unwrap();
+ pass_failed_attempt_with_retry_along_path!(channel_id_2, true);
+
+ // Advance the time so the second attempt fails due to timeout.
+ SinceEpoch::advance(Duration::from_secs(61));
+
+ // Make sure we don't retry again.
+ nodes[0].node.process_pending_htlc_forwards();
+ let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 0);
+
+ nodes[0].node.abandon_payment(PaymentId(payment_hash.0));
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id } => {
+ assert_eq!(payment_hash, *ev_payment_hash);
+ assert_eq!(PaymentId(payment_hash.0), *ev_payment_id);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ }
+ } else if test == AutoRetry::FailOnRestart {
+ // Ensure ChannelManager will not retry a payment after restart, even if there were retry
+ // attempts remaining prior to restart.
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(2)).unwrap();
+ pass_failed_attempt_with_retry_along_path!(channel_id_2, true);
+
+ // Open a new channel with no liquidity on the second hop so we can find a (bad) route for
+ // the retry attempt, since the initial second hop channel will be excluded from pathfinding
+ let channel_id_3 = create_announced_chan_between_nodes(&nodes, 2, 1).2;
+
+ // Ensure the first retry attempt fails, with 1 retry attempt remaining
+ nodes[0].node.process_pending_htlc_forwards();
+ pass_failed_attempt_with_retry_along_path!(channel_id_3, true);
+
+ // Restart the node and ensure that ChannelManager does not use its remaining retry attempt
+ let node_encoded = nodes[0].node.encode();
+ let chan_1_monitor_serialized = get_monitor!(nodes[0], channel_id_1).encode();
+ reload_node!(nodes[0], node_encoded, &[&chan_1_monitor_serialized], persister, new_chain_monitor, node_0_deserialized);
+
+ // Make sure we don't retry again.
+ nodes[0].node.process_pending_htlc_forwards();
+ let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 0);
+
+ nodes[0].node.abandon_payment(PaymentId(payment_hash.0));
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id } => {
+ assert_eq!(payment_hash, *ev_payment_hash);
+ assert_eq!(PaymentId(payment_hash.0), *ev_payment_id);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ }
+}
+
+#[test]
+fn auto_retry_partial_failure() {
+ // Test that we'll retry appropriately on send partial failure and retry partial failure.
+ 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 mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let chan_1_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+ let chan_2_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+ let chan_3_id = create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+
+ // Marshall data to send the payment
+ let amt_msat = 20_000;
+ let (_, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
+ #[cfg(feature = "std")]
+ let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
+ #[cfg(not(feature = "std"))]
+ let payment_expiry_secs = 60 * 60;
+ let mut invoice_features = InvoiceFeatures::empty();
+ invoice_features.set_variable_length_onion_required();
+ invoice_features.set_payment_secret_required();
+ invoice_features.set_basic_mpp_optional();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
+ .with_expiry_time(payment_expiry_secs as u64)
+ .with_features(invoice_features);
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ final_cltv_expiry_delta: TEST_FINAL_CLTV,
+ };
+
+ // 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.
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
+ // Ensure third monitor update (for the retry1's path1 over chan_1) succeeds, but the fourth (for
+ // the retry1's path2 over chan_3) fails, and monitor updates succeed after that.
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::Completed);
+
+ // Configure the initial send, retry1 and retry2's paths.
+ let send_route = Route {
+ paths: vec![
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_1_id,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: amt_msat / 2,
+ cltv_expiry_delta: 100,
+ }],
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_2_id,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: amt_msat / 2,
+ cltv_expiry_delta: 100,
+ }],
+ ],
+ payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())),
+ };
+ let retry_1_route = Route {
+ paths: vec![
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_1_id,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: amt_msat / 4,
+ cltv_expiry_delta: 100,
+ }],
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_3_id,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: amt_msat / 4,
+ cltv_expiry_delta: 100,
+ }],
+ ],
+ payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())),
+ };
+ let retry_2_route = Route {
+ paths: vec![
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_1_id,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: amt_msat / 4,
+ cltv_expiry_delta: 100,
+ }],
+ ],
+ payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())),
+ };
+ nodes[0].router.expect_find_route(Ok(send_route));
+ nodes[0].router.expect_find_route(Ok(retry_1_route));
+ nodes[0].router.expect_find_route(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_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(3)).unwrap();
+ let closed_chan_events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(closed_chan_events.len(), 2);
+ match closed_chan_events[0] {
+ Event::ChannelClosed { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+ match closed_chan_events[1] {
+ Event::ChannelClosed { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+
+ // Pass the first part of the payment along the path.
+ check_added_monitors!(nodes[0], 5); // three outbound channel updates succeeded, two permanently failed
+ let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+
+ // First message is the first update_add, remaining messages are broadcasting channel updates and
+ // errors for the permfailed channels
+ assert_eq!(msg_events.len(), 5);
+ let mut payment_event = SendEvent::from_event(msg_events.remove(0));
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &payment_event.commitment_msg);
+ check_added_monitors!(nodes[1], 1);
+ let (bs_first_raa, bs_first_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_first_raa);
+ check_added_monitors!(nodes[0], 1);
+ let as_second_htlc_updates = SendEvent::from_node(&nodes[0]);
+
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_first_cs);
+ check_added_monitors!(nodes[0], 1);
+ let as_first_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_first_raa);
+ check_added_monitors!(nodes[1], 1);
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &as_second_htlc_updates.msgs[0]);
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &as_second_htlc_updates.msgs[1]);
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &as_second_htlc_updates.commitment_msg);
+ check_added_monitors!(nodes[1], 1);
+ let (bs_second_raa, bs_second_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_second_raa);
+ check_added_monitors!(nodes[0], 1);
+
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_second_cs);
+ check_added_monitors!(nodes[0], 1);
+ let as_second_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_second_raa);
+ check_added_monitors!(nodes[1], 1);
+
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_payment_claimable!(nodes[1], payment_hash, payment_secret, amt_msat);
+ nodes[1].node.claim_funds(payment_preimage);
+ expect_payment_claimed!(nodes[1], payment_hash, amt_msat);
+ let bs_claim_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ assert_eq!(bs_claim_update.update_fulfill_htlcs.len(), 1);
+
+ nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_claim_update.update_fulfill_htlcs[0]);
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_claim_update.commitment_signed);
+ check_added_monitors!(nodes[0], 1);
+ let (as_third_raa, as_third_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_third_raa);
+ check_added_monitors!(nodes[1], 4);
+ let bs_second_claim_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &as_third_cs);
+ check_added_monitors!(nodes[1], 1);
+ let bs_third_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_third_raa);
+ check_added_monitors!(nodes[0], 1);
+
+ nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_second_claim_update.update_fulfill_htlcs[0]);
+ nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_second_claim_update.update_fulfill_htlcs[1]);
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_second_claim_update.commitment_signed);
+ check_added_monitors!(nodes[0], 1);
+ let (as_fourth_raa, as_fourth_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_fourth_raa);
+ check_added_monitors!(nodes[1], 1);
+
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &as_fourth_cs);
+ check_added_monitors!(nodes[1], 1);
+ let bs_second_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_second_raa);
+ check_added_monitors!(nodes[0], 1);
+ expect_payment_sent!(nodes[0], payment_preimage);
+}
+
+#[test]
+fn auto_retry_zero_attempts_send_error() {
+ 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 mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+ create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+
+ // Marshall data to send the payment
+ let amt_msat = 20_000;
+ let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
+ #[cfg(feature = "std")]
+ let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
+ #[cfg(not(feature = "std"))]
+ let payment_expiry_secs = 60 * 60;
+ let mut invoice_features = InvoiceFeatures::empty();
+ invoice_features.set_variable_length_onion_required();
+ invoice_features.set_payment_secret_required();
+ invoice_features.set_basic_mpp_optional();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
+ .with_expiry_time(payment_expiry_secs as u64)
+ .with_features(invoice_features);
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ final_cltv_expiry_delta: TEST_FINAL_CLTV,
+ };
+
+ chanmon_cfgs[0].persister.set_update_ret(ChannelMonitorUpdateStatus::PermanentFailure);
+ let err = nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap_err();
+ if let PaymentSendFailure::AllFailedResendSafe(_) = err {
+ } else { panic!("Unexpected error"); }
+ assert_eq!(nodes[0].node.get_and_clear_pending_msg_events().len(), 2); // channel close messages
+ assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 1); // channel close event
+ check_added_monitors!(nodes[0], 2);
+}
+
+#[test]
+fn fails_paying_after_rejected_by_payee() {
+ 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 mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
+
+ // Marshall data to send the payment
+ let amt_msat = 20_000;
+ let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
+ #[cfg(feature = "std")]
+ let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
+ #[cfg(not(feature = "std"))]
+ let payment_expiry_secs = 60 * 60;
+ let mut invoice_features = InvoiceFeatures::empty();
+ invoice_features.set_variable_length_onion_required();
+ invoice_features.set_payment_secret_required();
+ invoice_features.set_basic_mpp_optional();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
+ .with_expiry_time(payment_expiry_secs as u64)
+ .with_features(invoice_features);
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ final_cltv_expiry_delta: TEST_FINAL_CLTV,
+ };
+
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let mut events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), 1);
+ let mut payment_event = SendEvent::from_event(events.pop().unwrap());
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ check_added_monitors!(nodes[1], 0);
+ commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ expect_payment_claimable!(&nodes[1], payment_hash, payment_secret, amt_msat);
+
+ nodes[1].node.fail_htlc_backwards(&payment_hash);
+ expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], [HTLCDestination::FailedPayment { payment_hash }]);
+ pass_failed_payment_back(&nodes[0], &[&[&nodes[1]]], false, payment_hash);
+}
+
+#[test]
+fn retry_multi_path_single_failed_payment() {
+ // Tests that we can/will retry after a single path of an MPP payment failed immediately
+ 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, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ let chans = nodes[0].node.list_usable_channels();
+ let mut route = Route {
+ paths: vec![
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chans[0].short_channel_id.unwrap(),
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: 10_000,
+ cltv_expiry_delta: 100,
+ }],
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chans[1].short_channel_id.unwrap(),
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
+ cltv_expiry_delta: 100,
+ }],
+ ],
+ payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())),
+ };
+ nodes[0].router.expect_find_route(Ok(route.clone()));
+ // On retry, split the payment across both channels.
+ route.paths[0][0].fee_msat = 50_000_001;
+ route.paths[1][0].fee_msat = 50_000_000;
+ nodes[0].router.expect_find_route(Ok(route.clone()));
+
+ let amt_msat = 100_010_000;
+ let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
+ #[cfg(feature = "std")]
+ let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
+ #[cfg(not(feature = "std"))]
+ let payment_expiry_secs = 60 * 60;
+ let mut invoice_features = InvoiceFeatures::empty();
+ invoice_features.set_variable_length_onion_required();
+ invoice_features.set_payment_secret_required();
+ invoice_features.set_basic_mpp_optional();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
+ .with_expiry_time(payment_expiry_secs as u64)
+ .with_features(invoice_features);
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ final_cltv_expiry_delta: TEST_FINAL_CLTV,
+ };
+
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
+ let htlc_msgs = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(htlc_msgs.len(), 2);
+ check_added_monitors!(nodes[0], 2);
+}
+
+#[test]
+fn immediate_retry_on_failure() {
+ // Tests that we can/will retry immediately after a failure
+ 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, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
+ let chans = nodes[0].node.list_usable_channels();
+ let mut route = Route {
+ paths: vec![
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chans[0].short_channel_id.unwrap(),
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: 100_000_001, // Our default max-HTLC-value is 10% of the channel value, which this is one more than
+ cltv_expiry_delta: 100,
+ }],
+ ],
+ payment_params: Some(PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())),
+ };
+ nodes[0].router.expect_find_route(Ok(route.clone()));
+ // On retry, split the payment across both channels.
+ route.paths.push(route.paths[0].clone());
+ route.paths[0][0].short_channel_id = chans[1].short_channel_id.unwrap();
+ route.paths[0][0].fee_msat = 50_000_000;
+ route.paths[1][0].fee_msat = 50_000_001;
+ nodes[0].router.expect_find_route(Ok(route.clone()));
+
+ let amt_msat = 100_010_000;
+ let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
+ #[cfg(feature = "std")]
+ let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
+ #[cfg(not(feature = "std"))]
+ let payment_expiry_secs = 60 * 60;
+ let mut invoice_features = InvoiceFeatures::empty();
+ invoice_features.set_variable_length_onion_required();
+ invoice_features.set_payment_secret_required();
+ invoice_features.set_basic_mpp_optional();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
+ .with_expiry_time(payment_expiry_secs as u64)
+ .with_features(invoice_features);
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ final_cltv_expiry_delta: TEST_FINAL_CLTV,
+ };
+
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
+ let htlc_msgs = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(htlc_msgs.len(), 2);
+ check_added_monitors!(nodes[0], 2);
+}
+
+#[test]
+fn no_extra_retries_on_back_to_back_fail() {
+ // In a previous release, we had a race where we may exceed the payment retry count if we
+ // get two failures in a row with the second having `all_paths_failed` set.
+ // Generally, when we give up trying to retry a payment, we don't know for sure what the
+ // current state of the ChannelManager event queue is. Specifically, we cannot be sure that
+ // there are not multiple additional `PaymentPathFailed` or even `PaymentSent` events
+ // pending which we will see later. Thus, when we previously removed the retry tracking map
+ // entry after a `all_paths_failed` `PaymentPathFailed` event, we may have dropped the
+ // retry entry even though more events for the same payment were still pending. This led to
+ // us retrying a payment again even though we'd already given up on it.
+ //
+ // We now have a separate event - `PaymentFailed` which indicates no HTLCs remain and which
+ // is used to remove the payment retry counter entries instead. This tests for the specific
+ // excess-retry case while also testing `PaymentFailed` generation.
+
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
+ let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let chan_1_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0.contents.short_channel_id;
+ let chan_2_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0.contents.short_channel_id;
+
+ let mut route = Route {
+ paths: vec![
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_1_scid,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: 0,
+ cltv_expiry_delta: 100,
+ }, RouteHop {
+ pubkey: nodes[2].node.get_our_node_id(),
+ node_features: nodes[2].node.node_features(),
+ short_channel_id: chan_2_scid,
+ channel_features: nodes[2].node.channel_features(),
+ fee_msat: 100_000_000,
+ cltv_expiry_delta: 100,
+ }],
+ vec![RouteHop {
+ pubkey: nodes[1].node.get_our_node_id(),
+ node_features: nodes[1].node.node_features(),
+ short_channel_id: chan_1_scid,
+ channel_features: nodes[1].node.channel_features(),
+ fee_msat: 0,
+ cltv_expiry_delta: 100,
+ }, RouteHop {
+ pubkey: nodes[2].node.get_our_node_id(),
+ node_features: nodes[2].node.node_features(),
+ short_channel_id: chan_2_scid,
+ channel_features: nodes[2].node.channel_features(),
+ fee_msat: 100_000_000,
+ cltv_expiry_delta: 100,
+ }]
+ ],
+ payment_params: Some(PaymentParameters::from_node_id(nodes[2].node.get_our_node_id())),
+ };
+ nodes[0].router.expect_find_route(Ok(route.clone()));
+ // On retry, we'll only be asked for one path
+ route.paths.remove(1);
+ nodes[0].router.expect_find_route(Ok(route.clone()));
+
+ let amt_msat = 100_010_000;
+ let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
+ #[cfg(feature = "std")]
+ let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
+ #[cfg(not(feature = "std"))]
+ let payment_expiry_secs = 60 * 60;
+ let mut invoice_features = InvoiceFeatures::empty();
+ invoice_features.set_variable_length_onion_required();
+ invoice_features.set_payment_secret_required();
+ invoice_features.set_basic_mpp_optional();
+ let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
+ .with_expiry_time(payment_expiry_secs as u64)
+ .with_features(invoice_features);
+ let route_params = RouteParameters {
+ payment_params,
+ final_value_msat: amt_msat,
+ final_cltv_expiry_delta: TEST_FINAL_CLTV,
+ };
+
+ nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
+ let htlc_updates = SendEvent::from_node(&nodes[0]);
+ check_added_monitors!(nodes[0], 1);
+ assert_eq!(htlc_updates.msgs.len(), 1);
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &htlc_updates.msgs[0]);
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &htlc_updates.commitment_msg);
+ check_added_monitors!(nodes[1], 1);
+ let (bs_first_raa, bs_first_cs) = get_revoke_commit_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_first_raa);
+ check_added_monitors!(nodes[0], 1);
+ let second_htlc_updates = SendEvent::from_node(&nodes[0]);
+
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_first_cs);
+ check_added_monitors!(nodes[0], 1);
+ let as_first_raa = get_event_msg!(nodes[0], MessageSendEvent::SendRevokeAndACK, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &second_htlc_updates.msgs[0]);
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &second_htlc_updates.commitment_msg);
+ check_added_monitors!(nodes[1], 1);
+ let bs_second_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_first_raa);
+ check_added_monitors!(nodes[1], 1);
+ let bs_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_second_raa);
+ check_added_monitors!(nodes[0], 1);
+
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[0]);
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_fail_update.commitment_signed);
+ check_added_monitors!(nodes[0], 1);
+ let (as_second_raa, as_third_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_second_raa);
+ check_added_monitors!(nodes[1], 1);
+ let bs_second_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &as_third_cs);
+ check_added_monitors!(nodes[1], 1);
+ let bs_third_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &bs_second_fail_update.update_fail_htlcs[0]);
+ nodes[0].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bs_second_fail_update.commitment_signed);
+ check_added_monitors!(nodes[0], 1);
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_third_raa);
+ check_added_monitors!(nodes[0], 1);
+ let (as_third_raa, as_fourth_cs) = get_revoke_commit_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_revoke_and_ack(&nodes[0].node.get_our_node_id(), &as_third_raa);
+ check_added_monitors!(nodes[1], 1);
+ nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &as_fourth_cs);
+ check_added_monitors!(nodes[1], 1);
+ let bs_fourth_raa = get_event_msg!(nodes[1], MessageSendEvent::SendRevokeAndACK, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bs_fourth_raa);
+ check_added_monitors!(nodes[0], 1);
+
+ // At this point A has sent two HTLCs which both failed due to lack of fee. It now has two
+ // pending `PaymentPathFailed` events, one with `all_paths_failed` unset, and the second
+ // with it set. The first event will use up the only retry we are allowed, with the second
+ // `PaymentPathFailed` being passed up to the user (us, in this case). Previously, we'd
+ // treated this as "HTLC complete" and dropped the retry counter, causing us to retry again
+ // if the final HTLC failed.
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 4);
+ match events[0] {
+ Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => {
+ assert_eq!(payment_hash, ev_payment_hash);
+ assert_eq!(payment_failed_permanently, false);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ match events[1] {
+ Event::PendingHTLCsForwardable { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+ match events[2] {
+ Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => {
+ assert_eq!(payment_hash, ev_payment_hash);
+ assert_eq!(payment_failed_permanently, false);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ match events[3] {
+ Event::PendingHTLCsForwardable { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+
+ nodes[0].node.process_pending_htlc_forwards();
+ let retry_htlc_updates = SendEvent::from_node(&nodes[0]);
+ check_added_monitors!(nodes[0], 1);
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &retry_htlc_updates.msgs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], &retry_htlc_updates.commitment_msg, false, true);
+ let bs_fail_update = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &bs_fail_update.update_fail_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[1], &bs_fail_update.commitment_signed, false, true);
+
+ let mut events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => {
+ assert_eq!(payment_hash, ev_payment_hash);
+ assert_eq!(payment_failed_permanently, false);
+ },
+ _ => panic!("Unexpected event"),
+ }
+ nodes[0].node.abandon_payment(PaymentId(payment_hash.0));
+ events = nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id } => {
+ assert_eq!(payment_hash, *ev_payment_hash);
+ assert_eq!(PaymentId(payment_hash.0), *ev_payment_id);
+ },
+ _ => panic!("Unexpected event"),
+ }
+}
use crate::prelude::*;
+use crate::chain::keysinterface::{NodeSigner, Recipient};
use crate::ln::msgs::LightningError;
use crate::ln::msgs;
use crate::ln::wire;
use crate::util::ser::VecWriter;
use bitcoin::hashes::hex::ToHex;
+use core::ops::Deref;
+
/// Maximum Lightning message data length according to
/// [BOLT-8](https://github.com/lightning/bolts/blob/v1.0/08-transport.md#lightning-message-specification)
/// and [BOLT-1](https://github.com/lightning/bolts/blob/master/01-messaging.md#lightning-message-format):
// Sha256(NOISE_CK || "lightning")
const NOISE_H: [u8; 32] = [0xd1, 0xfb, 0xf6, 0xde, 0xe4, 0xf6, 0x86, 0xf1, 0x32, 0xfd, 0x70, 0x2c, 0x4a, 0xbf, 0x8f, 0xba, 0x4b, 0xb4, 0x20, 0xd8, 0x9d, 0x2a, 0x04, 0x8a, 0x3c, 0x4f, 0x4c, 0x09, 0x2e, 0x37, 0xb6, 0x76];
+enum NoiseSecretKey<'a, 'b, NS: Deref> where NS::Target: NodeSigner {
+ InMemory(&'a SecretKey),
+ NodeSigner(&'b NS)
+}
+
pub enum NextNoiseStep {
ActOne,
ActTwo,
}
}
- pub fn new_inbound<C: secp256k1::Signing>(our_node_secret: &SecretKey, secp_ctx: &Secp256k1<C>) -> PeerChannelEncryptor {
+ pub fn new_inbound<NS: Deref>(node_signer: &NS) -> PeerChannelEncryptor where NS::Target: NodeSigner {
let mut sha = Sha256::engine();
sha.input(&NOISE_H);
- let our_node_id = PublicKey::from_secret_key(&secp_ctx, our_node_secret);
+ let our_node_id = node_signer.get_node_id(Recipient::Node).unwrap();
sha.input(&our_node_id.serialize()[..]);
let h = Sha256::from_engine(sha).into_inner();
}
#[inline]
- fn inbound_noise_act(state: &mut BidirectionalNoiseState, act: &[u8], our_key: &SecretKey) -> Result<(PublicKey, [u8; 32]), LightningError> {
+ fn inbound_noise_act<'a, 'b, NS: Deref>(
+ state: &mut BidirectionalNoiseState, act: &[u8], secret_key: NoiseSecretKey<'a, 'b, NS>
+ ) -> Result<(PublicKey, [u8; 32]), LightningError> where NS::Target: NodeSigner {
assert_eq!(act.len(), 50);
if act[0] != 0 {
sha.input(&their_pub.serialize()[..]);
state.h = Sha256::from_engine(sha).into_inner();
- let ss = SharedSecret::new(&their_pub, &our_key);
+ let ss = match secret_key {
+ NoiseSecretKey::InMemory(secret_key) => SharedSecret::new(&their_pub, secret_key),
+ NoiseSecretKey::NodeSigner(node_signer) => node_signer
+ .ecdh(Recipient::Node, &their_pub, None)
+ .map_err(|_| LightningError {
+ err: "Failed to derive shared secret".to_owned(),
+ action: msgs::ErrorAction::DisconnectPeer { msg: None }
+ })?,
+ };
let temp_k = PeerChannelEncryptor::hkdf(state, ss);
let mut dec = [0; 0];
}
}
- pub fn process_act_one_with_keys<C: secp256k1::Signing>(
- &mut self, act_one: &[u8], our_node_secret: &SecretKey, our_ephemeral: SecretKey, secp_ctx: &Secp256k1<C>)
- -> Result<[u8; 50], LightningError> {
+ pub fn process_act_one_with_keys<C: secp256k1::Signing, NS: Deref>(
+ &mut self, act_one: &[u8], node_signer: &NS, our_ephemeral: SecretKey, secp_ctx: &Secp256k1<C>)
+ -> Result<[u8; 50], LightningError> where NS::Target: NodeSigner {
assert_eq!(act_one.len(), 50);
match self.noise_state {
panic!("Requested act at wrong step");
}
- let (their_pub, _) = PeerChannelEncryptor::inbound_noise_act(bidirectional_state, act_one, &our_node_secret)?;
+ let (their_pub, _) = PeerChannelEncryptor::inbound_noise_act(bidirectional_state, act_one, NoiseSecretKey::NodeSigner(node_signer))?;
ie.get_or_insert(their_pub);
re.get_or_insert(our_ephemeral);
}
}
- pub fn process_act_two<C: secp256k1::Signing>(
- &mut self, act_two: &[u8], our_node_secret: &SecretKey, secp_ctx: &Secp256k1<C>)
- -> Result<([u8; 66], PublicKey), LightningError> {
+ pub fn process_act_two<NS: Deref>(
+ &mut self, act_two: &[u8], node_signer: &NS)
+ -> Result<([u8; 66], PublicKey), LightningError> where NS::Target: NodeSigner {
assert_eq!(act_two.len(), 50);
let final_hkdf;
panic!("Requested act at wrong step");
}
- let (re, temp_k2) = PeerChannelEncryptor::inbound_noise_act(bidirectional_state, act_two, &ie)?;
+ let (re, temp_k2) = PeerChannelEncryptor::inbound_noise_act(bidirectional_state, act_two, NoiseSecretKey::<NS>::InMemory(&ie))?;
let mut res = [0; 66];
- let our_node_id = PublicKey::from_secret_key(secp_ctx, &our_node_secret);
+ let our_node_id = node_signer.get_node_id(Recipient::Node).map_err(|_| LightningError {
+ err: "Failed to encrypt message".to_owned(),
+ action: msgs::ErrorAction::DisconnectPeer { msg: None }
+ })?;
PeerChannelEncryptor::encrypt_with_ad(&mut res[1..50], 1, &temp_k2, &bidirectional_state.h, &our_node_id.serialize()[..]);
sha.input(&res[1..50]);
bidirectional_state.h = Sha256::from_engine(sha).into_inner();
- let ss = SharedSecret::new(&re, our_node_secret);
+ let ss = node_signer.ecdh(Recipient::Node, &re, None).map_err(|_| LightningError {
+ err: "Failed to derive shared secret".to_owned(),
+ action: msgs::ErrorAction::DisconnectPeer { msg: None }
+ })?;
let temp_k = PeerChannelEncryptor::hkdf(bidirectional_state, ss);
PeerChannelEncryptor::encrypt_with_ad(&mut res[50..], 0, &temp_k, &bidirectional_state.h, &[0; 0]);
mod tests {
use super::LN_MAX_MSG_LEN;
- use bitcoin::secp256k1::{PublicKey,SecretKey};
+ use bitcoin::secp256k1::{PublicKey, SecretKey};
use bitcoin::secp256k1::Secp256k1;
use hex;
use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor,NoiseState};
+ use crate::util::test_utils::TestNodeSigner;
fn get_outbound_peer_for_initiator_test_vectors() -> PeerChannelEncryptor {
let their_node_id = PublicKey::from_slice(&hex::decode("028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7").unwrap()[..]).unwrap();
// transport-responder successful handshake
let our_node_id = SecretKey::from_slice(&hex::decode("2121212121212121212121212121212121212121212121212121212121212121").unwrap()[..]).unwrap();
let our_ephemeral = SecretKey::from_slice(&hex::decode("2222222222222222222222222222222222222222222222222222222222222222").unwrap()[..]).unwrap();
- let secp_ctx = Secp256k1::signing_only();
+ let secp_ctx = Secp256k1::new();
+ let node_signer = TestNodeSigner::new(our_node_id);
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec();
- assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
+ assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
let act_three = hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap().to_vec();
// test vector doesn't specify the initiator static key, but it's the same as the one
#[test]
fn noise_initiator_test_vectors() {
let our_node_id = SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap();
- let secp_ctx = Secp256k1::signing_only();
+ let node_signer = TestNodeSigner::new(our_node_id);
{
// transport-initiator successful handshake
let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors();
let act_two = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec();
- assert_eq!(outbound_peer.process_act_two(&act_two[..], &our_node_id, &secp_ctx).unwrap().0[..], hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap()[..]);
+ assert_eq!(outbound_peer.process_act_two(&act_two[..], &&node_signer).unwrap().0[..], hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap()[..]);
match outbound_peer.noise_state {
NoiseState::Finished { sk, sn, sck, rk, rn, rck } => {
let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors();
let act_two = hex::decode("0102466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec();
- assert!(outbound_peer.process_act_two(&act_two[..], &our_node_id, &secp_ctx).is_err());
+ assert!(outbound_peer.process_act_two(&act_two[..], &&node_signer).is_err());
}
{
let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors();
let act_two = hex::decode("0004466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec();
- assert!(outbound_peer.process_act_two(&act_two[..], &our_node_id, &secp_ctx).is_err());
+ assert!(outbound_peer.process_act_two(&act_two[..], &&node_signer).is_err());
}
{
let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors();
let act_two = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730af").unwrap().to_vec();
- assert!(outbound_peer.process_act_two(&act_two[..], &our_node_id, &secp_ctx).is_err());
+ assert!(outbound_peer.process_act_two(&act_two[..], &&node_signer).is_err());
}
}
fn noise_responder_test_vectors() {
let our_node_id = SecretKey::from_slice(&hex::decode("2121212121212121212121212121212121212121212121212121212121212121").unwrap()[..]).unwrap();
let our_ephemeral = SecretKey::from_slice(&hex::decode("2222222222222222222222222222222222222222222222222222222222222222").unwrap()[..]).unwrap();
- let secp_ctx = Secp256k1::signing_only();
+ let secp_ctx = Secp256k1::new();
+ let node_signer = TestNodeSigner::new(our_node_id);
{
let _ = get_inbound_peer_for_test_vectors();
}
{
// transport-responder act1 bad version test
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one = hex::decode("01036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec();
- assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).is_err());
+ assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).is_err());
}
{
// transport-responder act1 bad key serialization test
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one =hex::decode("00046360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec();
- assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).is_err());
+ assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).is_err());
}
{
// transport-responder act1 bad MAC test
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6b").unwrap().to_vec();
- assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).is_err());
+ assert!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).is_err());
}
{
// transport-responder act3 bad version test
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec();
- assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
+ assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
let act_three = hex::decode("01b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap().to_vec();
assert!(inbound_peer.process_act_three(&act_three[..]).is_err());
}
{
// transport-responder act3 bad MAC for ciphertext test
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec();
- assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
+ assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
let act_three = hex::decode("00c9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap().to_vec();
assert!(inbound_peer.process_act_three(&act_three[..]).is_err());
}
{
// transport-responder act3 bad rs test
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec();
- assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
+ assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
let act_three = hex::decode("00bfe3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa2235536ad09a8ee351870c2bb7f78b754a26c6cef79a98d25139c856d7efd252c2ae73c").unwrap().to_vec();
assert!(inbound_peer.process_act_three(&act_three[..]).is_err());
}
{
// transport-responder act3 bad MAC test
- let mut inbound_peer = PeerChannelEncryptor::new_inbound(&our_node_id, &secp_ctx);
+ let mut inbound_peer = PeerChannelEncryptor::new_inbound(&&node_signer);
let act_one = hex::decode("00036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f70df6086551151f58b8afe6c195782c6a").unwrap().to_vec();
- assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &our_node_id, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
+ assert_eq!(inbound_peer.process_act_one_with_keys(&act_one[..], &&node_signer, our_ephemeral.clone(), &secp_ctx).unwrap()[..], hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap()[..]);
let act_three = hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139bb").unwrap().to_vec();
assert!(inbound_peer.process_act_three(&act_three[..]).is_err());
// We use the same keys as the initiator and responder test vectors, so we copy those tests
// here and use them to encrypt.
let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors();
- let secp_ctx = Secp256k1::signing_only();
{
let our_node_id = SecretKey::from_slice(&hex::decode("1111111111111111111111111111111111111111111111111111111111111111").unwrap()[..]).unwrap();
+ let node_signer = TestNodeSigner::new(our_node_id);
let act_two = hex::decode("0002466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f276e2470b93aac583c9ef6eafca3f730ae").unwrap().to_vec();
- assert_eq!(outbound_peer.process_act_two(&act_two[..], &our_node_id, &secp_ctx).unwrap().0[..], hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap()[..]);
+ assert_eq!(outbound_peer.process_act_two(&act_two[..], &&node_signer).unwrap().0[..], hex::decode("00b9e3a702e93e3a9948c2ed6e5fd7590a6e1c3a0344cfc9d5b57357049aa22355361aa02e55a8fc28fef5bd6d71ad0c38228dc68b1c466263b47fdf31e560e139ba").unwrap()[..]);
match outbound_peer.noise_state {
NoiseState::Finished { sk, sn, sck, rk, rn, rck } => {
use bitcoin::secp256k1::{self, Secp256k1, SecretKey, PublicKey};
+use crate::chain::keysinterface::{KeysManager, NodeSigner, Recipient};
use crate::ln::features::{InitFeatures, NodeFeatures};
use crate::ln::msgs;
use crate::ln::msgs::{ChannelMessageHandler, LightningError, NetAddress, OnionMessageHandler, RoutingMessageHandler};
use crate::onion_message::{CustomOnionMessageContents, CustomOnionMessageHandler, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
use crate::routing::gossip::{NetworkGraph, P2PGossipSync};
use crate::util::atomic_counter::AtomicCounter;
-use crate::util::crypto::sign;
use crate::util::events::{MessageSendEvent, MessageSendEventsProvider, OnionMessageProvider};
use crate::util::logger::Logger;
#[cfg(feature = "std")] use std::error;
use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::sha256::HashEngine as Sha256Engine;
use bitcoin::hashes::{HashEngine, Hash};
impl ChannelMessageHandler for ErroringMessageHandler {
// Any messages which are related to a specific channel generate an error message to let the
// peer know we don't care about channels.
- fn handle_open_channel(&self, their_node_id: &PublicKey, _their_features: InitFeatures, msg: &msgs::OpenChannel) {
+ fn handle_open_channel(&self, their_node_id: &PublicKey, msg: &msgs::OpenChannel) {
ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id);
}
- fn handle_accept_channel(&self, their_node_id: &PublicKey, _their_features: InitFeatures, msg: &msgs::AcceptChannel) {
+ fn handle_accept_channel(&self, their_node_id: &PublicKey, msg: &msgs::AcceptChannel) {
ErroringMessageHandler::push_error(self, their_node_id, msg.temporary_channel_id);
}
fn handle_funding_created(&self, their_node_id: &PublicKey, msg: &msgs::FundingCreated) {
fn handle_channel_ready(&self, their_node_id: &PublicKey, msg: &msgs::ChannelReady) {
ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id);
}
- fn handle_shutdown(&self, their_node_id: &PublicKey, _their_features: &InitFeatures, msg: &msgs::Shutdown) {
+ fn handle_shutdown(&self, their_node_id: &PublicKey, msg: &msgs::Shutdown) {
ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id);
}
fn handle_closing_signed(&self, their_node_id: &PublicKey, msg: &msgs::ClosingSigned) {
/// issues such as overly long function definitions.
///
/// (C-not exported) as `Arc`s don't make sense in bindings.
-pub type SimpleArcPeerManager<SD, M, T, F, C, L> = PeerManager<SD, Arc<SimpleArcChannelManager<M, T, F, L>>, Arc<P2PGossipSync<Arc<NetworkGraph<Arc<L>>>, Arc<C>, Arc<L>>>, Arc<SimpleArcOnionMessenger<L>>, Arc<L>, IgnoringMessageHandler>;
+pub type SimpleArcPeerManager<SD, M, T, F, C, L> = PeerManager<SD, Arc<SimpleArcChannelManager<M, T, F, L>>, Arc<P2PGossipSync<Arc<NetworkGraph<Arc<L>>>, Arc<C>, Arc<L>>>, Arc<SimpleArcOnionMessenger<L>>, Arc<L>, IgnoringMessageHandler, Arc<KeysManager>>;
/// SimpleRefPeerManager is a type alias for a PeerManager reference, and is the reference
/// counterpart to the SimpleArcPeerManager type alias. Use this type by default when you don't
/// helps with issues such as long function definitions.
///
/// (C-not exported) as general type aliases don't make sense in bindings.
-pub type SimpleRefPeerManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm, SD, M, T, F, C, L> = PeerManager<SD, SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'm, M, T, F, L>, &'f P2PGossipSync<&'g NetworkGraph<&'f L>, &'h C, &'f L>, &'i SimpleRefOnionMessenger<'j, 'k, L>, &'f L, IgnoringMessageHandler>;
+pub type SimpleRefPeerManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k, 'l, 'm, SD, M, T, F, C, L> = PeerManager<SD, SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'm, M, T, F, L>, &'f P2PGossipSync<&'g NetworkGraph<&'f L>, &'h C, &'f L>, &'i SimpleRefOnionMessenger<'j, 'k, L>, &'f L, IgnoringMessageHandler, &'c KeysManager>;
/// A PeerManager manages a set of peers, described by their [`SocketDescriptor`] and marshalls
/// socket events into messages which it passes on to its [`MessageHandler`].
/// you're using lightning-net-tokio.
///
/// [`read_event`]: PeerManager::read_event
-pub struct PeerManager<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CMH: Deref> where
+pub struct PeerManager<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CMH: Deref, NS: Deref> where
CM::Target: ChannelMessageHandler,
RM::Target: RoutingMessageHandler,
OM::Target: OnionMessageHandler,
L::Target: Logger,
- CMH::Target: CustomMessageHandler {
+ CMH::Target: CustomMessageHandler,
+ NS::Target: NodeSigner {
message_handler: MessageHandler<CM, RM, OM>,
/// Connection state for each connected peer - we have an outer read-write lock which is taken
/// as read while we're doing processing for a peer and taken write when a peer is being added
/// value increases strictly since we don't assume access to a time source.
last_node_announcement_serial: AtomicU32,
- our_node_secret: SecretKey,
ephemeral_key_midstate: Sha256Engine,
custom_message_handler: CMH,
peer_counter: AtomicCounter,
+ node_signer: NS,
+
logger: L,
secp_ctx: Secp256k1<secp256k1::SignOnly>
}
}}
}
-impl<Descriptor: SocketDescriptor, CM: Deref, OM: Deref, L: Deref> PeerManager<Descriptor, CM, IgnoringMessageHandler, OM, L, IgnoringMessageHandler> where
+impl<Descriptor: SocketDescriptor, CM: Deref, OM: Deref, L: Deref, NS: Deref> PeerManager<Descriptor, CM, IgnoringMessageHandler, OM, L, IgnoringMessageHandler, NS> where
CM::Target: ChannelMessageHandler,
OM::Target: OnionMessageHandler,
- L::Target: Logger {
+ L::Target: Logger,
+ NS::Target: NodeSigner {
/// Constructs a new `PeerManager` with the given `ChannelMessageHandler` and
/// `OnionMessageHandler`. No routing message handler is used and network graph messages are
/// ignored.
/// minute should suffice.
///
/// (C-not exported) as we can't export a PeerManager with a dummy route handler
- pub fn new_channel_only(channel_message_handler: CM, onion_message_handler: OM, our_node_secret: SecretKey, current_time: u32, ephemeral_random_data: &[u8; 32], logger: L) -> Self {
+ pub fn new_channel_only(channel_message_handler: CM, onion_message_handler: OM, current_time: u32, ephemeral_random_data: &[u8; 32], logger: L, node_signer: NS) -> Self {
Self::new(MessageHandler {
chan_handler: channel_message_handler,
route_handler: IgnoringMessageHandler{},
onion_message_handler,
- }, our_node_secret, current_time, ephemeral_random_data, logger, IgnoringMessageHandler{})
+ }, current_time, ephemeral_random_data, logger, IgnoringMessageHandler{}, node_signer)
}
}
-impl<Descriptor: SocketDescriptor, RM: Deref, L: Deref> PeerManager<Descriptor, ErroringMessageHandler, RM, IgnoringMessageHandler, L, IgnoringMessageHandler> where
+impl<Descriptor: SocketDescriptor, RM: Deref, L: Deref, NS: Deref> PeerManager<Descriptor, ErroringMessageHandler, RM, IgnoringMessageHandler, L, IgnoringMessageHandler, NS> where
RM::Target: RoutingMessageHandler,
- L::Target: Logger {
+ L::Target: Logger,
+ NS::Target: NodeSigner {
/// Constructs a new `PeerManager` with the given `RoutingMessageHandler`. No channel message
/// handler or onion message handler is used and onion and channel messages will be ignored (or
/// generate error messages). Note that some other lightning implementations time-out connections
/// cryptographically secure random bytes.
///
/// (C-not exported) as we can't export a PeerManager with a dummy channel handler
- pub fn new_routing_only(routing_message_handler: RM, our_node_secret: SecretKey, current_time: u32, ephemeral_random_data: &[u8; 32], logger: L) -> Self {
+ pub fn new_routing_only(routing_message_handler: RM, current_time: u32, ephemeral_random_data: &[u8; 32], logger: L, node_signer: NS) -> Self {
Self::new(MessageHandler {
chan_handler: ErroringMessageHandler::new(),
route_handler: routing_message_handler,
onion_message_handler: IgnoringMessageHandler{},
- }, our_node_secret, current_time, ephemeral_random_data, logger, IgnoringMessageHandler{})
+ }, current_time, ephemeral_random_data, logger, IgnoringMessageHandler{}, node_signer)
}
}
}
}
-impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CMH: Deref> PeerManager<Descriptor, CM, RM, OM, L, CMH> where
+impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CMH: Deref, NS: Deref> PeerManager<Descriptor, CM, RM, OM, L, CMH, NS> where
CM::Target: ChannelMessageHandler,
RM::Target: RoutingMessageHandler,
OM::Target: OnionMessageHandler,
L::Target: Logger,
- CMH::Target: CustomMessageHandler {
+ CMH::Target: CustomMessageHandler,
+ NS::Target: NodeSigner
+{
/// Constructs a new PeerManager with the given message handlers and node_id secret key
/// ephemeral_random_data is used to derive per-connection ephemeral keys and must be
/// cryptographically secure random bytes.
/// incremented irregularly internally. In general it is best to simply use the current UNIX
/// timestamp, however if it is not available a persistent counter that increases once per
/// minute should suffice.
- pub fn new(message_handler: MessageHandler<CM, RM, OM>, our_node_secret: SecretKey, current_time: u32, ephemeral_random_data: &[u8; 32], logger: L, custom_message_handler: CMH) -> Self {
+ pub fn new(message_handler: MessageHandler<CM, RM, OM>, current_time: u32, ephemeral_random_data: &[u8; 32], logger: L, custom_message_handler: CMH, node_signer: NS) -> Self {
let mut ephemeral_key_midstate = Sha256::engine();
ephemeral_key_midstate.input(ephemeral_random_data);
node_id_to_descriptor: Mutex::new(HashMap::new()),
event_processing_lock: Mutex::new(()),
blocked_event_processors: AtomicBool::new(false),
- our_node_secret,
ephemeral_key_midstate,
peer_counter: AtomicCounter::new(),
last_node_announcement_serial: AtomicU32::new(current_time),
logger,
custom_message_handler,
+ node_signer,
secp_ctx,
}
}
///
/// [`socket_disconnected()`]: PeerManager::socket_disconnected
pub fn new_inbound_connection(&self, descriptor: Descriptor, remote_network_address: Option<NetAddress>) -> Result<(), PeerHandleError> {
- let peer_encryptor = PeerChannelEncryptor::new_inbound(&self.our_node_secret, &self.secp_ctx);
+ let peer_encryptor = PeerChannelEncryptor::new_inbound(&self.node_signer);
let pending_read_buffer = [0; 50].to_vec(); // Noise act one is 50 bytes
let mut peers = self.peers.write().unwrap();
NextNoiseStep::ActOne => {
let act_two = try_potential_handleerror!(peer, peer.channel_encryptor
.process_act_one_with_keys(&peer.pending_read_buffer[..],
- &self.our_node_secret, self.get_ephemeral_key(), &self.secp_ctx)).to_vec();
+ &self.node_signer, self.get_ephemeral_key(), &self.secp_ctx)).to_vec();
peer.pending_outbound_buffer.push_back(act_two);
peer.pending_read_buffer = [0; 66].to_vec(); // act three is 66 bytes long
},
NextNoiseStep::ActTwo => {
let (act_three, their_node_id) = try_potential_handleerror!(peer,
peer.channel_encryptor.process_act_two(&peer.pending_read_buffer[..],
- &self.our_node_secret, &self.secp_ctx));
+ &self.node_signer));
peer.pending_outbound_buffer.push_back(act_three.to_vec());
peer.pending_read_buffer = [0; 18].to_vec(); // Message length header is 18 bytes
peer.pending_read_is_header = true;
return Ok(None);
}
- let their_features = peer_lock.their_features.clone();
mem::drop(peer_lock);
if is_gossip_msg(message.type_id()) {
// Channel messages:
wire::Message::OpenChannel(msg) => {
- self.message_handler.chan_handler.handle_open_channel(&their_node_id, their_features.clone().unwrap(), &msg);
+ self.message_handler.chan_handler.handle_open_channel(&their_node_id, &msg);
},
wire::Message::AcceptChannel(msg) => {
- self.message_handler.chan_handler.handle_accept_channel(&their_node_id, their_features.clone().unwrap(), &msg);
+ self.message_handler.chan_handler.handle_accept_channel(&their_node_id, &msg);
},
wire::Message::FundingCreated(msg) => {
},
wire::Message::Shutdown(msg) => {
- self.message_handler.chan_handler.handle_shutdown(&their_node_id, their_features.as_ref().unwrap(), &msg);
+ self.message_handler.chan_handler.handle_shutdown(&their_node_id, &msg);
},
wire::Message::ClosingSigned(msg) => {
self.message_handler.chan_handler.handle_closing_signed(&their_node_id, &msg);
let announcement = msgs::UnsignedNodeAnnouncement {
features,
timestamp: self.last_node_announcement_serial.fetch_add(1, Ordering::AcqRel),
- node_id: PublicKey::from_secret_key(&self.secp_ctx, &self.our_node_secret),
+ node_id: self.node_signer.get_node_id(Recipient::Node).unwrap(),
rgb, alias, addresses,
excess_address_data: Vec::new(),
excess_data: Vec::new(),
};
- let msghash = hash_to_message!(&Sha256dHash::hash(&announcement.encode()[..])[..]);
- let node_announce_sig = sign(&self.secp_ctx, &msghash, &self.our_node_secret);
+ let node_announce_sig = match self.node_signer.sign_gossip_message(
+ msgs::UnsignedGossipMessage::NodeAnnouncement(&announcement)
+ ) {
+ Ok(sig) => sig,
+ Err(_) => {
+ log_error!(self.logger, "Failed to generate signature for node_announcement");
+ return;
+ },
+ };
let msg = msgs::NodeAnnouncement {
signature: node_announce_sig,
#[cfg(test)]
mod tests {
+ use crate::chain::keysinterface::{NodeSigner, Recipient};
use crate::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler, filter_addresses};
use crate::ln::{msgs, wire};
use crate::ln::msgs::NetAddress;
use crate::util::events;
use crate::util::test_utils;
- use bitcoin::secp256k1::Secp256k1;
- use bitcoin::secp256k1::{SecretKey, PublicKey};
+ use bitcoin::secp256k1::SecretKey;
use crate::prelude::*;
use crate::sync::{Arc, Mutex};
chan_handler: test_utils::TestChannelMessageHandler,
routing_handler: test_utils::TestRoutingMessageHandler,
logger: test_utils::TestLogger,
+ node_signer: test_utils::TestNodeSigner,
}
fn create_peermgr_cfgs(peer_count: usize) -> Vec<PeerManagerCfg> {
let mut cfgs = Vec::new();
- for _ in 0..peer_count {
+ for i in 0..peer_count {
+ let node_secret = SecretKey::from_slice(&[42 + i as u8; 32]).unwrap();
cfgs.push(
PeerManagerCfg{
chan_handler: test_utils::TestChannelMessageHandler::new(),
logger: test_utils::TestLogger::new(),
routing_handler: test_utils::TestRoutingMessageHandler::new(),
+ node_signer: test_utils::TestNodeSigner::new(node_secret),
}
);
}
cfgs
}
- fn create_network<'a>(peer_count: usize, cfgs: &'a Vec<PeerManagerCfg>) -> Vec<PeerManager<FileDescriptor, &'a test_utils::TestChannelMessageHandler, &'a test_utils::TestRoutingMessageHandler, IgnoringMessageHandler, &'a test_utils::TestLogger, IgnoringMessageHandler>> {
+ fn create_network<'a>(peer_count: usize, cfgs: &'a Vec<PeerManagerCfg>) -> Vec<PeerManager<FileDescriptor, &'a test_utils::TestChannelMessageHandler, &'a test_utils::TestRoutingMessageHandler, IgnoringMessageHandler, &'a test_utils::TestLogger, IgnoringMessageHandler, &'a test_utils::TestNodeSigner>> {
let mut peers = Vec::new();
for i in 0..peer_count {
- let node_secret = SecretKey::from_slice(&[42 + i as u8; 32]).unwrap();
let ephemeral_bytes = [i as u8; 32];
let msg_handler = MessageHandler { chan_handler: &cfgs[i].chan_handler, route_handler: &cfgs[i].routing_handler, onion_message_handler: IgnoringMessageHandler {} };
- let peer = PeerManager::new(msg_handler, node_secret, 0, &ephemeral_bytes, &cfgs[i].logger, IgnoringMessageHandler {});
+ let peer = PeerManager::new(msg_handler, 0, &ephemeral_bytes, &cfgs[i].logger, IgnoringMessageHandler {}, &cfgs[i].node_signer);
peers.push(peer);
}
peers
}
- fn establish_connection<'a>(peer_a: &PeerManager<FileDescriptor, &'a test_utils::TestChannelMessageHandler, &'a test_utils::TestRoutingMessageHandler, IgnoringMessageHandler, &'a test_utils::TestLogger, IgnoringMessageHandler>, peer_b: &PeerManager<FileDescriptor, &'a test_utils::TestChannelMessageHandler, &'a test_utils::TestRoutingMessageHandler, IgnoringMessageHandler, &'a test_utils::TestLogger, IgnoringMessageHandler>) -> (FileDescriptor, FileDescriptor) {
- let secp_ctx = Secp256k1::new();
- let a_id = PublicKey::from_secret_key(&secp_ctx, &peer_a.our_node_secret);
+ fn establish_connection<'a>(peer_a: &PeerManager<FileDescriptor, &'a test_utils::TestChannelMessageHandler, &'a test_utils::TestRoutingMessageHandler, IgnoringMessageHandler, &'a test_utils::TestLogger, IgnoringMessageHandler, &'a test_utils::TestNodeSigner>, peer_b: &PeerManager<FileDescriptor, &'a test_utils::TestChannelMessageHandler, &'a test_utils::TestRoutingMessageHandler, IgnoringMessageHandler, &'a test_utils::TestLogger, IgnoringMessageHandler, &'a test_utils::TestNodeSigner>) -> (FileDescriptor, FileDescriptor) {
+ let a_id = peer_a.node_signer.get_node_id(Recipient::Node).unwrap();
let mut fd_a = FileDescriptor { fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())) };
let mut fd_b = FileDescriptor { fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())) };
let initial_data = peer_b.new_outbound_connection(a_id, fd_b.clone(), None).unwrap();
establish_connection(&peers[0], &peers[1]);
assert_eq!(peers[0].peers.read().unwrap().len(), 1);
- let secp_ctx = Secp256k1::new();
- let their_id = PublicKey::from_secret_key(&secp_ctx, &peers[1].our_node_secret);
+ let their_id = peers[1].node_signer.get_node_id(Recipient::Node).unwrap();
chan_handler.pending_events.lock().unwrap().push(events::MessageSendEvent::HandleError {
node_id: their_id,
let (fd_a, mut fd_b) = establish_connection(&peers[0], &peers[1]);
assert_eq!(peers[0].peers.read().unwrap().len(), 1);
- let secp_ctx = Secp256k1::new();
- let their_id = PublicKey::from_secret_key(&secp_ctx, &peers[1].our_node_secret);
+ let their_id = peers[1].node_signer.get_node_id(Recipient::Node).unwrap();
let msg = msgs::Shutdown { channel_id: [42; 32], scriptpubkey: bitcoin::Script::new() };
a_chan_handler.pending_events.lock().unwrap().push(events::MessageSendEvent::SendShutdown {
cfgs[1].routing_handler.request_full_sync.store(true, Ordering::Release);
let peers = create_network(2, &cfgs);
- let secp_ctx = Secp256k1::new();
- let a_id = PublicKey::from_secret_key(&secp_ctx, &peers[0].our_node_secret);
+ let a_id = peers[0].node_signer.get_node_id(Recipient::Node).unwrap();
let mut fd_a = FileDescriptor { fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())) };
let mut fd_b = FileDescriptor { fd: 1, outbound_data: Arc::new(Mutex::new(Vec::new())) };
let initial_data = peers[1].new_outbound_connection(a_id, fd_b.clone(), None).unwrap();
//! LSP).
use crate::chain::ChannelMonitorUpdateStatus;
-use crate::chain::keysinterface::{Recipient, NodeSigner};
+use crate::chain::keysinterface::NodeSigner;
use crate::ln::channelmanager::{ChannelManager, MIN_CLTV_EXPIRY_DELTA, PaymentId};
use crate::routing::gossip::RoutingFees;
use crate::routing::router::{PaymentParameters, RouteHint, RouteHintHop};
use crate::ln::functional_test_utils::*;
use bitcoin::blockdata::constants::genesis_block;
-use bitcoin::hashes::Hash;
-use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::Secp256k1;
#[test]
fn test_priv_forwarding_rejection() {
open_channel.channel_type.as_mut().unwrap().set_scid_privacy_required();
assert_eq!(open_channel.channel_flags & 1, 1); // The `announce_channel` bit is set.
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
let err = get_err_msg!(nodes[1], nodes[0].node.get_our_node_id());
assert_eq!(err.data, "SCID Alias/Privacy Channel Type cannot be set on a public channel");
}
let second_open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
assert!(!second_open_channel.channel_type.as_ref().unwrap().supports_scid_privacy());
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &second_open_channel);
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &second_open_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
assert!(open_channel.channel_type.as_ref().unwrap().requires_scid_privacy());
- nodes[2].node.handle_open_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &open_channel);
+ nodes[2].node.handle_open_channel(&nodes[1].node.get_our_node_id(), &open_channel);
let accept_channel = get_event_msg!(nodes[2], MessageSendEvent::SendAcceptChannel, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_accept_channel(&nodes[2].node.get_our_node_id(), nodes[2].node.init_features(), &accept_channel);
+ nodes[1].node.handle_accept_channel(&nodes[2].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[1], &nodes[2].node.get_our_node_id(), 100_000, 42);
nodes[1].node.funding_transaction_generated(&temporary_channel_id, &nodes[2].node.get_our_node_id(), tx.clone()).unwrap();
fee_proportional_millionths: last_hop[0].counterparty.forwarding_info.as_ref().unwrap().fee_proportional_millionths,
excess_data: Vec::new(),
};
- let msg_hash = Sha256dHash::hash(&contents.encode()[..]);
- let signature = Secp256k1::new().sign_ecdsa(&hash_to_message!(&msg_hash[..]), &nodes[1].keys_manager.get_node_secret(Recipient::Node).unwrap());
+ let signature = nodes[1].keys_manager.sign_gossip_message(msgs::UnsignedGossipMessage::ChannelUpdate(&contents)).unwrap();
let msg = msgs::ChannelUpdate { signature, contents };
let mut err_data = Vec::new();
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, Some(chan_config)).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(), nodes[0].node.init_features(), &open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
let events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
let mut accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
assert_eq!(accept_channel.minimum_depth, 0);
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, funding_output) = create_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();
open_channel_msg.channel_type = Some(channel_type_features.clone());
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(), &open_channel_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
match msg_events[0] {
open_channel_msg.channel_type = Some(channel_type_features.clone());
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(),
- &open_channel_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
// Assert that `nodes[1]` has no `MessageSendEvent::SendAcceptChannel` in the `msg_events`.
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
open_channel_msg.channel_type = Some(channel_type_features);
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), nodes[0].node.init_features(),
- &open_channel_msg);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
let events = nodes[1].node.get_and_clear_pending_events();
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 10_001, 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(), nodes[0].node.init_features(), &open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
let events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
let mut accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
assert_eq!(accept_channel.minimum_depth, 0);
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
let push_msat = 10001;
let node_a = nodes.remove(0);
let node_b = nodes.remove(0);
- let a_flags = node_a.node.init_features();
- let b_flags = node_b.node.init_features();
node_a.node.create_channel(node_b.node.get_our_node_id(), channel_value, push_msat, 42, None).unwrap();
- node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), a_flags, &get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id()));
- node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), b_flags, &get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id()));
+ node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), &get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id()));
+ node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), &get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id()));
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(&node_a, &node_b.node.get_our_node_id(), channel_value, 42);
nodes[0].node.close_channel(&OutPoint { txid: tx.txid(), index: 0 }.to_channel_id(), &nodes[1].node.get_our_node_id()).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
nodes[0].node.close_channel(&chan_1.2, &nodes[1].node.get_our_node_id()).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
nodes[1].node.close_channel(&chan_1.2, &nodes[0].node.get_our_node_id()).unwrap();
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]);
nodes[1].node.handle_commitment_signed(&nodes[0].node.get_our_node_id(), &updates.commitment_signed);
check_added_monitors!(nodes[1], 1);
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
commitment_signed_dance!(nodes[1], nodes[0], (), false, true, false);
let updates_2 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
nodes[1].node.close_channel(&chan_1.2, &nodes[0].node.get_our_node_id()).unwrap();
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
if recv_count > 0 {
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
if recv_count > 1 {
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
}
}
nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &node_1_reestablish);
let node_0_2nd_shutdown = if recv_count > 0 {
let node_0_2nd_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_2nd_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_2nd_shutdown);
node_0_2nd_shutdown
} else {
let node_0_chan_update = get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
assert_eq!(node_0_chan_update.contents.flags & 2, 0); // "disabled" flag must not be set as we just reconnected.
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_2nd_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_2nd_shutdown);
get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id())
};
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_2nd_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_2nd_shutdown);
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
let node_1_3rd_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
assert!(node_1_3rd_shutdown == node_1_2nd_shutdown);
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_3rd_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_3rd_shutdown);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_3rd_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_3rd_shutdown);
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_2nd_closing_signed);
let node_1_closing_signed = get_event_msg!(nodes[1], MessageSendEvent::SendClosingSigned, nodes[0].node.get_our_node_id());
node_0_shutdown.scriptpubkey = Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script().to_p2sh();
// Test we enforce upfront_scriptpbukey if by providing a different one at closing that we warn
// the peer and ignore the message.
- nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
assert!(regex::Regex::new(r"Got shutdown request with a scriptpubkey \([A-Fa-f0-9]+\) which did not match their previous scriptpubkey.")
.unwrap().is_match(&check_warn_msg!(nodes[2], nodes[0].node.get_our_node_id(), chan.2)));
// This allows nodes[2] to retry the shutdown message, which should get a response:
- nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_orig_shutdown);
+ nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_orig_shutdown);
get_event_msg!(nodes[2], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
// We test that in case of peer committing upfront to a script, if it doesn't change at closing, we sign
nodes[0].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[2].node.get_our_node_id()).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[2].node.get_our_node_id());
// We test that in case of peer committing upfront to a script, if it oesn't change at closing, we sign
- nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[2].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
let events = nodes[2].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
match events[0] {
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 1000000);
nodes[0].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[1].node.get_our_node_id()).unwrap();
let node_1_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_1_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_1_shutdown);
check_added_monitors!(nodes[1], 1);
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
nodes[1].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id()).unwrap();
check_added_monitors!(nodes[1], 1);
let node_0_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_0_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_0_shutdown);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
match events[0] {
nodes[1].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[0].node.get_our_node_id()).unwrap();
check_added_monitors!(nodes[1], 1);
let node_0_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_0_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_0_shutdown);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
match events[0] {
#[test]
fn test_unsupported_anysegwit_upfront_shutdown_script() {
let chanmon_cfgs = create_chanmon_cfgs(2);
- let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ // Clear shutdown_anysegwit on initiator
+ *node_cfgs[0].override_init_features.borrow_mut() = Some(channelmanager::provided_init_features(&test_default_channel_config()).clear_shutdown_anysegwit());
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
// Use a non-v0 segwit script supported by option_shutdown_anysegwit
- let node_features = nodes[0].node.init_features().clear_shutdown_anysegwit();
let anysegwit_shutdown_script = Builder::new()
.push_int(16)
.push_slice(&[0, 40])
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
let mut open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
open_channel.shutdown_scriptpubkey = Present(anysegwit_shutdown_script.clone());
- nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), node_features.clone(), &open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
_ => panic!("Unexpected event"),
}
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ // Clear shutdown_anysegwit on responder
+ *node_cfgs[1].override_init_features.borrow_mut() = Some(channelmanager::provided_init_features(&test_default_channel_config()).clear_shutdown_anysegwit());
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
// Check script when handling an accept_channel message
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(), nodes[0].node.init_features(), &open_channel);
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
let mut accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
accept_channel.shutdown_scriptpubkey = Present(anysegwit_shutdown_script.clone());
- nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), node_features, &accept_channel);
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
open_channel.shutdown_scriptpubkey = Present(Builder::new().push_int(0)
.push_slice(&[0, 0])
.into_script());
- nodes[0].node.handle_open_channel(&nodes[1].node.get_our_node_id(), nodes[1].node.init_features(), &open_channel);
+ nodes[0].node.handle_open_channel(&nodes[1].node.get_our_node_id(), &open_channel);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
node_0_shutdown.scriptpubkey = Builder::new().push_int(0)
.push_slice(&[0; 20])
.into_script();
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_0_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_0_shutdown);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
node_0_shutdown.scriptpubkey = Builder::new().push_int(16)
.push_slice(&[0, 0])
.into_script();
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_0_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_0_shutdown);
let events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
// Use a non-v0 segwit script unsupported without option_shutdown_anysegwit
let mut node_0_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
node_0_shutdown.scriptpubkey = unsupported_shutdown_script.into_inner();
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].override_init_features.borrow().as_ref().unwrap(), &node_0_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_0_shutdown);
assert_eq!(&check_warn_msg!(nodes[0], nodes[1].node.get_our_node_id(), chan.2),
"Got a nonstandard scriptpubkey (60020028) from remote peer");
node_0_shutdown.scriptpubkey = Builder::new().push_int(0)
.push_slice(&[0, 0])
.into_script();
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_0_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_0_shutdown);
assert_eq!(&check_warn_msg!(nodes[0], nodes[1].node.get_our_node_id(), chan.2),
"Got a nonstandard scriptpubkey (00020000) from remote peer");
nodes[0].node.close_channel(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
{
// Now we set nodes[1] to require a relatively high feerate for closing. This should result
nodes[0].node.close_channel(&OutPoint { txid: chan.3.txid(), index: 0 }.to_channel_id(), &nodes[1].node.get_our_node_id()).unwrap();
let node_0_shutdown = get_event_msg!(nodes[0], MessageSendEvent::SendShutdown, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
let mut node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
node_0_closing_signed.fee_range = None;
nodes[1].node.close_channel_with_target_feerate(&chan_id, &nodes[0].node.get_our_node_id(), 253 * 5).unwrap();
let node_1_shutdown = get_event_msg!(nodes[1], MessageSendEvent::SendShutdown, nodes[0].node.get_our_node_id());
- nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &nodes[0].node.init_features(), &node_0_shutdown);
- nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &node_1_shutdown);
+ nodes[1].node.handle_shutdown(&nodes[0].node.get_our_node_id(), &node_0_shutdown);
+ nodes[0].node.handle_shutdown(&nodes[1].node.get_our_node_id(), &node_1_shutdown);
let node_0_closing_signed = get_event_msg!(nodes[0], MessageSendEvent::SendClosingSigned, nodes[1].node.get_our_node_id());
nodes[1].node.handle_closing_signed(&nodes[0].node.get_our_node_id(), &node_0_closing_signed);
--- /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.
+
+//! Data structures and encoding for `invoice` messages.
+//!
+//! An [`Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid" flow or
+//! from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment then sends
+//! the invoice to the intended payer, who will then pay it.
+//!
+//! The payment recipient must include a [`PaymentHash`], so as to reveal the preimage upon payment
+//! receipt, and one or more [`BlindedPath`]s for the payer to use when sending the payment.
+//!
+//! ```ignore
+//! extern crate bitcoin;
+//! extern crate lightning;
+//!
+//! use bitcoin::hashes::Hash;
+//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
+//! use core::convert::{Infallible, TryFrom};
+//! use lightning::offers::invoice_request::InvoiceRequest;
+//! use lightning::offers::refund::Refund;
+//! use lightning::util::ser::Writeable;
+//!
+//! # use lightning::ln::PaymentHash;
+//! # use lightning::offers::invoice::BlindedPayInfo;
+//! # use lightning::onion_message::BlindedPath;
+//! #
+//! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() }
+//! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
+//! #
+//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! let payment_paths = create_payment_paths();
+//! let payment_hash = create_payment_hash();
+//! let secp_ctx = Secp256k1::new();
+//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
+//! let pubkey = PublicKey::from(keys);
+//! let wpubkey_hash = bitcoin::util::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
+//! let mut buffer = Vec::new();
+//!
+//! // Invoice for the "offer to be paid" flow.
+//! InvoiceRequest::try_from(bytes)?
+//! .respond_with(payment_paths, payment_hash)?
+//! .relative_expiry(3600)
+//! .allow_mpp()
+//! .fallback_v0_p2wpkh(&wpubkey_hash)
+//! .build()?
+//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//! .expect("failed verifying signature")
+//! .write(&mut buffer)
+//! .unwrap();
+//! # Ok(())
+//! # }
+//!
+//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # let payment_paths = create_payment_paths();
+//! # let payment_hash = create_payment_hash();
+//! # let secp_ctx = Secp256k1::new();
+//! # let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32])?);
+//! # let pubkey = PublicKey::from(keys);
+//! # let wpubkey_hash = bitcoin::util::key::PublicKey::new(pubkey).wpubkey_hash().unwrap();
+//! # let mut buffer = Vec::new();
+//!
+//! // Invoice for the "offer for money" flow.
+//! "lnr1qcp4256ypq"
+//! .parse::<Refund>()?
+//! .respond_with(payment_paths, payment_hash, pubkey)?
+//! .relative_expiry(3600)
+//! .allow_mpp()
+//! .fallback_v0_p2wpkh(&wpubkey_hash)
+//! .build()?
+//! .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+//! .expect("failed verifying signature")
+//! .write(&mut buffer)
+//! .unwrap();
+//! # Ok(())
+//! # }
+//!
+//! ```
+
+use bitcoin::blockdata::constants::ChainHash;
+use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
+use bitcoin::hashes::Hash;
+use bitcoin::network::constants::Network;
+use bitcoin::secp256k1::{Message, PublicKey};
+use bitcoin::secp256k1::schnorr::Signature;
+use bitcoin::util::address::{Address, Payload, WitnessVersion};
+use bitcoin::util::schnorr::TweakedPublicKey;
+use core::convert::TryFrom;
+use core::time::Duration;
+use crate::io;
+use crate::ln::PaymentHash;
+use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
+use crate::ln::msgs::DecodeError;
+use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, WithoutSignatures, self};
+use crate::offers::offer::{Amount, OfferTlvStream, OfferTlvStreamRef};
+use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
+use crate::offers::payer::{PayerTlvStream, PayerTlvStreamRef};
+use crate::offers::refund::{Refund, RefundContents};
+use crate::onion_message::BlindedPath;
+use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer};
+
+use crate::prelude::*;
+
+#[cfg(feature = "std")]
+use std::time::SystemTime;
+
+const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
+
+const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
+
+/// Builds an [`Invoice`] from either:
+/// - an [`InvoiceRequest`] for the "offer to be paid" flow or
+/// - a [`Refund`] for the "offer for money" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Refund`]: crate::offers::refund::Refund
+/// [module-level documentation]: self
+pub struct InvoiceBuilder<'a> {
+ invreq_bytes: &'a Vec<u8>,
+ invoice: InvoiceContents,
+}
+
+impl<'a> InvoiceBuilder<'a> {
+ pub(super) fn for_offer(
+ invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+ created_at: Duration, payment_hash: PaymentHash
+ ) -> Result<Self, SemanticError> {
+ let amount_msats = match invoice_request.amount_msats() {
+ Some(amount_msats) => amount_msats,
+ None => match invoice_request.contents.offer.amount() {
+ Some(Amount::Bitcoin { amount_msats }) => {
+ amount_msats * invoice_request.quantity().unwrap_or(1)
+ },
+ Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+ None => return Err(SemanticError::MissingAmount),
+ },
+ };
+
+ let contents = InvoiceContents::ForOffer {
+ invoice_request: invoice_request.contents.clone(),
+ fields: InvoiceFields {
+ payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
+ fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
+ signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
+ },
+ };
+
+ Self::new(&invoice_request.bytes, contents)
+ }
+
+ pub(super) fn for_refund(
+ refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+ payment_hash: PaymentHash, signing_pubkey: PublicKey
+ ) -> Result<Self, SemanticError> {
+ let contents = InvoiceContents::ForRefund {
+ refund: refund.contents.clone(),
+ fields: InvoiceFields {
+ payment_paths, created_at, relative_expiry: None, payment_hash,
+ amount_msats: refund.amount_msats(), fallbacks: None,
+ features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
+ },
+ };
+
+ Self::new(&refund.bytes, contents)
+ }
+
+ fn new(invreq_bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
+ if contents.fields().payment_paths.is_empty() {
+ return Err(SemanticError::MissingPaths);
+ }
+
+ Ok(Self { invreq_bytes, invoice: contents })
+ }
+
+ /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
+ /// that has already passed is valid and can be checked for using [`Invoice::is_expired`].
+ ///
+ /// Successive calls to this method will override the previous setting.
+ pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self {
+ let relative_expiry = Duration::from_secs(relative_expiry_secs as u64);
+ self.invoice.fields_mut().relative_expiry = Some(relative_expiry);
+ self
+ }
+
+ /// Adds a P2WSH address to [`Invoice::fallbacks`].
+ ///
+ /// Successive calls to this method will add another address. Caller is responsible for not
+ /// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses.
+ pub fn fallback_v0_p2wsh(mut self, script_hash: &WScriptHash) -> Self {
+ let address = FallbackAddress {
+ version: WitnessVersion::V0.to_num(),
+ program: Vec::from(&script_hash.into_inner()[..]),
+ };
+ self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
+ self
+ }
+
+ /// Adds a P2WPKH address to [`Invoice::fallbacks`].
+ ///
+ /// Successive calls to this method will add another address. Caller is responsible for not
+ /// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses.
+ pub fn fallback_v0_p2wpkh(mut self, pubkey_hash: &WPubkeyHash) -> Self {
+ let address = FallbackAddress {
+ version: WitnessVersion::V0.to_num(),
+ program: Vec::from(&pubkey_hash.into_inner()[..]),
+ };
+ self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
+ self
+ }
+
+ /// Adds a P2TR address to [`Invoice::fallbacks`].
+ ///
+ /// Successive calls to this method will add another address. Caller is responsible for not
+ /// adding duplicate addresses and only calling if capable of receiving to P2TR addresses.
+ pub fn fallback_v1_p2tr_tweaked(mut self, output_key: &TweakedPublicKey) -> Self {
+ let address = FallbackAddress {
+ version: WitnessVersion::V1.to_num(),
+ program: Vec::from(&output_key.serialize()[..]),
+ };
+ self.invoice.fields_mut().fallbacks.get_or_insert_with(Vec::new).push(address);
+ self
+ }
+
+ /// Sets [`Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
+ pub fn allow_mpp(mut self) -> Self {
+ self.invoice.fields_mut().features.set_basic_mpp_optional();
+ self
+ }
+
+ /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
+ /// [`UnsignedInvoice::sign`].
+ pub fn build(self) -> Result<UnsignedInvoice<'a>, SemanticError> {
+ #[cfg(feature = "std")] {
+ if self.invoice.is_offer_or_refund_expired() {
+ return Err(SemanticError::AlreadyExpired);
+ }
+ }
+
+ let InvoiceBuilder { invreq_bytes, invoice } = self;
+ Ok(UnsignedInvoice { invreq_bytes, invoice })
+ }
+}
+
+/// A semantically valid [`Invoice`] that hasn't been signed.
+pub struct UnsignedInvoice<'a> {
+ invreq_bytes: &'a Vec<u8>,
+ invoice: InvoiceContents,
+}
+
+impl<'a> UnsignedInvoice<'a> {
+ /// Signs the invoice using the given function.
+ pub fn sign<F, E>(self, sign: F) -> Result<Invoice, SignError<E>>
+ where
+ F: FnOnce(&Message) -> Result<Signature, E>
+ {
+ // Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
+ // have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
+ // `RefundContents`.
+ let (_, _, _, invoice_tlv_stream) = self.invoice.as_tlv_stream();
+ let invoice_request_bytes = WithoutSignatures(self.invreq_bytes);
+ let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
+
+ let mut bytes = Vec::new();
+ unsigned_tlv_stream.write(&mut bytes).unwrap();
+
+ let pubkey = self.invoice.fields().signing_pubkey;
+ let signature = merkle::sign_message(sign, SIGNATURE_TAG, &bytes, pubkey)?;
+
+ // Append the signature TLV record to the bytes.
+ let signature_tlv_stream = SignatureTlvStreamRef {
+ signature: Some(&signature),
+ };
+ signature_tlv_stream.write(&mut bytes).unwrap();
+
+ Ok(Invoice {
+ bytes,
+ contents: self.invoice,
+ signature,
+ })
+ }
+}
+
+/// An `Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
+///
+/// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
+/// directly after scanning a refund. It includes all the information needed to pay a recipient.
+///
+/// [`Offer`]: crate::offers::offer::Offer
+/// [`Refund`]: crate::offers::refund::Refund
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+pub struct Invoice {
+ bytes: Vec<u8>,
+ contents: InvoiceContents,
+ signature: Signature,
+}
+
+/// The contents of an [`Invoice`] for responding to either an [`Offer`] or a [`Refund`].
+///
+/// [`Offer`]: crate::offers::offer::Offer
+/// [`Refund`]: crate::offers::refund::Refund
+enum InvoiceContents {
+ /// Contents for an [`Invoice`] corresponding to an [`Offer`].
+ ///
+ /// [`Offer`]: crate::offers::offer::Offer
+ ForOffer {
+ invoice_request: InvoiceRequestContents,
+ fields: InvoiceFields,
+ },
+ /// Contents for an [`Invoice`] corresponding to a [`Refund`].
+ ///
+ /// [`Refund`]: crate::offers::refund::Refund
+ ForRefund {
+ refund: RefundContents,
+ fields: InvoiceFields,
+ },
+}
+
+/// Invoice-specific fields for an `invoice` message.
+struct InvoiceFields {
+ payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+ created_at: Duration,
+ relative_expiry: Option<Duration>,
+ payment_hash: PaymentHash,
+ amount_msats: u64,
+ fallbacks: Option<Vec<FallbackAddress>>,
+ features: Bolt12InvoiceFeatures,
+ signing_pubkey: PublicKey,
+}
+
+impl Invoice {
+ /// Paths to the recipient originating from publicly reachable nodes, including information
+ /// needed for routing payments across them.
+ ///
+ /// Blinded paths provide recipient privacy by obfuscating its node id. Note, however, that this
+ /// privacy is lost if a public node id is used for [`Invoice::signing_pubkey`].
+ pub fn payment_paths(&self) -> &[(BlindedPath, BlindedPayInfo)] {
+ &self.contents.fields().payment_paths[..]
+ }
+
+ /// Duration since the Unix epoch when the invoice was created.
+ pub fn created_at(&self) -> Duration {
+ self.contents.fields().created_at
+ }
+
+ /// Duration since [`Invoice::created_at`] when the invoice has expired and therefore should no
+ /// longer be paid.
+ pub fn relative_expiry(&self) -> Duration {
+ self.contents.fields().relative_expiry.unwrap_or(DEFAULT_RELATIVE_EXPIRY)
+ }
+
+ /// Whether the invoice has expired.
+ #[cfg(feature = "std")]
+ pub fn is_expired(&self) -> bool {
+ let absolute_expiry = self.created_at().checked_add(self.relative_expiry());
+ match absolute_expiry {
+ Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+ Ok(elapsed) => elapsed > seconds_from_epoch,
+ Err(_) => false,
+ },
+ None => false,
+ }
+ }
+
+ /// SHA256 hash of the payment preimage that will be given in return for paying the invoice.
+ pub fn payment_hash(&self) -> PaymentHash {
+ self.contents.fields().payment_hash
+ }
+
+ /// The minimum amount required for a successful payment of the invoice.
+ pub fn amount_msats(&self) -> u64 {
+ self.contents.fields().amount_msats
+ }
+
+ /// Fallback addresses for paying the invoice on-chain, in order of most-preferred to
+ /// least-preferred.
+ pub fn fallbacks(&self) -> Vec<Address> {
+ let network = match self.network() {
+ None => return Vec::new(),
+ Some(network) => network,
+ };
+
+ let to_valid_address = |address: &FallbackAddress| {
+ let version = match WitnessVersion::try_from(address.version) {
+ Ok(version) => version,
+ Err(_) => return None,
+ };
+
+ let program = &address.program;
+ if program.len() < 2 || program.len() > 40 {
+ return None;
+ }
+
+ let address = Address {
+ payload: Payload::WitnessProgram {
+ version,
+ program: address.program.clone(),
+ },
+ network,
+ };
+
+ if !address.is_standard() && version == WitnessVersion::V0 {
+ return None;
+ }
+
+ Some(address)
+ };
+
+ self.contents.fields().fallbacks
+ .as_ref()
+ .map(|fallbacks| fallbacks.iter().filter_map(to_valid_address).collect())
+ .unwrap_or_else(Vec::new)
+ }
+
+ fn network(&self) -> Option<Network> {
+ let chain = self.contents.chain();
+ if chain == ChainHash::using_genesis_block(Network::Bitcoin) {
+ Some(Network::Bitcoin)
+ } else if chain == ChainHash::using_genesis_block(Network::Testnet) {
+ Some(Network::Testnet)
+ } else if chain == ChainHash::using_genesis_block(Network::Signet) {
+ Some(Network::Signet)
+ } else if chain == ChainHash::using_genesis_block(Network::Regtest) {
+ Some(Network::Regtest)
+ } else {
+ None
+ }
+ }
+
+ /// Features pertaining to paying an invoice.
+ pub fn features(&self) -> &Bolt12InvoiceFeatures {
+ &self.contents.fields().features
+ }
+
+ /// The public key used to sign invoices.
+ pub fn signing_pubkey(&self) -> PublicKey {
+ self.contents.fields().signing_pubkey
+ }
+
+ /// Signature of the invoice using [`Invoice::signing_pubkey`].
+ pub fn signature(&self) -> Signature {
+ self.signature
+ }
+
+ #[cfg(test)]
+ fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
+ let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
+ self.contents.as_tlv_stream();
+ let signature_tlv_stream = SignatureTlvStreamRef {
+ signature: Some(&self.signature),
+ };
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
+ signature_tlv_stream)
+ }
+}
+
+impl InvoiceContents {
+ /// Whether the original offer or refund has expired.
+ #[cfg(feature = "std")]
+ fn is_offer_or_refund_expired(&self) -> bool {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.offer.is_expired(),
+ InvoiceContents::ForRefund { refund, .. } => refund.is_expired(),
+ }
+ }
+
+ fn chain(&self) -> ChainHash {
+ match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.chain(),
+ InvoiceContents::ForRefund { refund, .. } => refund.chain(),
+ }
+ }
+
+ fn fields(&self) -> &InvoiceFields {
+ match self {
+ InvoiceContents::ForOffer { fields, .. } => fields,
+ InvoiceContents::ForRefund { fields, .. } => fields,
+ }
+ }
+
+ fn fields_mut(&mut self) -> &mut InvoiceFields {
+ match self {
+ InvoiceContents::ForOffer { fields, .. } => fields,
+ InvoiceContents::ForRefund { fields, .. } => fields,
+ }
+ }
+
+ fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
+ let (payer, offer, invoice_request) = match self {
+ InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
+ InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
+ };
+ let invoice = self.fields().as_tlv_stream();
+
+ (payer, offer, invoice_request, invoice)
+ }
+}
+
+impl InvoiceFields {
+ fn as_tlv_stream(&self) -> InvoiceTlvStreamRef {
+ let features = {
+ if self.features == Bolt12InvoiceFeatures::empty() { None }
+ else { Some(&self.features) }
+ };
+
+ InvoiceTlvStreamRef {
+ paths: Some(Iterable(self.payment_paths.iter().map(|(path, _)| path))),
+ blindedpay: Some(Iterable(self.payment_paths.iter().map(|(_, payinfo)| payinfo))),
+ created_at: Some(self.created_at.as_secs()),
+ relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
+ payment_hash: Some(&self.payment_hash),
+ amount: Some(self.amount_msats),
+ fallbacks: self.fallbacks.as_ref(),
+ features,
+ node_id: Some(&self.signing_pubkey),
+ }
+ }
+}
+
+impl Writeable for Invoice {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ WithoutLength(&self.bytes).write(writer)
+ }
+}
+
+impl Writeable for InvoiceContents {
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ self.as_tlv_stream().write(writer)
+ }
+}
+
+impl TryFrom<Vec<u8>> for Invoice {
+ type Error = ParseError;
+
+ fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+ let parsed_invoice = ParsedMessage::<FullInvoiceTlvStream>::try_from(bytes)?;
+ Invoice::try_from(parsed_invoice)
+ }
+}
+
+tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
+ (160, paths: (Vec<BlindedPath>, WithoutLength, Iterable<'a, BlindedPathIter<'a>, BlindedPath>)),
+ (162, blindedpay: (Vec<BlindedPayInfo>, WithoutLength, Iterable<'a, BlindedPayInfoIter<'a>, BlindedPayInfo>)),
+ (164, created_at: (u64, HighZeroBytesDroppedBigSize)),
+ (166, relative_expiry: (u32, HighZeroBytesDroppedBigSize)),
+ (168, payment_hash: PaymentHash),
+ (170, amount: (u64, HighZeroBytesDroppedBigSize)),
+ (172, fallbacks: (Vec<FallbackAddress>, WithoutLength)),
+ (174, features: (Bolt12InvoiceFeatures, WithoutLength)),
+ (176, node_id: PublicKey),
+});
+
+type BlindedPathIter<'a> = core::iter::Map<
+ core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
+ for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPath,
+>;
+
+type BlindedPayInfoIter<'a> = core::iter::Map<
+ core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
+ for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPayInfo,
+>;
+
+/// Information needed to route a payment across a [`BlindedPath`].
+#[derive(Clone, Debug, PartialEq)]
+pub struct BlindedPayInfo {
+ fee_base_msat: u32,
+ fee_proportional_millionths: u32,
+ cltv_expiry_delta: u16,
+ htlc_minimum_msat: u64,
+ htlc_maximum_msat: u64,
+ features: BlindedHopFeatures,
+}
+
+impl_writeable!(BlindedPayInfo, {
+ fee_base_msat,
+ fee_proportional_millionths,
+ cltv_expiry_delta,
+ htlc_minimum_msat,
+ htlc_maximum_msat,
+ features
+});
+
+/// Wire representation for an on-chain fallback address.
+#[derive(Clone, Debug, PartialEq)]
+pub(super) struct FallbackAddress {
+ version: u8,
+ program: Vec<u8>,
+}
+
+impl_writeable!(FallbackAddress, { version, program });
+
+type FullInvoiceTlvStream =
+ (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
+
+#[cfg(test)]
+type FullInvoiceTlvStreamRef<'a> = (
+ PayerTlvStreamRef<'a>,
+ OfferTlvStreamRef<'a>,
+ InvoiceRequestTlvStreamRef<'a>,
+ InvoiceTlvStreamRef<'a>,
+ SignatureTlvStreamRef<'a>,
+);
+
+impl SeekReadable for FullInvoiceTlvStream {
+ fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
+ let payer = SeekReadable::read(r)?;
+ let offer = SeekReadable::read(r)?;
+ let invoice_request = SeekReadable::read(r)?;
+ let invoice = SeekReadable::read(r)?;
+ let signature = SeekReadable::read(r)?;
+
+ Ok((payer, offer, invoice_request, invoice, signature))
+ }
+}
+
+type PartialInvoiceTlvStream =
+ (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream);
+
+type PartialInvoiceTlvStreamRef<'a> = (
+ PayerTlvStreamRef<'a>,
+ OfferTlvStreamRef<'a>,
+ InvoiceRequestTlvStreamRef<'a>,
+ InvoiceTlvStreamRef<'a>,
+);
+
+impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Invoice {
+ type Error = ParseError;
+
+ fn try_from(invoice: ParsedMessage<FullInvoiceTlvStream>) -> Result<Self, Self::Error> {
+ let ParsedMessage { bytes, tlv_stream } = invoice;
+ let (
+ payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
+ SignatureTlvStream { signature },
+ ) = tlv_stream;
+ let contents = InvoiceContents::try_from(
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
+ )?;
+
+ let signature = match signature {
+ None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ Some(signature) => signature,
+ };
+ let pubkey = contents.fields().signing_pubkey;
+ merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, pubkey)?;
+
+ Ok(Invoice { bytes, contents, signature })
+ }
+}
+
+impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
+ type Error = SemanticError;
+
+ fn try_from(tlv_stream: PartialInvoiceTlvStream) -> Result<Self, Self::Error> {
+ let (
+ payer_tlv_stream,
+ offer_tlv_stream,
+ invoice_request_tlv_stream,
+ InvoiceTlvStream {
+ paths, blindedpay, created_at, relative_expiry, payment_hash, amount, fallbacks,
+ features, node_id,
+ },
+ ) = tlv_stream;
+
+ let payment_paths = match (paths, blindedpay) {
+ (None, _) => return Err(SemanticError::MissingPaths),
+ (_, None) => return Err(SemanticError::InvalidPayInfo),
+ (Some(paths), _) if paths.is_empty() => return Err(SemanticError::MissingPaths),
+ (Some(paths), Some(blindedpay)) if paths.len() != blindedpay.len() => {
+ return Err(SemanticError::InvalidPayInfo);
+ },
+ (Some(paths), Some(blindedpay)) => {
+ paths.into_iter().zip(blindedpay.into_iter()).collect::<Vec<_>>()
+ },
+ };
+
+ let created_at = match created_at {
+ None => return Err(SemanticError::MissingCreationTime),
+ Some(timestamp) => Duration::from_secs(timestamp),
+ };
+
+ let relative_expiry = relative_expiry
+ .map(Into::<u64>::into)
+ .map(Duration::from_secs);
+
+ let payment_hash = match payment_hash {
+ None => return Err(SemanticError::MissingPaymentHash),
+ Some(payment_hash) => payment_hash,
+ };
+
+ let amount_msats = match amount {
+ None => return Err(SemanticError::MissingAmount),
+ Some(amount) => amount,
+ };
+
+ let features = features.unwrap_or_else(Bolt12InvoiceFeatures::empty);
+
+ let signing_pubkey = match node_id {
+ None => return Err(SemanticError::MissingSigningPubkey),
+ Some(node_id) => node_id,
+ };
+
+ let fields = InvoiceFields {
+ payment_paths, created_at, relative_expiry, payment_hash, amount_msats, fallbacks,
+ features, signing_pubkey,
+ };
+
+ match offer_tlv_stream.node_id {
+ Some(expected_signing_pubkey) => {
+ if fields.signing_pubkey != expected_signing_pubkey {
+ return Err(SemanticError::InvalidSigningPubkey);
+ }
+
+ let invoice_request = InvoiceRequestContents::try_from(
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
+ )?;
+ Ok(InvoiceContents::ForOffer { invoice_request, fields })
+ },
+ None => {
+ let refund = RefundContents::try_from(
+ (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
+ )?;
+ Ok(InvoiceContents::ForRefund { refund, fields })
+ },
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
+
+ use bitcoin::blockdata::script::Script;
+ use bitcoin::hashes::Hash;
+ use bitcoin::network::constants::Network;
+ use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey, self};
+ use bitcoin::secp256k1::schnorr::Signature;
+ use bitcoin::util::address::{Address, Payload, WitnessVersion};
+ use bitcoin::util::schnorr::TweakedPublicKey;
+ use core::convert::{Infallible, TryFrom};
+ use core::time::Duration;
+ use crate::ln::PaymentHash;
+ use crate::ln::msgs::DecodeError;
+ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
+ use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
+ use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
+ use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef};
+ use crate::offers::parse::{ParseError, SemanticError};
+ use crate::offers::payer::PayerTlvStreamRef;
+ use crate::offers::refund::RefundBuilder;
+ use crate::onion_message::{BlindedHop, BlindedPath};
+ use crate::util::ser::{BigSize, Iterable, Writeable};
+
+ fn payer_keys() -> KeyPair {
+ let secp_ctx = Secp256k1::new();
+ KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
+ }
+
+ fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
+ let secp_ctx = Secp256k1::new();
+ let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ }
+
+ fn payer_pubkey() -> PublicKey {
+ payer_keys().public_key()
+ }
+
+ fn recipient_keys() -> KeyPair {
+ let secp_ctx = Secp256k1::new();
+ KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
+ }
+
+ fn recipient_sign(digest: &Message) -> Result<Signature, Infallible> {
+ let secp_ctx = Secp256k1::new();
+ let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
+ Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
+ }
+
+ fn recipient_pubkey() -> PublicKey {
+ recipient_keys().public_key()
+ }
+
+ fn pubkey(byte: u8) -> PublicKey {
+ let secp_ctx = Secp256k1::new();
+ PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
+ }
+
+ fn privkey(byte: u8) -> SecretKey {
+ SecretKey::from_slice(&[byte; 32]).unwrap()
+ }
+
+ trait ToBytes {
+ fn to_bytes(&self) -> Vec<u8>;
+ }
+
+ impl<'a> ToBytes for FullInvoiceTlvStreamRef<'a> {
+ fn to_bytes(&self) -> Vec<u8> {
+ let mut buffer = Vec::new();
+ self.0.write(&mut buffer).unwrap();
+ self.1.write(&mut buffer).unwrap();
+ self.2.write(&mut buffer).unwrap();
+ self.3.write(&mut buffer).unwrap();
+ self.4.write(&mut buffer).unwrap();
+ buffer
+ }
+ }
+
+ fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
+ let paths = vec![
+ BlindedPath {
+ introduction_node_id: pubkey(40),
+ blinding_point: pubkey(41),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
+ BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
+ ],
+ },
+ BlindedPath {
+ introduction_node_id: pubkey(40),
+ blinding_point: pubkey(41),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
+ BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
+ ],
+ },
+ ];
+
+ let payinfo = vec![
+ BlindedPayInfo {
+ fee_base_msat: 1,
+ fee_proportional_millionths: 1_000,
+ cltv_expiry_delta: 42,
+ htlc_minimum_msat: 100,
+ htlc_maximum_msat: 1_000_000_000_000,
+ features: BlindedHopFeatures::empty(),
+ },
+ BlindedPayInfo {
+ fee_base_msat: 1,
+ fee_proportional_millionths: 1_000,
+ cltv_expiry_delta: 42,
+ htlc_minimum_msat: 100,
+ htlc_maximum_msat: 1_000_000_000_000,
+ features: BlindedHopFeatures::empty(),
+ },
+ ];
+
+ paths.into_iter().zip(payinfo.into_iter()).collect()
+ }
+
+ fn payment_hash() -> PaymentHash {
+ PaymentHash([42; 32])
+ }
+
+ fn now() -> Duration {
+ std::time::SystemTime::now()
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
+ .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH")
+ }
+
+ #[test]
+ fn builds_invoice_for_offer_with_defaults() {
+ let payment_paths = payment_paths();
+ let payment_hash = payment_hash();
+ let now = now();
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths.clone(), payment_hash, now).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ assert_eq!(invoice.bytes, buffer.as_slice());
+ assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
+ assert_eq!(invoice.created_at(), now);
+ assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
+ #[cfg(feature = "std")]
+ assert!(!invoice.is_expired());
+ assert_eq!(invoice.payment_hash(), payment_hash);
+ assert_eq!(invoice.amount_msats(), 1000);
+ assert_eq!(invoice.fallbacks(), vec![]);
+ assert_eq!(invoice.features(), &Bolt12InvoiceFeatures::empty());
+ assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
+ assert!(
+ merkle::verify_signature(
+ &invoice.signature, SIGNATURE_TAG, &invoice.bytes, recipient_pubkey()
+ ).is_ok()
+ );
+
+ assert_eq!(
+ invoice.as_tlv_stream(),
+ (
+ PayerTlvStreamRef { metadata: Some(&vec![1; 32]) },
+ OfferTlvStreamRef {
+ chains: None,
+ metadata: None,
+ currency: None,
+ amount: Some(1000),
+ description: Some(&String::from("foo")),
+ features: None,
+ absolute_expiry: None,
+ paths: None,
+ issuer: None,
+ quantity_max: None,
+ node_id: Some(&recipient_pubkey()),
+ },
+ InvoiceRequestTlvStreamRef {
+ chain: None,
+ amount: None,
+ features: None,
+ quantity: None,
+ payer_id: Some(&payer_pubkey()),
+ payer_note: None,
+ },
+ InvoiceTlvStreamRef {
+ paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
+ blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+ created_at: Some(now.as_secs()),
+ relative_expiry: None,
+ payment_hash: Some(&payment_hash),
+ amount: Some(1000),
+ fallbacks: None,
+ features: None,
+ node_id: Some(&recipient_pubkey()),
+ },
+ SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
+ ),
+ );
+
+ if let Err(e) = Invoice::try_from(buffer) {
+ panic!("error parsing invoice: {:?}", e);
+ }
+ }
+
+ #[test]
+ fn builds_invoice_for_refund_with_defaults() {
+ let payment_paths = payment_paths();
+ let payment_hash = payment_hash();
+ let now = now();
+ let invoice = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+ .build().unwrap()
+ .respond_with(payment_paths.clone(), payment_hash, recipient_pubkey(), now).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ assert_eq!(invoice.bytes, buffer.as_slice());
+ assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
+ assert_eq!(invoice.created_at(), now);
+ assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
+ #[cfg(feature = "std")]
+ assert!(!invoice.is_expired());
+ assert_eq!(invoice.payment_hash(), payment_hash);
+ assert_eq!(invoice.amount_msats(), 1000);
+ assert_eq!(invoice.fallbacks(), vec![]);
+ assert_eq!(invoice.features(), &Bolt12InvoiceFeatures::empty());
+ assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
+ assert!(
+ merkle::verify_signature(
+ &invoice.signature, SIGNATURE_TAG, &invoice.bytes, recipient_pubkey()
+ ).is_ok()
+ );
+
+ assert_eq!(
+ invoice.as_tlv_stream(),
+ (
+ PayerTlvStreamRef { metadata: Some(&vec![1; 32]) },
+ OfferTlvStreamRef {
+ chains: None,
+ metadata: None,
+ currency: None,
+ amount: None,
+ description: Some(&String::from("foo")),
+ features: None,
+ absolute_expiry: None,
+ paths: None,
+ issuer: None,
+ quantity_max: None,
+ node_id: None,
+ },
+ InvoiceRequestTlvStreamRef {
+ chain: None,
+ amount: Some(1000),
+ features: None,
+ quantity: None,
+ payer_id: Some(&payer_pubkey()),
+ payer_note: None,
+ },
+ InvoiceTlvStreamRef {
+ paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
+ blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+ created_at: Some(now.as_secs()),
+ relative_expiry: None,
+ payment_hash: Some(&payment_hash),
+ amount: Some(1000),
+ fallbacks: None,
+ features: None,
+ node_id: Some(&recipient_pubkey()),
+ },
+ SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
+ ),
+ );
+
+ if let Err(e) = Invoice::try_from(buffer) {
+ panic!("error parsing invoice: {:?}", e);
+ }
+ }
+
+ #[cfg(feature = "std")]
+ #[test]
+ fn builds_invoice_from_refund_with_expiration() {
+ let future_expiry = Duration::from_secs(u64::max_value());
+ let past_expiry = Duration::from_secs(0);
+
+ if let Err(e) = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+ .absolute_expiry(future_expiry)
+ .build().unwrap()
+ .respond_with(payment_paths(), payment_hash(), recipient_pubkey(), now()).unwrap()
+ .build()
+ {
+ panic!("error building invoice: {:?}", e);
+ }
+
+ match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+ .absolute_expiry(past_expiry)
+ .build().unwrap()
+ .respond_with(payment_paths(), payment_hash(), recipient_pubkey(), now()).unwrap()
+ .build()
+ {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+ }
+ }
+
+ #[test]
+ fn builds_invoice_with_relative_expiry() {
+ let now = now();
+ let one_hour = Duration::from_secs(3600);
+
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now).unwrap()
+ .relative_expiry(one_hour.as_secs() as u32)
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
+ #[cfg(feature = "std")]
+ assert!(!invoice.is_expired());
+ assert_eq!(invoice.relative_expiry(), one_hour);
+ assert_eq!(tlv_stream.relative_expiry, Some(one_hour.as_secs() as u32));
+
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now - one_hour).unwrap()
+ .relative_expiry(one_hour.as_secs() as u32 - 1)
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
+ #[cfg(feature = "std")]
+ assert!(invoice.is_expired());
+ assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
+ assert_eq!(tlv_stream.relative_expiry, Some(one_hour.as_secs() as u32 - 1));
+ }
+
+ #[test]
+ fn builds_invoice_with_amount_from_request() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .amount_msats(1001).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
+ assert_eq!(invoice.amount_msats(), 1001);
+ assert_eq!(tlv_stream.amount, Some(1001));
+ }
+
+ #[test]
+ fn builds_invoice_with_fallback_address() {
+ let script = Script::new();
+ let pubkey = bitcoin::util::key::PublicKey::new(recipient_pubkey());
+ let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0;
+ let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey);
+
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .fallback_v0_p2wsh(&script.wscript_hash())
+ .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap())
+ .fallback_v1_p2tr_tweaked(&tweaked_pubkey)
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
+ assert_eq!(
+ invoice.fallbacks(),
+ vec![
+ Address::p2wsh(&script, Network::Bitcoin),
+ Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap(),
+ Address::p2tr_tweaked(tweaked_pubkey, Network::Bitcoin),
+ ],
+ );
+ assert_eq!(
+ tlv_stream.fallbacks,
+ Some(&vec![
+ FallbackAddress {
+ version: WitnessVersion::V0.to_num(),
+ program: Vec::from(&script.wscript_hash().into_inner()[..]),
+ },
+ FallbackAddress {
+ version: WitnessVersion::V0.to_num(),
+ program: Vec::from(&pubkey.wpubkey_hash().unwrap().into_inner()[..]),
+ },
+ FallbackAddress {
+ version: WitnessVersion::V1.to_num(),
+ program: Vec::from(&tweaked_pubkey.serialize()[..]),
+ },
+ ])
+ );
+ }
+
+ #[test]
+ fn builds_invoice_with_allow_mpp() {
+ let mut features = Bolt12InvoiceFeatures::empty();
+ features.set_basic_mpp_optional();
+
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .allow_mpp()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
+ assert_eq!(invoice.features(), &features);
+ assert_eq!(tlv_stream.features, Some(&features));
+ }
+
+ #[test]
+ fn fails_signing_invoice() {
+ match OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(|_| Err(()))
+ {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, SignError::Signing(())),
+ }
+
+ match OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign)
+ {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, SignError::Verification(secp256k1::Error::InvalidSignature)),
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_payment_paths() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ if let Err(e) = Invoice::try_from(buffer) {
+ panic!("error parsing invoice: {:?}", e);
+ }
+
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.paths = None;
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+ }
+
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.blindedpay = None;
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+ }
+
+ let empty_payment_paths = vec![];
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+ }
+
+ let mut payment_paths = payment_paths();
+ payment_paths.pop();
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_created_at() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ if let Err(e) = Invoice::try_from(buffer) {
+ panic!("error parsing invoice: {:?}", e);
+ }
+
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.created_at = None;
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => {
+ assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingCreationTime));
+ },
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_relative_expiry() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .relative_expiry(3600)
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ match Invoice::try_from(buffer) {
+ Ok(invoice) => assert_eq!(invoice.relative_expiry(), Duration::from_secs(3600)),
+ Err(e) => panic!("error parsing invoice: {:?}", e),
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_payment_hash() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ if let Err(e) = Invoice::try_from(buffer) {
+ panic!("error parsing invoice: {:?}", e);
+ }
+
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.payment_hash = None;
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => {
+ assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaymentHash));
+ },
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_amount() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ if let Err(e) = Invoice::try_from(buffer) {
+ panic!("error parsing invoice: {:?}", e);
+ }
+
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.amount = None;
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_allow_mpp() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .allow_mpp()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ match Invoice::try_from(buffer) {
+ Ok(invoice) => {
+ let mut features = Bolt12InvoiceFeatures::empty();
+ features.set_basic_mpp_optional();
+ assert_eq!(invoice.features(), &features);
+ },
+ Err(e) => panic!("error parsing invoice: {:?}", e),
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_fallback_address() {
+ let script = Script::new();
+ let pubkey = bitcoin::util::key::PublicKey::new(recipient_pubkey());
+ let x_only_pubkey = XOnlyPublicKey::from_keypair(&recipient_keys()).0;
+ let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(x_only_pubkey);
+
+ let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap();
+ let invoice_request = offer
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap();
+ let mut unsigned_invoice = invoice_request
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .fallback_v0_p2wsh(&script.wscript_hash())
+ .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap())
+ .fallback_v1_p2tr_tweaked(&tweaked_pubkey)
+ .build().unwrap();
+
+ // Only standard addresses will be included.
+ let mut fallbacks = unsigned_invoice.invoice.fields_mut().fallbacks.as_mut().unwrap();
+ // Non-standard addresses
+ fallbacks.push(FallbackAddress { version: 1, program: vec![0u8; 41] });
+ fallbacks.push(FallbackAddress { version: 2, program: vec![0u8; 1] });
+ fallbacks.push(FallbackAddress { version: 17, program: vec![0u8; 40] });
+ // Standard address
+ fallbacks.push(FallbackAddress { version: 1, program: vec![0u8; 33] });
+ fallbacks.push(FallbackAddress { version: 2, program: vec![0u8; 40] });
+
+ let invoice = unsigned_invoice.sign(recipient_sign).unwrap();
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ match Invoice::try_from(buffer) {
+ Ok(invoice) => {
+ assert_eq!(
+ invoice.fallbacks(),
+ vec![
+ Address::p2wsh(&script, Network::Bitcoin),
+ Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap(),
+ Address::p2tr_tweaked(tweaked_pubkey, Network::Bitcoin),
+ Address {
+ payload: Payload::WitnessProgram {
+ version: WitnessVersion::V1,
+ program: vec![0u8; 33],
+ },
+ network: Network::Bitcoin,
+ },
+ Address {
+ payload: Payload::WitnessProgram {
+ version: WitnessVersion::V2,
+ program: vec![0u8; 40],
+ },
+ network: Network::Bitcoin,
+ },
+ ],
+ );
+ },
+ Err(e) => panic!("error parsing invoice: {:?}", e),
+ }
+ }
+
+ #[test]
+ fn parses_invoice_with_node_id() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ if let Err(e) = Invoice::try_from(buffer) {
+ panic!("error parsing invoice: {:?}", e);
+ }
+
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.node_id = None;
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => {
+ assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+ },
+ }
+
+ let invalid_pubkey = payer_pubkey();
+ let mut tlv_stream = invoice.as_tlv_stream();
+ tlv_stream.3.node_id = Some(&invalid_pubkey);
+
+ match Invoice::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => {
+ assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidSigningPubkey));
+ },
+ }
+ }
+
+ #[test]
+ fn fails_parsing_invoice_without_signature() {
+ let mut buffer = Vec::new();
+ OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .invoice
+ .write(&mut buffer).unwrap();
+
+ match Invoice::try_from(buffer) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+ }
+ }
+
+ #[test]
+ fn fails_parsing_invoice_with_invalid_signature() {
+ let mut invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+ let last_signature_byte = invoice.bytes.last_mut().unwrap();
+ *last_signature_byte = last_signature_byte.wrapping_add(1);
+
+ let mut buffer = Vec::new();
+ invoice.write(&mut buffer).unwrap();
+
+ match Invoice::try_from(buffer) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => {
+ assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
+ },
+ }
+ }
+
+ #[test]
+ fn fails_parsing_invoice_with_extra_tlv_records() {
+ let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap()
+ .respond_with(payment_paths(), payment_hash(), now()).unwrap()
+ .build().unwrap()
+ .sign(recipient_sign).unwrap();
+
+ let mut encoded_invoice = Vec::new();
+ invoice.write(&mut encoded_invoice).unwrap();
+ BigSize(1002).write(&mut encoded_invoice).unwrap();
+ BigSize(32).write(&mut encoded_invoice).unwrap();
+ [42u8; 32].write(&mut encoded_invoice).unwrap();
+
+ match Invoice::try_from(encoded_invoice) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+ }
+ }
+}
//!
//! An [`InvoiceRequest`] can be built from a parsed [`Offer`] as an "offer to be paid". It is
//! typically constructed by a customer and sent to the merchant who had published the corresponding
-//! offer. The recipient of the request responds with an `Invoice`.
+//! offer. The recipient of the request responds with an [`Invoice`].
//!
//! For an "offer for money" (e.g., refund, ATM withdrawal), where an offer doesn't exist as a
//! precursor, see [`Refund`].
//!
+//! [`Invoice`]: crate::offers::invoice::Invoice
//! [`Refund`]: crate::offers::refund::Refund
//!
//! ```ignore
use bitcoin::secp256k1::schnorr::Signature;
use core::convert::TryFrom;
use crate::io;
+use crate::ln::PaymentHash;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::msgs::DecodeError;
+use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
+use crate::onion_message::BlindedPath;
use crate::util::ser::{HighZeroBytesDroppedBigSize, SeekReadable, WithoutLength, Writeable, Writer};
use crate::util::string::PrintableString;
}
}
-/// An `InvoiceRequest` is a request for an `Invoice` formulated from an [`Offer`].
+/// An `InvoiceRequest` is a request for an [`Invoice`] formulated from an [`Offer`].
///
/// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request
/// specifies these such that its recipient can send an invoice for payment.
///
+/// [`Invoice`]: crate::offers::invoice::Invoice
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
pub struct InvoiceRequest {
pub(super) bytes: Vec<u8>,
- contents: InvoiceRequestContents,
+ pub(super) contents: InvoiceRequestContents,
signature: Signature,
}
-/// The contents of an [`InvoiceRequest`], which may be shared with an `Invoice`.
+/// The contents of an [`InvoiceRequest`], which may be shared with an [`Invoice`].
+///
+/// [`Invoice`]: crate::offers::invoice::Invoice
#[derive(Clone, Debug)]
pub(super) struct InvoiceRequestContents {
payer: PayerContents,
- offer: OfferContents,
+ pub(super) offer: OfferContents,
chain: Option<ChainHash>,
amount_msats: Option<u64>,
features: InvoiceRequestFeatures,
self.signature
}
+ /// Creates an [`Invoice`] for the request with the given required fields.
+ ///
+ /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
+ /// calling this method in `std` builds. For `no-std` builds, a final [`Duration`] parameter
+ /// must be given, which is used to set [`Invoice::created_at`] since [`std::time::SystemTime`]
+ /// is not available.
+ ///
+ /// The caller is expected to remember the preimage of `payment_hash` in order to claim a payment
+ /// for the invoice.
+ ///
+ /// The `payment_paths` parameter is useful for maintaining the payment recipient's privacy. It
+ /// must contain one or more elements ordered from most-preferred to least-preferred, if there's
+ /// a preference. Note, however, that any privacy is lost if a public node id was used for
+ /// [`Offer::signing_pubkey`].
+ ///
+ /// Errors if the request contains unknown required features.
+ ///
+ /// [`Duration`]: core::time::Duration
+ /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
+ pub fn respond_with(
+ &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ #[cfg(any(test, not(feature = "std")))]
+ created_at: core::time::Duration
+ ) -> Result<InvoiceBuilder, SemanticError> {
+ if self.features().requires_unknown_bits() {
+ return Err(SemanticError::UnknownRequiredFeatures);
+ }
+
+ #[cfg(all(not(test), feature = "std"))]
+ let created_at = std::time::SystemTime::now()
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
+ .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
+
+ InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
+ }
+
#[cfg(test)]
fn as_tlv_stream(&self) -> FullInvoiceRequestTlvStreamRef {
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) =
}
impl InvoiceRequestContents {
- fn chain(&self) -> ChainHash {
+ pub(super) fn chain(&self) -> ChainHash {
self.chain.unwrap_or_else(|| self.offer.implied_chain())
}
#[test]
fn builds_invoice_request_with_quantity() {
+ let one = NonZeroU64::new(1).unwrap();
let ten = NonZeroU64::new(10).unwrap();
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
- .supported_quantity(Quantity::one())
+ .supported_quantity(Quantity::One)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
match OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
- .supported_quantity(Quantity::one())
+ .supported_quantity(Quantity::One)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.amount_msats(2_000).unwrap()
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
}
+
+ match OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .supported_quantity(Quantity::Bounded(one))
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build()
+ {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, SemanticError::MissingQuantity),
+ }
}
#[test]
#[test]
fn parses_invoice_request_with_quantity() {
+ let one = NonZeroU64::new(1).unwrap();
let ten = NonZeroU64::new(10).unwrap();
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
- .supported_quantity(Quantity::one())
+ .supported_quantity(Quantity::One)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
- .supported_quantity(Quantity::one())
+ .supported_quantity(Quantity::One)
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.amount_msats(2_000).unwrap()
Ok(_) => panic!("expected error"),
Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
}
+
+ let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
+ .amount_msats(1000)
+ .supported_quantity(Quantity::Bounded(one))
+ .build().unwrap()
+ .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build_unchecked()
+ .sign(payer_sign).unwrap();
+
+ let mut buffer = Vec::new();
+ invoice_request.write(&mut buffer).unwrap();
+
+ match InvoiceRequest::try_from(buffer) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingQuantity)),
+ }
}
#[test]
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
use crate::io;
-use crate::util::ser::{BigSize, Readable};
+use crate::util::ser::{BigSize, Readable, Writeable, Writer};
use crate::prelude::*;
/// Computes a merkle root hash for the given data, which must be a well-formed TLV stream
/// containing at least one TLV record.
fn root_hash(data: &[u8]) -> sha256::Hash {
- let mut tlv_stream = TlvStream::new(&data[..]).peekable();
let nonce_tag = tagged_hash_engine(sha256::Hash::from_engine({
+ let first_tlv_record = TlvStream::new(&data[..]).next().unwrap();
let mut engine = sha256::Hash::engine();
engine.input("LnNonce".as_bytes());
- engine.input(tlv_stream.peek().unwrap().record_bytes);
+ engine.input(first_tlv_record.record_bytes);
engine
}));
let leaf_tag = tagged_hash_engine(sha256::Hash::hash("LnLeaf".as_bytes()));
let branch_tag = tagged_hash_engine(sha256::Hash::hash("LnBranch".as_bytes()));
let mut leaves = Vec::new();
- for record in tlv_stream {
- if !SIGNATURE_TYPES.contains(&record.r#type) {
- leaves.push(tagged_hash_from_engine(leaf_tag.clone(), &record));
- leaves.push(tagged_hash_from_engine(nonce_tag.clone(), &record.type_bytes));
- }
+ let tlv_stream = TlvStream::new(&data[..]);
+ for record in tlv_stream.skip_signatures() {
+ leaves.push(tagged_hash_from_engine(leaf_tag.clone(), &record.record_bytes));
+ leaves.push(tagged_hash_from_engine(nonce_tag.clone(), &record.type_bytes));
}
// Calculate the merkle root hash in place.
data: io::Cursor::new(data),
}
}
+
+ fn skip_signatures(self) -> core::iter::Filter<TlvStream<'a>, fn(&TlvRecord) -> bool> {
+ self.filter(|record| !SIGNATURE_TYPES.contains(&record.r#type))
+ }
}
/// A slice into a [`TlvStream`] for a record.
record_bytes: &'a [u8],
}
-impl AsRef<[u8]> for TlvRecord<'_> {
- fn as_ref(&self) -> &[u8] { &self.record_bytes }
-}
-
impl<'a> Iterator for TlvStream<'a> {
type Item = TlvRecord<'a>;
}
}
+/// Encoding for a pre-serialized TLV stream that excludes any signature TLV records.
+///
+/// Panics if the wrapped bytes are not a well-formed TLV stream.
+pub(super) struct WithoutSignatures<'a>(pub &'a Vec<u8>);
+
+impl<'a> Writeable for WithoutSignatures<'a> {
+ #[inline]
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let tlv_stream = TlvStream::new(&self.0[..]);
+ for record in tlv_stream.skip_signatures() {
+ writer.write_all(record.record_bytes)?;
+ }
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
+ use super::{TlvStream, WithoutSignatures};
+
use bitcoin::hashes::{Hash, sha256};
use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey};
use core::convert::Infallible;
use crate::offers::offer::{Amount, OfferBuilder};
use crate::offers::invoice_request::InvoiceRequest;
use crate::offers::parse::Bech32Encode;
+ use crate::util::ser::Writeable;
#[test]
fn calculates_merkle_root_hash() {
);
}
+ #[test]
+ fn skips_encoding_signature_tlv_records() {
+ let secp_ctx = Secp256k1::new();
+ let recipient_pubkey = {
+ let secret_key = SecretKey::from_slice(&[41; 32]).unwrap();
+ KeyPair::from_secret_key(&secp_ctx, &secret_key).public_key()
+ };
+ let payer_keys = {
+ let secret_key = SecretKey::from_slice(&[42; 32]).unwrap();
+ KeyPair::from_secret_key(&secp_ctx, &secret_key)
+ };
+
+ let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey)
+ .amount_msats(100)
+ .build_unchecked()
+ .request_invoice(vec![0; 8], payer_keys.public_key()).unwrap()
+ .build_unchecked()
+ .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &payer_keys)))
+ .unwrap();
+
+ let mut bytes_without_signature = Vec::new();
+ WithoutSignatures(&invoice_request.bytes).write(&mut bytes_without_signature).unwrap();
+
+ assert_ne!(bytes_without_signature, invoice_request.bytes);
+ assert_eq!(
+ TlvStream::new(&bytes_without_signature).count(),
+ TlvStream::new(&invoice_request.bytes).count() - 1,
+ );
+ }
+
impl AsRef<[u8]> for InvoiceRequest {
fn as_ref(&self) -> &[u8] {
&self.bytes
//!
//! Offers are a flexible protocol for Lightning payments.
+pub mod invoice;
pub mod invoice_request;
mod merkle;
pub mod offer;
let offer = OfferContents {
chains: None, metadata: None, amount: None, description,
features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
- supported_quantity: Quantity::one(), signing_pubkey,
+ supported_quantity: Quantity::One, signing_pubkey,
};
OfferBuilder { offer }
}
}
/// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to
- /// [`Quantity::one`].
+ /// [`Quantity::One`].
///
/// Successive calls to this method will override the previous setting.
pub fn supported_quantity(mut self, quantity: Quantity) -> Self {
/// An `Offer` is a potentially long-lived proposal for payment of a good or service.
///
/// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
-/// customer may request an `Invoice` for a specific quantity and using an amount sufficient to
+/// customer may request an [`Invoice`] for a specific quantity and using an amount sufficient to
/// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
///
/// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
/// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
#[derive(Clone, Debug)]
pub struct Offer {
// The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
pub(super) contents: OfferContents,
}
-/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
+/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an [`Invoice`].
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
#[derive(Clone, Debug)]
pub(super) struct OfferContents {
chains: Option<Vec<ChainHash>>,
/// Whether the offer has expired.
#[cfg(feature = "std")]
pub fn is_expired(&self) -> bool {
- match self.absolute_expiry() {
- Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
- Ok(elapsed) => elapsed > seconds_from_epoch,
- Err(_) => false,
- },
- None => false,
- }
+ self.contents.is_expired()
}
/// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
/// The public key used by the recipient to sign invoices.
pub fn signing_pubkey(&self) -> PublicKey {
- self.contents.signing_pubkey
+ self.contents.signing_pubkey()
}
/// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which
self.chains().contains(&chain)
}
+ #[cfg(feature = "std")]
+ pub(super) fn is_expired(&self) -> bool {
+ match self.absolute_expiry {
+ Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+ Ok(elapsed) => elapsed > seconds_from_epoch,
+ Err(_) => false,
+ },
+ None => false,
+ }
+ }
+
pub fn amount(&self) -> Option<&Amount> {
self.amount.as_ref()
}
fn is_valid_quantity(&self, quantity: u64) -> bool {
match self.supported_quantity {
- Quantity::Bounded(n) => {
- let n = n.get();
- if n == 1 { false }
- else { quantity > 0 && quantity <= n }
- },
+ Quantity::Bounded(n) => quantity <= n.get(),
Quantity::Unbounded => quantity > 0,
+ Quantity::One => quantity == 1,
}
}
fn expects_quantity(&self) -> bool {
match self.supported_quantity {
- Quantity::Bounded(n) => n.get() != 1,
+ Quantity::Bounded(_) => true,
Quantity::Unbounded => true,
+ Quantity::One => false,
}
}
+ pub(super) fn signing_pubkey(&self) -> PublicKey {
+ self.signing_pubkey
+ }
+
pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
let (currency, amount) = match &self.amount {
None => (None, None),
/// Quantity of items supported by an [`Offer`].
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Quantity {
- /// Up to a specific number of items (inclusive).
+ /// Up to a specific number of items (inclusive). Use when more than one item can be requested
+ /// but is limited (e.g., because of per customer or inventory limits).
+ ///
+ /// May be used with `NonZeroU64::new(1)` but prefer to use [`Quantity::One`] if only one item
+ /// is supported.
Bounded(NonZeroU64),
- /// One or more items.
+ /// One or more items. Use when more than one item can be requested without any limit.
Unbounded,
+ /// Only one item. Use when only a single item can be requested.
+ One,
}
impl Quantity {
- /// The default quantity of one.
- pub fn one() -> Self {
- Quantity::Bounded(NonZeroU64::new(1).unwrap())
- }
-
fn to_tlv_record(&self) -> Option<u64> {
match self {
- Quantity::Bounded(n) => {
- let n = n.get();
- if n == 1 { None } else { Some(n) }
- },
+ Quantity::Bounded(n) => Some(n.get()),
Quantity::Unbounded => Some(0),
+ Quantity::One => None,
}
}
}
.map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
let supported_quantity = match quantity_max {
- None => Quantity::one(),
+ None => Quantity::One,
Some(0) => Quantity::Unbounded,
- Some(1) => return Err(SemanticError::InvalidQuantity),
Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
};
assert!(!offer.is_expired());
assert_eq!(offer.paths(), &[]);
assert_eq!(offer.issuer(), None);
- assert_eq!(offer.supported_quantity(), Quantity::one());
+ assert_eq!(offer.supported_quantity(), Quantity::One);
assert_eq!(offer.signing_pubkey(), pubkey(42));
assert_eq!(
#[test]
fn builds_offer_with_supported_quantity() {
+ let one = NonZeroU64::new(1).unwrap();
let ten = NonZeroU64::new(10).unwrap();
let offer = OfferBuilder::new("foo".into(), pubkey(42))
- .supported_quantity(Quantity::one())
+ .supported_quantity(Quantity::One)
.build()
.unwrap();
let tlv_stream = offer.as_tlv_stream();
- assert_eq!(offer.supported_quantity(), Quantity::one());
+ assert_eq!(offer.supported_quantity(), Quantity::One);
assert_eq!(tlv_stream.quantity_max, None);
let offer = OfferBuilder::new("foo".into(), pubkey(42))
assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
assert_eq!(tlv_stream.quantity_max, Some(10));
+ let offer = OfferBuilder::new("foo".into(), pubkey(42))
+ .supported_quantity(Quantity::Bounded(one))
+ .build()
+ .unwrap();
+ let tlv_stream = offer.as_tlv_stream();
+ assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
+ assert_eq!(tlv_stream.quantity_max, Some(1));
+
let offer = OfferBuilder::new("foo".into(), pubkey(42))
.supported_quantity(Quantity::Bounded(ten))
- .supported_quantity(Quantity::one())
+ .supported_quantity(Quantity::One)
.build()
.unwrap();
let tlv_stream = offer.as_tlv_stream();
- assert_eq!(offer.supported_quantity(), Quantity::one());
+ assert_eq!(offer.supported_quantity(), Quantity::One);
assert_eq!(tlv_stream.quantity_max, None);
}
#[test]
fn parses_offer_with_quantity() {
let offer = OfferBuilder::new("foo".into(), pubkey(42))
- .supported_quantity(Quantity::one())
+ .supported_quantity(Quantity::One)
.build()
.unwrap();
if let Err(e) = offer.to_string().parse::<Offer>() {
panic!("error parsing offer: {:?}", e);
}
- let mut tlv_stream = offer.as_tlv_stream();
- tlv_stream.quantity_max = Some(1);
-
- let mut encoded_offer = Vec::new();
- tlv_stream.write(&mut encoded_offer).unwrap();
-
- match Offer::try_from(encoded_offer) {
- Ok(_) => panic!("expected error"),
- Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity));
- },
+ let offer = OfferBuilder::new("foo".into(), pubkey(42))
+ .supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap()))
+ .build()
+ .unwrap();
+ if let Err(e) = offer.to_string().parse::<Offer>() {
+ panic!("error parsing offer: {:?}", e);
}
}
MissingDescription,
/// A signing pubkey was not provided.
MissingSigningPubkey,
+ /// A signing pubkey was provided but a different one was expected.
+ InvalidSigningPubkey,
/// A signing pubkey was provided but was not expected.
UnexpectedSigningPubkey,
/// A quantity was expected but was missing.
InvalidQuantity,
/// A quantity or quantity bounds was provided but was not expected.
UnexpectedQuantity,
+ /// Metadata was provided but was not expected.
+ UnexpectedMetadata,
/// Payer metadata was expected but was missing.
MissingPayerMetadata,
/// A payer id was expected but was missing.
MissingPayerId,
+ /// Blinded paths were expected but were missing.
+ MissingPaths,
+ /// The blinded payinfo given does not match the number of blinded path hops.
+ InvalidPayInfo,
+ /// An invoice creation time was expected but was missing.
+ MissingCreationTime,
+ /// An invoice payment hash was expected but was missing.
+ MissingPaymentHash,
/// A signature was expected but was missing.
MissingSignature,
}
//! Data structures and encoding for refunds.
//!
//! A [`Refund`] is an "offer for money" and is typically constructed by a merchant and presented
-//! directly to the customer. The recipient responds with an `Invoice` to be paid.
+//! directly to the customer. The recipient responds with an [`Invoice`] to be paid.
//!
//! This is an [`InvoiceRequest`] produced *not* in response to an [`Offer`].
//!
+//! [`Invoice`]: crate::offers::invoice::Invoice
//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
//! [`Offer`]: crate::offers::offer::Offer
//!
use core::str::FromStr;
use core::time::Duration;
use crate::io;
+use crate::ln::PaymentHash;
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
+use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
}
let refund = RefundContents {
- payer: PayerContents(metadata), metadata: None, description, absolute_expiry: None,
- issuer: None, paths: None, chain: None, amount_msats,
- features: InvoiceRequestFeatures::empty(), payer_id, payer_note: None,
+ payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None,
+ paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(),
+ quantity: None, payer_id, payer_note: None,
};
Ok(RefundBuilder { refund })
self
}
+ /// Sets [`Refund::quantity`] of items. This is purely for informational purposes. It is useful
+ /// when the refund pertains to an [`Invoice`] that paid for more than one item from an
+ /// [`Offer`] as specified by [`InvoiceRequest::quantity`].
+ ///
+ /// Successive calls to this method will override the previous setting.
+ ///
+ /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
+ /// [`Offer`]: crate::offers::offer::Offer
+ pub fn quantity(mut self, quantity: u64) -> Self {
+ self.refund.quantity = Some(quantity);
+ self
+ }
+
/// Sets the [`Refund::payer_note`].
///
/// Successive calls to this method will override the previous setting.
}
}
-/// A `Refund` is a request to send an `Invoice` without a preceding [`Offer`].
+/// A `Refund` is a request to send an [`Invoice`] without a preceding [`Offer`].
///
/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
/// recoup their funds. A refund may be used more generally as an "offer for money", such as with a
/// bitcoin ATM.
///
+/// [`Invoice`]: crate::offers::invoice::Invoice
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug)]
pub struct Refund {
- bytes: Vec<u8>,
- contents: RefundContents,
+ pub(super) bytes: Vec<u8>,
+ pub(super) contents: RefundContents,
}
-/// The contents of a [`Refund`], which may be shared with an `Invoice`.
+/// The contents of a [`Refund`], which may be shared with an [`Invoice`].
+///
+/// [`Invoice`]: crate::offers::invoice::Invoice
#[derive(Clone, Debug)]
-struct RefundContents {
+pub(super) struct RefundContents {
payer: PayerContents,
// offer fields
- metadata: Option<Vec<u8>>,
description: String,
absolute_expiry: Option<Duration>,
issuer: Option<String>,
chain: Option<ChainHash>,
amount_msats: u64,
features: InvoiceRequestFeatures,
+ quantity: Option<u64>,
payer_id: PublicKey,
payer_note: Option<String>,
}
/// Whether the refund has expired.
#[cfg(feature = "std")]
pub fn is_expired(&self) -> bool {
- match self.absolute_expiry() {
- Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
- Ok(elapsed) => elapsed > seconds_from_epoch,
- Err(_) => false,
- },
- None => false,
- }
+ self.contents.is_expired()
}
/// The issuer of the refund, possibly beginning with `user@domain` or `domain`. Intended to be
&self.contents.features
}
+ /// The quantity of an item that refund is for.
+ pub fn quantity(&self) -> Option<u64> {
+ self.contents.quantity
+ }
+
/// A public node id to send to in the case where there are no [`paths`]. Otherwise, a possibly
/// transient pubkey.
///
self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
}
+ /// Creates an [`Invoice`] for the refund with the given required fields.
+ ///
+ /// Unless [`InvoiceBuilder::relative_expiry`] is set, the invoice will expire two hours after
+ /// calling this method in `std` builds. For `no-std` builds, a final [`Duration`] parameter
+ /// must be given, which is used to set [`Invoice::created_at`] since [`std::time::SystemTime`]
+ /// is not available.
+ ///
+ /// The caller is expected to remember the preimage of `payment_hash` in order to
+ /// claim a payment for the invoice.
+ ///
+ /// The `signing_pubkey` is required to sign the invoice since refunds are not in response to an
+ /// offer, which does have a `signing_pubkey`.
+ ///
+ /// The `payment_paths` parameter is useful for maintaining the payment recipient's privacy. It
+ /// must contain one or more elements ordered from most-preferred to least-preferred, if there's
+ /// a preference. Note, however, that any privacy is lost if a public node id is used for
+ /// `signing_pubkey`.
+ ///
+ /// Errors if the request contains unknown required features.
+ ///
+ /// [`Invoice`]: crate::offers::invoice::Invoice
+ /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
+ pub fn respond_with(
+ &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ signing_pubkey: PublicKey,
+ #[cfg(any(test, not(feature = "std")))]
+ created_at: Duration
+ ) -> Result<InvoiceBuilder, SemanticError> {
+ if self.features().requires_unknown_bits() {
+ return Err(SemanticError::UnknownRequiredFeatures);
+ }
+
+ #[cfg(all(not(test), feature = "std"))]
+ let created_at = std::time::SystemTime::now()
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
+ .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
+
+ InvoiceBuilder::for_refund(self, payment_paths, created_at, payment_hash, signing_pubkey)
+ }
+
#[cfg(test)]
fn as_tlv_stream(&self) -> RefundTlvStreamRef {
self.contents.as_tlv_stream()
}
impl RefundContents {
- fn chain(&self) -> ChainHash {
+ #[cfg(feature = "std")]
+ pub(super) fn is_expired(&self) -> bool {
+ match self.absolute_expiry {
+ Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+ Ok(elapsed) => elapsed > seconds_from_epoch,
+ Err(_) => false,
+ },
+ None => false,
+ }
+ }
+
+ pub(super) fn chain(&self) -> ChainHash {
self.chain.unwrap_or_else(|| self.implied_chain())
}
let offer = OfferTlvStreamRef {
chains: None,
- metadata: self.metadata.as_ref(),
+ metadata: None,
currency: None,
amount: None,
description: Some(&self.description),
chain: self.chain.as_ref(),
amount: Some(self.amount_msats),
features,
- quantity: None,
+ quantity: self.quantity,
payer_id: Some(&self.payer_id),
payer_note: self.payer_note.as_ref(),
};
Some(metadata) => PayerContents(metadata),
};
+ if metadata.is_some() {
+ return Err(SemanticError::UnexpectedMetadata);
+ }
+
if chains.is_some() {
return Err(SemanticError::UnexpectedChain);
}
let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
- // TODO: Check why this isn't in the spec.
- if quantity.is_some() {
- return Err(SemanticError::UnexpectedQuantity);
- }
-
let payer_id = match payer_id {
None => return Err(SemanticError::MissingPayerId),
Some(payer_id) => payer_id,
};
- // TODO: Should metadata be included?
Ok(RefundContents {
- payer, metadata, description, absolute_expiry, issuer, paths, chain, amount_msats,
- features, payer_id, payer_note,
+ payer, description, absolute_expiry, issuer, paths, chain, amount_msats, features,
+ quantity, payer_id, payer_note,
})
}
}
assert_eq!(tlv_stream.chain, Some(&testnet));
}
+ #[test]
+ fn builds_refund_with_quantity() {
+ let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+ .quantity(10)
+ .build().unwrap();
+ let (_, _, tlv_stream) = refund.as_tlv_stream();
+ assert_eq!(refund.quantity(), Some(10));
+ assert_eq!(tlv_stream.quantity, Some(10));
+
+ let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+ .quantity(10)
+ .quantity(1)
+ .build().unwrap();
+ let (_, _, tlv_stream) = refund.as_tlv_stream();
+ assert_eq!(refund.quantity(), Some(1));
+ assert_eq!(tlv_stream.quantity, Some(1));
+ }
+
#[test]
fn builds_refund_with_payer_note() {
let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
.path(paths[1].clone())
.chain(Network::Testnet)
.features_unchecked(InvoiceRequestFeatures::unknown())
+ .quantity(10)
.payer_note("baz".into())
.build()
.unwrap();
assert_eq!(refund.issuer(), Some(PrintableString("bar")));
assert_eq!(refund.chain(), ChainHash::using_genesis_block(Network::Testnet));
assert_eq!(refund.features(), &InvoiceRequestFeatures::unknown());
+ assert_eq!(refund.quantity(), Some(10));
assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
},
Err(e) => panic!("error parsing refund: {:?}", e),
panic!("error parsing refund: {:?}", e);
}
+ let metadata = vec![42; 32];
+ let mut tlv_stream = refund.as_tlv_stream();
+ tlv_stream.1.metadata = Some(&metadata);
+
+ match Refund::try_from(tlv_stream.to_bytes()) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => {
+ assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedMetadata));
+ },
+ }
+
let chains = vec![ChainHash::using_genesis_block(Network::Testnet)];
let mut tlv_stream = refund.as_tlv_stream();
tlv_stream.1.chains = Some(&chains);
assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedSigningPubkey));
},
}
-
- let mut tlv_stream = refund.as_tlv_stream();
- tlv_stream.2.quantity = Some(10);
-
- match Refund::try_from(tlv_stream.to_bytes()) {
- Ok(_) => panic!("expected error"),
- Err(e) => {
- assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
- },
- }
}
#[test]
impl MessengerNode {
fn get_node_pk(&self) -> PublicKey {
- let secp_ctx = Secp256k1::new();
- PublicKey::from_secret_key(&secp_ctx, &self.keys_manager.get_node_secret(Recipient::Node).unwrap())
+ self.keys_manager.get_node_id(Recipient::Node).unwrap()
}
}
use crate::util::events::{MessageSendEvent, MessageSendEventsProvider};
use crate::util::scid_utils::{block_from_scid, scid_from_parts, MAX_SCID_BLOCK};
use crate::util::string::PrintableString;
+use crate::util::indexed_map::{IndexedMap, Entry as IndexedMapEntry};
use crate::io;
use crate::io_extras::{copy, sink};
use crate::prelude::*;
-use alloc::collections::{BTreeMap, btree_map::Entry as BtreeEntry};
use core::{cmp, fmt};
use crate::sync::{RwLock, RwLockReadGuard};
#[cfg(feature = "std")]
genesis_hash: BlockHash,
logger: L,
// Lock order: channels -> nodes
- channels: RwLock<BTreeMap<u64, ChannelInfo>>,
- nodes: RwLock<BTreeMap<NodeId, NodeInfo>>,
+ channels: RwLock<IndexedMap<u64, ChannelInfo>>,
+ nodes: RwLock<IndexedMap<NodeId, NodeInfo>>,
// Lock order: removed_channels -> removed_nodes
//
// NOTE: In the following `removed_*` maps, we use seconds since UNIX epoch to track time instead
/// A read-only view of [`NetworkGraph`].
pub struct ReadOnlyNetworkGraph<'a> {
- channels: RwLockReadGuard<'a, BTreeMap<u64, ChannelInfo>>,
- nodes: RwLockReadGuard<'a, BTreeMap<NodeId, NodeInfo>>,
+ channels: RwLockReadGuard<'a, IndexedMap<u64, ChannelInfo>>,
+ nodes: RwLockReadGuard<'a, IndexedMap<NodeId, NodeInfo>>,
}
/// Update to the [`NetworkGraph`] based on payment failure information conveyed via the Onion
pub struct NodeInfo {
/// All valid channels a node has announced
pub channels: Vec<u64>,
- /// Lowest fees enabling routing via any of the enabled, known channels to a node.
- /// The two fields (flat and proportional fee) are independent,
- /// meaning they don't have to refer to the same channel.
- pub lowest_inbound_channel_fees: Option<RoutingFees>,
/// More information about a node from node_announcement.
/// Optional because we store a Node entry after learning about it from
/// a channel announcement, but before receiving a node announcement.
impl fmt::Display for NodeInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- write!(f, "lowest_inbound_channel_fees: {:?}, channels: {:?}, announcement_info: {:?}",
- self.lowest_inbound_channel_fees, &self.channels[..], self.announcement_info)?;
+ write!(f, " channels: {:?}, announcement_info: {:?}",
+ &self.channels[..], self.announcement_info)?;
Ok(())
}
}
impl Writeable for NodeInfo {
fn write<W: crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
write_tlv_fields!(writer, {
- (0, self.lowest_inbound_channel_fees, option),
+ // Note that older versions of LDK wrote the lowest inbound fees here at type 0
(2, self.announcement_info, option),
(4, self.channels, vec_type),
});
impl Readable for NodeInfo {
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
- _init_tlv_field_var!(lowest_inbound_channel_fees, option);
+ // Historically, we tracked the lowest inbound fees for any node in order to use it as an
+ // A* heuristic when routing. Sadly, these days many, many nodes have at least one channel
+ // with zero inbound fees, causing that heuristic to provide little gain. Worse, because it
+ // requires additional complexity and lookups during routing, it ends up being a
+ // performance loss. Thus, we simply ignore the old field here and no longer track it.
+ let mut _lowest_inbound_channel_fees: Option<RoutingFees> = None;
let mut announcement_info_wrap: Option<NodeAnnouncementInfoDeserWrapper> = None;
_init_tlv_field_var!(channels, vec_type);
read_tlv_fields!(reader, {
- (0, lowest_inbound_channel_fees, option),
+ (0, _lowest_inbound_channel_fees, option),
(2, announcement_info_wrap, ignorable),
(4, channels, vec_type),
});
Ok(NodeInfo {
- lowest_inbound_channel_fees: _init_tlv_based_struct_field!(lowest_inbound_channel_fees, option),
announcement_info: announcement_info_wrap.map(|w| w.0),
channels: _init_tlv_based_struct_field!(channels, vec_type),
})
self.genesis_hash.write(writer)?;
let channels = self.channels.read().unwrap();
(channels.len() as u64).write(writer)?;
- for (ref chan_id, ref chan_info) in channels.iter() {
+ for (ref chan_id, ref chan_info) in channels.unordered_iter() {
(*chan_id).write(writer)?;
chan_info.write(writer)?;
}
let nodes = self.nodes.read().unwrap();
(nodes.len() as u64).write(writer)?;
- for (ref node_id, ref node_info) in nodes.iter() {
+ for (ref node_id, ref node_info) in nodes.unordered_iter() {
node_id.write(writer)?;
node_info.write(writer)?;
}
let genesis_hash: BlockHash = Readable::read(reader)?;
let channels_count: u64 = Readable::read(reader)?;
- let mut channels = BTreeMap::new();
+ let mut channels = IndexedMap::new();
for _ in 0..channels_count {
let chan_id: u64 = Readable::read(reader)?;
let chan_info = Readable::read(reader)?;
channels.insert(chan_id, chan_info);
}
let nodes_count: u64 = Readable::read(reader)?;
- let mut nodes = BTreeMap::new();
+ let mut nodes = IndexedMap::new();
for _ in 0..nodes_count {
let node_id = Readable::read(reader)?;
let node_info = Readable::read(reader)?;
impl<L: Deref> fmt::Display for NetworkGraph<L> where L::Target: Logger {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
writeln!(f, "Network map\n[Channels]")?;
- for (key, val) in self.channels.read().unwrap().iter() {
+ for (key, val) in self.channels.read().unwrap().unordered_iter() {
writeln!(f, " {}: {}", key, val)?;
}
writeln!(f, "[Nodes]")?;
- for (&node_id, val) in self.nodes.read().unwrap().iter() {
+ for (&node_id, val) in self.nodes.read().unwrap().unordered_iter() {
writeln!(f, " {}: {}", log_bytes!(node_id.as_slice()), val)?;
}
Ok(())
secp_ctx: Secp256k1::verification_only(),
genesis_hash,
logger,
- channels: RwLock::new(BTreeMap::new()),
- nodes: RwLock::new(BTreeMap::new()),
+ channels: RwLock::new(IndexedMap::new()),
+ nodes: RwLock::new(IndexedMap::new()),
last_rapid_gossip_sync_timestamp: Mutex::new(None),
removed_channels: Mutex::new(HashMap::new()),
removed_nodes: Mutex::new(HashMap::new()),
/// purposes.
#[cfg(test)]
pub fn clear_nodes_announcement_info(&self) {
- for node in self.nodes.write().unwrap().iter_mut() {
+ for node in self.nodes.write().unwrap().unordered_iter_mut() {
node.1.announcement_info = None;
}
}
let node_id_b = channel_info.node_two.clone();
match channels.entry(short_channel_id) {
- BtreeEntry::Occupied(mut entry) => {
+ IndexedMapEntry::Occupied(mut entry) => {
//TODO: because asking the blockchain if short_channel_id is valid is only optional
//in the blockchain API, we need to handle it smartly here, though it's unclear
//exactly how...
return Err(LightningError{err: "Already have knowledge of channel".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
}
},
- BtreeEntry::Vacant(entry) => {
+ IndexedMapEntry::Vacant(entry) => {
entry.insert(channel_info);
}
};
for current_node_id in [node_id_a, node_id_b].iter() {
match nodes.entry(current_node_id.clone()) {
- BtreeEntry::Occupied(node_entry) => {
+ IndexedMapEntry::Occupied(node_entry) => {
node_entry.into_mut().channels.push(short_channel_id);
},
- BtreeEntry::Vacant(node_entry) => {
+ IndexedMapEntry::Vacant(node_entry) => {
node_entry.insert(NodeInfo {
channels: vec!(short_channel_id),
- lowest_inbound_channel_fees: None,
announcement_info: None,
});
}
for scid in node.channels.iter() {
if let Some(chan_info) = channels.remove(scid) {
let other_node_id = if node_id == chan_info.node_one { chan_info.node_two } else { chan_info.node_one };
- if let BtreeEntry::Occupied(mut other_node_entry) = nodes.entry(other_node_id) {
+ if let IndexedMapEntry::Occupied(mut other_node_entry) = nodes.entry(other_node_id) {
other_node_entry.get_mut().channels.retain(|chan_id| {
*scid != *chan_id
});
// Sadly BTreeMap::retain was only stabilized in 1.53 so we can't switch to it for some
// time.
let mut scids_to_remove = Vec::new();
- for (scid, info) in channels.iter_mut() {
+ for (scid, info) in channels.unordered_iter_mut() {
if info.one_to_two.is_some() && info.one_to_two.as_ref().unwrap().last_update < min_time_unix {
info.one_to_two = None;
}
}
fn update_channel_intern(&self, msg: &msgs::UnsignedChannelUpdate, full_msg: Option<&msgs::ChannelUpdate>, sig: Option<&secp256k1::ecdsa::Signature>) -> Result<(), LightningError> {
- let dest_node_id;
let chan_enabled = msg.flags & (1 << 1) != (1 << 1);
- let chan_was_enabled;
#[cfg(all(feature = "std", not(test), not(feature = "_test_utils")))]
{
} else if existing_chan_info.last_update == msg.timestamp {
return Err(LightningError{err: "Update had same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
}
- chan_was_enabled = existing_chan_info.enabled;
- } else {
- chan_was_enabled = false;
}
}
}
let msg_hash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]);
if msg.flags & 1 == 1 {
- dest_node_id = channel.node_one.clone();
check_update_latest!(channel.two_to_one);
if let Some(sig) = sig {
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_two.as_slice()).map_err(|_| LightningError{
}
channel.two_to_one = get_new_channel_info!();
} else {
- dest_node_id = channel.node_two.clone();
check_update_latest!(channel.one_to_two);
if let Some(sig) = sig {
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_one.as_slice()).map_err(|_| LightningError{
}
}
- let mut nodes = self.nodes.write().unwrap();
- if chan_enabled {
- let node = nodes.get_mut(&dest_node_id).unwrap();
- let mut base_msat = msg.fee_base_msat;
- let mut proportional_millionths = msg.fee_proportional_millionths;
- if let Some(fees) = node.lowest_inbound_channel_fees {
- base_msat = cmp::min(base_msat, fees.base_msat);
- proportional_millionths = cmp::min(proportional_millionths, fees.proportional_millionths);
- }
- node.lowest_inbound_channel_fees = Some(RoutingFees {
- base_msat,
- proportional_millionths
- });
- } else if chan_was_enabled {
- let node = nodes.get_mut(&dest_node_id).unwrap();
- let mut lowest_inbound_channel_fees = None;
-
- for chan_id in node.channels.iter() {
- let chan = channels.get(chan_id).unwrap();
- let chan_info_opt;
- if chan.node_one == dest_node_id {
- chan_info_opt = chan.two_to_one.as_ref();
- } else {
- chan_info_opt = chan.one_to_two.as_ref();
- }
- if let Some(chan_info) = chan_info_opt {
- if chan_info.enabled {
- let fees = lowest_inbound_channel_fees.get_or_insert(RoutingFees {
- base_msat: u32::max_value(), proportional_millionths: u32::max_value() });
- fees.base_msat = cmp::min(fees.base_msat, chan_info.fees.base_msat);
- fees.proportional_millionths = cmp::min(fees.proportional_millionths, chan_info.fees.proportional_millionths);
- }
- }
- }
-
- node.lowest_inbound_channel_fees = lowest_inbound_channel_fees;
- }
-
Ok(())
}
- fn remove_channel_in_nodes(nodes: &mut BTreeMap<NodeId, NodeInfo>, chan: &ChannelInfo, short_channel_id: u64) {
+ fn remove_channel_in_nodes(nodes: &mut IndexedMap<NodeId, NodeInfo>, chan: &ChannelInfo, short_channel_id: u64) {
macro_rules! remove_from_node {
($node_id: expr) => {
- if let BtreeEntry::Occupied(mut entry) = nodes.entry($node_id) {
+ if let IndexedMapEntry::Occupied(mut entry) = nodes.entry($node_id) {
entry.get_mut().channels.retain(|chan_id| {
short_channel_id != *chan_id
});
impl ReadOnlyNetworkGraph<'_> {
/// Returns all known valid channels' short ids along with announced channel info.
///
- /// (C-not exported) because we have no mapping for `BTreeMap`s
- pub fn channels(&self) -> &BTreeMap<u64, ChannelInfo> {
+ /// (C-not exported) because we don't want to return lifetime'd references
+ pub fn channels(&self) -> &IndexedMap<u64, ChannelInfo> {
&*self.channels
}
#[cfg(c_bindings)] // Non-bindings users should use `channels`
/// Returns the list of channels in the graph
pub fn list_channels(&self) -> Vec<u64> {
- self.channels.keys().map(|c| *c).collect()
+ self.channels.unordered_keys().map(|c| *c).collect()
}
/// Returns all known nodes' public keys along with announced node info.
///
- /// (C-not exported) because we have no mapping for `BTreeMap`s
- pub fn nodes(&self) -> &BTreeMap<NodeId, NodeInfo> {
+ /// (C-not exported) because we don't want to return lifetime'd references
+ pub fn nodes(&self) -> &IndexedMap<NodeId, NodeInfo> {
&*self.nodes
}
#[cfg(c_bindings)] // Non-bindings users should use `nodes`
/// Returns the list of nodes in the graph
pub fn list_nodes(&self) -> Vec<NodeId> {
- self.nodes.keys().map(|n| *n).collect()
+ self.nodes.unordered_keys().map(|n| *n).collect()
}
/// Get network addresses by node id.
use crate::chain;
use crate::ln::channelmanager;
use crate::ln::chan_utils::make_funding_redeemscript;
+ #[cfg(feature = "std")]
use crate::ln::features::InitFeatures;
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, NodeAlias, MAX_EXCESS_BYTES_FOR_RELAY, NodeId, RoutingFees, ChannelUpdateInfo, ChannelInfo, NodeAnnouncementInfo, NodeInfo};
use crate::ln::msgs::{RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement,
// 2. Check we can read a NodeInfo anyways, but set the NodeAnnouncementInfo to None if invalid
let valid_node_info = NodeInfo {
channels: Vec::new(),
- lowest_inbound_channel_fees: None,
announcement_info: Some(valid_node_ann_info),
};
/// [`Event::PaymentPathFailed`] for retrying a failed payment path.
///
/// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RouteParameters {
/// The parameters of the failed payment path.
pub payment_params: PaymentParameters,
#[derive(Eq, PartialEq)]
struct RouteGraphNode {
node_id: NodeId,
- lowest_fee_to_peer_through_node: u64,
lowest_fee_to_node: u64,
total_cltv_delta: u32,
// The maximum value a yet-to-be-constructed payment path might flow through this node.
impl cmp::Ord for RouteGraphNode {
fn cmp(&self, other: &RouteGraphNode) -> cmp::Ordering {
- let other_score = cmp::max(other.lowest_fee_to_peer_through_node, other.path_htlc_minimum_msat)
+ let other_score = cmp::max(other.lowest_fee_to_node, other.path_htlc_minimum_msat)
.saturating_add(other.path_penalty_msat);
- let self_score = cmp::max(self.lowest_fee_to_peer_through_node, self.path_htlc_minimum_msat)
+ let self_score = cmp::max(self.lowest_fee_to_node, self.path_htlc_minimum_msat)
.saturating_add(self.path_penalty_msat);
other_score.cmp(&self_score).then_with(|| other.node_id.cmp(&self.node_id))
}
candidate: CandidateRouteHop<'a>,
fee_msat: u64,
- /// Minimal fees required to route to the source node of the current hop via any of its inbound channels.
- src_lowest_inbound_fees: RoutingFees,
/// All the fees paid *after* this channel on the way to the destination
next_hops_fee_msat: u64,
/// Fee paid for the use of the current channel (see candidate.fees()).
}
}
+#[inline(always)]
+/// Calculate the fees required to route the given amount over a channel with the given fees.
fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option<u64> {
- let proportional_fee_millions =
- amount_msat.checked_mul(channel_fees.proportional_millionths as u64);
- if let Some(new_fee) = proportional_fee_millions.and_then(|part| {
- (channel_fees.base_msat as u64).checked_add(part / 1_000_000) }) {
+ amount_msat.checked_mul(channel_fees.proportional_millionths as u64)
+ .and_then(|part| (channel_fees.base_msat as u64).checked_add(part / 1_000_000))
+}
- Some(new_fee)
- } else {
- // This function may be (indirectly) called without any verification,
- // with channel_fees provided by a caller. We should handle it gracefully.
- None
- }
+#[inline(always)]
+/// Calculate the fees required to route the given amount over a channel with the given fees,
+/// saturating to [`u64::max_value`].
+fn compute_fees_saturating(amount_msat: u64, channel_fees: RoutingFees) -> u64 {
+ amount_msat.checked_mul(channel_fees.proportional_millionths as u64)
+ .map(|prop| prop / 1_000_000).unwrap_or(u64::max_value())
+ .saturating_add(channel_fees.base_msat as u64)
}
/// The default `features` we assume for a node in a route, when no `features` are known about that
// 8. If our maximum channel saturation limit caused us to pick two identical paths, combine
// them so that we're not sending two HTLCs along the same path.
- // As for the actual search algorithm,
- // we do a payee-to-payer pseudo-Dijkstra's sorting by each node's distance from the payee
- // plus the minimum per-HTLC fee to get from it to another node (aka "shitty pseudo-A*").
+ // As for the actual search algorithm, we do a payee-to-payer Dijkstra's sorting by each node's
+ // distance from the payee
//
// We are not a faithful Dijkstra's implementation because we can change values which impact
// earlier nodes while processing later nodes. Specifically, if we reach a channel with a lower
// runtime for little gain. Specifically, the current algorithm rather efficiently explores the
// graph for candidate paths, calculating the maximum value which can realistically be sent at
// the same time, remaining generic across different payment values.
- //
- // TODO: There are a few tweaks we could do, including possibly pre-calculating more stuff
- // to use as the A* heuristic beyond just the cost to get one node further than the current
- // one.
let network_channels = network_graph.channels();
let network_nodes = network_graph.nodes();
}
}
- // The main heap containing all candidate next-hops sorted by their score (max(A* fee,
+ // The main heap containing all candidate next-hops sorted by their score (max(fee,
// htlc_minimum)). Ideally this would be a heap which allowed cheap score reduction instead of
// adding duplicate entries when we find a better path to a given node.
let mut targets: BinaryHeap<RouteGraphNode> = BinaryHeap::new();
// might violate htlc_minimum_msat on the hops which are next along the
// payment path (upstream to the payee). To avoid that, we recompute
// path fees knowing the final path contribution after constructing it.
- let path_htlc_minimum_msat = compute_fees($next_hops_path_htlc_minimum_msat, $candidate.fees())
- .and_then(|fee_msat| fee_msat.checked_add($next_hops_path_htlc_minimum_msat))
- .map(|fee_msat| cmp::max(fee_msat, $candidate.htlc_minimum_msat()))
- .unwrap_or_else(|| u64::max_value());
+ let path_htlc_minimum_msat = cmp::max(
+ compute_fees_saturating($next_hops_path_htlc_minimum_msat, $candidate.fees())
+ .saturating_add($next_hops_path_htlc_minimum_msat),
+ $candidate.htlc_minimum_msat());
let hm_entry = dist.entry($src_node_id);
let old_entry = hm_entry.or_insert_with(|| {
// If there was previously no known way to access the source node
// semi-dummy record just to compute the fees to reach the source node.
// This will affect our decision on selecting short_channel_id
// as a way to reach the $dest_node_id.
- let mut fee_base_msat = 0;
- let mut fee_proportional_millionths = 0;
- if let Some(Some(fees)) = network_nodes.get(&$src_node_id).map(|node| node.lowest_inbound_channel_fees) {
- fee_base_msat = fees.base_msat;
- fee_proportional_millionths = fees.proportional_millionths;
- }
PathBuildingHop {
node_id: $dest_node_id.clone(),
candidate: $candidate.clone(),
fee_msat: 0,
- src_lowest_inbound_fees: RoutingFees {
- base_msat: fee_base_msat,
- proportional_millionths: fee_proportional_millionths,
- },
next_hops_fee_msat: u64::max_value(),
hop_use_fee_msat: u64::max_value(),
total_fee_msat: u64::max_value(),
if should_process {
let mut hop_use_fee_msat = 0;
- let mut total_fee_msat = $next_hops_fee_msat;
+ let mut total_fee_msat: u64 = $next_hops_fee_msat;
// Ignore hop_use_fee_msat for channel-from-us as we assume all channels-from-us
// will have the same effective-fee
if $src_node_id != our_node_id {
- match compute_fees(amount_to_transfer_over_msat, $candidate.fees()) {
- // max_value means we'll always fail
- // the old_entry.total_fee_msat > total_fee_msat check
- None => total_fee_msat = u64::max_value(),
- Some(fee_msat) => {
- hop_use_fee_msat = fee_msat;
- total_fee_msat += hop_use_fee_msat;
- // When calculating the lowest inbound fees to a node, we
- // calculate fees here not based on the actual value we think
- // will flow over this channel, but on the minimum value that
- // we'll accept flowing over it. The minimum accepted value
- // is a constant through each path collection run, ensuring
- // consistent basis. Otherwise we may later find a
- // different path to the source node that is more expensive,
- // but which we consider to be cheaper because we are capacity
- // constrained and the relative fee becomes lower.
- match compute_fees(minimal_value_contribution_msat, old_entry.src_lowest_inbound_fees)
- .map(|a| a.checked_add(total_fee_msat)) {
- Some(Some(v)) => {
- total_fee_msat = v;
- },
- _ => {
- total_fee_msat = u64::max_value();
- }
- };
- }
- }
+ // Note that `u64::max_value` means we'll always fail the
+ // `old_entry.total_fee_msat > total_fee_msat` check below
+ hop_use_fee_msat = compute_fees_saturating(amount_to_transfer_over_msat, $candidate.fees());
+ total_fee_msat = total_fee_msat.saturating_add(hop_use_fee_msat);
}
let channel_usage = ChannelUsage {
.saturating_add(channel_penalty_msat);
let new_graph_node = RouteGraphNode {
node_id: $src_node_id,
- lowest_fee_to_peer_through_node: total_fee_msat,
- lowest_fee_to_node: $next_hops_fee_msat as u64 + hop_use_fee_msat,
+ lowest_fee_to_node: total_fee_msat,
total_cltv_delta: hop_total_cltv_delta,
value_contribution_msat,
path_htlc_minimum_msat,
'load_endpoints: for _ in 0..10 {
loop {
seed = seed.overflowing_mul(0xdeadbeef).0;
- let src = &PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
+ let src = &PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
seed = seed.overflowing_mul(0xdeadbeef).0;
- let dst = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
+ let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
let payment_params = PaymentParameters::from_node_id(dst);
let amt = seed as u64 % 200_000_000;
let params = ProbabilisticScoringParameters::default();
'load_endpoints: for _ in 0..10 {
loop {
seed = seed.overflowing_mul(0xdeadbeef).0;
- let src = &PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
+ let src = &PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
seed = seed.overflowing_mul(0xdeadbeef).0;
- let dst = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
+ let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
let payment_params = PaymentParameters::from_node_id(dst).with_features(channelmanager::provided_invoice_features(&config));
let amt = seed as u64 % 200_000_000;
let params = ProbabilisticScoringParameters::default();
use std::fs::File;
/// Tries to open a network graph file, or panics with a URL to fetch it.
pub(crate) fn get_route_file() -> Result<std::fs::File, &'static str> {
- let res = File::open("net_graph-2021-05-31.bin") // By default we're run in RL/lightning
- .or_else(|_| File::open("lightning/net_graph-2021-05-31.bin")) // We may be run manually in RL/
+ let res = File::open("net_graph-2023-01-18.bin") // By default we're run in RL/lightning
+ .or_else(|_| File::open("lightning/net_graph-2023-01-18.bin")) // We may be run manually in RL/
.or_else(|_| { // Fall back to guessing based on the binary location
// path is likely something like .../rust-lightning/target/debug/deps/lightning-...
let mut path = std::env::current_exe().unwrap();
path.pop(); // debug
path.pop(); // target
path.push("lightning");
- path.push("net_graph-2021-05-31.bin");
+ path.push("net_graph-2023-01-18.bin");
eprintln!("{}", path.to_str().unwrap());
File::open(path)
})
- .map_err(|_| "Please fetch https://bitcoin.ninja/ldk-net_graph-v0.0.15-2021-05-31.bin and place it at lightning/net_graph-2021-05-31.bin");
+ .map_err(|_| "Please fetch https://bitcoin.ninja/ldk-net_graph-v0.0.113-2023-01-18.bin and place it at lightning/net_graph-2023-01-18.bin");
#[cfg(require_route_graph_test)]
return Ok(res.unwrap());
#[cfg(not(require_route_graph_test))]
'load_endpoints: for _ in 0..150 {
loop {
seed *= 0xdeadbeef;
- let src = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
+ let src = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
seed *= 0xdeadbeef;
- let dst = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
+ let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
let params = PaymentParameters::from_node_id(dst).with_features(features.clone());
let first_hop = first_hop(src);
let amt = seed as u64 % 1_000_000;
///
/// [`SignerProvider::get_shutdown_scriptpubkey`]: crate::chain::keysinterface::SignerProvider::get_shutdown_scriptpubkey
pub commit_upfront_shutdown_pubkey: bool,
-
/// The Proportion of the channel value to configure as counterparty's channel reserve,
/// i.e., `their_channel_reserve_satoshis` for both outbound and inbound channels.
///
/// as 1000 sats instead, which is a safe implementation-specific lower bound.
/// Maximum value: 1,000,000, any values larger than 1 Million will be treated as 1 Million (or 100%)
/// instead, although channel negotiations will fail in that case.
- pub their_channel_reserve_proportional_millionths: u32
+ pub their_channel_reserve_proportional_millionths: u32,
+ #[cfg(anchors)]
+ /// If set, we attempt to negotiate the `anchors_zero_fee_htlc_tx`option for outbound channels.
+ ///
+ /// If this option is set, channels may be created that will not be readable by LDK versions
+ /// prior to 0.0.114, causing [`ChannelManager`]'s read method to return a
+ /// [`DecodeError::InvalidValue`].
+ ///
+ /// Note that setting this to true does *not* prevent us from opening channels with
+ /// counterparties that do not support the `anchors_zero_fee_htlc_tx` option; we will simply
+ /// fall back to a `static_remote_key` channel.
+ ///
+ /// LDK will not support the legacy `option_anchors` commitment version due to a discovered
+ /// vulnerability after its deployment. For more context, see the [`SIGHASH_SINGLE + update_fee
+ /// Considered Harmful`] mailing list post.
+ ///
+ /// Default value: false. This value is likely to change to true in the future.
+ ///
+ /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+ /// [`DecodeError::InvalidValue`]: crate::ln::msgs::DecodeError::InvalidValue
+ /// [`SIGHASH_SINGLE + update_fee Considered Harmful`]: https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html
+ pub negotiate_anchors_zero_fee_htlc_tx: bool,
}
impl Default for ChannelHandshakeConfig {
announced_channel: false,
commit_upfront_shutdown_pubkey: true,
their_channel_reserve_proportional_millionths: 10_000,
+ #[cfg(anchors)]
+ negotiate_anchors_zero_fee_htlc_tx: false,
}
}
}
use crate::ln::channel::{ANCHOR_OUTPUT_VALUE_SATOSHI, MIN_CHAN_DUST_LIMIT_SATOSHIS};
use crate::ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitmentTransaction, CommitmentTransaction, ChannelTransactionParameters, TrustedCommitmentTransaction, ClosingTransaction};
use crate::ln::{chan_utils, msgs, PaymentPreimage};
-use crate::chain::keysinterface::{Sign, InMemorySigner, BaseSign};
+use crate::chain::keysinterface::{WriteableEcdsaChannelSigner, InMemorySigner, ChannelSigner, EcdsaChannelSigner};
use crate::prelude::*;
use core::cmp;
pub disable_revocation_policy_check: bool,
}
+impl PartialEq for EnforcingSigner {
+ fn eq(&self, o: &Self) -> bool {
+ Arc::ptr_eq(&self.state, &o.state)
+ }
+}
+
impl EnforcingSigner {
/// Construct an EnforcingSigner
pub fn new(inner: InMemorySigner) -> Self {
}
}
-impl BaseSign for EnforcingSigner {
+impl ChannelSigner for EnforcingSigner {
fn get_per_commitment_point(&self, idx: u64, secp_ctx: &Secp256k1<secp256k1::All>) -> PublicKey {
self.inner.get_per_commitment_point(idx, secp_ctx)
}
}
fn pubkeys(&self) -> &ChannelPublicKeys { self.inner.pubkeys() }
+
fn channel_keys_id(&self) -> [u8; 32] { self.inner.channel_keys_id() }
+ fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) {
+ self.inner.provide_channel_parameters(channel_parameters)
+ }
+}
+
+impl EcdsaChannelSigner for EnforcingSigner {
fn sign_counterparty_commitment(&self, commitment_tx: &CommitmentTransaction, preimages: Vec<PaymentPreimage>, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<(Signature, Vec<Signature>), ()> {
self.verify_counterparty_commitment_tx(commitment_tx, secp_ctx);
self.inner.sign_holder_anchor_input(anchor_tx, input, secp_ctx)
}
- fn sign_channel_announcement(&self, msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>)
- -> Result<(Signature, Signature), ()> {
- self.inner.sign_channel_announcement(msg, secp_ctx)
- }
-
- fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) {
- self.inner.provide_channel_parameters(channel_parameters)
+ fn sign_channel_announcement_with_funding_key(
+ &self, msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>
+ ) -> Result<Signature, ()> {
+ self.inner.sign_channel_announcement_with_funding_key(msg, secp_ctx)
}
}
-impl Sign for EnforcingSigner {}
+impl WriteableEcdsaChannelSigner for EnforcingSigner {}
impl Writeable for EnforcingSigner {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
/// Some information provided on receipt of payment depends on whether the payment received is a
/// spontaneous payment or a "conventional" lightning payment that's paying an invoice.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaymentPurpose {
/// Information for receiving a payment that we generated an invoice for.
InvoicePayment {
/// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner
pub channel_value_satoshis: u64,
/// The necessary channel parameters that need to be provided to the re-derived
- /// [`InMemorySigner`] through [`BaseSign::ready_channel`].
+ /// [`InMemorySigner`] through [`BaseSign::provide_channel_parameters`].
///
/// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner
- /// [`BaseSign::ready_channel`]: crate::chain::keysinterface::BaseSign::ready_channel
+ /// [`BaseSign::provide_channel_parameters`]: crate::chain::keysinterface::BaseSign::provide_channel_parameters
pub channel_parameters: ChannelTransactionParameters,
/// The txid of the commitment transaction in which the HTLC output lives.
pub commitment_txid: Txid,
/// Note that while Writeable and Readable are implemented for Event, you probably shouldn't use
/// them directly as they don't round-trip exactly (for example FundingGenerationReady is never
/// written as it makes no sense to respond to it after reconnecting to peers).
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
/// Used to indicate that the client should generate a funding transaction with the given
/// parameters and then call [`ChannelManager::funding_transaction_generated`].
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
/// Note that if the preimage is not known, you should call
- /// [`ChannelManager::fail_htlc_backwards`] to free up resources for this HTLC and avoid
- /// network congestion.
- /// If you fail to call either [`ChannelManager::claim_funds`] or
- /// [`ChannelManager::fail_htlc_backwards`] within the HTLC's timeout, the HTLC will be
+ /// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
+ /// to free up resources for this HTLC and avoid network congestion.
+ /// If you fail to call either [`ChannelManager::claim_funds`], [`ChannelManager::fail_htlc_backwards`],
+ /// or [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will be
/// automatically failed.
///
/// # Note
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
+ /// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
PaymentClaimable {
/// The node that will receive the payment after it has been claimed.
/// This is useful to identify payments received via [phantom nodes].
--- /dev/null
+//! This module has a map which can be iterated in a deterministic order. See the [`IndexedMap`].
+
+use crate::prelude::{HashMap, hash_map};
+use alloc::collections::{BTreeSet, btree_set};
+use core::hash::Hash;
+use core::cmp::Ord;
+use core::ops::RangeBounds;
+
+/// A map which can be iterated in a deterministic order.
+///
+/// This would traditionally be accomplished by simply using a [`BTreeMap`], however B-Trees
+/// generally have very slow lookups. Because we use a nodes+channels map while finding routes
+/// across the network graph, our network graph backing map must be as performant as possible.
+/// However, because peers expect to sync the network graph from us (and we need to support that
+/// without holding a lock on the graph for the duration of the sync or dumping the entire graph
+/// into our outbound message queue), we need an iterable map with a consistent iteration order we
+/// can jump to a starting point on.
+///
+/// Thus, we have a custom data structure here - its API mimics that of Rust's [`BTreeMap`], but is
+/// actually backed by a [`HashMap`], with some additional tracking to ensure we can iterate over
+/// keys in the order defined by [`Ord`].
+///
+/// [`BTreeMap`]: alloc::collections::BTreeMap
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct IndexedMap<K: Hash + Ord, V> {
+ map: HashMap<K, V>,
+ // TODO: Explore swapping this for a sorted vec (that is only sorted on first range() call)
+ keys: BTreeSet<K>,
+}
+
+impl<K: Clone + Hash + Ord, V> IndexedMap<K, V> {
+ /// Constructs a new, empty map
+ pub fn new() -> Self {
+ Self {
+ map: HashMap::new(),
+ keys: BTreeSet::new(),
+ }
+ }
+
+ #[inline(always)]
+ /// Fetches the element with the given `key`, if one exists.
+ pub fn get(&self, key: &K) -> Option<&V> {
+ self.map.get(key)
+ }
+
+ /// Fetches a mutable reference to the element with the given `key`, if one exists.
+ pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
+ self.map.get_mut(key)
+ }
+
+ #[inline]
+ /// Returns true if an element with the given `key` exists in the map.
+ pub fn contains_key(&self, key: &K) -> bool {
+ self.map.contains_key(key)
+ }
+
+ /// Removes the element with the given `key`, returning it, if one exists.
+ pub fn remove(&mut self, key: &K) -> Option<V> {
+ let ret = self.map.remove(key);
+ if let Some(_) = ret {
+ assert!(self.keys.remove(key), "map and keys must be consistent");
+ }
+ ret
+ }
+
+ /// Inserts the given `key`/`value` pair into the map, returning the element that was
+ /// previously stored at the given `key`, if one exists.
+ pub fn insert(&mut self, key: K, value: V) -> Option<V> {
+ let ret = self.map.insert(key.clone(), value);
+ if ret.is_none() {
+ assert!(self.keys.insert(key), "map and keys must be consistent");
+ }
+ ret
+ }
+
+ /// Returns an [`Entry`] for the given `key` in the map, allowing access to the value.
+ pub fn entry(&mut self, key: K) -> Entry<'_, K, V> {
+ match self.map.entry(key.clone()) {
+ hash_map::Entry::Vacant(entry) => {
+ Entry::Vacant(VacantEntry {
+ underlying_entry: entry,
+ key,
+ keys: &mut self.keys,
+ })
+ },
+ hash_map::Entry::Occupied(entry) => {
+ Entry::Occupied(OccupiedEntry {
+ underlying_entry: entry,
+ keys: &mut self.keys,
+ })
+ }
+ }
+ }
+
+ /// Returns an iterator which iterates over the keys in the map, in a random order.
+ pub fn unordered_keys(&self) -> impl Iterator<Item = &K> {
+ self.map.keys()
+ }
+
+ /// Returns an iterator which iterates over the `key`/`value` pairs in a random order.
+ pub fn unordered_iter(&self) -> impl Iterator<Item = (&K, &V)> {
+ self.map.iter()
+ }
+
+ /// Returns an iterator which iterates over the `key`s and mutable references to `value`s in a
+ /// random order.
+ pub fn unordered_iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
+ self.map.iter_mut()
+ }
+
+ /// Returns an iterator which iterates over the `key`/`value` pairs in a given range.
+ pub fn range<R: RangeBounds<K>>(&self, range: R) -> Range<K, V> {
+ Range {
+ inner_range: self.keys.range(range),
+ map: &self.map,
+ }
+ }
+
+ /// Returns the number of `key`/`value` pairs in the map
+ pub fn len(&self) -> usize {
+ self.map.len()
+ }
+
+ /// Returns true if there are no elements in the map
+ pub fn is_empty(&self) -> bool {
+ self.map.is_empty()
+ }
+}
+
+/// An iterator over a range of values in an [`IndexedMap`]
+pub struct Range<'a, K: Hash + Ord, V> {
+ inner_range: btree_set::Range<'a, K>,
+ map: &'a HashMap<K, V>,
+}
+impl<'a, K: Hash + Ord, V: 'a> Iterator for Range<'a, K, V> {
+ type Item = (&'a K, &'a V);
+ fn next(&mut self) -> Option<(&'a K, &'a V)> {
+ self.inner_range.next().map(|k| {
+ (k, self.map.get(k).expect("map and keys must be consistent"))
+ })
+ }
+}
+
+/// An [`Entry`] for a key which currently has no value
+pub struct VacantEntry<'a, K: Hash + Ord, V> {
+ #[cfg(feature = "hashbrown")]
+ underlying_entry: hash_map::VacantEntry<'a, K, V, hash_map::DefaultHashBuilder>,
+ #[cfg(not(feature = "hashbrown"))]
+ underlying_entry: hash_map::VacantEntry<'a, K, V>,
+ key: K,
+ keys: &'a mut BTreeSet<K>,
+}
+
+/// An [`Entry`] for an existing key-value pair
+pub struct OccupiedEntry<'a, K: Hash + Ord, V> {
+ #[cfg(feature = "hashbrown")]
+ underlying_entry: hash_map::OccupiedEntry<'a, K, V, hash_map::DefaultHashBuilder>,
+ #[cfg(not(feature = "hashbrown"))]
+ underlying_entry: hash_map::OccupiedEntry<'a, K, V>,
+ keys: &'a mut BTreeSet<K>,
+}
+
+/// A mutable reference to a position in the map. This can be used to reference, add, or update the
+/// value at a fixed key.
+pub enum Entry<'a, K: Hash + Ord, V> {
+ /// A mutable reference to a position within the map where there is no value.
+ Vacant(VacantEntry<'a, K, V>),
+ /// A mutable reference to a position within the map where there is currently a value.
+ Occupied(OccupiedEntry<'a, K, V>),
+}
+
+impl<'a, K: Hash + Ord, V> VacantEntry<'a, K, V> {
+ /// Insert a value into the position described by this entry.
+ pub fn insert(self, value: V) -> &'a mut V {
+ assert!(self.keys.insert(self.key), "map and keys must be consistent");
+ self.underlying_entry.insert(value)
+ }
+}
+
+impl<'a, K: Hash + Ord, V> OccupiedEntry<'a, K, V> {
+ /// Remove the value at the position described by this entry.
+ pub fn remove_entry(self) -> (K, V) {
+ let res = self.underlying_entry.remove_entry();
+ assert!(self.keys.remove(&res.0), "map and keys must be consistent");
+ res
+ }
+
+ /// Get a reference to the value at the position described by this entry.
+ pub fn get(&self) -> &V {
+ self.underlying_entry.get()
+ }
+
+ /// Get a mutable reference to the value at the position described by this entry.
+ pub fn get_mut(&mut self) -> &mut V {
+ self.underlying_entry.get_mut()
+ }
+
+ /// Consume this entry, returning a mutable reference to the value at the position described by
+ /// this entry.
+ pub fn into_mut(self) -> &'a mut V {
+ self.underlying_entry.into_mut()
+ }
+}
}
}
if num_preimage > 0 || num_timeout > 0 || num_revoked > 0 {
- write!(f, "HTLC claim tx ({} preimage, {} timeout, {} revoked)",
+ write!(f, "HTLC claim tx ({} preimage, {} timeout, {} revoked) ",
num_preimage, num_timeout, num_revoked)?;
}
}
pub(crate) mod scid_utils;
pub(crate) mod time;
+pub mod indexed_map;
+
/// Logging macro utilities.
#[macro_use]
pub(crate) mod macro_logger;
use crate::chain;
use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use crate::chain::chainmonitor::{Persist, MonitorUpdateId};
-use crate::chain::keysinterface::{EntropySource, NodeSigner, Sign, SignerProvider};
+use crate::chain::keysinterface::{EntropySource, NodeSigner, WriteableEcdsaChannelSigner, SignerProvider};
use crate::chain::transaction::OutPoint;
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate};
use crate::ln::channelmanager::ChannelManager;
}
}
-impl<ChannelSigner: Sign, K: KVStorePersister> Persist<ChannelSigner> for K {
+impl<ChannelSigner: WriteableEcdsaChannelSigner, K: KVStorePersister> Persist<ChannelSigner> for K {
// TODO: We really need a way for the persister to inform the user that its time to crash/shut
// down once these start returning failure.
// A PermanentFailure implies we should probably just shut down the node since we're
}
}
- fn update_persisted_channel(&self, funding_txo: OutPoint, _update: &Option<ChannelMonitorUpdate>, monitor: &ChannelMonitor<ChannelSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
+ fn update_persisted_channel(&self, funding_txo: OutPoint, _update: Option<&ChannelMonitorUpdate>, monitor: &ChannelMonitor<ChannelSigner>, _update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
let key = format!("monitors/{}_{}", funding_txo.txid.to_hex(), funding_txo.index);
match self.persist(&key, monitor) {
Ok(()) => chain::ChannelMonitorUpdateStatus::Completed,
use core::convert::TryFrom;
use core::ops::Deref;
+use alloc::collections::BTreeMap;
+
use bitcoin::secp256k1::{PublicKey, SecretKey};
use bitcoin::secp256k1::constants::{PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, COMPACT_SIGNATURE_SIZE, SCHNORR_SIGNATURE_SIZE};
use bitcoin::secp256k1::ecdsa;
}
}
+/// The lightning protocol uses u16s for lengths in most cases. As our serialization framework
+/// primarily targets that, we must as well. However, because we may serialize objects that have
+/// more than 65K entries, we need to be able to store larger values. Thus, we define a variable
+/// length integer here that is backwards-compatible for values < 0xffff. We treat 0xffff as
+/// "read eight more bytes".
+///
+/// To ensure we only have one valid encoding per value, we add 0xffff to values written as eight
+/// bytes. Thus, 0xfffe is serialized as 0xfffe, whereas 0xffff is serialized as
+/// 0xffff0000000000000000 (i.e. read-eight-bytes then zero).
+struct CollectionLength(pub u64);
+impl Writeable for CollectionLength {
+ #[inline]
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ if self.0 < 0xffff {
+ (self.0 as u16).write(writer)
+ } else {
+ 0xffffu16.write(writer)?;
+ (self.0 - 0xffff).write(writer)
+ }
+ }
+}
+
+impl Readable for CollectionLength {
+ #[inline]
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let mut val: u64 = <u16 as Readable>::read(r)? as u64;
+ if val == 0xffff {
+ val = <u64 as Readable>::read(r)?
+ .checked_add(0xffff).ok_or(DecodeError::InvalidValue)?;
+ }
+ Ok(CollectionLength(val))
+ }
+}
+
/// In TLV we occasionally send fields which only consist of, or potentially end with, a
/// variable-length integer which is simply truncated by skipping high zero bytes. This type
/// encapsulates such integers implementing [`Readable`]/[`Writeable`] for them.
fn from(v: &'a Vec<T>) -> Self { Self(v) }
}
-// HashMap
-impl<K, V> Writeable for HashMap<K, V>
- where K: Writeable + Eq + Hash,
- V: Writeable
-{
+#[derive(Debug)]
+pub(crate) struct Iterable<'a, I: Iterator<Item = &'a T> + Clone, T: 'a>(pub I);
+
+impl<'a, I: Iterator<Item = &'a T> + Clone, T: 'a + Writeable> Writeable for Iterable<'a, I, T> {
#[inline]
- fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- (self.len() as u16).write(w)?;
- for (key, value) in self.iter() {
- key.write(w)?;
- value.write(w)?;
+ fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+ for ref v in self.0.clone() {
+ v.write(writer)?;
}
Ok(())
}
}
-impl<K, V> Readable for HashMap<K, V>
- where K: Readable + Eq + Hash,
- V: MaybeReadable
-{
- #[inline]
- fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let len: u16 = Readable::read(r)?;
- let mut ret = HashMap::with_capacity(len as usize);
- for _ in 0..len {
- let k = K::read(r)?;
- let v_opt = V::read(r)?;
- if let Some(v) = v_opt {
- if ret.insert(k, v).is_some() {
- return Err(DecodeError::InvalidValue);
+#[cfg(test)]
+impl<'a, I: Iterator<Item = &'a T> + Clone, T: 'a + PartialEq> PartialEq for Iterable<'a, I, T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.clone().collect::<Vec<_>>() == other.0.clone().collect::<Vec<_>>()
+ }
+}
+
+macro_rules! impl_for_map {
+ ($ty: ident, $keybound: ident, $constr: expr) => {
+ impl<K, V> Writeable for $ty<K, V>
+ where K: Writeable + Eq + $keybound, V: Writeable
+ {
+ #[inline]
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ CollectionLength(self.len() as u64).write(w)?;
+ for (key, value) in self.iter() {
+ key.write(w)?;
+ value.write(w)?;
}
+ Ok(())
+ }
+ }
+
+ impl<K, V> Readable for $ty<K, V>
+ where K: Readable + Eq + $keybound, V: MaybeReadable
+ {
+ #[inline]
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let len: CollectionLength = Readable::read(r)?;
+ let mut ret = $constr(len.0 as usize);
+ for _ in 0..len.0 {
+ let k = K::read(r)?;
+ let v_opt = V::read(r)?;
+ if let Some(v) = v_opt {
+ if ret.insert(k, v).is_some() {
+ return Err(DecodeError::InvalidValue);
+ }
+ }
+ }
+ Ok(ret)
}
}
- Ok(ret)
}
}
+impl_for_map!(BTreeMap, Ord, |_| BTreeMap::new());
+impl_for_map!(HashMap, Hash, |len| HashMap::with_capacity(len));
+
// HashSet
impl<T> Writeable for HashSet<T>
where T: Writeable + Eq + Hash
{
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- (self.len() as u16).write(w)?;
+ CollectionLength(self.len() as u64).write(w)?;
for item in self.iter() {
item.write(w)?;
}
{
#[inline]
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let len: u16 = Readable::read(r)?;
- let mut ret = HashSet::with_capacity(len as usize);
- for _ in 0..len {
+ let len: CollectionLength = Readable::read(r)?;
+ let mut ret = HashSet::with_capacity(cmp::min(len.0 as usize, MAX_BUF_SIZE / core::mem::size_of::<T>()));
+ for _ in 0..len.0 {
if !ret.insert(T::read(r)?) {
return Err(DecodeError::InvalidValue)
}
}
// Vectors
-impl Writeable for Vec<u8> {
- #[inline]
- fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- (self.len() as u16).write(w)?;
- w.write_all(&self)
- }
-}
+macro_rules! impl_for_vec {
+ ($ty: ty $(, $name: ident)*) => {
+ impl<$($name : Writeable),*> Writeable for Vec<$ty> {
+ #[inline]
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ CollectionLength(self.len() as u64).write(w)?;
+ for elem in self.iter() {
+ elem.write(w)?;
+ }
+ Ok(())
+ }
+ }
-impl Readable for Vec<u8> {
- #[inline]
- fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let len: u16 = Readable::read(r)?;
- let mut ret = Vec::with_capacity(len as usize);
- ret.resize(len as usize, 0);
- r.read_exact(&mut ret)?;
- Ok(ret)
+ impl<$($name : Readable),*> Readable for Vec<$ty> {
+ #[inline]
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let len: CollectionLength = Readable::read(r)?;
+ let mut ret = Vec::with_capacity(cmp::min(len.0 as usize, MAX_BUF_SIZE / core::mem::size_of::<$ty>()));
+ for _ in 0..len.0 {
+ if let Some(val) = MaybeReadable::read(r)? {
+ ret.push(val);
+ }
+ }
+ Ok(ret)
+ }
+ }
}
}
-impl Writeable for Vec<ecdsa::Signature> {
+
+impl Writeable for Vec<u8> {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- (self.len() as u16).write(w)?;
- for e in self.iter() {
- e.write(w)?;
- }
- Ok(())
+ CollectionLength(self.len() as u64).write(w)?;
+ w.write_all(&self)
}
}
-impl Readable for Vec<ecdsa::Signature> {
+impl Readable for Vec<u8> {
#[inline]
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let len: u16 = Readable::read(r)?;
- let byte_size = (len as usize)
- .checked_mul(COMPACT_SIGNATURE_SIZE)
- .ok_or(DecodeError::BadLengthDescriptor)?;
- if byte_size > MAX_BUF_SIZE {
- return Err(DecodeError::BadLengthDescriptor);
+ let mut len: CollectionLength = Readable::read(r)?;
+ let mut ret = Vec::new();
+ while len.0 > 0 {
+ let readamt = cmp::min(len.0 as usize, MAX_BUF_SIZE);
+ let readstart = ret.len();
+ ret.resize(readstart + readamt, 0);
+ r.read_exact(&mut ret[readstart..])?;
+ len.0 -= readamt as u64;
}
- let mut ret = Vec::with_capacity(len as usize);
- for _ in 0..len { ret.push(Readable::read(r)?); }
Ok(ret)
}
}
+impl_for_vec!(ecdsa::Signature);
+impl_for_vec!((A, B), A, B);
+
impl Writeable for Script {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
(self.len() as u16).write(w)?;
}
}
+impl<A: Readable, B: Readable, C: Readable, D: Readable> Readable for (A, B, C, D) {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let a: A = Readable::read(r)?;
+ let b: B = Readable::read(r)?;
+ let c: C = Readable::read(r)?;
+ let d: D = Readable::read(r)?;
+ Ok((a, b, c, d))
+ }
+}
+impl<A: Writeable, B: Writeable, C: Writeable, D: Writeable> Writeable for (A, B, C, D) {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ self.0.write(w)?;
+ self.1.write(w)?;
+ self.2.write(w)?;
+ self.3.write(w)
+ }
+}
+
impl Writeable for () {
fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
Ok(())
impl Writeable for String {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- (self.len() as u16).write(w)?;
+ CollectionLength(self.len() as u64).write(w)?;
w.write_all(self.as_bytes())
}
}
field.write($stream)?;
}
};
+ ($stream: expr, $type: expr, $field: expr, ignorable) => {
+ $crate::_encode_tlv!($stream, $type, $field, required);
+ };
($stream: expr, $type: expr, $field: expr, (option, encoding: ($fieldty: ty, $encoding: ident))) => {
$crate::_encode_tlv!($stream, $type, $field.map(|f| $encoding(f)), option);
};
$len.0 += field_len;
}
};
+ ($len: expr, $type: expr, $field: expr, ignorable) => {
+ $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, required);
+ };
}
/// See the documentation of [`write_tlv_fields`].
($reader: expr, $field: ident, (option: $trait: ident $(, $read_arg: expr)?)) => {{
$field = Some($trait::read(&mut $reader $(, $read_arg)*)?);
}};
+ ($reader: expr, $field: ident, (option, encoding: ($fieldty: ty, $encoding: ident, $encoder:ty))) => {{
+ $crate::_decode_tlv!($reader, $field, (option, encoding: ($fieldty, $encoding)));
+ }};
($reader: expr, $field: ident, (option, encoding: ($fieldty: ty, $encoding: ident))) => {{
$field = {
let field: $encoding<$fieldty> = ser::Readable::read(&mut $reader)?;
($field: ident, option) => {
$field
};
+ ($field: ident, ignorable) => {
+ if $field.is_none() { return Ok(None); } else { $field.unwrap() }
+ };
($field: ident, required) => {
$field.0.unwrap()
};
($field: ident, option) => {
let mut $field = None;
};
+ ($field: ident, ignorable) => {
+ let mut $field = None;
+ };
}
/// Equivalent to running [`_init_tlv_field_var`] then [`read_tlv_fields`].
)*
}
- #[derive(Debug, PartialEq)]
+ #[cfg_attr(test, derive(PartialEq))]
+ #[derive(Debug)]
pub(super) struct $nameref<'a> {
$(
pub(super) $field: Option<tlv_record_ref_type!($fieldty)>,
macro_rules! tlv_record_type {
(($type:ty, $wrapper:ident)) => { $type };
+ (($type:ty, $wrapper:ident, $encoder:ty)) => { $type };
($type:ty) => { $type };
}
((u32, $wrapper: ident)) => { u32 };
((u64, $wrapper: ident)) => { u64 };
(($type:ty, $wrapper:ident)) => { &'a $type };
+ (($type:ty, $wrapper:ident, $encoder:ty)) => { $encoder };
($type:ty) => { &'a $type };
}
use crate::ln::channelmanager;
use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
use crate::ln::{msgs, wire};
+use crate::ln::msgs::LightningError;
use crate::ln::script::ShutdownScript;
use crate::routing::gossip::NetworkGraph;
use crate::routing::router::{find_route, InFlightHtlcs, Route, RouteHop, RouteParameters, Router, ScorerAccountingForInFlightHtlcs};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use core::mem;
use bitcoin::bech32::u5;
-use crate::chain::keysinterface::{InMemorySigner, Recipient, KeyMaterial, EntropySource, NodeSigner, SignerProvider};
+use crate::chain::keysinterface::{InMemorySigner, Recipient, EntropySource, NodeSigner, SignerProvider};
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
pub struct TestRouter<'a> {
pub network_graph: Arc<NetworkGraph<&'a TestLogger>>,
+ pub next_routes: Mutex<VecDeque<Result<Route, LightningError>>>,
}
impl<'a> TestRouter<'a> {
pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>) -> Self {
- Self { network_graph }
+ Self { network_graph, next_routes: Mutex::new(VecDeque::new()), }
+ }
+
+ pub fn expect_find_route(&self, result: Result<Route, LightningError>) {
+ let mut expected_routes = self.next_routes.lock().unwrap();
+ expected_routes.push_back(result);
}
}
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>,
inflight_htlcs: &InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
+ if let Some(find_route_res) = self.next_routes.lock().unwrap().pop_front() {
+ return find_route_res
+ }
let logger = TestLogger::new();
find_route(
payer, params, &self.network_graph, first_hops, &logger,
fn notify_payment_probe_failed(&self, _path: &[&RouteHop], _short_channel_id: u64) {}
}
+#[cfg(feature = "std")] // If we put this on the `if`, we get "attributes are not yet allowed on `if` expressions" on 1.41.1
+impl<'a> Drop for TestRouter<'a> {
+ fn drop(&mut self) {
+ if std::thread::panicking() {
+ return;
+ }
+ assert!(self.next_routes.lock().unwrap().is_empty());
+ }
+}
+
pub struct OnlyReadsKeysInterface {}
impl EntropySource for OnlyReadsKeysInterface {
fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] }}
-impl NodeSigner for OnlyReadsKeysInterface {
- fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> { unreachable!(); }
- fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
- let secp_ctx = Secp256k1::signing_only();
- Ok(PublicKey::from_secret_key(&secp_ctx, &self.get_node_secret(recipient)?))
- }
- fn ecdh(&self, _recipient: Recipient, _other_key: &PublicKey, _tweak: Option<&Scalar>) -> Result<SharedSecret, ()> { unreachable!(); }
- fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!(); }
- fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> { unreachable!(); }
-}
-
impl SignerProvider for OnlyReadsKeysInterface {
type Signer = EnforcingSigner;
fn derive_channel_signer(&self, _channel_value_satoshis: u64, _channel_keys_id: [u8; 32]) -> Self::Signer { unreachable!(); }
fn read_chan_signer(&self, mut reader: &[u8]) -> Result<Self::Signer, msgs::DecodeError> {
- let dummy_sk = SecretKey::from_slice(&[42; 32]).unwrap();
- let inner: InMemorySigner = ReadableArgs::read(&mut reader, dummy_sk)?;
+ let inner: InMemorySigner = Readable::read(&mut reader)?;
let state = Arc::new(Mutex::new(EnforcementState::new()));
Ok(EnforcingSigner::new_with_revoked(
self.chain_monitor.watch_channel(funding_txo, new_monitor)
}
- fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> chain::ChannelMonitorUpdateStatus {
+ fn update_channel(&self, funding_txo: OutPoint, update: &channelmonitor::ChannelMonitorUpdate) -> chain::ChannelMonitorUpdateStatus {
// Every monitor update should survive roundtrip
let mut w = TestVecWriter(Vec::new());
update.write(&mut w).unwrap();
assert!(channelmonitor::ChannelMonitorUpdate::read(
- &mut io::Cursor::new(&w.0)).unwrap() == update);
+ &mut io::Cursor::new(&w.0)).unwrap() == *update);
self.monitor_updates.lock().unwrap().entry(funding_txo.to_channel_id()).or_insert(Vec::new()).push(update.clone());
}
self.latest_monitor_update_id.lock().unwrap().insert(funding_txo.to_channel_id(),
- (funding_txo, update.update_id, MonitorUpdateId::from_monitor_update(&update)));
+ (funding_txo, update.update_id, MonitorUpdateId::from_monitor_update(update)));
let update_res = self.chain_monitor.update_channel(funding_txo, update);
// At every point where we get a monitor update, we should be able to send a useful monitor
// to a watchtower and disk...
self.update_rets.lock().unwrap().push_back(next_ret);
}
}
-impl<Signer: keysinterface::Sign> chainmonitor::Persist<Signer> for TestPersister {
+impl<Signer: keysinterface::WriteableEcdsaChannelSigner> chainmonitor::Persist<Signer> for TestPersister {
fn persist_new_channel(&self, _funding_txo: OutPoint, _data: &channelmonitor::ChannelMonitor<Signer>, _id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
if let Some(update_ret) = self.update_rets.lock().unwrap().pop_front() {
return update_ret
chain::ChannelMonitorUpdateStatus::Completed
}
- fn update_persisted_channel(&self, funding_txo: OutPoint, update: &Option<channelmonitor::ChannelMonitorUpdate>, _data: &channelmonitor::ChannelMonitor<Signer>, update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
+ fn update_persisted_channel(&self, funding_txo: OutPoint, update: Option<&channelmonitor::ChannelMonitorUpdate>, _data: &channelmonitor::ChannelMonitor<Signer>, update_id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
let mut ret = chain::ChannelMonitorUpdateStatus::Completed;
if let Some(update_ret) = self.update_rets.lock().unwrap().pop_front() {
ret = update_ret;
}
impl msgs::ChannelMessageHandler for TestChannelMessageHandler {
- fn handle_open_channel(&self, _their_node_id: &PublicKey, _their_features: InitFeatures, msg: &msgs::OpenChannel) {
+ fn handle_open_channel(&self, _their_node_id: &PublicKey, msg: &msgs::OpenChannel) {
self.received_msg(wire::Message::OpenChannel(msg.clone()));
}
- fn handle_accept_channel(&self, _their_node_id: &PublicKey, _their_features: InitFeatures, msg: &msgs::AcceptChannel) {
+ fn handle_accept_channel(&self, _their_node_id: &PublicKey, msg: &msgs::AcceptChannel) {
self.received_msg(wire::Message::AcceptChannel(msg.clone()));
}
fn handle_funding_created(&self, _their_node_id: &PublicKey, msg: &msgs::FundingCreated) {
fn handle_channel_ready(&self, _their_node_id: &PublicKey, msg: &msgs::ChannelReady) {
self.received_msg(wire::Message::ChannelReady(msg.clone()));
}
- fn handle_shutdown(&self, _their_node_id: &PublicKey, _their_features: &InitFeatures, msg: &msgs::Shutdown) {
+ fn handle_shutdown(&self, _their_node_id: &PublicKey, msg: &msgs::Shutdown) {
self.received_msg(wire::Message::Shutdown(msg.clone()));
}
fn handle_closing_signed(&self, _their_node_id: &PublicKey, msg: &msgs::ClosingSigned) {
}
}
+pub struct TestNodeSigner {
+ node_secret: SecretKey,
+}
+
+impl TestNodeSigner {
+ pub fn new(node_secret: SecretKey) -> Self {
+ Self { node_secret }
+ }
+}
+
+impl NodeSigner for TestNodeSigner {
+ fn get_inbound_payment_key_material(&self) -> crate::chain::keysinterface::KeyMaterial {
+ unreachable!()
+ }
+
+ fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
+ let node_secret = match recipient {
+ Recipient::Node => Ok(&self.node_secret),
+ Recipient::PhantomNode => Err(())
+ }?;
+ Ok(PublicKey::from_secret_key(&Secp256k1::signing_only(), node_secret))
+ }
+
+ fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&bitcoin::secp256k1::Scalar>) -> Result<SharedSecret, ()> {
+ let mut node_secret = match recipient {
+ Recipient::Node => Ok(self.node_secret.clone()),
+ Recipient::PhantomNode => Err(())
+ }?;
+ if let Some(tweak) = tweak {
+ node_secret = node_secret.mul_tweak(tweak).map_err(|_| ())?;
+ }
+ Ok(SharedSecret::new(other_key, &node_secret))
+ }
+
+ fn sign_invoice(&self, _: &[u8], _: &[bitcoin::bech32::u5], _: Recipient) -> Result<bitcoin::secp256k1::ecdsa::RecoverableSignature, ()> {
+ unreachable!()
+ }
+
+ fn sign_gossip_message(&self, _msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
+ unreachable!()
+ }
+}
+
pub struct TestKeysInterface {
pub backing: keysinterface::PhantomKeysManager,
pub override_random_bytes: Mutex<Option<[u8; 32]>>,
}
impl NodeSigner for TestKeysInterface {
- fn get_node_secret(&self, recipient: Recipient) -> Result<SecretKey, ()> {
- self.backing.get_node_secret(recipient)
- }
-
fn get_node_id(&self, recipient: Recipient) -> Result<PublicKey, ()> {
self.backing.get_node_id(recipient)
}
fn sign_invoice(&self, hrp_bytes: &[u8], invoice_data: &[u5], recipient: Recipient) -> Result<RecoverableSignature, ()> {
self.backing.sign_invoice(hrp_bytes, invoice_data, recipient)
}
+
+ fn sign_gossip_message(&self, msg: msgs::UnsignedGossipMessage) -> Result<Signature, ()> {
+ self.backing.sign_gossip_message(msg)
+ }
}
impl SignerProvider for TestKeysInterface {
fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::Signer, msgs::DecodeError> {
let mut reader = io::Cursor::new(buffer);
- let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret(Recipient::Node).unwrap())?;
+ let inner: InMemorySigner = Readable::read(&mut reader)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);
Ok(EnforcingSigner::new_with_revoked(
lightning = { path = "../lightning", default-features = false }
lightning-invoice = { path = "../lightning-invoice", default-features = false }
lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync", default-features = false }
+lightning-background-processor = { path = "../lightning-background-processor", features = ["futures"], default-features = false }
--- /dev/null
+## API Updates
+- The functions `inbound_payment::{create, create_from_hash}` and
+ `channelmanager::{create_inbound_payment, create_inbound_payment_for_hash}` now accept a
+ `min_final_cltv_expiry_delta` argument. This encodes the `min_final_cltv_expiry_delta` in the
+ payment secret metadata bytes to be validated on payment receipt.
+
+## Backwards Compatibility
+- If `min_final_cltv_expiry_delta` set for any of `inbound_payment::{create, create_from_hash}` or
+ `channelmanager::{create_inbound_payment, create_inbound_payment_for_hash}` then the payment will
+ not be receivable on versions of LDK prior to 0.0.114.
+