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