Make `htlc_maximum_msat` a required field.
[rust-lightning] / lightning-rapid-gossip-sync / src / lib.rs
1 #![deny(missing_docs)]
2 #![deny(unsafe_code)]
3 #![deny(broken_intra_doc_links)]
4 #![deny(non_upper_case_globals)]
5 #![deny(non_camel_case_types)]
6 #![deny(non_snake_case)]
7 #![deny(unused_mut)]
8 #![deny(unused_variables)]
9 #![deny(unused_imports)]
10 //! This crate exposes functionality to rapidly sync gossip data, aimed primarily at mobile
11 //! devices.
12 //!
13 //! The server sends a compressed response containing differential gossip data. The gossip data is
14 //! formatted compactly, omitting signatures and opportunistically incremental where previous
15 //! channel updates are known (a mechanism that is enabled when the timestamp of the last known
16 //! channel update is communicated). A reference server implementation can be found
17 //! [here](https://github.com/lightningdevkit/rapid-gossip-sync-server).
18 //!
19 //! An example server request could look as simple as the following. Note that the first ever rapid
20 //! sync should use `0` for `last_sync_timestamp`:
21 //!
22 //! ```shell
23 //! curl -o rapid_sync.lngossip https://rapidsync.lightningdevkit.org/snapshot/<last_sync_timestamp>
24 //! ```
25 //!
26 //! Then, call the network processing function. In this example, we process the update by reading
27 //! its contents from disk, which we do by calling the `sync_network_graph_with_file_path` method:
28 //!
29 //! ```
30 //! use bitcoin::blockdata::constants::genesis_block;
31 //! use bitcoin::Network;
32 //! use lightning::routing::gossip::NetworkGraph;
33 //! use lightning_rapid_gossip_sync::RapidGossipSync;
34 //!
35 //! # use lightning::util::logger::{Logger, Record};
36 //! # struct FakeLogger {}
37 //! # impl Logger for FakeLogger {
38 //! #     fn log(&self, record: &Record) { unimplemented!() }
39 //! # }
40 //! # let logger = FakeLogger {};
41 //!
42 //! let block_hash = genesis_block(Network::Bitcoin).header.block_hash();
43 //! let network_graph = NetworkGraph::new(block_hash, &logger);
44 //! let rapid_sync = RapidGossipSync::new(&network_graph);
45 //! let new_last_sync_timestamp_result = rapid_sync.sync_network_graph_with_file_path("./rapid_sync.lngossip");
46 //! ```
47 //!
48 //! The primary benefit this syncing mechanism provides is that given a trusted server, a
49 //! low-powered client can offload the validation of gossip signatures. This enables a client to
50 //! privately calculate routes for payments, and do so much faster and earlier than requiring a full
51 //! peer-to-peer gossip sync to complete.
52 //!
53 //! The reason the rapid sync server requires trust is that it could provide bogus data, though at
54 //! worst, all that would result in is a fake network topology, which wouldn't enable the server to
55 //! steal or siphon off funds. It could, however, reduce the client's privacy by forcing all
56 //! payments to be routed via channels the server controls.
57 //!
58 //! The way a server is meant to calculate this rapid gossip sync data is by using a `latest_seen`
59 //! timestamp provided by the client. It's not included in either channel announcement or update,
60 //! (not least due to announcements not including any timestamps at all, but only a block height)
61 //! but rather, it's a timestamp of when the server saw a particular message.
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 /// Rapid Gossip Sync struct
84 /// See [crate-level documentation] for usage.
85 ///
86 /// [crate-level documentation]: crate
87 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
88 where L::Target: Logger {
89         network_graph: NG,
90         is_initial_sync_complete: AtomicBool
91 }
92
93 impl<NG: Deref<Target=NetworkGraph<L>>, L: Deref> RapidGossipSync<NG, L> where L::Target: Logger {
94         /// Instantiate a new [`RapidGossipSync`] instance
95         pub fn new(network_graph: NG) -> Self {
96                 Self {
97                         network_graph,
98                         is_initial_sync_complete: AtomicBool::new(false)
99                 }
100         }
101
102         /// Sync gossip data from a file
103         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
104         ///
105         /// `network_graph`: The network graph to apply the updates to
106         ///
107         /// `sync_path`: Path to the file where the gossip update data is located
108         ///
109         pub fn sync_network_graph_with_file_path(
110                 &self,
111                 sync_path: &str,
112         ) -> Result<u32, GraphSyncError> {
113                 let mut file = File::open(sync_path)?;
114                 self.update_network_graph_from_byte_stream(&mut file)
115         }
116
117         /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
118         /// [`RapidGossipSync::new`].
119         ///
120         /// (C-not exported) as bindings don't support a reference-to-a-reference yet
121         pub fn network_graph(&self) -> &NG {
122                 &self.network_graph
123         }
124
125         /// Returns whether a rapid gossip sync has completed at least once
126         pub fn is_initial_sync_complete(&self) -> bool {
127                 self.is_initial_sync_complete.load(Ordering::Acquire)
128         }
129 }
130
131 #[cfg(test)]
132 mod tests {
133         use std::fs;
134
135         use bitcoin::blockdata::constants::genesis_block;
136         use bitcoin::Network;
137
138         use lightning::ln::msgs::DecodeError;
139         use lightning::routing::gossip::NetworkGraph;
140         use lightning::util::test_utils::TestLogger;
141         use crate::RapidGossipSync;
142
143         #[test]
144         fn test_sync_from_file() {
145                 struct FileSyncTest {
146                         directory: String,
147                 }
148
149                 impl FileSyncTest {
150                         fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
151                                 let test = FileSyncTest { directory: tmp_directory.to_owned() };
152
153                                 let graph_sync_test_directory = test.get_test_directory();
154                                 fs::create_dir_all(graph_sync_test_directory).unwrap();
155
156                                 let graph_sync_test_file = test.get_test_file_path();
157                                 fs::write(&graph_sync_test_file, valid_response).unwrap();
158
159                                 test
160                         }
161                         fn get_test_directory(&self) -> String {
162                                 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
163                                 graph_sync_test_directory
164                         }
165                         fn get_test_file_path(&self) -> String {
166                                 let graph_sync_test_directory = self.get_test_directory();
167                                 let graph_sync_test_file = graph_sync_test_directory.to_owned() + "/test_data.lngossip";
168                                 graph_sync_test_file
169                         }
170                 }
171
172                 impl Drop for FileSyncTest {
173                         fn drop(&mut self) {
174                                 fs::remove_dir_all(self.directory.clone()).unwrap();
175                         }
176                 }
177
178                 // same as incremental_only_update_fails_without_prior_same_direction_updates
179                 let valid_response = vec![
180                         76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
181                         79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
182                         0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
183                         187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
184                         157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
185                         88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
186                         204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
187                         181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
188                         110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
189                         76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
190                         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,
191                         0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
192                         0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
193                 ];
194
195                 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
196                 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
197                 let graph_sync_test_file = sync_test.get_test_file_path();
198
199                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
200                 let logger = TestLogger::new();
201                 let network_graph = NetworkGraph::new(block_hash, &logger);
202
203                 assert_eq!(network_graph.read_only().channels().len(), 0);
204
205                 let rapid_sync = RapidGossipSync::new(&network_graph);
206                 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
207
208                 if sync_result.is_err() {
209                         panic!("Unexpected sync result: {:?}", sync_result)
210                 }
211
212                 assert_eq!(network_graph.read_only().channels().len(), 2);
213                 let after = network_graph.to_string();
214                 assert!(
215                         after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
216                 );
217                 assert!(
218                         after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
219                 );
220                 assert!(
221                         after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
222                 );
223                 assert!(
224                         after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
225                 );
226                 assert!(after.contains("619737530008010752"));
227                 assert!(after.contains("783241506229452801"));
228         }
229
230         #[test]
231         fn measure_native_read_from_file() {
232                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
233                 let logger = TestLogger::new();
234                 let network_graph = NetworkGraph::new(block_hash, &logger);
235
236                 assert_eq!(network_graph.read_only().channels().len(), 0);
237
238                 let rapid_sync = RapidGossipSync::new(&network_graph);
239                 let start = std::time::Instant::now();
240                 let sync_result = rapid_sync
241                         .sync_network_graph_with_file_path("./res/full_graph.lngossip");
242                 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
243                         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);
244                         #[cfg(not(require_route_graph_test))]
245                         {
246                                 println!("{}", error_string);
247                                 return;
248                         }
249                         #[cfg(require_route_graph_test)]
250                         panic!("{}", error_string);
251                 }
252                 let elapsed = start.elapsed();
253                 println!("initialization duration: {:?}", elapsed);
254                 if sync_result.is_err() {
255                         panic!("Unexpected sync result: {:?}", sync_result)
256                 }
257         }
258 }
259
260 #[cfg(all(test, feature = "_bench_unstable"))]
261 pub mod bench {
262         use test::Bencher;
263
264         use bitcoin::blockdata::constants::genesis_block;
265         use bitcoin::Network;
266
267         use lightning::ln::msgs::DecodeError;
268         use lightning::routing::gossip::NetworkGraph;
269         use lightning::util::test_utils::TestLogger;
270
271         use crate::RapidGossipSync;
272
273         #[bench]
274         fn bench_reading_full_graph_from_file(b: &mut Bencher) {
275                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
276                 let logger = TestLogger::new();
277                 b.iter(|| {
278                         let network_graph = NetworkGraph::new(block_hash, &logger);
279                         let rapid_sync = RapidGossipSync::new(&network_graph);
280                         let sync_result = rapid_sync.sync_network_graph_with_file_path("./res/full_graph.lngossip");
281                         if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
282                                 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);
283                                 #[cfg(not(require_route_graph_test))]
284                                 {
285                                         println!("{}", error_string);
286                                         return;
287                                 }
288                                 #[cfg(require_route_graph_test)]
289                                 panic!("{}", error_string);
290                         }
291                         assert!(sync_result.is_ok())
292                 });
293         }
294 }