Skip to content

Commit 4b043c9

Browse files
authored
Merge pull request #1921 from arik-so/2022-12-prohibit-old-rgs-updates
Throw error for RGS data that's more than two weeks old
2 parents 30b9d9f + 2f36c92 commit 4b043c9

File tree

3 files changed

+114
-22
lines changed

3 files changed

+114
-22
lines changed

lightning-background-processor/src/lib.rs

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

13161316
// this should have added two channels
13171317
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: 103 additions & 19 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 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,8 +245,28 @@ 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

251+
const VALID_RGS_BINARY: [u8; 300] = [
252+
76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
253+
79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
254+
0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
255+
187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
256+
157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
257+
88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
258+
204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
259+
181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
260+
110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
261+
76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
262+
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,
263+
0, 0, 0, 1, 0, 0, 0, 0, 29, 129, 25, 192, 255, 8, 153, 192, 0, 2, 27, 0, 0, 60, 0, 0,
264+
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,
265+
0, 0, 1, 0, 0, 0, 125, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1,
266+
0, 0, 1,
267+
];
268+
const VALID_BINARY_TIMESTAMP: u64 = 1642291930;
269+
220270
#[test]
221271
fn network_graph_fails_to_update_from_clipped_input() {
222272
let block_hash = genesis_block(Network::Bitcoin).block_hash();
@@ -478,32 +528,14 @@ mod tests {
478528

479529
#[test]
480530
fn full_update_succeeds() {
481-
let valid_input = vec![
482-
76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
483-
79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
484-
0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
485-
187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
486-
157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
487-
88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
488-
204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
489-
181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
490-
110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
491-
76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
492-
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,
493-
0, 0, 0, 1, 0, 0, 0, 0, 29, 129, 25, 192, 255, 8, 153, 192, 0, 2, 27, 0, 0, 60, 0, 0,
494-
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,
495-
0, 0, 1, 0, 0, 0, 125, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1,
496-
0, 0, 1,
497-
];
498-
499531
let block_hash = genesis_block(Network::Bitcoin).block_hash();
500532
let logger = TestLogger::new();
501533
let network_graph = NetworkGraph::new(block_hash, &logger);
502534

503535
assert_eq!(network_graph.read_only().channels().len(), 0);
504536

505537
let rapid_sync = RapidGossipSync::new(&network_graph);
506-
let update_result = rapid_sync.update_network_graph(&valid_input[..]);
538+
let update_result = rapid_sync.update_network_graph(&VALID_RGS_BINARY);
507539
if update_result.is_err() {
508540
panic!("Unexpected update result: {:?}", update_result)
509541
}
@@ -526,6 +558,58 @@ mod tests {
526558
assert!(after.contains("783241506229452801"));
527559
}
528560

561+
#[test]
562+
fn full_update_succeeds_at_the_beginning_of_the_unix_era() {
563+
let block_hash = genesis_block(Network::Bitcoin).block_hash();
564+
let logger = TestLogger::new();
565+
let network_graph = NetworkGraph::new(block_hash, &logger);
566+
567+
assert_eq!(network_graph.read_only().channels().len(), 0);
568+
569+
let rapid_sync = RapidGossipSync::new(&network_graph);
570+
// this is mostly for checking uint underflow issues before the fuzzer does
571+
let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(0));
572+
assert!(update_result.is_ok());
573+
assert_eq!(network_graph.read_only().channels().len(), 2);
574+
}
575+
576+
#[test]
577+
fn timestamp_edge_cases_are_handled_correctly() {
578+
// this is the timestamp encoded in the binary data of valid_input below
579+
let block_hash = genesis_block(Network::Bitcoin).block_hash();
580+
let logger = TestLogger::new();
581+
582+
let latest_succeeding_time = VALID_BINARY_TIMESTAMP + STALE_RGS_UPDATE_AGE_LIMIT_SECS;
583+
let earliest_failing_time = latest_succeeding_time + 1;
584+
585+
{
586+
let network_graph = NetworkGraph::new(block_hash, &logger);
587+
assert_eq!(network_graph.read_only().channels().len(), 0);
588+
589+
let rapid_sync = RapidGossipSync::new(&network_graph);
590+
let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(latest_succeeding_time));
591+
assert!(update_result.is_ok());
592+
assert_eq!(network_graph.read_only().channels().len(), 2);
593+
}
594+
595+
{
596+
let network_graph = NetworkGraph::new(block_hash, &logger);
597+
assert_eq!(network_graph.read_only().channels().len(), 0);
598+
599+
let rapid_sync = RapidGossipSync::new(&network_graph);
600+
let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(earliest_failing_time));
601+
assert!(update_result.is_err());
602+
if let Err(GraphSyncError::LightningError(lightning_error)) = update_result {
603+
assert_eq!(
604+
lightning_error.err,
605+
"Rapid Gossip Sync data is more than two weeks old"
606+
);
607+
} else {
608+
panic!("Unexpected update result: {:?}", update_result)
609+
}
610+
}
611+
}
612+
529613
#[test]
530614
pub fn update_fails_with_unknown_version() {
531615
let unknown_version_input = vec![

0 commit comments

Comments
 (0)