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};
76 use core::fmt::Formatter;
79 use lightning::ln::msgs::{DecodeError, LightningError};
80 use lightning::routing::gossip::NetworkGraph;
81 use lightning::util::logger::Logger;
83 /// Core functionality of this crate
86 /// All-encompassing standard error type that processing can return
87 pub enum GraphSyncError {
88 /// Error trying to read the update data, typically due to an erroneous data length indication
89 /// that is greater than the actual amount of data provided
90 DecodeError(DecodeError),
91 /// Error applying the patch to the network graph, usually the result of updates that are too
92 /// old or missing prerequisite data to the application of updates out of order
93 LightningError(LightningError),
96 impl From<lightning::io::Error> for GraphSyncError {
97 fn from(error: lightning::io::Error) -> Self {
98 Self::DecodeError(DecodeError::Io(error.kind()))
102 impl From<DecodeError> for GraphSyncError {
103 fn from(error: DecodeError) -> Self {
104 Self::DecodeError(error)
108 impl From<LightningError> for GraphSyncError {
109 fn from(error: LightningError) -> Self {
110 Self::LightningError(error)
114 impl Debug for GraphSyncError {
115 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
117 GraphSyncError::DecodeError(e) => f.write_fmt(format_args!("DecodeError: {:?}", e)),
118 GraphSyncError::LightningError(e) => f.write_fmt(format_args!("LightningError: {:?}", e))
123 /// The main Rapid Gossip Sync object.
125 /// See [crate-level documentation] for usage.
127 /// [crate-level documentation]: crate
128 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
129 where L::Target: Logger {
132 is_initial_sync_complete: AtomicBool
135 impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
136 /// Instantiate a new [`RapidGossipSync`] instance.
137 pub fn new(network_graph: NG, logger: L) -> Self {
141 is_initial_sync_complete: AtomicBool::new(false)
145 /// Sync gossip data from a file.
146 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
148 /// `network_graph`: The network graph to apply the updates to
150 /// `sync_path`: Path to the file where the gossip update data is located
152 #[cfg(feature = "std")]
153 pub fn sync_network_graph_with_file_path(
156 ) -> Result<u32, GraphSyncError> {
157 let mut file = File::open(sync_path)?;
158 self.update_network_graph_from_byte_stream(&mut file)
161 /// Update network graph from binary data.
162 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
164 /// `update_data`: `&[u8]` binary stream that comprises the update data
165 #[cfg(feature = "std")]
166 pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
167 let mut read_cursor = io::Cursor::new(update_data);
168 self.update_network_graph_from_byte_stream(&mut read_cursor)
171 /// Update network graph from binary data.
172 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
174 /// `update_data`: `&[u8]` binary stream that comprises the update data
175 /// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
176 pub fn update_network_graph_no_std(&self, update_data: &[u8], current_time_unix: Option<u64>) -> Result<u32, GraphSyncError> {
177 let mut read_cursor = io::Cursor::new(update_data);
178 self.update_network_graph_from_byte_stream_no_std(&mut read_cursor, current_time_unix)
181 /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
182 /// [`RapidGossipSync::new`].
184 /// This is not exported to bindings users as bindings don't support a reference-to-a-reference yet
185 pub fn network_graph(&self) -> &NG {
189 /// Returns whether a rapid gossip sync has completed at least once.
190 pub fn is_initial_sync_complete(&self) -> bool {
191 self.is_initial_sync_complete.load(Ordering::Acquire)
195 #[cfg(feature = "std")]
200 use bitcoin::Network;
202 use lightning::ln::msgs::DecodeError;
203 use lightning::routing::gossip::NetworkGraph;
204 use lightning::util::test_utils::TestLogger;
205 use crate::{GraphSyncError, RapidGossipSync};
208 fn test_sync_from_file() {
209 struct FileSyncTest {
214 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
215 let test = FileSyncTest { directory: tmp_directory.to_owned() };
217 let graph_sync_test_directory = test.get_test_directory();
218 fs::create_dir_all(graph_sync_test_directory).unwrap();
220 let graph_sync_test_file = test.get_test_file_path();
221 fs::write(graph_sync_test_file, valid_response).unwrap();
226 fn get_test_directory(&self) -> String {
227 self.directory.clone() + "/graph-sync-tests"
230 fn get_test_file_path(&self) -> String {
231 self.get_test_directory() + "/test_data.lngossip"
235 impl Drop for FileSyncTest {
237 fs::remove_dir_all(self.directory.clone()).unwrap();
241 // same as incremental_only_update_fails_without_prior_same_direction_updates
242 let valid_response = vec![
243 76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
244 79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
245 0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
246 187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
247 157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
248 88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
249 204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
250 181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
251 110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
252 76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
253 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,
254 0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
255 0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
258 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
259 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
260 let graph_sync_test_file = sync_test.get_test_file_path();
262 let logger = TestLogger::new();
263 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
265 assert_eq!(network_graph.read_only().channels().len(), 0);
267 let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
268 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
270 if sync_result.is_err() {
271 panic!("Unexpected sync result: {:?}", sync_result)
274 assert_eq!(network_graph.read_only().channels().len(), 2);
275 let after = network_graph.to_string();
277 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
280 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
283 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
286 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
288 assert!(after.contains("619737530008010752"));
289 assert!(after.contains("783241506229452801"));
293 fn measure_native_read_from_file() {
294 let logger = TestLogger::new();
295 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
297 assert_eq!(network_graph.read_only().channels().len(), 0);
299 let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
300 let start = std::time::Instant::now();
301 let sync_result = rapid_sync
302 .sync_network_graph_with_file_path("./res/full_graph.lngossip");
303 if let Err(GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
304 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);
305 #[cfg(not(require_route_graph_test))]
307 println!("{}", error_string);
310 #[cfg(require_route_graph_test)]
311 panic!("{}", error_string);
313 let elapsed = start.elapsed();
314 println!("initialization duration: {:?}", elapsed);
315 if sync_result.is_err() {
316 panic!("Unexpected sync result: {:?}", sync_result)
324 use bitcoin::Network;
326 use criterion::Criterion;
330 use lightning::routing::gossip::NetworkGraph;
331 use lightning::util::test_utils::TestLogger;
333 use crate::RapidGossipSync;
336 pub fn bench_reading_full_graph_from_file(b: &mut Criterion) {
337 let logger = TestLogger::new();
338 b.bench_function("read_full_graph_from_rgs", |b| b.iter(|| {
339 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
340 let rapid_sync = RapidGossipSync::new(&network_graph, &logger);
341 let mut file = match fs::read("../lightning-rapid-gossip-sync/res/full_graph.lngossip") {
344 let error_string = format!(
345 "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{:?}",
347 #[cfg(not(require_route_graph_test))]
349 println!("{}", error_string);
352 #[cfg(require_route_graph_test)]
353 panic!("{}", error_string);
356 rapid_sync.update_network_graph_no_std(&mut file, None).unwrap();