Skip to content

Commit 0216d7d

Browse files
authored
Merge pull request #3629 from joostjager/log-attribution-failures
Log cases where an onion failure cannot be attributed or interpreted
2 parents 5099dce + f38244c commit 0216d7d

File tree

1 file changed

+232
-45
lines changed

1 file changed

+232
-45
lines changed

lightning/src/ln/onion_utils.rs

+232-45
Original file line numberDiff line numberDiff line change
@@ -931,15 +931,11 @@ pub(crate) struct DecodedOnionFailure {
931931
pub(crate) onion_error_data: Option<Vec<u8>>,
932932
}
933933

934-
/// Note that we always decrypt `packet` in-place here even if the deserialization into
935-
/// [`msgs::DecodedOnionErrorPacket`] ultimately fails.
936-
fn decrypt_onion_error_packet(
937-
packet: &mut Vec<u8>, shared_secret: SharedSecret,
938-
) -> Result<msgs::DecodedOnionErrorPacket, msgs::DecodeError> {
934+
/// Decrypt the error packet in-place.
935+
fn decrypt_onion_error_packet(packet: &mut Vec<u8>, shared_secret: SharedSecret) {
939936
let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref());
940937
let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]);
941938
chacha.process_in_place(packet);
942-
msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(packet))
943939
}
944940

945941
/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
@@ -1021,9 +1017,11 @@ where
10211017
{
10221018
// Actually parse the onion error data in tests so we can check that blinded hops fail
10231019
// back correctly.
1024-
let err_packet =
1025-
decrypt_onion_error_packet(&mut encrypted_packet, shared_secret)
1026-
.unwrap();
1020+
decrypt_onion_error_packet(&mut encrypted_packet, shared_secret);
1021+
let err_packet = msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(
1022+
&encrypted_packet,
1023+
))
1024+
.unwrap();
10271025
error_code_ret = Some(u16::from_be_bytes(
10281026
err_packet.failuremsg.get(0..2).unwrap().try_into().unwrap(),
10291027
));
@@ -1044,22 +1042,44 @@ where
10441042
let amt_to_forward = htlc_msat - route_hop.fee_msat;
10451043
htlc_msat = amt_to_forward;
10461044

1047-
let err_packet = match decrypt_onion_error_packet(&mut encrypted_packet, shared_secret) {
1048-
Ok(p) => p,
1049-
Err(_) => return,
1050-
};
1045+
decrypt_onion_error_packet(&mut encrypted_packet, shared_secret);
1046+
10511047
let um = gen_um_from_shared_secret(shared_secret.as_ref());
10521048
let mut hmac = HmacEngine::<Sha256>::new(&um);
1053-
hmac.input(&err_packet.encode()[32..]);
1049+
hmac.input(&encrypted_packet[32..]);
10541050

