seismic_node/
utils.rs

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/// Seismic reth test command
18#[derive(Debug)]
19pub struct SeismicRethTestCommand();
20impl SeismicRethTestCommand {
21    /// Run the seismic reth test command
22    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") // Specify the binary name
33            .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    /// Get the data directory for the seismic reth test command
101    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    /// Get the chain id for the seismic reth test command
108    pub fn chain_id() -> u64 {
109        SEISMIC_DEV.chain().into()
110    }
111
112    /// Get the url for the seismic reth test command
113    pub fn url() -> String {
114        format!("http://127.0.0.1:8545")
115    }
116}
117
118/// Helper function to create a new eth payload attributes
119pub 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
132/// Test utils for seismic node
133pub 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    /// Get the nonce from the client
152    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    /// Get an unsigned seismic transaction request
163    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    /// Create a seismic transaction
186    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    /// Get an unsigned seismic transaction typed data
199    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    /// Create a seismic transaction with typed data
216    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    /// Get the network public key
234    pub fn get_network_public_key() -> PublicKey {
235        MockEnclaveServer::get_public_key()
236    }
237
238    /// Encrypt plaintext using network public key and client private key
239    pub fn get_ciphertext() -> Bytes {
240        let encrypted_data = client_encrypt(&get_plaintext());
241        encrypted_data
242    }
243
244    /// Encrypt plaintext using network public key and client private key
245    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    /// Decrypt ciphertext using network public key and client private key
252    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    /// Get the encryption private key
259    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    /// Get the encryption nonce
266    pub fn get_encryption_nonce() -> U96 {
267        U96::MAX
268    }
269
270    /// Get the seismic elements
271    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    /// Get a wrong private key
280    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    /// Get the signing private key
287    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    /// Get the plaintext for a seismic transaction
296    pub fn get_plaintext() -> Bytes {
297        Bytes::from_str("24a7f0b7000000000000000000000000000000000000000000000000000000000000000b")
298            .unwrap()
299    }
300
301    /// Get a seismic transaction
302    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    /// Get the encoding of a signed seismic transaction
319    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    /// Sign a seismic transaction
328    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    /// Get a signed seismic transaction
347    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}