Skip to main content

ethrex_p2p/
utils.rs

1use crate::peer_handler::DumpError;
2use ethrex_common::{H256, H512, U256, types::AccountState, utils::keccak};
3use ethrex_rlp::encode::RLPEncode;
4use secp256k1::{PublicKey, SecretKey};
5use std::{
6    path::{Path, PathBuf},
7    time::{Duration, SystemTime, UNIX_EPOCH},
8};
9use tracing::error;
10
11/// Computes the node_id from a public key (aka computes the Keccak256 hash of the given public key)
12pub fn node_id(public_key: &H512) -> H256 {
13    keccak(public_key)
14}
15
16pub fn current_unix_time() -> u64 {
17    SystemTime::now()
18        .duration_since(UNIX_EPOCH)
19        .unwrap_or_default()
20        .as_secs()
21}
22
23pub fn get_msg_expiration_from_seconds(seconds: u64) -> u64 {
24    (SystemTime::now() + Duration::from_secs(seconds))
25        .duration_since(UNIX_EPOCH)
26        .unwrap_or_default()
27        .as_secs()
28}
29
30pub fn is_msg_expired(expiration: u64) -> bool {
31    // this cast to a signed integer is needed as the rlp decoder doesn't take into account the sign
32    // otherwise if a msg contains a negative expiration, it would pass since as it would wrap around the u64.
33    (expiration as i64) < (current_unix_time() as i64)
34}
35
36pub fn public_key_from_signing_key(signer: &SecretKey) -> H512 {
37    let public_key = PublicKey::from_secret_key(secp256k1::SECP256K1, signer);
38    let encoded = public_key.serialize_uncompressed();
39    H512::from_slice(&encoded[1..])
40}
41
42/// Deletes the snap folders needed for downloading the leaves during the initial
43/// step of snap sync.
44pub fn delete_leaves_folder(datadir: &Path) {
45    // We ignore the errors because this can happen when the folders don't exist
46    let _ = std::fs::remove_dir_all(get_account_state_snapshots_dir(datadir));
47    let _ = std::fs::remove_dir_all(get_account_storages_snapshots_dir(datadir));
48    let _ = std::fs::remove_dir_all(get_code_hashes_snapshots_dir(datadir));
49    #[cfg(feature = "rocksdb")]
50    {
51        let _ = std::fs::remove_dir_all(get_rocksdb_temp_accounts_dir(datadir));
52        let _ = std::fs::remove_dir_all(get_rocksdb_temp_storage_dir(datadir));
53    };
54}
55
56pub fn get_account_storages_snapshots_dir(datadir: &Path) -> PathBuf {
57    datadir.join("account_storages_snapshots")
58}
59
60pub fn get_account_state_snapshots_dir(datadir: &Path) -> PathBuf {
61    datadir.join("account_state_snapshots")
62}
63
64pub fn get_rocksdb_temp_accounts_dir(datadir: &Path) -> PathBuf {
65    datadir.join("temp_acc_dir")
66}
67
68pub fn get_rocksdb_temp_storage_dir(datadir: &Path) -> PathBuf {
69    datadir.join("temp_storage_dir")
70}
71
72pub fn get_account_state_snapshot_file(directory: &Path, chunk_index: u64) -> PathBuf {
73    directory.join(format!("account_state_chunk.rlp.{chunk_index}"))
74}
75
76pub fn get_account_storages_snapshot_file(directory: &Path, chunk_index: u64) -> PathBuf {
77    directory.join(format!("account_storages_chunk.rlp.{chunk_index}"))
78}
79
80#[cfg(feature = "rocksdb")]
81pub fn dump_accounts_to_rocks_db(
82    path: &Path,
83    mut contents: Vec<(H256, AccountState)>,
84) -> Result<(), rocksdb::Error> {
85    // This can happen sometimes during download, and the sst ingestion method
86    // fails with empty chunk files
87    if contents.is_empty() {
88        return Ok(());
89    }
90    contents.sort_by_key(|(k, _)| *k);
91    contents.dedup_by_key(|(k, _)| {
92        let mut buf = [0u8; 32];
93        buf[..32].copy_from_slice(&k.0);
94        buf
95    });
96    let mut buffer: Vec<u8> = Vec::new();
97    let writer_options = rocksdb::Options::default();
98    let mut writer = rocksdb::SstFileWriter::create(&writer_options);
99    writer.open(std::path::Path::new(&path))?;
100    for (key, account) in contents {
101        buffer.clear();
102        account.encode(&mut buffer);
103        writer.put(key.0.as_ref(), buffer.as_slice())?;
104    }
105    writer.finish()
106}
107
108#[cfg(feature = "rocksdb")]
109pub fn dump_storages_to_rocks_db(
110    path: &Path,
111    mut contents: Vec<(H256, H256, U256)>,
112) -> Result<(), rocksdb::Error> {
113    // This can happen sometimes during download, and the sst ingestion method
114    // fails with empty chunk files
115    if contents.is_empty() {
116        return Ok(());
117    }
118    contents.sort();
119    contents.dedup_by_key(|(k0, k1, _)| {
120        let mut buffer = [0_u8; 64];
121        buffer[0..32].copy_from_slice(&k0.0);
122        buffer[32..64].copy_from_slice(&k1.0);
123        buffer
124    });
125    let writer_options = rocksdb::Options::default();
126    let mut writer = rocksdb::SstFileWriter::create(&writer_options);
127    let mut buffer_key = [0_u8; 64];
128    let mut buffer_storage: Vec<u8> = Vec::new();
129    writer.open(std::path::Path::new(&path))?;
130    for (account, slot_hash, slot_value) in contents {
131        buffer_key[0..32].copy_from_slice(&account.0);
132        buffer_key[32..64].copy_from_slice(&slot_hash.0);
133        buffer_storage.clear();
134        slot_value.encode(&mut buffer_storage);
135        writer.put(buffer_key.as_ref(), buffer_storage.as_slice())?;
136    }
137    writer.finish()
138}
139
140pub fn get_code_hashes_snapshots_dir(datadir: &Path) -> PathBuf {
141    datadir.join("bytecode_hashes_snapshots")
142}
143
144pub fn get_code_hashes_snapshot_file(directory: &Path, chunk_index: u64) -> PathBuf {
145    directory.join(format!("bytecode_hashes_chunk.rlp.{chunk_index}"))
146}
147
148pub fn dump_to_file(path: &Path, contents: Vec<u8>) -> Result<(), DumpError> {
149    std::fs::write(path, &contents)
150        .inspect_err(|err| error!(%err, ?path, "Failed to dump snapshot to file"))
151        .map_err(|err| DumpError {
152            path: path.to_path_buf(),
153            error: err.kind(),
154        })
155}
156
157pub fn dump_accounts_to_file(
158    path: &Path,
159    accounts: Vec<(H256, AccountState)>,
160) -> Result<(), DumpError> {
161    #[cfg(feature = "rocksdb")]
162    return dump_accounts_to_rocks_db(path, accounts)
163        .inspect_err(|err| error!("RocksDB SST write error: {err:?}"))
164        .map_err(|_| DumpError {
165            path: path.to_path_buf(),
166            error: std::io::ErrorKind::Other,
167        });
168    #[cfg(not(feature = "rocksdb"))]
169    dump_to_file(path, accounts.encode_to_vec())
170}
171
172/// Struct representing the storage slots of certain accounts that share the same storage root
173pub struct AccountsWithStorage {
174    /// Accounts with the same storage root
175    pub accounts: Vec<H256>,
176    /// All slots in the trie from the accounts
177    pub storages: Vec<(H256, U256)>,
178}
179
180pub fn dump_storages_to_file(
181    path: &Path,
182    storages: Vec<AccountsWithStorage>,
183) -> Result<(), DumpError> {
184    #[cfg(feature = "rocksdb")]
185    return dump_storages_to_rocks_db(
186        path,
187        storages
188            .into_iter()
189            .flat_map(|accounts_with_slots| {
190                accounts_with_slots
191                    .accounts
192                    .into_iter()
193                    .map(|hash| {
194                        accounts_with_slots
195                            .storages
196                            .iter()
197                            .map(move |(slot_hash, slot_value)| (hash, *slot_hash, *slot_value))
198                            .collect::<Vec<_>>()
199                    })
200                    .collect::<Vec<_>>()
201            })
202            .flatten()
203            .collect::<Vec<_>>(),
204    )
205    .inspect_err(|err| error!("RocksDB SST write error: {err:?}"))
206    .map_err(|_| DumpError {
207        path: path.to_path_buf(),
208        error: std::io::ErrorKind::Other,
209    });
210
211    #[cfg(not(feature = "rocksdb"))]
212    dump_to_file(
213        path,
214        storages
215            .into_iter()
216            .map(|accounts_with_slots| (accounts_with_slots.accounts, accounts_with_slots.storages))
217            .collect::<Vec<_>>()
218            .encode_to_vec(),
219    )
220}
221
222/// Computes the distance between two nodes according to the discv4/5 protocols
223/// <https://github.com/ethereum/devp2p/blob/master/discv4.md#node-identities>
224/// <https://github.com/ethereum/devp2p/blob/master/discv5/discv5-theory.md#nodes-records-and-distances>
225pub fn distance(node_id_1: &H256, node_id_2: &H256) -> usize {
226    let xor = node_id_1 ^ node_id_2;
227    let distance = U256::from_big_endian(xor.as_bytes());
228    distance.bits()
229}