Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(anvil): remove all txs from tx pool by sender origin #7480

Merged
merged 2 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/anvil/core/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,13 @@ pub enum EthRequest {
/// contract.
#[cfg_attr(feature = "serde", serde(rename = "ots_getContractCreator", with = "sequence"))]
OtsGetContractCreator(Address),

/// Removes transactions from the pool by sender origin.
#[cfg_attr(
feature = "serde",
serde(rename = "anvil_removePoolTransactions", with = "sequence")
)]
RemovePoolTransactions(Address),
}

/// Represents ethereum JSON-RPC API
Expand Down Expand Up @@ -1506,4 +1513,11 @@ true}]}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
}

#[test]
fn test_remove_pool_transactions() {
let s = r#"{"method": "anvil_removePoolTransactions", "params":["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
}
}
9 changes: 9 additions & 0 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ impl EthApi {
EthRequest::OtsGetContractCreator(address) => {
self.ots_get_contract_creator(address).await.to_rpc_result()
}
EthRequest::RemovePoolTransactions(address) => {
self.anvil_remove_pool_transactions(address).await.to_rpc_result()
}
}
}

Expand Down Expand Up @@ -1781,6 +1784,12 @@ impl EthApi {
})
}

pub async fn anvil_remove_pool_transactions(&self, address: Address) -> Result<()> {
node_info!("anvil_removePoolTransactions");
self.pool.remove_transactions_by_address(address);
Ok(())
}

/// Snapshot the state of the blockchain at the current block.
///
/// Handler for RPC call: `evm_snapshot`
Expand Down
32 changes: 31 additions & 1 deletion crates/anvil/src/eth/pool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::{
},
mem::storage::MinedBlockOutcome,
};
use alloy_primitives::{TxHash, U64};
use alloy_primitives::{Address, TxHash, U64};
use alloy_rpc_types::txpool::TxpoolStatus;
use anvil_core::eth::transaction::PendingTransaction;
use futures::channel::mpsc::{channel, Receiver, Sender};
Expand Down Expand Up @@ -141,6 +141,11 @@ impl Pool {
self.inner.write().remove_invalid(tx_hashes)
}

/// Remove transactions by sender
pub fn remove_transactions_by_address(&self, sender: Address) -> Vec<Arc<PoolTransaction>> {
self.inner.write().remove_transactions_by_address(sender)
}

/// Removes a single transaction from the pool
///
/// This is similar to `[Pool::remove_invalid()]` but for a single transaction.
Expand Down Expand Up @@ -342,6 +347,31 @@ impl PoolInner {

removed
}

/// Remove transactions by sender address
pub fn remove_transactions_by_address(&mut self, sender: Address) -> Vec<Arc<PoolTransaction>> {
let mut tx_hashes: Vec<TxHash> =
self.pending_transactions.transactions_by_sender(sender).map(|tx| tx.hash()).collect();
tx_hashes.extend(
self.ready_transactions
.transactions_by_sender(sender)
.map(|tx| tx.hash())
.collect::<Vec<TxHash>>(),
);

if tx_hashes.is_empty() {
return vec![]
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we extract this to a transactions_by_sender that merges those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored! Let me know if this is what you meant!


trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes);

let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None);
removed.extend(self.pending_transactions.remove(tx_hashes));

trace!(target: "txpool", "Removed transactions: {:?}", removed);

removed
}
}

/// Represents the outcome of a prune
Expand Down
16 changes: 16 additions & 0 deletions crates/anvil/src/eth/pool/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ impl PendingTransactions {
self.waiting_queue.values().map(|tx| tx.transaction.clone())
}

/// Returns an iterator over all transactions in the waiting pool filtered by the sender
pub fn transactions_by_sender(
&self,
sender: Address,
) -> impl Iterator<Item = Arc<PoolTransaction>> + '_ {
self.transactions().filter(move |tx| tx.pending_transaction.sender().eq(&sender))
}

/// Adds a transaction to Pending queue of transactions
pub fn add_transaction(&mut self, tx: PendingPoolTransaction) -> Result<(), PoolError> {
assert!(!tx.is_ready(), "transaction must not be ready");
Expand Down Expand Up @@ -377,6 +385,14 @@ impl ReadyTransactions {
}
}

/// Returns an iterator over all transactions by the sender
pub fn transactions_by_sender(
&self,
sender: Address,
) -> impl Iterator<Item = Arc<PoolTransaction>> + '_ {
self.get_transactions().filter(move |tx| tx.pending_transaction.sender().eq(&sender))
}

/// Returns true if the transaction is part of the queue.
pub fn contains(&self, hash: &TxHash) -> bool {
self.ready_tx.read().contains_key(hash)
Expand Down
25 changes: 25 additions & 0 deletions crates/anvil/tests/it/anvil_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use anvil_core::{
use ethers::{
abi::{ethereum_types::BigEndianHash, AbiDecode},
prelude::{Middleware, SignerMiddleware},
signers::Signer,
types::{
transaction::eip2718::TypedTransaction, Address, BlockNumber, Eip1559TransactionRequest,
TransactionRequest, H256, U256, U64,
Expand Down Expand Up @@ -631,3 +632,27 @@ async fn test_fork_revert_call_latest_block_timestamp() {
latest_block.header.miner.to_ethers()
);
}

#[tokio::test(flavor = "multi_thread")]
async fn can_remove_pool_transactions() {
let (api, handle) = spawn(NodeConfig::test()).await;
let provider = ethers_http_provider(&handle.http_endpoint());
let wallet = handle.dev_wallets().next().unwrap().to_ethers();
let provider = Arc::new(SignerMiddleware::new(provider, wallet.clone()));

let sender = Address::random();
let to = Address::random();
let val = 1337u64;

let tx = TransactionRequest::new().from(sender).to(to).value(val);

provider.send_transaction(tx.from(wallet.address()), None).await.unwrap();

let initial_txs = provider.txpool_inspect().await.unwrap();
assert_eq!(initial_txs.pending.len(), 1);

api.anvil_remove_pool_transactions(wallet.address().to_alloy()).await.unwrap();

let final_txs = provider.txpool_inspect().await.unwrap();
assert_eq!(final_txs.pending.len(), 0);
}