Merge pull request #2779 from G8XSU/2706-stop
[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::routing::gossip::NetworkGraph;
78 use lightning::util::logger::Logger;
79
80 pub use crate::error::GraphSyncError;
81
82 /// Error types that these functions can return
83 mod error;
84
85 /// Core functionality of this crate
86 mod processing;
87
88 /// The main Rapid Gossip Sync object.
89 ///
90 /// See [crate-level documentation] for usage.
91 ///
92 /// [crate-level documentation]: crate
93 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
94 where L::Target: Logger {
95         network_graph: NG,
96         logger: L,
97         is_initial_sync_complete: AtomicBool
98 }
99
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 {
103                 Self {
104                         network_graph,
105                         logger,
106                         is_initial_sync_complete: AtomicBool::new(false)
107                 }
108         }
109
110         /// Sync gossip data from a file.
111         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
112         ///
113         /// `network_graph`: The network graph to apply the updates to
114         ///
115         /// `sync_path`: Path to the file where the gossip update data is located
116         ///
117         #[cfg(feature = "std")]
118         pub fn sync_network_graph_with_file_path(
119                 &self,
120                 sync_path: &str,
121         ) -> Result<u32, GraphSyncError> {
122                 let mut file = File::open(sync_path)?;
123                 self.update_network_graph_from_byte_stream(&mut file)
124         }
125
126         /// Update network graph from binary data.
127         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
128         ///
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)
134         }
135
136         /// Update network graph from binary data.
137         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
138         ///
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)
144         }
145
146         /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
147         /// [`RapidGossipSync::new`].
148         ///
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 {
151                 &self.network_graph
152         }
153
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)
157         }
158 }
159
160 #[cfg(feature = "std")]
161 #[cfg(test)]
162 mod tests {
163         use std::fs;
164
165         use bitcoin::Network;
166
167         use lightning::ln::msgs::DecodeError;
168         use lightning::routing::gossip::NetworkGraph;
169         use lightning::util::test_utils::TestLogger;
170         use crate::RapidGossipSync;
171
172         #[test]
173         fn test_sync_from_file() {
174                 struct FileSyncTest {
175                         directory: String,
176                 }
177
178                 impl FileSyncTest {
179                         fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
180                                 let test = FileSyncTest { directory: tmp_directory.to_owned() };
181
182                                 let graph_sync_test_directory = test.get_test_directory();
183                                 fs::create_dir_all(graph_sync_test_directory).unwrap();
184
185                                 let graph_sync_test_file = test.get_test_file_path();
186                                 fs::write(graph_sync_test_file, valid_response).unwrap();
187
188                                 test
189                         }
190
191                         fn get_test_directory(&self) -> String {
192                                 self.directory.clone() + "/graph-sync-tests"
193                         }
194
195                         fn get_test_file_path(&self) -> String {
196                                 self.get_test_directory() + "/test_data.lngossip"
197                         }
198                 }
199
200                 impl Drop for FileSyncTest {
201                         fn drop(&mut self) {
202                                 fs::remove_dir_all(self.directory.clone()).unwrap();
203                         }
204                 }
205
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,
221                 ];
222
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();
226
227                 let logger = TestLogger::new();
228                 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
229
230                 assert_eq!(network_graph.read_only().channels().len(), 0);
231
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);
234
235                 if sync_result.is_err() {
236                         panic!("Unexpected sync result: {:?}", sync_result)
237                 }
238
239                 assert_eq!(network_graph.read_only().channels().len(), 2);
240                 let after = network_graph.to_string();
241                 assert!(
242                         after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
243                 );
244                 assert!(
245                         after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
246                 );
247                 assert!(
248                         after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
249                 );
250                 assert!(
251                         after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
252                 );
253                 assert!(after.contains("619737530008010752"));
254                 assert!(after.contains("783241506229452801"));
255         }
256
257         #[test]
258         fn measure_native_read_from_file() {
259                 let logger = TestLogger::new();
260                 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
261
262                 assert_eq!(network_graph.read_only().channels().len(), 0);
263
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))]
271                         {
272                                 println!("{}", error_string);
273                                 return;
274                         }
275                         #[cfg(require_route_graph_test)]
276                         panic!("{}", error_string);
277                 }
278                 let elapsed = start.elapsed();
279                 println!("initialization duration: {:?}", elapsed);
280                 if sync_result.is_err() {
281                         panic!("Unexpected sync result: {:?}", sync_result)
282                 }
283         }
284 }
285
286 #[cfg(ldk_bench)]
287 /// Benches
288 pub mod bench {
289         use bitcoin::Network;
290
291         use criterion::Criterion;
292
293         use std::fs;
294
295         use lightning::routing::gossip::NetworkGraph;
296         use lightning::util::test_utils::TestLogger;
297
298         use crate::RapidGossipSync;
299
300         /// Bench!
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") {
307                                 Ok(f) => f,
308                                 Err(io_error) => {
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{:?}",
311                                                 io_error);
312                                         #[cfg(not(require_route_graph_test))]
313                                         {
314                                                 println!("{}", error_string);
315                                                 return;
316                                         }
317                                         #[cfg(require_route_graph_test)]
318                                         panic!("{}", error_string);
319                                 },
320                         };
321                         rapid_sync.update_network_graph_no_std(&mut file, None).unwrap();
322                 }));
323         }
324 }