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 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 /// `network_graph`: network graph to be updated
131 /// `update_data`: `&[u8]` binary stream that comprises the update data
132 pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
133 let mut read_cursor = io::Cursor::new(update_data);
134 self.update_network_graph_from_byte_stream(&mut read_cursor)
137 /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
138 /// [`RapidGossipSync::new`].
140 /// (C-not exported) as bindings don't support a reference-to-a-reference yet
141 pub fn network_graph(&self) -> &NG {
145 /// Returns whether a rapid gossip sync has completed at least once.
146 pub fn is_initial_sync_complete(&self) -> bool {
147 self.is_initial_sync_complete.load(Ordering::Acquire)
151 #[cfg(feature = "std")]
156 use bitcoin::blockdata::constants::genesis_block;
157 use bitcoin::Network;
159 use lightning::ln::msgs::DecodeError;
160 use lightning::routing::gossip::NetworkGraph;
161 use lightning::util::test_utils::TestLogger;
162 use crate::RapidGossipSync;
165 fn test_sync_from_file() {
166 struct FileSyncTest {
171 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
172 let test = FileSyncTest { directory: tmp_directory.to_owned() };
174 let graph_sync_test_directory = test.get_test_directory();
175 fs::create_dir_all(graph_sync_test_directory).unwrap();
177 let graph_sync_test_file = test.get_test_file_path();
178 fs::write(&graph_sync_test_file, valid_response).unwrap();
182 fn get_test_directory(&self) -> String {
183 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
184 graph_sync_test_directory
186 fn get_test_file_path(&self) -> String {
187 let graph_sync_test_directory = self.get_test_directory();
188 let graph_sync_test_file = graph_sync_test_directory.to_owned() + "/test_data.lngossip";
193 impl Drop for FileSyncTest {
195 fs::remove_dir_all(self.directory.clone()).unwrap();
199 // same as incremental_only_update_fails_without_prior_same_direction_updates
200 let valid_response = vec![
201 76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
202 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
203 0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
204 187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
205 157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
206 88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
207 204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
208 181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
209 110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
210 76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
211 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,
212 0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
213 0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
216 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
217 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
218 let graph_sync_test_file = sync_test.get_test_file_path();
220 let block_hash = genesis_block(Network::Bitcoin).block_hash();
221 let logger = TestLogger::new();
222 let network_graph = NetworkGraph::new(block_hash, &logger);
224 assert_eq!(network_graph.read_only().channels().len(), 0);
226 let rapid_sync = RapidGossipSync::new(&network_graph);
227 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
229 if sync_result.is_err() {
230 panic!("Unexpected sync result: {:?}", sync_result)
233 assert_eq!(network_graph.read_only().channels().len(), 2);
234 let after = network_graph.to_string();
236 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
239 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
242 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
245 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
247 assert!(after.contains("619737530008010752"));
248 assert!(after.contains("783241506229452801"));
252 fn measure_native_read_from_file() {
253 let block_hash = genesis_block(Network::Bitcoin).block_hash();
254 let logger = TestLogger::new();
255 let network_graph = NetworkGraph::new(block_hash, &logger);
257 assert_eq!(network_graph.read_only().channels().len(), 0);
259 let rapid_sync = RapidGossipSync::new(&network_graph);
260 let start = std::time::Instant::now();
261 let sync_result = rapid_sync
262 .sync_network_graph_with_file_path("./res/full_graph.lngossip");
263 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
264 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);
265 #[cfg(not(require_route_graph_test))]
267 println!("{}", error_string);
270 #[cfg(require_route_graph_test)]
271 panic!("{}", error_string);
273 let elapsed = start.elapsed();
274 println!("initialization duration: {:?}", elapsed);
275 if sync_result.is_err() {
276 panic!("Unexpected sync result: {:?}", sync_result)
281 #[cfg(all(test, feature = "_bench_unstable"))]
285 use bitcoin::blockdata::constants::genesis_block;
286 use bitcoin::Network;
288 use lightning::ln::msgs::DecodeError;
289 use lightning::routing::gossip::NetworkGraph;
290 use lightning::util::test_utils::TestLogger;
292 use crate::RapidGossipSync;
295 fn bench_reading_full_graph_from_file(b: &mut Bencher) {
296 let block_hash = genesis_block(Network::Bitcoin).block_hash();
297 let logger = TestLogger::new();
299 let network_graph = NetworkGraph::new(block_hash, &logger);
300 let rapid_sync = RapidGossipSync::new(&network_graph);
301 let sync_result = rapid_sync.sync_network_graph_with_file_path("./res/full_graph.lngossip");
302 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
303 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);
304 #[cfg(not(require_route_graph_test))]
306 println!("{}", error_string);
309 #[cfg(require_route_graph_test)]
310 panic!("{}", error_string);
312 assert!(sync_result.is_ok())