use crate::{
    abi::{Greeter, Multicall, SimpleStorage},
    utils::{connect_pubsub, http_provider_with_signer},
};
use alloy_consensus::{Header, Transaction};
use alloy_network::{
    AnyRpcTransaction, EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionResponse,
    eip2718::Decodable2718,
};
use alloy_primitives::{
    Address, B256, Bytes, FixedBytes, TxHash, U64, U256, address, hex, map::B256HashSet,
};
use alloy_provider::{Provider, WsConnect};
use alloy_rlp::Decodable;
use alloy_rpc_types::{
    AccessList, AccessListItem, AccessListResult, BlockId, BlockNumberOrTag, BlockOverrides,
    BlockTransactions, TransactionRequest,
    state::{AccountOverride, EvmOverrides, StateOverride, StateOverridesBuilder},
};
use alloy_serde::WithOtherFields;
use alloy_sol_types::SolValue;
use anvil::{NodeConfig, spawn};
use anvil_core::eth::block::Block;
use eyre::Ok;
use foundry_evm::hardfork::EthereumHardfork;
use foundry_primitives::FoundryReceiptEnvelope;
use futures::{FutureExt, StreamExt, future::join_all};
use revm::primitives::eip7825::TX_GAS_LIMIT_CAP;
use std::{
    str::FromStr,
    time::{Duration, Instant},
};
use tokio::time::timeout;

#[tokio::test(flavor = "multi_thread")]
async fn can_transfer_eth() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let nonce = provider.get_transaction_count(from).await.unwrap();
    assert!(nonce == 0);

    let balance_before = provider.get_balance(to).await.unwrap();

    let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap();

    // craft the tx
    // specify the `from` field so that the client knows which account to use
    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    // broadcast it via the eth_sendTransaction API
    let tx = provider.send_transaction(tx).await.unwrap();

    let tx = tx.get_receipt().await.unwrap();

    assert_eq!(tx.block_number, Some(1));
    assert_eq!(tx.transaction_index, Some(0));

    let nonce = provider.get_transaction_count(from).await.unwrap();

    assert_eq!(nonce, 1);

    let to_balance = provider.get_balance(to).await.unwrap();

    assert_eq!(balance_before.saturating_add(amount), to_balance);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_pending_transactions() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    api.anvil_set_auto_mine(false).await.unwrap();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let sender = accounts[0].address();
    let other_sender = accounts[1].address();
    let recipient = accounts[2].address();

    let sender_tx =
        TransactionRequest::default().from(sender).to(recipient).value(U256::from(1)).nonce(0);
    let sender_tx = provider.send_transaction(WithOtherFields::new(sender_tx)).await.unwrap();

    let queued_tx =
        TransactionRequest::default().from(sender).to(recipient).value(U256::from(2)).nonce(2);
    let queued_tx = provider.send_transaction(WithOtherFields::new(queued_tx)).await.unwrap();

    let other_tx = TransactionRequest::default()
        .from(other_sender)
        .to(recipient)
        .value(U256::from(3))
        .nonce(0);
    let other_tx = provider.send_transaction(WithOtherFields::new(other_tx)).await.unwrap();

    let pending: Vec<AnyRpcTransaction> =
        provider.client().request("eth_pendingTransactions", ()).await.unwrap();
    let hashes = pending.iter().map(|tx| B256::from(*tx.inner.tx_hash())).collect::<B256HashSet>();

    assert_eq!(pending.len(), 2);
    assert!(hashes.contains(sender_tx.tx_hash()));
    assert!(hashes.contains(other_tx.tx_hash()));
    assert!(!hashes.contains(queued_tx.tx_hash()));
    assert!(pending.iter().any(|tx| tx.from() == sender));
    assert!(pending.iter().any(|tx| tx.from() == other_sender));
}

#[tokio::test(flavor = "multi_thread")]
async fn can_order_transactions() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    // disable automine
    api.anvil_set_auto_mine(false).await.unwrap();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap();

    let gas_price = provider.get_gas_price().await.unwrap();

    // craft the tx with lower price
    let mut tx = TransactionRequest::default().to(to).from(from).value(amount);

    tx.set_gas_price(gas_price);
    let tx = WithOtherFields::new(tx);
    let tx_lower = provider.send_transaction(tx).await.unwrap();

    // craft the tx with higher price
    let mut tx = TransactionRequest::default().to(from).from(to).value(amount);

    tx.set_gas_price(gas_price + 1);
    let tx = WithOtherFields::new(tx);
    let tx_higher = provider.send_transaction(tx).await.unwrap();

    // manually mine the block with the transactions
    api.mine_one().await.unwrap();

    let higher_price = tx_higher.get_receipt().await.unwrap().transaction_hash;
    let lower_price = tx_lower.get_receipt().await.unwrap().transaction_hash;

    // get the block, await receipts
    let block = provider.get_block(BlockId::latest()).await.unwrap().unwrap();

    assert_eq!(block.transactions, BlockTransactions::Hashes(vec![higher_price, lower_price]))
}

#[tokio::test(flavor = "multi_thread")]
async fn can_respect_nonces() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let nonce = provider.get_transaction_count(from).await.unwrap();
    let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap();

    let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce + 1);

    let tx = WithOtherFields::new(tx);

    // send the transaction with higher nonce than on chain
    let higher_pending_tx = provider.send_transaction(tx).await.unwrap();

    // ensure the listener for ready transactions times out
    let mut listener = api.new_ready_transactions();
    let res = timeout(Duration::from_millis(1500), listener.next()).await;
    res.unwrap_err();

    let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce);

    let tx = WithOtherFields::new(tx);
    // send with the actual nonce which is mined immediately
    let tx = provider.send_transaction(tx).await.unwrap();

    let tx = tx.get_receipt().await.unwrap();
    // this will unblock the currently pending tx
    let higher_tx = higher_pending_tx.get_receipt().await.unwrap(); // Awaits endlessly here due to alloy/#389

    let block = provider.get_block(1.into()).await.unwrap().unwrap();
    assert_eq!(2, block.transactions.len());
    assert_eq!(
        BlockTransactions::Hashes(vec![tx.transaction_hash, higher_tx.transaction_hash]),
        block.transactions
    );
}

#[tokio::test(flavor = "multi_thread")]
async fn can_replace_transaction() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    // disable auto mining
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let nonce = provider.get_transaction_count(from).await.unwrap();
    let gas_price = provider.get_gas_price().await.unwrap();
    let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap();

    let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce);

    let mut tx = WithOtherFields::new(tx);

    tx.set_gas_price(gas_price);
    // send transaction with lower gas price
    let _lower_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap();

    tx.set_gas_price(gas_price + 1);
    // send the same transaction with higher gas price
    let higher_priced_pending_tx = provider.send_transaction(tx).await.unwrap();

    let higher_tx_hash = *higher_priced_pending_tx.tx_hash();
    // mine exactly one block
    api.mine_one().await.unwrap();

    let block = provider.get_block(1.into()).await.unwrap().unwrap();

    assert_eq!(block.transactions.len(), 1);
    assert_eq!(BlockTransactions::Hashes(vec![higher_tx_hash]), block.transactions);

    // verify the higher priced transaction was included
    let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap();
    assert_eq!(higher_priced_receipt.transaction_hash, higher_tx_hash);

    // verify only one transaction was included in the block (lower priced was replaced)
    assert_eq!(1, block.transactions.len());
    assert_eq!(
        BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]),
        block.transactions
    );
}

