From d0e3504ecc3fec0d03f8c64dce9b158487cb6da8 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 7 Jun 2024 16:52:44 +0100 Subject: [PATCH] example(exex): tests for OP Bridge (#8658) --- Cargo.lock | 5 + crates/exex/test-utils/src/lib.rs | 20 +++ examples/exex/op-bridge/Cargo.toml | 8 ++ examples/exex/op-bridge/src/main.rs | 169 ++++++++++++++++++++++++ testing/testing-utils/src/generators.rs | 7 +- 5 files changed, 208 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 613716d98..7b0a570d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2822,14 +2822,19 @@ dependencies = [ "alloy-sol-types", "eyre", "futures", + "rand 0.8.5", "reth", "reth-exex", + "reth-exex-test-utils", "reth-node-api", "reth-node-ethereum", "reth-primitives", "reth-provider", + "reth-testing-utils", "reth-tracing", "rusqlite", + "tempfile", + "tokio", ] [[package]] diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index ea177dfa5..2c7b57172 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -145,6 +145,26 @@ impl TestExExHandle { Ok(()) } + /// Send a notification to the Execution Extension that the chain has been reorged + pub async fn send_notification_chain_reorged( + &self, + old: Chain, + new: Chain, + ) -> eyre::Result<()> { + self.notifications_tx + .send(ExExNotification::ChainReorged { old: Arc::new(old), new: Arc::new(new) }) + .await?; + Ok(()) + } + + /// Send a notification to the Execution Extension that the chain has been reverted + pub async fn send_notification_chain_reverted(&self, chain: Chain) -> eyre::Result<()> { + self.notifications_tx + .send(ExExNotification::ChainReverted { old: Arc::new(chain) }) + .await?; + Ok(()) + } + /// Asserts that the Execution Extension did not emit any events. #[track_caller] pub fn assert_events_empty(&self) { diff --git a/examples/exex/op-bridge/Cargo.toml b/examples/exex/op-bridge/Cargo.toml index 5145080c1..ce1e39db1 100644 --- a/examples/exex/op-bridge/Cargo.toml +++ b/examples/exex/op-bridge/Cargo.toml @@ -18,3 +18,11 @@ eyre.workspace = true futures.workspace = true alloy-sol-types = { workspace = true, features = ["json"] } rusqlite = { version = "0.31.0", features = ["bundled"] } + +[dev-dependencies] +reth-exex-test-utils.workspace = true +reth-testing-utils.workspace = true + +tokio.workspace = true +rand.workspace = true +tempfile.workspace = true diff --git a/examples/exex/op-bridge/src/main.rs b/examples/exex/op-bridge/src/main.rs index 02c87ba15..c4eac3618 100644 --- a/examples/exex/op-bridge/src/main.rs +++ b/examples/exex/op-bridge/src/main.rs @@ -252,3 +252,172 @@ fn main() -> eyre::Result<()> { handle.wait_for_node_exit().await }) } + +#[cfg(test)] +mod tests { + use std::pin::pin; + + use alloy_sol_types::SolEvent; + use reth::revm::db::BundleState; + use reth_exex_test_utils::{test_exex_context, PollOnce}; + use reth_primitives::{ + Address, Block, Header, Log, Receipt, Receipts, Transaction, TransactionSigned, TxKind, + TxLegacy, TxType, U256, + }; + use reth_provider::{BundleStateWithReceipts, Chain}; + use reth_testing_utils::generators::sign_tx_with_random_key_pair; + use rusqlite::Connection; + + use crate::{L1StandardBridge, OP_BRIDGES}; + + /// Given the address of a bridge contract and an event, construct a transaction signed with a + /// random private key and a receipt for that transaction. + fn construct_tx_and_receipt( + to: Address, + event: E, + ) -> eyre::Result<(TransactionSigned, Receipt)> { + let tx = Transaction::Legacy(TxLegacy { to: TxKind::Call(to), ..Default::default() }); + let log = Log::new( + to, + event.encode_topics().into_iter().map(|topic| topic.0).collect(), + event.encode_data().into(), + ) + .ok_or_else(|| eyre::eyre!("failed to encode event"))?; + let receipt = Receipt { + tx_type: TxType::Legacy, + success: true, + cumulative_gas_used: 0, + logs: vec![log], + ..Default::default() + }; + Ok((sign_tx_with_random_key_pair(&mut rand::thread_rng(), tx), receipt)) + } + + #[tokio::test] + async fn test_exex() -> eyre::Result<()> { + // Initialize the test Execution Extension context with all dependencies + let (ctx, handle) = test_exex_context().await?; + // Create a temporary database file, so we can access it later for assertions + let db_file = tempfile::NamedTempFile::new()?; + + // Initialize the ExEx + let mut exex = pin!(super::init(ctx, Connection::open(&db_file)?).await?); + + // Generate random "from" and "to" addresses for deposit and withdrawal events + let from_address = Address::random(); + let to_address = Address::random(); + + // Construct deposit event, transaction and receipt + let deposit_event = L1StandardBridge::ETHBridgeInitiated { + from: from_address, + to: to_address, + amount: U256::from(100), + extraData: Default::default(), + }; + let (deposit_tx, deposit_tx_receipt) = + construct_tx_and_receipt(OP_BRIDGES[0], deposit_event.clone())?; + + // Construct withdrawal event, transaction and receipt + let withdrawal_event = L1StandardBridge::ETHBridgeFinalized { + from: from_address, + to: to_address, + amount: U256::from(200), + extraData: Default::default(), + }; + let (withdrawal_tx, withdrawal_tx_receipt) = + construct_tx_and_receipt(OP_BRIDGES[1], withdrawal_event.clone())?; + + // Construct a block + let block = Block { + header: Header::default(), + body: vec![deposit_tx, withdrawal_tx], + ..Default::default() + } + .seal_slow() + .seal_with_senders() + .ok_or_else(|| eyre::eyre!("failed to recover senders"))?; + + // Construct a chain + let chain = Chain::new( + vec![block.clone()], + BundleStateWithReceipts::new( + BundleState::default(), + Receipts::from_block_receipt(vec![deposit_tx_receipt, withdrawal_tx_receipt]), + block.number, + ), + None, + ); + + // Send a notification that the chain has been committed + handle.send_notification_chain_committed(chain.clone()).await?; + // Poll the ExEx once, it will process the notification that we just sent + exex.poll_once().await?; + + let connection = Connection::open(&db_file)?; + + // Assert that the deposit event was parsed correctly and inserted into the database + let deposits: Vec<(u64, String, String, String, String, String)> = connection + .prepare(r#"SELECT block_number, contract_address, "from", "to", amount, tx_hash FROM deposits"#)? + .query_map([], |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?, row.get(5)?)) + })? + .collect::, _>>()?; + assert_eq!(deposits.len(), 1); + assert_eq!( + deposits[0], + ( + block.number, + OP_BRIDGES[0].to_string(), + from_address.to_string(), + to_address.to_string(), + deposit_event.amount.to_string(), + block.body[0].hash().to_string() + ) + ); + + // Assert that the withdrawal event was parsed correctly and inserted into the database + let withdrawals: Vec<(u64, String, String, String, String, String)> = connection + .prepare(r#"SELECT block_number, contract_address, "from", "to", amount, tx_hash FROM withdrawals"#)? + .query_map([], |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?, row.get(5)?)) + })? + .collect::, _>>()?; + assert_eq!(withdrawals.len(), 1); + assert_eq!( + withdrawals[0], + ( + block.number, + OP_BRIDGES[1].to_string(), + from_address.to_string(), + to_address.to_string(), + withdrawal_event.amount.to_string(), + block.body[1].hash().to_string() + ) + ); + + // Send a notification that the same chain has been reverted + handle.send_notification_chain_reverted(chain).await?; + // Poll the ExEx once, it will process the notification that we just sent + exex.poll_once().await?; + + // Assert that the deposit was removed from the database + let deposits = connection + .prepare(r#"SELECT block_number, contract_address, "from", "to", amount, tx_hash FROM deposits"#)? + .query_map([], |_| { + Ok(()) + })? + .count(); + assert_eq!(deposits, 0); + + // Assert that the withdrawal was removed from the database + let withdrawals = connection + .prepare(r#"SELECT block_number, contract_address, "from", "to", amount, tx_hash FROM withdrawals"#)? + .query_map([], |_| { + Ok(()) + })? + .count(); + assert_eq!(withdrawals, 0); + + Ok(()) + } +} diff --git a/testing/testing-utils/src/generators.rs b/testing/testing-utils/src/generators.rs index e7c854459..4ef65043f 100644 --- a/testing/testing-utils/src/generators.rs +++ b/testing/testing-utils/src/generators.rs @@ -89,9 +89,14 @@ pub fn random_tx(rng: &mut R) -> Transaction { /// /// - There is no guarantee that the nonce is not used twice for the same account pub fn random_signed_tx(rng: &mut R) -> TransactionSigned { + let tx = random_tx(rng); + sign_tx_with_random_key_pair(rng, tx) +} + +/// Signs the [Transaction] with a random key pair. +pub fn sign_tx_with_random_key_pair(rng: &mut R, tx: Transaction) -> TransactionSigned { let secp = Secp256k1::new(); let key_pair = Keypair::new(&secp, rng); - let tx = random_tx(rng); sign_tx_with_key_pair(key_pair, tx) }