X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=fuzz%2Fsrc%2Frouter.rs;h=00c53dfe58e5d2ca7ed365bc6381f0de6fb4427b;hb=59778dac488cff735004671cdefb3f4ac1f920fd;hp=9a11935cad6d1fdcee64a5d983211be802d7ae3e;hpb=28c9b56113ff1ebb1b505a2c979c55c1626aa06b;p=rust-lightning diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index 9a11935c..afe02813 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -7,29 +7,32 @@ // You may not use this file except in accordance with one or both of these // licenses. +use bitcoin::blockdata::constants::ChainHash; use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::transaction::TxOut; -use bitcoin::hash_types::BlockHash; -use lightning::chain; +use lightning::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use lightning::chain::transaction::OutPoint; -use lightning::ln::channelmanager::{ChannelDetails, ChannelCounterparty}; -use lightning::ln::features::InitFeatures; +use lightning::ln::ChannelId; +use lightning::ln::channelmanager::{self, ChannelDetails, ChannelCounterparty}; +use lightning::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures}; use lightning::ln::msgs; +use lightning::offers::invoice::BlindedPayInfo; use lightning::routing::gossip::{NetworkGraph, RoutingFees}; +use lightning::routing::utxo::{UtxoFuture, UtxoLookup, UtxoLookupError, UtxoResult}; use lightning::routing::router::{find_route, PaymentParameters, RouteHint, RouteHintHop, RouteParameters}; -use lightning::routing::scoring::FixedPenaltyScorer; +use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters}; +use lightning::util::config::UserConfig; +use lightning::util::hash_tables::*; use lightning::util::ser::Readable; use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::network::constants::Network; -use bitcoin::blockdata::constants::genesis_block; -use utils::test_logger; +use crate::utils::test_logger; use std::convert::TryInto; -use std::collections::HashSet; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -81,17 +84,36 @@ impl InputData { } } -struct FuzzChainSource { +struct FuzzChainSource<'a, 'b, Out: test_logger::Output> { input: Arc, + net_graph: &'a NetworkGraph<&'b test_logger::TestLogger>, } -impl chain::Access for FuzzChainSource { - fn get_utxo(&self, _genesis_hash: &BlockHash, _short_channel_id: u64) -> Result { - match self.input.get_slice(2) { - Some(&[0, _]) => Err(chain::AccessError::UnknownChain), - Some(&[1, _]) => Err(chain::AccessError::UnknownTx), - Some(&[_, x]) => Ok(TxOut { value: 0, script_pubkey: Builder::new().push_int(x as i64).into_script().to_v0_p2wsh() }), - None => Err(chain::AccessError::UnknownTx), - _ => unreachable!(), +impl UtxoLookup for FuzzChainSource<'_, '_, Out> { + fn get_utxo(&self, _chain_hash: &ChainHash, _short_channel_id: u64) -> UtxoResult { + let input_slice = self.input.get_slice(2); + if input_slice.is_none() { return UtxoResult::Sync(Err(UtxoLookupError::UnknownTx)); } + let input_slice = input_slice.unwrap(); + let txo_res = TxOut { + value: if input_slice[0] % 2 == 0 { 1_000_000 } else { 1_000 }, + script_pubkey: Builder::new().push_int(input_slice[1] as i64).into_script().to_v0_p2wsh(), + }; + match input_slice { + &[0, _] => UtxoResult::Sync(Err(UtxoLookupError::UnknownChain)), + &[1, _] => UtxoResult::Sync(Err(UtxoLookupError::UnknownTx)), + &[2, _] => { + let future = UtxoFuture::new(); + future.resolve_without_forwarding(self.net_graph, Ok(txo_res)); + UtxoResult::Async(future.clone()) + }, + &[3, _] => { + let future = UtxoFuture::new(); + future.resolve_without_forwarding(self.net_graph, Err(UtxoLookupError::UnknownTx)); + UtxoResult::Async(future.clone()) + }, + &[4, _] => { + UtxoResult::Async(UtxoFuture::new()) // the future will never resolve + }, + &[..] => UtxoResult::Sync(Ok(txo_res)), } } } @@ -135,6 +157,7 @@ pub fn do_test(data: &[u8], out: Out) { msgs::DecodeError::ShortRead => panic!("We picked the length..."), msgs::DecodeError::Io(e) => panic!("{:?}", e), msgs::DecodeError::UnsupportedCompression => return, + msgs::DecodeError::DangerousValue => return, } } }} @@ -149,6 +172,15 @@ pub fn do_test(data: &[u8], out: Out) { } } + macro_rules! get_pubkey_from_node_id { + ($node_id: expr ) => { + match PublicKey::from_slice($node_id.as_slice()) { + Ok(pk) => pk, + Err(_) => return, + } + } + } + macro_rules! get_pubkey { () => { match PublicKey::from_slice(get_slice!(33)) { @@ -161,11 +193,104 @@ pub fn do_test(data: &[u8], out: Out) { let logger = test_logger::TestLogger::new("".to_owned(), out); let our_pubkey = get_pubkey!(); - let net_graph = NetworkGraph::new(genesis_block(Network::Bitcoin).header.block_hash(), &logger); + let net_graph = NetworkGraph::new(Network::Bitcoin, &logger); + let chain_source = FuzzChainSource { + input: Arc::clone(&input), + net_graph: &net_graph, + }; - let mut node_pks = HashSet::new(); + let mut node_pks = new_hash_map(); let mut scid = 42; + macro_rules! first_hops { + ($first_hops_vec: expr) => { + match get_slice!(1)[0] { + 0 => None, + count => { + for _ in 0..count { + scid += 1; + let (rnid, _) = + node_pks.iter().skip(u16::from_be_bytes(get_slice!(2).try_into().unwrap()) as usize % node_pks.len()).next().unwrap(); + let capacity = u64::from_be_bytes(get_slice!(8).try_into().unwrap()); + $first_hops_vec.push(ChannelDetails { + channel_id: ChannelId::new_zero(), + counterparty: ChannelCounterparty { + node_id: *rnid, + features: channelmanager::provided_init_features(&UserConfig::default()), + unspendable_punishment_reserve: 0, + forwarding_info: None, + outbound_htlc_minimum_msat: None, + outbound_htlc_maximum_msat: None, + }, + funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), + channel_type: None, + short_channel_id: Some(scid), + inbound_scid_alias: None, + outbound_scid_alias: None, + channel_value_satoshis: capacity, + user_channel_id: 0, inbound_capacity_msat: 0, + unspendable_punishment_reserve: None, + confirmations_required: None, + confirmations: None, + force_close_spend_delay: None, + is_outbound: true, is_channel_ready: true, + is_usable: true, is_public: true, + balance_msat: 0, + outbound_capacity_msat: capacity.saturating_mul(1000), + next_outbound_htlc_limit_msat: capacity.saturating_mul(1000), + next_outbound_htlc_minimum_msat: 0, + inbound_htlc_minimum_msat: None, + inbound_htlc_maximum_msat: None, + config: None, + feerate_sat_per_1000_weight: None, + channel_shutdown_state: Some(channelmanager::ChannelShutdownState::NotShuttingDown), + pending_inbound_htlcs: Vec::new(), + pending_outbound_htlcs: Vec::new(), + }); + } + Some(&$first_hops_vec[..]) + }, + } + } + } + + macro_rules! last_hops { + ($last_hops: expr) => { + let count = get_slice!(1)[0]; + for _ in 0..count { + scid += 1; + let (rnid, _) = + node_pks.iter().skip(slice_to_be16(get_slice!(2)) as usize % node_pks.len()).next().unwrap(); + $last_hops.push(RouteHint(vec![RouteHintHop { + src_node_id: *rnid, + short_channel_id: scid, + fees: RoutingFees { + base_msat: slice_to_be32(get_slice!(4)), + proportional_millionths: slice_to_be32(get_slice!(4)), + }, + cltv_expiry_delta: slice_to_be16(get_slice!(2)), + htlc_minimum_msat: Some(slice_to_be64(get_slice!(8))), + htlc_maximum_msat: None, + }])); + } + } + } + + macro_rules! find_routes { + ($first_hops: expr, $node_pks: expr, $route_params: expr) => { + let scorer = ProbabilisticScorer::new(ProbabilisticScoringDecayParameters::default(), &net_graph, &logger); + let random_seed_bytes: [u8; 32] = [get_slice!(1)[0]; 32]; + for (target, ()) in $node_pks { + let final_value_msat = slice_to_be64(get_slice!(8)); + let final_cltv_expiry_delta = slice_to_be32(get_slice!(4)); + let route_params = $route_params(final_value_msat, final_cltv_expiry_delta, target); + let _ = find_route(&our_pubkey, &route_params, &net_graph, + $first_hops.map(|c| c.iter().collect::>()).as_ref().map(|a| a.as_slice()), + &logger, &scorer, &ProbabilisticScoringFeeParameters::default(), &random_seed_bytes); + } + } + } + loop { match get_slice!(1)[0] { 0 => { @@ -175,103 +300,82 @@ pub fn do_test(data: &[u8], out: Out) { return; } let msg = decode_msg_with_len16!(msgs::UnsignedNodeAnnouncement, 288); - node_pks.insert(msg.node_id); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id), ()); let _ = net_graph.update_node_from_unsigned_announcement(&msg); }, 1 => { let msg = decode_msg_with_len16!(msgs::UnsignedChannelAnnouncement, 32+8+33*4); - node_pks.insert(msg.node_id_1); - node_pks.insert(msg.node_id_2); - let _ = net_graph.update_channel_from_unsigned_announcement::<&FuzzChainSource>(&msg, &None); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_1), ()); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_2), ()); + let _ = net_graph.update_channel_from_unsigned_announcement:: + <&FuzzChainSource<'_, '_, Out>>(&msg, &None); }, 2 => { let msg = decode_msg_with_len16!(msgs::UnsignedChannelAnnouncement, 32+8+33*4); - node_pks.insert(msg.node_id_1); - node_pks.insert(msg.node_id_2); - let _ = net_graph.update_channel_from_unsigned_announcement(&msg, &Some(&FuzzChainSource { input: Arc::clone(&input) })); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_1), ()); + node_pks.insert(get_pubkey_from_node_id!(msg.node_id_2), ()); + let _ = net_graph.update_channel_from_unsigned_announcement(&msg, &Some(&chain_source)); }, 3 => { let _ = net_graph.update_channel_unsigned(&decode_msg!(msgs::UnsignedChannelUpdate, 72)); }, 4 => { let short_channel_id = slice_to_be64(get_slice!(8)); - net_graph.channel_failed(short_channel_id, false); + net_graph.channel_failed_permanent(short_channel_id); }, _ if node_pks.is_empty() => {}, - _ => { + x if x < 250 => { let mut first_hops_vec = Vec::new(); - let first_hops = match get_slice!(1)[0] { - 0 => None, - count => { - for _ in 0..count { - scid += 1; - let rnid = node_pks.iter().skip(u16::from_be_bytes(get_slice!(2).try_into().unwrap()) as usize % node_pks.len()).next().unwrap(); - let capacity = u64::from_be_bytes(get_slice!(8).try_into().unwrap()); - first_hops_vec.push(ChannelDetails { - channel_id: [0; 32], - counterparty: ChannelCounterparty { - node_id: *rnid, - features: InitFeatures::known(), - unspendable_punishment_reserve: 0, - forwarding_info: None, - outbound_htlc_minimum_msat: None, - outbound_htlc_maximum_msat: None, - }, - funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), - channel_type: None, - short_channel_id: Some(scid), - inbound_scid_alias: None, - outbound_scid_alias: None, - channel_value_satoshis: capacity, - user_channel_id: 0, inbound_capacity_msat: 0, - unspendable_punishment_reserve: None, - confirmations_required: None, - force_close_spend_delay: None, - is_outbound: true, is_channel_ready: true, - is_usable: true, is_public: true, - balance_msat: 0, - outbound_capacity_msat: capacity.saturating_mul(1000), - next_outbound_htlc_limit_msat: capacity.saturating_mul(1000), - inbound_htlc_minimum_msat: None, - inbound_htlc_maximum_msat: None, - config: None, - }); - } - Some(&first_hops_vec[..]) - }, - }; + // Use macros here and in the blinded match arm to ensure values are fetched from the fuzz + // input in the same order, for better coverage. + let first_hops = first_hops!(first_hops_vec); let mut last_hops = Vec::new(); - { - let count = get_slice!(1)[0]; - for _ in 0..count { - scid += 1; - let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap(); - last_hops.push(RouteHint(vec![RouteHintHop { - src_node_id: *rnid, - short_channel_id: scid, - fees: RoutingFees { - base_msat: slice_to_be32(get_slice!(4)), - proportional_millionths: slice_to_be32(get_slice!(4)), - }, - cltv_expiry_delta: slice_to_be16(get_slice!(2)), - htlc_minimum_msat: Some(slice_to_be64(get_slice!(8))), - htlc_maximum_msat: None, - }])); - } - } - let scorer = FixedPenaltyScorer::with_penalty(0); - let random_seed_bytes: [u8; 32] = [get_slice!(1)[0]; 32]; - for target in node_pks.iter() { - let route_params = RouteParameters { - payment_params: PaymentParameters::from_node_id(*target).with_route_hints(last_hops.clone()), - final_value_msat: slice_to_be64(get_slice!(8)), - final_cltv_expiry_delta: slice_to_be32(get_slice!(4)), - }; - let _ = find_route(&our_pubkey, &route_params, &net_graph, - first_hops.map(|c| c.iter().collect::>()).as_ref().map(|a| a.as_slice()), - &logger, &scorer, &random_seed_bytes); - } + last_hops!(last_hops); + find_routes!(first_hops, node_pks.iter(), |final_amt, final_delta, target: &PublicKey| { + RouteParameters::from_payment_params_and_value( + PaymentParameters::from_node_id(*target, final_delta) + .with_route_hints(last_hops.clone()).unwrap(), + final_amt) + }); }, + x => { + let mut first_hops_vec = Vec::new(); + let first_hops = first_hops!(first_hops_vec); + let mut last_hops_unblinded = Vec::new(); + last_hops!(last_hops_unblinded); + let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap(); + let last_hops: Vec<(BlindedPayInfo, BlindedPath)> = last_hops_unblinded.into_iter().map(|hint| { + let hop = &hint.0[0]; + let payinfo = BlindedPayInfo { + fee_base_msat: hop.fees.base_msat, + fee_proportional_millionths: hop.fees.proportional_millionths, + htlc_minimum_msat: hop.htlc_minimum_msat.unwrap(), + htlc_maximum_msat: hop.htlc_minimum_msat.unwrap().saturating_mul(100), + cltv_expiry_delta: hop.cltv_expiry_delta, + features: BlindedHopFeatures::empty(), + }; + let num_blinded_hops = x % 250; + let mut blinded_hops = Vec::new(); + for _ in 0..num_blinded_hops { + blinded_hops.push(BlindedHop { + blinded_node_id: dummy_pk, + encrypted_payload: Vec::new() + }); + } + (payinfo, BlindedPath { + introduction_node: IntroductionNode::NodeId(hop.src_node_id), + blinding_point: dummy_pk, + blinded_hops, + }) + }).collect(); + let mut features = Bolt12InvoiceFeatures::empty(); + features.set_basic_mpp_optional(); + find_routes!(first_hops, [(dummy_pk, ())].iter(), |final_amt, _, _| { + RouteParameters::from_payment_params_and_value(PaymentParameters::blinded(last_hops.clone()) + .with_bolt12_features(features.clone()).unwrap(), + final_amt) + }); + } } } }