#[tokio::test(flavor = "multi_thread")]
async fn can_resend_transaction() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let nonce = provider.get_transaction_count(from).await.unwrap();
    let gas_price = provider.get_gas_price().await.unwrap();
    let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap();

    let tx = TransactionRequest::default()
        .to(to)
        .value(amount)
        .from(from)
        .nonce(nonce)
        .gas_price(gas_price);

    let tx = WithOtherFields::new(tx);
    let original_pending_tx = provider.send_transaction(tx.clone()).await.unwrap();

    let replacement_gas_price = gas_price + 1;
    let replacement_gas_limit = 22_000;
    let replacement_hash: TxHash = provider
        .client()
        .request(
            "eth_resend",
            (tx, Some(U256::from(replacement_gas_price)), Some(U64::from(replacement_gas_limit))),
        )
        .await
        .unwrap();

    assert_ne!(*original_pending_tx.tx_hash(), replacement_hash);

    api.mine_one().await.unwrap();

    let block = provider.get_block(1.into()).await.unwrap().unwrap();
    assert_eq!(BlockTransactions::Hashes(vec![replacement_hash]), block.transactions);

    let replacement_tx: AnyRpcTransaction =
        provider.get_transaction_by_hash(replacement_hash).await.unwrap().unwrap();
    assert_eq!(replacement_tx.inner.tx_hash(), replacement_hash);
    assert_eq!(replacement_tx.inner.gas_limit(), replacement_gas_limit);
    assert_eq!(Transaction::max_fee_per_gas(&replacement_tx.inner), replacement_gas_price);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_reject_invalid_resend_transaction() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let nonce = provider.get_transaction_count(from).await.unwrap();
    let gas_price = provider.get_gas_price().await.unwrap();
    let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap();

    let missing_nonce_tx =
        TransactionRequest::default().to(to).value(amount).from(from).gas_price(gas_price);
    let missing_nonce_resend: Result<TxHash, _> = provider
        .client()
        .request("eth_resend", (WithOtherFields::new(missing_nonce_tx), None::<U256>, None::<U64>))
        .await;
    assert!(missing_nonce_resend.unwrap_err().to_string().contains("missing transaction nonce"));

    let tx = TransactionRequest::default()
        .to(to)
        .value(amount)
        .from(from)
        .nonce(nonce)
        .gas_price(gas_price);

    let not_in_pool_resend: Result<TxHash, _> = provider
        .client()
        .request("eth_resend", (WithOtherFields::new(tx.clone()), None::<U256>, None::<U64>))
        .await;
    assert!(not_in_pool_resend.unwrap_err().to_string().contains("transaction not found"));

    let mut tx = WithOtherFields::new(tx);
    tx.set_gas_price(gas_price + 1);
    let pending_tx = provider.send_transaction(tx.clone()).await.unwrap();

    let underpriced_resend: Result<TxHash, _> = provider
        .client()
        .request("eth_resend", (tx, Some(U256::from(gas_price)), None::<U64>))
        .await;
    assert!(
        underpriced_resend.unwrap_err().to_string().contains("replacement transaction underpriced")
    );

    api.mine_one().await.unwrap();
    pending_tx.get_receipt().await.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
async fn can_reject_too_high_gas_limits() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let gas_limit = api.gas_limit().to::<u64>();
    let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap();

    let tx =
        TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit);

    let mut tx = WithOtherFields::new(tx);

    // send transaction with the exact gas limit
    let pending = provider.send_transaction(tx.clone()).await.unwrap();

    let pending_receipt = pending.get_receipt().await;
    assert!(pending_receipt.is_ok());

    tx.set_gas_limit(gas_limit + 1);

    // send transaction with higher gas limit
    let pending = provider.send_transaction(tx.clone()).await;

    assert!(pending.is_err());
    let err = pending.unwrap_err();
    assert!(err.to_string().contains("gas too high"));

    api.anvil_set_balance(from, U256::MAX).await.unwrap();

    tx.set_gas_limit(gas_limit);
    let pending = provider.send_transaction(tx).await;
    let _ = pending.unwrap();
}

// <https://github.com/foundry-rs/foundry/issues/8094>
#[tokio::test(flavor = "multi_thread")]
async fn can_mine_large_gas_limit() {
    let (_, handle) = spawn(NodeConfig::test().disable_block_gas_limit(true)).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let gas_limit = anvil::DEFAULT_GAS_LIMIT;
    let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap();

    let tx =
        TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit);

    // send transaction with higher gas limit
    let pending = provider.send_transaction(WithOtherFields::new(tx)).await.unwrap();

    let _resp = pending.get_receipt().await.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
async fn can_reject_underpriced_replacement() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    // disable auto mining
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let nonce = provider.get_transaction_count(from).await.unwrap();
    let gas_price = provider.get_gas_price().await.unwrap();
    let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap();

    let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce);

    let mut tx = WithOtherFields::new(tx);

    tx.set_gas_price(gas_price + 1);
    // send transaction with higher gas price
    let higher_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap();

    tx.set_gas_price(gas_price);
    // send the same transaction with lower gas price
    let lower_priced_pending_tx = provider.send_transaction(tx).await;

    let replacement_err = lower_priced_pending_tx.unwrap_err();
    assert!(replacement_err.to_string().contains("replacement transaction underpriced"));

    // mine exactly one block
    api.mine_one().await.unwrap();
    let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap();

    // ensure that only the higher priced tx was mined
    let block = provider.get_block(1.into()).await.unwrap().unwrap();
    assert_eq!(1, block.transactions.len());
    assert_eq!(
        BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]),
        block.transactions
    );
}

#[tokio::test(flavor = "multi_thread")]
async fn can_deploy_greeter_http() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let wallet = handle.dev_wallets().next().unwrap();

    let signer: EthereumWallet = wallet.clone().into();

    let alloy_provider = http_provider_with_signer(&handle.http_endpoint(), signer);

    let alloy_greeter_addr =
        Greeter::deploy_builder(alloy_provider.clone(), "Hello World!".to_string())
            // .legacy() unimplemented! in alloy
            .deploy()
            .await
            .unwrap();

    let alloy_greeter = Greeter::new(alloy_greeter_addr, alloy_provider);

    let greeting = alloy_greeter.greet().call().await.unwrap();

    assert_eq!("Hello World!", greeting);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_deploy_and_mine_manually() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    // can mine in auto-mine mode
    api.evm_mine(None).await.unwrap();
    // disable auto mine
    api.anvil_set_auto_mine(false).await.unwrap();
    // can mine in manual mode
    api.evm_mine(None).await.unwrap();

    let provider = handle.http_provider();

    let wallet = handle.dev_wallets().next().unwrap();
    let from = wallet.address();

    let greeter_builder =
        Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()).from(from);
    let greeter_calldata = greeter_builder.calldata();

    let tx = TransactionRequest::default().from(from).with_input(greeter_calldata.to_owned());

    let tx = WithOtherFields::new(tx);

    let tx = provider.send_transaction(tx).await.unwrap();

    // mine block with tx manually
    api.evm_mine(None).await.unwrap();

    let receipt = tx.get_receipt().await.unwrap();

    let address = receipt.contract_address.unwrap();
    let greeter_contract = Greeter::new(address, provider);
    let greeting = greeter_contract.greet().call().await.unwrap();
    assert_eq!("Hello World!", greeting);

    let set_greeting = greeter_contract.setGreeting("Another Message".to_string());
    let tx = set_greeting.send().await.unwrap();

    // mine block manually
    api.evm_mine(None).await.unwrap();

    let _tx = tx.get_receipt().await.unwrap();
    let greeting = greeter_contract.greet().call().await.unwrap();
    assert_eq!("Another Message", greeting);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_mine_automatically() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    // disable auto mine
    api.anvil_set_auto_mine(false).await.unwrap();

    let wallet = handle.dev_wallets().next().unwrap();

    let greeter_builder = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string())
        .from(wallet.address());

    let greeter_calldata = greeter_builder.calldata();

    let tx = TransactionRequest::default()
        .from(wallet.address())
        .with_input(greeter_calldata.to_owned());

    let tx = WithOtherFields::new(tx);

    let sent_tx = provider.send_transaction(tx).await.unwrap();

    // re-enable auto mine
    api.anvil_set_auto_mine(true).await.unwrap();

    let receipt = sent_tx.get_receipt().await.unwrap();
    assert_eq!(receipt.block_number, Some(1));
}

