opt: reduce allocs for parse_deposits_from_receipts (#11949)

This commit is contained in:
James Prestwich
2024-10-22 07:53:39 -04:00
committed by GitHub
parent cab76f2083
commit 90aaad8285

View File

@ -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(
@ -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`].
/// 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());
}
/// 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())
})?;
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)
}
/// 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))
}
/// 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>(
chain_spec: &ChainSpec,
chainspec: &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();
for log in &logs {
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())
}
Ok(requests.into())
}
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()
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)]