Skip to content

Commit

Permalink
feat: implement EIP-6110 (#8507)
Browse files Browse the repository at this point in the history
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
Co-authored-by: Oliver Nordbjerg <onbjerg@users.noreply.github.com>
Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
  • Loading branch information
4 people authored May 30, 2024
1 parent 9a08ad7 commit 80809a4
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 29 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion crates/ethereum/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ reth-ethereum-consensus.workspace = true
# Ethereum
revm-primitives.workspace = true

# Alloy
alloy-consensus.workspace = true
alloy-eips.workspace = true
alloy-sol-types.workspace = true

[dev-dependencies]
reth-testing-utils.workspace = true
reth-revm = { workspace = true, features = ["test-utils"] }
alloy-eips.workspace = true
secp256k1.workspace = true
serde_json.workspace = true

125 changes: 125 additions & 0 deletions crates/ethereum/evm/src/eip6110.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! EIP-6110 deposit requests parsing
use alloy_consensus::Request;
use alloy_eips::eip6110::{DepositRequest, MAINNET_DEPOSIT_CONTRACT_ADDRESS};
use alloy_sol_types::{sol, SolEvent};
use reth_evm::execute::BlockValidationError;
use reth_primitives::{ChainSpec, Receipt};
use revm_primitives::Log;

sol! {
#[allow(missing_docs)]
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
}

/// 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)[Request].
pub fn parse_deposits_from_receipts<'a, I>(
chain_spec: &ChainSpec,
receipts: I,
) -> Result<Vec<Request>, BlockValidationError>
where
I: IntoIterator<Item = &'a Receipt>,
{
let deposit_contract_address = chain_spec
.deposit_contract
.as_ref()
.map_or(MAINNET_DEPOSIT_CONTRACT_ADDRESS, |contract| contract.address);
receipts
.into_iter()
.flat_map(|receipt| receipt.logs.iter())
// 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)
.map(|log| {
let decoded_log = DepositEvent::decode_log(log, false)?;
let deposit = parse_deposit_from_log(&decoded_log);
Ok(Request::DepositRequest(deposit))
})
.collect::<Result<Vec<_>, _>>()
.map_err(|err: alloy_sol_types::Error| {
BlockValidationError::DepositRequestDecode(err.to_string())
})
}

fn parse_deposit_from_log(log: &Log<DepositEvent>) -> DepositRequest {
// 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.
DepositRequest {
pubkey: log
.pubkey
.as_ref()
.try_into()
.expect("pubkey length should be enforced in deposit contract"),
withdrawal_credentials: log
.withdrawal_credentials
.as_ref()
.try_into()
.expect("withdrawal_credentials length should be enforced in deposit contract"),
amount: u64::from_le_bytes(
log.amount
.as_ref()
.try_into()
.expect("amount length should be enforced in deposit contract"),
),
signature: log
.signature
.as_ref()
.try_into()
.expect("signature length should be enforced in deposit contract"),
index: u64::from_le_bytes(
log.index
.as_ref()
.try_into()
.expect("deposit index length should be enforced in deposit contract"),
),
}
}

#[cfg(test)]
mod tests {
use super::*;
use reth_primitives::{TxType, MAINNET};

#[test]
fn test_parse_deposit_from_log() {
let receipts = vec![
// https://etherscan.io/tx/0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9
#[allow(clippy::needless_update)] // side-effect of optimism fields
Receipt {
// these don't matter
tx_type: TxType::Legacy,
success: true,
cumulative_gas_used: 0,
logs: serde_json::from_str(
r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"#
).unwrap(),
..Default::default()
},
// https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338
#[allow(clippy::needless_update)] // side-effect of optimism fields
Receipt {
// these don't matter
tx_type: TxType::Legacy,
success: true,
cumulative_gas_used: 0,
logs: serde_json::from_str(
r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#,
).unwrap(),
..Default::default()
},
];

let requests = parse_deposits_from_receipts(&MAINNET, &receipts).unwrap();
assert_eq!(requests.len(), 2);
assert_eq!(requests[0].as_deposit_request().unwrap().amount, 32e9 as u64);
assert_eq!(requests[1].as_deposit_request().unwrap().amount, 32e9 as u64);
}
}
8 changes: 7 additions & 1 deletion crates/ethereum/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,14 @@ where
}

