X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Fgossip.rs;h=92a0e479a3de6a2d12266e8f67cd049243d6ca0c;hb=3fb3218b6c311eaa4d932b3eea9e50e04bda4f9b;hp=925cd4cd4d32e6c0597217ff334413bc782d1a0e;hpb=12687d75d5a0eba4bc691998e51a5313c715e559;p=rust-lightning diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 925cd4cd..92a0e479 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -16,14 +16,13 @@ use bitcoin::secp256k1; use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash; -use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::transaction::TxOut; -use bitcoin::blockdata::opcodes; use bitcoin::hash_types::BlockHash; use chain; use chain::Access; -use ln::features::{ChannelFeatures, NodeFeatures}; +use ln::chan_utils::make_funding_redeemscript; +use ln::features::{ChannelFeatures, NodeFeatures, InitFeatures}; use ln::msgs::{DecodeError, ErrorAction, Init, LightningError, RoutingMessageHandler, NetAddress, MAX_VALUE_MSAT}; use ln::msgs::{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement, GossipTimestampFilter}; use ln::msgs::{QueryChannelRange, ReplyChannelRange, QueryShortChannelIds, ReplyShortChannelIdsEnd}; @@ -571,6 +570,12 @@ where C::Target: chain::Access, L::Target: Logger action: ErrorAction::IgnoreError, }) } + + fn provided_init_features(&self, _their_node_id: &PublicKey) -> InitFeatures { + let mut features = InitFeatures::empty(); + features.set_gossip_queries_optional(); + features + } } impl>, C: Deref, L: Deref> MessageSendEventsProvider for P2PGossipSync @@ -916,7 +921,7 @@ impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> { /// /// While this may be smaller than the actual channel capacity, amounts greater than /// [`Self::as_msat`] should not be routed through the channel. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum EffectiveCapacity { /// The available liquidity in the channel known from being a channel counterparty, and thus a /// direct hop. @@ -1434,6 +1439,39 @@ impl NetworkGraph where L::Target: Logger { return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError}); } + { + let channels = self.channels.read().unwrap(); + + if let Some(chan) = channels.get(&msg.short_channel_id) { + if chan.capacity_sats.is_some() { + // If we'd previously looked up the channel on-chain and checked the script + // against what appears on-chain, ignore the duplicate announcement. + // + // Because a reorg could replace one channel with another at the same SCID, if + // the channel appears to be different, we re-validate. This doesn't expose us + // to any more DoS risk than not, as a peer can always flood us with + // randomly-generated SCID values anyway. + // + // We use the Node IDs rather than the bitcoin_keys to check for "equivalence" + // as we didn't (necessarily) store the bitcoin keys, and we only really care + // if the peers on the channel changed anyway. + if NodeId::from_pubkey(&msg.node_id_1) == chan.node_one && NodeId::from_pubkey(&msg.node_id_2) == chan.node_two { + return Err(LightningError { + err: "Already have chain-validated channel".to_owned(), + action: ErrorAction::IgnoreDuplicateGossip + }); + } + } else if chain_access.is_none() { + // Similarly, if we can't check the chain right now anyway, ignore the + // duplicate announcement without bothering to take the channels write lock. + return Err(LightningError { + err: "Already have non-chain-validated channel".to_owned(), + action: ErrorAction::IgnoreDuplicateGossip + }); + } + } + } + let utxo_value = match &chain_access { &None => { // Tentatively accept, potentially exposing us to DoS attacks @@ -1442,13 +1480,10 @@ impl NetworkGraph where L::Target: Logger { &Some(ref chain_access) => { match chain_access.get_utxo(&msg.chain_hash, msg.short_channel_id) { Ok(TxOut { value, script_pubkey }) => { - let expected_script = Builder::new().push_opcode(opcodes::all::OP_PUSHNUM_2) - .push_slice(&msg.bitcoin_key_1.serialize()) - .push_slice(&msg.bitcoin_key_2.serialize()) - .push_opcode(opcodes::all::OP_PUSHNUM_2) - .push_opcode(opcodes::all::OP_CHECKMULTISIG).into_script().to_v0_p2wsh(); + let expected_script = + make_funding_redeemscript(&msg.bitcoin_key_1, &msg.bitcoin_key_2).to_v0_p2wsh(); if script_pubkey != expected_script { - return Err(LightningError{err: format!("Channel announcement key ({}) didn't match on-chain script ({})", script_pubkey.to_hex(), expected_script.to_hex()), action: ErrorAction::IgnoreError}); + return Err(LightningError{err: format!("Channel announcement key ({}) didn't match on-chain script ({})", expected_script.to_hex(), script_pubkey.to_hex()), action: ErrorAction::IgnoreError}); } //TODO: Check if value is worth storing, use it to inform routing, and compare it //to the new HTLC max field in channel_update @@ -1823,6 +1858,7 @@ impl ReadOnlyNetworkGraph<'_> { #[cfg(test)] mod tests { use chain; + use ln::chan_utils::make_funding_redeemscript; use ln::PaymentHash; use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, NodeAlias, MAX_EXCESS_BYTES_FOR_RELAY, NodeId, RoutingFees, ChannelUpdateInfo, ChannelInfo, NodeAnnouncementInfo, NodeInfo}; @@ -1840,9 +1876,8 @@ mod tests { use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::blockdata::constants::genesis_block; - use bitcoin::blockdata::script::{Builder, Script}; + use bitcoin::blockdata::script::Script; use bitcoin::blockdata::transaction::TxOut; - use bitcoin::blockdata::opcodes; use hex; @@ -1932,14 +1967,10 @@ mod tests { } fn get_channel_script(secp_ctx: &Secp256k1) -> Script { - let node_1_btckey = &SecretKey::from_slice(&[40; 32]).unwrap(); - let node_2_btckey = &SecretKey::from_slice(&[39; 32]).unwrap(); - 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 node_1_btckey = SecretKey::from_slice(&[40; 32]).unwrap(); + let node_2_btckey = SecretKey::from_slice(&[39; 32]).unwrap(); + make_funding_redeemscript(&PublicKey::from_secret_key(secp_ctx, &node_1_btckey), + &PublicKey::from_secret_key(secp_ctx, &node_2_btckey)).to_v0_p2wsh() } fn get_signed_channel_update(f: F, node_key: &SecretKey, secp_ctx: &Secp256k1) -> ChannelUpdate { @@ -2054,7 +2085,7 @@ mod tests { // drop new one on the floor, since we can't see any changes. match gossip_sync.handle_channel_announcement(&valid_announcement) { Ok(_) => panic!(), - Err(e) => assert_eq!(e.err, "Already have knowledge of channel") + Err(e) => assert_eq!(e.err, "Already have non-chain-validated channel") }; // Test if an associated transaction were not on-chain (or not confirmed). @@ -2088,32 +2119,13 @@ mod tests { }; } - // 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_source.utxo_ret.lock().unwrap() = Err(chain::AccessError::UnknownTx); - match gossip_sync.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 + // If we receive announcement for the same channel, once we've validated it against the + // chain, we simply ignore all new (duplicate) announcements. *chain_source.utxo_ret.lock().unwrap() = Ok(TxOut { value: 0, script_pubkey: good_script }); - let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| { - unsigned_announcement.features = ChannelFeatures::empty(); - unsigned_announcement.short_channel_id += 2; - }, node_1_privkey, node_2_privkey, &secp_ctx); match gossip_sync.handle_channel_announcement(&valid_announcement) { - Ok(res) => assert!(res), - _ => panic!() + Ok(_) => panic!(), + Err(e) => assert_eq!(e.err, "Already have chain-validated channel") }; - { - match network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id) { - Some(channel_entry) => { - assert_eq!(channel_entry.features, ChannelFeatures::empty()); - }, - _ => panic!() - }; - } // Don't relay valid channels with excess data let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| { @@ -2273,7 +2285,7 @@ mod tests { network_graph.handle_event(&Event::PaymentPathFailed { payment_id: None, payment_hash: PaymentHash([0; 32]), - rejected_by_dest: false, + payment_failed_permanently: false, all_paths_failed: true, path: vec![], network_update: Some(NetworkUpdate::ChannelUpdateMessage { @@ -2300,7 +2312,7 @@ mod tests { network_graph.handle_event(&Event::PaymentPathFailed { payment_id: None, payment_hash: PaymentHash([0; 32]), - rejected_by_dest: false, + payment_failed_permanently: false, all_paths_failed: true, path: vec![], network_update: Some(NetworkUpdate::ChannelFailure { @@ -2325,7 +2337,7 @@ mod tests { network_graph.handle_event(&Event::PaymentPathFailed { payment_id: None, payment_hash: PaymentHash([0; 32]), - rejected_by_dest: false, + payment_failed_permanently: false, all_paths_failed: true, path: vec![], network_update: Some(NetworkUpdate::ChannelFailure { @@ -2985,7 +2997,7 @@ mod tests { let legacy_chan_update_info_with_none: Vec = hex::decode("2c0004000000170201010402002a060800000000000004d20801000a0d0c00040000000902040000000a0c0100").unwrap(); let read_chan_update_info_res: Result = ::util::ser::Readable::read(&mut legacy_chan_update_info_with_none.as_slice()); assert!(read_chan_update_info_res.is_err()); - + // 2. Test encoding/decoding of ChannelInfo // Check we can encode/decode ChannelInfo without ChannelUpdateInfo fields present. let chan_info_none_updates = ChannelInfo {