#[tokio::test(flavor = "multi_thread")]
async fn can_call_greeter_historic() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let wallet = handle.dev_wallets().next().unwrap();

    let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string())
        .from(wallet.address())
        .deploy()
        .await
        .unwrap();

    let greeter_contract = Greeter::new(greeter_addr, provider.clone());

    let greeting = greeter_contract.greet().call().await.unwrap();
    assert_eq!("Hello World!", greeting);

    let block_number = provider.get_block_number().await.unwrap();

    let _receipt = greeter_contract
        .setGreeting("Another Message".to_string())
        .send()
        .await
        .unwrap()
        .get_receipt()
        .await
        .unwrap();

    let greeting = greeter_contract.greet().call().await.unwrap();
    assert_eq!("Another Message", greeting);

    // min
    api.mine_one().await.unwrap();

    // returns previous state
    let greeting =
        greeter_contract.greet().block(BlockId::Number(block_number.into())).call().await.unwrap();
    assert_eq!("Hello World!", greeting);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_deploy_greeter_ws() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.ws_provider();

    let wallet = handle.dev_wallets().next().unwrap();

    let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string())
        .from(wallet.address())
        // .legacy() unimplemented! in alloy
        .deploy()
        .await
        .unwrap();

    let greeter_contract = Greeter::new(greeter_addr, provider.clone());

    let greeting = greeter_contract.greet().call().await.unwrap();
    assert_eq!("Hello World!", greeting);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_deploy_get_code() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.ws_provider();

    let wallet = handle.dev_wallets().next().unwrap();

    let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string())
        .from(wallet.address())
        .deploy()
        .await
        .unwrap();

    let code = provider.get_code_at(greeter_addr).await.unwrap();
    assert!(!code.as_ref().is_empty());
}

#[tokio::test(flavor = "multi_thread")]
async fn get_blocktimestamp_works() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let contract = Multicall::deploy(provider.clone()).await.unwrap();

    let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap();

    assert!(timestamp > U256::from(1));

    let latest_block =
        api.block_by_number(alloy_rpc_types::BlockNumberOrTag::Latest).await.unwrap().unwrap();

    let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap();
    assert_eq!(timestamp.to::<u64>(), latest_block.header.timestamp);

    // repeat call same result
    let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap();
    assert_eq!(timestamp.to::<u64>(), latest_block.header.timestamp);

    // mock timestamp
    let next_timestamp = timestamp.to::<u64>() + 1337;
    api.evm_set_next_block_timestamp(next_timestamp).unwrap();

    let timestamp =
        contract.getCurrentBlockTimestamp().block(BlockId::pending()).call().await.unwrap();
    assert_eq!(timestamp, U256::from(next_timestamp));

    // repeat call same result
    let timestamp =
        contract.getCurrentBlockTimestamp().block(BlockId::pending()).call().await.unwrap();
    assert_eq!(timestamp, U256::from(next_timestamp));
}

#[tokio::test(flavor = "multi_thread")]
async fn call_past_state() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let wallet = handle.dev_wallets().next().unwrap();
    let contract_addr =
        SimpleStorage::deploy_builder(provider.clone(), "initial value".to_string())
            .from(wallet.address())
            .deploy()
            .await
            .unwrap();

    let contract = SimpleStorage::new(contract_addr, provider.clone());

    let deployed_block = provider.get_block_number().await.unwrap();

    let value = contract.getValue().call().await.unwrap();
    assert_eq!(value, "initial value");

    let gas_price = api.gas_price();
    let set_tx = contract.setValue("hi".to_string()).gas_price(gas_price + 1);

    let _receipt = set_tx.send().await.unwrap().get_receipt().await.unwrap();

    // assert new value
    let value = contract.getValue().call().await.unwrap();
    assert_eq!(value, "hi");

    // assert previous value
    let value =
        contract.getValue().block(BlockId::Number(deployed_block.into())).call().await.unwrap();
    assert_eq!(value, "initial value");

    let hash = provider.get_block(BlockId::Number(1.into())).await.unwrap().unwrap().header.hash;
    let value = contract.getValue().block(BlockId::Hash(hash.into())).call().await.unwrap();
    assert_eq!(value, "initial value");
}

#[tokio::test(flavor = "multi_thread")]
async fn can_handle_multiple_concurrent_transfers_with_same_nonce() {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    let provider = handle.ws_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let nonce = provider.get_transaction_count(from).await.unwrap();

    // explicitly set the nonce
    let tx = TransactionRequest::default()
        .to(to)
        .value(U256::from(100))
        .from(from)
        .nonce(nonce)
        .with_gas_limit(21000);

    let tx = WithOtherFields::new(tx);

    let mut tasks = Vec::new();
    for _ in 0..10 {
        let tx = tx.clone();
        let provider = provider.clone();
        let task = tokio::task::spawn(async move {
            provider.send_transaction(tx).await.unwrap().get_receipt().await
        });
        tasks.push(task);
    }

    // only one succeeded
    let successful_tx =
        join_all(tasks).await.into_iter().filter(|res| res.as_ref().is_ok()).count();
    assert_eq!(successful_tx, 1);

    assert_eq!(provider.get_transaction_count(from).await.unwrap(), 1u64);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_handle_multiple_concurrent_deploys_with_same_nonce() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.ws_provider();

    let wallet = handle.dev_wallets().next().unwrap();
    let from = wallet.address();
    let nonce = provider.get_transaction_count(from).await.unwrap();

    let mut tasks = Vec::new();

    let greeter = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string());

    let greeter_calldata = greeter.calldata();

    let tx = TransactionRequest::default()
        .from(from)
        .with_input(greeter_calldata.to_owned())
        .nonce(nonce)
        .with_gas_limit(300_000);

    let tx = WithOtherFields::new(tx);

    for _ in 0..10 {
        let provider = provider.clone();
        let tx = tx.clone();
        let task = tokio::task::spawn(async move {
            Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap())
        });
        tasks.push(task);
    }

    // only one succeeded
    let successful_tx =
        join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count();
    assert_eq!(successful_tx, 1);
    assert_eq!(provider.get_transaction_count(from).await.unwrap(), 1u64);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_handle_multiple_concurrent_transactions_with_same_nonce() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.ws_provider();

    let wallet = handle.dev_wallets().next().unwrap();
    let from = wallet.address();

    let greeter_contract =
        Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap();

    let nonce = provider.get_transaction_count(from).await.unwrap();

    let mut tasks = Vec::new();

    let deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string());
    let deploy_calldata = deploy.calldata();
    let deploy_tx = TransactionRequest::default()
        .from(from)
        .with_input(deploy_calldata.to_owned())
        .nonce(nonce)
        .with_gas_limit(300_000);
    let deploy_tx = WithOtherFields::new(deploy_tx);

    let set_greeting = greeter_contract.setGreeting("Hello".to_string());
    let set_greeting_calldata = set_greeting.calldata();

    let set_greeting_tx = TransactionRequest::default()
        .from(from)
        .with_input(set_greeting_calldata.to_owned())
        .nonce(nonce)
        .with_gas_limit(300_000);
    let set_greeting_tx = WithOtherFields::new(set_greeting_tx);

    for idx in 0..10 {
        let provider = provider.clone();
        let task = if idx % 2 == 0 {
            let tx = deploy_tx.clone();
            tokio::task::spawn(async move {
                Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap())
            })
        } else {
            let tx = set_greeting_tx.clone();
            tokio::task::spawn(async move {
                Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap())
            })
        };

        tasks.push(task);
    }

    // only one succeeded
    let successful_tx =
        join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count();
    assert_eq!(successful_tx, 1);
    assert_eq!(provider.get_transaction_count(from).await.unwrap(), nonce + 1);
}
#[tokio::test(flavor = "multi_thread")]
async fn can_get_pending_transaction() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    // disable auto mining so we can check if we can return pending tx from the mempool
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();

    let from = handle.dev_wallets().next().unwrap().address();
    let tx = TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random());
    let tx = WithOtherFields::new(tx);
    let tx = provider.send_transaction(tx).await.unwrap();

    let pending = provider.get_transaction_by_hash(*tx.tx_hash()).await;
    assert!(pending.is_ok());

    api.mine_one().await.unwrap();
    let mined = provider.get_transaction_by_hash(*tx.tx_hash()).await.unwrap().unwrap();

    assert_eq!(mined.tx_hash(), pending.unwrap().unwrap().tx_hash());
}

