9142b0c8993f37b0a9955ce8863ce948b9d16459
[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 [sync_network_graph_with_file_path]:
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 block_hash = genesis_block(Network::Bitcoin).header.block_hash();
57 //! let network_graph = NetworkGraph::new(block_hash, &logger);
58 //! let rapid_sync = RapidGossipSync::new(&network_graph);
59 //! let new_last_sync_timestamp_result = rapid_sync.sync_network_graph_with_file_path("./rapid_sync.lngossip");
60 //! ```
61 //! [sync_network_graph_with_file_path]: RapidGossipSync::sync_network_graph_with_file_path
62
63 // Allow and import test features for benching
64 #![cfg_attr(all(test, feature = "_bench_unstable"), feature(test))]
65 #[cfg(all(test, feature = "_bench_unstable"))]
66 extern crate test;
67
68 use std::fs::File;
69 use std::ops::Deref;
70 use std::sync::atomic::{AtomicBool, Ordering};
71
72 use lightning::routing::gossip::NetworkGraph;
73 use lightning::util::logger::Logger;
74
75 use crate::error::GraphSyncError;
76
77 /// Error types that these functions can return
78 pub mod error;
79
80 /// Core functionality of this crate
81 pub mod processing;
82
83 /// The main Rapid Gossip Sync object.
84 ///
85 /// See [crate-level documentation] for usage.
86 ///
87 /// [crate-level documentation]: crate
88 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
89 where L::Target: Logger {
90         network_graph: NG,
91         is_initial_sync_complete: AtomicBool
92 }
93
94 impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
95         /// Instantiate a new [`RapidGossipSync`] instance.
96         pub fn new(network_graph: NG) -> Self {
97                 Self {
98                         network_graph,
99                         is_initial_sync_complete: AtomicBool::new(false)
100                 }
101         }
102
103         /// Sync gossip data from a file.
104         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
105         ///
106         /// `network_graph`: The network graph to apply the updates to
107         ///
108         /// `sync_path`: Path to the file where the gossip update data is located
109         ///
110         pub fn sync_network_graph_with_file_path(
111                 &self,
112                 sync_path: &str,
113         ) -> Result<u32, GraphSyncError> {
114                 let mut file = File::open(sync_path)?;
115                 self.update_network_graph_from_byte_stream(&mut file)
116         }
117
118         /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
119         /// [`RapidGossipSync::new`].
120         ///
121         /// (C-not exported) as bindings don't support a reference-to-a-reference yet
122         pub fn network_graph(&self) -> &NG {
123                 &self.network_graph
124         }
125
126         /// Returns whether a rapid gossip sync has completed at least once.
127         pub fn is_initial_sync_complete(&self) -> bool {
128                 self.is_initial_sync_complete.load(Ordering::Acquire)
129         }
130 }
131
132 #[cfg(test)]
133 mod tests {
134         use std::fs;
135
136         use bitcoin::blockdata::constants::genesis_block;
137         use bitcoin::Network;
138
139         use lightning::ln::msgs::DecodeError;
140         use lightning::routing::gossip::NetworkGraph;
141         use lightning::util::test_utils::TestLogger;
142         use crate::RapidGossipSync;
143
144         #[test]
145         fn test_sync_from_file() {
146                 struct FileSyncTest {
147                         directory: String,
148                 }
149
150                 impl FileSyncTest {
151                         fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
152                                 let test = FileSyncTest { directory: tmp_directory.to_owned() };
153
154                                 let graph_sync_test_directory = test.get_test_directory();
155                                 fs::create_dir_all(graph_sync_test_directory).unwrap();
156
157                                 let graph_sync_test_file = test.get_test_file_path();
158                                 fs::write(&graph_sync_test_file, valid_response).unwrap();
159
160                                 test
161                         }
162                         fn get_test_directory(&self) -> String {
163                                 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
164                                 graph_sync_test_directory
165                         }
166                         fn get_test_file_path(&self) -> String {
167                                 let graph_sync_test_directory = self.get_test_directory();
168                                 let graph_sync_test_file = graph_sync_test_directory.to_owned() + "/test_data.lngossip";
169                                 graph_sync_test_file
170                         }
171                 }
172
173                 impl Drop for FileSyncTest {
174                         fn drop(&mut self) {
175                                 fs::remove_dir_all(self.directory.clone()).unwrap();
176                         }
177                 }
178
179                 // same as incremental_only_update_fails_without_prior_same_direction_updates
180                 let valid_response = vec![
181                         76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
182                         79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
183                         0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
184                         187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
185                         157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
186                         88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
187                         204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
188                         181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
189                         110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
190                         76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
191                         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,
192                         0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
193                         0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
194                 ];
195
196                 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
197                 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
198                 let graph_sync_test_file = sync_test.get_test_file_path();
199
200                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
201                 let logger = TestLogger::new();
202                 let network_graph = NetworkGraph::new(block_hash, &logger);
203
204                 assert_eq!(network_graph.read_only().channels().len(), 0);
205
206                 let rapid_sync = RapidGossipSync::new(&network_graph);
207                 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
208
209                 if sync_result.is_err() {
210                         panic!("Unexpected sync result: {:?}", sync_result)
211                 }
212
213                 assert_eq!(network_graph.read_only().channels().len(), 2);
214                 let after = network_graph.to_string();
215                 assert!(
216                         after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
217                 );
218                 assert!(
219                         after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
220                 );
221                 assert!(
222                         after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
223                 );
224                 assert!(
225                         after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
226                 );
227                 assert!(after.contains("619737530008010752"));
228                 assert!(after.contains("783241506229452801"));
229         }
230
231         #[test]
232         fn measure_native_read_from_file() {
233                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
234                 let logger = TestLogger::new();
235                 let network_graph = NetworkGraph::new(block_hash, &logger);
236
237                 assert_eq!(network_graph.read_only().channels().len(), 0);
238
239                 let rapid_sync = RapidGossipSync::new(&network_graph);
240                 let start = std::time::Instant::now();
241                 let sync_result = rapid_sync
242                         .sync_network_graph_with_file_path("./res/full_graph.lngossip");
243                 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
244                         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);
245                         #[cfg(not(require_route_graph_test))]
246                         {
247                                 println!("{}", error_string);
248                                 return;
249                         }
250                         #[cfg(require_route_graph_test)]
251                         panic!("{}", error_string);
252                 }
253                 let elapsed = start.elapsed();
254                 println!("initialization duration: {:?}", elapsed);
255                 if sync_result.is_err() {
256                         panic!("Unexpected sync result: {:?}", sync_result)
257                 }
258         }
259 }
260
261 #[cfg(all(test, feature = "_bench_unstable"))]
262 pub mod bench {
263         use test::Bencher;
264
265         use bitcoin::blockdata::constants::genesis_block;
266         use bitcoin::Network;
267
268         use lightning::ln::msgs::DecodeError;
269         use lightning::routing::gossip::NetworkGraph;
270         use lightning::util::test_utils::TestLogger;
271
272         use crate::RapidGossipSync;
273
274         #[bench]
275         fn bench_reading_full_graph_from_file(b: &mut Bencher) {
276                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
277                 let logger = TestLogger::new();
278                 b.iter(|| {
279                         let network_graph = NetworkGraph::new(block_hash, &logger);
280                         let rapid_sync = RapidGossipSync::new(&network_graph);
281                         let sync_result = rapid_sync.sync_network_graph_with_file_path("./res/full_graph.lngossip");
282                         if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
283                                 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);
284                                 #[cfg(not(require_route_graph_test))]
285                                 {
286                                         println!("{}", error_string);
287                                         return;
288                                 }
289                                 #[cfg(require_route_graph_test)]
290                                 panic!("{}", error_string);
291                         }
292                         assert!(sync_result.is_ok())
293                 });
294         }
295 }