From 37f0c1af79a66586fabda35ec5b98f7ceccecb8e Mon Sep 17 00:00:00 2001 From: User Date: Mon, 6 Apr 2020 19:21:14 -0400 Subject: [PATCH] Add tests for handling channel announcements --- lightning/src/chain/chaininterface.rs | 1 + lightning/src/ln/router.rs | 194 ++++++++++++++++++++++++++ lightning/src/util/test_utils.rs | 31 +++- 3 files changed, 224 insertions(+), 2 deletions(-) diff --git a/lightning/src/chain/chaininterface.rs b/lightning/src/chain/chaininterface.rs index 73aecfe94..b30ebc1ee 100644 --- a/lightning/src/chain/chaininterface.rs +++ b/lightning/src/chain/chaininterface.rs @@ -22,6 +22,7 @@ use std::marker::PhantomData; use std::ptr; /// Used to give chain error details upstream +#[derive(Clone)] pub enum ChainError { /// Client doesn't support UTXO lookup (but the chain hash matches our genesis block hash) NotSupported, diff --git a/lightning/src/ln/router.rs b/lightning/src/ln/router.rs index e9b5eb29a..a4b128459 100644 --- a/lightning/src/ln/router.rs +++ b/lightning/src/ln/router.rs @@ -1066,6 +1066,8 @@ mod tests { use bitcoin_hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::blockdata::constants::genesis_block; + use bitcoin::blockdata::script::Builder; + use bitcoin::blockdata::opcodes; use bitcoin::util::hash::BitcoinHash; use hex; @@ -1075,6 +1077,7 @@ mod tests { use secp256k1::Secp256k1; use std::sync::Arc; + use std::collections::btree_map::Entry as BtreeEntry; fn create_router() -> (Secp256k1, PublicKey, Router) { let secp_ctx = Secp256k1::new(); @@ -1969,4 +1972,195 @@ mod tests { Err(e) => assert_eq!(e.err, "Update older than last processed update") }; } + + #[test] + fn handling_channel_announcements() { + let secp_ctx = Secp256k1::new(); + let our_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice( + &hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap()); + let logger: Arc = Arc::new(test_utils::TestLogger::new()); + let chain_monitor = Arc::new(test_utils::TestChainWatcher::new()); + let router = Router::new(our_id, chain_monitor.clone(), Arc::clone(&logger)); + + let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap(); + let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap(); + let node_id_1 = PublicKey::from_secret_key(&secp_ctx, node_1_privkey); + let node_id_2 = PublicKey::from_secret_key(&secp_ctx, node_2_privkey); + let node_1_btckey = &SecretKey::from_slice(&[40; 32]).unwrap(); + let node_2_btckey = &SecretKey::from_slice(&[39; 32]).unwrap(); + + let good_script = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_slice(&PublicKey::from_secret_key(&secp_ctx, node_1_btckey).serialize()) + .push_slice(&PublicKey::from_secret_key(&secp_ctx, node_2_btckey).serialize()) + .push_opcode(opcodes::all::OP_PUSHNUM_2) + .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script().to_v0_p2wsh(); + + + let mut unsigned_announcement = UnsignedChannelAnnouncement { + features: ChannelFeatures::supported(), + chain_hash: genesis_block(Network::Testnet).header.bitcoin_hash(), + short_channel_id: 0, + node_id_1, + node_id_2, + bitcoin_key_1: PublicKey::from_secret_key(&secp_ctx, node_1_btckey), + bitcoin_key_2: PublicKey::from_secret_key(&secp_ctx, node_2_btckey), + excess_data: Vec::new(), + }; + + let channel_key = NetworkMap::get_key(unsigned_announcement.short_channel_id, + unsigned_announcement.chain_hash); + + let mut msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); + let valid_announcement = ChannelAnnouncement { + node_signature_1: secp_ctx.sign(&msghash, node_1_privkey), + node_signature_2: secp_ctx.sign(&msghash, node_2_privkey), + bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign(&msghash, node_2_btckey), + contents: unsigned_announcement.clone(), + }; + + // Test if the UTXO lookups were not supported + *chain_monitor.utxo_ret.lock().unwrap() = Err(chaininterface::ChainError::NotSupported); + + match router.handle_channel_announcement(&valid_announcement) { + Ok(res) => assert!(res), + _ => panic!() + }; + { + let network = router.network_map.write().unwrap(); + match network.channels.get(&channel_key) { + None => panic!(), + Some(_) => () + } + } + + // If we receive announcement for the same channel (with UTXO lookups disabled), + // drop new one on the floor, since we can't see any changes. + match router.handle_channel_announcement(&valid_announcement) { + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "Already have knowledge of channel") + }; + + + // Test if an associated transaction were not on-chain (or not confirmed). + *chain_monitor.utxo_ret.lock().unwrap() = Err(chaininterface::ChainError::UnknownTx); + unsigned_announcement.short_channel_id += 1; + + msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); + let valid_announcement = ChannelAnnouncement { + node_signature_1: secp_ctx.sign(&msghash, node_1_privkey), + node_signature_2: secp_ctx.sign(&msghash, node_2_privkey), + bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign(&msghash, node_2_btckey), + contents: unsigned_announcement.clone(), + }; + + match router.handle_channel_announcement(&valid_announcement) { + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "Channel announced without corresponding UTXO entry") + }; + + + // Now test if the transaction is found in the UTXO set and the script is correct. + unsigned_announcement.short_channel_id += 1; + *chain_monitor.utxo_ret.lock().unwrap() = Ok((good_script.clone(), 0)); + let channel_key = NetworkMap::get_key(unsigned_announcement.short_channel_id, + unsigned_announcement.chain_hash); + + msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); + let valid_announcement = ChannelAnnouncement { + node_signature_1: secp_ctx.sign(&msghash, node_1_privkey), + node_signature_2: secp_ctx.sign(&msghash, node_2_privkey), + bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign(&msghash, node_2_btckey), + contents: unsigned_announcement.clone(), + }; + match router.handle_channel_announcement(&valid_announcement) { + Ok(res) => assert!(res), + _ => panic!() + }; + { + let network = router.network_map.write().unwrap(); + match network.channels.get(&channel_key) { + None => panic!(), + Some(_) => () + } + } + + // If we receive announcement for the same channel (but TX is not confirmed), + // drop new one on the floor, since we can't see any changes. + *chain_monitor.utxo_ret.lock().unwrap() = Err(chaininterface::ChainError::UnknownTx); + match router.handle_channel_announcement(&valid_announcement) { + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "Channel announced without corresponding UTXO entry") + }; + + // But if it is confirmed, replace the channel + *chain_monitor.utxo_ret.lock().unwrap() = Ok((good_script, 0)); + unsigned_announcement.features = ChannelFeatures::empty(); + msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); + let valid_announcement = ChannelAnnouncement { + node_signature_1: secp_ctx.sign(&msghash, node_1_privkey), + node_signature_2: secp_ctx.sign(&msghash, node_2_privkey), + bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign(&msghash, node_2_btckey), + contents: unsigned_announcement.clone(), + }; + match router.handle_channel_announcement(&valid_announcement) { + Ok(res) => assert!(res), + _ => panic!() + }; + { + let mut network = router.network_map.write().unwrap(); + match network.channels.entry(channel_key) { + BtreeEntry::Occupied(channel_entry) => { + assert_eq!(channel_entry.get().features, ChannelFeatures::empty()); + }, + _ => panic!() + } + } + + // Don't relay valid channels with excess data + unsigned_announcement.short_channel_id += 1; + unsigned_announcement.excess_data.push(1); + msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); + let valid_announcement = ChannelAnnouncement { + node_signature_1: secp_ctx.sign(&msghash, node_1_privkey), + node_signature_2: secp_ctx.sign(&msghash, node_2_privkey), + bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign(&msghash, node_2_btckey), + contents: unsigned_announcement.clone(), + }; + match router.handle_channel_announcement(&valid_announcement) { + Ok(res) => assert!(!res), + _ => panic!() + }; + + unsigned_announcement.excess_data = Vec::new(); + let invalid_sig_announcement = ChannelAnnouncement { + node_signature_1: secp_ctx.sign(&msghash, node_1_privkey), + node_signature_2: secp_ctx.sign(&msghash, node_2_privkey), + bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign(&msghash, node_1_btckey), + contents: unsigned_announcement.clone(), + }; + match router.handle_channel_announcement(&invalid_sig_announcement) { + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "Invalid signature from remote node") + }; + + unsigned_announcement.node_id_1 = PublicKey::from_secret_key(&secp_ctx, node_2_privkey); + msghash = hash_to_message!(&Sha256dHash::hash(&unsigned_announcement.encode()[..])[..]); + let channel_to_itself_announcement = ChannelAnnouncement { + node_signature_1: secp_ctx.sign(&msghash, node_1_privkey), + node_signature_2: secp_ctx.sign(&msghash, node_1_privkey), + bitcoin_signature_1: secp_ctx.sign(&msghash, node_1_btckey), + bitcoin_signature_2: secp_ctx.sign(&msghash, node_2_btckey), + contents: unsigned_announcement.clone(), + }; + match router.handle_channel_announcement(&channel_to_itself_announcement) { + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "Channel announcement node had a channel with itself") + }; + } } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index b6d90b3ed..13cbde4aa 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -1,5 +1,5 @@ use chain::chaininterface; -use chain::chaininterface::ConfirmationTarget; +use chain::chaininterface::{ConfirmationTarget, ChainError, ChainWatchInterface}; use chain::transaction::OutPoint; use chain::keysinterface; use ln::channelmonitor; @@ -13,7 +13,9 @@ use util::logger::{Logger, Level, Record}; use util::ser::{Readable, ReadableArgs, Writer, Writeable}; use bitcoin::blockdata::transaction::Transaction; -use bitcoin::blockdata::script::Script; +use bitcoin::blockdata::script::{Builder, Script}; +use bitcoin::blockdata::block::Block; +use bitcoin::blockdata::opcodes; use bitcoin_hashes::sha256d::Hash as Sha256dHash; use bitcoin::network::constants::Network; @@ -263,3 +265,28 @@ impl TestKeysInterface { } } } + +pub struct TestChainWatcher { + pub utxo_ret: Mutex>, +} + +impl TestChainWatcher { + pub fn new() -> Self { + let script = Builder::new().push_opcode(opcodes::OP_TRUE).into_script(); + Self { utxo_ret: Mutex::new(Ok((script, u64::max_value()))) } + } +} + +impl ChainWatchInterface for TestChainWatcher { + fn install_watch_tx(&self, _txid: &Sha256dHash, _script_pub_key: &Script) { } + fn install_watch_outpoint(&self, _outpoint: (Sha256dHash, u32), _out_script: &Script) { } + fn watch_all_txn(&self) { } + fn filter_block<'a>(&self, _block: &'a Block) -> (Vec<&'a Transaction>, Vec) { + (Vec::new(), Vec::new()) + } + fn reentered(&self) -> usize { 0 } + + fn get_chain_utxo(&self, _genesis_hash: Sha256dHash, _unspent_tx_output_identifier: u64) -> Result<(Script, u64), ChainError> { + self.utxo_ret.lock().unwrap().clone() + } +} -- 2.39.5