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 [sync_network_graph_with_file_path]:
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 new_last_sync_timestamp_result = rapid_sync.sync_network_graph_with_file_path("./rapid_sync.lngossip");
61 //! [sync_network_graph_with_file_path]: RapidGossipSync::sync_network_graph_with_file_path
63 // Allow and import test features for benching
64 #![cfg_attr(all(test, feature = "_bench_unstable"), feature(test))]
65 #[cfg(all(test, feature = "_bench_unstable"))]
70 use std::sync::atomic::{AtomicBool, Ordering};
72 use lightning::routing::gossip::NetworkGraph;
73 use lightning::util::logger::Logger;
75 use crate::error::GraphSyncError;
77 /// Error types that these functions can return
80 /// Core functionality of this crate
83 /// The main Rapid Gossip Sync object.
85 /// See [crate-level documentation] for usage.
87 /// [crate-level documentation]: crate
88 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
89 where L::Target: Logger {
91 is_initial_sync_complete: AtomicBool
94 impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
95 /// Instantiate a new [`RapidGossipSync`] instance.
96 pub fn new(network_graph: NG) -> Self {
99 is_initial_sync_complete: AtomicBool::new(false)
103 /// Sync gossip data from a file.
104 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
106 /// `network_graph`: The network graph to apply the updates to
108 /// `sync_path`: Path to the file where the gossip update data is located
110 pub fn sync_network_graph_with_file_path(
113 ) -> Result<u32, GraphSyncError> {
114 let mut file = File::open(sync_path)?;
115 self.update_network_graph_from_byte_stream(&mut file)
118 /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
119 /// [`RapidGossipSync::new`].
121 /// (C-not exported) as bindings don't support a reference-to-a-reference yet
122 pub fn network_graph(&self) -> &NG {
126 /// Returns whether a rapid gossip sync has completed at least once.
127 pub fn is_initial_sync_complete(&self) -> bool {
128 self.is_initial_sync_complete.load(Ordering::Acquire)
136 use bitcoin::blockdata::constants::genesis_block;
137 use bitcoin::Network;
139 use lightning::ln::msgs::DecodeError;
140 use lightning::routing::gossip::NetworkGraph;
141 use lightning::util::test_utils::TestLogger;
142 use crate::RapidGossipSync;
145 fn test_sync_from_file() {
146 struct FileSyncTest {
151 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
152 let test = FileSyncTest { directory: tmp_directory.to_owned() };
154 let graph_sync_test_directory = test.get_test_directory();
155 fs::create_dir_all(graph_sync_test_directory).unwrap();
157 let graph_sync_test_file = test.get_test_file_path();
158 fs::write(&graph_sync_test_file, valid_response).unwrap();
162 fn get_test_directory(&self) -> String {
163 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
164 graph_sync_test_directory
166 fn get_test_file_path(&self) -> String {
167 let graph_sync_test_directory = self.get_test_directory();
168 let graph_sync_test_file = graph_sync_test_directory.to_owned() + "/test_data.lngossip";
173 impl Drop for FileSyncTest {
175 fs::remove_dir_all(self.directory.clone()).unwrap();
179 // same as incremental_only_update_fails_without_prior_same_direction_updates
180 let valid_response = vec![
181 76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
182 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
183 0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
184 187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
185 157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
186 88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
187 204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
188 181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
189 110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
190 76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
191 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,
192 0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
193 0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
196 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
197 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
198 let graph_sync_test_file = sync_test.get_test_file_path();
200 let block_hash = genesis_block(Network::Bitcoin).block_hash();
201 let logger = TestLogger::new();
202 let network_graph = NetworkGraph::new(block_hash, &logger);
204 assert_eq!(network_graph.read_only().channels().len(), 0);
206 let rapid_sync = RapidGossipSync::new(&network_graph);
207 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
209 if sync_result.is_err() {
210 panic!("Unexpected sync result: {:?}", sync_result)
213 assert_eq!(network_graph.read_only().channels().len(), 2);
214 let after = network_graph.to_string();
216 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
219 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
222 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
225 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
227 assert!(after.contains("619737530008010752"));
228 assert!(after.contains("783241506229452801"));
232 fn measure_native_read_from_file() {
233 let block_hash = genesis_block(Network::Bitcoin).block_hash();
234 let logger = TestLogger::new();
235 let network_graph = NetworkGraph::new(block_hash, &logger);
237 assert_eq!(network_graph.read_only().channels().len(), 0);
239 let rapid_sync = RapidGossipSync::new(&network_graph);
240 let start = std::time::Instant::now();
241 let sync_result = rapid_sync
242 .sync_network_graph_with_file_path("./res/full_graph.lngossip");
243 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
244 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);
245 #[cfg(not(require_route_graph_test))]
247 println!("{}", error_string);
250 #[cfg(require_route_graph_test)]
251 panic!("{}", error_string);
253 let elapsed = start.elapsed();
254 println!("initialization duration: {:?}", elapsed);
255 if sync_result.is_err() {
256 panic!("Unexpected sync result: {:?}", sync_result)
261 #[cfg(all(test, feature = "_bench_unstable"))]
265 use bitcoin::blockdata::constants::genesis_block;
266 use bitcoin::Network;
268 use lightning::ln::msgs::DecodeError;
269 use lightning::routing::gossip::NetworkGraph;
270 use lightning::util::test_utils::TestLogger;
272 use crate::RapidGossipSync;
275 fn bench_reading_full_graph_from_file(b: &mut Bencher) {
276 let block_hash = genesis_block(Network::Bitcoin).block_hash();
277 let logger = TestLogger::new();
279 let network_graph = NetworkGraph::new(block_hash, &logger);
280 let rapid_sync = RapidGossipSync::new(&network_graph);
281 let sync_result = rapid_sync.sync_network_graph_with_file_path("./res/full_graph.lngossip");
282 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
283 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);
284 #[cfg(not(require_route_graph_test))]
286 println!("{}", error_string);
289 #[cfg(require_route_graph_test)]
290 panic!("{}", error_string);
292 assert!(sync_result.is_ok())