mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
example(exex): tests for OP Bridge (#8658)
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -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]]
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<E: SolEvent>(
|
||||
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::<Result<Vec<_>, _>>()?;
|
||||
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::<Result<Vec<_>, _>>()?;
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,9 +89,14 @@ pub fn random_tx<R: Rng>(rng: &mut R) -> Transaction {
|
||||
///
|
||||
/// - There is no guarantee that the nonce is not used twice for the same account
|
||||
pub fn random_signed_tx<R: Rng>(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<R: Rng>(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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user