Merge pull request #47 from ariard/block_disconnected_close_chan
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Fri, 20 Jul 2018 03:05:22 +0000 (23:05 -0400)
committerGitHub <noreply@github.com>
Fri, 20 Jul 2018 03:05:22 +0000 (23:05 -0400)
Implement channel closing for block_disconnected on ChainListener + test

1  2 
src/ln/channel.rs
src/ln/channelmanager.rs

diff --combined src/ln/channel.rs
index ded81f60c200f23506f3d0df3e48b32233f2aeca,6c04ff1df48e6a33fab482c4d20bc4ecee66ad31..9e9ff8c8d09dc331018d8720c731dee3f2a263b1
@@@ -231,7 -231,7 +231,7 @@@ const BOTH_SIDES_SHUTDOWN_MASK: u32 = (
  
  // TODO: We should refactor this to be an Inbound/OutboundChannel until initial setup handshaking
  // has been completed, and then turn into a Channel to get compiler-time enforcement of things like
- // calling get_channel_id() before we're set up or things like get_outbound_funding_signed on an
+ // calling channel_id() before we're set up or things like get_outbound_funding_signed on an
  // inbound channel.
  pub struct Channel {
        user_id: u64,
  
        local_keys: ChannelKeys,
  
 +      // Our commitment numbers start at 2^48-1 and count down, whereas the ones used in transaction
 +      // generation start at 0 and count up...this simplifies some parts of implementation at the
 +      // cost of others, but should really just be changed.
 +
        cur_local_commitment_transaction_number: u64,
        cur_remote_commitment_transaction_number: u64,
        value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
@@@ -347,7 -343,7 +347,7 @@@ impl Channel 
  
        /// Guaranteed to return a value no larger than channel_value_satoshis
        fn get_our_channel_reserve_satoshis(channel_value_satoshis: u64) -> u64 {
 -              cmp::min(channel_value_satoshis, 10) //TODO
 +              cmp::min(channel_value_satoshis, 1000) //TODO
        }
  
        fn derive_our_dust_limit_satoshis(at_open_background_feerate: u64) -> u64 {
                if msg.dust_limit_satoshis > msg.funding_satoshis {
                        return Err(HandleError{err: "Peer never wants payout outputs?", msg: Some(msgs::ErrorAction::DisconnectPeer{})});
                }
 -              if msg.max_htlc_value_in_flight_msat > msg.funding_satoshis * 1000 {
 -                      return Err(HandleError{err: "Bogus max_htlc_value_in_flight_satoshis", msg: Some(msgs::ErrorAction::DisconnectPeer{})});
 -              }
                if msg.htlc_minimum_msat >= (msg.funding_satoshis - msg.channel_reserve_satoshis) * 1000 {
                        return Err(HandleError{err: "Minimum htlc value is full channel value", msg: Some(msgs::ErrorAction::DisconnectPeer{})});
                }
                        channel_value_satoshis: msg.funding_satoshis,
                        their_dust_limit_satoshis: msg.dust_limit_satoshis,
                        our_dust_limit_satoshis: Channel::derive_our_dust_limit_satoshis(background_feerate),
 -                      their_max_htlc_value_in_flight_msat: msg.max_htlc_value_in_flight_msat,
 +                      their_max_htlc_value_in_flight_msat: cmp::min(msg.max_htlc_value_in_flight_msat, msg.funding_satoshis * 1000),
                        their_channel_reserve_satoshis: msg.channel_reserve_satoshis,
                        their_htlc_minimum_msat: msg.htlc_minimum_msat,
                        our_htlc_minimum_msat: Channel::derive_our_htlc_minimum_msat(msg.feerate_per_kw as u64),
        /// which peer generated this transaction and "to whom" this transaction flows.
        #[inline]
        fn build_commitment_transaction(&self, commitment_number: u64, keys: &TxCreationKeys, local: bool, generated_by_local: bool) -> (Transaction, Vec<HTLCOutputInCommitment>) {
 -              let obscured_commitment_transaction_number = self.get_commitment_transaction_number_obscure_factor() ^ commitment_number;
 +              let obscured_commitment_transaction_number = self.get_commitment_transaction_number_obscure_factor() ^ (0xffffffffffff - commitment_number);
  
                let txins = {
                        let mut ins: Vec<TxIn> = Vec::new();
                if msg.dust_limit_satoshis > 21000000 * 100000000 {
                        return Err(HandleError{err: "Peer never wants payout outputs?", msg: None});
                }
 -              if msg.max_htlc_value_in_flight_msat > self.channel_value_satoshis * 1000 {
 -                      return Err(HandleError{err: "Bogus max_htlc_value_in_flight_satoshis", msg: None});
 -              }
                if msg.channel_reserve_satoshis > self.channel_value_satoshis {
                        return Err(HandleError{err: "Bogus channel_reserve_satoshis", msg: None});
                }
                self.channel_monitor.set_their_htlc_base_key(&msg.htlc_basepoint);
  
                self.their_dust_limit_satoshis = msg.dust_limit_satoshis;
 -              self.their_max_htlc_value_in_flight_msat = msg.max_htlc_value_in_flight_msat;
 +              self.their_max_htlc_value_in_flight_msat = cmp::min(msg.max_htlc_value_in_flight_msat, self.channel_value_satoshis * 1000);
                self.their_channel_reserve_satoshis = msg.channel_reserve_satoshis;
                self.their_htlc_minimum_msat = msg.htlc_minimum_msat;
                self.their_to_self_delay = msg.to_self_delay;
                }
  
                let funding_txo = OutPoint::new(msg.funding_txid, msg.funding_output_index);
 -              self.channel_monitor.set_funding_info(funding_txo);
 +              let funding_txo_script = self.get_funding_redeemscript().to_v0_p2wsh();
 +              self.channel_monitor.set_funding_info((funding_txo, funding_txo_script));
  
                let (remote_initial_commitment_tx, our_signature) = match self.funding_created_signature(&msg.signature) {
                        Ok(res) => res,
                        panic!("Should not have advanced channel commitment tx numbers prior to funding_created");
                }
  
 -              self.channel_monitor.set_funding_info(funding_txo);
 +              let funding_txo_script = self.get_funding_redeemscript().to_v0_p2wsh();
 +              self.channel_monitor.set_funding_info((funding_txo, funding_txo_script));
  
                let (our_signature, commitment_tx) = match self.get_outbound_funding_created_signature() {
                        Ok(res) => res,
@@@ -2326,7 -2326,6 +2326,7 @@@ mod tests 
        use bitcoin::util::hash::Sha256dHash;
        use bitcoin::util::bip143;
        use bitcoin::network::serialize::serialize;
 +      use bitcoin::blockdata::script::Script;
        use bitcoin::blockdata::transaction::Transaction;
        use ln::channel::{Channel,ChannelKeys,HTLCOutput,HTLCState,HTLCOutputInCommitment,TxCreationKeys};
        use ln::channel::MAX_FUNDING_SATOSHIS;
                chan.our_dust_limit_satoshis = 546;
  
                let funding_info = OutPoint::new(Sha256dHash::from_hex("8984484a580b825b9972d7adb15050b3ab624ccd731946b3eeddb92f4e7ef6be").unwrap(), 0);
 -              chan.channel_monitor.set_funding_info(funding_info);
 +              chan.channel_monitor.set_funding_info((funding_info, Script::new()));
  
                chan.their_payment_basepoint = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&secp_ctx, &hex_bytes("4444444444444444444444444444444444444444444444444444444444444444").unwrap()[..]).unwrap()).unwrap();
                assert_eq!(chan.their_payment_basepoint.serialize()[..],
  
                macro_rules! test_commitment {
                        ( $their_sig_hex: expr, $our_sig_hex: expr, $tx_hex: expr) => {
 -                              unsigned_tx = chan.build_commitment_transaction(42, &keys, true, false);
 +                              unsigned_tx = chan.build_commitment_transaction(0xffffffffffff - 42, &keys, true, false);
                                let their_signature = Signature::from_der(&secp_ctx, &hex_bytes($their_sig_hex).unwrap()[..]).unwrap();
                                let sighash = Message::from_slice(&bip143::SighashComponents::new(&unsigned_tx.0).sighash_all(&unsigned_tx.0.input[0], &chan.get_funding_redeemscript(), chan.channel_value_satoshis)[..]).unwrap();
                                secp_ctx.verify(&sighash, &their_signature, &chan.their_funding_pubkey).unwrap();
diff --combined src/ln/channelmanager.rs
index f5af09a71a084f62568321010e52c33e20c8134a,725f598c3337739625e1b74cd25d1fad5c8d4076..4ba6e3d6af2ac72b76e8133a338be7759676490c
@@@ -754,7 -754,7 +754,7 @@@ impl ChannelManager 
                                                match forward_chan.send_htlc(forward_info.amt_to_forward, forward_info.payment_hash, forward_info.outgoing_cltv_value, forward_info.onion_packet.unwrap()) {
                                                        Err(_e) => {
                                                                let chan_update = self.get_channel_update(forward_chan).unwrap();
 -                                                              failed_forwards.push((forward_info.payment_hash, 0x4000 | 7, Some(chan_update)));
 +                                                              failed_forwards.push((forward_info.payment_hash, 0x1000 | 7, Some(chan_update)));
                                                                continue;
                                                        },
                                                        Ok(update_add) => {
@@@ -1085,13 -1085,25 +1085,25 @@@ impl ChainListener for ChannelManager 
                }
        }
  
+       /// We force-close the channel without letting our counterparty participate in the shutdown
        fn block_disconnected(&self, header: &BlockHeader) {
-               let mut channel_state = self.channel_state.lock().unwrap();
-               for channel in channel_state.by_id.values_mut() {
-                       if channel.block_disconnected(header) {
-                               //TODO Close channel here
+               let mut channel_lock = self.channel_state.lock().unwrap();
+               let channel_state = channel_lock.borrow_parts();
+               let short_to_id = channel_state.short_to_id;
+               channel_state.by_id.retain(|_,  v| {
+                       if v.block_disconnected(header) {
+                               let tx = v.force_shutdown();
+                               for broadcast_tx in tx {
+                                       self.tx_broadcaster.broadcast_transaction(&broadcast_tx);
+                               }
+                               if let Some(short_id) = v.get_short_channel_id() {
+                                       short_to_id.remove(&short_id);
+                               }
+                               false
+                       } else {
+                               true
                        }
-               }
+               });
        }
  }
  
@@@ -1465,7 -1477,7 +1477,7 @@@ impl ChannelMessageHandler for ChannelM
                        let chan = channel_state.by_id.get_mut(&forwarding_id).unwrap();
                        if !chan.is_live() {
                                let chan_update = self.get_channel_update(chan).unwrap();
 -                              return_err!("Forwarding channel is not in a ready state.", 0x4000 | 7, &chan_update.encode_with_len()[..]);
 +                              return_err!("Forwarding channel is not in a ready state.", 0x1000 | 7, &chan_update.encode_with_len()[..]);
                        }
                }
  
  mod tests {
        use chain::chaininterface;
        use chain::transaction::OutPoint;
+       use chain::chaininterface::ChainListener;
        use ln::channelmanager::{ChannelManager,OnionKeys};
        use ln::router::{Route, RouteHop, Router};
        use ln::msgs;
                        assert_eq!(node.chan_monitor.added_monitors.lock().unwrap().len(), 0);
                }
        }
+       #[test]
+       fn test_unconf_chan() {
+               // After creating a chan between nodes, we disconnect all blocks previously seen to force a channel close on nodes[0] side
+               let nodes = create_network(2);
+               create_announced_chan_between_nodes(&nodes, 0, 1);
+               let channel_state = nodes[0].node.channel_state.lock().unwrap();
+               assert_eq!(channel_state.by_id.len(), 1);
+               assert_eq!(channel_state.short_to_id.len(), 1);
+               mem::drop(channel_state);
+               let mut headers = Vec::new();
+               let mut header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
+               headers.push(header.clone());
+               for _i in 2..100 {
+                       header = BlockHeader { version: 0x20000000, prev_blockhash: header.bitcoin_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
+                       headers.push(header.clone());
+               }
+               while !headers.is_empty() {
+                       nodes[0].node.block_disconnected(&headers.pop().unwrap());
+               }
+               let channel_state = nodes[0].node.channel_state.lock().unwrap();
+               assert_eq!(channel_state.by_id.len(), 0);
+               assert_eq!(channel_state.short_to_id.len(), 0);
+       }
  }