Skip to content

Commit 4e5f74a

Browse files
authored
Merge pull request lightningdevkit#1567 from tnull/2022-06-send-probe
Add simple probing API
2 parents e254912 + eb8bce0 commit 4e5f74a

File tree

9 files changed

+453
-55
lines changed

9 files changed

+453
-55
lines changed

fuzz/src/chanmon_consistency.rs

+6
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,12 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out) {
850850
events::Event::PaymentClaimed { .. } => {},
851851
events::Event::PaymentPathSuccessful { .. } => {},
852852
events::Event::PaymentPathFailed { .. } => {},
853+
events::Event::ProbeSuccessful { .. } | events::Event::ProbeFailed { .. } => {
854+
// Even though we don't explicitly send probes, because probes are
855+
// detected based on hashing the payment hash+preimage, its rather
856+
// trivial for the fuzzer to build payments that accidentally end up
857+
// looking like probes.
858+
},
853859
events::Event::PaymentForwarded { .. } if $node == 1 => {},
854860
events::Event::PendingHTLCsForwardable { .. } => {
855861
nodes[$node].process_pending_htlc_forwards();

fuzz/src/full_stack.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
393393
// Adding new calls to `KeysInterface::get_secure_random_bytes` during startup can change all the
394394
// keys subsequently generated in this test. Rather than regenerating all the messages manually,
395395
// it's easier to just increment the counter here so the keys don't change.
396-
keys_manager.counter.fetch_sub(1, Ordering::AcqRel);
396+
keys_manager.counter.fetch_sub(2, Ordering::AcqRel);
397397
let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret(Recipient::Node).unwrap());
398398
let network_graph = Arc::new(NetworkGraph::new(genesis_block(network).block_hash(), Arc::clone(&logger)));
399399
let gossip_sync = Arc::new(P2PGossipSync::new(Arc::clone(&network_graph), None, Arc::clone(&logger)));

lightning-invoice/src/payment.rs

+80-12
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@
9494
//! # ) -> u64 { 0 }
9595
//! # fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
9696
//! # fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
97+
//! # fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
98+
//! # fn probe_successful(&mut self, _path: &[&RouteHop]) {}
9799
//! # }
98100
//! #
99101
//! # struct FakeLogger {}
@@ -584,6 +586,18 @@ where
584586
.map_or(1, |attempts| attempts.count + 1);
585587
log_trace!(self.logger, "Payment {} succeeded (attempts: {})", log_bytes!(payment_hash.0), attempts);
586588
},
589+
Event::ProbeSuccessful { payment_hash, path, .. } => {
590+
log_trace!(self.logger, "Probe payment {} of {}msat was successful", log_bytes!(payment_hash.0), path.last().unwrap().fee_msat);
591+
let path = path.iter().collect::<Vec<_>>();
592+
self.scorer.lock().probe_successful(&path);
593+
},
594+
Event::ProbeFailed { payment_hash, path, short_channel_id, .. } => {
595+
if let Some(short_channel_id) = short_channel_id {
596+
log_trace!(self.logger, "Probe payment {} of {}msat failed at channel {}", log_bytes!(payment_hash.0), path.last().unwrap().fee_msat, *short_channel_id);
597+
let path = path.iter().collect::<Vec<_>>();
598+
self.scorer.lock().probe_failed(&path, *short_channel_id);
599+
}
600+
},
587601
_ => {},
588602
}
589603

