diff --git a/crates/core/types/block.rs b/crates/core/types/block.rs index d1efa75c16..08477ac5d2 100644 --- a/crates/core/types/block.rs +++ b/crates/core/types/block.rs @@ -203,18 +203,18 @@ impl BlockBody { withdrawals: Some(Vec::new()), } } +} - pub fn compute_transactions_root(&self) -> H256 { - let mut trie = PatriciaMerkleTree::, Vec, Keccak256>::new(); - for (idx, tx) in self.transactions.iter().enumerate() { - // Key: RLP(tx_index) - // Value: tx_type || RLP(tx) if tx_type != 0 - // RLP(tx) else - trie.insert(idx.encode_to_vec(), tx.encode_to_vec()); - } - let &root = trie.compute_hash(); - H256(root.into()) +pub fn compute_transactions_root(transactions: &[Transaction]) -> H256 { + let mut trie = PatriciaMerkleTree::, Vec, Keccak256>::new(); + for (idx, tx) in transactions.iter().enumerate() { + // Key: RLP(tx_index) + // Value: tx_type || RLP(tx) if tx_type != 0 + // RLP(tx) else + trie.insert(idx.encode_to_vec(), tx.encode_to_vec()); } + let &root = trie.compute_hash(); + H256(root.into()) } pub fn compute_receipts_root(receipts: &[Receipt]) -> H256 { @@ -442,7 +442,7 @@ mod serializable { BlockBodyWrapper::OnlyHashes(OnlyHashesBlockBody { transactions: body.transactions.iter().map(|t| t.compute_hash()).collect(), uncles: body.ommers, - withdrawals: body.withdrawals.unwrap(), + withdrawals: body.withdrawals.unwrap_or_default(), }) }; let hash = header.compute_block_hash(); diff --git a/crates/core/types/constants.rs b/crates/core/types/constants.rs index 68db86c8f8..1890d1a174 100644 --- a/crates/core/types/constants.rs +++ b/crates/core/types/constants.rs @@ -4,3 +4,4 @@ pub const BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8; pub const GAS_LIMIT_ADJUSTMENT_FACTOR: u64 = 1024; pub const GAS_LIMIT_MINIMUM: u64 = 5000; pub const GWEI_TO_WEI: u64 = 1_000_000_000; +pub const INITIAL_BASE_FEE: u64 = 1_000_000_000; //Initial base fee as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) diff --git a/crates/core/types/engine/payload.rs b/crates/core/types/engine/payload.rs index 80b98db93e..fe7228a05c 100644 --- a/crates/core/types/engine/payload.rs +++ b/crates/core/types/engine/payload.rs @@ -7,7 +7,8 @@ use crate::rlp::decode::RLPDecode; use crate::{rlp::error::RLPDecodeError, serde_utils}; use crate::types::{ - compute_withdrawals_root, BlockBody, BlockHeader, Transaction, Withdrawal, DEFAULT_OMMERS_HASH, + compute_transactions_root, compute_withdrawals_root, Block, BlockBody, BlockHash, BlockHeader, + Transaction, Withdrawal, DEFAULT_OMMERS_HASH, }; #[allow(unused)] @@ -71,11 +72,8 @@ impl EncodedTransaction { impl ExecutionPayloadV3 { /// Converts an `ExecutionPayloadV3` into a block (aka a BlockHeader and BlockBody) /// using the parentBeaconBlockRoot received along with the payload in the rpc call `engine_newPayloadV3` - pub fn into_block( - self, - parent_beacon_block_root: H256, - ) -> Result<(BlockHeader, BlockBody), RLPDecodeError> { - let block_body = BlockBody { + pub fn into_block(self, parent_beacon_block_root: H256) -> Result { + let body = BlockBody { transactions: self .transactions .iter() @@ -84,13 +82,13 @@ impl ExecutionPayloadV3 { ommers: vec![], withdrawals: Some(self.withdrawals), }; - Ok(( - BlockHeader { + Ok(Block { + header: BlockHeader { parent_hash: self.parent_hash, ommers_hash: *DEFAULT_OMMERS_HASH, coinbase: self.fee_recipient, state_root: self.state_root, - transactions_root: block_body.compute_transactions_root(), + transactions_root: compute_transactions_root(&body.transactions), receipt_root: self.receipts_root, logs_bloom: self.logs_bloom, difficulty: 0.into(), @@ -103,14 +101,14 @@ impl ExecutionPayloadV3 { nonce: 0, base_fee_per_gas: self.base_fee_per_gas, withdrawals_root: Some(compute_withdrawals_root( - &block_body.withdrawals.clone().unwrap(), + &body.withdrawals.clone().unwrap_or_default(), )), blob_gas_used: Some(self.blob_gas_used), excess_blob_gas: Some(self.excess_blob_gas), parent_beacon_block_root: Some(parent_beacon_block_root), }, - block_body, - )) + body, + }) } } @@ -132,6 +130,46 @@ pub enum PayloadValidationStatus { Accepted, } +impl PayloadStatus { + // Convenience methods to create payload status + + /// Creates a PayloadStatus with invalid status and error message + pub fn invalid_with_err(error: &str) -> Self { + PayloadStatus { + status: PayloadValidationStatus::Invalid, + latest_valid_hash: None, + validation_error: Some(error.to_string()), + } + } + + /// Creates a PayloadStatus with invalid status and latest valid hash + pub fn invalid_with_hash(hash: BlockHash) -> Self { + PayloadStatus { + status: PayloadValidationStatus::Invalid, + latest_valid_hash: Some(hash), + validation_error: None, + } + } + + /// Creates a PayloadStatus with syncing status and no other info + pub fn syncing() -> Self { + PayloadStatus { + status: PayloadValidationStatus::Syncing, + latest_valid_hash: None, + validation_error: None, + } + } + + /// Creates a PayloadStatus with valid status and latest valid hash + pub fn valid_with_hash(hash: BlockHash) -> Self { + PayloadStatus { + status: PayloadValidationStatus::Valid, + latest_valid_hash: Some(hash), + validation_error: None, + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/core/types/genesis.rs b/crates/core/types/genesis.rs index 6e4b560de9..2017a1438b 100644 --- a/crates/core/types/genesis.rs +++ b/crates/core/types/genesis.rs @@ -8,7 +8,9 @@ use std::collections::HashMap; use crate::rlp::encode::RLPEncode as _; use super::{ - code_hash, AccountInfo, AccountState, Block, BlockBody, BlockHeader, DEFAULT_OMMERS_HASH, + code_hash, compute_receipts_root, compute_transactions_root, compute_withdrawals_root, + AccountInfo, AccountState, Block, BlockBody, BlockHeader, DEFAULT_OMMERS_HASH, + INITIAL_BASE_FEE, }; #[allow(unused)] @@ -109,8 +111,8 @@ impl Genesis { ommers_hash: *DEFAULT_OMMERS_HASH, coinbase: self.coinbase, state_root: self.compute_state_root(), - transactions_root: H256::zero(), - receipt_root: H256::zero(), + transactions_root: compute_transactions_root(&[]), + receipt_root: compute_receipts_root(&[]), logs_bloom: Bloom::zero(), difficulty: self.difficulty, number: 0, @@ -120,11 +122,11 @@ impl Genesis { extra_data: Bytes::new(), prev_randao: self.mixhash, nonce: self.nonce, - base_fee_per_gas: 0, - withdrawals_root: None, - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, + base_fee_per_gas: INITIAL_BASE_FEE, + withdrawals_root: Some(compute_withdrawals_root(&[])), + blob_gas_used: Some(0), + excess_blob_gas: Some(0), + parent_beacon_block_root: Some(H256::zero()), } } @@ -132,7 +134,7 @@ impl Genesis { BlockBody { transactions: vec![], ommers: vec![], - withdrawals: None, + withdrawals: Some(vec![]), } } @@ -272,8 +274,8 @@ mod tests { H256::from_str("0x2dab6a1d6d638955507777aecea699e6728825524facbd446bd4e86d44fa5ecd") .unwrap() ); - assert_eq!(header.transactions_root, H256::from([0; 32])); - assert_eq!(header.receipt_root, H256::from([0; 32])); + assert_eq!(header.transactions_root, compute_transactions_root(&[])); + assert_eq!(header.receipt_root, compute_receipts_root(&[])); assert_eq!(header.logs_bloom, Bloom::default()); assert_eq!(header.difficulty, U256::from(1)); assert_eq!(header.gas_limit, 25_000_000); @@ -282,13 +284,28 @@ mod tests { assert_eq!(header.extra_data, Bytes::default()); assert_eq!(header.prev_randao, H256::from([0; 32])); assert_eq!(header.nonce, 4660); - assert_eq!(header.base_fee_per_gas, 0); - assert_eq!(header.withdrawals_root, None); - assert_eq!(header.blob_gas_used, None); - assert_eq!(header.excess_blob_gas, None); - assert_eq!(header.parent_beacon_block_root, None); + assert_eq!(header.base_fee_per_gas, INITIAL_BASE_FEE); + assert_eq!(header.withdrawals_root, Some(compute_withdrawals_root(&[]))); + assert_eq!(header.blob_gas_used, Some(0)); + assert_eq!(header.excess_blob_gas, Some(0)); + assert_eq!(header.parent_beacon_block_root, Some(H256::zero())); assert!(body.transactions.is_empty()); assert!(body.ommers.is_empty()); - assert_eq!(body.withdrawals, None); + assert!(body.withdrawals.is_some_and(|w| w.is_empty())); + } + + #[test] + // Parses genesis received by kurtosis and checks that the hash matches the next block's parent hash + fn read_and_compute_hash() { + let file = File::open("../../test_data/genesis.json").expect("Failed to open genesis file"); + let reader = BufReader::new(file); + let genesis: Genesis = + serde_json::from_reader(reader).expect("Failed to deserialize genesis file"); + let genesis_block_hash = genesis.get_block().header.compute_block_hash(); + assert_eq!( + genesis_block_hash, + H256::from_str("0xcb5306dd861d0f2c1f9952fbfbc75a46d0b6ce4f37bea370c3471fe8410bf40b") + .unwrap() + ) } } diff --git a/crates/core/types/transaction.rs b/crates/core/types/transaction.rs index 0eb529da67..f396eccc9c 100644 --- a/crates/core/types/transaction.rs +++ b/crates/core/types/transaction.rs @@ -914,7 +914,7 @@ mod serde_impl { #[cfg(test)] mod tests { - use crate::types::{compute_receipts_root, BlockBody, Receipt}; + use crate::types::{compute_receipts_root, compute_transactions_root, BlockBody, Receipt}; use super::*; use hex_literal::hex; @@ -941,7 +941,7 @@ mod tests { body.transactions.push(Transaction::LegacyTransaction(tx)); let expected_root = hex!("8151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcb"); - let result = body.compute_transactions_root(); + let result = compute_transactions_root(&body.transactions); assert_eq!(result, expected_root.into()); } diff --git a/crates/evm/evm.rs b/crates/evm/evm.rs index 95cfe77508..47435265f1 100644 --- a/crates/evm/evm.rs +++ b/crates/evm/evm.rs @@ -306,6 +306,7 @@ pub fn beacon_root_contract_call( }; let mut block_env = block_env(header); block_env.basefee = RevmU256::ZERO; + block_env.gas_limit = RevmU256::from(30_000_000); let mut evm = Evm::builder() .with_db(&mut state.0) diff --git a/crates/rpc/engine/mod.rs b/crates/rpc/engine/mod.rs index ab1a152d59..347bc31704 100644 --- a/crates/rpc/engine/mod.rs +++ b/crates/rpc/engine/mod.rs @@ -1,7 +1,8 @@ use ethereum_rust_core::{ - types::{ExecutionPayloadV3, PayloadStatus, PayloadValidationStatus}, + types::{validate_block_header, ExecutionPayloadV3, PayloadStatus}, H256, }; +use ethereum_rust_evm::{evm_state, execute_block, SpecId}; use ethereum_rust_storage::Store; use serde_json::{json, Value}; use tracing::info; @@ -50,57 +51,65 @@ pub fn new_payload_v3( storage: Store, ) -> Result { let block_hash = request.payload.block_hash; + info!("Received new payload with block hash: {block_hash}"); - info!("Received new payload with block hash: {}", block_hash); - - let (block_header, block_body) = - match request.payload.into_block(request.parent_beacon_block_root) { - Ok(block) => block, - Err(error) => { - return Ok(PayloadStatus { - status: PayloadValidationStatus::Invalid, - latest_valid_hash: Some(H256::zero()), - validation_error: Some(error.to_string()), - }) - } - }; + let block = match request.payload.into_block(request.parent_beacon_block_root) { + Ok(block) => block, + Err(error) => return Ok(PayloadStatus::invalid_with_err(&error.to_string())), + }; // Payload Validation // Check timestamp does not fall within the time frame of the Cancun fork match storage.get_cancun_time().map_err(|_| RpcErr::Internal)? { - Some(cancun_time) if block_header.timestamp > cancun_time => {} + Some(cancun_time) if block.header.timestamp > cancun_time => {} _ => return Err(RpcErr::UnsuportedFork), } // Check that block_hash is valid - let actual_block_hash = block_header.compute_block_hash(); + let actual_block_hash = block.header.compute_block_hash(); if block_hash != actual_block_hash { - return Ok(PayloadStatus { - status: PayloadValidationStatus::Invalid, - latest_valid_hash: None, - validation_error: Some("Invalid block hash".to_string()), - }); + return Ok(PayloadStatus::invalid_with_err("Invalid block hash")); } - info!("Block hash {} is valid", block_hash); + info!("Block hash {block_hash} is valid"); // Concatenate blob versioned hashes lists (tx.blob_versioned_hashes) of each blob transaction included in the payload, respecting the order of inclusion // and check that the resulting array matches expected_blob_versioned_hashes - let blob_versioned_hashes: Vec = block_body + let blob_versioned_hashes: Vec = block + .body .transactions .iter() .flat_map(|tx| tx.blob_versioned_hashes()) .collect(); if request.expected_blob_versioned_hashes != blob_versioned_hashes { - return Ok(PayloadStatus { - status: PayloadValidationStatus::Invalid, - latest_valid_hash: None, - validation_error: Some("Invalid blob_versioned_hashes".to_string()), - }); + return Ok(PayloadStatus::invalid_with_err( + "Invalid blob_versioned_hashes", + )); } - Ok(PayloadStatus { - status: PayloadValidationStatus::Valid, - latest_valid_hash: Some(block_hash), - validation_error: None, - }) + // Fetch parent block header and validate current header + if let Some(parent_header) = storage + .get_block_header(block.header.number.saturating_sub(1)) + .map_err(|_| RpcErr::Internal)? + { + if !validate_block_header(&block.header, &parent_header) { + return Ok(PayloadStatus::invalid_with_hash( + parent_header.compute_block_hash(), + )); + } + } else { + return Ok(PayloadStatus::syncing()); + } + + // Execute and store the block + info!("Executing payload with block hash: {block_hash}"); + execute_block(&block, &mut evm_state(storage.clone()), SpecId::CANCUN) + .map_err(|_| RpcErr::Vm)?; + info!("Block with hash {block_hash} executed succesfully"); + storage + .add_block_number(block_hash, block.header.number) + .map_err(|_| RpcErr::Internal)?; + storage.add_block(block).map_err(|_| RpcErr::Internal)?; + info!("Block with hash {block_hash} added to storage"); + + Ok(PayloadStatus::valid_with_hash(block_hash)) }