Merge pull request #2954 from TheBlueMatt/2024-03-test-ci-beta-fail
[rust-lightning] / lightning-rapid-gossip-sync / src / processing.rs
index 3e001ec45c4be1aa7becbaa6da9f9158f3d217fa..b3fae0ffd8bea8e19822c5a2cf9bc19d3fe33939 100644 (file)
@@ -2,7 +2,7 @@ use core::cmp::max;
 use core::ops::Deref;
 use core::sync::atomic::Ordering;
 
-use bitcoin::BlockHash;
+use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::secp256k1::PublicKey;
 
 use lightning::ln::msgs::{
@@ -10,13 +10,16 @@ use lightning::ln::msgs::{
 };
 use lightning::routing::gossip::NetworkGraph;
 use lightning::util::logger::Logger;
+use lightning::{log_debug, log_warn, log_trace, log_given_level, log_gossip};
 use lightning::util::ser::{BigSize, Readable};
 use lightning::io;
 
-use crate::error::GraphSyncError;
-use crate::RapidGossipSync;
+use crate::{GraphSyncError, RapidGossipSync};
 
-#[cfg(not(feature = "std"))]
+#[cfg(all(feature = "std", not(test)))]
+use std::time::{SystemTime, UNIX_EPOCH};
+
+#[cfg(all(not(feature = "std"), not(test)))]
 use alloc::{vec::Vec, borrow::ToOwned};
 
 /// The purpose of this prefix is to identify the serialization format, should other rapid gossip
@@ -29,11 +32,33 @@ const GOSSIP_PREFIX: [u8; 4] = [76, 68, 75, 1];
 /// avoid malicious updates being able to trigger excessive memory allocation.
 const MAX_INITIAL_NODE_ID_VECTOR_CAPACITY: u32 = 50_000;
 
+/// We disallow gossip data that's more than two weeks old, per BOLT 7's
+/// suggestion.
+const STALE_RGS_UPDATE_AGE_LIMIT_SECS: u64 = 60 * 60 * 24 * 14;
+
 impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
+       #[cfg(feature = "std")]
        pub(crate) fn update_network_graph_from_byte_stream<R: io::Read>(
+               &self,
+               read_cursor: &mut R,
+       ) -> Result<u32, GraphSyncError> {
+               #[allow(unused_mut, unused_assignments)]
+               let mut current_time_unix = None;
+               #[cfg(not(test))]
+               {
+                       // Note that many tests rely on being able to set arbitrarily old timestamps, thus we
+                       // disable this check during tests!
+                       current_time_unix = Some(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs());
+               }
+               self.update_network_graph_from_byte_stream_no_std(read_cursor, current_time_unix)
+       }
+
+       pub(crate) fn update_network_graph_from_byte_stream_no_std<R: io::Read>(
                &self,
                mut read_cursor: &mut R,
+               current_time_unix: Option<u64>
        ) -> Result<u32, GraphSyncError> {
+               log_trace!(self.logger, "Processing RGS data...");
                let mut prefix = [0u8; 4];
                read_cursor.read_exact(&mut prefix)?;
 
@@ -41,8 +66,25 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
                        return Err(DecodeError::UnknownVersion.into());
                }
 
-               let chain_hash: BlockHash = Readable::read(read_cursor)?;
+               let chain_hash: ChainHash = Readable::read(read_cursor)?;
+               let ng_chain_hash = self.network_graph.get_chain_hash();
+               if chain_hash != ng_chain_hash {
+                       return Err(
+                               LightningError {
+                                       err: "Rapid Gossip Sync data's chain hash does not match the network graph's".to_owned(),
+                                       action: ErrorAction::IgnoreError,
+                               }.into()
+                       );
+               }
+
                let latest_seen_timestamp: u32 = Readable::read(read_cursor)?;
+
+               if let Some(time) = current_time_unix {
+                       if (latest_seen_timestamp as u64) < time.saturating_sub(STALE_RGS_UPDATE_AGE_LIMIT_SECS) {
+                               return Err(LightningError{err: "Rapid Gossip Sync data is more than two weeks old".to_owned(), action: ErrorAction::IgnoreError}.into());
+                       }
+               }
+
                // backdate the applied timestamp by a week
                let backdated_timestamp = latest_seen_timestamp.saturating_sub(24 * 3600 * 7);
 
@@ -79,6 +121,9 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
                        let node_id_1 = node_ids[node_id_1_index.0 as usize];
                        let node_id_2 = node_ids[node_id_2_index.0 as usize];
 
+                       log_gossip!(self.logger, "Adding channel {} from RGS announcement at {}",
+                               short_channel_id, latest_seen_timestamp);
+
                        let announcement_result = network_graph.add_channel_from_partial_announcement(
                                short_channel_id,
                                backdated_timestamp as u64,
@@ -90,6 +135,7 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
                                if let ErrorAction::IgnoreDuplicateGossip = lightning_error.action {
                                        // everything is fine, just a duplicate channel announcement
                                } else {
+                                       log_warn!(self.logger, "Failed to process channel announcement: {:?}", lightning_error);
                                        return Err(lightning_error.into());
                                }
                        }
@@ -98,6 +144,8 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
                previous_scid = 0; // updates start at a new scid
 
                let update_count: u32 = Readable::read(read_cursor)?;
+               log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
+                       latest_seen_timestamp, node_id_count, announcement_count, update_count);
                if update_count == 0 {
                        return Ok(latest_seen_timestamp);
                }