#[tokio::test(flavor = "multi_thread")]
async fn can_listen_full_pending_transaction() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    // Disable auto-mining so transactions remain pending
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = alloy_provider::ProviderBuilder::new()
        .connect_ws(WsConnect::new(handle.ws_endpoint()))
        .await
        .unwrap();

    // Subscribe to full pending transactions
    let sub = provider.subscribe_full_pending_transactions().await;
    tokio::time::sleep(Duration::from_millis(1000)).await;
    let mut stream = sub.expect("Failed to subscribe to pending tx").into_stream().take(5);

    let from = handle.dev_wallets().next().unwrap().address();
    let tx = TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random());

    let tx = provider.send_transaction(tx).await.unwrap();

    // Wait for the subscription to yield a transaction
    let received = stream.next().await.expect("Failed to receive pending tx");

    assert_eq!(received.tx_hash(), *tx.tx_hash());
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_raw_transaction() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    // first test the pending tx, disable auto mine
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();

    let from = handle.dev_wallets().next().unwrap().address();
    let tx = TransactionRequest::default().from(from).value(U256::from(1488)).to(Address::random());
    let tx = WithOtherFields::new(tx);
    let tx = provider.send_transaction(tx).await.unwrap();

    let res1 = api.raw_transaction(*tx.tx_hash()).await;
    assert!(res1.is_ok());

    api.mine_one().await.unwrap();
    let res2 = api.raw_transaction(*tx.tx_hash()).await;

    assert_eq!(res1.unwrap(), res2.unwrap());
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_raw_receipts() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();
    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let first = TransactionRequest::default()
        .from(accounts[0].address())
        .value(U256::from(1))
        .to(Address::random());
    let second = TransactionRequest::default()
        .from(accounts[1].address())
        .value(U256::from(2))
        .to(Address::random());

    let first = provider.send_transaction(WithOtherFields::new(first)).await.unwrap();
    let second = provider.send_transaction(WithOtherFields::new(second)).await.unwrap();

    api.mine_one().await.unwrap();
    let first_receipt = first.get_receipt().await.unwrap();
    let second_receipt = second.get_receipt().await.unwrap();
    assert_eq!(first_receipt.block_number, Some(1));
    assert_eq!(second_receipt.block_number, Some(1));

    let block = provider.get_block(BlockId::number(1)).await.unwrap().unwrap();
    let raw_by_number: Vec<Bytes> =
        provider.client().request("debug_getRawReceipts", (BlockId::number(1),)).await.unwrap();
    let raw_by_hash: Vec<Bytes> = provider
        .client()
        .request("debug_getRawReceipts", (BlockId::hash(block.header.hash),))
        .await
        .unwrap();
    let missing: Result<Vec<Bytes>, _> =
        provider.client().request("debug_getRawReceipts", (BlockId::number(999),)).await;

    assert_eq!(raw_by_number, raw_by_hash);
    assert_eq!(raw_by_number.len(), 2);
    assert!(missing.is_err());

    let mut first_raw = raw_by_number[0].as_ref();
    let first_decoded = FoundryReceiptEnvelope::decode_2718(&mut first_raw).unwrap();
    let mut second_raw = raw_by_number[1].as_ref();
    let second_decoded = FoundryReceiptEnvelope::decode_2718(&mut second_raw).unwrap();

    assert!(first_decoded.status());
    assert!(second_decoded.status());
    assert_eq!(first_decoded.cumulative_gas_used(), 21_000);
    assert_eq!(second_decoded.cumulative_gas_used(), 42_000);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_raw_transactions() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();
    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let first = TransactionRequest::default()
        .from(accounts[0].address())
        .value(U256::from(1))
        .to(Address::random());
    let second = TransactionRequest::default()
        .from(accounts[1].address())
        .value(U256::from(2))
        .to(Address::random());

    let first = provider.send_transaction(WithOtherFields::new(first)).await.unwrap();
    let second = provider.send_transaction(WithOtherFields::new(second)).await.unwrap();
    let first_hash = *first.tx_hash();
    let second_hash = *second.tx_hash();

    api.mine_one().await.unwrap();
    let first_receipt = first.get_receipt().await.unwrap();
    let second_receipt = second.get_receipt().await.unwrap();
    assert_eq!(first_receipt.block_number, Some(1));
    assert_eq!(second_receipt.block_number, Some(1));

    let block = provider.get_block(BlockId::number(1)).await.unwrap().unwrap();
    let raw_by_number: Vec<Bytes> =
        provider.client().request("debug_getRawTransactions", (BlockId::number(1),)).await.unwrap();
    let raw_by_hash: Vec<Bytes> = provider
        .client()
        .request("debug_getRawTransactions", (BlockId::hash(block.header.hash),))
        .await
        .unwrap();
    let missing: Vec<Bytes> = provider
        .client()
        .request("debug_getRawTransactions", (BlockId::number(999),))
        .await
        .unwrap();

    assert_eq!(raw_by_number, raw_by_hash);
    assert_eq!(raw_by_number.len(), 2);
    assert!(missing.is_empty());

    let first_raw = api.raw_transaction(first_hash).await.unwrap().unwrap();
    let second_raw = api.raw_transaction(second_hash).await.unwrap().unwrap();
    assert!(raw_by_number.contains(&first_raw));
    assert!(raw_by_number.contains(&second_raw));
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_raw_header() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let from = handle.dev_wallets().next().unwrap().address();
    let tx = TransactionRequest::default().from(from).value(U256::from(1)).to(Address::random());
    provider.send_transaction(WithOtherFields::new(tx)).await.unwrap().get_receipt().await.unwrap();

    let block = provider.get_block(BlockId::number(1)).await.unwrap().unwrap();
    let raw_by_number: Bytes =
        provider.client().request("debug_getRawHeader", (BlockId::number(1),)).await.unwrap();
    let raw_by_hash: Bytes = provider
        .client()
        .request("debug_getRawHeader", (BlockId::hash(block.header.hash),))
        .await
        .unwrap();

    assert_eq!(raw_by_number, raw_by_hash);
    assert!(!raw_by_number.is_empty());

    let decoded = Header::decode(&mut raw_by_number.as_ref()).unwrap();
    assert_eq!(decoded.number, 1);
    assert_eq!(decoded.hash_slow(), block.header.hash);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_raw_block() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let from = handle.dev_wallets().next().unwrap().address();
    let tx = TransactionRequest::default().from(from).value(U256::from(1)).to(Address::random());
    provider.send_transaction(WithOtherFields::new(tx)).await.unwrap().get_receipt().await.unwrap();

    let block = provider.get_block(BlockId::number(1)).await.unwrap().unwrap();
    let raw_by_number: Bytes =
        provider.client().request("debug_getRawBlock", (BlockId::number(1),)).await.unwrap();
    let raw_by_hash: Bytes = provider
        .client()
        .request("debug_getRawBlock", (BlockId::hash(block.header.hash),))
        .await
        .unwrap();

    assert_eq!(raw_by_number, raw_by_hash);
    assert!(!raw_by_number.is_empty());

    let decoded: Block = Block::decode(&mut raw_by_number.as_ref()).unwrap();
    assert_eq!(decoded.header.hash_slow(), block.header.hash);
    assert_eq!(decoded.header.number, 1);
    assert_eq!(decoded.body.transactions.len(), 1);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_first_nonce_is_zero() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();
    let from = handle.dev_wallets().next().unwrap().address();

    let nonce = provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap();

    assert_eq!(nonce, 0);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_handle_different_sender_nonce_calculation() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();
    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from_first = accounts[0].address();
    let from_second = accounts[1].address();

    let tx_count = 10u64;

    // send a bunch of tx to the mempool and check nonce is returned correctly
    for idx in 1..=tx_count {
        let tx_from_first = TransactionRequest::default()
            .from(from_first)
            .value(U256::from(1337u64))
            .to(Address::random());
        let tx_from_first = WithOtherFields::new(tx_from_first);
        let _tx = provider.send_transaction(tx_from_first).await.unwrap();
        let nonce_from_first =
            provider.get_transaction_count(from_first).block_id(BlockId::pending()).await.unwrap();
        assert_eq!(nonce_from_first, idx);

        let tx_from_second = TransactionRequest::default()
            .from(from_second)
            .value(U256::from(1337u64))
            .to(Address::random());
        let tx_from_second = WithOtherFields::new(tx_from_second);
        let _tx = provider.send_transaction(tx_from_second).await.unwrap();
        let nonce_from_second =
            provider.get_transaction_count(from_second).block_id(BlockId::pending()).await.unwrap();
        assert_eq!(nonce_from_second, idx);
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn includes_pending_tx_for_transaction_count() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();
    let from = handle.dev_wallets().next().unwrap().address();

    let tx_count = 10u64;

    // send a bunch of tx to the mempool and check nonce is returned correctly
    for idx in 1..=tx_count {
        let tx =
            TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random());
        let tx = WithOtherFields::new(tx);
        let _tx = provider.send_transaction(tx).await.unwrap();
        let nonce =
            provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap();
        assert_eq!(nonce, idx);
    }

    api.mine_one().await.unwrap();
    let nonce = provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap();
    assert_eq!(nonce, tx_count);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_historic_info() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from = accounts[0].address();
    let to = accounts[1].address();

    let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap();
    let tx = TransactionRequest::default().to(to).value(amount).from(from);
    let tx = WithOtherFields::new(tx);
    let tx = provider.send_transaction(tx).await.unwrap();
    let _ = tx.get_receipt().await.unwrap();

    let nonce_pre =
        provider.get_transaction_count(from).block_id(BlockId::number(0)).await.unwrap();

    let nonce_post = provider.get_transaction_count(from).await.unwrap();

    assert!(nonce_pre < nonce_post);

    let balance_pre = provider.get_balance(from).block_id(BlockId::number(0)).await.unwrap();

    let balance_post = provider.get_balance(from).await.unwrap();

    assert!(balance_post < balance_pre);

    let to_balance = provider.get_balance(to).await.unwrap();
    assert_eq!(balance_pre.saturating_add(amount), to_balance);
}