let requests = if self.chain_spec.is_prague_active_at_timestamp(block.timestamp) {
// Collect all EIP-6110 deposits
let deposit_requests =
crate::eip6110::parse_deposits_from_receipts(&self.chain_spec, &receipts)?;

// Collect all EIP-7685 requests
apply_withdrawal_requests_contract_call(&mut evm)?
let withdrawal_requests = apply_withdrawal_requests_contract_call(&mut evm)?;

[deposit_requests, withdrawal_requests].concat()
} else {
vec![]
};
Expand Down
3 changes: 3 additions & 0 deletions crates/ethereum/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pub mod execute;
/// Ethereum DAO hardfork state change data.
pub mod dao_fork;

/// [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110) handling.
pub mod eip6110;

/// Ethereum-related EVM configuration.
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
Expand Down
1 change: 1 addition & 0 deletions crates/evm/execution-errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository.workspace = true
workspace = true

[dependencies]
# reth
reth-consensus.workspace = true
reth-primitives.workspace = true
reth-storage-errors.workspace = true
Expand Down
13 changes: 11 additions & 2 deletions crates/evm/execution-errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,24 @@ pub enum BlockValidationError {
/// The error message.
message: String,
},
/// Provider error during the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) block hash account loading.
/// Provider error during the [EIP-2935] block hash account loading.
///
/// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935
#[error(transparent)]
BlockHashAccountLoadingFailed(#[from] ProviderError),
/// EVM error during withdrawal requests contract call
/// EVM error during withdrawal requests contract call [EIP-7002]
///
/// [EIP-7002]: https://eips.ethereum.org/EIPS/eip-7002
#[error("failed to apply withdrawal requests contract call: {message}")]
WithdrawalRequestsContractCall {
/// The error message.
message: String,
},
/// Error when decoding deposit requests from receipts [EIP-6110]
///
/// [EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110
#[error("failed to decode deposit requests from receipts: {0}")]
DepositRequestDecode(String),
}

/// BlockExecutor Errors
Expand Down
2 changes: 1 addition & 1 deletion crates/payload/basic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ reth-metrics.workspace = true
metrics.workspace = true

# misc
tracing.workspace = true
tracing.workspace = true
1 change: 1 addition & 0 deletions crates/payload/ethereum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ reth-payload-builder.workspace = true
reth-basic-payload-builder.workspace = true
reth-evm.workspace = true
reth-evm-ethereum.workspace = true
reth-errors.workspace = true

# ethereum
revm.workspace = true
Expand Down
35 changes: 19 additions & 16 deletions crates/payload/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ use reth_basic_payload_builder::{
pre_block_beacon_root_contract_call, BuildArguments, BuildOutcome, PayloadBuilder,
PayloadConfig, WithdrawalsOutcome,
};
use reth_errors::RethError;
use reth_evm::ConfigureEvm;
use reth_evm_ethereum::EthEvmConfig;
use reth_evm_ethereum::{eip6110::parse_deposits_from_receipts, EthEvmConfig};
use reth_payload_builder::{
error::PayloadBuilderError, EthBuiltPayload, EthPayloadBuilderAttributes,
};
Expand Down Expand Up @@ -417,21 +418,23 @@ where
}

// calculate the requests and the requests root
let (requests, requests_root) =
if chain_spec.is_prague_active_at_timestamp(attributes.timestamp) {
let withdrawal_requests = post_block_withdrawal_requests_contract_call(
&mut db,
&initialized_cfg,
&initialized_block_env,
)?;

// TODO: add deposit requests
let requests = withdrawal_requests;
let requests_root = calculate_requests_root(&requests);
(Some(requests.into()), Some(requests_root))
} else {
(None, None)
};
let (requests, requests_root) = if chain_spec
.is_prague_active_at_timestamp(attributes.timestamp)
{
let deposit_requests = parse_deposits_from_receipts(&chain_spec, receipts.iter().flatten())
.map_err(|err| PayloadBuilderError::Internal(RethError::Execution(err.into())))?;
let withdrawal_requests = post_block_withdrawal_requests_contract_call(
&mut db,
&initialized_cfg,
&initialized_block_env,
)?;

let requests = [deposit_requests, withdrawal_requests].concat();
let requests_root = calculate_requests_root(&requests);
(Some(requests.into()), Some(requests_root))
} else {
(None, None)
};

