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