diff --git a/fendermint/testing/materializer/tests/docker_tests/standalone.rs b/fendermint/testing/materializer/tests/docker_tests/standalone.rs index abdad4cfe..346cc071c 100644 --- a/fendermint/testing/materializer/tests/docker_tests/standalone.rs +++ b/fendermint/testing/materializer/tests/docker_tests/standalone.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use crate::make_testnet; use anyhow::{bail, Context}; +use ethers::prelude::transaction::eip2718::TypedTransaction; use ethers::{ core::k256::ecdsa::SigningKey, middleware::SignerMiddleware, @@ -10,6 +11,7 @@ use ethers::{ types::{Eip1559TransactionRequest, H160}, }; use fendermint_materializer::{manifest::Rootnet, materials::DefaultAccount, HasEthApi}; +use std::time::{Duration, Instant}; const MANIFEST: &str = "standalone.yaml"; @@ -90,102 +92,97 @@ async fn test_sent_tx_found_in_mempool() -> Result<(), anyhow::Error> { res } -// /// Test that transactions sent out-of-order with regards to the nonce are not rejected, -// /// but rather get included in block eventually, their submission managed by the ethereum -// /// API facade. -// #[serial_test::serial] -// #[tokio::test] -// async fn test_out_of_order_mempool() { -// const MAX_WAIT_TIME: Duration = Duration::from_secs(10); -// const SLEEP_TIME: Duration = Duration::from_secs(1); -// -// with_testnet( -// MANIFEST, -// None, -// |_| {}, -// |_, _, testnet, _| { -// let test = async move { -// let bob = testnet.account("bob")?; -// let charlie = testnet.account("charlie")?; -// -// let pangea = testnet.node(&testnet.root().node("pangea"))?; -// let provider = pangea -// .ethapi_http_provider()? -// .expect("ethapi should be enabled"); -// -// let middleware = make_middleware(provider, bob) -// .await -// .context("failed to set up middleware")?; -// -// // Create the simplest transaction possible: send tokens between accounts. -// let to: H160 = charlie.eth_addr().into(); -// let tx = Eip1559TransactionRequest::new().to(to).value(1); -// let mut tx: TypedTransaction = tx.into(); -// -// // Fill out the nonce, gas, etc. -// middleware -// .fill_transaction(&mut tx, None) -// .await -// .context("failed to fill tx")?; -// -// // Create a few more transactions to be sent out-of-order. -// let mut txs = vec![tx]; -// -// for i in 1..5 { -// let mut tx = txs[0].clone(); -// let nonce = tx.nonce().expect("fill_transaction filled the nonce"); -// tx.set_nonce(nonce.saturating_add(i.into())); -// txs.push(tx) -// } -// -// let mut pending_txs = Vec::new(); -// -// // Submit transactions in opposite order. -// for (i, tx) in txs.iter().enumerate().rev() { -// let sig = middleware -// .signer() -// .sign_transaction(tx) -// .await -// .context("failed to sign tx")?; -// -// let rlp = tx.rlp_signed(&sig); -// -// let pending_tx: PendingTransaction<_> = middleware -// .send_raw_transaction(rlp) -// .await -// .with_context(|| format!("failed to send tx {i}"))?; -// -// pending_txs.push(pending_tx) -// } -// -// // Check that they eventually get included. -// let start = Instant::now(); -// 'pending: loop { -// for tx in pending_txs.iter() { -// let receipt = middleware -// .get_transaction_receipt(tx.tx_hash()) -// .await -// .context("failed to get receipt")?; -// -// if receipt.is_none() { -// if start.elapsed() > MAX_WAIT_TIME { -// bail!("some transactions are still not executed") -// } else { -// tokio::time::sleep(SLEEP_TIME).await; -// continue 'pending; -// } -// } -// } -// // All of them have receipt. -// break 'pending; -// } -// -// Ok(()) -// }; -// -// test.boxed() -// }, -// ) -// .await -// .unwrap() -// } +/// Test that transactions sent out-of-order with regards to the nonce are not rejected, +/// but rather get included in block eventually, their submission managed by the ethereum +/// API facade. +#[serial_test::serial] +#[tokio::test] +async fn test_out_of_order_mempool() { + const MAX_WAIT_TIME: Duration = Duration::from_secs(10); + const SLEEP_TIME: Duration = Duration::from_secs(1); + + let (testnet, cleanup) = make_testnet(MANIFEST, |_| {}).await.unwrap(); + + let res = async { + let bob = testnet.account("bob")?; + let charlie = testnet.account("charlie")?; + + let pangea = testnet.node(&testnet.root().node("pangea"))?; + let provider = pangea + .ethapi_http_provider()? + .expect("ethapi should be enabled"); + + let middleware = make_middleware(provider, bob) + .await + .context("failed to set up middleware")?; + + // Create the simplest transaction possible: send tokens between accounts. + let to: H160 = charlie.eth_addr().into(); + let tx = Eip1559TransactionRequest::new().to(to).value(1); + let mut tx: TypedTransaction = tx.into(); + + // Fill out the nonce, gas, etc. + middleware + .fill_transaction(&mut tx, None) + .await + .context("failed to fill tx")?; + + // Create a few more transactions to be sent out-of-order. + let mut txs = vec![tx]; + + for i in 1..5 { + let mut tx = txs[0].clone(); + let nonce = tx.nonce().expect("fill_transaction filled the nonce"); + tx.set_nonce(nonce.saturating_add(i.into())); + txs.push(tx) + } + + let mut pending_txs = Vec::new(); + + // Submit transactions in opposite order. + for (i, tx) in txs.iter().enumerate().rev() { + let sig = middleware + .signer() + .sign_transaction(tx) + .await + .context("failed to sign tx")?; + + let rlp = tx.rlp_signed(&sig); + + let pending_tx: PendingTransaction<_> = middleware + .send_raw_transaction(rlp) + .await + .with_context(|| format!("failed to send tx {i}"))?; + + pending_txs.push(pending_tx) + } + + // Check that they eventually get included. + let start = Instant::now(); + 'pending: loop { + for tx in pending_txs.iter() { + let receipt = middleware + .get_transaction_receipt(tx.tx_hash()) + .await + .context("failed to get receipt")?; + + if receipt.is_none() { + if start.elapsed() > MAX_WAIT_TIME { + bail!("some transactions are still not executed") + } else { + tokio::time::sleep(SLEEP_TIME).await; + continue 'pending; + } + } + } + // All of them have receipt. + break 'pending; + } + + Ok(()) + }; + + let res = res.await; + cleanup(res.is_err(), testnet).await; + res.unwrap() +}