X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Ffunctional_test_utils.rs;h=af11ef5d86baf01c98d0d815a753ac23ffc3535b;hb=e23c270720df2798c3e35e2ba804d98060d76d17;hp=3672a1ba4b8fa0bce1703e35a4838477b1054675;hpb=e985334fd2a297a6b3a3e4637a4277147c4b9d7d;p=rust-lightning diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 3672a1ba..af11ef5d 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -10,7 +10,7 @@ //! A bunch of useful utilities for building networks of nodes and exchanging messages between //! nodes for functional tests. -use chain::Watch; +use chain::{Listen, Watch}; use chain::channelmonitor::ChannelMonitor; use chain::transaction::OutPoint; use ln::channelmanager::{ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentPreimage, PaymentHash, PaymentSecret, PaymentSendFailure}; @@ -50,7 +50,7 @@ pub const CHAN_CONFIRM_DEPTH: u32 = 10; /// top, giving the given transaction CHAN_CONFIRM_DEPTH confirmations. pub fn confirm_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) { confirm_transaction_at(node, tx, node.best_block_info().1 + 1); - connect_blocks(node, CHAN_CONFIRM_DEPTH - 1, node.best_block_info().1, false, Default::default()); + connect_blocks(node, CHAN_CONFIRM_DEPTH - 1); } /// Mine a signle block containing the given transaction pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) { @@ -60,65 +60,110 @@ pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transac /// Mine the given transaction at the given height, mining blocks as required to build to that /// height pub fn confirm_transaction_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) { - let starting_block = node.best_block_info(); + let first_connect_height = node.best_block_info().1 + 1; + assert!(first_connect_height <= conf_height); + if conf_height - first_connect_height >= 1 { + connect_blocks(node, conf_height - first_connect_height); + } let mut block = Block { - header: BlockHeader { version: 0x20000000, prev_blockhash: starting_block.0, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 }, + header: BlockHeader { version: 0x20000000, prev_blockhash: node.best_block_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 }, txdata: Vec::new(), }; - let height = starting_block.1 + 1; - assert!(height <= conf_height); - for i in height..conf_height { - connect_block(node, &block, i); - block = Block { - header: BlockHeader { version: 0x20000000, prev_blockhash: block.header.block_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 }, - txdata: vec![], - }; - } - 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: 0, input: Vec::new(), output: Vec::new() }); } block.txdata.push(tx.clone()); - connect_block(node, &block, conf_height); -} + connect_block(node, &block); +} + +/// The possible ways we may notify a ChannelManager of a new block +pub enum ConnectStyle { + /// Calls update_best_block first, detecting transactions in the block only after receiving the + /// header and height information. + BestBlockFirst, + /// The same as BestBlockFirst, however when we have multiple blocks to connect, we only + /// make a single update_best_block call. + BestBlockFirstSkippingBlocks, + /// Calls transactions_confirmed first, detecting transactions in the block before updating the + /// header and height information. + TransactionsFirst, + /// The same as TransactionsFirst, however when we have multiple blocks to connect, we only + /// make a single update_best_block call. + TransactionsFirstSkippingBlocks, + /// Provides the full block via the chain::Listen interface. In the current code this is + /// equivalent to TransactionsFirst with some additional assertions. + FullBlockViaListen, +} + +pub fn connect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, depth: u32) -> BlockHash { + let skip_intermediaries = match *node.connect_style.borrow() { + ConnectStyle::BestBlockFirstSkippingBlocks|ConnectStyle::TransactionsFirstSkippingBlocks => true, + _ => false, + }; -pub fn connect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, depth: u32, height: u32, parent: bool, prev_blockhash: BlockHash) -> BlockHash { let mut block = Block { - header: BlockHeader { version: 0x2000000, prev_blockhash: if parent { prev_blockhash } else { node.best_block_hash() }, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 }, + header: BlockHeader { version: 0x2000000, prev_blockhash: node.best_block_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 }, txdata: vec![], }; - connect_block(node, &block, height + 1); - for i in 2..depth + 1 { + assert!(depth >= 1); + for _ in 0..depth - 1 { + do_connect_block(node, &block, skip_intermediaries); block = Block { header: BlockHeader { version: 0x20000000, prev_blockhash: block.header.block_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 }, txdata: vec![], }; - connect_block(node, &block, height + i); } + connect_block(node, &block); block.header.block_hash() } -pub fn connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: &Block, height: u32) { +pub fn connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: &Block) { + do_connect_block(node, block, false); +} + +fn do_connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: &Block, skip_manager: bool) { let txdata: Vec<_> = block.txdata.iter().enumerate().collect(); + let height = node.best_block_info().1 + 1; node.chain_monitor.chain_monitor.block_connected(&block.header, &txdata, height); - node.node.block_connected(&block.header, &txdata, height); + if !skip_manager { + match *node.connect_style.borrow() { + ConnectStyle::BestBlockFirst|ConnectStyle::BestBlockFirstSkippingBlocks => { + node.node.update_best_block(&block.header, height); + node.node.transactions_confirmed(&block.header, height, &block.txdata.iter().enumerate().collect::>()); + }, + ConnectStyle::TransactionsFirst|ConnectStyle::TransactionsFirstSkippingBlocks => { + node.node.transactions_confirmed(&block.header, height, &block.txdata.iter().enumerate().collect::>()); + node.node.update_best_block(&block.header, height); + }, + ConnectStyle::FullBlockViaListen => { + Listen::block_connected(node.node, &block, height); + } + } + } node.node.test_process_background_events(); node.blocks.borrow_mut().push((block.header, height)); } -pub fn disconnect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, header: &BlockHeader, height: u32) { - node.chain_monitor.chain_monitor.block_disconnected(header, height); - node.node.block_disconnected(header); - node.blocks.borrow_mut().pop(); -} pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) { - assert!(node.blocks.borrow_mut().len() as u32 > count); // Cannot disconnect genesis - for _ in 0..count { - let (block_header, height) = { - let blocks = node.blocks.borrow_mut(); - (blocks[blocks.len() - 1].0, blocks[blocks.len() - 1].1) - }; - disconnect_block(&node, &block_header, height); + for i in 0..count { + let orig_header = node.blocks.borrow_mut().pop().unwrap(); + assert!(orig_header.1 > 0); // Cannot disconnect genesis + let prev_header = node.blocks.borrow().last().unwrap().clone(); + + node.chain_monitor.chain_monitor.block_disconnected(&orig_header.0, orig_header.1); + match *node.connect_style.borrow() { + ConnectStyle::FullBlockViaListen => { + Listen::block_disconnected(node.node, &orig_header.0, orig_header.1); + }, + ConnectStyle::BestBlockFirstSkippingBlocks|ConnectStyle::TransactionsFirstSkippingBlocks => { + if i == count - 1 { + node.node.update_best_block(&prev_header.0, prev_header.1); + } + }, + _ => { + node.node.update_best_block(&prev_header.0, prev_header.1); + }, + } } } @@ -158,6 +203,7 @@ pub struct Node<'a, 'b: 'a, 'c: 'b> { pub network_chan_count: Rc>, pub logger: &'c test_utils::TestLogger, pub blocks: RefCell>, + pub connect_style: Rc>, } impl<'a, 'b, 'c> Node<'a, 'b, 'c> { pub fn best_block_hash(&self) -> BlockHash { @@ -319,6 +365,24 @@ macro_rules! get_event_msg { } } +/// Get a specific event from the pending events queue. +#[macro_export] +macro_rules! get_event { + ($node: expr, $event_type: path) => { + { + let mut events = $node.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + let ev = events.pop().unwrap(); + match ev { + $event_type { .. } => { + ev + }, + _ => panic!("Unexpected event"), + } + } + } +} + #[cfg(test)] macro_rules! get_htlc_update_msgs { ($node: expr, $node_id: expr) => { @@ -347,7 +411,8 @@ macro_rules! get_feerate { } } -#[cfg(test)] +/// Returns any local commitment transactions for the channel. +#[macro_export] macro_rules! get_local_commitment_txn { ($node: expr, $channel_id: expr) => { { @@ -461,7 +526,7 @@ pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, ' pub fn create_chan_between_nodes_with_value_confirm_first<'a, 'b, 'c, 'd>(node_recv: &'a Node<'b, 'c, 'c>, node_conf: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) { confirm_transaction_at(node_conf, tx, conf_height); - connect_blocks(node_conf, CHAN_CONFIRM_DEPTH - 1, node_conf.best_block_info().1, false, Default::default()); + connect_blocks(node_conf, CHAN_CONFIRM_DEPTH - 1); node_recv.node.handle_funding_locked(&node_conf.node.get_our_node_id(), &get_event_msg!(node_conf, MessageSendEvent::SendFundingLocked, node_recv.node.get_our_node_id())); } @@ -489,7 +554,7 @@ pub fn create_chan_between_nodes_with_value_confirm<'a, 'b, 'c, 'd>(node_a: &'a let conf_height = std::cmp::max(node_a.best_block_info().1 + 1, node_b.best_block_info().1 + 1); create_chan_between_nodes_with_value_confirm_first(node_a, node_b, tx, conf_height); confirm_transaction_at(node_a, tx, conf_height); - connect_blocks(node_a, CHAN_CONFIRM_DEPTH - 1, node_a.best_block_info().1, false, Default::default()); + connect_blocks(node_a, CHAN_CONFIRM_DEPTH - 1); create_chan_between_nodes_with_value_confirm_second(node_b, node_a) } @@ -853,7 +918,7 @@ macro_rules! expect_pending_htlcs_forwardable { }} } -#[cfg(test)] +#[cfg(any(test, feature = "unstable"))] macro_rules! expect_payment_received { ($node: expr, $expected_payment_hash: expr, $expected_recv_value: expr) => { let events = $node.node.get_and_clear_pending_events(); @@ -1230,6 +1295,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec(node: &Node<'a, 'b, 'c>, prev_txn: &Vec< pub fn get_announce_close_broadcast_events<'a, 'b, 'c>(nodes: &Vec>, a: usize, b: usize) { let events_1 = nodes[a].node.get_and_clear_pending_msg_events(); - assert_eq!(events_1.len(), 1); + assert_eq!(events_1.len(), 2); let as_update = match events_1[0] { MessageSendEvent::BroadcastChannelUpdate { ref msg } => { msg.clone() }, _ => panic!("Unexpected event"), }; + match events_1[1] { + MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => { + assert_eq!(node_id, nodes[b].node.get_our_node_id()); + assert_eq!(msg.data, "Commitment or closing transaction was confirmed on chain."); + }, + _ => panic!("Unexpected event"), + } let events_2 = nodes[b].node.get_and_clear_pending_msg_events(); - assert_eq!(events_2.len(), 1); + assert_eq!(events_2.len(), 2); let bs_update = match events_2[0] { MessageSendEvent::BroadcastChannelUpdate { ref msg } => { msg.clone() }, _ => panic!("Unexpected event"), }; + match events_2[1] { + MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => { + assert_eq!(node_id, nodes[a].node.get_our_node_id()); + assert_eq!(msg.data, "Commitment or closing transaction was confirmed on chain."); + }, + _ => panic!("Unexpected event"), + } for node in nodes { node.net_graph_msg_handler.handle_channel_update(&as_update).unwrap();