@@ -1296,7 +1310,7 @@ mod tests {
12961310
.expect_send(Amount::ForInvoice(final_value_msat))
12971311
.expect_send(Amount::OnRetry(final_value_msat / 2));
12981312
let router = TestRouter {};
1299-
let scorer = RefCell::new(TestScorer::new().expect(PaymentPath::Failure {
1313+
let scorer = RefCell::new(TestScorer::new().expect(TestResult::PaymentFailure {
13001314
path: path.clone(), short_channel_id: path[0].short_channel_id,
13011315
}));
13021316
let logger = TestLogger::new();
@@ -1332,8 +1346,8 @@ mod tests {
13321346
let payer = TestPayer::new().expect_send(Amount::ForInvoice(final_value_msat));
13331347
let router = TestRouter {};
13341348
let scorer = RefCell::new(TestScorer::new()
1335-
.expect(PaymentPath::Success { path: route.paths[0].clone() })
1336-
.expect(PaymentPath::Success { path: route.paths[1].clone() })
1349+
.expect(TestResult::PaymentSuccess { path: route.paths[0].clone() })
1350+
.expect(TestResult::PaymentSuccess { path: route.paths[1].clone() })
13371351
);
13381352
let logger = TestLogger::new();
13391353
let invoice_payer =
@@ -1416,13 +1430,15 @@ mod tests {
14161430
}
14171431

14181432
struct TestScorer {
1419-
expectations: Option<VecDeque<PaymentPath>>,
1433+
expectations: Option<VecDeque<TestResult>>,
14201434
}
14211435

14221436
#[derive(Debug)]
1423-
enum PaymentPath {
1424-
Failure { path: Vec<RouteHop>, short_channel_id: u64 },
1425-
Success { path: Vec<RouteHop> },
1437+
enum TestResult {
1438+
PaymentFailure { path: Vec<RouteHop>, short_channel_id: u64 },
1439+
PaymentSuccess { path: Vec<RouteHop> },
1440+
ProbeFailure { path: Vec<RouteHop>, short_channel_id: u64 },
1441+
ProbeSuccess { path: Vec<RouteHop> },
14261442
}
14271443

14281444
impl TestScorer {
@@ -1432,7 +1448,7 @@ mod tests {
14321448
}
14331449
}
14341450

1435-
fn expect(mut self, expectation: PaymentPath) -> Self {
1451+
fn expect(mut self, expectation: TestResult) -> Self {
14361452
self.expectations.get_or_insert_with(|| VecDeque::new()).push_back(expectation);
14371453
self
14381454
}
@@ -1451,13 +1467,19 @@ mod tests {
14511467
fn payment_path_failed(&mut self, actual_path: &[&RouteHop], actual_short_channel_id: u64) {
14521468
if let Some(expectations) = &mut self.expectations {
14531469
match expectations.pop_front() {
1454-
Some(PaymentPath::Failure { path, short_channel_id }) => {
1470+
Some(TestResult::PaymentFailure { path, short_channel_id }) => {
14551471
assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
14561472
assert_eq!(actual_short_channel_id, short_channel_id);
14571473
},
1458-
Some(PaymentPath::Success { path }) => {
1474+
Some(TestResult::PaymentSuccess { path }) => {
14591475
panic!("Unexpected successful payment path: {:?}", path)
14601476
},
1477+
Some(TestResult::ProbeFailure { path, .. }) => {
1478+
panic!("Unexpected failed payment probe: {:?}", path)
1479+
},
1480+
Some(TestResult::ProbeSuccess { path }) => {
1481+
panic!("Unexpected successful payment probe: {:?}", path)
1482+
},
14611483
None => panic!("Unexpected payment_path_failed call: {:?}", actual_path),
14621484
}
14631485
}
@@ -1466,10 +1488,56 @@ mod tests {
14661488
fn payment_path_successful(&mut self, actual_path: &[&RouteHop]) {
14671489
if let Some(expectations) = &mut self.expectations {
14681490
match expectations.pop_front() {
1469-
Some(PaymentPath::Failure { path, .. }) => {
1491+
Some(TestResult::PaymentFailure { path, .. }) => {
14701492
panic!("Unexpected payment path failure: {:?}", path)
14711493
},
1472-
Some(PaymentPath::Success { path }) => {
1494+
Some(TestResult::PaymentSuccess { path }) => {
1495+
assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
1496+
},
1497+
Some(TestResult::ProbeFailure { path, .. }) => {
1498+
panic!("Unexpected failed payment probe: {:?}", path)
1499+
},
1500+
Some(TestResult::ProbeSuccess { path }) => {
1501+
panic!("Unexpected successful payment probe: {:?}", path)
1502+
},
1503+
None => panic!("Unexpected payment_path_successful call: {:?}", actual_path),
1504+
}
1505+
}
1506+
}
1507+
1508+
fn probe_failed(&mut self, actual_path: &[&RouteHop], actual_short_channel_id: u64) {
1509+
if let Some(expectations) = &mut self.expectations {
1510+
match expectations.pop_front() {
1511+
Some(TestResult::PaymentFailure { path, .. }) => {
1512+
panic!("Unexpected failed payment path: {:?}", path)
1513+
},
1514+
Some(TestResult::PaymentSuccess { path }) => {
1515+
panic!("Unexpected successful payment path: {:?}", path)
1516+
},
1517+
Some(TestResult::ProbeFailure { path, short_channel_id }) => {
1518+
assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
1519+
assert_eq!(actual_short_channel_id, short_channel_id);
1520+
},
1521+
Some(TestResult::ProbeSuccess { path }) => {
1522+
panic!("Unexpected successful payment probe: {:?}", path)
1523+
},
1524+
None => panic!("Unexpected payment_path_failed call: {:?}", actual_path),
1525+
}
1526+
}
1527+
}
1528+
fn probe_successful(&mut self, actual_path: &[&RouteHop]) {
1529+
if let Some(expectations) = &mut self.expectations {
1530+
match expectations.pop_front() {
1531+
Some(TestResult::PaymentFailure { path, .. }) => {
1532+
panic!("Unexpected payment path failure: {:?}", path)
1533+
},
1534+
Some(TestResult::PaymentSuccess { path }) => {
1535+
panic!("Unexpected successful payment path: {:?}", path)
1536+
},
1537+
Some(TestResult::ProbeFailure { path, .. }) => {
1538+
panic!("Unexpected failed payment probe: {:?}", path)
1539+
},
1540+
Some(TestResult::ProbeSuccess { path }) => {
14731541
assert_eq!(actual_path, &path.iter().collect::<Vec<_>>()[..]);
14741542
},
14751543
None => panic!("Unexpected payment_path_successful call: {:?}", actual_path),

lightning/src/ln/channelmanager.rs

+87-16
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,11 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
740740
/// [fake scids]: crate::util::scid_utils::fake_scid
741741
fake_scid_rand_bytes: [u8; 32],
742742

743+
/// When we send payment probes, we generate the [`PaymentHash`] based on this cookie secret
744+
/// and a random [`PaymentId`]. This allows us to discern probes from real payments, without
745+
/// keeping additional state.
746+
probing_cookie_secret: [u8; 32],
747+
743748
/// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
744749
/// value increases strictly since we don't assume access to a time source.
745750
last_node_announcement_serial: AtomicUsize,
@@ -1589,6 +1594,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
15891594
inbound_payment_key: expanded_inbound_key,
15901595
fake_scid_rand_bytes: keys_manager.get_secure_random_bytes(),
15911596

1597+
probing_cookie_secret: keys_manager.get_secure_random_bytes(),
1598+
15921599
last_node_announcement_serial: AtomicUsize::new(0),
15931600
highest_seen_timestamp: AtomicUsize::new(0),
15941601

@@ -2731,6 +2738,43 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
27312738
}
27322739
}
27332740

2741+
/// Send a payment that is probing the given route for liquidity. We calculate the
2742+
/// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
2743+
/// us to easily discern them from real payments.
2744+
pub fn send_probe(&self, hops: Vec<RouteHop>) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> {
2745+
let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes());
2746+
2747+
let payment_hash = self.probing_cookie_from_id(&payment_id);
2748+
2749+
if hops.len() < 2 {
2750+
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
2751+
err: "No need probing a path with less than two hops".to_string()
2752+
}))
2753+
}
2754+
2755+
let route = Route { paths: vec![hops], payment_params: None };
2756+
2757+
match self.send_payment_internal(&route, payment_hash, &None, None, Some(payment_id), None) {
2758+
Ok(payment_id) => Ok((payment_hash, payment_id)),
2759+
Err(e) => Err(e)
2760+
}
2761+
}
2762+
2763+
/// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
2764+
/// payment probe.
2765+
pub(crate) fn payment_is_probe(&self, payment_hash: &PaymentHash, payment_id: &PaymentId) -> bool {
2766+
let target_payment_hash = self.probing_cookie_from_id(payment_id);
2767+
target_payment_hash == *payment_hash
2768+
}
2769+
2770+
/// Returns the 'probing cookie' for the given [`PaymentId`].
2771+
fn probing_cookie_from_id(&self, payment_id: &PaymentId) -> PaymentHash {
2772+
let mut preimage = [0u8; 64];
2773+
preimage[..32].copy_from_slice(&self.probing_cookie_secret);
2774+
preimage[32..].copy_from_slice(&payment_id.0);
2775+
PaymentHash(Sha256::hash(&preimage).into_inner())
2776+
}
2777+
27342778
/// Handles the generation of a funding transaction, optionally (for tests) with a function
27352779
/// which checks the correctness of the funding transaction given the associated channel.
27362780
fn funding_transaction_generated_intern<FundingOutput: Fn(&Channel<Signer>, &Transaction) -> Result<OutPoint, APIError>>(
@@ -3839,22 +3883,40 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
38393883
let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
38403884
#[cfg(not(test))]
38413885
let (network_update, short_channel_id, payment_retryable, _, _) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
3842-
// TODO: If we decided to blame ourselves (or one of our channels) in
3843-
// process_onion_failure we should close that channel as it implies our
3844-
// next-hop is needlessly blaming us!
3845-
events::Event::PaymentPathFailed {
3846-
payment_id: Some(payment_id),
3847-
payment_hash: payment_hash.clone(),
3848-
rejected_by_dest: !payment_retryable,
3849-
network_update,
3850-
all_paths_failed,
3851-
path: path.clone(),
3852-
short_channel_id,
3853-
retry,
3854-
#[cfg(test)]
3855-
error_code: onion_error_code,
3856-
#[cfg(test)]
3857-
error_data: onion_error_data
3886+
3887+
if self.payment_is_probe(payment_hash, &payment_id) {
3888+
if !payment_retryable {
3889+
events::Event::ProbeSuccessful {
3890+
payment_id,
3891+
payment_hash: payment_hash.clone(),
3892+
path: path.clone(),
3893+
}
3894+
} else {
3895+
events::Event::ProbeFailed {
3896+
payment_id: payment_id,
3897+
payment_hash: payment_hash.clone(),
3898+
path: path.clone(),
3899+
short_channel_id,
3900+
}
3901+
}
3902+
} else {
3903+
// TODO: If we decided to blame ourselves (or one of our channels) in
3904+
// process_onion_failure we should close that channel as it implies our
3905+
// next-hop is needlessly blaming us!
3906+
events::Event::PaymentPathFailed {
3907+
payment_id: Some(payment_id),
3908+
payment_hash: payment_hash.clone(),
3909+
rejected_by_dest: !payment_retryable,
3910+
network_update,
3911+
all_paths_failed,
3912+
path: path.clone(),
3913+
short_channel_id,
3914+
retry,
3915+
#[cfg(test)]
3916+
error_code: onion_error_code,
3917+
#[cfg(test)]
3918+
error_data: onion_error_data
3919+
}
38583920
}
38593921
},
38603922
&HTLCFailReason::Reason {
@@ -6631,6 +6693,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
66316693
(5, self.our_network_pubkey, required),
66326694
(7, self.fake_scid_rand_bytes, required),
66336695
(9, htlc_purposes, vec_type),
6696+
(11, self.probing_cookie_secret, required),
66346697
});
66356698

66366699
Ok(())
@@ -6927,18 +6990,24 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
69276990
let mut pending_outbound_payments = None;
69286991
let mut received_network_pubkey: Option<PublicKey> = None;
69296992
let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
6993+
let mut probing_cookie_secret: Option<[u8; 32]> = None;
69306994
let mut claimable_htlc_purposes = None;
69316995
read_tlv_fields!(reader, {
69326996
(1, pending_outbound_payments_no_retry, option),
69336997
(3, pending_outbound_payments, option),
69346998
(5, received_network_pubkey, option),
69356999
(7, fake_scid_rand_bytes, option),
69367000
(9, claimable_htlc_purposes, vec_type),
7001+
(11, probing_cookie_secret, option),
69377002
});
69387003
if fake_scid_rand_bytes.is_none() {
69397004
fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes());
69407005
}
69417006

7007+
if probing_cookie_secret.is_none() {
7008+
probing_cookie_secret = Some(args.keys_manager.get_secure_random_bytes());
7009+
}
7010+
69427011
if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() {
69437012
pending_outbound_payments = Some(pending_outbound_payments_compat);
69447013
} else if pending_outbound_payments.is_none() {
@@ -7144,6 +7213,8 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
71447213
outbound_scid_aliases: Mutex::new(outbound_scid_aliases),
71457214
fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),
71467215

7216+
probing_cookie_secret: probing_cookie_secret.unwrap(),
7217+
71477218
our_network_key,
71487219
our_network_pubkey,
71497220
secp_ctx,

lightning/src/ln/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN;
8080
/// payment_hash type, use to cross-lock hop
8181
/// (C-not exported) as we just use [u8; 32] directly
8282
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
83-
pub struct PaymentHash(pub [u8;32]);
83+
pub struct PaymentHash(pub [u8; 32]);
8484
/// payment_preimage type, use to route payment between hop
8585
/// (C-not exported) as we just use [u8; 32] directly
8686
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
87-
pub struct PaymentPreimage(pub [u8;32]);
87+
pub struct PaymentPreimage(pub [u8; 32]);
8888
/// payment_secret type, use to authenticate sender to the receiver and tie MPP HTLCs together
8989
/// (C-not exported) as we just use [u8; 32] directly
9090
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
91-
pub struct PaymentSecret(pub [u8;32]);
91+
pub struct PaymentSecret(pub [u8; 32]);
9292

9393
use prelude::*;
9494
use bitcoin::bech32;

0 commit comments

Comments
 (0)