@@ -139,24 +187,19 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
                        if (channel_flags & 0b_1000_0000) != 0 {
                                // incremental update, field flags will indicate mutated values
                                let read_only_network_graph = network_graph.read_only();
-                               if let Some(channel) = read_only_network_graph
-                                       .channels()
-                                       .get(&short_channel_id) {
-
-                                       let directional_info = channel
-                                               .get_directional_info(channel_flags)
-                                               .ok_or(LightningError {
-                                                       err: "Couldn't find previous directional data for update".to_owned(),
-                                                       action: ErrorAction::IgnoreError,
-                                               })?;
-
+                               if let Some(directional_info) =
+                                       read_only_network_graph.channels().get(&short_channel_id)
+                                       .and_then(|channel| channel.get_directional_info(channel_flags))
+                               {
                                        synthetic_update.cltv_expiry_delta = directional_info.cltv_expiry_delta;
                                        synthetic_update.htlc_minimum_msat = directional_info.htlc_minimum_msat;
                                        synthetic_update.htlc_maximum_msat = directional_info.htlc_maximum_msat;
                                        synthetic_update.fee_base_msat = directional_info.fees.base_msat;
                                        synthetic_update.fee_proportional_millionths = directional_info.fees.proportional_millionths;
-
                                } else {
+                                       log_trace!(self.logger,
+                                               "Skipping application of channel update for chan {} with flags {} as original data is missing.",
+                                               short_channel_id, channel_flags);
                                        skip_update_for_unknown_channel = true;
                                }
                        };
@@ -190,38 +233,68 @@ impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L
                                continue;
                        }
 
+                       log_gossip!(self.logger, "Updating channel {} with flags {} from RGS announcement at {}",
+                               short_channel_id, channel_flags, latest_seen_timestamp);
                        match network_graph.update_channel_unsigned(&synthetic_update) {
                                Ok(_) => {},
                                Err(LightningError { action: ErrorAction::IgnoreDuplicateGossip, .. }) => {},
-                               Err(LightningError { action: ErrorAction::IgnoreAndLog(_), .. }) => {},
+                               Err(LightningError { action: ErrorAction::IgnoreAndLog(level), err }) => {
+                                       log_given_level!(self.logger, level, "Failed to apply channel update: {:?}", err);
+                               },
                                Err(LightningError { action: ErrorAction::IgnoreError, .. }) => {},
                                Err(e) => return Err(e.into()),
                        }
                }
 
                self.network_graph.set_last_rapid_gossip_sync_timestamp(latest_seen_timestamp);