// <https://github.com/eth-brownie/brownie/issues/1549>
#[tokio::test(flavor = "multi_thread")]
async fn test_tx_receipt() {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    let wallet = handle.dev_wallets().next().unwrap();
    let provider = handle.http_provider();

    let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337));

    let tx = WithOtherFields::new(tx);
    let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
    assert!(tx.to.is_some());

    let greeter_deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string());
    let greeter_calldata = greeter_deploy.calldata();

    let tx = TransactionRequest::default()
        .from(wallet.address())
        .with_input(greeter_calldata.to_owned());

    let tx = WithOtherFields::new(tx);
    let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();

    // `to` field is none if it's a contract creation transaction: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
    assert!(tx.to.is_none());
    assert!(tx.contract_address.is_some());
}

// <https://github.com/foundry-rs/foundry/issues/12837>
#[tokio::test(flavor = "multi_thread")]
async fn test_reverted_contract_creation_has_contract_address() {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    let provider = handle.http_provider();
    let wallet = handle.dev_wallets().next().unwrap();

    // Init code that immediately reverts: PUSH1 0x00 PUSH1 0x00 REVERT (0x60006000fd)
    let reverting_init_code = hex!("60006000fd");

    let tx = TransactionRequest::default()
        .from(wallet.address())
        .with_input(reverting_init_code.to_vec());

    let tx = WithOtherFields::new(tx);
    let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();

    // Transaction should have reverted
    assert!(!receipt.status());

    // `to` field should be none (contract creation)
    assert!(receipt.to.is_none());

    // `contractAddress` should still be set even though the transaction reverted
    // This matches geth's behavior: https://github.com/ethereum/go-ethereum/issues/27937
    assert!(
        receipt.contract_address.is_some(),
        "contractAddress should be set for reverted contract creation"
    );

    // Verify the computed address is correct (sender.create(nonce))
    let expected_addr = wallet.address().create(0);
    assert_eq!(receipt.contract_address, Some(expected_addr));
}

