#[cfg(any(test, feature = "std"))]
extern crate core;
+#[cfg(not(feature = "std"))]
+extern crate alloc;
+
#[macro_use] extern crate lightning;
extern crate lightning_rapid_gossip_sync;
use lightning::ln::peer_handler::{CustomMessageHandler, PeerManager, SocketDescriptor};
use lightning::routing::gossip::{NetworkGraph, P2PGossipSync};
use lightning::routing::router::Router;
-use lightning::routing::scoring::WriteableScore;
+use lightning::routing::scoring::{Score, WriteableScore};
use lightning::util::events::{Event, EventHandler, EventsProvider};
use lightning::util::logger::Logger;
use lightning::util::persist::Persister;
#[cfg(feature = "futures")]
use futures_util::{select_biased, future::FutureExt, task};
+#[cfg(not(feature = "std"))]
+use alloc::vec::Vec;
/// `BackgroundProcessor` takes care of tasks that (1) need to happen periodically to keep
/// Rust-Lightning running properly, and (2) either can or should be run in the background. Its
}
}
+fn update_scorer<'a, S: 'static + Deref<Target = SC> + Send + Sync, SC: 'a + WriteableScore<'a>>(
+ scorer: &'a S, event: &Event
+) {
+ let mut score = scorer.lock();
+ match event {
+ Event::PaymentPathFailed { ref path, short_channel_id: Some(scid), .. } => {
+ let path = path.iter().collect::<Vec<_>>();
+ score.payment_path_failed(&path, *scid);
+ },
+ Event::PaymentPathFailed { ref path, payment_failed_permanently: true, .. } => {
+ // Reached if the destination explicitly failed it back. We treat this as a successful probe
+ // because the payment made it all the way to the destination with sufficient liquidity.
+ let path = path.iter().collect::<Vec<_>>();
+ score.probe_successful(&path);
+ },
+ Event::PaymentPathSuccessful { path, .. } => {
+ let path = path.iter().collect::<Vec<_>>();
+ score.payment_path_successful(&path);
+ },
+ Event::ProbeSuccessful { path, .. } => {
+ let path = path.iter().collect::<Vec<_>>();
+ score.probe_successful(&path);
+ },
+ Event::ProbeFailed { path, short_channel_id: Some(scid), .. } => {
+ let path = path.iter().collect::<Vec<_>>();
+ score.probe_failed(&path, *scid);
+ },
+ _ => {},
+ }
+}
+
macro_rules! define_run_body {
($persister: ident, $chain_monitor: ident, $process_chain_monitor_events: expr,
$channel_manager: ident, $process_channel_manager_events: expr,
PGS: 'static + Deref<Target = P2PGossipSync<G, CA, L>> + Send + Sync,
RGS: 'static + Deref<Target = RapidGossipSync<G, L>> + Send,
UMH: 'static + Deref + Send + Sync,
- PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH>> + Send + Sync,
+ PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH, NS>> + Send + Sync,
S: 'static + Deref<Target = SC> + Send + Sync,
- SC: WriteableScore<'a>,
+ SC: for<'b> WriteableScore<'b>,
SleepFuture: core::future::Future<Output = bool> + core::marker::Unpin,
Sleeper: Fn(Duration) -> SleepFuture
>(
let async_event_handler = |event| {
let network_graph = gossip_sync.network_graph();
let event_handler = &event_handler;
+ let scorer = &scorer;
async move {
if let Some(network_graph) = network_graph {
handle_network_graph_update(network_graph, &event)
}
+ if let Some(ref scorer) = scorer {
+ update_scorer(scorer, &event);
+ }
event_handler(event).await;
}
};
PGS: 'static + Deref<Target = P2PGossipSync<G, CA, L>> + Send + Sync,
RGS: 'static + Deref<Target = RapidGossipSync<G, L>> + Send,
UMH: 'static + Deref + Send + Sync,
- PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH>> + Send + Sync,
+ PM: 'static + Deref<Target = PeerManager<Descriptor, CMH, RMH, OMH, L, UMH, NS>> + Send + Sync,
S: 'static + Deref<Target = SC> + Send + Sync,
- SC: WriteableScore<'a>,
+ SC: for <'b> WriteableScore<'b>,
>(
persister: PS, event_handler: EH, chain_monitor: M, channel_manager: CM,
gossip_sync: GossipSync<PGS, RGS, G, CA, L>, peer_manager: PM, logger: L, scorer: Option<S>,
if let Some(network_graph) = network_graph {
handle_network_graph_update(network_graph, &event)
}
+ if let Some(ref scorer) = scorer {
+ update_scorer(scorer, &event);
+ }
event_handler.handle_event(event);
};
define_run_body!(persister, chain_monitor, chain_monitor.process_pending_events(&event_handler),
use bitcoin::blockdata::locktime::PackedLockTime;
use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::network::constants::Network;
+ use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1};
use lightning::chain::{BestBlock, Confirm, chainmonitor};
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
- use lightning::chain::keysinterface::{InMemorySigner, Recipient, EntropySource, KeysManager, NodeSigner};
+ use lightning::chain::keysinterface::{InMemorySigner, EntropySource, KeysManager};
use lightning::chain::transaction::OutPoint;
use lightning::get_event_msg;
- use lightning::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChainParameters, ChannelManager, SimpleArcChannelManager};
- use lightning::ln::features::ChannelFeatures;
+ use lightning::ln::PaymentHash;
+ use lightning::ln::channelmanager;
+ use lightning::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChainParameters, MIN_CLTV_EXPIRY_DELTA, PaymentId};
+ use lightning::ln::features::{ChannelFeatures, NodeFeatures};
use lightning::ln::msgs::{ChannelMessageHandler, Init};
use lightning::ln::peer_handler::{PeerManager, MessageHandler, SocketDescriptor, IgnoringMessageHandler};
- use lightning::routing::gossip::{NetworkGraph, P2PGossipSync};
- use lightning::routing::router::DefaultRouter;
- use lightning::routing::scoring::{ProbabilisticScoringParameters, ProbabilisticScorer};
+ use lightning::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync};
+ use lightning::routing::router::{DefaultRouter, RouteHop};
+ use lightning::routing::scoring::{ChannelUsage, Score};
use lightning::util::config::UserConfig;
use lightning::util::events::{Event, MessageSendEventsProvider, MessageSendEvent};
use lightning::util::ser::Writeable;
use lightning::util::persist::KVStorePersister;
use lightning_invoice::payment::{InvoicePayer, Retry};
use lightning_persister::FilesystemPersister;
+ use std::collections::VecDeque;
use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
fn disconnect_socket(&mut self) {}
}
+ type ChannelManager = channelmanager::ChannelManager<Arc<ChainMonitor>, Arc<test_utils::TestBroadcaster>, Arc<KeysManager>, Arc<KeysManager>, Arc<KeysManager>, Arc<test_utils::TestFeeEstimator>, Arc<DefaultRouter< Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>, Arc<Mutex<TestScorer>>>>, Arc<test_utils::TestLogger>>;
+
type ChainMonitor = chainmonitor::ChainMonitor<InMemorySigner, Arc<test_utils::TestChainSource>, Arc<test_utils::TestBroadcaster>, Arc<test_utils::TestFeeEstimator>, Arc<test_utils::TestLogger>, Arc<FilesystemPersister>>;
type PGS = Arc<P2PGossipSync<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestChainSource>, Arc<test_utils::TestLogger>>>;
type RGS = Arc<RapidGossipSync<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>>>;
struct Node {
- node: Arc<SimpleArcChannelManager<ChainMonitor, test_utils::TestBroadcaster, test_utils::TestFeeEstimator, test_utils::TestLogger>>,
+ node: Arc<ChannelManager>,
p2p_gossip_sync: PGS,
rapid_gossip_sync: RGS,
- peer_manager: Arc<PeerManager<TestDescriptor, Arc<test_utils::TestChannelMessageHandler>, Arc<test_utils::TestRoutingMessageHandler>, IgnoringMessageHandler, Arc<test_utils::TestLogger>, IgnoringMessageHandler>>,
+ peer_manager: Arc<PeerManager<TestDescriptor, Arc<test_utils::TestChannelMessageHandler>, Arc<test_utils::TestRoutingMessageHandler>, IgnoringMessageHandler, Arc<test_utils::TestLogger>, IgnoringMessageHandler, Arc<KeysManager>>>,
chain_monitor: Arc<ChainMonitor>,
persister: Arc<FilesystemPersister>,
tx_broadcaster: Arc<test_utils::TestBroadcaster>,
network_graph: Arc<NetworkGraph<Arc<test_utils::TestLogger>>>,
logger: Arc<test_utils::TestLogger>,
best_block: BestBlock,
- scorer: Arc<Mutex<ProbabilisticScorer<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>>>>,
+ scorer: Arc<Mutex<TestScorer>>,
}
impl Node {
}
}
+ struct TestScorer {
+ event_expectations: Option<VecDeque<TestResult>>,
+ }
+
+ #[derive(Debug)]
+ enum TestResult {
+ PaymentFailure { path: Vec<RouteHop>, short_channel_id: u64 },
+ PaymentSuccess { path: Vec<RouteHop> },
+ ProbeFailure { path: Vec<RouteHop> },
+ ProbeSuccess { path: Vec<RouteHop> },
+ }
+
+ impl TestScorer {
+ fn new() -> Self {
+ Self { event_expectations: None }
+ }
+
+ fn expect(&mut self, expectation: TestResult) {
+ self.event_expectations.get_or_insert_with(|| VecDeque::new()).push_back(expectation);
+ }
+ }
+
+ impl lightning::util::ser::Writeable for TestScorer {
+ fn write<W: lightning::util::ser::Writer>(&self, _: &mut W) -> Result<(), lightning::io::Error> { Ok(()) }
+ }
+
+ impl Score for TestScorer {
+ fn channel_penalty_msat(
+ &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage
+ ) -> u64 { unimplemented!(); }
+
+ fn payment_path_failed(&mut self, actual_path: &[&RouteHop], actual_short_channel_id: u64) {
+ if let Some(expectations) = &mut self.event_expectations {
+ match expectations.pop_front().unwrap() {
+ TestResult::PaymentFailure { path, short_channel_id } => {
+ assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+ assert_eq!(actual_short_channel_id, short_channel_id);
+ },
+ TestResult::PaymentSuccess { path } => {
+ panic!("Unexpected successful payment path: {:?}", path)
+ },
+ TestResult::ProbeFailure { path } => {
+ panic!("Unexpected probe failure: {:?}", path)
+ },
+ TestResult::ProbeSuccess { path } => {
+ panic!("Unexpected probe success: {:?}", path)
+ }
+ }
+ }
+ }
+
+ fn payment_path_successful(&mut self, actual_path: &[&RouteHop]) {
+ if let Some(expectations) = &mut self.event_expectations {
+ match expectations.pop_front().unwrap() {
+ TestResult::PaymentFailure { path, .. } => {
+ panic!("Unexpected payment path failure: {:?}", path)
+ },
+ TestResult::PaymentSuccess { path } => {
+ assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+ },
+ TestResult::ProbeFailure { path } => {
+ panic!("Unexpected probe failure: {:?}", path)
+ },
+ TestResult::ProbeSuccess { path } => {
+ panic!("Unexpected probe success: {:?}", path)
+ }
+ }
+ }
+ }
+
+ fn probe_failed(&mut self, actual_path: &[&RouteHop], _: u64) {
+ if let Some(expectations) = &mut self.event_expectations {
+ match expectations.pop_front().unwrap() {
+ TestResult::PaymentFailure { path, .. } => {
+ panic!("Unexpected payment path failure: {:?}", path)
+ },
+ TestResult::PaymentSuccess { path } => {
+ panic!("Unexpected payment path success: {:?}", path)
+ },
+ TestResult::ProbeFailure { path } => {
+ assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+ },
+ TestResult::ProbeSuccess { path } => {
+ panic!("Unexpected probe success: {:?}", path)
+ }
+ }
+ }
+ }
+ fn probe_successful(&mut self, actual_path: &[&RouteHop]) {
+ if let Some(expectations) = &mut self.event_expectations {
+ match expectations.pop_front().unwrap() {
+ TestResult::PaymentFailure { path, .. } => {
+ panic!("Unexpected payment path failure: {:?}", path)
+ },
+ TestResult::PaymentSuccess { path } => {
+ panic!("Unexpected payment path success: {:?}", path)
+ },
+ TestResult::ProbeFailure { path } => {
+ panic!("Unexpected probe failure: {:?}", path)
+ },
+ TestResult::ProbeSuccess { path } => {
+ assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
+ }
+ }
+ }
+ }
+ }
+
+ impl Drop for TestScorer {
+ fn drop(&mut self) {
+ if std::thread::panicking() {
+ return;
+ }
+
+ if let Some(event_expectations) = &self.event_expectations {
+ if !event_expectations.is_empty() {
+ panic!("Unsatisfied event expectations: {:?}", event_expectations);
+ }
+ }
+ }
+ }
+
fn get_full_filepath(filepath: String, filename: String) -> String {
let mut path = PathBuf::from(filepath);
path.push(filename);
let network = Network::Testnet;
let genesis_block = genesis_block(network);
let network_graph = Arc::new(NetworkGraph::new(genesis_block.header.block_hash(), logger.clone()));
- let params = ProbabilisticScoringParameters::default();
- let scorer = Arc::new(Mutex::new(ProbabilisticScorer::new(params, network_graph.clone(), logger.clone())));
+ let scorer = Arc::new(Mutex::new(TestScorer::new()));
let seed = [i as u8; 32];
let router = Arc::new(DefaultRouter::new(network_graph.clone(), logger.clone(), seed, scorer.clone()));
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
let p2p_gossip_sync = Arc::new(P2PGossipSync::new(network_graph.clone(), Some(chain_source.clone()), logger.clone()));
let rapid_gossip_sync = Arc::new(RapidGossipSync::new(network_graph.clone()));
let msg_handler = MessageHandler { chan_handler: Arc::new(test_utils::TestChannelMessageHandler::new()), route_handler: Arc::new(test_utils::TestRoutingMessageHandler::new()), onion_message_handler: IgnoringMessageHandler{}};
- let peer_manager = Arc::new(PeerManager::new(msg_handler, keys_manager.get_node_secret(Recipient::Node).unwrap(), 0, &seed, logger.clone(), IgnoringMessageHandler{}));
+ let peer_manager = Arc::new(PeerManager::new(msg_handler, 0, &seed, logger.clone(), IgnoringMessageHandler{}, keys_manager.clone()));
let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, persister, tx_broadcaster, network_graph, logger, best_block, scorer };
nodes.push(node);
}
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
assert!(bg_processor.stop().is_ok());
}
+
+ #[test]
+ fn test_payment_path_scoring() {
+ // Ensure that we update the scorer when relevant events are processed. In this case, we ensure
+ // that we update the scorer upon a payment path succeeding (note that the channel must be
+ // public or else we won't score it).
+ // Set up a background event handler for FundingGenerationReady events.
+ let (sender, receiver) = std::sync::mpsc::sync_channel(1);
+ let event_handler = move |event: Event| match event {
+ Event::PaymentPathFailed { .. } => sender.send(event).unwrap(),
+ Event::PaymentPathSuccessful { .. } => sender.send(event).unwrap(),
+ Event::ProbeSuccessful { .. } => sender.send(event).unwrap(),
+ Event::ProbeFailed { .. } => sender.send(event).unwrap(),
+ _ => panic!("Unexpected event: {:?}", event),
+ };
+
+ let nodes = create_nodes(1, "test_payment_path_scoring".to_string());
+ let data_dir = nodes[0].persister.get_data_dir();
+ let persister = Arc::new(Persister::new(data_dir.clone()));
+ let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].no_gossip_sync(), nodes[0].peer_manager.clone(), nodes[0].logger.clone(), Some(nodes[0].scorer.clone()));
+
+ let scored_scid = 4242;
+ let secp_ctx = Secp256k1::new();
+ let node_1_privkey = SecretKey::from_slice(&[42; 32]).unwrap();
+ let node_1_id = PublicKey::from_secret_key(&secp_ctx, &node_1_privkey);
+
+ let path = vec![RouteHop {
+ pubkey: node_1_id,
+ node_features: NodeFeatures::empty(),
+ short_channel_id: scored_scid,
+ channel_features: ChannelFeatures::empty(),
+ fee_msat: 0,
+ cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA as u32,
+ }];
+
+ nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
+ nodes[0].node.push_pending_event(Event::PaymentPathFailed {
+ payment_id: None,
+ payment_hash: PaymentHash([42; 32]),
+ payment_failed_permanently: false,
+ network_update: None,
+ all_paths_failed: true,
+ path: path.clone(),
+ short_channel_id: Some(scored_scid),
+ retry: None,
+ });
+ let event = receiver
+ .recv_timeout(Duration::from_secs(EVENT_DEADLINE))
+ .expect("PaymentPathFailed not handled within deadline");
+ match event {
+ Event::PaymentPathFailed { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+
+ // Ensure we'll score payments that were explicitly failed back by the destination as
+ // ProbeSuccess.
+ nodes[0].scorer.lock().unwrap().expect(TestResult::ProbeSuccess { path: path.clone() });
+ nodes[0].node.push_pending_event(Event::PaymentPathFailed {
+ payment_id: None,
+ payment_hash: PaymentHash([42; 32]),
+ payment_failed_permanently: true,
+ network_update: None,
+ all_paths_failed: true,
+ path: path.clone(),
+ short_channel_id: None,
+ retry: None,
+ });
+ let event = receiver
+ .recv_timeout(Duration::from_secs(EVENT_DEADLINE))
+ .expect("PaymentPathFailed not handled within deadline");
+ match event {
+ Event::PaymentPathFailed { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+
+ nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentSuccess { path: path.clone() });
+ nodes[0].node.push_pending_event(Event::PaymentPathSuccessful {
+ payment_id: PaymentId([42; 32]),
+ payment_hash: None,
+ path: path.clone(),
+ });
+ let event = receiver
+ .recv_timeout(Duration::from_secs(EVENT_DEADLINE))
+ .expect("PaymentPathSuccessful not handled within deadline");
+ match event {
+ Event::PaymentPathSuccessful { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+
+ nodes[0].scorer.lock().unwrap().expect(TestResult::ProbeSuccess { path: path.clone() });
+ nodes[0].node.push_pending_event(Event::ProbeSuccessful {
+ payment_id: PaymentId([42; 32]),
+ payment_hash: PaymentHash([42; 32]),
+ path: path.clone(),
+ });
+ let event = receiver
+ .recv_timeout(Duration::from_secs(EVENT_DEADLINE))
+ .expect("ProbeSuccessful not handled within deadline");
+ match event {
+ Event::ProbeSuccessful { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+
+ nodes[0].scorer.lock().unwrap().expect(TestResult::ProbeFailure { path: path.clone() });
+ nodes[0].node.push_pending_event(Event::ProbeFailed {
+ payment_id: PaymentId([42; 32]),
+ payment_hash: PaymentHash([42; 32]),
+ path: path.clone(),
+ short_channel_id: Some(scored_scid),
+ });
+ let event = receiver
+ .recv_timeout(Duration::from_secs(EVENT_DEADLINE))
+ .expect("ProbeFailure not handled within deadline");
+ match event {
+ Event::ProbeFailed { .. } => {},
+ _ => panic!("Unexpected event"),
+ }
+
+ assert!(bg_processor.stop().is_ok());
+ }
}