Drop manual `Debug` impl on RGS' `GraphSyncError`
[rust-lightning] / lightning-rapid-gossip-sync / src / lib.rs
1 #![deny(rustdoc::broken_intra_doc_links)]
2 #![deny(rustdoc::private_intra_doc_links)]
3
4 #![deny(missing_docs)]
5 #![deny(unsafe_code)]
6 #![deny(non_upper_case_globals)]
7 #![deny(non_camel_case_types)]
8 #![deny(non_snake_case)]
9 #![deny(unused_mut)]
10 #![deny(unused_variables)]
11 #![deny(unused_imports)]
12 //! This crate exposes client functionality to rapidly sync gossip data, aimed primarily at mobile
13 //! devices.
14 //!
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).
20 //!
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.
25 //!
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.
28 //!
29 //! # Getting Started
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:
32 //!
33 //! ```shell
34 //! curl -o rapid_sync.lngossip https://rapidsync.lightningdevkit.org/snapshot/<last_sync_timestamp>
35 //! ```
36 //! Note that the first ever rapid sync should use `0` for `last_sync_timestamp`.
37 //!
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`]:
41 //!
42 //! ```
43 //! use bitcoin::blockdata::constants::genesis_block;
44 //! use bitcoin::Network;
45 //! use lightning::routing::gossip::NetworkGraph;
46 //! use lightning_rapid_gossip_sync::RapidGossipSync;
47 //!
48 //! # use lightning::util::logger::{Logger, Record};
49 //! # struct FakeLogger {}
50 //! # impl Logger for FakeLogger {
51 //! #     fn log(&self, record: Record) { }
52 //! # }
53 //! # let logger = FakeLogger {};
54 //!
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));
62 //! ```
63
64 #![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
65
66 #[cfg(ldk_bench)] extern crate criterion;
67
68 #[cfg(not(feature = "std"))]
69 extern crate alloc;
70
71 #[cfg(feature = "std")]
72 use std::fs::File;
73 use core::ops::Deref;
74 use core::sync::atomic::{AtomicBool, Ordering};
75
76 use lightning::io;
77 use lightning::ln::msgs::{DecodeError, LightningError};
78 use lightning::routing::gossip::NetworkGraph;
79 use lightning::util::logger::Logger;
80
81 /// Core functionality of this crate
82 mod processing;
83
84 /// All-encompassing standard error type that processing can return
85 #[derive(Debug)]
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),
93 }
94
95 impl From<lightning::io::Error> for GraphSyncError {
96         fn from(error: lightning::io::Error) -> Self {
97                 Self::DecodeError(DecodeError::Io(error.kind()))
98         }
99 }
100
101 impl From<DecodeError> for GraphSyncError {
102         fn from(error: DecodeError) -> Self {
103                 Self::DecodeError(error)
104         }
105 }
106
107 impl From<LightningError> for GraphSyncError {
108         fn from(error: LightningError) -> Self {
109                 Self::LightningError(error)
110         }
111 }
112
113 /// The main Rapid Gossip Sync object.
114 ///
115 /// See [crate-level documentation] for usage.
116 ///
117 /// [crate-level documentation]: crate
118 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
119 where L::Target: Logger {
120         network_graph: NG,
121         logger: L,
122         is_initial_sync_complete: AtomicBool
123 }
124
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 {
128                 Self {
129                         network_graph,
130                         logger,
131                         is_initial_sync_complete: AtomicBool::new(false)
132                 }
133         }
134
135         /// Sync gossip data from a file.
136         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
137         ///
138         /// `network_graph`: The network graph to apply the updates to
139         ///
140         /// `sync_path`: Path to the file where the gossip update data is located
141         ///
142         #[cfg(feature = "std")]
143         pub fn sync_network_graph_with_file_path(
144                 &self,
145                 sync_path: &str,
146         ) -> Result<u32, GraphSyncError> {
147                 let mut file = File::open(sync_path)?;
148                 self.update_network_graph_from_byte_stream(&mut file)
149         }
150
151         /// Update network graph from binary data.
152         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
153         ///
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)
159         }
160
161         /// Update network graph from binary data.
162         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
163         ///
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)
169         }
170
171         /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
172         /// [`RapidGossipSync::new`].
173         ///
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 {
176                 &self.network_graph
177         }
178
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)
182         }
183 }
184
185 #[cfg(feature = "std")]
186 #[cfg(test)]
187 mod tests {
188         use std::fs;
189
190         use bitcoin::Network;
191
192         use lightning::ln::msgs::DecodeError;
193         use lightning::routing::gossip::NetworkGraph;
194         use lightning::util::test_utils::TestLogger;
195         use crate::{GraphSyncError, RapidGossipSync};
196
197         #[test]
198         fn test_sync_from_file() {
199                 struct FileSyncTest {
200                         directory: String,
201                 }
202
203                 impl FileSyncTest {
204                         fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
205                                 let test = FileSyncTest { directory: tmp_directory.to_owned() };
206
207                                 let graph_sync_test_directory = test.get_test_directory();
208                                 fs::create_dir_all(graph_sync_test_directory).unwrap();
209
210                                 let graph_sync_test_file = test.get_test_file_path();
211                                 fs::write(graph_sync_test_file, valid_response).unwrap();
212
213                                 test
214                         }
215
216                         fn get_test_directory(&self) -> String {
217                                 self.directory.clone() + "/graph-sync-tests"
218                         }
219
220                         fn get_test_file_path(&self) -> String {
221                                 self.get_test_directory() + "/test_data.lngossip"
222                         }
223                 }
224
225                 impl Drop for FileSyncTest {
226                         fn drop(&mut self) {
227                                 fs::remove_dir_all(self.directory.clone()).unwrap();
228                         }
229                 }
230
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,
246                 ];
247
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();
251
252                 let logger = TestLogger::new();
253                 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
254
255                 assert_eq!(network_graph.read_only().channels().len(), 0);
256
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);
259
260                 if sync_result.is_err() {
261                         panic!("Unexpected sync result: {:?}", sync_result)
262                 }
263
264                 assert_eq!(network_graph.read_only().channels().len(), 2);
265                 let after = network_graph.to_string();
266                 assert!(
267                         after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
268                 );
269                 assert!(
270                         after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
271                 );
272                 assert!(
273                         after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
274                 );
275                 assert!(
276                         after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
277                 );
278                 assert!(after.contains("619737530008010752"));
279                 assert!(after.contains("783241506229452801"));
280         }
281
282         #[test]
283         fn measure_native_read_from_file() {
284                 let logger = TestLogger::new();
285                 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
286
287                 assert_eq!(network_graph.read_only().channels().len(), 0);
288
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))]
296                         {
297                                 println!("{}", error_string);
298                                 return;
299                         }
300                         #[cfg(require_route_graph_test)]
301                         panic!("{}", error_string);
302                 }
303                 let elapsed = start.elapsed();
304                 println!("initialization duration: {:?}", elapsed);
305                 if sync_result.is_err() {
306                         panic!("Unexpected sync result: {:?}", sync_result)
307                 }
308         }
309 }
310
311 #[cfg(ldk_bench)]
312 /// Benches
313 pub mod bench {
314         use bitcoin::Network;
315
316         use criterion::Criterion;
317
318         use std::fs;
319
320         use lightning::routing::gossip::NetworkGraph;
321         use lightning::util::test_utils::TestLogger;
322
323         use crate::RapidGossipSync;
324
325         /// Bench!
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") {
332                                 Ok(f) => f,
333                                 Err(io_error) => {
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{:?}",
336                                                 io_error);
337                                         #[cfg(not(require_route_graph_test))]
338                                         {
339                                                 println!("{}", error_string);
340                                                 return;
341                                         }
342                                         #[cfg(require_route_graph_test)]
343                                         panic!("{}", error_string);
344                                 },
345                         };
346                         rapid_sync.update_network_graph_no_std(&mut file, None).unwrap();
347                 }));
348         }
349 }