3 #![deny(broken_intra_doc_links)]
4 #![deny(non_upper_case_globals)]
5 #![deny(non_camel_case_types)]
6 #![deny(non_snake_case)]
8 #![deny(unused_variables)]
9 #![deny(unused_imports)]
10 //! This crate exposes functionality to rapidly sync gossip data, aimed primarily at mobile
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).
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`:
23 //! curl -o rapid_sync.lngossip https://rapidsync.lightningdevkit.org/snapshot/<last_sync_timestamp>
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:
30 //! use bitcoin::blockdata::constants::genesis_block;
31 //! use bitcoin::Network;
32 //! use lightning::routing::network_graph::NetworkGraph;
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");
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.
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.
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.
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"))]
61 use lightning::routing::network_graph;
63 use crate::error::GraphSyncError;
65 /// Error types that these functions can return
68 /// Core functionality of this crate
71 /// Sync gossip data from a file
72 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
74 /// `network_graph`: The network graph to apply the updates to
76 /// `sync_path`: Path to the file where the gossip update data is located
78 pub fn sync_network_graph_with_file_path(
79 network_graph: &network_graph::NetworkGraph,
81 ) -> Result<u32, GraphSyncError> {
82 let mut file = File::open(sync_path)?;
83 processing::update_network_graph_from_byte_stream(&network_graph, &mut file)
90 use bitcoin::blockdata::constants::genesis_block;
93 use lightning::ln::msgs::DecodeError;
94 use lightning::routing::network_graph::NetworkGraph;
96 use crate::sync_network_graph_with_file_path;
99 fn test_sync_from_file() {
100 struct FileSyncTest {
105 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
106 let test = FileSyncTest { directory: tmp_directory.to_owned() };
108 let graph_sync_test_directory = test.get_test_directory();
109 fs::create_dir_all(graph_sync_test_directory).unwrap();
111 let graph_sync_test_file = test.get_test_file_path();
112 fs::write(&graph_sync_test_file, valid_response).unwrap();
116 fn get_test_directory(&self) -> String {
117 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
118 graph_sync_test_directory
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";
127 impl Drop for FileSyncTest {
129 fs::remove_dir_all(self.directory.clone()).unwrap();
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,
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();
154 let block_hash = genesis_block(Network::Bitcoin).block_hash();
155 let network_graph = NetworkGraph::new(block_hash);
157 assert_eq!(network_graph.read_only().channels().len(), 0);
159 let sync_result = sync_network_graph_with_file_path(&network_graph, &graph_sync_test_file);
161 if sync_result.is_err() {
162 panic!("Unexpected sync result: {:?}", sync_result)
165 assert_eq!(network_graph.read_only().channels().len(), 2);
166 let after = network_graph.to_string();
168 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
171 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
174 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
177 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
179 assert!(after.contains("619737530008010752"));
180 assert!(after.contains("783241506229452801"));
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);
188 assert_eq!(network_graph.read_only().channels().len(), 0);
190 let start = std::time::Instant::now();
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))]
197 println!("{}", error_string);
200 #[cfg(require_route_graph_test)]
201 panic!("{}", error_string);
203 let elapsed = start.elapsed();
204 println!("initialization duration: {:?}", elapsed);
205 if sync_result.is_err() {
206 panic!("Unexpected sync result: {:?}", sync_result)
211 #[cfg(all(test, feature = "_bench_unstable"))]
215 use bitcoin::blockdata::constants::genesis_block;
216 use bitcoin::Network;
218 use lightning::ln::msgs::DecodeError;
219 use lightning::routing::network_graph::NetworkGraph;
221 use crate::sync_network_graph_with_file_path;
224 fn bench_reading_full_graph_from_file(b: &mut Bencher) {
225 let block_hash = genesis_block(Network::Bitcoin).block_hash();
227 let network_graph = NetworkGraph::new(block_hash);
228 let sync_result = sync_network_graph_with_file_path(
230 "./res/full_graph.lngossip",
232 if let Err(crate::error::GraphSyncError::DecodeError(DecodeError::Io(io_error))) = &sync_result {
233 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);
234 #[cfg(not(require_route_graph_test))]
236 println!("{}", error_string);
239 panic!("{}", error_string);
241 assert!(sync_result.is_ok())