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