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