#[tokio::test(flavor = "multi_thread")]
async fn can_stream_pending_transactions() {
    let (_api, handle) =
        spawn(NodeConfig::test().with_blocktime(Some(Duration::from_secs(2)))).await;
    let num_txs = 5;

    let provider = handle.http_provider();
    let ws_provider = connect_pubsub(&handle.ws_endpoint()).await;

    let accounts = provider.get_accounts().await.unwrap();
    let tx =
        TransactionRequest::default().from(accounts[0]).to(accounts[0]).value(U256::from(1e18));

    let mut sending = futures::future::join_all(
        std::iter::repeat_n(tx.clone(), num_txs)
            .enumerate()
            .map(|(nonce, tx)| tx.nonce(nonce as u64))
            .map(|tx| async {
                let tx = WithOtherFields::new(tx);
                provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap()
            }),
    )
    .fuse();

    let mut watch_tx_stream = provider
        .watch_pending_transactions()
        .await
        .unwrap()
        .into_stream()
        .flat_map(futures::stream::iter)
        .take(num_txs)
        .fuse();

    let mut sub_tx_stream = ws_provider
        .subscribe_pending_transactions()
        .await
        .unwrap()
        .into_stream()
        .take(num_txs)
        .fuse();

    let mut sent = None;
    let mut watch_received = Vec::with_capacity(num_txs);
    let mut sub_received = Vec::with_capacity(num_txs);

    loop {
        futures::select! {
            txs = sending => {
                sent = Some(txs)
            },
            tx = watch_tx_stream.next() => {
                if let Some(tx) = tx {
                    watch_received.push(tx);
                }
            },
            tx = sub_tx_stream.next() => {
                if let Some(tx) = tx {
                    sub_received.push(tx);
                }
            },
            complete => unreachable!(),
        };

        if watch_received.len() == num_txs
            && sub_received.len() == num_txs
            && let Some(sent) = &sent
        {
            assert_eq!(sent.len(), watch_received.len());
            let sent_txs = sent.iter().map(|tx| tx.transaction_hash).collect::<B256HashSet>();
            assert_eq!(sent_txs, watch_received.iter().copied().collect());
            assert_eq!(sent_txs, sub_received.iter().copied().collect());
            break;
        }
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn test_tx_access_list() {
    /// returns a String representation of the AccessList, with sorted
    /// keys (address) and storage slots
    fn access_list_to_sorted_string(a: AccessList) -> String {
        let mut a = a.0;
        a.sort_by_key(|v| v.address);

        let a = a
            .iter_mut()
            .map(|v| {
                v.storage_keys.sort();
                (v.address, std::mem::take(&mut v.storage_keys))
            })
            .collect::<Vec<_>>();

        format!("{a:?}")
    }

    /// asserts that the two access lists are equal, by comparing their sorted
    /// string representation
    fn assert_access_list_eq(a: AccessList, b: AccessList) {
        assert_eq!(access_list_to_sorted_string(a), access_list_to_sorted_string(b))
    }

    // We want to test a couple of things:
    //     - When calling a contract with no storage read/write, it shouldn't be in the AL
    //     - When a contract calls a contract, the latter one should be in the AL
    //     - No precompiles should be in the AL
    //     - The sender shouldn't be in the AL
    let (_api, handle) = spawn(NodeConfig::test()).await;

    let provider = handle.http_provider();

    let sender = Address::random();
    let other_acc = Address::random();
    let multicall = Multicall::deploy(provider.clone()).await.unwrap();
    let simple_storage = SimpleStorage::deploy(provider.clone(), "foo".to_string()).await.unwrap();

    // Mirrors execution-apis `create-al-contract.io`:
    // https://github.com/ethereum/execution-apis/blob/8d6b784cc57047ef10e4044ec2efc1c60950dd7f/tests/eth_createAccessList/create-al-contract.io
    // when calling `setValue` on SimpleStorage, both the `lastSender` and `_value` storages are
    // modified The `_value` is a `string`, so the storage slots here (small string) are `0x1`
    // and `keccak(0x1)`
    let set_value = simple_storage.setValue("bar".to_string());
    let set_value_calldata = set_value.calldata();
    let set_value_tx = TransactionRequest::default()
        .from(sender)
        .to(*simple_storage.address())
        .with_input(set_value_calldata.to_owned());
    let set_value_tx = WithOtherFields::new(set_value_tx);
    let access_list = provider.create_access_list(&set_value_tx).await.unwrap();
    assert_access_list_eq(
        access_list.access_list,
        AccessList::from(vec![AccessListItem {
            address: *simple_storage.address(),
            storage_keys: vec![
                FixedBytes::ZERO,
                FixedBytes::with_last_byte(1),
                FixedBytes::from_str(
                    "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6",
                )
                .unwrap(),
            ],
        }]),
    );

    // With a subcall that fetches the balances of an account (`other_acc`), only the address
    // of this account should be in the Access List
    let call_tx = multicall.getEthBalance(other_acc);
    let call_tx_data = call_tx.calldata();
    let call_tx = TransactionRequest::default()
        .from(sender)
        .to(*multicall.address())
        .with_input(call_tx_data.to_owned());
    let call_tx = WithOtherFields::new(call_tx);
    let access_list = provider.create_access_list(&call_tx).await.unwrap();
    assert_access_list_eq(
        access_list.access_list,
        AccessList::from(vec![AccessListItem { address: other_acc, storage_keys: vec![] }]),
    );

    // With a subcall to another contract, the AccessList should be the same as when calling the
    // subcontract directly (given that the proxy contract doesn't read/write any state)
    let subcall_tx = multicall.aggregate(vec![Multicall::Call {
        target: *simple_storage.address(),
        callData: set_value_calldata.to_owned(),
    }]);

    let subcall_tx_calldata = subcall_tx.calldata();

    let subcall_tx = TransactionRequest::default()
        .from(sender)
        .to(*multicall.address())
        .with_input(subcall_tx_calldata.to_owned());
    let subcall_tx = WithOtherFields::new(subcall_tx);
    let access_list = provider.create_access_list(&subcall_tx).await.unwrap();
    assert_access_list_eq(
        access_list.access_list,
        AccessList::from(vec![AccessListItem {
            address: *simple_storage.address(),
            storage_keys: vec![
                FixedBytes::ZERO,
                FixedBytes::with_last_byte(1),
                FixedBytes::from_str(
                    "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6",
                )
                .unwrap(),
            ],
        }]),
    );

    // Mirrors execution-apis `create-al-abi-revert.io`:
    // https://github.com/ethereum/execution-apis/blob/8d6b784cc57047ef10e4044ec2efc1c60950dd7f/tests/eth_createAccessList/create-al-abi-revert.io
    // eth_createAccessList should return a successful RPC result for reverted execution,
    // preserving the generated access list and reporting the revert in the result object.
    //
    // Runtime:
    //   60 00      PUSH1 0x00       ; calldata offset
    //   35         CALLDATALOAD     ; read the 32-byte storage slot from calldata
    //   54         SLOAD            ; access that slot so it appears in the access list
    //   60 00      PUSH1 0x00       ; revert data offset
    //   60 00      PUSH1 0x00       ; revert data length
    //   fd         REVERT           ; fail after the storage read
    let reverter_initcode =
        Bytes::from(hex!("6009600c60003960096000f36000355460006000fd").to_vec());
    let funded_sender = handle.dev_accounts().next().unwrap();
    let deploy_reverter_tx =
        TransactionRequest::default().from(funded_sender).with_deploy_code(reverter_initcode);
    let reverter_receipt = provider
        .send_transaction(WithOtherFields::new(deploy_reverter_tx))
        .await
        .unwrap()
        .get_receipt()
        .await
        .unwrap();
    let reverter = reverter_receipt.contract_address.unwrap();

    let slot =
        FixedBytes::from_str("0x00000000000000000000000000000000000000000000000000000000000042ff")
            .unwrap();
    let reverter_call_tx = TransactionRequest::default()
        .from(funded_sender)
        .to(reverter)
        .input(Bytes::from(slot.to_vec()).into());
    let reverter_call_tx = WithOtherFields::new(reverter_call_tx);
    let access_list = provider.create_access_list(&reverter_call_tx).await.unwrap();

    assert_eq!(access_list.error.as_deref(), Some("execution reverted"));
    assert!(access_list.gas_used > U256::ZERO);
    assert_access_list_eq(
        access_list.access_list,
        AccessList::from(vec![AccessListItem { address: reverter, storage_keys: vec![slot] }]),
    );
}

#[tokio::test(flavor = "multi_thread")]
async fn can_create_access_list_with_state_override() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let sender = Address::random();
    let target = Address::random();
    let tx = TransactionRequest::default().from(sender).to(target);
    let tx = WithOtherFields::new(tx);

    // PUSH1 0; SLOAD; STOP.
    let code = Bytes::from(hex!("60005400").to_vec());
    let state_override = StateOverridesBuilder::default()
        .append(target, AccountOverride::default().with_code(code.to_vec()))
        .build();

    let access_list: AccessListResult = provider
        .client()
        .request("eth_createAccessList", (tx, None::<BlockId>, state_override))
        .await
        .unwrap();

    assert_eq!(access_list.access_list.0.len(), 1);
    let item = access_list.access_list.0.first().unwrap();
    assert_eq!(item.address, target);
    assert_eq!(item.storage_keys, vec![FixedBytes::ZERO]);
}

// ensures that the gas estimate is running on pending block by default
#[tokio::test(flavor = "multi_thread")]
async fn estimates_gas_on_pending_by_default() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    // disable auto mine
    api.anvil_set_auto_mine(false).await.unwrap();

    let provider = handle.http_provider();

    let wallet = handle.dev_wallets().next().unwrap();
    let sender = wallet.address();
    let recipient = Address::random();

    let tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18));
    let tx = WithOtherFields::new(tx);

    let _pending = provider.send_transaction(tx).await.unwrap();

    let tx = TransactionRequest::default()
        .from(recipient)
        .to(sender)
        .value(U256::from(1e10))
        .input(Bytes::from(vec![0x42]).into());
    api.estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::default()).await.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
async fn test_estimate_gas() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    let wallet = handle.dev_wallets().next().unwrap();
    let sender = wallet.address();
    let recipient = Address::random();

    let tx = TransactionRequest::default()
        .from(recipient)
        .to(sender)
        .value(U256::from(1e10))
        .input(Bytes::from(vec![0x42]).into());
    // Expect the gas estimation to fail due to insufficient funds.
    let error_result =
        api.estimate_gas(WithOtherFields::new(tx.clone()), None, EvmOverrides::default()).await;

    assert!(error_result.is_err(), "Expected an error due to insufficient funds");
    let error_message = error_result.unwrap_err().to_string();
    assert!(
        error_message.contains("Insufficient funds for gas * price + value"),
        "Error message did not match expected: {error_message}"
    );

    // Setup state override to simulate sufficient funds for the recipient.
    let addr = recipient;
    let account_override =
        AccountOverride { balance: Some(alloy_primitives::U256::from(1e18)), ..Default::default() };
    let mut state_override = StateOverride::default();
    state_override.insert(addr, account_override);

    // Estimate gas with state override implying sufficient funds.
    let gas_estimate = api
        .estimate_gas(WithOtherFields::new(tx), None, EvmOverrides::new(Some(state_override), None))
        .await
        .expect("Failed to estimate gas with state override");

    // Assert the gas estimate meets the expected minimum.
    assert!(gas_estimate >= U256::from(21000), "Gas estimate is lower than expected minimum");
}

