Skip to content

Commit 7fd91af

Browse files
committed
Throw error for RGS data that's more than two weeks old, fixing #1785
1 parent 137b77c commit 7fd91af

File tree

3 files changed

+151
-3
lines changed

3 files changed

+151
-3
lines changed

lightning-background-processor/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,7 @@ mod tests {
13101310
0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
13111311
0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
13121312
];
1313-
nodes[0].rapid_gossip_sync.update_network_graph(&initialization_input[..]).unwrap();
1313+
nodes[0].rapid_gossip_sync.update_network_graph_no_std(&initialization_input[..], Some(1642291930)).unwrap();
13141314

13151315
// this should have added two channels
13161316
assert_eq!(network_graph.read_only().channels().len(), 3);

lightning-rapid-gossip-sync/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,22 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
126126
/// Update network graph from binary data.
127127
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
128128
///
129-
/// `network_graph`: network graph to be updated
130-
///
131129
/// `update_data`: `&[u8]` binary stream that comprises the update data
132130
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
133131
let mut read_cursor = io::Cursor::new(update_data);
134132
self.update_network_graph_from_byte_stream(&mut read_cursor)
135133
}
136134

135+
/// Update network graph from binary data.
136+
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
137+
///
138+
/// `update_data`: `&[u8]` binary stream that comprises the update data
139+
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
140+
pub fn update_network_graph_no_std(&self, update_data: &[u8], current_time_unix: Option<u64>) -> Result<u32, GraphSyncError> {
141+
let mut read_cursor = io::Cursor::new(update_data);
142+
self.update_network_graph_from_byte_stream_no_std(&mut read_cursor, current_time_unix)
143+
}
144+
137145
/// Gets a reference to the underlying [`NetworkGraph`] which was provided in
138146
/// [`RapidGossipSync::new`].
139147
///

lightning-rapid-gossip-sync/src/processing.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ use lightning::io;
1616
use crate::error::GraphSyncError;
1717
use crate::RapidGossipSync;
1818

19+
#[cfg(all(feature = "std", not(test)))]
20+
use std::time::{SystemTime, UNIX_EPOCH};
21+
1922
#[cfg(not(feature = "std"))]
2023
use alloc::{vec::Vec, borrow::ToOwned};
2124

@@ -29,10 +32,30 @@ const GOSSIP_PREFIX: [u8; 4] = [76, 68, 75, 1];
2932
/// avoid malicious updates being able to trigger excessive memory allocation.
3033
const MAX_INITIAL_NODE_ID_VECTOR_CAPACITY: u32 = 50_000;
3134

