Remove genesis block hash from public API
[rust-lightning] / lightning-rapid-gossip-sync / src / lib.rs
1 // Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings.
2 #![deny(broken_intra_doc_links)]
3 #![deny(private_intra_doc_links)]
4
5 #![deny(missing_docs)]
6 #![deny(unsafe_code)]
7 #![deny(non_upper_case_globals)]
8 #![deny(non_camel_case_types)]
9 #![deny(non_snake_case)]
10 #![deny(unused_mut)]
11 #![deny(unused_variables)]
12 #![deny(unused_imports)]
13 //! This crate exposes client functionality to rapidly sync gossip data, aimed primarily at mobile
14 //! devices.
15 //!
16 //! The rapid gossip sync server will provide a compressed response containing differential gossip
17 //! data. The gossip data is formatted compactly, omitting signatures and opportunistically
18 //! incremental where previous channel updates are known. This mechanism is enabled when the
19 //! timestamp of the last known channel update is communicated. A reference server implementation
20 //! can be found [on Github](https://github.com/lightningdevkit/rapid-gossip-sync-server).
21 //!
22 //! The primary benefit of this syncing mechanism is that it allows a low-powered client to offload
23 //! the validation of gossip signatures to a semi-trusted server. This enables the client to
24 //! privately calculate routes for payments, and to do so much faster than requiring a full
25 //! peer-to-peer gossip sync to complete.
26 //!
27 //! The server calculates its response on the basis of a client-provided `latest_seen` timestamp,
28 //! i.e., the server will return all rapid gossip sync data it has seen after the given timestamp.
29 //!
30 //! # Getting Started
31 //! Firstly, the data needs to be retrieved from the server. For example, you could use the server
32 //! at <https://rapidsync.lightningdevkit.org> with the following request format:
33 //!
34 //! ```shell
35 //! curl -o rapid_sync.lngossip https://rapidsync.lightningdevkit.org/snapshot/<last_sync_timestamp>
36 //! ```
37 //! Note that the first ever rapid sync should use `0` for `last_sync_timestamp`.
38 //!
39 //! After the gossip data snapshot has been downloaded, one of the client's graph processing
40 //! functions needs to be called. In this example, we process the update by reading its contents
41 //! from disk, which we do by calling [`RapidGossipSync::update_network_graph`]:
42 //!
43 //! ```
44 //! use bitcoin::blockdata::constants::genesis_block;
45 //! use bitcoin::Network;
46 //! use lightning::routing::gossip::NetworkGraph;
47 //! use lightning_rapid_gossip_sync::RapidGossipSync;
48 //!
49 //! # use lightning::util::logger::{Logger, Record};
50 //! # struct FakeLogger {}
51 //! # impl Logger for FakeLogger {
52 //! #     fn log(&self, record: &Record) { unimplemented!() }
53 //! # }
54 //! # let logger = FakeLogger {};
55 //!
56 //! let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
57 //! let rapid_sync = RapidGossipSync::new(&network_graph);
58 //! let snapshot_contents: &[u8] = &[0; 0];
59 //! let new_last_sync_timestamp_result = rapid_sync.update_network_graph(snapshot_contents);
60 //! ```
61
62 #![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
63
64 // Allow and import test features for benching
65 #![cfg_attr(all(test, feature = "_bench_unstable"), feature(test))]
66 #[cfg(all(test, feature = "_bench_unstable"))]
67 extern crate test;
68
69 #[cfg(not(feature = "std"))]
70 extern crate alloc;
71
72 #[cfg(feature = "std")]
73 use std::fs::File;
74 use core::ops::Deref;
75 use core::sync::atomic::{AtomicBool, Ordering};
76
77 use lightning::io;
78 use lightning::routing::gossip::NetworkGraph;
79 use lightning::util::logger::Logger;
80
81 pub use crate::error::GraphSyncError;
82
83 /// Error types that these functions can return
84 mod error;
85
86 /// Core functionality of this crate
87 mod processing;
88
89 /// The main Rapid Gossip Sync object.
90 ///
91 /// See [crate-level documentation] for usage.
92 ///
93 /// [crate-level documentation]: crate
94 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
95 where L::Target: Logger {
96         network_graph: NG,
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) -> Self {
103                 Self {
104                         network_graph,
105                         is_initial_sync_complete: AtomicBool::new(false)
106                 }
107         }
108
109         /// Sync gossip data from a file.
110         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
111         ///
112         /// `network_graph`: The network graph to apply the updates to
113         ///
114         /// `sync_path`: Path to the file where the gossip update data is located
115         ///
116         #[cfg(feature = "std")]
117         pub fn sync_network_graph_with_file_path(
118                 &self,
119                 sync_path: &str,
120         ) -> Result<u32, GraphSyncError> {
121                 let mut file = File::open(sync_path)?;
122                 self.update_network_graph_from_byte_stream(&mut file)
123         }
124
125         /// Update network graph from binary data.
126         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
127         ///
128         /// `update_data`: `&[u8]` binary stream that comprises the update data
129         pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
130                 let mut read_cursor = io::Cursor::new(update_data);
131                 self.update_network_graph_from_byte_stream(&mut read_cursor)
132         }
133
134         /// Update network graph from binary data.
135         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
136         ///
137         /// `update_data`: `&[u8]` binary stream that comprises the update data
138         /// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
139         pub fn update_network_graph_no_std(&self, update_data: &[u8], current_time_unix: Option<u64>) -> Result<u32, GraphSyncError> {
140                 let mut read_cursor = io::Cursor::new(update_data);
141                 self.update_network_graph_from_byte_stream_no_std(&mut read_cursor, current_time_unix)
142         }
143
144         /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
145         /// [`RapidGossipSync::new`].
146         ///
147         /// (C-not exported) as bindings don't support a reference-to-a-reference yet
148         pub fn network_graph(&self) -> &NG {
149                 &self.network_graph
150         }
151
152         /// Returns whether a rapid gossip sync has completed at least once.
153         pub fn is_initial_sync_complete(&self) -> bool {
154                 self.is_initial_sync_complete.load(Ordering::Acquire)
155         }
156 }
157
158 #[cfg(feature = "std")]
159 #[cfg(test)]
160 mod tests {
161         use std::fs;
162
163         use bitcoin::Network;
164
165         use lightning::ln::msgs::DecodeError;
166         use lightning::routing::gossip::NetworkGraph;
167         use lightning::util::test_utils::TestLogger;
168         use crate::RapidGossipSync;
169
170         #[test]
171         fn test_sync_from_file() {
172                 struct FileSyncTest {
173                         directory: String,
174                 }
175
176                 impl FileSyncTest {
177                         fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
178                                 let test = FileSyncTest { directory: tmp_directory.to_owned() };
179
180                                 let graph_sync_test_directory = test.get_test_directory();
181                                 fs::create_dir_all(graph_sync_test_directory).unwrap();
182
183                                 let graph_sync_test_file = test.get_test_file_path();
184                                 fs::write(&graph_sync_test_file, valid_response).unwrap();
185
186                                 test
187                         }
188                         fn get_test_directory(&self) -> String {
189                                 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
190                                 graph_sync_test_directory
191                         }
192                         fn get_test_file_path(&self) -> String {
193                                 let graph_sync_test_directory = self.get_test_directory();
194                                 let graph_sync_test_file = graph_sync_test_directory.to_owned() + "/test_data.lngossip";
195                                 graph_sync_test_file
196                         }
197                 }
198
199                 impl Drop for FileSyncTest {
200                         fn drop(&mut self) {
201                                 fs::remove_dir_all(self.directory.clone()).unwrap();
202                         }
203                 }
204
205                 // same as incremental_only_update_fails_without_prior_same_direction_updates
206                 let valid_response = vec![
207                         76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
208                         79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
209                         0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
210                         187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
211                         157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
212                         88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
213                         204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
214                         181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
215                         110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
216                         76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
217                         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,
218                         0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
219                         0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
220                 ];
221
222                 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
223                 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
224                 let graph_sync_test_file = sync_test.get_test_file_path();
225
226                 let logger = TestLogger::new();
227                 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
228
229                 assert_eq!(network_graph.read_only().channels().len(), 0);
230
231                 let rapid_sync = RapidGossipSync::new(&network_graph);
232                 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
233
234                 if sync_result.is_err() {
235                         panic!("Unexpected sync result: {:?}", sync_result)
236                 }
237
238                 assert_eq!(network_graph.read_only().channels().len(), 2);
239                 let after = network_graph.to_string();
240                 assert!(
241                         after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
242                 );
243                 assert!(
244                         after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
245                 );
246                 assert!(
247                         after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
248                 );
249                 assert!(
250                         after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
251                 );
252                 assert!(after.contains("619737530008010752"));
253                 assert!(after.contains("783241506229452801"));
254         }
255
256         #[test]
257         fn measure_native_read_from_file() {
258                 let logger = TestLogger::new();
259                 let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
260
261                 assert_eq!(network_graph.read_only().channels().len(), 0);
262
263                 let rapid_sync = RapidGossipSync::new(&network_graph);
264                 let start = std::time::Instant::now();
265                 let sync_result = rapid_sync
266                         .sync_network_graph_with_file_path("./res/full_graph.lngossip");
267                 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
268                         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);
269                         #[cfg(not(require_route_graph_test))]
270                         {
271                                 println!("{}", error_string);
272                                 return;
273                         }
274                         #[cfg(require_route_graph_test)]
275                         panic!("{}", error_string);
276                 }
277                 let elapsed = start.elapsed();
278                 println!("initialization duration: {:?}", elapsed);
279                 if sync_result.is_err() {
280                         panic!("Unexpected sync result: {:?}", sync_result)
281                 }
282         }
283 }
284
285 #[cfg(all(test, feature = "_bench_unstable"))]
286 pub mod bench {
287         use test::Bencher;
288
289         use bitcoin::Network;
290
291         use lightning::ln::msgs::DecodeError;
292         use lightning::routing::gossip::NetworkGraph;
293         use lightning::util::test_utils::TestLogger;
294
295         use crate::RapidGossipSync;
296
297         #[bench]
298         fn bench_reading_full_graph_from_file(b: &mut Bencher) {
299                 let logger = TestLogger::new();
300                 b.iter(|| {
301                         let network_graph = NetworkGraph::new(Network::Bitcoin, &logger);
302                         let rapid_sync = RapidGossipSync::new(&network_graph);
303                         let sync_result = rapid_sync.sync_network_graph_with_file_path("./res/full_graph.lngossip");
304                         if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
305                                 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-bc08df7542-2022-05-05.bin\n\n{:?}", io_error);
306                                 #[cfg(not(require_route_graph_test))]
307                                 {
308                                         println!("{}", error_string);
309                                         return;
310                                 }
311                                 #[cfg(require_route_graph_test)]
312                                 panic!("{}", error_string);
313                         }
314                         assert!(sync_result.is_ok())
315                 });
316         }
317 }