1use alloy_consensus::{TxEnvelope, TxEnvelope::Seismic};
2use alloy_primitives::{Address, TxKind, B256};
3use alloy_rpc_types::engine::PayloadAttributes;
4use alloy_rpc_types_eth::TransactionRequest;
5use alloy_signer_local::PrivateKeySigner;
6use reth_chainspec::SEISMIC_DEV;
7use reth_payload_builder::EthPayloadBuilderAttributes;
8use secp256k1::{PublicKey, SecretKey};
9use serde_json::Value;
10use std::{path::PathBuf, process::Stdio};
11use tokio::{
12 io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
13 process::Command,
14 sync::mpsc,
15};
16
17#[derive(Debug)]
19pub struct SeismicRethTestCommand();
20impl SeismicRethTestCommand {
21 pub async fn run(tx: mpsc::Sender<()>, mut shutdown_rx: mpsc::Receiver<()>) {
23 let output =
24 Command::new("cargo").arg("metadata").arg("--format-version=1").output().await.unwrap();
25 let metadata: Value = serde_json::from_slice(&output.stdout).unwrap();
26 let workspace_root = metadata.get("workspace_root").unwrap().as_str().unwrap();
27 println!("Workspace root: {}", workspace_root);
28
29 let mut child = Command::new("cargo")
30 .arg("run")
31 .arg("--bin")
32 .arg("seismic-reth") .arg("--")
34 .arg("node")
35 .arg("--datadir")
36 .arg(SeismicRethTestCommand::data_dir().to_str().unwrap())
37 .arg("--dev")
38 .arg("--dev.block-max-transactions")
39 .arg("1")
40 .arg("--enclave.mock-server")
41 .arg("-vvvv")
42 .current_dir(workspace_root)
43 .stdout(Stdio::piped())
44 .stderr(Stdio::piped())
45 .spawn()
46 .expect("Failed to start the binary");
47
48 tokio::spawn(async move {
49 let stdout = child.stdout.as_mut().expect("Failed to capture stdout");
50 let stderr = child.stderr.as_mut().expect("Failed to capture stderr");
51 let mut stdout_reader = BufReader::new(stdout);
52 let mut stderr_reader = BufReader::new(stderr);
53 let mut stdout_line = String::new();
54 let mut stderr_line = String::new();
55 let mut sent = false;
56 std::panic::set_hook(Box::new(|info| {
57 eprintln!("❌ PANIC DETECTED: {:?}", info);
58 }));
59
60 loop {
61 tokio::select! {
62 result = stdout_reader.read_line(&mut stdout_line) => {
63 if result.unwrap() == 0 {
64 eprintln!("🛑 STDOUT reached EOF! Breaking loop.");
65 break;
66 }
67 eprint!("{}", stdout_line);
68
69 if stdout_line.contains("Starting consensus engine") && !sent {
70 eprintln!("🚀 Reth server is ready!");
71 let _ = tx.send(()).await;
72 sent = true;
73 }
74 stdout_line.clear();
75 tokio::io::stdout().flush().await.unwrap();
76 }
77
78 result = stderr_reader.read_line(&mut stderr_line) => {
79 if result.unwrap() == 0 {
80 eprintln!("🛑 STDERR reached EOF! Breaking loop.");
81 break;
82 }
83 eprint!("{}", stderr_line);
84 stderr_line.clear();
85 }
86
87 Some(_) = shutdown_rx.recv() => {
88 eprintln!("🛑 Shutdown signal received! Breaking loop.");
89 break;
90 }
91 }
92 }
93 println!("✅ Exiting loop.");
94
95 child.kill().await.unwrap();
96 println!("✅ Killed child process.");
97 });
98 }
99
100 pub fn data_dir() -> PathBuf {
102 static TEMP_DIR: once_cell::sync::Lazy<tempfile::TempDir> =
103 once_cell::sync::Lazy::new(|| tempfile::tempdir().unwrap());
104 TEMP_DIR.path().to_path_buf()
105 }
106
107 pub fn chain_id() -> u64 {
109 SEISMIC_DEV.chain().into()
110 }
111
112 pub fn url() -> String {
114 format!("http://127.0.0.1:8545")
115 }
116}
117
118pub fn seismic_payload_attributes(timestamp: u64) -> EthPayloadBuilderAttributes {
120 let attributes = PayloadAttributes {
121 timestamp,
122 prev_randao: B256::ZERO,
123 suggested_fee_recipient: Address::ZERO,
124 withdrawals: Some(vec![]),
125 parent_beacon_block_root: Some(B256::ZERO),
126 target_blobs_per_block: None,
127 max_blobs_per_block: None,
128 };
129 EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
130}
131
132pub mod test_utils {
134 use super::*;
135 use alloy_consensus::{
136 transaction::TxSeismicElements, SignableTransaction, TxSeismic, TypedTransaction,
137 };
138 use alloy_dyn_abi::TypedData;
139 use alloy_eips::{eip2718::Encodable2718, eip712::TypedDataRequest};
140 use alloy_primitives::{aliases::U96, hex_literal, Address, Bytes, PrimitiveSignature, U256};
141 use alloy_rpc_types::{Block, Header, Transaction, TransactionInput, TransactionReceipt};
142 use core::str::FromStr;
143 use enr::EnrKey;
144 use jsonrpsee::http_client::HttpClient;
145 use k256::ecdsa::SigningKey;
146 use reth_e2e_test_utils::transaction::TransactionTestContext;
147 use reth_enclave::MockEnclaveServer;
148 use reth_primitives::TransactionSigned;
149 use reth_rpc_eth_api::EthApiClient;
150
151 pub async fn get_nonce(client: &HttpClient, address: Address) -> u64 {
153 let nonce =
154 EthApiClient::<Transaction, Block, TransactionReceipt, Header>::transaction_count(
155 client, address, None,
156 )
157 .await
158 .unwrap();
159 nonce.wrapping_to::<u64>()
160 }
161
162 pub async fn get_unsigned_seismic_tx_request(
164 sk_wallet: &PrivateKeySigner,
165 nonce: u64,
166 to: TxKind,
167 chain_id: u64,
168 plaintext: Bytes,
169 ) -> TransactionRequest {
170 TransactionRequest {
171 from: Some(sk_wallet.address()),
172 nonce: Some(nonce),
173 value: Some(U256::from(0)),
174 to: Some(to),
175 gas: Some(6000000),
176 gas_price: Some(20e9 as u128),
177 chain_id: Some(chain_id),
178 input: TransactionInput { input: Some(client_encrypt(&plaintext)), data: None },
179 transaction_type: Some(TxSeismic::TX_TYPE),
180 seismic_elements: Some(get_seismic_elements()),
181 ..Default::default()
182 }
183 }
184
185 pub async fn get_signed_seismic_tx_bytes(
187 sk_wallet: &PrivateKeySigner,
188 nonce: u64,
189 to: TxKind,
190 chain_id: u64,
191 plaintext: Bytes,
192 ) -> Bytes {
193 let tx = get_unsigned_seismic_tx_request(sk_wallet, nonce, to, chain_id, plaintext).await;
194 let signed = TransactionTestContext::sign_tx(sk_wallet.clone(), tx).await;
195 <TxEnvelope as Encodable2718>::encoded_2718(&signed).into()
196 }
197
198 pub async fn get_unsigned_seismic_tx_typed_data(
200 sk_wallet: &PrivateKeySigner,
201 nonce: u64,
202 to: TxKind,
203 chain_id: u64,
204 decrypted_input: Bytes,
205 ) -> TypedData {
206 let tx_request =
207 get_unsigned_seismic_tx_request(sk_wallet, nonce, to, chain_id, decrypted_input).await;
208 let typed_tx = tx_request.build_consensus_tx().unwrap();
209 match typed_tx {
210 TypedTransaction::Seismic(seismic) => seismic.eip712_to_type_data(),
211 _ => panic!("Typed transaction is not a seismic transaction"),
212 }
213 }
214
215 pub async fn get_signed_seismic_tx_typed_data(
217 sk_wallet: &PrivateKeySigner,
218 nonce: u64,
219 to: TxKind,
220 chain_id: u64,
221 plaintext: Bytes,
222 ) -> TypedDataRequest {
223 let tx = get_unsigned_seismic_tx_request(sk_wallet, nonce, to, chain_id, plaintext).await;
224 tx.seismic_elements.unwrap().message_version = 2;
225 let signed = TransactionTestContext::sign_tx(sk_wallet.clone(), tx).await;
226
227 match signed {
228 Seismic(tx) => tx.into(),
229 _ => panic!("Signed transaction is not a seismic transaction"),
230 }
231 }
232
233 pub fn get_network_public_key() -> PublicKey {
235 MockEnclaveServer::get_public_key()
236 }
237
238 pub fn get_ciphertext() -> Bytes {
240 let encrypted_data = client_encrypt(&get_plaintext());
241 encrypted_data
242 }
243
244 pub fn client_encrypt(plaintext: &Bytes) -> Bytes {
246 get_seismic_elements()
247 .client_encrypt(plaintext, &get_network_public_key(), &get_encryption_private_key())
248 .unwrap()
249 }
250
251 pub fn client_decrypt(ciphertext: &Bytes) -> Bytes {
253 get_seismic_elements()
254 .client_decrypt(ciphertext, &get_network_public_key(), &get_encryption_private_key())
255 .unwrap()
256 }
257
258 pub fn get_encryption_private_key() -> SecretKey {
260 let private_key_bytes =
261 hex_literal::hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
262 SecretKey::from_slice(&private_key_bytes).expect("Invalid private key")
263 }
264
265 pub fn get_encryption_nonce() -> U96 {
267 U96::MAX
268 }
269
270 pub fn get_seismic_elements() -> TxSeismicElements {
272 TxSeismicElements {
273 encryption_pubkey: get_encryption_private_key().public(),
274 encryption_nonce: get_encryption_nonce(),
275 message_version: 0,
276 }
277 }
278
279 pub fn get_wrong_private_key() -> SecretKey {
281 let private_key_bytes =
282 hex_literal::hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1e");
283 SecretKey::from_slice(&private_key_bytes).expect("Invalid private key")
284 }
285
286 pub fn get_signing_private_key() -> SigningKey {
288 let private_key_bytes =
289 hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
290 let signing_key =
291 SigningKey::from_bytes(&private_key_bytes.into()).expect("Invalid private key");
292 signing_key
293 }
294
295 pub fn get_plaintext() -> Bytes {
297 Bytes::from_str("24a7f0b7000000000000000000000000000000000000000000000000000000000000000b")
298 .unwrap()
299 }
300
301 pub fn get_seismic_tx() -> TxSeismic {
303 let ciphertext = get_ciphertext();
304 TxSeismic {
305 chain_id: 1337,
306 nonce: 1,
307 gas_price: 20000000000,
308 gas_limit: 210000,
309 to: alloy_primitives::TxKind::Call(
310 Address::from_str("0x5fbdb2315678afecb367f032d93f642f64180aa3").unwrap(),
311 ),
312 value: U256::ZERO,
313 input: Bytes::copy_from_slice(&ciphertext),
314 seismic_elements: get_seismic_elements(),
315 }
316 }
317
318 pub fn get_signed_seismic_tx_encoding() -> Vec<u8> {
320 let signed_tx = get_signed_seismic_tx();
321 let mut encoding = Vec::new();
322
323 signed_tx.encode_2718(&mut encoding);
324 encoding
325 }
326
327 pub fn sign_seismic_tx(tx: &TxSeismic) -> PrimitiveSignature {
329 let _signature = get_signing_private_key()
330 .clone()
331 .sign_prehash_recoverable(tx.signature_hash().as_slice())
332 .expect("Failed to sign");
333
334 let recoverid = _signature.1;
335 let _signature = _signature.0;
336
337 let signature = PrimitiveSignature::new(
338 U256::from_be_slice(_signature.r().to_bytes().as_slice()),
339 U256::from_be_slice(_signature.s().to_bytes().as_slice()),
340 recoverid.is_y_odd(),
341 );
342
343 signature
344 }
345
346 pub fn get_signed_seismic_tx() -> TransactionSigned {
348 let tx = get_seismic_tx();
349 let signature = sign_seismic_tx(&tx);
350 SignableTransaction::into_signed(tx, signature).into()
351 }
352}