let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals)?;
Expand Down
3 changes: 2 additions & 1 deletion crates/primitives/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ pub use alloy_chains::{Chain, ChainKind, NamedChain};
pub use info::ChainInfo;
pub use spec::{
AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder,
DisplayHardforks, ForkBaseFeeParams, ForkCondition, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA,
DepositContract, DisplayHardforks, ForkBaseFeeParams, ForkCondition, DEV, GOERLI, HOLESKY,
MAINNET, SEPOLIA,
};
#[cfg(feature = "optimism")]
pub use spec::{BASE_MAINNET, BASE_SEPOLIA, OP_MAINNET, OP_SEPOLIA};
Expand Down
15 changes: 12 additions & 3 deletions crates/primitives/src/chain/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
revm_primitives::{address, b256},
Address, BlockNumber, Chain, ChainKind, ForkFilter, ForkFilterKey, ForkHash, ForkId, Genesis,
Hardfork, Head, Header, NamedChain, NodeRecord, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH,
U256,
MAINNET_DEPOSIT_CONTRACT, U256,
};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -1053,13 +1053,21 @@ impl From<Genesis> for ChainSpec {

hardforks.extend(time_hardforks);

// NOTE: in full node, we prune all receipts except the deposit contract's. We do not
// have the deployment block in the genesis file, so we use block zero. We use the same
// deposit topic as the mainnet contract if we have the deposit contract address in the
// genesis json.
let deposit_contract = genesis.config.deposit_contract_address.map(|address| {
DepositContract { address, block: 0, topic: MAINNET_DEPOSIT_CONTRACT.topic }
});

Self {
chain: genesis.config.chain_id.into(),
genesis,
genesis_hash: None,
hardforks,
paris_block_and_final_difficulty,
deposit_contract: None,
deposit_contract,
..Default::default()
}
}
Expand Down Expand Up @@ -1601,7 +1609,8 @@ pub struct DepositContract {
}

impl DepositContract {
const fn new(address: Address, block: BlockNumber, topic: B256) -> Self {
/// Creates a new [DepositContract].
pub const fn new(address: Address, block: BlockNumber, topic: B256) -> Self {
Self { address, block, topic }
}
}
Expand Down
13 changes: 12 additions & 1 deletion crates/primitives/src/constants/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Ethereum protocol-related constants
use crate::{revm_primitives::b256, B256, U256};
use crate::{
chain::DepositContract,
revm_primitives::{address, b256},
B256, U256,
};
use std::time::Duration;

#[cfg(feature = "optimism")]
Expand Down Expand Up @@ -64,6 +68,13 @@ pub const EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 2;
/// Minimum gas limit allowed for transactions.
pub const MINIMUM_GAS_LIMIT: u64 = 5000;

/// Deposit contract address
pub const MAINNET_DEPOSIT_CONTRACT: DepositContract = DepositContract::new(
address!("00000000219ab540356cbb839cbe05303d7705fa"),
11052984,
b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
);

/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
Expand Down
6 changes: 3 additions & 3 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ pub use block::{
};
pub use chain::{
AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, Chain, ChainInfo, ChainKind, ChainSpec,
ChainSpecBuilder, DisplayHardforks, ForkBaseFeeParams, ForkCondition, NamedChain, DEV, GOERLI,
HOLESKY, MAINNET, SEPOLIA,
ChainSpecBuilder, DepositContract, DisplayHardforks, ForkBaseFeeParams, ForkCondition,
NamedChain, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA,
};
#[cfg(feature = "zstd-codec")]
pub use compression::*;
pub use constants::{
DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH,
KECCAK_EMPTY, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH,
KECCAK_EMPTY, MAINNET_DEPOSIT_CONTRACT, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH,
};
pub use error::{GotExpected, GotExpectedBoxed};
pub use exex::FinishedExExHeight;
Expand Down

0 comments on commit 80809a4

Please sign in to comment.