#[tokio::test(flavor = "multi_thread")]
async fn test_block_override() {
    let (api, handle) = spawn(NodeConfig::test()).await;

    let wallet = handle.dev_wallets().next().unwrap();
    let sender = wallet.address();
    let recipient = Address::random();

    let tx = TransactionRequest::default()
        .from(sender)
        .to(recipient)
        .input(Bytes::from(hex!("42cbb15c").to_vec()).into());

    //     function getBlockNumber() external view returns (uint256) {
    //         return block.number;
    //     }
    let code = hex!(
        "6080604052348015600e575f5ffd5b50600436106026575f3560e01c806342cbb15c14602a575b5f5ffd5b60306044565b604051603b91906061565b60405180910390f35b5f43905090565b5f819050919050565b605b81604b565b82525050565b5f60208201905060725f8301846054565b9291505056fea26469706673582212207741266d8151c5e7d1a96fc1697f8fc94e60e730b3f2861d398339c74a2180d464736f6c634300081e0033"
    );

    let account_override =
        AccountOverride { balance: Some(U256::from(1e18)), ..Default::default() };
    let state_override = StateOverridesBuilder::default()
        .append(sender, account_override)
        .append(recipient, AccountOverride::default().with_code(code.to_vec()))
        .build();

    let block_override = BlockOverrides { number: Some(U256::from(99)), ..Default::default() };

    let output = api
        .call(
            WithOtherFields::new(tx),
            None,
            EvmOverrides::new(Some(state_override), Some(Box::new(block_override))),
        )
        .await
        .expect("Failed to estimate gas with state override");

    assert_eq!(output, U256::from(99).abi_encode());
}

#[tokio::test(flavor = "multi_thread")]
async fn test_reject_gas_too_low() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let account = handle.dev_accounts().next().unwrap();

    let gas = 21_000u64 - 1;
    let tx = TransactionRequest::default()
        .to(Address::random())
        .value(U256::from(1337u64))
        .from(account)
        .with_gas_limit(gas);
    let tx = WithOtherFields::new(tx);

    let resp = provider.send_transaction(tx).await;

    let err = resp.unwrap_err().to_string();
    assert!(err.contains("intrinsic gas too low"));
}

// <https://github.com/foundry-rs/foundry/issues/3783>
#[tokio::test(flavor = "multi_thread")]
async fn can_call_with_high_gas_limit() {
    let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(100_000_000))).await;
    let provider = handle.http_provider();

    let greeter_contract = Greeter::deploy(provider, "Hello World!".to_string()).await.unwrap();

    let greeting = greeter_contract.greet().gas(60_000_000).call().await.unwrap();
    assert_eq!("Hello World!", greeting);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_reject_eip1559_pre_london() {
    let (api, handle) =
        spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await;
    let provider = handle.http_provider();

    let gas_limit = api.gas_limit().to::<u64>();
    let gas_price = api.gas_price();

    let unsupported_call_builder =
        Greeter::deploy_builder(provider.clone(), "Hello World!".to_string());
    let unsupported_calldata = unsupported_call_builder.calldata();

    let unsup_tx = TransactionRequest::default()
        .from(handle.dev_accounts().next().unwrap())
        .with_input(unsupported_calldata.to_owned())
        .with_gas_limit(gas_limit)
        .with_max_fee_per_gas(gas_price)
        .with_max_priority_fee_per_gas(gas_price);

    let unsup_tx = WithOtherFields::new(unsup_tx);

    let unsupported = provider.send_transaction(unsup_tx).await.unwrap_err().to_string();
    assert!(unsupported.contains("not supported by the current hardfork"), "{unsupported}");

    let greeter_contract_addr =
        Greeter::deploy_builder(provider.clone(), "Hello World!".to_string())
            .gas(gas_limit)
            .gas_price(gas_price)
            .deploy()
            .await
            .unwrap();

    let greeter_contract = Greeter::new(greeter_contract_addr, provider);

    let greeting = greeter_contract.greet().call().await.unwrap();
    assert_eq!("Hello World!", greeting);
}

// https://github.com/foundry-rs/foundry/issues/6931
#[tokio::test(flavor = "multi_thread")]
async fn can_mine_multiple_in_block() {
    let (api, _handle) = spawn(NodeConfig::test()).await;

    // disable auto mine
    api.anvil_set_auto_mine(false).await.unwrap();

    let tx = TransactionRequest {
        from: Some("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()),
        ..Default::default()
    };

    // broadcast it via the eth_sendTransaction API
    let first = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap();
    let second = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap();

    api.anvil_mine(Some(U256::from(1)), Some(U256::ZERO)).await.unwrap();

    let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap();

    let txs = block.transactions.hashes().collect::<Vec<_>>();
    assert_eq!(txs, vec![first, second]);
}

// ensures that the gas estimate is running on pending block by default
#[tokio::test(flavor = "multi_thread")]
async fn can_estimate_gas_prague() {
    let (api, _handle) =
        spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()))).await;

    // {"data":"0xcafebabe","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"
    // 0x70997970c51812dc3a010c7d01b50e0d17dc79c8"}
    let req = TransactionRequest::default()
        .with_input(hex!("0xcafebabe"))
        .with_from(address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"))
        .with_to(address!("0x70997970c51812dc3a010c7d01b50e0d17dc79c8"));
    api.estimate_gas(WithOtherFields::new(req), None, EvmOverrides::default()).await.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
async fn can_send_tx_osaka_valid_with_limit_enabled() {
    let (_api, handle) = spawn(
        NodeConfig::test()
            .enable_tx_gas_limit(true)
            .with_hardfork(Some(EthereumHardfork::Osaka.into())),
    )
    .await;
    let provider = handle.http_provider();
    let wallet = handle.dev_wallets().next().unwrap();
    let sender = wallet.address();
    let recipient = Address::random();

    let base_tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18));

    // gas limit below the cap is accepted
    let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP - 1);
    let tx = WithOtherFields::new(tx);
    let pending_tx = provider.send_transaction(tx).await.unwrap();
    let tx_receipt = pending_tx.get_receipt().await.unwrap();
    assert!(tx_receipt.inner.inner.is_success());

    // gas limit at the cap is accepted
    let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP);
    let tx = WithOtherFields::new(tx);
    let pending_tx = provider.send_transaction(tx).await.unwrap();
    let tx_receipt = pending_tx.get_receipt().await.unwrap();
    assert!(tx_receipt.inner.inner.is_success());

    // gas limit above the cap is rejected
    let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP + 1);
    let tx = WithOtherFields::new(tx);
    let err = provider.send_transaction(tx).await.unwrap_err().to_string();
    assert!(
        err.contains("intrinsic gas too high -- tx.gas_limit > env.cfg.tx_gas_limit_cap"),
        "{err}"
    );
}

#[tokio::test(flavor = "multi_thread")]
async fn can_send_tx_osaka_valid_with_limit_disabled() {
    let (_api, handle) =
        spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Osaka.into()))).await;
    let provider = handle.http_provider();
    let wallet = handle.dev_wallets().next().unwrap();
    let sender = wallet.address();
    let recipient = Address::random();

    let base_tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18));

    // gas limit below the cap is accepted
    let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP - 1);
    let tx = WithOtherFields::new(tx);
    let pending_tx = provider.send_transaction(tx).await.unwrap();
    let tx_receipt = pending_tx.get_receipt().await.unwrap();
    assert!(tx_receipt.inner.inner.is_success());

    // gas limit at the cap is accepted
    let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP);
    let tx = WithOtherFields::new(tx);
    let pending_tx = provider.send_transaction(tx).await.unwrap();
    let tx_receipt = pending_tx.get_receipt().await.unwrap();
    assert!(tx_receipt.inner.inner.is_success());

    // gas limit above the cap is accepted when the limit is disabled
    let tx = base_tx.clone().gas_limit(TX_GAS_LIMIT_CAP + 1);
    let tx = WithOtherFields::new(tx);
    let pending_tx = provider.send_transaction(tx).await.unwrap();
    let tx_receipt = pending_tx.get_receipt().await.unwrap();
    assert!(tx_receipt.inner.inner.is_success());
}

