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 network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
57 //! let rapid_sync = RapidGossipSync::new(&network_graph);
58 //! let snapshot_contents: &[u8] = &[0; 0];
59 //! let new_last_sync_timestamp_result = rapid_sync.update_network_graph(snapshot_contents);
62 #![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
64 // Allow and import test features for benching
65 #![cfg_attr(all(test, feature = "_bench_unstable"), feature(test))]
66 #[cfg(all(test, feature = "_bench_unstable"))]
69 #[cfg(not(feature = "std"))]
72 #[cfg(feature = "std")]
75 use core::sync::atomic::{AtomicBool, Ordering};
78 use lightning::routing::gossip::NetworkGraph;
79 use lightning::util::logger::Logger;
81 pub use crate::error::GraphSyncError;
83 /// Error types that these functions can return
86 /// Core functionality of this crate
89 /// The main Rapid Gossip Sync object.
91 /// See [crate-level documentation] for usage.
93 /// [crate-level documentation]: crate
94 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
95 where L::Target: Logger {
97 is_initial_sync_complete: AtomicBool
100 impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
101 /// Instantiate a new [`RapidGossipSync`] instance.
102 pub fn new(network_graph: NG) -> Self {
105 is_initial_sync_complete: AtomicBool::new(false)
109 /// Sync gossip data from a file.
110 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
112 /// `network_graph`: The network graph to apply the updates to
114 /// `sync_path`: Path to the file where the gossip update data is located
116 #[cfg(feature = "std")]
117 pub fn sync_network_graph_with_file_path(
120 ) -> Result<u32, GraphSyncError> {
121 let mut file = File::open(sync_path)?;
122 self.update_network_graph_from_byte_stream(&mut file)
125 /// Update network graph from binary data.
126 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
128 /// `update_data`: `&[u8]` binary stream that comprises the update data
129 pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
130 let mut read_cursor = io::Cursor::new(update_data);
131 self.update_network_graph_from_byte_stream(&mut read_cursor)
134 /// Update network graph from binary data.
135 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
137 /// `update_data`: `&[u8]` binary stream that comprises the update data
138 /// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
139 pub fn update_network_graph_no_std(&self, update_data: &[u8], current_time_unix: Option<u64>) -> Result<u32, GraphSyncError> {
140 let mut read_cursor = io::Cursor::new(update_data);
141 self.update_network_graph_from_byte_stream_no_std(&mut read_cursor, current_time_unix)
144 /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
145 /// [`RapidGossipSync::new`].
147 /// (C-not exported) as bindings don't support a reference-to-a-reference yet
148 pub fn network_graph(&self) -> &NG {
152 /// Returns whether a rapid gossip sync has completed at least once.
153 pub fn is_initial_sync_complete(&self) -> bool {
154 self.is_initial_sync_complete.load(Ordering::Acquire)
158 #[cfg(feature = "std")]
163 use bitcoin::Network;
165 use lightning::ln::msgs::DecodeError;
166 use lightning::routing::gossip::NetworkGraph;
167 use lightning::util::test_utils::TestLogger;
168 use crate::RapidGossipSync;
171 fn test_sync_from_file() {
172 struct FileSyncTest {
177 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
178 let test = FileSyncTest { directory: tmp_directory.to_owned() };
180 let graph_sync_test_directory = test.get_test_directory();
181 fs::create_dir_all(graph_sync_test_directory).unwrap();
183 let graph_sync_test_file = test.get_test_file_path();
184 fs::write(graph_sync_test_file, valid_response).unwrap();
189 fn get_test_directory(&self) -> String {
190 self.directory.clone() + "/graph-sync-tests"
193 fn get_test_file_path(&self) -> String {
194 self.get_test_directory() + "/test_data.lngossip"
198 impl Drop for FileSyncTest {
200 fs::remove_dir_all(self.directory.clone()).unwrap();
204 // same as incremental_only_update_fails_without_prior_same_direction_updates
205 let valid_response = vec![
206 76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
207 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
208 0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
209 187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
210 157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
211 88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
212 204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
213 181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
214 110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
215 76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
216 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,
217 0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
218 0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
221 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
222 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
223 let graph_sync_test_file = sync_test.get_test_file_path();
225 let logger = TestLogger::new();
226 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
228 assert_eq!(network_graph.read_only().channels().len(), 0);
230 let rapid_sync = RapidGossipSync::new(&network_graph);
231 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
233 if sync_result.is_err() {
234 panic!("Unexpected sync result: {:?}", sync_result)
237 assert_eq!(network_graph.read_only().channels().len(), 2);
238 let after = network_graph.to_string();
240 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
243 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
246 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
249 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
251 assert!(after.contains("619737530008010752"));
252 assert!(after.contains("783241506229452801"));
256 fn measure_native_read_from_file() {
257 let logger = TestLogger::new();
258 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
260 assert_eq!(network_graph.read_only().channels().len(), 0);
262 let rapid_sync = RapidGossipSync::new(&network_graph);
263 let start = std::time::Instant::now();
264 let sync_result = rapid_sync
265 .sync_network_graph_with_file_path("./res/full_graph.lngossip");
266 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
267 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);
268 #[cfg(not(require_route_graph_test))]
270 println!("{}", error_string);
273 #[cfg(require_route_graph_test)]
274 panic!("{}", error_string);
276 let elapsed = start.elapsed();
277 println!("initialization duration: {:?}", elapsed);
278 if sync_result.is_err() {
279 panic!("Unexpected sync result: {:?}", sync_result)
284 #[cfg(all(test, feature = "_bench_unstable"))]
288 use bitcoin::Network;
290 use lightning::ln::msgs::DecodeError;
291 use lightning::routing::gossip::NetworkGraph;
292 use lightning::util::test_utils::TestLogger;
294 use crate::RapidGossipSync;
297 fn bench_reading_full_graph_from_file(b: &mut Bencher) {
298 let logger = TestLogger::new();
300 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
301 let rapid_sync = RapidGossipSync::new(&network_graph);
302 let sync_result = rapid_sync.sync_network_graph_with_file_path("./res/full_graph.lngossip");
303 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
304 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);
305 #[cfg(not(require_route_graph_test))]
307 println!("{}", error_string);
310 #[cfg(require_route_graph_test)]
311 panic!("{}", error_string);
313 assert!(sync_result.is_ok())