Skip to content

Commit

Permalink
opt: reduce allocs for parse_deposits_from_receipts (#11949)
Browse files Browse the repository at this point in the history
  • Loading branch information
prestwich authored Oct 22, 2024
1 parent cab76f2 commit 90aaad8
Showing 1 changed file with 73 additions and 41 deletions.
114 changes: 73 additions & 41 deletions crates/ethereum/evm/src/eip6110.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
//! EIP-6110 deposit requests parsing
use alloc::{string::ToString, vec::Vec};
use alloy_eips::eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS;
use alloy_primitives::{Bytes, Log};
use alloy_primitives::{Address, Bytes, Log};
use alloy_sol_types::{sol, SolEvent};
use reth_chainspec::ChainSpec;
use reth_chainspec::{ChainSpec, EthChainSpec};
use reth_evm::execute::BlockValidationError;
use reth_primitives::Receipt;

/// The size of a deposit request in bytes. While the event fields emit
/// bytestrings, those bytestrings are fixed size. The fields are: 48-byte
/// pubkey, 32-byte withdrawal credentials, 8-byte amount, 96-byte signature,
/// and 8-byte index.
const DEPOSIT_BYTES_SIZE: usize = 48 + 32 + 8 + 96 + 8;

sol! {
#[allow(missing_docs)]
event DepositEvent(
Expand All @@ -18,53 +24,79 @@ sol! {
);
}

/// Parse [deposit contract](https://etherscan.io/address/0x00000000219ab540356cbb839cbe05303d7705fa)
/// (address is from the passed [`ChainSpec`]) deposits from receipts, and return them as a
/// [vector](Vec) of (requests)[`alloy_eips::eip7685::Requests`].
pub fn parse_deposits_from_receipts<'a, I>(
chain_spec: &ChainSpec,
receipts: I,
) -> Result<Bytes, BlockValidationError>
where
I: IntoIterator<Item = &'a Receipt>,
{
let mut requests = Vec::new();
let deposit_contract_address = chain_spec
.deposit_contract
.as_ref()
.map_or(MAINNET_DEPOSIT_CONTRACT_ADDRESS, |contract| contract.address);
let logs: Vec<_> = receipts
.into_iter()
.flat_map(|receipt| &receipt.logs)
// No need to filter for topic because there's only one event and that's the Deposit
// event in the deposit contract.
.filter(|log| log.address == deposit_contract_address)
.collect();
/// Accumulate a deposit request from a log. containing a [`DepositEvent`].
pub fn accumulate_deposit_from_log(log: &Log<DepositEvent>, out: &mut Vec<u8>) {
out.reserve(DEPOSIT_BYTES_SIZE);
out.extend_from_slice(log.pubkey.as_ref());
out.extend_from_slice(log.withdrawal_credentials.as_ref());
out.extend_from_slice(log.amount.as_ref());
out.extend_from_slice(log.signature.as_ref());
out.extend_from_slice(log.index.as_ref());
}

for log in &logs {
/// Accumulate deposits from an iterator of logs.
pub fn accumulate_deposits_from_logs<'a>(
address: Address,
logs: impl IntoIterator<Item = &'a Log>,
out: &mut Vec<u8>,
) -> Result<(), BlockValidationError> {
logs.into_iter().filter(|log| log.address == address).try_for_each(|log| {
// We assume that the log is valid because it was emitted by the
// deposit contract.
let decoded_log =
DepositEvent::decode_log(log, false).map_err(|err: alloy_sol_types::Error| {
BlockValidationError::DepositRequestDecode(err.to_string())
})?;
requests.extend(parse_deposit_from_log(&decoded_log).as_ref())
}
accumulate_deposit_from_log(&decoded_log, out);
Ok(())
})
}

/// Accumulate deposits from a receipt. Iterates over the logs in the receipt
/// and accumulates the deposit request bytestrings.
pub fn accumulate_deposits_from_receipt(
address: Address,
receipt: &Receipt,
out: &mut Vec<u8>,
) -> Result<(), BlockValidationError> {
accumulate_deposits_from_logs(address, &receipt.logs, out)
}

Ok(requests.into())
/// Accumulate deposits from a list of receipts. Iterates over the logs in the
/// receipts and accumulates the deposit request bytestrings.
pub fn accumulate_deposits_from_receipts<'a, I>(
address: Address,
receipts: I,
out: &mut Vec<u8>,
) -> Result<(), BlockValidationError>
where
I: IntoIterator<Item = &'a Receipt>,
{
receipts
.into_iter()
.try_for_each(|receipt| accumulate_deposits_from_receipt(address, receipt, out))
}

fn parse_deposit_from_log(log: &Log<DepositEvent>) -> Bytes {
// SAFETY: These `expect` https://github.com/ethereum/consensus-specs/blob/5f48840f4d768bf0e0a8156a3ed06ec333589007/solidity_deposit_contract/deposit_contract.sol#L107-L110
// are safe because the `DepositEvent` is the only event in the deposit contract and the length
// checks are done there.
[
log.pubkey.as_ref(),
log.withdrawal_credentials.as_ref(),
log.amount.as_ref(),
log.signature.as_ref(),
log.index.as_ref(),
]
.concat()
.into()
/// Find deposit logs in a list of receipts, and return the concatenated
/// deposit request bytestring.
///
/// The address of the deposit contract is taken from the chain spec, and
/// defaults to [`MAINNET_DEPOSIT_CONTRACT_ADDRESS`] if not specified in
/// the chain spec.
pub fn parse_deposits_from_receipts<'a, I>(
chainspec: &ChainSpec,
receipts: I,
) -> Result<Bytes, BlockValidationError>
where
I: IntoIterator<Item = &'a Receipt>,
{
let mut out = Vec::new();
accumulate_deposits_from_receipts(
chainspec.deposit_contract().map(|c| c.address).unwrap_or(MAINNET_DEPOSIT_CONTRACT_ADDRESS),
receipts,
&mut out,
)?;
Ok(out.into())
}

#[cfg(test)]
Expand Down

0 comments on commit 90aaad8

Please sign in to comment.