Skip to content

Commit

Permalink
feat(core): cache the sender address on mempool transactions (#1282)
Browse files Browse the repository at this point in the history
**Motivation**

Depends on #1273 

When doing flamegraphs of load tests, the node spends around 50% of its
time on the `fill_transactions` function, and in there spends most of
its time just calling the `sender()` method to recover the sender's
address. By caching it when it's first added to the mempool, the time
spent on `fill_transactions` goes down to around 8%, and block execution
becomes roughly ~7x faster

Before the change:


![flamegraph_original](https://github.com/user-attachments/assets/d9e443f5-c151-4dfd-81c5-eef339ec8ca7)

After the change:


![flamegraph_cached_sender](https://github.com/user-attachments/assets/076a79d7-6c46-4ec7-9456-8c819ddcb4f3)


**Description**

<!-- A clear and concise general description of the changes this PR
introduces -->

<!-- Link to issues: Resolves #111, Resolves #222 -->
  • Loading branch information
jrchatruc authored Nov 28, 2024
1 parent 4a837c0 commit f6d9f83
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 16 deletions.
31 changes: 19 additions & 12 deletions crates/blockchain/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ pub fn add_blob_transaction(
// Validate blobs bundle
blobs_bundle.validate(&transaction)?;

// Validate transaction
let transaction = Transaction::EIP4844Transaction(transaction);
validate_transaction(&transaction, store.clone())?;
let sender = transaction.sender();

// Validate transaction
validate_transaction(&transaction, sender, store.clone())?;

// Add transaction and blobs bundle to storage
let hash = transaction.compute_hash();
store.add_transaction_to_pool(hash, MempoolTransaction::new(transaction))?;
store.add_transaction_to_pool(hash, MempoolTransaction::new(transaction, sender))?;
store.add_blobs_bundle_to_pool(hash, blobs_bundle)?;
Ok(hash)
}
Expand All @@ -44,13 +46,14 @@ pub fn add_transaction(transaction: Transaction, store: Store) -> Result<H256, M
if matches!(transaction, Transaction::EIP4844Transaction(_)) {
return Err(MempoolError::BlobTxNoBlobsBundle);
}
let sender = transaction.sender();
// Validate transaction
validate_transaction(&transaction, store.clone())?;
validate_transaction(&transaction, sender, store.clone())?;

let hash = transaction.compute_hash();

// Add transaction to storage
store.add_transaction_to_pool(hash, MempoolTransaction::new(transaction))?;
store.add_transaction_to_pool(hash, MempoolTransaction::new(transaction, sender))?;

Ok(hash)
}
Expand Down Expand Up @@ -163,7 +166,11 @@ Stateful validations
*/

fn validate_transaction(tx: &Transaction, store: Store) -> Result<(), MempoolError> {
fn validate_transaction(
tx: &Transaction,
sender: Address,
store: Store,
) -> Result<(), MempoolError> {
// TODO: Add validations here

let header_no = store
Expand Down Expand Up @@ -207,7 +214,7 @@ fn validate_transaction(tx: &Transaction, store: Store) -> Result<(), MempoolErr
}
};

let maybe_sender_acc_info = store.get_account_info(header_no, tx.sender())?;
let maybe_sender_acc_info = store.get_account_info(header_no, sender)?;

if let Some(sender_acc_info) = maybe_sender_acc_info {
if tx.nonce() < sender_acc_info.nonce {
Expand Down Expand Up @@ -541,7 +548,7 @@ mod tests {
};

let tx = Transaction::EIP1559Transaction(tx);
let validation = validate_transaction(&tx, store);
let validation = validate_transaction(&tx, Address::random(), store);
assert!(matches!(
validation,
Err(MempoolError::TxMaxInitCodeSizeError)
Expand All @@ -567,7 +574,7 @@ mod tests {
};

let tx = Transaction::EIP1559Transaction(tx);
let validation = validate_transaction(&tx, store);
let validation = validate_transaction(&tx, Address::random(), store);
assert!(matches!(
validation,
Err(MempoolError::TxGasLimitExceededError)
Expand All @@ -593,7 +600,7 @@ mod tests {
};

let tx = Transaction::EIP1559Transaction(tx);
let validation = validate_transaction(&tx, store);
let validation = validate_transaction(&tx, Address::random(), store);
assert!(matches!(
validation,
Err(MempoolError::TxTipAboveFeeCapError)
Expand All @@ -620,7 +627,7 @@ mod tests {
};

let tx = Transaction::EIP1559Transaction(tx);
let validation = validate_transaction(&tx, store);
let validation = validate_transaction(&tx, Address::random(), store);
assert!(matches!(
validation,
Err(MempoolError::TxIntrinsicGasCostAboveLimitError)
Expand All @@ -646,7 +653,7 @@ mod tests {
};

let tx = Transaction::EIP4844Transaction(tx);
let validation = validate_transaction(&tx, store);
let validation = validate_transaction(&tx, Address::random(), store);
assert!(matches!(
validation,
Err(MempoolError::TxBlobBaseFeeTooLowError)
Expand Down
18 changes: 16 additions & 2 deletions crates/common/types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2122,22 +2122,28 @@ mod mempool {
pub struct MempoolTransaction {
// Unix timestamp (in microseconds) created once the transaction reached the MemPool
timestamp: u128,
sender: Address,
inner: Transaction,
}

impl MempoolTransaction {
pub fn new(tx: Transaction) -> Self {
pub fn new(tx: Transaction, sender: Address) -> Self {
Self {
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Invalid system time")
.as_micros(),
sender,
inner: tx,
}
}
pub fn time(&self) -> u128 {
self.timestamp
}

pub fn sender(&self) -> Address {
self.sender
}
}

impl RLPEncode for MempoolTransaction {
Expand All @@ -2153,8 +2159,16 @@ mod mempool {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
let decoder = Decoder::new(rlp)?;
let (timestamp, decoder) = decoder.decode_field("timestamp")?;
let (sender, decoder) = decoder.decode_field("sender")?;
let (inner, decoder) = decoder.decode_field("inner")?;
Ok((Self { timestamp, inner }, decoder.finish()?))
Ok((
Self {
timestamp,
sender,
inner,
},
decoder.finish()?,
))
}
}

Expand Down
8 changes: 6 additions & 2 deletions crates/storage/store/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1213,8 +1213,12 @@ mod tests {
use hex_literal::hex;

fn test_filter_mempool_transactions(store: Store) {
let plain_tx = MempoolTransaction::new(Transaction::decode_canonical(&hex!("f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4")).unwrap());
let blob_tx = MempoolTransaction::new(Transaction::decode_canonical(&hex!("03f88f0780843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001e1a0010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401401a0840650aa8f74d2b07f40067dc33b715078d73422f01da17abdbd11e02bbdfda9a04b2260f6022bf53eadb337b3e59514936f7317d872defb891a708ee279bdca90")).unwrap());
let plain_tx_decoded = Transaction::decode_canonical(&hex!("f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4")).unwrap();
let plain_tx_sender = plain_tx_decoded.sender();
let plain_tx = MempoolTransaction::new(plain_tx_decoded, plain_tx_sender);
let blob_tx_decoded = Transaction::decode_canonical(&hex!("03f88f0780843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001e1a0010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401401a0840650aa8f74d2b07f40067dc33b715078d73422f01da17abdbd11e02bbdfda9a04b2260f6022bf53eadb337b3e59514936f7317d872defb891a708ee279bdca90")).unwrap();
let blob_tx_sender = blob_tx_decoded.sender();
let blob_tx = MempoolTransaction::new(blob_tx_decoded, blob_tx_sender);
let plain_tx_hash = plain_tx.compute_hash();
let blob_tx_hash = blob_tx.compute_hash();
let filter =
Expand Down

0 comments on commit f6d9f83

Please sign in to comment.