1 // Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings.
2 #![deny(broken_intra_doc_links)]
3 #![deny(private_intra_doc_links)]
7 #![deny(non_upper_case_globals)]
8 #![deny(non_camel_case_types)]
9 #![deny(non_snake_case)]
11 #![deny(unused_variables)]
12 #![deny(unused_imports)]
13 //! This crate exposes client functionality to rapidly sync gossip data, aimed primarily at mobile
16 //! The rapid gossip sync server will provide a compressed response containing differential gossip
17 //! data. The gossip data is formatted compactly, omitting signatures and opportunistically
18 //! incremental where previous channel updates are known. This mechanism is enabled when the
19 //! timestamp of the last known channel update is communicated. A reference server implementation
20 //! can be found [on Github](https://github.com/lightningdevkit/rapid-gossip-sync-server).
22 //! The primary benefit of this syncing mechanism is that it allows a low-powered client to offload
23 //! the validation of gossip signatures to a semi-trusted server. This enables the client to
24 //! privately calculate routes for payments, and to do so much faster than requiring a full
25 //! peer-to-peer gossip sync to complete.
27 //! The server calculates its response on the basis of a client-provided `latest_seen` timestamp,
28 //! i.e., the server will return all rapid gossip sync data it has seen after the given timestamp.
31 //! Firstly, the data needs to be retrieved from the server. For example, you could use the server
32 //! at <https://rapidsync.lightningdevkit.org> with the following request format:
35 //! curl -o rapid_sync.lngossip https://rapidsync.lightningdevkit.org/snapshot/<last_sync_timestamp>
37 //! Note that the first ever rapid sync should use `0` for `last_sync_timestamp`.
39 //! After the gossip data snapshot has been downloaded, one of the client's graph processing
40 //! functions needs to be called. In this example, we process the update by reading its contents
41 //! from disk, which we do by calling [`RapidGossipSync::update_network_graph`]:
44 //! use bitcoin::blockdata::constants::genesis_block;
45 //! use bitcoin::Network;
46 //! use lightning::routing::gossip::NetworkGraph;
47 //! use lightning_rapid_gossip_sync::RapidGossipSync;
49 //! # use lightning::util::logger::{Logger, Record};
50 //! # struct FakeLogger {}
51 //! # impl Logger for FakeLogger {
52 //! # fn log(&self, record: &Record) { unimplemented!() }
54 //! # let logger = FakeLogger {};
56 //! let block_hash = genesis_block(Network::Bitcoin).header.block_hash();
57 //! let network_graph = NetworkGraph::new(block_hash, &logger);
58 //! let rapid_sync = RapidGossipSync::new(&network_graph);
59 //! let snapshot_contents: &[u8] = &[0; 0];
60 //! let new_last_sync_timestamp_result = rapid_sync.update_network_graph(snapshot_contents);
63 #![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
65 // Allow and import test features for benching
66 #![cfg_attr(all(test, feature = "_bench_unstable"), feature(test))]
67 #[cfg(all(test, feature = "_bench_unstable"))]
70 #[cfg(not(feature = "std"))]
73 #[cfg(feature = "std")]
76 use core::sync::atomic::{AtomicBool, Ordering};
79 use lightning::routing::gossip::NetworkGraph;
80 use lightning::util::logger::Logger;
82 pub use crate::error::GraphSyncError;
84 /// Error types that these functions can return
87 /// Core functionality of this crate
90 /// The main Rapid Gossip Sync object.
92 /// See [crate-level documentation] for usage.
94 /// [crate-level documentation]: crate
95 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
96 where L::Target: Logger {
98 is_initial_sync_complete: AtomicBool
101 impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
102 /// Instantiate a new [`RapidGossipSync`] instance.
103 pub fn new(network_graph: NG) -> Self {
106 is_initial_sync_complete: AtomicBool::new(false)
110 /// Sync gossip data from a file.
111 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
113 /// `network_graph`: The network graph to apply the updates to
115 /// `sync_path`: Path to the file where the gossip update data is located
117 #[cfg(feature = "std")]
118 pub fn sync_network_graph_with_file_path(
121 ) -> Result<u32, GraphSyncError> {
122 let mut file = File::open(sync_path)?;
123 self.update_network_graph_from_byte_stream(&mut file)
126 /// Update network graph from binary data.
127 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
129 /// `update_data`: `&[u8]` binary stream that comprises the update data
130 pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
131 let mut read_cursor = io::Cursor::new(update_data);
132 self.update_network_graph_from_byte_stream(&mut read_cursor)
135 /// Update network graph from binary data.
136 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
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)
145 /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
146 /// [`RapidGossipSync::new`].
148 /// (C-not exported) as bindings don't support a reference-to-a-reference yet
149 pub fn network_graph(&self) -> &NG {
153 /// Returns whether a rapid gossip sync has completed at least once.
154 pub fn is_initial_sync_complete(&self) -> bool {
155 self.is_initial_sync_complete.load(Ordering::Acquire)
159 #[cfg(feature = "std")]
164 use bitcoin::blockdata::constants::genesis_block;
165 use bitcoin::Network;
167 use lightning::ln::msgs::DecodeError;
168 use lightning::routing::gossip::NetworkGraph;
169 use lightning::util::test_utils::TestLogger;
170 use crate::RapidGossipSync;
173 fn test_sync_from_file() {
174 struct FileSyncTest {
179 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
180 let test = FileSyncTest { directory: tmp_directory.to_owned() };
182 let graph_sync_test_directory = test.get_test_directory();
183 fs::create_dir_all(graph_sync_test_directory).unwrap();
185 let graph_sync_test_file = test.get_test_file_path();
186 fs::write(&graph_sync_test_file, valid_response).unwrap();
190 fn get_test_directory(&self) -> String {
191 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
192 graph_sync_test_directory
194 fn get_test_file_path(&self) -> String {
195 let graph_sync_test_directory = self.get_test_directory();
196 let graph_sync_test_file = graph_sync_test_directory.to_owned() + "/test_data.lngossip";
201 impl Drop for FileSyncTest {
203 fs::remove_dir_all(self.directory.clone()).unwrap();
207 // same as incremental_only_update_fails_without_prior_same_direction_updates
208 let valid_response = vec![
209 76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
210 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
211 0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
212 187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
213 157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
214 88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
215 204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
216 181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
217 110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
218 76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
219 226, 0, 6, 11, 0, 1, 2, 3, 0, 0, 0, 2, 0, 40, 0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 3, 232,
220 0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
221 0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
224 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
225 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
226 let graph_sync_test_file = sync_test.get_test_file_path();
228 let block_hash = genesis_block(Network::Bitcoin).block_hash();
229 let logger = TestLogger::new();
230 let network_graph = NetworkGraph::new(block_hash, &logger);
232 assert_eq!(network_graph.read_only().channels().len(), 0);
234 let rapid_sync = RapidGossipSync::new(&network_graph);
235 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
237 if sync_result.is_err() {
238 panic!("Unexpected sync result: {:?}", sync_result)
241 assert_eq!(network_graph.read_only().channels().len(), 2);
242 let after = network_graph.to_string();
244 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
247 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
250 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
253 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
255 assert!(after.contains("619737530008010752"));
256 assert!(after.contains("783241506229452801"));
260 fn measure_native_read_from_file() {
261 let block_hash = genesis_block(Network::Bitcoin).block_hash();
262 let logger = TestLogger::new();
263 let network_graph = NetworkGraph::new(block_hash, &logger);
265 assert_eq!(network_graph.read_only().channels().len(), 0);
267 let rapid_sync = RapidGossipSync::new(&network_graph);
268 let start = std::time::Instant::now();
269 let sync_result = rapid_sync
270 .sync_network_graph_with_file_path("./res/full_graph.lngossip");
271 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
272 let error_string = format!("Input file lightning-rapid-gossip-sync/res/full_graph.lngossip is missing! Download it from https://bitcoin.ninja/ldk-compressed_graph-285cb27df79-2022-07-21.bin\n\n{:?}", io_error);
273 #[cfg(not(require_route_graph_test))]
275 println!("{}", error_string);
278 #[cfg(require_route_graph_test)]
279 panic!("{}", error_string);
281 let elapsed = start.elapsed();
282 println!("initialization duration: {:?}", elapsed);
283 if sync_result.is_err() {
284 panic!("Unexpected sync result: {:?}", sync_result)
289 #[cfg(all(test, feature = "_bench_unstable"))]
293 use bitcoin::blockdata::constants::genesis_block;
294 use bitcoin::Network;
296 use lightning::ln::msgs::DecodeError;
297 use lightning::routing::gossip::NetworkGraph;
298 use lightning::util::test_utils::TestLogger;
300 use crate::RapidGossipSync;
303 fn bench_reading_full_graph_from_file(b: &mut Bencher) {
304 let block_hash = genesis_block(Network::Bitcoin).block_hash();
305 let logger = TestLogger::new();
307 let network_graph = NetworkGraph::new(block_hash, &logger);
308 let rapid_sync = RapidGossipSync::new(&network_graph);
309 let sync_result = rapid_sync.sync_network_graph_with_file_path("./res/full_graph.lngossip");
310 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
311 let error_string = format!("Input file lightning-rapid-gossip-sync/res/full_graph.lngossip is missing! Download it from https://bitcoin.ninja/ldk-compressed_graph-bc08df7542-2022-05-05.bin\n\n{:?}", io_error);
312 #[cfg(not(require_route_graph_test))]
314 println!("{}", error_string);
317 #[cfg(require_route_graph_test)]
318 panic!("{}", error_string);
320 assert!(sync_result.is_ok())