cuprate_address_book/
store.rs
1#![expect(
2 single_use_lifetimes,
3 reason = "false positive on generated derive code on `SerPeerDataV1`"
4)]
5
6use std::fs;
7
8use borsh::{from_slice, to_vec, BorshDeserialize, BorshSerialize};
9use tokio::task::{spawn_blocking, JoinHandle};
10
11use cuprate_p2p_core::{services::ZoneSpecificPeerListEntryBase, NetZoneAddress};
12
13use crate::{peer_list::PeerList, AddressBookConfig, BorshNetworkZone};
14
15#[derive(BorshSerialize)]
18struct SerPeerDataV1<'a, A: NetZoneAddress> {
19 white_list: Vec<&'a ZoneSpecificPeerListEntryBase<A>>,
20 gray_list: Vec<&'a ZoneSpecificPeerListEntryBase<A>>,
21}
22
23#[derive(BorshDeserialize)]
24struct DeserPeerDataV1<A: NetZoneAddress> {
25 white_list: Vec<ZoneSpecificPeerListEntryBase<A>>,
26 gray_list: Vec<ZoneSpecificPeerListEntryBase<A>>,
27}
28
29pub(crate) fn save_peers_to_disk<Z: BorshNetworkZone>(
30 cfg: &AddressBookConfig,
31 white_list: &PeerList<Z>,
32 gray_list: &PeerList<Z>,
33) -> JoinHandle<std::io::Result<()>> {
34 let data = to_vec(&SerPeerDataV1 {
37 white_list: white_list.peers.values().collect::<Vec<_>>(),
38 gray_list: gray_list.peers.values().collect::<Vec<_>>(),
39 })
40 .unwrap();
41
42 let dir = cfg.peer_store_directory.clone();
43 let file = dir.join(Z::NAME);
44 let mut tmp_file = file.clone();
45 tmp_file.set_extension("tmp");
46
47 spawn_blocking(move || {
48 fs::create_dir_all(dir)?;
49 fs::write(&tmp_file, &data).and_then(|()| fs::rename(tmp_file, file))
50 })
51}
52
53pub(crate) async fn read_peers_from_disk<Z: BorshNetworkZone>(
54 cfg: &AddressBookConfig,
55) -> Result<
56 (
57 Vec<ZoneSpecificPeerListEntryBase<Z::Addr>>,
58 Vec<ZoneSpecificPeerListEntryBase<Z::Addr>>,
59 ),
60 std::io::Error,
61> {
62 let file = cfg.peer_store_directory.join(Z::NAME);
63
64 tracing::info!("Loading peers from file: {} ", file.display());
65
66 let data = spawn_blocking(move || fs::read(file)).await.unwrap()?;
67
68 let de_ser: DeserPeerDataV1<Z::Addr> = from_slice(&data)?;
69 Ok((de_ser.white_list, de_ser.gray_list))
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::peer_list::{tests::make_fake_peer_list, PeerList};
76
77 use cuprate_test_utils::test_netzone::{TestNetZone, TestNetZoneAddr};
78
79 #[test]
80 fn ser_deser_peer_list() {
81 let white_list = make_fake_peer_list(0, 50);
82 let gray_list = make_fake_peer_list(50, 100);
83
84 let data = to_vec(&SerPeerDataV1 {
85 white_list: white_list.peers.values().collect::<Vec<_>>(),
86 gray_list: gray_list.peers.values().collect::<Vec<_>>(),
87 })
88 .unwrap();
89
90 let de_ser: DeserPeerDataV1<TestNetZoneAddr> = from_slice(&data).unwrap();
91
92 let white_list_2: PeerList<TestNetZone<true>> = PeerList::new(de_ser.white_list);
93 let gray_list_2: PeerList<TestNetZone<true>> = PeerList::new(de_ser.gray_list);
94
95 assert_eq!(white_list.peers.len(), white_list_2.peers.len());
96 assert_eq!(gray_list.peers.len(), gray_list_2.peers.len());
97
98 for addr in white_list.peers.keys() {
99 assert!(white_list_2.contains_peer(addr));
100 }
101
102 for addr in gray_list.peers.keys() {
103 assert!(gray_list_2.contains_peer(addr));
104 }
105 }
106}