1 #![deny(rustdoc::broken_intra_doc_links)]
2 #![deny(rustdoc::private_intra_doc_links)]
6 #![deny(non_upper_case_globals)]
7 #![deny(non_camel_case_types)]
8 #![deny(non_snake_case)]
10 #![deny(unused_variables)]
11 #![deny(unused_imports)]
12 //! This crate exposes client functionality to rapidly sync gossip data, aimed primarily at mobile
15 //! The rapid gossip sync server will provide a compressed response containing differential gossip
16 //! data. The gossip data is formatted compactly, omitting signatures and opportunistically
17 //! incremental where previous channel updates are known. This mechanism is enabled when the
18 //! timestamp of the last known channel update is communicated. A reference server implementation
19 //! can be found [on Github](https://github.com/lightningdevkit/rapid-gossip-sync-server).
21 //! The primary benefit of this syncing mechanism is that it allows a low-powered client to offload
22 //! the validation of gossip signatures to a semi-trusted server. This enables the client to
23 //! privately calculate routes for payments, and to do so much faster than requiring a full
24 //! peer-to-peer gossip sync to complete.
26 //! The server calculates its response on the basis of a client-provided `latest_seen` timestamp,
27 //! i.e., the server will return all rapid gossip sync data it has seen after the given timestamp.
30 //! Firstly, the data needs to be retrieved from the server. For example, you could use the server
31 //! at <https://rapidsync.lightningdevkit.org> with the following request format:
34 //! curl -o rapid_sync.lngossip https://rapidsync.lightningdevkit.org/snapshot/<last_sync_timestamp>
36 //! Note that the first ever rapid sync should use `0` for `last_sync_timestamp`.
38 //! After the gossip data snapshot has been downloaded, one of the client's graph processing
39 //! functions needs to be called. In this example, we process the update by reading its contents
40 //! from disk, which we do by calling [`RapidGossipSync::update_network_graph`]:
43 //! use bitcoin::blockdata::constants::genesis_block;
44 //! use bitcoin::Network;
45 //! use lightning::routing::gossip::NetworkGraph;
46 //! use lightning_rapid_gossip_sync::RapidGossipSync;
48 //! # use lightning::util::logger::{Logger, Record};
49 //! # struct FakeLogger {}
50 //! # impl Logger for FakeLogger {
51 //! # fn log(&self, record: Record) { }
53 //! # let logger = FakeLogger {};
55 //! let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
56 //! let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
57 //! let snapshot_contents: &[u8] = &[0; 0];
58 //! // In no-std you need to provide the current time in unix epoch seconds
59 //! // otherwise you can use update_network_graph
60 //! let current_time_unix = 0;
61 //! let new_last_sync_timestamp_result = rapid_sync.update_network_graph_no_std(snapshot_contents, Some(current_time_unix));
64 #![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
66 #[cfg(ldk_bench)] extern crate criterion;
68 #[cfg(not(feature = "std"))]
71 #[cfg(feature = "std")]
74 use core::sync::atomic::{AtomicBool, Ordering};
77 use lightning::routing::gossip::NetworkGraph;
78 use lightning::util::logger::Logger;
80 pub use crate::error::GraphSyncError;
82 /// Error types that these functions can return
85 /// Core functionality of this crate
88 /// The main Rapid Gossip Sync object.
90 /// See [crate-level documentation] for usage.
92 /// [crate-level documentation]: crate
93 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
94 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, logger: L) -> 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 /// `update_data`: `&[u8]` binary stream that comprises the update data
130 #[cfg(feature = "std")]
131 pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
132 let mut read_cursor = io::Cursor::new(update_data);
133 self.update_network_graph_from_byte_stream(&mut read_cursor)
136 /// Update network graph from binary data.
137 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
139 /// `update_data`: `&[u8]` binary stream that comprises the update data
140 /// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
141 pub fn update_network_graph_no_std(&self, update_data: &[u8], current_time_unix: Option<u64>) -> Result<u32, GraphSyncError> {
142 let mut read_cursor = io::Cursor::new(update_data);
143 self.update_network_graph_from_byte_stream_no_std(&mut read_cursor, current_time_unix)
146 /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
147 /// [`RapidGossipSync::new`].
149 /// This is not exported to bindings users as bindings don't support a reference-to-a-reference yet
150 pub fn network_graph(&self) -> &NG {
154 /// Returns whether a rapid gossip sync has completed at least once.
155 pub fn is_initial_sync_complete(&self) -> bool {
156 self.is_initial_sync_complete.load(Ordering::Acquire)
160 #[cfg(feature = "std")]
165 use bitcoin::Network;
167 use lightning::ln::msgs::DecodeError;
168 use lightning::routing::gossip::NetworkGraph;
169 use lightning::util::test_utils::TestLogger;
170 use crate::RapidGossipSync;
173 fn test_sync_from_file() {
174 struct FileSyncTest {
179 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
180 let test = FileSyncTest { directory: tmp_directory.to_owned() };
182 let graph_sync_test_directory = test.get_test_directory();
183 fs::create_dir_all(graph_sync_test_directory).unwrap();
185 let graph_sync_test_file = test.get_test_file_path();
186 fs::write(graph_sync_test_file, valid_response).unwrap();
191 fn get_test_directory(&self) -> String {
192 self.directory.clone() + "/graph-sync-tests"
195 fn get_test_file_path(&self) -> String {
196 self.get_test_directory() + "/test_data.lngossip"
200 impl Drop for FileSyncTest {
202 fs::remove_dir_all(self.directory.clone()).unwrap();
206 // same as incremental_only_update_fails_without_prior_same_direction_updates
207 let valid_response = vec![
208 76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
209 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
210 0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
211 187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
212 157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
213 88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
214 204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
215 181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
216 110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
217 76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
218 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,
219 0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
220 0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
223 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
224 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
225 let graph_sync_test_file = sync_test.get_test_file_path();
227 let logger = TestLogger::new();
228 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
230 assert_eq!(network_graph.read_only().channels().len(), 0);
232 let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
233 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
235 if sync_result.is_err() {
236 panic!("Unexpected sync result: {:?}", sync_result)
239 assert_eq!(network_graph.read_only().channels().len(), 2);
240 let after = network_graph.to_string();
242 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
245 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
248 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
251 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
253 assert!(after.contains("619737530008010752"));
254 assert!(after.contains("783241506229452801"));
258 fn measure_native_read_from_file() {
259 let logger = TestLogger::new();
260 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
262 assert_eq!(network_graph.read_only().channels().len(), 0);
264 let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
265 let start = std::time::Instant::now();
266 let sync_result = rapid_sync
267 .sync_network_graph_with_file_path("./res/full_graph.lngossip");
268 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
269 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);
270 #[cfg(not(require_route_graph_test))]
272 println!("{}", error_string);
275 #[cfg(require_route_graph_test)]
276 panic!("{}", error_string);
278 let elapsed = start.elapsed();
279 println!("initialization duration: {:?}", elapsed);
280 if sync_result.is_err() {
281 panic!("Unexpected sync result: {:?}", sync_result)
289 use bitcoin::Network;
291 use criterion::Criterion;
295 use lightning::routing::gossip::NetworkGraph;
296 use lightning::util::test_utils::TestLogger;
298 use crate::RapidGossipSync;
301 pub fn bench_reading_full_graph_from_file(b: &mut Criterion) {
302 let logger = TestLogger::new();
303 b.bench_function("read_full_graph_from_rgs", |b| b.iter(|| {
304 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
305 let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
306 let mut file = match fs::read("../lightning-rapid-gossip-sync/res/full_graph.lngossip") {
309 let error_string = format!(
310 "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{:?}",
312 #[cfg(not(require_route_graph_test))]
314 println!("{}", error_string);
317 #[cfg(require_route_graph_test)]
318 panic!("{}", error_string);
321 rapid_sync.update_network_graph_no_std(&mut file, None).unwrap();