reth_cli_commands/test_vectors/
compact.rs
1use alloy_eips::eip4895::Withdrawals;
2use alloy_primitives::{hex, Signature, TxKind, B256};
3use arbitrary::Arbitrary;
4use eyre::{Context, Result};
5use proptest::{
6 prelude::{ProptestConfig, RngCore},
7 test_runner::{TestRng, TestRunner},
8};
9use reth_codecs::alloy::{
10 authorization_list::Authorization,
11 genesis_account::GenesisAccount,
12 header::{Header, HeaderExt},
13 transaction::{
14 eip1559::TxEip1559, eip2930::TxEip2930, eip4844::TxEip4844, eip7702::TxEip7702,
15 legacy::TxLegacy,
16 },
17 withdrawal::Withdrawal,
18};
19use reth_db::{
20 models::{
21 AccountBeforeTx, StaticFileBlockWithdrawals, StoredBlockBodyIndices, StoredBlockOmmers,
22 StoredBlockWithdrawals,
23 },
24 ClientVersion,
25};
26use reth_ethereum_primitives::{Receipt, Transaction, TransactionSigned, TxType};
27use reth_fs_util as fs;
28use reth_primitives_traits::{Account, Log, LogData, StorageEntry};
29use reth_prune_types::{PruneCheckpoint, PruneMode};
30use reth_stages_types::{
31 AccountHashingCheckpoint, CheckpointBlockRange, EntitiesCheckpoint, ExecutionCheckpoint,
32 HeadersCheckpoint, IndexHistoryCheckpoint, StageCheckpoint, StageUnitCheckpoint,
33 StorageHashingCheckpoint,
34};
35use reth_trie::{hash_builder::HashBuilderValue, TrieMask};
36use reth_trie_common::{hash_builder::HashBuilderState, StoredNibbles, StoredNibblesSubKey};
37use std::{fs::File, io::BufReader};
38
39pub const VECTORS_FOLDER: &str = "testdata/micro/compact";
40pub const VECTOR_SIZE: usize = 100;
41
42#[macro_export]
43macro_rules! compact_types {
44 (regular: [$($regular_ty:ident),*], identifier: [$($id_ty:ident),*]) => {
45 pub const GENERATE_VECTORS: &[fn(&mut TestRunner) -> eyre::Result<()>] = &[
46 $(
47 generate_vector::<$regular_ty> as fn(&mut TestRunner) -> eyre::Result<()>,
48 )*
49 $(
50 generate_vector::<$id_ty> as fn(&mut TestRunner) -> eyre::Result<()>,
51 )*
52 ];
53
54 pub const READ_VECTORS: &[fn() -> eyre::Result<()>] = &[
55 $(
56 read_vector::<$regular_ty> as fn() -> eyre::Result<()>,
57 )*
58 $(
59 read_vector::<$id_ty> as fn() -> eyre::Result<()>,
60 )*
61 ];
62
63 pub static IDENTIFIER_TYPE: std::sync::LazyLock<std::collections::HashSet<String>> = std::sync::LazyLock::new(|| {
64 let mut map = std::collections::HashSet::new();
65 $(
66 map.insert(type_name::<$id_ty>());
67 )*
68 map
69 });
70 };
71}
72
73compact_types!(
76 regular: [
77 Account,
79 Receipt,
80 Authorization,
82 GenesisAccount,
83 Header,
84 HeaderExt,
85 Withdrawal,
86 Withdrawals,
87 TxEip2930,
88 TxEip1559,
89 TxEip4844,
90 TxEip7702,
91 TxLegacy,
92 HashBuilderValue,
93 LogData,
94 Log,
95 TrieMask,
97 PruneCheckpoint,
100 PruneMode,
101 AccountHashingCheckpoint,
103 StorageHashingCheckpoint,
104 ExecutionCheckpoint,
105 HeadersCheckpoint,
106 IndexHistoryCheckpoint,
107 EntitiesCheckpoint,
108 CheckpointBlockRange,
109 StageCheckpoint,
110 StageUnitCheckpoint,
111 StoredBlockOmmers,
113 StoredBlockBodyIndices,
114 StoredBlockWithdrawals,
115 StaticFileBlockWithdrawals,
116 TransactionSigned,
118 StorageEntry,
120 AccountBeforeTx,
122 ClientVersion,
123 StoredNibbles,
124 StoredNibblesSubKey,
125 HashBuilderState
128 ],
129 identifier: [
131 Signature,
132 Transaction,
133 TxType,
134 TxKind
135 ]
136);
137
138pub fn generate_vectors() -> Result<()> {
140 generate_vectors_with(GENERATE_VECTORS)
141}
142
143pub fn read_vectors() -> Result<()> {
144 read_vectors_with(READ_VECTORS)
145}
146
147pub fn generate_vectors_with(generator: &[fn(&mut TestRunner) -> eyre::Result<()>]) -> Result<()> {
149 let seed = B256::random();
151 println!("Seed for compact test vectors: {:?}", hex::encode_prefixed(seed));
152
153 let config = ProptestConfig::default();
155 let rng = TestRng::from_seed(config.rng_algorithm, &seed.0);
156 let mut runner = TestRunner::new_with_rng(config, rng);
157
158 fs::create_dir_all(VECTORS_FOLDER)?;
159
160 for generate_fn in generator {
161 generate_fn(&mut runner)?;
162 }
163
164 Ok(())
165}
166
167pub fn read_vectors_with(read: &[fn() -> eyre::Result<()>]) -> Result<()> {
170 fs::create_dir_all(VECTORS_FOLDER)?;
171 let mut errors = None;
172
173 for read_fn in read {
174 if let Err(err) = read_fn() {
175 errors.get_or_insert_with(Vec::new).push(err);
176 }
177 }
178
179 if let Some(err_list) = errors {
180 for error in err_list {
181 eprintln!("{error:?}");
182 }
183 return Err(eyre::eyre!(
184 "If there are missing types, make sure to run `reth test-vectors compact --write` first.\n
185 If it happened during CI, ignore IF it's a new proposed type that `main` branch does not have."
186 ));
187 }
188
189 Ok(())
190}
191
192pub fn generate_vector<T>(runner: &mut TestRunner) -> Result<()>
194where
195 T: for<'a> Arbitrary<'a> + reth_codecs::Compact,
196{
197 let type_name = type_name::<T>();
198 print!("{}", &type_name);
199
200 let mut bytes = std::iter::repeat_n(0u8, 256).collect::<Vec<u8>>();
201 let mut compact_buffer = vec![];
202
203 let mut values = Vec::with_capacity(VECTOR_SIZE);
204 for _ in 0..VECTOR_SIZE {
205 runner.rng().fill_bytes(&mut bytes);
206 compact_buffer.clear();
207
208 let mut tries = 0;
210 let obj = loop {
211 match T::arbitrary(&mut arbitrary::Unstructured::new(&bytes)) {
212 Ok(obj) => break obj,
213 Err(err) => {
214 if tries < 5 && matches!(err, arbitrary::Error::NotEnoughData) {
215 tries += 1;
216 bytes.extend(std::iter::repeat_n(0u8, 256));
217 } else {
218 return Err(err)?
219 }
220 }
221 }
222 };
223 let res = obj.to_compact(&mut compact_buffer);
224
225 if IDENTIFIER_TYPE.contains(&type_name) {
226 compact_buffer.push(res as u8);
227 }
228
229 values.push(hex::encode(&compact_buffer));
230 }
231
232 serde_json::to_writer(
233 std::io::BufWriter::new(
234 std::fs::File::create(format!("{VECTORS_FOLDER}/{}.json", &type_name)).unwrap(),
235 ),
236 &values,
237 )?;
238
239 println!(" ✅");
240
241 Ok(())
242}
243
244pub fn read_vector<T>() -> Result<()>
247where
248 T: reth_codecs::Compact,
249{
250 let type_name = type_name::<T>();
251 print!("{}", &type_name);
252
253 let file_path = format!("{VECTORS_FOLDER}/{}.json", &type_name);
255 let file =
256 File::open(&file_path).wrap_err_with(|| format!("Failed to open vector {type_name}."))?;
257 let reader = BufReader::new(file);
258
259 let stored_values: Vec<String> = serde_json::from_reader(reader)?;
260 let mut buffer = vec![];
261
262 for hex_str in stored_values {
263 let mut compact_bytes = hex::decode(hex_str)?;
264 let mut identifier = None;
265 buffer.clear();
266
267 if IDENTIFIER_TYPE.contains(&type_name) {
268 identifier = compact_bytes.pop().map(|b| b as usize);
269 }
270 let len_or_identifier = identifier.unwrap_or(compact_bytes.len());
271
272 let (reconstructed, _) = T::from_compact(&compact_bytes, len_or_identifier);
273 reconstructed.to_compact(&mut buffer);
274 assert_eq!(buffer, compact_bytes, "mismatch {type_name}");
275 }
276
277 println!(" ✅");
278
279 Ok(())
280}
281
282pub fn type_name<T>() -> String {
284 let name = std::any::type_name::<T>();
286 match name {
287 "alloy_consensus::transaction::typed::EthereumTypedTransaction<alloy_consensus::transaction::eip4844::TxEip4844>" => "Transaction".to_string(),
288 "alloy_consensus::transaction::envelope::EthereumTxEnvelope<alloy_consensus::transaction::eip4844::TxEip4844>" => "TransactionSigned".to_string(),
289 name => {
290 name.split("::").last().unwrap_or(std::any::type_name::<T>()).to_string()
291 }
292 }
293}