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