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::gossip::NetworkGraph;
33 //! use lightning_rapid_gossip_sync::RapidGossipSync;
35 //! # use lightning::util::logger::{Logger, Record};
36 //! # struct FakeLogger {}
37 //! # impl Logger for FakeLogger {
38 //! # fn log(&self, record: &Record) { unimplemented!() }
40 //! # let logger = FakeLogger {};
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");
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.
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.
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.
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"))]
70 use std::sync::atomic::{AtomicBool, Ordering};
72 use lightning::routing::gossip::NetworkGraph;
73 use lightning::util::logger::Logger;
75 use crate::error::GraphSyncError;
77 /// Error types that these functions can return
80 /// Core functionality of this crate
83 /// Rapid Gossip Sync struct
84 /// See [crate-level documentation] for usage.
86 /// [crate-level documentation]: crate
87 pub struct RapidGossipSync<NG: Deref<Target=NetworkGraph<L>>, L: Deref>
88 where L::Target: Logger {
90 is_initial_sync_complete: AtomicBool
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 {
98 is_initial_sync_complete: AtomicBool::new(false)
102 /// Sync gossip data from a file
103 /// Returns the last sync timestamp to be used the next time rapid sync data is queried.
105 /// `network_graph`: The network graph to apply the updates to
107 /// `sync_path`: Path to the file where the gossip update data is located
109 pub fn sync_network_graph_with_file_path(
112 ) -> Result<u32, GraphSyncError> {
113 let mut file = File::open(sync_path)?;
114 self.update_network_graph_from_byte_stream(&mut file)
117 /// Gets a reference to the underlying [`NetworkGraph`] which was provided in
118 /// [`RapidGossipSync::new`].
120 /// (C-not exported) as bindings don't support a reference-to-a-reference yet
121 pub fn network_graph(&self) -> &NG {
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)
135 use bitcoin::blockdata::constants::genesis_block;
136 use bitcoin::Network;
138 use lightning::ln::msgs::DecodeError;
139 use lightning::routing::gossip::NetworkGraph;
140 use lightning::util::test_utils::TestLogger;
141 use crate::RapidGossipSync;
144 fn test_sync_from_file() {
145 struct FileSyncTest {
150 fn new(tmp_directory: &str, valid_response: &[u8]) -> FileSyncTest {
151 let test = FileSyncTest { directory: tmp_directory.to_owned() };
153 let graph_sync_test_directory = test.get_test_directory();
154 fs::create_dir_all(graph_sync_test_directory).unwrap();
156 let graph_sync_test_file = test.get_test_file_path();
157 fs::write(&graph_sync_test_file, valid_response).unwrap();
161 fn get_test_directory(&self) -> String {
162 let graph_sync_test_directory = self.directory.clone() + "/graph-sync-tests";
163 graph_sync_test_directory
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";
172 impl Drop for FileSyncTest {
174 fs::remove_dir_all(self.directory.clone()).unwrap();
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,
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();
199 let block_hash = genesis_block(Network::Bitcoin).block_hash();
200 let logger = TestLogger::new();
201 let network_graph = NetworkGraph::new(block_hash, &logger);
203 assert_eq!(network_graph.read_only().channels().len(), 0);
205 let rapid_sync = RapidGossipSync::new(&network_graph);
206 let sync_result = rapid_sync.sync_network_graph_with_file_path(&graph_sync_test_file);
208 if sync_result.is_err() {
209 panic!("Unexpected sync result: {:?}", sync_result)
212 assert_eq!(network_graph.read_only().channels().len(), 2);
213 let after = network_graph.to_string();
215 after.contains("021607cfce19a4c5e7e6e738663dfafbbbac262e4ff76c2c9b30dbeefc35c00643")
218 after.contains("02247d9db0dfafea745ef8c9e161eb322f73ac3f8858d8730b6fd97254747ce76b")
221 after.contains("029e01f279986acc83ba235d46d80aede0b7595f410353b93a8ab540bb677f4432")
224 after.contains("02c913118a8895b9e29c89af6e20ed00d95a1f64e4952edbafa84d048f26804c61")
226 assert!(after.contains("619737530008010752"));
227 assert!(after.contains("783241506229452801"));
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);
236 assert_eq!(network_graph.read_only().channels().len(), 0);
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))]
246 println!("{}", error_string);
249 #[cfg(require_route_graph_test)]
250 panic!("{}", error_string);
252 let elapsed = start.elapsed();
253 println!("initialization duration: {:?}", elapsed);
254 if sync_result.is_err() {
255 panic!("Unexpected sync result: {:?}", sync_result)
260 #[cfg(all(test, feature = "_bench_unstable"))]
264 use bitcoin::blockdata::constants::genesis_block;
265 use bitcoin::Network;
267 use lightning::ln::msgs::DecodeError;
268 use lightning::routing::gossip::NetworkGraph;
269 use lightning::util::test_utils::TestLogger;
271 use crate::RapidGossipSync;
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();
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))]
285 println!("{}", error_string);
288 #[cfg(require_route_graph_test)]
289 panic!("{}", error_string);
291 assert!(sync_result.is_ok())