e2e7807e398b48b327b63f6b5fec49a5724464df
[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::network_graph::NetworkGraph;
33 //! use lightning_rapid_gossip_sync::RapidGossipSync;
34 //!
35 //! let block_hash = genesis_block(Network::Bitcoin).header.block_hash();
36 //! let network_graph = NetworkGraph::new(block_hash);
37 //! let rapid_sync = RapidGossipSync::new(&network_graph);
38 //! let new_last_sync_timestamp_result = rapid_sync.sync_network_graph_with_file_path("./rapid_sync.lngossip");
39 //! ```
40 //!
41 //! The primary benefit this syncing mechanism provides is that given a trusted server, a
42 //! low-powered client can offload the validation of gossip signatures. This enables a client to
43 //! privately calculate routes for payments, and do so much faster and earlier than requiring a full
44 //! peer-to-peer gossip sync to complete.
45 //!
46 //! The reason the rapid sync server requires trust is that it could provide bogus data, though at
47 //! worst, all that would result in is a fake network topology, which wouldn't enable the server to
48 //! steal or siphon off funds. It could, however, reduce the client's privacy by forcing all
49 //! payments to be routed via channels the server controls.
50 //!
51 //! The way a server is meant to calculate this rapid gossip sync data is by using a `latest_seen`
52 //! timestamp provided by the client. It's not included in either channel announcement or update,
53 //! (not least due to announcements not including any timestamps at all, but only a block height)
54 //! but rather, it's a timestamp of when the server saw a particular message.
55
56 // Allow and import test features for benching
57 #![cfg_attr(all(test, feature = "_bench_unstable"), feature(test))]
58 #[cfg(all(test, feature = "_bench_unstable"))]
59 extern crate test;
60
61 use std::fs::File;
62 use std::ops::Deref;
63 use std::sync::atomic::{AtomicBool, Ordering};
64
65 use lightning::routing::network_graph::NetworkGraph;
66
67 use crate::error::GraphSyncError;
68
69 /// Error types that these functions can return
70 pub mod error;
71
72 /// Core functionality of this crate
73 pub mod processing;
74
75 /// Rapid Gossip Sync struct
76 /// See [crate-level documentation] for usage.
77 ///
78 /// [crate-level documentation]: crate
79 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph>> {
80         network_graph: NG,
81         is_initial_sync_complete: AtomicBool
82 }
83
84 impl<NG: Deref<Target=NetworkGraph>> RapidGossipSync<NG> {
85         /// Instantiate a new [`RapidGossipSync`] instance
86         pub fn new(network_graph: NG) -> Self {
87                 Self {
88                         network_graph,
89                         is_initial_sync_complete: AtomicBool::new(false)
90                 }
91         }
92
93         /// Sync gossip data from a file
94         /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
95         ///
96         /// `network_graph`: The network graph to apply the updates to
97         ///
98         /// `sync_path`: Path to the file where the gossip update data is located
99         ///
100         pub fn sync_network_graph_with_file_path(
101                 &self,
102                 sync_path: &str,
103         ) -> Result<u32, GraphSyncError> {
104                 let mut file = File::open(sync_path)?;
105                 self.update_network_graph_from_byte_stream(&mut file)
106         }
107
108         /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
109         /// [`RapidGossipSync::new`].
110         ///
111         /// (C-not exported) as bindings don't support a reference-to-a-reference yet
112         pub fn network_graph(&self) -> &NG {
113                 &self.network_graph
114         }
115
116         /// Returns whether a rapid gossip sync has completed at least once
117         pub fn is_initial_sync_complete(&self) -> bool {
118                 self.is_initial_sync_complete.load(Ordering::Acquire)
119         }
120 }
121
122 #[cfg(test)]
123 mod tests {
124         use std::fs;
125
126         use bitcoin::blockdata::constants::genesis_block;
127         use bitcoin::Network;
128
129         use lightning::ln::msgs::DecodeError;
130         use lightning::routing::network_graph::NetworkGraph;
131         use crate::RapidGossipSync;
132
133         #[test]
134         fn test_sync_from_file() {
135                 struct FileSyncTest {
136                         directory: String,
137                 }
138
139                 impl FileSyncTest {
140                         fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
141                                 let test = FileSyncTest { directory: tmp_directory.to_owned() };
142
143                                 let graph_sync_test_directory = test.get_test_directory();
144                                 fs::create_dir_all(graph_sync_test_directory).unwrap();
145
146                                 let graph_sync_test_file = test.get_test_file_path();
147                                 fs::write(&graph_sync_test_file, valid_response).unwrap();
148
149                                 test
150                         }
151                         fn get_test_directory(&self) -> String {
152                                 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
153                                 graph_sync_test_directory
154                         }
155                         fn get_test_file_path(&self) -> String {
156                                 let graph_sync_test_directory = self.get_test_directory();
157                                 let graph_sync_test_file = graph_sync_test_directory.to_owned() + "/test_data.lngossip";
158                                 graph_sync_test_file
159                         }
160                 }
161
162                 impl Drop for FileSyncTest {
163                         fn drop(&mut self) {
164                                 fs::remove_dir_all(self.directory.clone()).unwrap();
165                         }
166                 }
167
168                 // same as incremental_only_update_fails_without_prior_same_direction_updates
169                 let valid_response = vec![
170                         76, 68, 75, 1, 111, 226, 140, 10, 182, 241, 179, 114, 193, 166, 162, 70, 174, 99, 247,
171                         79, 147, 30, 131, 101, 225, 90, 8, 156, 104, 214, 25, 0, 0, 0, 0, 0, 97, 227, 98, 218,
172                         0, 0, 0, 4, 2, 22, 7, 207, 206, 25, 164, 197, 231, 230, 231, 56, 102, 61, 250, 251,
173                         187, 172, 38, 46, 79, 247, 108, 44, 155, 48, 219, 238, 252, 53, 192, 6, 67, 2, 36, 125,
174                         157, 176, 223, 175, 234, 116, 94, 248, 201, 225, 97, 235, 50, 47, 115, 172, 63, 136,
175                         88, 216, 115, 11, 111, 217, 114, 84, 116, 124, 231, 107, 2, 158, 1, 242, 121, 152, 106,
176                         204, 131, 186, 35, 93, 70, 216, 10, 237, 224, 183, 89, 95, 65, 3, 83, 185, 58, 138,
177                         181, 64, 187, 103, 127, 68, 50, 2, 201, 19, 17, 138, 136, 149, 185, 226, 156, 137, 175,
178                         110, 32, 237, 0, 217, 90, 31, 100, 228, 149, 46, 219, 175, 168, 77, 4, 143, 38, 128,
179                         76, 97, 0, 0, 0, 2, 0, 0, 255, 8, 153, 192, 0, 2, 27, 0, 0, 0, 1, 0, 0, 255, 2, 68,
180                         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,
181                         0, 0, 0, 1, 0, 0, 0, 0, 58, 85, 116, 216, 255, 8, 153, 192, 0, 2, 27, 0, 0, 25, 0, 0,
182                         0, 1, 0, 0, 0, 125, 255, 2, 68, 226, 0, 6, 11, 0, 1, 5, 0, 0, 0, 0, 29, 129, 25, 192,
183                 ];
184
185                 let tmp_directory = "./rapid-gossip-sync-tests-tmp";
186                 let sync_test = FileSyncTest::new(tmp_directory, &valid_response);
187                 let graph_sync_test_file = sync_test.get_test_file_path();
188
189                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
190                 let network_graph = NetworkGraph::new(block_hash);
191
192                 assert_eq!(network_graph.read_only().channels().len(), 0);
193
194                 let rapid_sync = RapidGossipSync::new(&network_graph);
195                 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
196
197                 if sync_result.is_err() {
198                         panic!("Unexpected sync result: {:?}", sync_result)
199                 }
200
201                 assert_eq!(network_graph.read_only().channels().len(), 2);
202                 let after = network_graph.to_string();
203                 assert!(
204                         after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
205                 );
206                 assert!(
207                         after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
208                 );
209                 assert!(
210                         after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
211                 );
212                 assert!(
213                         after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
214                 );
215                 assert!(after.contains("619737530008010752"));
216                 assert!(after.contains("783241506229452801"));
217         }
218
219         #[test]
220         fn measure_native_read_from_file() {
221                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
222                 let network_graph = NetworkGraph::new(block_hash);
223
224                 assert_eq!(network_graph.read_only().channels().len(), 0);
225
226                 let rapid_sync = RapidGossipSync::new(&network_graph);
227                 let start = std::time::Instant::now();
228                 let sync_result = rapid_sync
229                         .sync_network_graph_with_file_path("./res/full_graph.lngossip");
230                 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
231                         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);
232                         #[cfg(not(require_route_graph_test))]
233                         {
234                                 println!("{}", error_string);
235                                 return;
236                         }
237                         #[cfg(require_route_graph_test)]
238                         panic!("{}", error_string);
239                 }
240                 let elapsed = start.elapsed();
241                 println!("initialization duration: {:?}", elapsed);
242                 if sync_result.is_err() {
243                         panic!("Unexpected sync result: {:?}", sync_result)
244                 }
245         }
246 }
247
248 #[cfg(all(test, feature = "_bench_unstable"))]
249 pub mod bench {
250         use test::Bencher;
251
252         use bitcoin::blockdata::constants::genesis_block;
253         use bitcoin::Network;
254
255         use lightning::ln::msgs::DecodeError;
256         use lightning::routing::network_graph::NetworkGraph;
257
258         use crate::RapidGossipSync;
259
260         #[bench]
261         fn bench_reading_full_graph_from_file(b: &mut Bencher) {
262                 let block_hash = genesis_block(Network::Bitcoin).block_hash();
263                 b.iter(|| {
264                         let network_graph = NetworkGraph::new(block_hash);
265                         let rapid_sync = RapidGossipSync::new(&network_graph);
266                         let sync_result = rapid_sync.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-bc08df7542-2022-05-05.bin\n\n{:?}", io_error);
269                                 #[cfg(not(require_route_graph_test))]
270                                 {
271                                         println!("{}", error_string);
272                                         return;
273                                 }
274                                 panic!("{}", error_string);
275                         }
276                         assert!(sync_result.is_ok())
277                 });
278         }
279 }