+
+               if let Some(time) = current_time_unix {
+                       self.network_graph.remove_stale_channels_and_tracking_with_time(time)
+               }
+
                self.is_initial_sync_complete.store(true, Ordering::Release);
+               log_trace!(self.logger, "Done processing RGS data from {}", latest_seen_timestamp);
                Ok(latest_seen_timestamp)
        }
 }
 
 #[cfg(test)]
 mod tests {
-       use bitcoin::blockdata::constants::genesis_block;
        use bitcoin::Network;
 
+       #[cfg(feature = "std")]
        use lightning::ln::msgs::DecodeError;
+
        use lightning::routing::gossip::NetworkGraph;
        use lightning::util::test_utils::TestLogger;
 
-       use crate::error::GraphSyncError;
-       use crate::RapidGossipSync;
+       use crate::processing::STALE_RGS_UPDATE_AGE_LIMIT_SECS;
+       use crate::{GraphSyncError, RapidGossipSync};
+
+       const VALID_RGS_BINARY: [u8; 300] = [
+               76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
+               79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
+               0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
+               187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
+               157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
+               88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
+               204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
+               181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
+               110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
+               76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
+               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,
+               0, 0, 0, 1, 0, 0, 0, 0, 29, 129, 25, 192, 255, 8, 153, 192, 0, 2, 27, 0, 0, 60, 0, 0,
+               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,
+               0, 0, 1, 0, 0, 0, 125, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1,
+               0, 0, 1,
+       ];
+       const VALID_BINARY_TIMESTAMP: u64 = 1642291930;
 
        #[test]
+       #[cfg(feature = "std")]
        fn network_graph_fails_to_update_from_clipped_input() {
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
 
                let example_input = vec![
                        76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
@@ -239,7 +312,7 @@ mod tests {
                        0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1, 24, 0,
                        0, 3, 232, 0, 0, 0,
                ];
-               let rapid_sync = RapidGossipSync::new(&network_graph);
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
                let update_result = rapid_sync.update_network_graph(&example_input[..]);
                assert!(update_result.is_err());
                if let Err(GraphSyncError::DecodeError(DecodeError::ShortRead)) = update_result {
@@ -250,6 +323,7 @@ mod tests {
        }
 
        #[test]
+       #[cfg(feature = "std")]
        fn incremental_only_update_ignores_missing_channel() {
                let incremental_update_input = vec![
                        76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
@@ -259,18 +333,18 @@ mod tests {
                        68, 226, 0, 6, 11, 0, 1, 128,
                ];
 
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
 
                assert_eq!(network_graph.read_only().channels().len(), 0);
 
-               let rapid_sync = RapidGossipSync::new(&network_graph);
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
                let update_result = rapid_sync.update_network_graph(&incremental_update_input[..]);
                assert!(update_result.is_ok());
        }
 
        #[test]
+       #[cfg(feature = "std")]
        fn incremental_only_update_fails_without_prior_updates() {
                let announced_update_input = vec![
                        76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
@@ -288,26 +362,17 @@ mod tests {
                        2, 68, 226, 0, 6, 11, 0, 1, 128,
                ];
 
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
 
                assert_eq!(network_graph.read_only().channels().len(), 0);
 
-               let rapid_sync = RapidGossipSync::new(&network_graph);
-               let update_result = rapid_sync.update_network_graph(&announced_update_input[..]);
-               assert!(update_result.is_err());
-               if let Err(GraphSyncError::LightningError(lightning_error)) = update_result {
-                       assert_eq!(
-                               lightning_error.err,
-                               "Couldn't find previous directional data for update"
-                       );
-               } else {
-                       panic!("Unexpected update result: {:?}", update_result)
-               }
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+               rapid_sync.update_network_graph(&announced_update_input[..]).unwrap();
        }
 
        #[test]
+       #[cfg(feature = "std")]
        fn incremental_only_update_fails_without_prior_same_direction_updates() {
                let initialization_input = vec![
                        76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
@@ -325,13 +390,12 @@ mod tests {
                        0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
                ];
 
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
 
                assert_eq!(network_graph.read_only().channels().len(), 0);
 
-               let rapid_sync = RapidGossipSync::new(&network_graph);
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
                let initialization_result = rapid_sync.update_network_graph(&initialization_input[..]);
                if initialization_result.is_err() {
                        panic!(
@@ -360,19 +424,11 @@ mod tests {
                        0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 136, 0, 0, 0, 221, 255, 2,
                        68, 226, 0, 6, 11, 0, 1, 128,
                ];
-               let update_result = rapid_sync.update_network_graph(&opposite_direction_incremental_update_input[..]);
-               assert!(update_result.is_err());
-               if let Err(GraphSyncError::LightningError(lightning_error)) = update_result {
-                       assert_eq!(
-                               lightning_error.err,
-                               "Couldn't find previous directional data for update"
-                       );
-               } else {
-                       panic!("Unexpected update result: {:?}", update_result)
-               }
+               rapid_sync.update_network_graph(&opposite_direction_incremental_update_input[..]).unwrap();
        }
 
        #[test]
+       #[cfg(feature = "std")]
        fn incremental_update_succeeds_with_prior_announcements_and_full_updates() {
                let initialization_input = vec![
                        76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
@@ -392,13 +448,12 @@ mod tests {
                        25, 192,
                ];
 
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
 
                assert_eq!(network_graph.read_only().channels().len(), 0);
 
-               let rapid_sync = RapidGossipSync::new(&network_graph);
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
                let initialization_result = rapid_sync.update_network_graph(&initialization_input[..]);
                assert!(initialization_result.is_ok());
 
@@ -433,6 +488,7 @@ mod tests {
        }
 
        #[test]
+       #[cfg(feature = "std")]
        fn update_succeeds_when_duplicate_gossip_is_applied() {
                let initialization_input = vec![
                        76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
@@ -452,13 +508,12 @@ mod tests {
                        25, 192,
                ];
 
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
 
                assert_eq!(network_graph.read_only().channels().len(), 0);
 
-               let rapid_sync = RapidGossipSync::new(&network_graph);
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
                let initialization_result = rapid_sync.update_network_graph(&initialization_input[..]);
                assert!(initialization_result.is_ok());
 
@@ -477,33 +532,15 @@ mod tests {
        }
 
        #[test]
+       #[cfg(feature = "std")]
        fn full_update_succeeds() {
-               let valid_input = vec![
-                       76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
-                       79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
-                       0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
-                       187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
-                       157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
-                       88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
-                       204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
-                       181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
-                       110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
-                       76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
-                       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,
-                       0, 0, 0, 1, 0, 0, 0, 0, 29, 129, 25, 192, 255, 8, 153, 192, 0, 2, 27, 0, 0, 60, 0, 0,
-                       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,
-                       0, 0, 1, 0, 0, 0, 125, 0, 0, 0, 0, 58, 85, 116, 216, 255, 2, 68, 226, 0, 6, 11, 0, 1,
-                       0, 0, 1,
-               ];
-
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
 
                assert_eq!(network_graph.read_only().channels().len(), 0);
 
-               let rapid_sync = RapidGossipSync::new(&network_graph);
-               let update_result = rapid_sync.update_network_graph(&valid_input[..]);
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+               let update_result = rapid_sync.update_network_graph(&VALID_RGS_BINARY);
                if update_result.is_err() {
                        panic!("Unexpected update result: {:?}", update_result)
                }
@@ -527,6 +564,85 @@ mod tests {
        }
 
        #[test]
+       fn full_update_succeeds_at_the_beginning_of_the_unix_era() {
+               let logger = TestLogger::new();
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
+
+               assert_eq!(network_graph.read_only().channels().len(), 0);
+
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+               // this is mostly for checking uint underflow issues before the fuzzer does
+               let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(0));
+               assert!(update_result.is_ok());
+               assert_eq!(network_graph.read_only().channels().len(), 2);
+       }
+
+       #[test]
+       fn prunes_after_update() {
+               // this is the timestamp encoded in the binary data of valid_input below
+               let logger = TestLogger::new();
+
+               let latest_nonpruning_time = VALID_BINARY_TIMESTAMP + 60 * 60 * 24 * 7;
+
+               {
+                       let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
+                       assert_eq!(network_graph.read_only().channels().len(), 0);
+
+                       let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+                       let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(latest_nonpruning_time));
+                       assert!(update_result.is_ok());
+                       assert_eq!(network_graph.read_only().channels().len(), 2);
+               }
+
+               {
+                       let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
+                       assert_eq!(network_graph.read_only().channels().len(), 0);
+
+                       let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+                       let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(latest_nonpruning_time + 1));
+                       assert!(update_result.is_ok());
+                       assert_eq!(network_graph.read_only().channels().len(), 0);
+               }
+       }
+
+       #[test]
+       fn timestamp_edge_cases_are_handled_correctly() {
+               // this is the timestamp encoded in the binary data of valid_input below
+               let logger = TestLogger::new();
+
+               let latest_succeeding_time = VALID_BINARY_TIMESTAMP + STALE_RGS_UPDATE_AGE_LIMIT_SECS;
+               let earliest_failing_time = latest_succeeding_time + 1;
+
+               {
+                       let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
+                       assert_eq!(network_graph.read_only().channels().len(), 0);
+
+                       let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+                       let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(latest_succeeding_time));
+                       assert!(update_result.is_ok());
+                       assert_eq!(network_graph.read_only().channels().len(), 0);
+               }
+
+               {
+                       let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
+                       assert_eq!(network_graph.read_only().channels().len(), 0);
+
+                       let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+                       let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(earliest_failing_time));
+                       assert!(update_result.is_err());
+                       if let Err(GraphSyncError::LightningError(lightning_error)) = update_result {
+                               assert_eq!(
+                                       lightning_error.err,
+                                       "Rapid Gossip Sync data is more than two weeks old"
+                               );
+                       } else {
+                               panic!("Unexpected update result: {:?}", update_result)
+                       }
+               }
+       }
+
+       #[test]
+       #[cfg(feature = "std")]
        pub fn update_fails_with_unknown_version() {
                let unknown_version_input = vec![
                        76, 68, 75, 2, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
@@ -546,10 +662,9 @@ mod tests {
                        0, 0, 1,
                ];
 
-               let block_hash = genesis_block(Network::Bitcoin).block_hash();
                let logger = TestLogger::new();
-               let network_graph = NetworkGraph::new(block_hash, &logger);
-               let rapid_sync = RapidGossipSync::new(&network_graph);
+               let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
                let update_result = rapid_sync.update_network_graph(&unknown_version_input[..]);
 
                assert!(update_result.is_err());
@@ -560,4 +675,22 @@ mod tests {
                        panic!("Unexpected update result: {:?}", update_result)
                }
        }
+
+       #[test]
+       fn fails_early_on_chain_hash_mismatch() {
+               let logger = TestLogger::new();
+               // Set to testnet so that the VALID_RGS_BINARY chain hash of mainnet does not match.
+               let network_graph = NetworkGraph::new(Network::Testnet, &logger);
+
+               assert_eq!(network_graph.read_only().channels().len(), 0);
+
+               let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
+               let update_result = rapid_sync.update_network_graph_no_std(&VALID_RGS_BINARY, Some(0));
+               assert!(update_result.is_err());
+               if let Err(GraphSyncError::LightningError(err)) = update_result {
+                       assert_eq!(err.err, "Rapid Gossip Sync data's chain hash does not match the network graph's");
+               } else {
+                       panic!("Unexpected update result: {:?}", update_result)
+               }
+       }
 }