use crate::ln::{msgs, wire};
use crate::ln::msgs::LightningError;
use crate::ln::script::ShutdownScript;
-use crate::routing::gossip::NetworkGraph;
+use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
+use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
use crate::routing::router::{find_route, InFlightHtlcs, Route, RouteHop, RouteParameters, Router, ScorerAccountingForInFlightHtlcs};
-use crate::routing::scoring::FixedPenaltyScorer;
+use crate::routing::scoring::{ChannelUsage, Score};
use crate::util::config::UserConfig;
use crate::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState};
use crate::util::events;
use crate::io;
use crate::prelude::*;
+use core::cell::RefCell;
use core::time::Duration;
use crate::sync::{Mutex, Arc};
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
pub struct TestRouter<'a> {
pub network_graph: Arc<NetworkGraph<&'a TestLogger>>,
- pub next_routes: Mutex<VecDeque<Result<Route, LightningError>>>,
+ pub next_routes: Mutex<VecDeque<(RouteParameters, Result<Route, LightningError>)>>,
+ pub scorer: &'a Mutex<TestScorer>,
}
impl<'a> TestRouter<'a> {
- pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>) -> Self {
- Self { network_graph, next_routes: Mutex::new(VecDeque::new()), }
+ pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>, scorer: &'a Mutex<TestScorer>) -> Self {
+ Self { network_graph, next_routes: Mutex::new(VecDeque::new()), scorer }
}
- pub fn expect_find_route(&self, result: Result<Route, LightningError>) {
+ pub fn expect_find_route(&self, query: RouteParameters, result: Result<Route, LightningError>) {
let mut expected_routes = self.next_routes.lock().unwrap();
- expected_routes.push_back(result);
+ expected_routes.push_back((query, result));
}
}
&self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&channelmanager::ChannelDetails]>,
inflight_htlcs: &InFlightHtlcs
) -> Result<Route, msgs::LightningError> {
- if let Some(find_route_res) = self.next_routes.lock().unwrap().pop_front() {
- return find_route_res
+ if let Some((find_route_query, find_route_res)) = self.next_routes.lock().unwrap().pop_front() {
+ assert_eq!(find_route_query, *params);
+ if let Ok(ref route) = find_route_res {
+ let locked_scorer = self.scorer.lock().unwrap();
+ let scorer = ScorerAccountingForInFlightHtlcs::new(locked_scorer, inflight_htlcs);
+ for path in &route.paths {
+ let mut aggregate_msat = 0u64;
+ for (idx, hop) in path.iter().rev().enumerate() {
+ aggregate_msat += hop.fee_msat;
+ let usage = ChannelUsage {
+ amount_msat: aggregate_msat,
+ inflight_htlc_msat: 0,
+ effective_capacity: EffectiveCapacity::Unknown,
+ };
+
+ // Since the path is reversed, the last element in our iteration is the first
+ // hop.
+ if idx == path.len() - 1 {
+ scorer.channel_penalty_msat(hop.short_channel_id, &NodeId::from_pubkey(payer), &NodeId::from_pubkey(&hop.pubkey), usage);
+ } else {
+ let curr_hop_path_idx = path.len() - 1 - idx;
+ scorer.channel_penalty_msat(hop.short_channel_id, &NodeId::from_pubkey(&path[curr_hop_path_idx - 1].pubkey), &NodeId::from_pubkey(&hop.pubkey), usage);
+ }
+ }
+ }
+ }
+ return find_route_res;
}
let logger = TestLogger::new();
+ let scorer = self.scorer.lock().unwrap();
find_route(
payer, params, &self.network_graph, first_hops, &logger,
- &ScorerAccountingForInFlightHtlcs::new(TestScorer::with_penalty(0), &inflight_htlcs),
+ &ScorerAccountingForInFlightHtlcs::new(scorer, &inflight_htlcs),
&[42; 32]
)
}
- fn notify_payment_path_failed(&self, _path: &[&RouteHop], _short_channel_id: u64) {}
- fn notify_payment_path_successful(&self, _path: &[&RouteHop]) {}
- fn notify_payment_probe_successful(&self, _path: &[&RouteHop]) {}
- fn notify_payment_probe_failed(&self, _path: &[&RouteHop], _short_channel_id: u64) {}
}
-#[cfg(feature = "std")] // If we put this on the `if`, we get "attributes are not yet allowed on `if` expressions" on 1.41.1
impl<'a> Drop for TestRouter<'a> {
fn drop(&mut self) {
- if std::thread::panicking() {
- return;
+ #[cfg(feature = "std")] {
+ if std::thread::panicking() {
+ return;
+ }
}
assert!(self.next_routes.lock().unwrap().is_empty());
}
self.update_rets.lock().unwrap().push_back(next_ret);
}
}
-impl<Signer: keysinterface::Sign> chainmonitor::Persist<Signer> for TestPersister {
+impl<Signer: keysinterface::WriteableEcdsaChannelSigner> chainmonitor::Persist<Signer> for TestPersister {
fn persist_new_channel(&self, _funding_txo: OutPoint, _data: &channelmonitor::ChannelMonitor<Signer>, _id: MonitorUpdateId) -> chain::ChannelMonitorUpdateStatus {
if let Some(update_ret) = self.update_rets.lock().unwrap().pop_front() {
return update_ret
pub struct TestChannelMessageHandler {
pub pending_events: Mutex<Vec<events::MessageSendEvent>>,
expected_recv_msgs: Mutex<Option<Vec<wire::Message<()>>>>,
+ connected_peers: Mutex<HashSet<PublicKey>>,
}
impl TestChannelMessageHandler {
TestChannelMessageHandler {
pending_events: Mutex::new(Vec::new()),
expected_recv_msgs: Mutex::new(None),
+ connected_peers: Mutex::new(HashSet::new()),
}
}
fn handle_channel_reestablish(&self, _their_node_id: &PublicKey, msg: &msgs::ChannelReestablish) {
self.received_msg(wire::Message::ChannelReestablish(msg.clone()));
}
- fn peer_disconnected(&self, _their_node_id: &PublicKey, _no_connection_possible: bool) {}
- fn peer_connected(&self, _their_node_id: &PublicKey, _msg: &msgs::Init) -> Result<(), ()> {
+ fn peer_disconnected(&self, their_node_id: &PublicKey) {
+ assert!(self.connected_peers.lock().unwrap().remove(their_node_id));
+ }
+ fn peer_connected(&self, their_node_id: &PublicKey, _msg: &msgs::Init, _inbound: bool) -> Result<(), ()> {
+ assert!(self.connected_peers.lock().unwrap().insert(their_node_id.clone()));
// Don't bother with `received_msg` for Init as its auto-generated and we don't want to
// bother re-generating the expected Init message in all tests.
Ok(())
features: ChannelFeatures::empty(),
chain_hash: genesis_block(network).header.block_hash(),
short_channel_id: short_chan_id,
- node_id_1: PublicKey::from_secret_key(&secp_ctx, &node_1_privkey),
- node_id_2: PublicKey::from_secret_key(&secp_ctx, &node_2_privkey),
- bitcoin_key_1: PublicKey::from_secret_key(&secp_ctx, &node_1_btckey),
- bitcoin_key_2: PublicKey::from_secret_key(&secp_ctx, &node_2_btckey),
+ node_id_1: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_1_privkey)),
+ node_id_2: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_2_privkey)),
+ bitcoin_key_1: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_1_btckey)),
+ bitcoin_key_2: NodeId::from_pubkey(&PublicKey::from_secret_key(&secp_ctx, &node_2_btckey)),
excess_data: Vec::new(),
};
Some((chan_ann, Some(chan_upd_1), Some(chan_upd_2)))
}
- fn get_next_node_announcement(&self, _starting_point: Option<&PublicKey>) -> Option<msgs::NodeAnnouncement> {
+ fn get_next_node_announcement(&self, _starting_point: Option<&NodeId>) -> Option<msgs::NodeAnnouncement> {
None
}
- fn peer_connected(&self, their_node_id: &PublicKey, init_msg: &msgs::Init) -> Result<(), ()> {
+ fn peer_connected(&self, their_node_id: &PublicKey, init_msg: &msgs::Init, _inbound: bool) -> Result<(), ()> {
if !init_msg.features.supports_gossip_queries() {
return Ok(());
}
features.set_gossip_queries_optional();
features
}
+
+ fn processing_queue_high(&self) -> bool { false }
}
impl events::MessageSendEventsProvider for TestRoutingMessageHandler {
pub struct TestChainSource {
pub genesis_hash: BlockHash,
- pub utxo_ret: Mutex<Result<TxOut, chain::AccessError>>,
+ pub utxo_ret: Mutex<UtxoResult>,
+ pub get_utxo_call_count: AtomicUsize,
pub watched_txn: Mutex<HashSet<(Txid, Script)>>,
pub watched_outputs: Mutex<HashSet<(OutPoint, Script)>>,
}
let script_pubkey = Builder::new().push_opcode(opcodes::OP_TRUE).into_script();
Self {
genesis_hash: genesis_block(network).block_hash(),
- utxo_ret: Mutex::new(Ok(TxOut { value: u64::max_value(), script_pubkey })),
+ utxo_ret: Mutex::new(UtxoResult::Sync(Ok(TxOut { value: u64::max_value(), script_pubkey }))),
+ get_utxo_call_count: AtomicUsize::new(0),
watched_txn: Mutex::new(HashSet::new()),
watched_outputs: Mutex::new(HashSet::new()),
}
}
}
-impl chain::Access for TestChainSource {
- fn get_utxo(&self, genesis_hash: &BlockHash, _short_channel_id: u64) -> Result<TxOut, chain::AccessError> {
+impl UtxoLookup for TestChainSource {
+ fn get_utxo(&self, genesis_hash: &BlockHash, _short_channel_id: u64) -> UtxoResult {
+ self.get_utxo_call_count.fetch_add(1, Ordering::Relaxed);
if self.genesis_hash != *genesis_hash {
- return Err(chain::AccessError::UnknownChain);
+ return UtxoResult::Sync(Err(UtxoLookupError::UnknownChain));
}
self.utxo_ret.lock().unwrap().clone()
}
}
-/// A scorer useful in testing, when the passage of time isn't a concern.
-pub type TestScorer = FixedPenaltyScorer;
+pub struct TestScorer {
+ /// Stores a tuple of (scid, ChannelUsage)
+ scorer_expectations: RefCell<Option<VecDeque<(u64, ChannelUsage)>>>,
+}
+
+impl TestScorer {
+ pub fn new() -> Self {
+ Self {
+ scorer_expectations: RefCell::new(None),
+ }
+ }
+
+ pub fn expect_usage(&self, scid: u64, expectation: ChannelUsage) {
+ self.scorer_expectations.borrow_mut().get_or_insert_with(|| VecDeque::new()).push_back((scid, expectation));
+ }
+}
+
+#[cfg(c_bindings)]
+impl crate::util::ser::Writeable for TestScorer {
+ fn write<W: crate::util::ser::Writer>(&self, _: &mut W) -> Result<(), crate::io::Error> { unreachable!(); }
+}
+
+impl Score for TestScorer {
+ fn channel_penalty_msat(
+ &self, short_channel_id: u64, _source: &NodeId, _target: &NodeId, usage: ChannelUsage
+ ) -> u64 {
+ if let Some(scorer_expectations) = self.scorer_expectations.borrow_mut().as_mut() {
+ match scorer_expectations.pop_front() {
+ Some((scid, expectation)) => {
+ assert_eq!(expectation, usage);
+ assert_eq!(scid, short_channel_id);
+ },
+ None => {},
+ }
+ }
+ 0
+ }
+
+ fn payment_path_failed(&mut self, _actual_path: &[&RouteHop], _actual_short_channel_id: u64) {}
+
+ fn payment_path_successful(&mut self, _actual_path: &[&RouteHop]) {}
+
+ fn probe_failed(&mut self, _actual_path: &[&RouteHop], _: u64) {}
+
+ fn probe_successful(&mut self, _actual_path: &[&RouteHop]) {}
+}
+
+impl Drop for TestScorer {
+ fn drop(&mut self) {
+ #[cfg(feature = "std")] {
+ if std::thread::panicking() {
+ return;
+ }
+ }
+
+ if let Some(scorer_expectations) = self.scorer_expectations.borrow().as_ref() {
+ if !scorer_expectations.is_empty() {
+ panic!("Unsatisfied scorer expectations: {:?}", scorer_expectations)
+ }
+ }
+ }
+}