#[tokio::test(flavor = "multi_thread")]
async fn can_get_tx_by_sender_and_nonce() {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let provider = handle.http_provider();

    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let sender = accounts[0].address();
    let recipient = accounts[1].address();

    api.anvil_set_auto_mine(false).await.unwrap();

    let mut tx_hashes = std::collections::BTreeMap::new();

    // send 4 transactions from the same sender with consecutive nonces
    for i in 0..4 {
        let tx_request = TransactionRequest::default()
            .to(recipient)
            .value(U256::from(1000 + i))
            .from(sender)
            .nonce(i as u64);

        let tx = WithOtherFields::new(tx_request);
        let pending_tx = provider.send_transaction(tx).await.unwrap();
        tx_hashes.insert(i as u64, *pending_tx.tx_hash());
    }

    // mine all transactions
    api.mine_one().await.unwrap();

    for nonce in 0..4 {
        let result: Option<alloy_network::AnyRpcTransaction> = provider
            .client()
            .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(nonce)))
            .await
            .unwrap();

        assert!(result.is_some());
        let found_tx = result.unwrap();

        assert_eq!(found_tx.inner.nonce(), nonce);
        assert_eq!(found_tx.from(), sender);
        assert_eq!(found_tx.inner.to(), Some(recipient));
        assert_eq!(found_tx.inner.value(), U256::from(1000 + nonce));
        assert_eq!(found_tx.inner.tx_hash(), tx_hashes[&nonce]);
    }

    let result: Option<alloy_network::AnyRpcTransaction> = provider
        .client()
        .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(999)))
        .await
        .unwrap();
    assert!(result.is_none());

    let different_sender = accounts[2].address();
    let result: Option<alloy_network::AnyRpcTransaction> = provider
        .client()
        .request("eth_getTransactionBySenderAndNonce", (different_sender, U256::from(0)))
        .await
        .unwrap();
    assert!(result.is_none());

    // send a pending transaction with explicit nonce 4
    let pending_tx_request =
        TransactionRequest::default().to(recipient).value(U256::from(5000)).from(sender).nonce(4);

    let tx = WithOtherFields::new(pending_tx_request);
    let pending_tx = provider.send_transaction(tx).await.unwrap();

    // find the pending transaction with nonce 4
    let result: Option<alloy_network::AnyRpcTransaction> = provider
        .client()
        .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(4)))
        .await
        .unwrap();

    assert!(result.is_some());
    let found_tx = result.unwrap();
    assert_eq!(found_tx.inner.nonce(), 4);
    assert_eq!(found_tx.inner.tx_hash(), *pending_tx.tx_hash());

    api.mine_one().await.unwrap();

    let result: Option<alloy_network::AnyRpcTransaction> = provider
        .client()
        .request("eth_getTransactionBySenderAndNonce", (sender, U256::from(4)))
        .await
        .unwrap();

    assert!(result.is_some());
    let found_tx = result.unwrap();
    assert_eq!(found_tx.inner.nonce(), 4);
}

// Instant-mine coalescing regression tests.

/// Polls `eth_getTransactionReceipt` until a block number is available.
async fn poll_receipt_block(client: &reqwest::Client, endpoint: &str, tx_hash: &str) -> u64 {
    let deadline = Instant::now() + Duration::from_secs(5);
    loop {
        let payload = serde_json::json!({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "eth_getTransactionReceipt",
            "params": [tx_hash],
        });
        let resp: serde_json::Value =
            client.post(endpoint).json(&payload).send().await.unwrap().json().await.unwrap();
        if let Some(receipt) = resp.get("result").and_then(|r| r.as_object())
            && let Some(block_hex) = receipt.get("blockNumber").and_then(|b| b.as_str())
        {
            return u64::from_str_radix(block_hex.trim_start_matches("0x"), 16).unwrap();
        }
        assert!(Instant::now() < deadline, "timed out waiting for receipt of {tx_hash}");
        tokio::time::sleep(Duration::from_millis(25)).await;
    }
}

/// Txs in one JSON-RPC batch POST must share a block.
#[tokio::test(flavor = "multi_thread")]
async fn instant_mine_groups_json_rpc_batch_in_one_block() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let endpoint = handle.http_endpoint();
    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from1 = accounts[1].address();
    let from2 = accounts[2].address();
    let to = accounts[0].address();

    let batch = serde_json::json!([
        {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "eth_sendTransaction",
            "params": [{"from": from1, "to": to, "value": "0x1"}]
        },
        {
            "jsonrpc": "2.0",
            "id": 2,
            "method": "eth_sendTransaction",
            "params": [{"from": from2, "to": to, "value": "0x1"}]
        }
    ]);

    let client = reqwest::Client::new();
    let resp: serde_json::Value =
        client.post(&endpoint).json(&batch).send().await.unwrap().json().await.unwrap();

    let arr = resp.as_array().expect("expected JSON-RPC batch response");
    assert_eq!(arr.len(), 2);
    let hashes: Vec<String> = arr
        .iter()
        .map(|r| r.get("result").expect("expected result").as_str().unwrap().to_owned())
        .collect();

    let mut blocks = Vec::new();
    for hash in &hashes {
        let block = poll_receipt_block(&client, &endpoint, hash).await;
        blocks.push(block);
    }

    assert_eq!(
        blocks[0], blocks[1],
        "batched eth_sendTransaction txs landed in different blocks ({blocks:?})"
    );
}

/// Txs from in-process parallel HTTP requests must share a block.
#[tokio::test(flavor = "multi_thread")]
async fn instant_mine_groups_in_process_parallel_http_in_one_block() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let endpoint = handle.http_endpoint();
    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from1 = accounts[1].address();
    let from2 = accounts[2].address();
    let to = accounts[0].address();

    let client = reqwest::Client::new();

    let send = |from: Address, id: u64| {
        let client = client.clone();
        let endpoint = endpoint.clone();
        async move {
            let payload = serde_json::json!({
                "jsonrpc": "2.0",
                "id": id,
                "method": "eth_sendTransaction",
                "params": [{"from": from, "to": to, "value": "0x1"}],
            });
            let resp: serde_json::Value =
                client.post(&endpoint).json(&payload).send().await.unwrap().json().await.unwrap();
            resp.get("result").unwrap().as_str().unwrap().to_owned()
        }
    };

    let (h1, h2) = tokio::join!(send(from1, 1), send(from2, 2));

    let b1 = poll_receipt_block(&client, &endpoint, &h1).await;
    let b2 = poll_receipt_block(&client, &endpoint, &h2).await;

    assert_eq!(b1, b2, "parallel-HTTP txs landed in different blocks (b1={b1}, b2={b2})");
}

/// Sends spaced beyond the coalescing window must still mine in separate blocks.
#[tokio::test(flavor = "multi_thread")]
async fn instant_mine_does_not_group_sequential_sends_beyond_window() {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let endpoint = handle.http_endpoint();
    let accounts = handle.dev_wallets().collect::<Vec<_>>();
    let from1 = accounts[1].address();
    let from2 = accounts[2].address();
    let to = accounts[0].address();
    let client = reqwest::Client::new();

    let send = |from: Address, id: u64| {
        let client = client.clone();
        let endpoint = endpoint.clone();
        async move {
            let payload = serde_json::json!({
                "jsonrpc": "2.0",
                "id": id,
                "method": "eth_sendTransaction",
                "params": [{"from": from, "to": to, "value": "0x1"}],
            });
            let resp: serde_json::Value =
                client.post(&endpoint).json(&payload).send().await.unwrap().json().await.unwrap();
            resp.get("result").unwrap().as_str().unwrap().to_owned()
        }
    };

    let h1 = send(from1, 1).await;
    let b1 = poll_receipt_block(&client, &endpoint, &h1).await;
    tokio::time::sleep(Duration::from_millis(50)).await;
    let h2 = send(from2, 2).await;
    let b2 = poll_receipt_block(&client, &endpoint, &h2).await;

    assert_ne!(b1, b2, "sequential sends with delay coalesced into the same block ({b1})");
}
