Ensure `transactions_confirmed` is idempotent
[rust-lightning] / lightning / src / ln / functional_test_utils.rs
index adc21cd02691d123afcb7bea5153c9f251e26366..64699dadcdd14580acae96b45e98089fcd96e1a0 100644 (file)
@@ -107,6 +107,14 @@ pub enum ConnectStyle {
        /// The same as `TransactionsFirst`, however when we have multiple blocks to connect, we only
        /// make a single `best_block_updated` call.
        TransactionsFirstSkippingBlocks,
+       /// The same as `TransactionsFirst`, however when we have multiple blocks to connect, we only
+       /// make a single `best_block_updated` call. Further, we call `transactions_confirmed` multiple
+       /// times to ensure it's idempotent.
+       TransactionsDuplicativelyFirstSkippingBlocks,
+       /// The same as `TransactionsFirst`, however when we have multiple blocks to connect, we only
+       /// make a single `best_block_updated` call. Further, we call `transactions_confirmed` multiple
+       /// times to ensure it's idempotent.
+       HighlyRedundantTransactionsFirstSkippingBlocks,
        /// The same as `TransactionsFirst` when connecting blocks. During disconnection only
        /// `transaction_unconfirmed` is called.
        TransactionsFirstReorgsOnlyTip,
@@ -121,14 +129,16 @@ impl ConnectStyle {
                        use core::hash::{BuildHasher, Hasher};
                        // Get a random value using the only std API to do so - the DefaultHasher
                        let rand_val = std::collections::hash_map::RandomState::new().build_hasher().finish();
-                       let res = match rand_val % 7 {
+                       let res = match rand_val % 9 {
                                0 => ConnectStyle::BestBlockFirst,
                                1 => ConnectStyle::BestBlockFirstSkippingBlocks,
                                2 => ConnectStyle::BestBlockFirstReorgsOnlyTip,
                                3 => ConnectStyle::TransactionsFirst,
                                4 => ConnectStyle::TransactionsFirstSkippingBlocks,
-                               5 => ConnectStyle::TransactionsFirstReorgsOnlyTip,
-                               6 => ConnectStyle::FullBlockViaListen,
+                               5 => ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks,
+                               6 => ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks,
+                               7 => ConnectStyle::TransactionsFirstReorgsOnlyTip,
+                               8 => ConnectStyle::FullBlockViaListen,
                                _ => unreachable!(),
                        };
                        eprintln!("Using Block Connection Style: {:?}", res);
@@ -143,6 +153,7 @@ impl ConnectStyle {
 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|
+                       ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks|ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks|
                        ConnectStyle::BestBlockFirstReorgsOnlyTip|ConnectStyle::TransactionsFirstReorgsOnlyTip => true,
                _ => false,
        };
@@ -193,8 +204,32 @@ fn do_connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: Block, sk
                                node.node.best_block_updated(&block.header, height);
                                node.node.transactions_confirmed(&block.header, &txdata, height);
                        },
-                       ConnectStyle::TransactionsFirst|ConnectStyle::TransactionsFirstSkippingBlocks|ConnectStyle::TransactionsFirstReorgsOnlyTip => {
+                       ConnectStyle::TransactionsFirst|ConnectStyle::TransactionsFirstSkippingBlocks|
+                       ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks|ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks|
+                       ConnectStyle::TransactionsFirstReorgsOnlyTip => {
+                               if *node.connect_style.borrow() == ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks {
+                                       let mut connections = Vec::new();
+                                       for (block, height) in node.blocks.lock().unwrap().iter() {
+                                               if !block.txdata.is_empty() {
+                                                       // Reconnect all transactions we've ever seen to ensure transaction connection
+                                                       // is *really* idempotent. This is a somewhat likely deployment for some
+                                                       // esplora implementations of chain sync which try to reduce state and
+                                                       // complexity as much as possible.
+                                                       //
+                                                       // Sadly we have to clone the block here to maintain lockorder. In the
+                                                       // future we should consider Arc'ing the blocks to avoid this.
+                                                       connections.push((block.clone(), *height));
+                                               }
+                                       }
+                                       for (old_block, height) in connections {
+                                               node.chain_monitor.chain_monitor.transactions_confirmed(&old_block.header,
+                                                       &old_block.txdata.iter().enumerate().collect::<Vec<_>>(), height);
+                                       }
+                               }
                                node.chain_monitor.chain_monitor.transactions_confirmed(&block.header, &txdata, height);
+                               if *node.connect_style.borrow() == ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks {
+                                       node.chain_monitor.chain_monitor.transactions_confirmed(&block.header, &txdata, height);
+                               }
                                call_claimable_balances(node);
                                node.chain_monitor.chain_monitor.best_block_updated(&block.header, height);
                                node.node.transactions_confirmed(&block.header, &txdata, height);
@@ -226,7 +261,8 @@ pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32)
                                node.chain_monitor.chain_monitor.block_disconnected(&orig.0.header, orig.1);
                                Listen::block_disconnected(node.node, &orig.0.header, orig.1);
                        },
-                       ConnectStyle::BestBlockFirstSkippingBlocks|ConnectStyle::TransactionsFirstSkippingBlocks => {
+                       ConnectStyle::BestBlockFirstSkippingBlocks|ConnectStyle::TransactionsFirstSkippingBlocks|
+                       ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks|ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks => {
                                if i == count - 1 {
                                        node.chain_monitor.chain_monitor.best_block_updated(&prev.0.header, prev.1);
                                        node.node.best_block_updated(&prev.0.header, prev.1);
@@ -276,6 +312,7 @@ pub struct NodeCfg<'a> {
 pub struct Node<'a, 'b: 'a, 'c: 'b> {
        pub chain_source: &'c test_utils::TestChainSource,
        pub tx_broadcaster: &'c test_utils::TestBroadcaster,
+       pub fee_estimator: &'c test_utils::TestFeeEstimator,
        pub chain_monitor: &'b test_utils::TestChainMonitor<'c>,
        pub keys_manager: &'b test_utils::TestKeysInterface,
        pub node: &'a ChannelManager<&'b TestChainMonitor<'c>, &'c test_utils::TestBroadcaster, &'b test_utils::TestKeysInterface, &'c test_utils::TestFeeEstimator, &'c test_utils::TestLogger>,
@@ -587,7 +624,7 @@ macro_rules! get_local_commitment_txn {
 macro_rules! unwrap_send_err {
        ($res: expr, $all_failed: expr, $type: pat, $check: expr) => {
                match &$res {
-                       &Err(PaymentSendFailure::AllFailedRetrySafe(ref fails)) if $all_failed => {
+                       &Err(PaymentSendFailure::AllFailedResendSafe(ref fails)) if $all_failed => {
                                assert_eq!(fails.len(), 1);
                                match fails[0] {
                                        $type => { $check },
@@ -618,6 +655,60 @@ macro_rules! check_added_monitors {
        }
 }
 
+pub fn _reload_node<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, default_config: UserConfig, chanman_encoded: &[u8], monitors_encoded: &[&[u8]]) -> ChannelManager<&'b TestChainMonitor<'c>, &'c test_utils::TestBroadcaster, &'b test_utils::TestKeysInterface, &'c test_utils::TestFeeEstimator, &'c test_utils::TestLogger> {
+       let mut monitors_read = Vec::with_capacity(monitors_encoded.len());
+       for encoded in monitors_encoded {
+               let mut monitor_read = &encoded[..];
+               let (_, monitor) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>
+                       ::read(&mut monitor_read, node.keys_manager).unwrap();
+               assert!(monitor_read.is_empty());
+               monitors_read.push(monitor);
+       }
+
+       let mut node_read = &chanman_encoded[..];
+       let (_, node_deserialized) = {
+               let mut channel_monitors = HashMap::new();
+               for monitor in monitors_read.iter_mut() {
+                       assert!(channel_monitors.insert(monitor.get_funding_txo().0, monitor).is_none());
+               }
+               <(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestLogger>)>::read(&mut node_read, ChannelManagerReadArgs {
+                       default_config,
+                       keys_manager: node.keys_manager,
+                       fee_estimator: node.fee_estimator,
+                       chain_monitor: node.chain_monitor,
+                       tx_broadcaster: node.tx_broadcaster,
+                       logger: node.logger,
+                       channel_monitors,
+               }).unwrap()
+       };
+       assert!(node_read.is_empty());
+
+       for monitor in monitors_read.drain(..) {
+               assert_eq!(node.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor),
+                       ChannelMonitorUpdateStatus::Completed);
+               check_added_monitors!(node, 1);
+       }
+
+       node_deserialized
+}
+
+#[cfg(test)]
+macro_rules! reload_node {
+       ($node: expr, $new_config: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => {
+               let chanman_encoded = $chanman_encoded;
+
+               $persister = test_utils::TestPersister::new();
+               $new_chain_monitor = test_utils::TestChainMonitor::new(Some($node.chain_source), $node.tx_broadcaster.clone(), $node.logger, $node.fee_estimator, &$persister, &$node.keys_manager);
+               $node.chain_monitor = &$new_chain_monitor;
+
+               $new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded);
+               $node.node = &$new_channelmanager;
+       };
+       ($node: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => {
+               reload_node!($node, $crate::util::config::UserConfig::default(), $chanman_encoded, $monitors_encoded, $persister, $new_chain_monitor, $new_channelmanager);
+       };
+}
+
 pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128) -> ([u8; 32], Transaction, OutPoint) {
        let chan_id = *node.network_chan_count.borrow();
 
@@ -2128,6 +2219,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
                let gossip_sync = P2PGossipSync::new(&cfgs[i].network_graph, None, cfgs[i].logger);
                nodes.push(Node{
                        chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster,
+                       fee_estimator: cfgs[i].fee_estimator,
                        chain_monitor: &cfgs[i].chain_monitor, keys_manager: &cfgs[i].keys_manager,
                        node: &chan_mgrs[i], network_graph: &cfgs[i].network_graph, gossip_sync,
                        node_seed: cfgs[i].node_seed, network_chan_count: chan_count.clone(),