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