Add tests for handling channel announcements
authorUser <naumenko.gs@gmail.com>
Mon, 6 Apr 2020 23:21:14 +0000 (19:21 -0400)
committerUser <naumenko.gs@gmail.com>
Sat, 11 Apr 2020 12:30:21 +0000 (08:30 -0400)
lightning/src/chain/chaininterface.rs
lightning/src/ln/router.rs
lightning/src/util/test_utils.rs

index 73aecfe9444892f2a4d3a0cbdfda0b6f35e49ec0..b30ebc1ee1629441ca7ed95e4e27a2518279ad35 100644 (file)
@@ -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,
index e9b5eb29a43e096eaab1f30f5a28c8f0818642c2..a4b128459244421a8696c8028de7f35ae281b1bf 100644 (file)
@@ -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<All>, 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<Logger> = 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")
+               };
+       }
 }
index b6d90b3ed17b9b09c10420f2f18f3c59d23d71a7..13cbde4aa1fe35c24702d277a48b310c77d08490 100644 (file)
@@ -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<Result<(Script, u64), ChainError>>,
+}
+
+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<u32>) {
+               (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()
+       }
+}