1055-
if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &err_packet.hmac) {
1051+
if !fixed_time_eq(&Hmac::from_engine(hmac).to_byte_array(), &encrypted_packet[..32]) {
10561052
return;
10571053
}
1054+
1055+
let err_packet =
1056+
match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&encrypted_packet)) {
1057+
Ok(p) => p,
1058+
Err(_) => {
1059+
log_warn!(logger, "Unreadable failure from {}", route_hop.pubkey);
1060+
1061+
let network_update = Some(NetworkUpdate::NodeFailure {
1062+
node_id: route_hop.pubkey,
1063+
is_permanent: true,
1064+
});
1065+
let short_channel_id = Some(route_hop.short_channel_id);
1066+
res = Some(FailureLearnings {
1067+
network_update,
1068+
short_channel_id,
1069+
payment_failed_permanently: is_from_final_node,
1070+
failed_within_blinded_path: false,
1071+
});
1072+
return;
1073+
},
1074+
};
1075+
10581076
let error_code_slice = match err_packet.failuremsg.get(0..2) {
10591077
Some(s) => s,
10601078
None => {
10611079
// Useless packet that we can't use but it passed HMAC, so it definitely came from the peer
10621080
// in question
1081+
log_warn!(logger, "Missing error code in failure from {}", route_hop.pubkey);
1082+
10631083
let network_update = Some(NetworkUpdate::NodeFailure {
10641084
node_id: route_hop.pubkey,
10651085
is_permanent: true,
@@ -1219,6 +1239,12 @@ where
12191239
} else {
12201240
// only not set either packet unparseable or hmac does not match with any
12211241
// payment not retryable only when garbage is from the final node
1242+
log_warn!(
1243+
logger,
1244+
"Non-attributable failure encountered on route {}",
1245+
path.hops.iter().map(|h| h.pubkey.to_string()).collect::<Vec<_>>().join("->")
1246+
);
1247+
12221248
DecodedOnionFailure {
12231249
network_update: None,
12241250
short_channel_id: None,
@@ -1764,7 +1790,10 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
17641790

17651791
#[cfg(test)]
17661792
mod tests {
1793+
use std::sync::Arc;
1794+
17671795
use crate::io;
1796+
use crate::ln::channelmanager::PaymentId;
17681797
use crate::ln::msgs;
17691798
use crate::routing::router::{Path, PaymentParameters, Route, RouteHop};
17701799
use crate::types::features::{ChannelFeatures, NodeFeatures};
@@ -1773,6 +1802,7 @@ mod tests {
17731802

17741803
#[allow(unused_imports)]
17751804
use crate::prelude::*;
1805+
use crate::util::test_utils::TestLogger;
17761806

17771807
use bitcoin::hex::FromHex;
17781808
use bitcoin::secp256k1::Secp256k1;
@@ -1785,40 +1815,95 @@ mod tests {
17851815
SecretKey::from_slice(&<Vec<u8>>::from_hex(hex).unwrap()[..]).unwrap()
17861816
}
17871817

1818+
fn build_test_path() -> Path {
1819+
Path {
1820+
hops: vec![
1821+
RouteHop {
1822+
pubkey: PublicKey::from_slice(
1823+
&<Vec<u8>>::from_hex(
1824+
"02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619",
1825+
)
1826+
.unwrap()[..],
1827+
)
1828+
.unwrap(),
1829+
channel_features: ChannelFeatures::empty(),
1830+
node_features: NodeFeatures::empty(),
1831+
short_channel_id: 0,
1832+
fee_msat: 0,
1833+
cltv_expiry_delta: 0,
1834+
maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1835+
},
1836+
RouteHop {
1837+
pubkey: PublicKey::from_slice(
1838+
&<Vec<u8>>::from_hex(
1839+
"0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c",
1840+
)
1841+
.unwrap()[..],
1842+
)
1843+
.unwrap(),
1844+
channel_features: ChannelFeatures::empty(),
1845+
node_features: NodeFeatures::empty(),
1846+
short_channel_id: 1,
1847+
fee_msat: 0,
1848+
cltv_expiry_delta: 0,
1849+
maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1850+
},
1851+
RouteHop {
1852+
pubkey: PublicKey::from_slice(
1853+
&<Vec<u8>>::from_hex(
1854+
"027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007",
1855+
)
1856+
.unwrap()[..],
1857+
)
1858+
.unwrap(),
1859+
channel_features: ChannelFeatures::empty(),
1860+
node_features: NodeFeatures::empty(),
1861+
short_channel_id: 2,
1862+
fee_msat: 0,
1863+
cltv_expiry_delta: 0,
1864+
maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1865+
},
1866+
RouteHop {
1867+
pubkey: PublicKey::from_slice(
1868+
&<Vec<u8>>::from_hex(
1869+
"032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991",
1870+
)
1871+
.unwrap()[..],
1872+
)
1873+
.unwrap(),
1874+
channel_features: ChannelFeatures::empty(),
1875+
node_features: NodeFeatures::empty(),
1876+
short_channel_id: 3,
1877+
fee_msat: 0,
1878+
cltv_expiry_delta: 0,
1879+
maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1880+
},
1881+
RouteHop {
1882+
pubkey: PublicKey::from_slice(
1883+
&<Vec<u8>>::from_hex(
1884+
"02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145",
1885+
)
1886+
.unwrap()[..],
1887+
)
1888+
.unwrap(),
1889+
channel_features: ChannelFeatures::empty(),
1890+
node_features: NodeFeatures::empty(),
1891+
short_channel_id: 4,
1892+
fee_msat: 0,
1893+
cltv_expiry_delta: 0,
1894+
maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1895+
},
1896+
],
1897+
blinded_tail: None,
1898+
}
1899+
}
1900+
17881901
fn build_test_onion_keys() -> Vec<OnionKeys> {
17891902
// Keys from BOLT 4, used in both test vector tests
17901903
let secp_ctx = Secp256k1::new();
17911904

1792-
let route = Route {
1793-
paths: vec![Path { hops: vec![
1794-
RouteHop {
1795-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
1796-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1797-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1798-
},
1799-
RouteHop {
1800-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
1801-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1802-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1803-
},
1804-
RouteHop {
1805-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
1806-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1807-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1808-
},
1809-
RouteHop {
1810-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]).unwrap(),
1811-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1812-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1813-
},
1814-
RouteHop {
1815-
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()[..]).unwrap(),
1816-
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
1817-
short_channel_id: 0, fee_msat: 0, cltv_expiry_delta: 0, maybe_announced_channel: true, // We fill in the payloads manually instead of generating them from RouteHops.
1818-
},
1819-
], blinded_tail: None }],
1820-
route_params: None,
1821-
};
1905+
let path = build_test_path();
1906+
let route = Route { paths: vec![path], route_params: None };
18221907

18231908
let onion_keys =
18241909
super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key())
@@ -2078,6 +2163,108 @@ mod tests {
20782163
);
20792164
let hex = "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d";
20802165
assert_eq!(onion_packet_5.data, <Vec<u8>>::from_hex(hex).unwrap());
2166+
2167+
let logger: Arc<TestLogger> = Arc::new(TestLogger::new());
2168+
let ctx_full = Secp256k1::new();
2169+
let path = build_test_path();
2170+
let htlc_source = HTLCSource::OutboundRoute {
2171+
path,
2172+
session_priv: get_test_session_key(),
2173+
first_hop_htlc_msat: 0,
2174+
payment_id: PaymentId([1; 32]),
2175+
};
2176+
2177+
// Assert that the original failure can be retrieved and that all hmacs check out.
2178+
let decrypted_failure =
2179+
process_onion_failure(&ctx_full, &logger, &htlc_source, onion_packet_5.data);
2180+
2181+
assert_eq!(decrypted_failure.onion_error_code, Some(0x2002));
2182+
}
2183+
2184+
#[test]
2185+
fn test_non_attributable_failure_packet_onion() {
2186+
// Create a failure packet with bogus data.
2187+
let packet = vec![1u8; 292];
2188+
2189+
// In the current protocol, it is unfortunately not possible to identify the failure source.
2190+
let logger: TestLogger = TestLogger::new();
2191+
let decrypted_failure = test_failure_attribution(&logger, &packet);
2192+
assert_eq!(decrypted_failure.short_channel_id, None);
2193+
2194+
logger.assert_log_contains(
2195+
"lightning::ln::onion_utils",
2196+
"Non-attributable failure encountered",
2197+
1,
2198+
);
2199+
}
2200+
2201+
#[test]
2202+
fn test_unreadable_failure_packet_onion() {
2203+
// Create a failure packet with a valid hmac but unreadable failure message.
2204+
let onion_keys: Vec<OnionKeys> = build_test_onion_keys();
2205+
let shared_secret = onion_keys[0].shared_secret.as_ref();
2206+
let um = gen_um_from_shared_secret(&shared_secret);
2207+
2208+
// The failure message is a single 0 byte.
2209+
let mut packet = [0u8; 33];
2210+
2211+
let mut hmac = HmacEngine::<Sha256>::new(&um);
2212+
hmac.input(&packet[32..]);
2213+
let hmac = Hmac::from_engine(hmac).to_byte_array();
2214+
packet[..32].copy_from_slice(&hmac);
2215+
2216+
let packet = encrypt_failure_packet(shared_secret, &packet);
2217+
2218+
// For the unreadable failure, it is still expected that the failing channel can be identified.
2219+
let logger: TestLogger = TestLogger::new();
2220+
let decrypted_failure = test_failure_attribution(&logger, &packet.data);
2221+
assert_eq!(decrypted_failure.short_channel_id, Some(0));
2222+
2223+
logger.assert_log_contains("lightning::ln::onion_utils", "Unreadable failure", 1);
2224+
}
2225+
2226+
#[test]
2227+
fn test_missing_error_code() {
2228+
// Create a failure packet with a valid hmac and structure, but no error code.
2229+
let onion_keys: Vec<OnionKeys> = build_test_onion_keys();
2230+
let shared_secret = onion_keys[0].shared_secret.as_ref();
2231+
let um = gen_um_from_shared_secret(&shared_secret);
2232+
2233+
let failuremsg = vec![1];
2234+
let pad = Vec::new();
2235+
let mut packet = msgs::DecodedOnionErrorPacket { hmac: [0; 32], failuremsg, pad };
2236+
2237+
let mut hmac = HmacEngine::<Sha256>::new(&um);
2238+
hmac.input(&packet.encode()[32..]);
2239+
packet.hmac = Hmac::from_engine(hmac).to_byte_array();
2240+
2241+
let packet = encrypt_failure_packet(shared_secret, &packet.encode()[..]);
2242+
2243+
let logger = TestLogger::new();
2244+
let decrypted_failure = test_failure_attribution(&logger, &packet.data);
2245+
assert_eq!(decrypted_failure.short_channel_id, Some(0));
2246+
2247+
logger.assert_log_contains(
2248+
"lightning::ln::onion_utils",
2249+
"Missing error code in failure",
2250+
1,
2251+
);
2252+
}
2253+
2254+
fn test_failure_attribution(logger: &TestLogger, packet: &[u8]) -> DecodedOnionFailure {
2255+
let ctx_full = Secp256k1::new();
2256+
let path = build_test_path();
2257+
let htlc_source = HTLCSource::OutboundRoute {
2258+
path,
2259+
session_priv: get_test_session_key(),
2260+
first_hop_htlc_msat: 0,
2261+
payment_id: PaymentId([1; 32]),
2262+
};
2263+
2264+
let decrypted_failure =
2265+
process_onion_failure(&ctx_full, &logger, &htlc_source, packet.into());
2266+
2267+
decrypted_failure
20812268
}
20822269

20832270
struct RawOnionHopData {

0 commit comments

Comments
 (0)