35+
/// We remove disallow gossip data that's more than two weeks old, per BOLT 7's
36+
/// suggestion.
37+
const STALE_RGS_UPDATE_AGE_LIMIT_SECS: u64 = 60 * 60 * 24 * 14;
38+
3239
impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
3340
pub(crate) fn update_network_graph_from_byte_stream<R: io::Read>(
41+
&self,
42+
read_cursor: &mut R,
43+
) -> Result<u32, GraphSyncError> {
44+
#[allow(unused_mut)]
45+
let mut current_time_unix = None;
46+
#[cfg(all(feature = "std", not(test)))]
47+
{
48+
// Note that many tests rely on being able to set arbitrarily old timestamps, thus we
49+
// disable this check during tests!
50+
current_time_unix = Some(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs());
51+
}
52+
self.update_network_graph_from_byte_stream_no_std(read_cursor, current_time_unix)
53+
}
54+
55+
pub(crate) fn update_network_graph_from_byte_stream_no_std<R: io::Read>(
3456
&self,
3557
mut read_cursor: &mut R,
58+
current_time_unix: Option<u64>
3659
) -> Result<u32, GraphSyncError> {
3760
let mut prefix = [0u8; 4];
3861
read_cursor.read_exact(&mut prefix)?;
@@ -43,6 +66,13 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
4366

4467
let chain_hash: BlockHash = Readable::read(read_cursor)?;
4568
let latest_seen_timestamp: u32 = Readable::read(read_cursor)?;
69+
70+
if let Some(time) = current_time_unix {
71+
if (latest_seen_timestamp as u64) < time.saturating_sub(STALE_RGS_UPDATE_AGE_LIMIT_SECS) {
72+
return Err(LightningError{err: "Rapid Gossip Sync data is more than two weeks old".to_owned(), action: ErrorAction::IgnoreError}.into());
73+
}
74+
}
75+
4676
// backdate the applied timestamp by a week
4777
let backdated_timestamp = latest_seen_timestamp.saturating_sub(24 * 3600 * 7);
4878

@@ -215,6 +245,7 @@ mod tests {
215245
use lightning::util::test_utils::TestLogger;
216246

217247
use crate::error::GraphSyncError;
248+
use crate::processing::STALE_RGS_UPDATE_AGE_LIMIT_SECS;
218249
use crate::RapidGossipSync;
219250

220251
#[test]
@@ -526,6 +557,115 @@ mod tests {
526557
assert!(after.contains("783241506229452801"));
527558
}
528559

560+
#[test]
561+
fn full_update_succeeds_with_edge_timestamp_age() {
562+
const LATEST_SEEN_TIMESTAMP: u64 = 1642291930;
563+
let valid_input = vec![
564+
76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
565+
79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
566+
0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
567+
187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
568+
157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
569+
88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
570+
204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
571+
181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
572+
110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
573+
76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
574+
226, 0, 6, 11, 0, 1, 2, 3, 0, 0, 0, 4, 0, 40, 0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 3, 232,
575+
0, 0, 0, 1, 0, 0, 0, 0, 29, 129, 25, 192, 255, 8, 153, 192, 0, 2, 27, 0, 0, 60, 0, 0,
576+
0, 0, 0, 0, 0, 1, 0, 0, 0, 100, 0, 0, 2, 224, 0, 0, 0, 0, 58, 85, 116, 216, 0, 29, 0,
577+
0, 0, 1, 0, 0, 0, 125, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1,
578+
0, 0, 1,
579+
];
580+
581+
let block_hash = genesis_block(Network::Bitcoin).block_hash();
582+
let logger = TestLogger::new();
583+
let network_graph = NetworkGraph::new(block_hash, &logger);
584+
585+
assert_eq!(network_graph.read_only().channels().len(), 0);
586+
587+
let earliest_failing_time = LATEST_SEEN_TIMESTAMP + STALE_RGS_UPDATE_AGE_LIMIT_SECS;
588+
589+
let rapid_sync = RapidGossipSync::new(&network_graph);
590+
let update_result = rapid_sync.update_network_graph_no_std(&valid_input[..], Some(earliest_failing_time));
591+
assert!(update_result.is_ok());
592+
assert_eq!(network_graph.read_only().channels().len(), 2);
593+
}
594+
595+
#[test]
596+
fn full_update_succeeds_with_oldest_possible_timestamp() {
597+
let valid_input = vec![
598+
76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
599+
79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
600+
0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
601+
187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
602+
157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
603+
88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
604+
204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
605+
181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
606+
110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
607+
76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
608+
226, 0, 6, 11, 0, 1, 2, 3, 0, 0, 0, 4, 0, 40, 0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 3, 232,
609+
0, 0, 0, 1, 0, 0, 0, 0, 29, 129, 25, 192, 255, 8, 153, 192, 0, 2, 27, 0, 0, 60, 0, 0,
610+
0, 0, 0, 0, 0, 1, 0, 0, 0, 100, 0, 0, 2, 224, 0, 0, 0, 0, 58, 85, 116, 216, 0, 29, 0,
611+
0, 0, 1, 0, 0, 0, 125, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1,
612+
0, 0, 1,
613+
];
614+
615+
let block_hash = genesis_block(Network::Bitcoin).block_hash();
616+
let logger = TestLogger::new();
617+
let network_graph = NetworkGraph::new(block_hash, &logger);
618+
619+
assert_eq!(network_graph.read_only().channels().len(), 0);
620+
621+
let rapid_sync = RapidGossipSync::new(&network_graph);
622+
let update_result = rapid_sync.update_network_graph_no_std(&valid_input[..], Some(0));
623+
assert!(update_result.is_ok());
624+
assert_eq!(network_graph.read_only().channels().len(), 2);
625+
}
626+
627+
#[test]
628+
fn full_update_fails_with_old_timestamp() {
629+
const LATEST_SEEN_TIMESTAMP: u64 = 1642291930;
630+
let valid_input = vec![
631+
76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
632+
79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
633+
0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
634+
187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
635+
157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
636+
88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
637+
204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
638+
181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
639+
110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
640+
76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
641+
226, 0, 6, 11, 0, 1, 2, 3, 0, 0, 0, 4, 0, 40, 0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 3, 232,
642+
0, 0, 0, 1, 0, 0, 0, 0, 29, 129, 25, 192, 255, 8, 153, 192, 0, 2, 27, 0, 0, 60, 0, 0,
643+
0, 0, 0, 0, 0, 1, 0, 0, 0, 100, 0, 0, 2, 224, 0, 0, 0, 0, 58, 85, 116, 216, 0, 29, 0,
644+
0, 0, 1, 0, 0, 0, 125, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1,
645+
0, 0, 1,
646+
];
647+
648+
let block_hash = genesis_block(Network::Bitcoin).block_hash();
649+
let logger = TestLogger::new();
650+
let network_graph = NetworkGraph::new(block_hash, &logger);
651+
652+
assert_eq!(network_graph.read_only().channels().len(), 0);
653+
654+
let earliest_failing_time = LATEST_SEEN_TIMESTAMP + STALE_RGS_UPDATE_AGE_LIMIT_SECS + 1;
655+
656+
let rapid_sync = RapidGossipSync::new(&network_graph);
657+
let update_result = rapid_sync.update_network_graph_no_std(&valid_input[..], Some(earliest_failing_time));
658+
assert!(update_result.is_err());
659+
if let Err(GraphSyncError::LightningError(lightning_error)) = update_result {
660+
assert_eq!(
661+
lightning_error.err,
662+
"Rapid Gossip Sync data is more than two weeks old"
663+
);
664+
} else {
665+
panic!("Unexpected update result: {:?}", update_result)
666+
}
667+
}
668+
529669
#[test]
530670
pub fn update_fails_with_unknown_version() {
531671
let unknown_version_input = vec![

0 commit comments

Comments
 (0)