From bc150e9dd7c3fb6c50da1085d8780c5761cdfb08 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Mon, 23 Sep 2024 16:18:28 -0300 Subject: [PATCH 01/23] Revert "fix: remove totalDifficulty (#347)" This reverts commit a66dc76539f7bf40e845b2f5af2eb4ef78c3a583. --- crates/rpc/eth/block.rs | 28 ++++++++++++++--- crates/rpc/types/block.rs | 10 ++++-- crates/storage/engines/api.rs | 26 ++++++++++++++- crates/storage/engines/in_memory.rs | 42 ++++++++++++++++++++++++- crates/storage/engines/libmdbx.rs | 49 +++++++++++++++++++++++++++-- crates/storage/rlp.rs | 3 ++ crates/storage/storage.rs | 30 +++++++++++++++++- 7 files changed, 176 insertions(+), 12 deletions(-) diff --git a/crates/rpc/eth/block.rs b/crates/rpc/eth/block.rs index 614fe2f69..57287d473 100644 --- a/crates/rpc/eth/block.rs +++ b/crates/rpc/eth/block.rs @@ -12,8 +12,12 @@ use crate::{ utils::RpcErr, RpcHandler, }; -use ethereum_rust_core::types::{ - calculate_base_fee_per_blob_gas, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, Receipt, +use ethereum_rust_core::{ + types::{ + calculate_base_fee_per_blob_gas, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, + Receipt, + }, + U256, }; use ethereum_rust_storage::Store; @@ -76,7 +80,15 @@ impl RpcHandler for GetBlockByNumberRequest { _ => return Ok(Value::Null), }; let hash = header.compute_block_hash(); - let block = RpcBlock::build(header, body, hash, self.hydrated); + // TODO (#307): Remove TotalDifficulty. + let total_difficulty = storage.get_block_total_difficulty(hash)?; + let block = RpcBlock::build( + header, + body, + hash, + self.hydrated, + total_difficulty.unwrap_or(U256::zero()), + ); serde_json::to_value(&block).map_err(|_| RpcErr::Internal) } @@ -107,7 +119,15 @@ impl RpcHandler for GetBlockByHashRequest { _ => return Ok(Value::Null), }; let hash = header.compute_block_hash(); - let block = RpcBlock::build(header, body, hash, self.hydrated); + // TODO (#307): Remove TotalDifficulty. + let total_difficulty = storage.get_block_total_difficulty(hash)?; + let block = RpcBlock::build( + header, + body, + hash, + self.hydrated, + total_difficulty.unwrap_or(U256::zero()), + ); serde_json::to_value(&block).map_err(|_| RpcErr::Internal) } } diff --git a/crates/rpc/types/block.rs b/crates/rpc/types/block.rs index 454cf686f..1bc2ca2f3 100644 --- a/crates/rpc/types/block.rs +++ b/crates/rpc/types/block.rs @@ -2,7 +2,7 @@ use super::transaction::RpcTransaction; use ethereum_rust_core::{ serde_utils, types::{Block, BlockBody, BlockHash, BlockHeader, BlockNumber, Withdrawal}, - H256, + H256, U256, }; use ethereum_rust_rlp::encode::RLPEncode; @@ -14,6 +14,8 @@ pub struct RpcBlock { hash: H256, #[serde(with = "serde_utils::u64::hex_str")] size: u64, + // TODO (#307): Remove TotalDifficulty. + total_difficulty: U256, #[serde(flatten)] header: BlockHeader, #[serde(flatten)] @@ -48,6 +50,7 @@ impl RpcBlock { body: BlockBody, hash: H256, full_transactions: bool, + total_difficulty: U256, ) -> RpcBlock { let size = Block { header: header.clone(), @@ -67,6 +70,7 @@ impl RpcBlock { RpcBlock { hash, + total_difficulty, size: size as u64, header, body: body_wrapper, @@ -190,8 +194,8 @@ mod test { }; let hash = block_header.compute_block_hash(); - let block = RpcBlock::build(block_header, block_body, hash, true); - let expected_block = r#"{"hash":"0x63d6a2504601fc2db0ccf02a28055eb0cdb40c444ecbceec0f613980421a035e","size":"0x2d6","parentHash":"0x1ac1bf1eef97dc6b03daba5af3b89881b7ae4bc1600dc434f450a9ec34d44999","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba","stateRoot":"0x9de6f95cb4ff4ef22a73705d6ba38c4b927c7bca9887ef5d24a734bb863218d9","transactionsRoot":"0x578602b2b7e3a3291c3eefca3a08bc13c0d194f9845a39b6f3bcf843d9fed79d","receiptsRoot":"0x035d56bac3f47246c5eed0e6642ca40dc262f9144b582f058bc23ded72aa72fa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x16345785d8a0000","gasUsed":"0xa8de","timestamp":"0x3e8","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x7","withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactions":[{"type":"0x2","nonce":"0x0","to":"0x6177843db3138ae69679a54b95cf345ed759450d","gas":"0xf618","value":"0xaa87bee538000","input":"0x307831353638","maxPriorityFeePerGas":"0x11","maxFeePerGas":"0x4e","gasPrice":"0x4e","accessList":[{"address":"0x6177843db3138ae69679a54b95cf345ed759450d","storageKeys":[]}],"chainId":"0x301824","yParity":"0x0","v":"0x0","r":"0x151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d","s":"0x64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","blockNumber":"0x1","blockHash":"0x63d6a2504601fc2db0ccf02a28055eb0cdb40c444ecbceec0f613980421a035e","from":"0x35af8ea983a3ba94c655e19b82b932a30d6b9558","hash":"0x0b8c8f37731d9493916b06d666c3fd5dee2c3bbda06dfe866160d717e00dda91","transactionIndex":"0x0"}],"uncles":[],"withdrawals":[]}"#; + let block = RpcBlock::build(block_header, block_body, hash, true, U256::zero()); + let expected_block = r#"{"hash":"0x63d6a2504601fc2db0ccf02a28055eb0cdb40c444ecbceec0f613980421a035e","size":"0x2d6","totalDifficulty":"0x0","parentHash":"0x1ac1bf1eef97dc6b03daba5af3b89881b7ae4bc1600dc434f450a9ec34d44999","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba","stateRoot":"0x9de6f95cb4ff4ef22a73705d6ba38c4b927c7bca9887ef5d24a734bb863218d9","transactionsRoot":"0x578602b2b7e3a3291c3eefca3a08bc13c0d194f9845a39b6f3bcf843d9fed79d","receiptsRoot":"0x035d56bac3f47246c5eed0e6642ca40dc262f9144b582f058bc23ded72aa72fa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x16345785d8a0000","gasUsed":"0xa8de","timestamp":"0x3e8","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x7","withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactions":[{"type":"0x2","nonce":"0x0","to":"0x6177843db3138ae69679a54b95cf345ed759450d","gas":"0xf618","value":"0xaa87bee538000","input":"0x307831353638","maxPriorityFeePerGas":"0x11","maxFeePerGas":"0x4e","gasPrice":"0x4e","accessList":[{"address":"0x6177843db3138ae69679a54b95cf345ed759450d","storageKeys":[]}],"chainId":"0x301824","yParity":"0x0","v":"0x0","r":"0x151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d","s":"0x64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","blockNumber":"0x1","blockHash":"0x63d6a2504601fc2db0ccf02a28055eb0cdb40c444ecbceec0f613980421a035e","from":"0x35af8ea983a3ba94c655e19b82b932a30d6b9558","hash":"0x0b8c8f37731d9493916b06d666c3fd5dee2c3bbda06dfe866160d717e00dda91","transactionIndex":"0x0"}],"uncles":[],"withdrawals":[]}"#; assert_eq!(serde_json::to_string(&block).unwrap(), expected_block) } } diff --git a/crates/storage/engines/api.rs b/crates/storage/engines/api.rs index bf7c1a4cc..5bcc99899 100644 --- a/crates/storage/engines/api.rs +++ b/crates/storage/engines/api.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use ethereum_rust_core::types::{ Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, }; -use ethereum_types::{Address, H256}; +use ethereum_types::{Address, H256, U256}; use std::{fmt::Debug, panic::RefUnwindSafe}; use crate::error::StoreError; @@ -53,6 +53,19 @@ pub trait StoreEngine: Debug + Send + Sync + RefUnwindSafe { /// Obtain block number for a given hash fn get_block_number(&self, block_hash: BlockHash) -> Result, StoreError>; + // TODO (#307): Remove TotalDifficulty. + /// Add block total difficulty + fn add_block_total_difficulty( + &self, + block_hash: BlockHash, + block_total_difficulty: U256, + ) -> Result<(), StoreError>; + + // TODO (#307): Remove TotalDifficulty. + /// Obtain block total difficulty + fn get_block_total_difficulty(&self, block_hash: BlockHash) + -> Result, StoreError>; + /// Store transaction location (block number and index of the transaction within the block) fn add_transaction_location( &self, @@ -169,6 +182,17 @@ pub trait StoreEngine: Debug + Send + Sync + RefUnwindSafe { // Obtain latest block number fn get_latest_block_number(&self) -> Result, StoreError>; + // TODO (#307): Remove TotalDifficulty. + // Update latest total difficulty + fn update_latest_total_difficulty( + &self, + latest_total_difficulty: U256, + ) -> Result<(), StoreError>; + + // TODO (#307): Remove TotalDifficulty. + // Obtain latest total difficulty + fn get_latest_total_difficulty(&self) -> Result, StoreError>; + // Update pending block number fn update_pending_block_number(&self, block_number: BlockNumber) -> Result<(), StoreError>; diff --git a/crates/storage/engines/in_memory.rs b/crates/storage/engines/in_memory.rs index d2522cb01..af24cba89 100644 --- a/crates/storage/engines/in_memory.rs +++ b/crates/storage/engines/in_memory.rs @@ -4,7 +4,7 @@ use ethereum_rust_core::types::{ BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, }; use ethereum_rust_trie::{InMemoryTrieDB, Trie}; -use ethereum_types::{Address, H256}; +use ethereum_types::{Address, H256, U256}; use std::{ collections::HashMap, fmt::Debug, @@ -34,6 +34,8 @@ struct StoreInner { receipts: HashMap>, state_trie_nodes: NodeMap, storage_trie_nodes: HashMap, + // TODO (#307): Remove TotalDifficulty. + block_total_difficulties: HashMap, } #[derive(Default, Debug)] @@ -43,6 +45,8 @@ struct ChainData { finalized_block_number: Option, safe_block_number: Option, latest_block_number: Option, + // TODO (#307): Remove TotalDifficulty. + latest_total_difficulty: Option, pending_block_number: Option, } @@ -105,6 +109,28 @@ impl StoreEngine for Store { Ok(self.inner().block_numbers.get(&block_hash).copied()) } + fn add_block_total_difficulty( + &self, + block_hash: BlockHash, + block_total_difficulty: U256, + ) -> Result<(), StoreError> { + self.inner() + .block_total_difficulties + .insert(block_hash, block_total_difficulty); + Ok(()) + } + + fn get_block_total_difficulty( + &self, + block_hash: BlockHash, + ) -> Result, StoreError> { + Ok(self + .inner() + .block_total_difficulties + .get(&block_hash) + .copied()) + } + fn add_transaction_location( &self, transaction_hash: H256, @@ -239,6 +265,16 @@ impl StoreEngine for Store { .replace(block_number); Ok(()) } + fn update_latest_total_difficulty( + &self, + latest_total_difficulty: U256, + ) -> Result<(), StoreError> { + self.inner() + .chain_data + .latest_total_difficulty + .replace(latest_total_difficulty); + Ok(()) + } fn get_latest_block_number(&self) -> Result, StoreError> { Ok(self.inner().chain_data.latest_block_number) @@ -252,6 +288,10 @@ impl StoreEngine for Store { Ok(()) } + fn get_latest_total_difficulty(&self) -> Result, StoreError> { + Ok(self.inner().chain_data.latest_total_difficulty) + } + fn get_pending_block_number(&self) -> Result, StoreError> { Ok(self.inner().chain_data.pending_block_number) } diff --git a/crates/storage/engines/libmdbx.rs b/crates/storage/engines/libmdbx.rs index 0ae765892..538b7546a 100644 --- a/crates/storage/engines/libmdbx.rs +++ b/crates/storage/engines/libmdbx.rs @@ -1,8 +1,8 @@ use super::api::StoreEngine; use crate::error::StoreError; use crate::rlp::{ - AccountCodeHashRLP, AccountCodeRLP, BlockBodyRLP, BlockHashRLP, BlockHeaderRLP, ReceiptRLP, - Rlp, TransactionHashRLP, TransactionRLP, TupleRLP, + AccountCodeHashRLP, AccountCodeRLP, BlockBodyRLP, BlockHashRLP, BlockHeaderRLP, + BlockTotalDifficultyRLP, ReceiptRLP, Rlp, TransactionHashRLP, TransactionRLP, TupleRLP, }; use anyhow::Result; use bytes::Bytes; @@ -131,6 +131,22 @@ impl StoreEngine for Store { ) -> std::result::Result, StoreError> { self.read::(block_hash.into()) } + fn add_block_total_difficulty( + &self, + block_hash: BlockHash, + block_total_difficulty: U256, + ) -> std::result::Result<(), StoreError> { + self.write::(block_hash.into(), block_total_difficulty.into()) + } + + fn get_block_total_difficulty( + &self, + block_hash: BlockHash, + ) -> std::result::Result, StoreError> { + Ok(self + .read::(block_hash.into())? + .map(|b| b.to())) + } fn add_account_code(&self, code_hash: H256, code: Bytes) -> Result<(), StoreError> { self.write::(code_hash.into(), code.into()) @@ -290,6 +306,25 @@ impl StoreEngine for Store { } } + fn update_latest_total_difficulty( + &self, + latest_total_difficulty: U256, + ) -> std::result::Result<(), StoreError> { + self.write::( + ChainDataIndex::LatestTotalDifficulty, + latest_total_difficulty.encode_to_vec(), + ) + } + + fn get_latest_total_difficulty(&self) -> Result, StoreError> { + match self.read::(ChainDataIndex::LatestTotalDifficulty)? { + None => Ok(None), + Some(ref rlp) => RLPDecode::decode(rlp) + .map(Some) + .map_err(|_| StoreError::DecodeError), + } + } + fn update_pending_block_number(&self, block_number: BlockNumber) -> Result<(), StoreError> { self.write::( ChainDataIndex::PendingBlockNumber, @@ -352,6 +387,12 @@ table!( ( BlockNumbers ) BlockHashRLP => BlockNumber ); +// TODO (#307): Remove TotalDifficulty. +table!( + /// Block hash to total difficulties table. + ( BlockTotalDifficulties ) BlockHashRLP => BlockTotalDifficultyRLP +); + table!( /// Block headers table. ( Headers ) BlockHashRLP => BlockHeaderRLP @@ -467,6 +508,8 @@ pub enum ChainDataIndex { SafeBlockNumber = 3, LatestBlockNumber = 4, PendingBlockNumber = 5, + // TODO (#307): Remove TotalDifficulty. + LatestTotalDifficulty = 6, } impl Encodable for ChainDataIndex { @@ -482,6 +525,8 @@ impl Encodable for ChainDataIndex { pub fn init_db(path: Option>) -> Database { let tables = [ table_info!(BlockNumbers), + // TODO (#307): Remove TotalDifficulty. + table_info!(BlockTotalDifficulties), table_info!(Headers), table_info!(Bodies), table_info!(AccountCodes), diff --git a/crates/storage/rlp.rs b/crates/storage/rlp.rs index a25c3b7e6..5bf248a47 100644 --- a/crates/storage/rlp.rs +++ b/crates/storage/rlp.rs @@ -6,6 +6,7 @@ use ethereum_rust_core::{ H256, }; use ethereum_rust_rlp::{decode::RLPDecode, encode::RLPEncode}; +use ethereum_types::U256; #[cfg(feature = "libmdbx")] use libmdbx::orm::{Decodable, Encodable}; @@ -17,6 +18,8 @@ pub type AccountCodeRLP = Rlp; pub type BlockHashRLP = Rlp; pub type BlockHeaderRLP = Rlp; pub type BlockBodyRLP = Rlp; +// TODO (#307): Remove TotalDifficulty. +pub type BlockTotalDifficultyRLP = Rlp; // Receipt types pub type ReceiptRLP = Rlp; diff --git a/crates/storage/storage.rs b/crates/storage/storage.rs index 9a8661aac..380c2804b 100644 --- a/crates/storage/storage.rs +++ b/crates/storage/storage.rs @@ -154,6 +154,22 @@ impl Store { self.engine.clone().get_block_number(block_hash) } + pub fn add_block_total_difficulty( + &self, + block_hash: BlockHash, + block_difficulty: U256, + ) -> Result<(), StoreError> { + self.engine + .add_block_total_difficulty(block_hash, block_difficulty) + } + + pub fn get_block_total_difficulty( + &self, + block_hash: BlockHash, + ) -> Result, StoreError> { + self.engine.get_block_total_difficulty(block_hash) + } + pub fn add_transaction_location( &self, transaction_hash: H256, @@ -328,12 +344,17 @@ impl Store { // TODO Maybe add both in a single tx? let header = block.header; let number = header.number; + let latest_total_difficulty = self.get_latest_total_difficulty()?; + let block_total_difficulty = + latest_total_difficulty.unwrap_or(U256::zero()) + header.difficulty; let hash = header.compute_block_hash(); self.add_transaction_locations(&block.body.transactions, number, hash)?; self.add_block_body(hash, block.body)?; self.add_block_header(hash, header)?; self.add_block_number(hash, number)?; - self.update_latest_block_number(number) + self.update_latest_block_number(number)?; + self.add_block_total_difficulty(hash, block_total_difficulty)?; + self.update_latest_total_difficulty(block_total_difficulty) } fn add_transaction_locations( @@ -460,11 +481,18 @@ impl Store { pub fn update_latest_block_number(&self, block_number: BlockNumber) -> Result<(), StoreError> { self.engine.update_latest_block_number(block_number) } + pub fn update_latest_total_difficulty(&self, block_difficulty: U256) -> Result<(), StoreError> { + self.engine.update_latest_total_difficulty(block_difficulty) + } pub fn get_latest_block_number(&self) -> Result, StoreError> { self.engine.get_latest_block_number() } + pub fn get_latest_total_difficulty(&self) -> Result, StoreError> { + self.engine.get_latest_total_difficulty() + } + pub fn update_pending_block_number(&self, block_number: BlockNumber) -> Result<(), StoreError> { self.engine.update_pending_block_number(block_number) } From 0c2cd4d822df90c3681b8429aa792eeb52ef2dd3 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Mon, 23 Sep 2024 18:29:41 -0300 Subject: [PATCH 02/23] Complete forkChoiceUpdatedV3 up until payload validation --- crates/rpc/engine/fork_choice.rs | 122 +++++++++++++++++++++++----- crates/rpc/types/fork_choice.rs | 36 ++++++-- crates/rpc/types/payload.rs | 8 ++ crates/storage/engines/api.rs | 5 +- crates/storage/engines/in_memory.rs | 4 + crates/storage/engines/libmdbx.rs | 4 + crates/storage/storage.rs | 7 ++ 7 files changed, 158 insertions(+), 28 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index 21d2aca9d..66b81820a 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -1,8 +1,12 @@ +use ethereum_rust_core::U256; use ethereum_rust_storage::Store; -use serde_json::{json, Value}; +use serde_json::Value; use crate::{ - types::fork_choice::{ForkChoiceState, PayloadAttributesV3}, + types::{ + fork_choice::{ForkChoiceResponse, ForkChoiceState, PayloadAttributesV3}, + payload::PayloadStatus, + }, RpcErr, RpcHandler, }; @@ -26,27 +30,103 @@ impl RpcHandler for ForkChoiceUpdatedV3 { } fn handle(&self, storage: Store) -> Result { - // Just a minimal implementation to pass rpc-compat Hive tests. - // TODO (#50): Implement `engine_forkchoiceUpdatedV3` - let safe = storage.get_block_number(self.fork_choice_state.safe_block_hash); - let finalized = storage.get_block_number(self.fork_choice_state.finalized_block_hash); - - // Check if we already have the blocks stored. - let (safe_block_number, finalized_block_number) = match (safe, finalized) { - (Ok(Some(safe)), Ok(Some(finalized))) => (safe, finalized), - _ => return Err(RpcErr::Internal), + let error_response = |err_msg: &str| { + serde_json::to_value(ForkChoiceResponse::from(PayloadStatus::invalid_with_err( + err_msg, + ))) + .map_err(|_| RpcErr::Internal) }; + if self.fork_choice_state.head_block_hash.is_zero() { + return Ok(error_response("forkchoice requested update to zero hash")?); + } + // Check if we have the block stored + let Some(head_block) = storage.get_block_by_hash(self.fork_choice_state.head_block_hash)? + else { + // TODO: We don't yet support syncing + return Err(RpcErr::Internal); + }; + // Check that we are not being pushed pre-merge + if !head_block.header.difficulty.is_zero() || head_block.header.number == 0 { + let total_difficulty = + storage.get_block_total_difficulty(self.fork_choice_state.head_block_hash)?; + let parent_total_difficulty = + storage.get_block_total_difficulty(head_block.header.parent_hash)?; + let terminal_total_difficulty = storage.get_chain_config()?.terminal_total_difficulty; + if terminal_total_difficulty.is_none() + || total_difficulty.is_none() + || head_block.header.number > 0 && parent_total_difficulty.is_none() + { + return Ok(error_response( + "total difficulties unavailable for terminal total difficulty check", + )?); + } + if total_difficulty.unwrap() < terminal_total_difficulty.unwrap().into() { + return Ok(error_response("refusing beacon update to pre-merge")?); + } + if head_block.header.number > 0 && parent_total_difficulty.unwrap() >= U256::zero() { + return Ok(error_response( + "parent block is already post terminal total difficulty", + )?); + } + } + let canonical_block = storage.get_canonical_block(head_block.header.number)?; + let current_block_hash = { + let current_block_number = + storage.get_latest_block_number()?.ok_or(RpcErr::Internal)?; + storage.get_canonical_block(current_block_number)? + }; + if canonical_block.is_some_and(|h| h != self.fork_choice_state.head_block_hash) { + // TODO: We don't handle re-orgs yet + return Err(RpcErr::Internal); + } else if current_block_hash.is_some_and(|h| h != self.fork_choice_state.head_block_hash) { + // If the head block is already in our canonical chain, the beacon client is + // probably resyncing. Ignore the update. + return Ok(serde_json::to_value(PayloadStatus::valid()).map_err(|_| RpcErr::Internal)?); + } - storage.update_finalized_block_number(finalized_block_number)?; - storage.update_safe_block_number(safe_block_number)?; - serde_json::to_value(json!({ - "payloadId": null, - "payloadStatus": { - "latestValidHash": null, - "status": "SYNCING", - "validationError": null + // Process finalized block + if !self.fork_choice_state.finalized_block_hash.is_zero() { + // If the finalized block is not in our canonical tree, something is wrong + let Some(finalized_block) = + storage.get_block_by_hash(self.fork_choice_state.finalized_block_hash)? + else { + return Ok(error_response("final block not available in database")?); + }; + + if !storage + .get_canonical_block(finalized_block.header.number)? + .is_some_and(|h| h == self.fork_choice_state.finalized_block_hash) + { + return Ok(error_response("final block not in canonical chain")?); + } + // Set the finalized block + storage.update_finalized_block_number(finalized_block.header.number)?; + } + + // Process safe block + if !self.fork_choice_state.safe_block_hash.is_zero() { + // If the safe block is not in our canonical tree, something is wrong + let Some(safe_block) = + storage.get_block_by_hash(self.fork_choice_state.safe_block_hash)? + else { + return Ok(error_response("safe block not available in database")?); + }; + + if !storage + .get_canonical_block(safe_block.header.number)? + .is_some_and(|h| h == self.fork_choice_state.safe_block_hash) + { + return Ok(error_response("safe block not in canonical chain")?); } - })) - .map_err(|_| RpcErr::Internal) + // Set the safe block + storage.update_safe_block_number(safe_block.header.number)?; + } + let response = ForkChoiceResponse::from(PayloadStatus::valid_with_hash( + self.fork_choice_state.head_block_hash, + )); + + // TODO: Build block + + Ok(serde_json::to_value(response).map_err(|_| RpcErr::Internal)?) } } diff --git a/crates/rpc/types/fork_choice.rs b/crates/rpc/types/fork_choice.rs index 2d856d22f..986c88e61 100644 --- a/crates/rpc/types/fork_choice.rs +++ b/crates/rpc/types/fork_choice.rs @@ -1,5 +1,7 @@ use ethereum_rust_core::{types::Withdrawal, Address, H256, U256}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; + +use super::payload::PayloadStatus; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -14,9 +16,31 @@ pub struct ForkChoiceState { #[serde(rename_all = "camelCase")] #[allow(unused)] pub struct PayloadAttributesV3 { - timestamp: U256, - prev_randao: H256, - suggested_fee_recipient: Address, - withdrawals: Vec, - parent_beacon_block_root: H256, + pub timestamp: U256, + pub prev_randao: H256, + pub suggested_fee_recipient: Address, + pub withdrawals: Vec, + pub parent_beacon_block_root: H256, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ForkChoiceResponse { + pub payload_status: PayloadStatus, + pub payload_id: Option, +} + +impl ForkChoiceResponse { + pub fn set_id(&mut self, id: u8) { + self.payload_id = Some(id) + } +} + +impl From for ForkChoiceResponse { + fn from(value: PayloadStatus) -> Self { + Self { + payload_status: value, + payload_id: None, + } + } } diff --git a/crates/rpc/types/payload.rs b/crates/rpc/types/payload.rs index 5de9b1a76..b005dac80 100644 --- a/crates/rpc/types/payload.rs +++ b/crates/rpc/types/payload.rs @@ -163,6 +163,14 @@ impl PayloadStatus { validation_error: None, } } + /// Creates a PayloadStatus with valid status and latest valid hash + pub fn valid() -> Self { + PayloadStatus { + status: PayloadValidationStatus::Valid, + latest_valid_hash: None, + validation_error: None, + } + } } #[cfg(test)] diff --git a/crates/storage/engines/api.rs b/crates/storage/engines/api.rs index 5bcc99899..0c3304635 100644 --- a/crates/storage/engines/api.rs +++ b/crates/storage/engines/api.rs @@ -211,6 +211,9 @@ pub trait StoreEngine: Debug + Send + Sync + RefUnwindSafe { // Used for internal store operations fn open_storage_trie(&self, address: Address, storage_root: H256) -> Trie; - // Get the canonical block hash for a given block number. + // Set the canonical block hash for a given block number. fn set_canonical_block(&self, number: BlockNumber, hash: BlockHash) -> Result<(), StoreError>; + + // Get the canonical block hash for a given block number. + fn get_canonical_block(&self, number: BlockNumber) -> Result, StoreError>; } diff --git a/crates/storage/engines/in_memory.rs b/crates/storage/engines/in_memory.rs index af24cba89..3b3e6bc5c 100644 --- a/crates/storage/engines/in_memory.rs +++ b/crates/storage/engines/in_memory.rs @@ -336,6 +336,10 @@ impl StoreEngine for Store { self.inner().canonical_hashes.insert(number, hash); Ok(()) } + + fn get_canonical_block(&self, number: BlockNumber) -> Result, StoreError> { + Ok(self.inner().canonical_hashes.get(&number).cloned()) + } } impl Debug for Store { diff --git a/crates/storage/engines/libmdbx.rs b/crates/storage/engines/libmdbx.rs index 538b7546a..4e39c10ad 100644 --- a/crates/storage/engines/libmdbx.rs +++ b/crates/storage/engines/libmdbx.rs @@ -367,6 +367,10 @@ impl StoreEngine for Store { fn set_canonical_block(&self, number: BlockNumber, hash: BlockHash) -> Result<(), StoreError> { self.write::(number, hash.into()) } + + fn get_canonical_block(&self, number: BlockNumber) -> Result, StoreError> { + Ok(self.read::(number)?.map(|b| b.to())) + } } impl Debug for Store { diff --git a/crates/storage/storage.rs b/crates/storage/storage.rs index 380c2804b..278699984 100644 --- a/crates/storage/storage.rs +++ b/crates/storage/storage.rs @@ -564,6 +564,13 @@ impl Store { let trie = self.engine.open_storage_trie(address, storage_root); Ok(trie.get_proof(&hash_key(storage_key))?) } + + pub fn get_canonical_block( + &self, + block_number: BlockNumber, + ) -> Result, StoreError> { + self.engine.get_canonical_block(block_number) + } } fn hash_address(address: &Address) -> Vec { From 1a1b781be096d61e1f38b2c9d7c5b4ba0f965e01 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Tue, 24 Sep 2024 12:25:00 -0300 Subject: [PATCH 03/23] Add BuildPayloadArgs + id --- crates/core/types/mod.rs | 2 ++ crates/rpc/engine/fork_choice.rs | 13 ++++++++++++- crates/rpc/types/payload.rs | 32 ++++++++++++++++---------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/crates/core/types/mod.rs b/crates/core/types/mod.rs index 02ceae38d..f25446836 100644 --- a/crates/core/types/mod.rs +++ b/crates/core/types/mod.rs @@ -2,6 +2,7 @@ mod account; mod block; mod constants; mod genesis; +mod payload; mod receipt; mod transaction; @@ -9,5 +10,6 @@ pub use account::*; pub use block::*; pub use constants::*; pub use genesis::*; +pub use payload::*; pub use receipt::*; pub use transaction::*; diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index 66b81820a..05cd1c537 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -1,4 +1,4 @@ -use ethereum_rust_core::U256; +use ethereum_rust_core::{types::BuildPayloadArgs, U256}; use ethereum_rust_storage::Store; use serde_json::Value; @@ -126,6 +126,17 @@ impl RpcHandler for ForkChoiceUpdatedV3 { )); // TODO: Build block + if let Some(attributes) = &self.payload_attributes { + let payload = BuildPayloadArgs { + parent: self.fork_choice_state.head_block_hash, + timestamp: attributes.timestamp, + fee_recipient: attributes.suggested_fee_recipient, + random: attributes.prev_randao, + withdrawals: attributes.withdrawals.clone(), + beacon_root: Some(attributes.parent_beacon_block_root), + version: 3, + }; + } Ok(serde_json::to_value(response).map_err(|_| RpcErr::Internal)?) } diff --git a/crates/rpc/types/payload.rs b/crates/rpc/types/payload.rs index b005dac80..5860db1c2 100644 --- a/crates/rpc/types/payload.rs +++ b/crates/rpc/types/payload.rs @@ -14,31 +14,31 @@ use ethereum_rust_core::{ #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExecutionPayloadV3 { - parent_hash: H256, - fee_recipient: Address, - state_root: H256, - receipts_root: H256, - logs_bloom: Bloom, - prev_randao: H256, + pub parent_hash: H256, + pub fee_recipient: Address, + pub state_root: H256, + pub receipts_root: H256, + pub logs_bloom: Bloom, + pub prev_randao: H256, #[serde(with = "serde_utils::u64::hex_str")] - block_number: u64, + pub block_number: u64, #[serde(with = "serde_utils::u64::hex_str")] - gas_limit: u64, + pub gas_limit: u64, #[serde(with = "serde_utils::u64::hex_str")] - gas_used: u64, + pub gas_used: u64, #[serde(with = "serde_utils::u64::hex_str")] - timestamp: u64, + pub timestamp: u64, #[serde(with = "serde_utils::bytes")] - extra_data: Bytes, + pub extra_data: Bytes, #[serde(with = "serde_utils::u64::hex_str")] - base_fee_per_gas: u64, + pub base_fee_per_gas: u64, pub block_hash: H256, - transactions: Vec, - withdrawals: Vec, + pub transactions: Vec, + pub withdrawals: Vec, #[serde(with = "serde_utils::u64::hex_str")] - blob_gas_used: u64, + pub blob_gas_used: u64, #[serde(with = "serde_utils::u64::hex_str")] - excess_blob_gas: u64, + pub excess_blob_gas: u64, } #[derive(Clone, Debug)] From 7386f2b71d7b9a93c51ef5542ee1e41ae5d27c5c Mon Sep 17 00:00:00 2001 From: fmoletta Date: Tue, 24 Sep 2024 14:15:41 -0300 Subject: [PATCH 04/23] Output proper payload id --- crates/core/serde_utils.rs | 24 ++++++++++++++++++++---- crates/rpc/engine/fork_choice.rs | 4 +++- crates/rpc/types/fork_choice.rs | 10 +++++----- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/crates/core/serde_utils.rs b/crates/core/serde_utils.rs index a28401e80..21c0d6b68 100644 --- a/crates/core/serde_utils.rs +++ b/crates/core/serde_utils.rs @@ -99,10 +99,7 @@ pub mod u64 { where D: Deserializer<'de>, { - let value = String::deserialize(d)?; - let res = u64::from_str_radix(value.trim_start_matches("0x"), 16) - .map_err(|_| D::Error::custom("Failed to deserialize u64 value")); - res + super::hex_str::deserialize(d) } pub fn serialize(value: &u64, serializer: S) -> Result @@ -139,6 +136,25 @@ pub mod u64 { } } + pub mod hex_str_opt_padded { + use serde::Serialize; + + use super::*; + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + Option::::serialize(&value.map(|v| format!("{:#018x}", v)), serializer) + } + + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + super::hex_str_opt::deserialize(d) + } + } + pub fn deser_dec_str<'de, D>(d: D) -> Result where D: Deserializer<'de>, diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index 05cd1c537..05345093f 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -121,7 +121,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { // Set the safe block storage.update_safe_block_number(safe_block.header.number)?; } - let response = ForkChoiceResponse::from(PayloadStatus::valid_with_hash( + let mut response = ForkChoiceResponse::from(PayloadStatus::valid_with_hash( self.fork_choice_state.head_block_hash, )); @@ -136,6 +136,8 @@ impl RpcHandler for ForkChoiceUpdatedV3 { beacon_root: Some(attributes.parent_beacon_block_root), version: 3, }; + let payload_id = payload.id(); + response.set_id(payload_id); } Ok(serde_json::to_value(response).map_err(|_| RpcErr::Internal)?) diff --git a/crates/rpc/types/fork_choice.rs b/crates/rpc/types/fork_choice.rs index 986c88e61..7ad75e3b1 100644 --- a/crates/rpc/types/fork_choice.rs +++ b/crates/rpc/types/fork_choice.rs @@ -1,7 +1,6 @@ -use ethereum_rust_core::{types::Withdrawal, Address, H256, U256}; -use serde::{Deserialize, Serialize}; - use super::payload::PayloadStatus; +use ethereum_rust_core::{serde_utils, types::Withdrawal, Address, H256, U256}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -27,11 +26,12 @@ pub struct PayloadAttributesV3 { #[serde(rename_all = "camelCase")] pub struct ForkChoiceResponse { pub payload_status: PayloadStatus, - pub payload_id: Option, + #[serde(with = "serde_utils::u64::hex_str_opt_padded")] + pub payload_id: Option, } impl ForkChoiceResponse { - pub fn set_id(&mut self, id: u8) { + pub fn set_id(&mut self, id: u64) { self.payload_id = Some(id) } } From adf52042bf5e792879bcd131bcb6b7dd1ef07b9f Mon Sep 17 00:00:00 2001 From: fmoletta Date: Tue, 24 Sep 2024 14:29:35 -0300 Subject: [PATCH 05/23] Move execution payload to core --- crates/core/types/payload.rs | 158 +++++++++++++++++++++++++++++++++++ crates/rpc/engine/payload.rs | 7 +- crates/rpc/types/payload.rs | 121 +-------------------------- 3 files changed, 161 insertions(+), 125 deletions(-) create mode 100644 crates/core/types/payload.rs diff --git a/crates/core/types/payload.rs b/crates/core/types/payload.rs new file mode 100644 index 000000000..f943602b7 --- /dev/null +++ b/crates/core/types/payload.rs @@ -0,0 +1,158 @@ +use bytes::Bytes; +use ethereum_rust_rlp::encode::RLPEncode; +use ethereum_rust_rlp::error::RLPDecodeError; +use serde::Deserialize; +use sha3::{Digest, Keccak256}; + +use crate::{ + serde_utils, + types::{ + compute_transactions_root, compute_withdrawals_root, Block, BlockBody, BlockHash, + BlockHeader, Transaction, Withdrawal, DEFAULT_OMMERS_HASH, + }, + Address, Bloom, H256, U256, +}; + +pub struct BuildPayloadArgs { + pub parent: BlockHash, + pub timestamp: U256, + pub fee_recipient: Address, + pub random: H256, + pub withdrawals: Vec, + pub beacon_root: Option, + pub version: u8, +} + +impl BuildPayloadArgs { + // Id computes an 8-byte identifier by hashing the components of the payload arguments. + pub fn id(&self) -> u64 { + let mut hasher = Keccak256::new(); + let mut timestamp = [0; 32]; + self.timestamp.to_big_endian(&mut timestamp); + hasher.update(self.parent); + hasher.update(timestamp); + hasher.update(self.random); + hasher.update(self.fee_recipient); + hasher.update(self.withdrawals.encode_to_vec()); + if let Some(beacon_root) = self.beacon_root { + hasher.update(beacon_root); + } + let res = &mut hasher.finalize()[..8]; + res[0] = self.version; + u64::from_be_bytes(res.try_into().unwrap()) + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExecutionPayloadV3 { + pub parent_hash: H256, + pub fee_recipient: Address, + pub state_root: H256, + pub receipts_root: H256, + pub logs_bloom: Bloom, + pub prev_randao: H256, + #[serde(with = "serde_utils::u64::hex_str")] + pub block_number: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub gas_limit: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub gas_used: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub timestamp: u64, + #[serde(with = "serde_utils::bytes")] + pub extra_data: Bytes, + #[serde(with = "serde_utils::u64::hex_str")] + pub base_fee_per_gas: u64, + pub block_hash: H256, + pub transactions: Vec, + pub withdrawals: Vec, + #[serde(with = "serde_utils::u64::hex_str")] + pub blob_gas_used: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub excess_blob_gas: u64, +} + +#[derive(Clone, Debug)] +pub struct EncodedTransaction(pub Bytes); + +impl<'de> Deserialize<'de> for EncodedTransaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(EncodedTransaction(serde_utils::bytes::deserialize( + deserializer, + )?)) + } +} + +impl EncodedTransaction { + /// Based on [EIP-2718] + /// Transactions can be encoded in the following formats: + /// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType) + /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction) + fn decode(&self) -> Result { + Transaction::decode_canonical(self.0.as_ref()) + } +} + +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 { + let body = BlockBody { + transactions: self + .transactions + .iter() + .map(|encoded_tx| encoded_tx.decode()) + .collect::, RLPDecodeError>>()?, + ommers: vec![], + withdrawals: Some(self.withdrawals), + }; + 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: compute_transactions_root(&body.transactions), + receipts_root: self.receipts_root, + logs_bloom: self.logs_bloom, + difficulty: 0.into(), + number: self.block_number, + gas_limit: self.gas_limit, + gas_used: self.gas_used, + timestamp: self.timestamp, + extra_data: self.extra_data, + prev_randao: self.prev_randao, + nonce: 0, + base_fee_per_gas: Some(self.base_fee_per_gas), + withdrawals_root: Some(compute_withdrawals_root( + &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), + }, + body, + }) + } +} + +pub fn build_payload_v3(args: &BuildPayloadArgs) -> ExecutionPayloadV3 { + todo!() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn deserialize_payload_into_block() { + // Payload extracted from running kurtosis, only some transactions are included to reduce it's size. + let json = r#"{"baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","blockHash":"0x4029a2342bb6d54db91457bc8e442be22b3481df8edea24cc721f9d0649f65be","blockNumber":"0x1","excessBlobGas":"0x0","extraData":"0xd883010e06846765746888676f312e32322e34856c696e7578","feeRecipient":"0x8943545177806ed17b9f23f0a21ee5948ecaa776","gasLimit":"0x17dd79d","gasUsed":"0x401640","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","prevRandao":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","receiptsRoot":"0x0185e8473b81c3a504c4919249a94a94965a2f61c06367ee6ffb88cb7a3ef02b","stateRoot":"0x0eb8fd0af53174e65bb660d0904e5016425a713d8f11c767c26148b526fc05f3","timestamp":"0x66846fb2","transactions":["0xf86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","0xf86d01843baa0c4082f61894687704db07e902e9a8b3754031d168d46e3d586e870aa87bee538000808360306ba0f6c479c3e9135a61d7cca17b7354ddc311cda2d8df265d0378f940bdefd62b54a077786891b0b6bcd438d8c24d00fa6628bc2f1caa554f9dec0a96daa4f40eb0d7","0xf86d02843baa0c4082f6189415e6a5a2e131dd5467fa1ff3acd104f45ee5940b870aa87bee538000808360306ca084469ec8ee41e9104cbe3ad7e7fe4225de86076dd2783749b099a4d155900305a07e64e8848c692f0fc251e78e6f3c388eb303349f3e247481366517c2a5ae2d89","0xf86d03843baa0c4082f6189480c4c7125967139acaa931ee984a9db4100e0f3b870aa87bee538000808360306ba021d2d8a35b8da03d7e0b494f71c9ed1c28a195b94c298407b81d65163a79fbdaa024a9bfcf5bbe75ba35130fa784ab88cd21c12c4e7daf3464de91bc1ed07d1bf6","0xf86d04843baa0c4082f61894d08a63244fcd28b0aec5075052cdce31ba04fead870aa87bee538000808360306ca07ee42fee5e426595056ad406aa65a3c7adb1d3d77279f56ebe2410bcf5118b2ca07b8a0e1d21578e9043a7331f60bafc71d15788d1a2d70d00b3c46e0856ff56d2","0xf86d05843baa0c4082f618940b06ef8be65fcda88f2dbae5813480f997ee8e35870aa87bee538000808360306ba0620669c8d6a781d3131bca874152bf833622af0edcd2247eab1b086875d5242ba01632353388f46946b5ce037130e92128e5837fe35d6c7de2b9e56a0f8cc1f5e6", "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6"],"withdrawals":[]}"#; + let payload: ExecutionPayloadV3 = serde_json::from_str(json).unwrap(); + assert!(payload.into_block(H256::zero()).is_ok()); + } +} diff --git a/crates/rpc/engine/payload.rs b/crates/rpc/engine/payload.rs index 19ea6bee8..18d90c6f7 100644 --- a/crates/rpc/engine/payload.rs +++ b/crates/rpc/engine/payload.rs @@ -1,15 +1,12 @@ use ethereum_rust_blockchain::error::ChainError; use ethereum_rust_blockchain::{add_block, latest_valid_hash}; -use ethereum_rust_core::types::ForkId; +use ethereum_rust_core::types::{ExecutionPayloadV3, ForkId}; use ethereum_rust_core::H256; use ethereum_rust_storage::Store; use serde_json::Value; use tracing::{info, warn}; -use crate::{ - types::payload::{ExecutionPayloadV3, PayloadStatus}, - RpcErr, RpcHandler, -}; +use crate::{types::payload::PayloadStatus, RpcErr, RpcHandler}; pub struct NewPayloadV3Request { pub payload: ExecutionPayloadV3, diff --git a/crates/rpc/types/payload.rs b/crates/rpc/types/payload.rs index 5860db1c2..49d00038b 100644 --- a/crates/rpc/types/payload.rs +++ b/crates/rpc/types/payload.rs @@ -1,112 +1,6 @@ -use bytes::Bytes; -use ethereum_rust_rlp::error::RLPDecodeError; use serde::{Deserialize, Serialize}; -use ethereum_rust_core::{ - serde_utils, - types::{ - compute_transactions_root, compute_withdrawals_root, Block, BlockBody, BlockHash, - BlockHeader, Transaction, Withdrawal, DEFAULT_OMMERS_HASH, - }, - Address, Bloom, H256, -}; - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ExecutionPayloadV3 { - pub parent_hash: H256, - pub fee_recipient: Address, - pub state_root: H256, - pub receipts_root: H256, - pub logs_bloom: Bloom, - pub prev_randao: H256, - #[serde(with = "serde_utils::u64::hex_str")] - pub block_number: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub gas_limit: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub gas_used: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub timestamp: u64, - #[serde(with = "serde_utils::bytes")] - pub extra_data: Bytes, - #[serde(with = "serde_utils::u64::hex_str")] - pub base_fee_per_gas: u64, - pub block_hash: H256, - pub transactions: Vec, - pub withdrawals: Vec, - #[serde(with = "serde_utils::u64::hex_str")] - pub blob_gas_used: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub excess_blob_gas: u64, -} - -#[derive(Clone, Debug)] -pub struct EncodedTransaction(pub Bytes); - -impl<'de> Deserialize<'de> for EncodedTransaction { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ok(EncodedTransaction(serde_utils::bytes::deserialize( - deserializer, - )?)) - } -} - -impl EncodedTransaction { - /// Based on [EIP-2718] - /// Transactions can be encoded in the following formats: - /// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType) - /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction) - fn decode(&self) -> Result { - Transaction::decode_canonical(self.0.as_ref()) - } -} - -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 { - let body = BlockBody { - transactions: self - .transactions - .iter() - .map(|encoded_tx| encoded_tx.decode()) - .collect::, RLPDecodeError>>()?, - ommers: vec![], - withdrawals: Some(self.withdrawals), - }; - 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: compute_transactions_root(&body.transactions), - receipts_root: self.receipts_root, - logs_bloom: self.logs_bloom, - difficulty: 0.into(), - number: self.block_number, - gas_limit: self.gas_limit, - gas_used: self.gas_used, - timestamp: self.timestamp, - extra_data: self.extra_data, - prev_randao: self.prev_randao, - nonce: 0, - base_fee_per_gas: Some(self.base_fee_per_gas), - withdrawals_root: Some(compute_withdrawals_root( - &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), - }, - body, - }) - } -} +use ethereum_rust_core::{types::BlockHash, H256}; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -172,16 +66,3 @@ impl PayloadStatus { } } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn deserialize_payload_into_block() { - // Payload extracted from running kurtosis, only some transactions are included to reduce it's size. - let json = r#"{"baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","blockHash":"0x4029a2342bb6d54db91457bc8e442be22b3481df8edea24cc721f9d0649f65be","blockNumber":"0x1","excessBlobGas":"0x0","extraData":"0xd883010e06846765746888676f312e32322e34856c696e7578","feeRecipient":"0x8943545177806ed17b9f23f0a21ee5948ecaa776","gasLimit":"0x17dd79d","gasUsed":"0x401640","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","prevRandao":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","receiptsRoot":"0x0185e8473b81c3a504c4919249a94a94965a2f61c06367ee6ffb88cb7a3ef02b","stateRoot":"0x0eb8fd0af53174e65bb660d0904e5016425a713d8f11c767c26148b526fc05f3","timestamp":"0x66846fb2","transactions":["0xf86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","0xf86d01843baa0c4082f61894687704db07e902e9a8b3754031d168d46e3d586e870aa87bee538000808360306ba0f6c479c3e9135a61d7cca17b7354ddc311cda2d8df265d0378f940bdefd62b54a077786891b0b6bcd438d8c24d00fa6628bc2f1caa554f9dec0a96daa4f40eb0d7","0xf86d02843baa0c4082f6189415e6a5a2e131dd5467fa1ff3acd104f45ee5940b870aa87bee538000808360306ca084469ec8ee41e9104cbe3ad7e7fe4225de86076dd2783749b099a4d155900305a07e64e8848c692f0fc251e78e6f3c388eb303349f3e247481366517c2a5ae2d89","0xf86d03843baa0c4082f6189480c4c7125967139acaa931ee984a9db4100e0f3b870aa87bee538000808360306ba021d2d8a35b8da03d7e0b494f71c9ed1c28a195b94c298407b81d65163a79fbdaa024a9bfcf5bbe75ba35130fa784ab88cd21c12c4e7daf3464de91bc1ed07d1bf6","0xf86d04843baa0c4082f61894d08a63244fcd28b0aec5075052cdce31ba04fead870aa87bee538000808360306ca07ee42fee5e426595056ad406aa65a3c7adb1d3d77279f56ebe2410bcf5118b2ca07b8a0e1d21578e9043a7331f60bafc71d15788d1a2d70d00b3c46e0856ff56d2","0xf86d05843baa0c4082f618940b06ef8be65fcda88f2dbae5813480f997ee8e35870aa87bee538000808360306ba0620669c8d6a781d3131bca874152bf833622af0edcd2247eab1b086875d5242ba01632353388f46946b5ce037130e92128e5837fe35d6c7de2b9e56a0f8cc1f5e6", "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6"],"withdrawals":[]}"#; - let payload: ExecutionPayloadV3 = serde_json::from_str(json).unwrap(); - assert!(payload.into_block(H256::zero()).is_ok()); - } -} From 772fac131a6dbf9c1788a54916e74ec90513583f Mon Sep 17 00:00:00 2001 From: fmoletta Date: Tue, 24 Sep 2024 14:34:26 -0300 Subject: [PATCH 06/23] Revert "Move execution payload to core" This reverts commit adf52042bf5e792879bcd131bcb6b7dd1ef07b9f. --- crates/core/types/payload.rs | 158 ----------------------------------- crates/rpc/engine/payload.rs | 7 +- crates/rpc/types/payload.rs | 121 ++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 161 deletions(-) delete mode 100644 crates/core/types/payload.rs diff --git a/crates/core/types/payload.rs b/crates/core/types/payload.rs deleted file mode 100644 index f943602b7..000000000 --- a/crates/core/types/payload.rs +++ /dev/null @@ -1,158 +0,0 @@ -use bytes::Bytes; -use ethereum_rust_rlp::encode::RLPEncode; -use ethereum_rust_rlp::error::RLPDecodeError; -use serde::Deserialize; -use sha3::{Digest, Keccak256}; - -use crate::{ - serde_utils, - types::{ - compute_transactions_root, compute_withdrawals_root, Block, BlockBody, BlockHash, - BlockHeader, Transaction, Withdrawal, DEFAULT_OMMERS_HASH, - }, - Address, Bloom, H256, U256, -}; - -pub struct BuildPayloadArgs { - pub parent: BlockHash, - pub timestamp: U256, - pub fee_recipient: Address, - pub random: H256, - pub withdrawals: Vec, - pub beacon_root: Option, - pub version: u8, -} - -impl BuildPayloadArgs { - // Id computes an 8-byte identifier by hashing the components of the payload arguments. - pub fn id(&self) -> u64 { - let mut hasher = Keccak256::new(); - let mut timestamp = [0; 32]; - self.timestamp.to_big_endian(&mut timestamp); - hasher.update(self.parent); - hasher.update(timestamp); - hasher.update(self.random); - hasher.update(self.fee_recipient); - hasher.update(self.withdrawals.encode_to_vec()); - if let Some(beacon_root) = self.beacon_root { - hasher.update(beacon_root); - } - let res = &mut hasher.finalize()[..8]; - res[0] = self.version; - u64::from_be_bytes(res.try_into().unwrap()) - } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ExecutionPayloadV3 { - pub parent_hash: H256, - pub fee_recipient: Address, - pub state_root: H256, - pub receipts_root: H256, - pub logs_bloom: Bloom, - pub prev_randao: H256, - #[serde(with = "serde_utils::u64::hex_str")] - pub block_number: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub gas_limit: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub gas_used: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub timestamp: u64, - #[serde(with = "serde_utils::bytes")] - pub extra_data: Bytes, - #[serde(with = "serde_utils::u64::hex_str")] - pub base_fee_per_gas: u64, - pub block_hash: H256, - pub transactions: Vec, - pub withdrawals: Vec, - #[serde(with = "serde_utils::u64::hex_str")] - pub blob_gas_used: u64, - #[serde(with = "serde_utils::u64::hex_str")] - pub excess_blob_gas: u64, -} - -#[derive(Clone, Debug)] -pub struct EncodedTransaction(pub Bytes); - -impl<'de> Deserialize<'de> for EncodedTransaction { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ok(EncodedTransaction(serde_utils::bytes::deserialize( - deserializer, - )?)) - } -} - -impl EncodedTransaction { - /// Based on [EIP-2718] - /// Transactions can be encoded in the following formats: - /// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType) - /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction) - fn decode(&self) -> Result { - Transaction::decode_canonical(self.0.as_ref()) - } -} - -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 { - let body = BlockBody { - transactions: self - .transactions - .iter() - .map(|encoded_tx| encoded_tx.decode()) - .collect::, RLPDecodeError>>()?, - ommers: vec![], - withdrawals: Some(self.withdrawals), - }; - 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: compute_transactions_root(&body.transactions), - receipts_root: self.receipts_root, - logs_bloom: self.logs_bloom, - difficulty: 0.into(), - number: self.block_number, - gas_limit: self.gas_limit, - gas_used: self.gas_used, - timestamp: self.timestamp, - extra_data: self.extra_data, - prev_randao: self.prev_randao, - nonce: 0, - base_fee_per_gas: Some(self.base_fee_per_gas), - withdrawals_root: Some(compute_withdrawals_root( - &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), - }, - body, - }) - } -} - -pub fn build_payload_v3(args: &BuildPayloadArgs) -> ExecutionPayloadV3 { - todo!() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn deserialize_payload_into_block() { - // Payload extracted from running kurtosis, only some transactions are included to reduce it's size. - let json = r#"{"baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","blockHash":"0x4029a2342bb6d54db91457bc8e442be22b3481df8edea24cc721f9d0649f65be","blockNumber":"0x1","excessBlobGas":"0x0","extraData":"0xd883010e06846765746888676f312e32322e34856c696e7578","feeRecipient":"0x8943545177806ed17b9f23f0a21ee5948ecaa776","gasLimit":"0x17dd79d","gasUsed":"0x401640","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","prevRandao":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","receiptsRoot":"0x0185e8473b81c3a504c4919249a94a94965a2f61c06367ee6ffb88cb7a3ef02b","stateRoot":"0x0eb8fd0af53174e65bb660d0904e5016425a713d8f11c767c26148b526fc05f3","timestamp":"0x66846fb2","transactions":["0xf86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","0xf86d01843baa0c4082f61894687704db07e902e9a8b3754031d168d46e3d586e870aa87bee538000808360306ba0f6c479c3e9135a61d7cca17b7354ddc311cda2d8df265d0378f940bdefd62b54a077786891b0b6bcd438d8c24d00fa6628bc2f1caa554f9dec0a96daa4f40eb0d7","0xf86d02843baa0c4082f6189415e6a5a2e131dd5467fa1ff3acd104f45ee5940b870aa87bee538000808360306ca084469ec8ee41e9104cbe3ad7e7fe4225de86076dd2783749b099a4d155900305a07e64e8848c692f0fc251e78e6f3c388eb303349f3e247481366517c2a5ae2d89","0xf86d03843baa0c4082f6189480c4c7125967139acaa931ee984a9db4100e0f3b870aa87bee538000808360306ba021d2d8a35b8da03d7e0b494f71c9ed1c28a195b94c298407b81d65163a79fbdaa024a9bfcf5bbe75ba35130fa784ab88cd21c12c4e7daf3464de91bc1ed07d1bf6","0xf86d04843baa0c4082f61894d08a63244fcd28b0aec5075052cdce31ba04fead870aa87bee538000808360306ca07ee42fee5e426595056ad406aa65a3c7adb1d3d77279f56ebe2410bcf5118b2ca07b8a0e1d21578e9043a7331f60bafc71d15788d1a2d70d00b3c46e0856ff56d2","0xf86d05843baa0c4082f618940b06ef8be65fcda88f2dbae5813480f997ee8e35870aa87bee538000808360306ba0620669c8d6a781d3131bca874152bf833622af0edcd2247eab1b086875d5242ba01632353388f46946b5ce037130e92128e5837fe35d6c7de2b9e56a0f8cc1f5e6", "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6"],"withdrawals":[]}"#; - let payload: ExecutionPayloadV3 = serde_json::from_str(json).unwrap(); - assert!(payload.into_block(H256::zero()).is_ok()); - } -} diff --git a/crates/rpc/engine/payload.rs b/crates/rpc/engine/payload.rs index 18d90c6f7..19ea6bee8 100644 --- a/crates/rpc/engine/payload.rs +++ b/crates/rpc/engine/payload.rs @@ -1,12 +1,15 @@ use ethereum_rust_blockchain::error::ChainError; use ethereum_rust_blockchain::{add_block, latest_valid_hash}; -use ethereum_rust_core::types::{ExecutionPayloadV3, ForkId}; +use ethereum_rust_core::types::ForkId; use ethereum_rust_core::H256; use ethereum_rust_storage::Store; use serde_json::Value; use tracing::{info, warn}; -use crate::{types::payload::PayloadStatus, RpcErr, RpcHandler}; +use crate::{ + types::payload::{ExecutionPayloadV3, PayloadStatus}, + RpcErr, RpcHandler, +}; pub struct NewPayloadV3Request { pub payload: ExecutionPayloadV3, diff --git a/crates/rpc/types/payload.rs b/crates/rpc/types/payload.rs index 49d00038b..5860db1c2 100644 --- a/crates/rpc/types/payload.rs +++ b/crates/rpc/types/payload.rs @@ -1,6 +1,112 @@ +use bytes::Bytes; +use ethereum_rust_rlp::error::RLPDecodeError; use serde::{Deserialize, Serialize}; -use ethereum_rust_core::{types::BlockHash, H256}; +use ethereum_rust_core::{ + serde_utils, + types::{ + compute_transactions_root, compute_withdrawals_root, Block, BlockBody, BlockHash, + BlockHeader, Transaction, Withdrawal, DEFAULT_OMMERS_HASH, + }, + Address, Bloom, H256, +}; + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExecutionPayloadV3 { + pub parent_hash: H256, + pub fee_recipient: Address, + pub state_root: H256, + pub receipts_root: H256, + pub logs_bloom: Bloom, + pub prev_randao: H256, + #[serde(with = "serde_utils::u64::hex_str")] + pub block_number: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub gas_limit: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub gas_used: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub timestamp: u64, + #[serde(with = "serde_utils::bytes")] + pub extra_data: Bytes, + #[serde(with = "serde_utils::u64::hex_str")] + pub base_fee_per_gas: u64, + pub block_hash: H256, + pub transactions: Vec, + pub withdrawals: Vec, + #[serde(with = "serde_utils::u64::hex_str")] + pub blob_gas_used: u64, + #[serde(with = "serde_utils::u64::hex_str")] + pub excess_blob_gas: u64, +} + +#[derive(Clone, Debug)] +pub struct EncodedTransaction(pub Bytes); + +impl<'de> Deserialize<'de> for EncodedTransaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(EncodedTransaction(serde_utils::bytes::deserialize( + deserializer, + )?)) + } +} + +impl EncodedTransaction { + /// Based on [EIP-2718] + /// Transactions can be encoded in the following formats: + /// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType) + /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction) + fn decode(&self) -> Result { + Transaction::decode_canonical(self.0.as_ref()) + } +} + +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 { + let body = BlockBody { + transactions: self + .transactions + .iter() + .map(|encoded_tx| encoded_tx.decode()) + .collect::, RLPDecodeError>>()?, + ommers: vec![], + withdrawals: Some(self.withdrawals), + }; + 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: compute_transactions_root(&body.transactions), + receipts_root: self.receipts_root, + logs_bloom: self.logs_bloom, + difficulty: 0.into(), + number: self.block_number, + gas_limit: self.gas_limit, + gas_used: self.gas_used, + timestamp: self.timestamp, + extra_data: self.extra_data, + prev_randao: self.prev_randao, + nonce: 0, + base_fee_per_gas: Some(self.base_fee_per_gas), + withdrawals_root: Some(compute_withdrawals_root( + &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), + }, + body, + }) + } +} #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -66,3 +172,16 @@ impl PayloadStatus { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn deserialize_payload_into_block() { + // Payload extracted from running kurtosis, only some transactions are included to reduce it's size. + let json = r#"{"baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","blockHash":"0x4029a2342bb6d54db91457bc8e442be22b3481df8edea24cc721f9d0649f65be","blockNumber":"0x1","excessBlobGas":"0x0","extraData":"0xd883010e06846765746888676f312e32322e34856c696e7578","feeRecipient":"0x8943545177806ed17b9f23f0a21ee5948ecaa776","gasLimit":"0x17dd79d","gasUsed":"0x401640","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","prevRandao":"0x2971eefd1f71f3548728cad87c16cc91b979ef035054828c59a02e49ae300a84","receiptsRoot":"0x0185e8473b81c3a504c4919249a94a94965a2f61c06367ee6ffb88cb7a3ef02b","stateRoot":"0x0eb8fd0af53174e65bb660d0904e5016425a713d8f11c767c26148b526fc05f3","timestamp":"0x66846fb2","transactions":["0xf86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4","0xf86d01843baa0c4082f61894687704db07e902e9a8b3754031d168d46e3d586e870aa87bee538000808360306ba0f6c479c3e9135a61d7cca17b7354ddc311cda2d8df265d0378f940bdefd62b54a077786891b0b6bcd438d8c24d00fa6628bc2f1caa554f9dec0a96daa4f40eb0d7","0xf86d02843baa0c4082f6189415e6a5a2e131dd5467fa1ff3acd104f45ee5940b870aa87bee538000808360306ca084469ec8ee41e9104cbe3ad7e7fe4225de86076dd2783749b099a4d155900305a07e64e8848c692f0fc251e78e6f3c388eb303349f3e247481366517c2a5ae2d89","0xf86d03843baa0c4082f6189480c4c7125967139acaa931ee984a9db4100e0f3b870aa87bee538000808360306ba021d2d8a35b8da03d7e0b494f71c9ed1c28a195b94c298407b81d65163a79fbdaa024a9bfcf5bbe75ba35130fa784ab88cd21c12c4e7daf3464de91bc1ed07d1bf6","0xf86d04843baa0c4082f61894d08a63244fcd28b0aec5075052cdce31ba04fead870aa87bee538000808360306ca07ee42fee5e426595056ad406aa65a3c7adb1d3d77279f56ebe2410bcf5118b2ca07b8a0e1d21578e9043a7331f60bafc71d15788d1a2d70d00b3c46e0856ff56d2","0xf86d05843baa0c4082f618940b06ef8be65fcda88f2dbae5813480f997ee8e35870aa87bee538000808360306ba0620669c8d6a781d3131bca874152bf833622af0edcd2247eab1b086875d5242ba01632353388f46946b5ce037130e92128e5837fe35d6c7de2b9e56a0f8cc1f5e6", "0x02f8ef83301824048413f157f8842daf517a830186a094000000000000000000000000000000000000000080b8807a0a600060a0553db8600060c855c77fb29ecd7661d8aefe101a0db652a728af0fded622ff55d019b545d03a7532932a60ad52604260cd5360bf60ce53609460cf53603e60d05360f560d153bc596000609e55600060c6556000601f556000609155535660556057536055605853606e60595360e7605a5360d0605b5360eb60c080a03acb03b1fc20507bc66210f7e18ff5af65038fb22c626ae488ad9513d9b6debca05d38459e9d2a221eb345b0c2761b719b313d062ff1ea3d10cf5b8762c44385a6"],"withdrawals":[]}"#; + let payload: ExecutionPayloadV3 = serde_json::from_str(json).unwrap(); + assert!(payload.into_block(H256::zero()).is_ok()); + } +} From f8409b0b9922e008420882c7517a8f3ddf54bf9d Mon Sep 17 00:00:00 2001 From: fmoletta Date: Tue, 24 Sep 2024 14:35:48 -0300 Subject: [PATCH 07/23] Push uncommited file --- crates/core/types/payload.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 crates/core/types/payload.rs diff --git a/crates/core/types/payload.rs b/crates/core/types/payload.rs new file mode 100644 index 000000000..dc0f30d11 --- /dev/null +++ b/crates/core/types/payload.rs @@ -0,0 +1,36 @@ +use ethereum_rust_rlp::encode::RLPEncode; +use ethereum_types::{Address, U256}; +use keccak_hash::H256; +use sha3::{Digest, Keccak256}; + +use super::{BlockHash, Withdrawal}; + +pub struct BuildPayloadArgs { + pub parent: BlockHash, + pub timestamp: U256, + pub fee_recipient: Address, + pub random: H256, + pub withdrawals: Vec, + pub beacon_root: Option, + pub version: u8, +} + +impl BuildPayloadArgs { + // Id computes an 8-byte identifier by hashing the components of the payload arguments. + pub fn id(&self) -> u64 { + let mut hasher = Keccak256::new(); + let mut timestamp = [0; 32]; + self.timestamp.to_big_endian(&mut timestamp); + hasher.update(self.parent); + hasher.update(timestamp); + hasher.update(self.random); + hasher.update(self.fee_recipient); + hasher.update(self.withdrawals.encode_to_vec()); + if let Some(beacon_root) = self.beacon_root { + hasher.update(beacon_root); + } + let res = &mut hasher.finalize()[..8]; + res[0] = self.version; + u64::from_be_bytes(res.try_into().unwrap()) + } +} From 7446289d617c3b5c9377640e12e29660f794d040 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Tue, 24 Sep 2024 16:32:13 -0300 Subject: [PATCH 08/23] Add block building --- crates/blockchain/Cargo.toml | 2 + crates/blockchain/blockchain.rs | 2 + crates/blockchain/constants.rs | 4 + crates/blockchain/payload.rs | 124 ++++++++++++++++++++++++++++ crates/core/types/block.rs | 2 +- crates/core/types/mod.rs | 2 - crates/core/types/payload.rs | 36 -------- crates/rpc/engine/fork_choice.rs | 11 ++- crates/storage/engines/api.rs | 4 + crates/storage/engines/in_memory.rs | 13 ++- crates/storage/engines/libmdbx.rs | 20 ++++- crates/storage/rlp.rs | 3 +- crates/storage/storage.rs | 8 ++ 13 files changed, 184 insertions(+), 47 deletions(-) create mode 100644 crates/blockchain/payload.rs delete mode 100644 crates/core/types/payload.rs diff --git a/crates/blockchain/Cargo.toml b/crates/blockchain/Cargo.toml index 8a729bb8e..f3538534a 100644 --- a/crates/blockchain/Cargo.toml +++ b/crates/blockchain/Cargo.toml @@ -8,8 +8,10 @@ edition = "2021" [dependencies] thiserror.workspace = true ethereum_rust-core.workspace = true +ethereum_rust-rlp.workspace = true ethereum_rust-storage.workspace = true ethereum_rust-evm.workspace = true +sha3.workspace = true [lib] diff --git a/crates/blockchain/blockchain.rs b/crates/blockchain/blockchain.rs index 7d30f079f..4e59730b1 100644 --- a/crates/blockchain/blockchain.rs +++ b/crates/blockchain/blockchain.rs @@ -1,6 +1,8 @@ pub mod constants; pub mod error; pub mod mempool; +pub mod payload; + use constants::{GAS_PER_BLOB, MAX_BLOB_GAS_PER_BLOCK, MAX_BLOB_NUMBER_PER_BLOCK}; use error::{ChainError, InvalidBlockError}; use ethereum_rust_core::types::{ diff --git a/crates/blockchain/constants.rs b/crates/blockchain/constants.rs index 1da4d0f82..651c0b038 100644 --- a/crates/blockchain/constants.rs +++ b/crates/blockchain/constants.rs @@ -55,3 +55,7 @@ pub const MAX_BLOB_GAS_PER_BLOCK: u64 = MAX_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB // Minimum base fee per blob pub const MIN_BASE_FEE_PER_BLOB_GAS: u64 = 1; + +pub const GAS_LIMIT_BOUND_DIVISOR: u64 = 1024; + +pub const MIN_GAS_LIMIT: u64 = 5000; diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs new file mode 100644 index 000000000..52248f608 --- /dev/null +++ b/crates/blockchain/payload.rs @@ -0,0 +1,124 @@ +use std::cmp::min; + +use ethereum_rust_core::{ + types::{ + calculate_base_fee_per_gas, compute_receipts_root, compute_transactions_root, + compute_withdrawals_root, Block, BlockBody, BlockHash, BlockHeader, Withdrawal, + DEFAULT_OMMERS_HASH, + }, + Address, Bloom, Bytes, H256, U256, +}; +use ethereum_rust_rlp::encode::RLPEncode; +use ethereum_rust_storage::{error::StoreError, Store}; +use sha3::{Digest, Keccak256}; + +use crate::constants::{GAS_LIMIT_BOUND_DIVISOR, MIN_GAS_LIMIT, TARGET_BLOB_GAS_PER_BLOCK}; + +pub struct BuildPayloadArgs { + pub parent: BlockHash, + pub timestamp: U256, + pub fee_recipient: Address, + pub random: H256, + pub withdrawals: Vec, + pub beacon_root: Option, + pub version: u8, +} + +impl BuildPayloadArgs { + // Id computes an 8-byte identifier by hashing the components of the payload arguments. + pub fn id(&self) -> u64 { + let mut hasher = Keccak256::new(); + let mut timestamp = [0; 32]; + self.timestamp.to_big_endian(&mut timestamp); + hasher.update(self.parent); + hasher.update(timestamp); + hasher.update(self.random); + hasher.update(self.fee_recipient); + hasher.update(self.withdrawals.encode_to_vec()); + if let Some(beacon_root) = self.beacon_root { + hasher.update(beacon_root); + } + let res = &mut hasher.finalize()[..8]; + res[0] = self.version; + u64::from_be_bytes(res.try_into().unwrap()) + } +} + +// Basic payload block building, can and should be improved +pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result { + let parent_block = storage.get_block_by_hash(args.parent).unwrap().unwrap(); + let timestamp = args.timestamp.as_u64(); + let chain_config = storage.get_chain_config().unwrap(); + let gas_limit = calc_gas_limit(parent_block.header.gas_limit, 30_000_000); + Ok(Block { + header: BlockHeader { + parent_hash: args.parent, + ommers_hash: *DEFAULT_OMMERS_HASH, + coinbase: args.fee_recipient, + state_root: parent_block.header.state_root, + transactions_root: compute_transactions_root(&[]), + receipts_root: compute_receipts_root(&[]), + logs_bloom: Bloom::default(), + difficulty: U256::zero(), + number: parent_block.header.number.saturating_add(1), + gas_limit, + gas_used: 0, + timestamp, + extra_data: Bytes::new(), + prev_randao: args.random, + nonce: 0, + base_fee_per_gas: calculate_base_fee_per_gas( + gas_limit, + parent_block.header.gas_limit, + parent_block.header.gas_used, + parent_block.header.base_fee_per_gas.unwrap_or_default(), + ), + withdrawals_root: chain_config + .is_shanghai_activated(timestamp) + .then_some(compute_withdrawals_root(&args.withdrawals)), + blob_gas_used: Some(0), + excess_blob_gas: chain_config.is_cancun_activated(timestamp).then_some( + calc_excess_blob_gas( + parent_block.header.excess_blob_gas.unwrap_or_default(), + parent_block.header.blob_gas_used.unwrap_or_default(), + ), + ), + parent_beacon_block_root: args.beacon_root, + }, + // Empty body as we just created this payload + body: BlockBody { + transactions: Vec::new(), + ommers: Vec::new(), + withdrawals: Some(args.withdrawals.clone()), + }, + }) +} + +fn calc_gas_limit(parent_gas_limit: u64, desired_limit: u64) -> u64 { + let delta = parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1; + let mut limit = parent_gas_limit; + let desired_limit = min(desired_limit, MIN_GAS_LIMIT); + if limit < desired_limit { + limit = parent_gas_limit + delta; + if limit > desired_limit { + limit = desired_limit + } + return limit; + } + if limit > desired_limit { + limit = parent_gas_limit - delta; + if limit < desired_limit { + limit = desired_limit + } + } + limit +} + +fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 { + let excess_blob_gas = parent_excess_blob_gas + parent_blob_gas_used; + if excess_blob_gas < TARGET_BLOB_GAS_PER_BLOCK { + 0 + } else { + excess_blob_gas - TARGET_BLOB_GAS_PER_BLOCK + } +} diff --git a/crates/core/types/block.rs b/crates/core/types/block.rs index e8d5d3718..9ed2cd69b 100644 --- a/crates/core/types/block.rs +++ b/crates/core/types/block.rs @@ -338,7 +338,7 @@ fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u64 { // Calculates the base fee for the current block based on its gas_limit and parent's gas and fee // Returns None if the block gas limit is not valid in relation to its parent's gas limit -fn calculate_base_fee_per_gas( +pub fn calculate_base_fee_per_gas( block_gas_limit: u64, parent_gas_limit: u64, parent_gas_used: u64, diff --git a/crates/core/types/mod.rs b/crates/core/types/mod.rs index f25446836..02ceae38d 100644 --- a/crates/core/types/mod.rs +++ b/crates/core/types/mod.rs @@ -2,7 +2,6 @@ mod account; mod block; mod constants; mod genesis; -mod payload; mod receipt; mod transaction; @@ -10,6 +9,5 @@ pub use account::*; pub use block::*; pub use constants::*; pub use genesis::*; -pub use payload::*; pub use receipt::*; pub use transaction::*; diff --git a/crates/core/types/payload.rs b/crates/core/types/payload.rs deleted file mode 100644 index dc0f30d11..000000000 --- a/crates/core/types/payload.rs +++ /dev/null @@ -1,36 +0,0 @@ -use ethereum_rust_rlp::encode::RLPEncode; -use ethereum_types::{Address, U256}; -use keccak_hash::H256; -use sha3::{Digest, Keccak256}; - -use super::{BlockHash, Withdrawal}; - -pub struct BuildPayloadArgs { - pub parent: BlockHash, - pub timestamp: U256, - pub fee_recipient: Address, - pub random: H256, - pub withdrawals: Vec, - pub beacon_root: Option, - pub version: u8, -} - -impl BuildPayloadArgs { - // Id computes an 8-byte identifier by hashing the components of the payload arguments. - pub fn id(&self) -> u64 { - let mut hasher = Keccak256::new(); - let mut timestamp = [0; 32]; - self.timestamp.to_big_endian(&mut timestamp); - hasher.update(self.parent); - hasher.update(timestamp); - hasher.update(self.random); - hasher.update(self.fee_recipient); - hasher.update(self.withdrawals.encode_to_vec()); - if let Some(beacon_root) = self.beacon_root { - hasher.update(beacon_root); - } - let res = &mut hasher.finalize()[..8]; - res[0] = self.version; - u64::from_be_bytes(res.try_into().unwrap()) - } -} diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index 05345093f..aaa40a584 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -1,4 +1,5 @@ -use ethereum_rust_core::{types::BuildPayloadArgs, U256}; +use ethereum_rust_blockchain::payload::{build_payload, BuildPayloadArgs}; +use ethereum_rust_core::U256; use ethereum_rust_storage::Store; use serde_json::Value; @@ -125,9 +126,9 @@ impl RpcHandler for ForkChoiceUpdatedV3 { self.fork_choice_state.head_block_hash, )); - // TODO: Build block + // Build block from received payload if let Some(attributes) = &self.payload_attributes { - let payload = BuildPayloadArgs { + let args = BuildPayloadArgs { parent: self.fork_choice_state.head_block_hash, timestamp: attributes.timestamp, fee_recipient: attributes.suggested_fee_recipient, @@ -136,8 +137,10 @@ impl RpcHandler for ForkChoiceUpdatedV3 { beacon_root: Some(attributes.parent_beacon_block_root), version: 3, }; - let payload_id = payload.id(); + let payload_id = args.id(); response.set_id(payload_id); + let payload = build_payload(&args, &storage)?; + storage.add_local_block(payload_id, payload)?; } Ok(serde_json::to_value(response).map_err(|_| RpcErr::Internal)?) diff --git a/crates/storage/engines/api.rs b/crates/storage/engines/api.rs index 0c3304635..b8023edf4 100644 --- a/crates/storage/engines/api.rs +++ b/crates/storage/engines/api.rs @@ -216,4 +216,8 @@ pub trait StoreEngine: Debug + Send + Sync + RefUnwindSafe { // Get the canonical block hash for a given block number. fn get_canonical_block(&self, number: BlockNumber) -> Result, StoreError>; + + fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError>; + + fn get_local_block(&self, payload_id: u64) -> Result, StoreError>; } diff --git a/crates/storage/engines/in_memory.rs b/crates/storage/engines/in_memory.rs index 3b3e6bc5c..268809d6e 100644 --- a/crates/storage/engines/in_memory.rs +++ b/crates/storage/engines/in_memory.rs @@ -1,7 +1,7 @@ use crate::error::StoreError; use bytes::Bytes; use ethereum_rust_core::types::{ - BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, + Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, }; use ethereum_rust_trie::{InMemoryTrieDB, Trie}; use ethereum_types::{Address, H256, U256}; @@ -36,6 +36,8 @@ struct StoreInner { storage_trie_nodes: HashMap, // TODO (#307): Remove TotalDifficulty. block_total_difficulties: HashMap, + // Stores local blocks by payload id + local_blocks: HashMap, } #[derive(Default, Debug)] @@ -340,6 +342,15 @@ impl StoreEngine for Store { fn get_canonical_block(&self, number: BlockNumber) -> Result, StoreError> { Ok(self.inner().canonical_hashes.get(&number).cloned()) } + + fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.inner().local_blocks.insert(payload_id, block); + Ok(()) + } + + fn get_local_block(&self, payload_id: u64) -> Result, StoreError> { + Ok(self.inner().local_blocks.get(&payload_id).cloned()) + } } impl Debug for Store { diff --git a/crates/storage/engines/libmdbx.rs b/crates/storage/engines/libmdbx.rs index 4e39c10ad..3b5b7bb59 100644 --- a/crates/storage/engines/libmdbx.rs +++ b/crates/storage/engines/libmdbx.rs @@ -1,13 +1,13 @@ use super::api::StoreEngine; use crate::error::StoreError; use crate::rlp::{ - AccountCodeHashRLP, AccountCodeRLP, BlockBodyRLP, BlockHashRLP, BlockHeaderRLP, + AccountCodeHashRLP, AccountCodeRLP, BlockBodyRLP, BlockHashRLP, BlockHeaderRLP, BlockRLP, BlockTotalDifficultyRLP, ReceiptRLP, Rlp, TransactionHashRLP, TransactionRLP, TupleRLP, }; use anyhow::Result; use bytes::Bytes; use ethereum_rust_core::types::{ - BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, + Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, }; use ethereum_rust_rlp::decode::RLPDecode; use ethereum_rust_rlp::encode::RLPEncode; @@ -371,6 +371,14 @@ impl StoreEngine for Store { fn get_canonical_block(&self, number: BlockNumber) -> Result, StoreError> { Ok(self.read::(number)?.map(|b| b.to())) } + + fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.write::(payload_id, block.into()) + } + + fn get_local_block(&self, payload_id: u64) -> Result, StoreError> { + Ok(self.read::(payload_id)?.map(|b| b.to())) + } } impl Debug for Store { @@ -444,6 +452,13 @@ table!( ( StateTrieNodes ) Vec => Vec ); +// Local Blocks + +table!( + /// state trie nodes + ( LocalBlocks ) u64 => BlockRLP +); + // Storage values are stored as bytes instead of using their rlp encoding // As they are stored in a dupsort table, they need to have a fixed size, and encoding them doesn't preserve their size pub struct AccountStorageKeyBytes(pub [u8; 32]); @@ -541,6 +556,7 @@ pub fn init_db(path: Option>) -> Database { table_info!(StateTrieNodes), table_info!(StorageTriesNodes), table_info!(CanonicalBlockHashes), + table_info!(LocalBlocks), ] .into_iter() .collect(); diff --git a/crates/storage/rlp.rs b/crates/storage/rlp.rs index 5bf248a47..86325a2f0 100644 --- a/crates/storage/rlp.rs +++ b/crates/storage/rlp.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use bytes::Bytes; use ethereum_rust_core::{ - types::{BlockBody, BlockHash, BlockHeader, Receipt, Transaction}, + types::{Block, BlockBody, BlockHash, BlockHeader, Receipt, Transaction}, H256, }; use ethereum_rust_rlp::{decode::RLPDecode, encode::RLPEncode}; @@ -18,6 +18,7 @@ pub type AccountCodeRLP = Rlp; pub type BlockHashRLP = Rlp; pub type BlockHeaderRLP = Rlp; pub type BlockBodyRLP = Rlp; +pub type BlockRLP = Rlp; // TODO (#307): Remove TotalDifficulty. pub type BlockTotalDifficultyRLP = Rlp; diff --git a/crates/storage/storage.rs b/crates/storage/storage.rs index 278699984..d7bb7407d 100644 --- a/crates/storage/storage.rs +++ b/crates/storage/storage.rs @@ -571,6 +571,14 @@ impl Store { ) -> Result, StoreError> { self.engine.get_canonical_block(block_number) } + + pub fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.engine.add_local_block(payload_id, block) + } + + pub fn get_local_block(&self, payload_id: u64) -> Result, StoreError> { + self.engine.get_local_block(payload_id) + } } fn hash_address(address: &Address) -> Vec { From 2b2f4dd5735b0919797622b27cb49c411718bee4 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Tue, 24 Sep 2024 16:39:30 -0300 Subject: [PATCH 09/23] Clippy + fmt --- crates/rpc/engine/fork_choice.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index aaa40a584..a6755b6bb 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -38,7 +38,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { .map_err(|_| RpcErr::Internal) }; if self.fork_choice_state.head_block_hash.is_zero() { - return Ok(error_response("forkchoice requested update to zero hash")?); + return error_response("forkchoice requested update to zero hash"); } // Check if we have the block stored let Some(head_block) = storage.get_block_by_hash(self.fork_choice_state.head_block_hash)? @@ -57,17 +57,15 @@ impl RpcHandler for ForkChoiceUpdatedV3 { || total_difficulty.is_none() || head_block.header.number > 0 && parent_total_difficulty.is_none() { - return Ok(error_response( + return error_response( "total difficulties unavailable for terminal total difficulty check", - )?); + ); } if total_difficulty.unwrap() < terminal_total_difficulty.unwrap().into() { - return Ok(error_response("refusing beacon update to pre-merge")?); + return error_response("refusing beacon update to pre-merge"); } if head_block.header.number > 0 && parent_total_difficulty.unwrap() >= U256::zero() { - return Ok(error_response( - "parent block is already post terminal total difficulty", - )?); + return error_response("parent block is already post terminal total difficulty"); } } let canonical_block = storage.get_canonical_block(head_block.header.number)?; @@ -82,7 +80,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { } else if current_block_hash.is_some_and(|h| h != self.fork_choice_state.head_block_hash) { // If the head block is already in our canonical chain, the beacon client is // probably resyncing. Ignore the update. - return Ok(serde_json::to_value(PayloadStatus::valid()).map_err(|_| RpcErr::Internal)?); + return serde_json::to_value(PayloadStatus::valid()).map_err(|_| RpcErr::Internal); } // Process finalized block @@ -91,14 +89,14 @@ impl RpcHandler for ForkChoiceUpdatedV3 { let Some(finalized_block) = storage.get_block_by_hash(self.fork_choice_state.finalized_block_hash)? else { - return Ok(error_response("final block not available in database")?); + return error_response("final block not available in database"); }; if !storage .get_canonical_block(finalized_block.header.number)? .is_some_and(|h| h == self.fork_choice_state.finalized_block_hash) { - return Ok(error_response("final block not in canonical chain")?); + return error_response("final block not in canonical chain"); } // Set the finalized block storage.update_finalized_block_number(finalized_block.header.number)?; @@ -110,14 +108,14 @@ impl RpcHandler for ForkChoiceUpdatedV3 { let Some(safe_block) = storage.get_block_by_hash(self.fork_choice_state.safe_block_hash)? else { - return Ok(error_response("safe block not available in database")?); + return error_response("safe block not available in database"); }; if !storage .get_canonical_block(safe_block.header.number)? .is_some_and(|h| h == self.fork_choice_state.safe_block_hash) { - return Ok(error_response("safe block not in canonical chain")?); + return error_response("safe block not in canonical chain"); } // Set the safe block storage.update_safe_block_number(safe_block.header.number)?; @@ -143,6 +141,6 @@ impl RpcHandler for ForkChoiceUpdatedV3 { storage.add_local_block(payload_id, payload)?; } - Ok(serde_json::to_value(response).map_err(|_| RpcErr::Internal)?) + serde_json::to_value(response).map_err(|_| RpcErr::Internal) } } From 5e4767313e7e4860484ea8b02992d45375a916b7 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Wed, 25 Sep 2024 15:08:33 -0300 Subject: [PATCH 10/23] Modularize fork choice update fn --- crates/rpc/engine/fork_choice.rs | 147 +++++++++++++++++++------------ 1 file changed, 91 insertions(+), 56 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index a6755b6bb..7db3572e6 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -1,6 +1,6 @@ use ethereum_rust_blockchain::payload::{build_payload, BuildPayloadArgs}; -use ethereum_rust_core::U256; -use ethereum_rust_storage::Store; +use ethereum_rust_core::{types::Block, H256, U256}; +use ethereum_rust_storage::{error::StoreError, Store}; use serde_json::Value; use crate::{ @@ -37,6 +37,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { ))) .map_err(|_| RpcErr::Internal) }; + if self.fork_choice_state.head_block_hash.is_zero() { return error_response("forkchoice requested update to zero hash"); } @@ -47,26 +48,12 @@ impl RpcHandler for ForkChoiceUpdatedV3 { return Err(RpcErr::Internal); }; // Check that we are not being pushed pre-merge - if !head_block.header.difficulty.is_zero() || head_block.header.number == 0 { - let total_difficulty = - storage.get_block_total_difficulty(self.fork_choice_state.head_block_hash)?; - let parent_total_difficulty = - storage.get_block_total_difficulty(head_block.header.parent_hash)?; - let terminal_total_difficulty = storage.get_chain_config()?.terminal_total_difficulty; - if terminal_total_difficulty.is_none() - || total_difficulty.is_none() - || head_block.header.number > 0 && parent_total_difficulty.is_none() - { - return error_response( - "total difficulties unavailable for terminal total difficulty check", - ); - } - if total_difficulty.unwrap() < terminal_total_difficulty.unwrap().into() { - return error_response("refusing beacon update to pre-merge"); - } - if head_block.header.number > 0 && parent_total_difficulty.unwrap() >= U256::zero() { - return error_response("parent block is already post terminal total difficulty"); - } + if let Some(error) = total_difficulty_check( + &self.fork_choice_state.head_block_hash, + &head_block, + &storage, + )? { + return error_response(error); } let canonical_block = storage.get_canonical_block(head_block.header.number)?; let current_block_hash = { @@ -83,42 +70,16 @@ impl RpcHandler for ForkChoiceUpdatedV3 { return serde_json::to_value(PayloadStatus::valid()).map_err(|_| RpcErr::Internal); } - // Process finalized block - if !self.fork_choice_state.finalized_block_hash.is_zero() { - // If the finalized block is not in our canonical tree, something is wrong - let Some(finalized_block) = - storage.get_block_by_hash(self.fork_choice_state.finalized_block_hash)? - else { - return error_response("final block not available in database"); - }; - - if !storage - .get_canonical_block(finalized_block.header.number)? - .is_some_and(|h| h == self.fork_choice_state.finalized_block_hash) - { - return error_response("final block not in canonical chain"); - } - // Set the finalized block - storage.update_finalized_block_number(finalized_block.header.number)?; + // Set finalized block + if let Some(error) = + set_finalized_block(&self.fork_choice_state.finalized_block_hash, &storage)? + { + return error_response(error); } - // Process safe block - if !self.fork_choice_state.safe_block_hash.is_zero() { - // If the safe block is not in our canonical tree, something is wrong - let Some(safe_block) = - storage.get_block_by_hash(self.fork_choice_state.safe_block_hash)? - else { - return error_response("safe block not available in database"); - }; - - if !storage - .get_canonical_block(safe_block.header.number)? - .is_some_and(|h| h == self.fork_choice_state.safe_block_hash) - { - return error_response("safe block not in canonical chain"); - } - // Set the safe block - storage.update_safe_block_number(safe_block.header.number)?; + // Set safe block + if let Some(error) = set_safe_block(&self.fork_choice_state.safe_block_hash, &storage)? { + return error_response(error); } let mut response = ForkChoiceResponse::from(PayloadStatus::valid_with_hash( self.fork_choice_state.head_block_hash, @@ -144,3 +105,77 @@ impl RpcHandler for ForkChoiceUpdatedV3 { serde_json::to_value(response).map_err(|_| RpcErr::Internal) } } + +fn total_difficulty_check<'a>( + head_block_hash: &'a H256, + head_block: &'a Block, + storage: &'a Store, +) -> Result, StoreError> { + if !head_block.header.difficulty.is_zero() || head_block.header.number == 0 { + let total_difficulty = storage.get_block_total_difficulty(*head_block_hash)?; + let parent_total_difficulty = + storage.get_block_total_difficulty(head_block.header.parent_hash)?; + let terminal_total_difficulty = storage.get_chain_config()?.terminal_total_difficulty; + if terminal_total_difficulty.is_none() + || total_difficulty.is_none() + || head_block.header.number > 0 && parent_total_difficulty.is_none() + { + return Ok(Some( + "total difficulties unavailable for terminal total difficulty check", + )); + } + if total_difficulty.unwrap() < terminal_total_difficulty.unwrap().into() { + return Ok(Some("refusing beacon update to pre-merge")); + } + if head_block.header.number > 0 && parent_total_difficulty.unwrap() >= U256::zero() { + return Ok(Some( + "parent block is already post terminal total difficulty", + )); + } + } + Ok(None) +} + +fn set_finalized_block<'a>( + finalized_block_hash: &H256, + storage: &'a Store, +) -> Result, StoreError> { + if finalized_block_hash.is_zero() { + // If the finalized block is not in our canonical tree, something is wrong + let Some(finalized_block) = storage.get_block_by_hash(*finalized_block_hash)? else { + return Ok(Some("final block not available in database")); + }; + + if !storage + .get_canonical_block(finalized_block.header.number)? + .is_some_and(|ref h| h == finalized_block_hash) + { + return Ok(Some("final block not in canonical chain")); + } + // Set the finalized block + storage.update_finalized_block_number(finalized_block.header.number)?; + } + Ok(None) +} + +fn set_safe_block<'a>( + safe_block_hash: &H256, + storage: &'a Store, +) -> Result, StoreError> { + if safe_block_hash.is_zero() { + // If the safe block is not in our canonical tree, something is wrong + let Some(safe_block) = storage.get_block_by_hash(*safe_block_hash)? else { + return Ok(Some("safe block not available in database")); + }; + + if !storage + .get_canonical_block(safe_block.header.number)? + .is_some_and(|ref h| h == safe_block_hash) + { + return Ok(Some("safe block not in canonical chain")); + } + // Set the safe block + storage.update_safe_block_number(safe_block.header.number)?; + } + Ok(None) +} From 31c380f8bf11cea46d03c4f0ba281c2caf33a74a Mon Sep 17 00:00:00 2001 From: fmoletta Date: Wed, 25 Sep 2024 15:15:03 -0300 Subject: [PATCH 11/23] Fix timestamp --- crates/blockchain/payload.rs | 13 +++++-------- crates/rpc/types/fork_choice.rs | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 52248f608..a832e60dd 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -16,7 +16,7 @@ use crate::constants::{GAS_LIMIT_BOUND_DIVISOR, MIN_GAS_LIMIT, TARGET_BLOB_GAS_P pub struct BuildPayloadArgs { pub parent: BlockHash, - pub timestamp: U256, + pub timestamp: u64, pub fee_recipient: Address, pub random: H256, pub withdrawals: Vec, @@ -28,10 +28,8 @@ impl BuildPayloadArgs { // Id computes an 8-byte identifier by hashing the components of the payload arguments. pub fn id(&self) -> u64 { let mut hasher = Keccak256::new(); - let mut timestamp = [0; 32]; - self.timestamp.to_big_endian(&mut timestamp); hasher.update(self.parent); - hasher.update(timestamp); + hasher.update(self.timestamp.to_be_bytes()); hasher.update(self.random); hasher.update(self.fee_recipient); hasher.update(self.withdrawals.encode_to_vec()); @@ -47,7 +45,6 @@ impl BuildPayloadArgs { // Basic payload block building, can and should be improved pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result { let parent_block = storage.get_block_by_hash(args.parent).unwrap().unwrap(); - let timestamp = args.timestamp.as_u64(); let chain_config = storage.get_chain_config().unwrap(); let gas_limit = calc_gas_limit(parent_block.header.gas_limit, 30_000_000); Ok(Block { @@ -63,7 +60,7 @@ pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result Result, From 1bdf863dcf13bf5abfb9aead0c94e64aecced028 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Wed, 25 Sep 2024 15:25:59 -0300 Subject: [PATCH 12/23] Fix ser --- crates/rpc/types/fork_choice.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rpc/types/fork_choice.rs b/crates/rpc/types/fork_choice.rs index c78c3c0da..68d20f8b5 100644 --- a/crates/rpc/types/fork_choice.rs +++ b/crates/rpc/types/fork_choice.rs @@ -15,6 +15,7 @@ pub struct ForkChoiceState { #[serde(rename_all = "camelCase")] #[allow(unused)] pub struct PayloadAttributesV3 { + #[serde(with = "serde_utils::u64::hex_str")] pub timestamp: u64, pub prev_randao: H256, pub suggested_fee_recipient: Address, From 69d280b0b9820d2972797e0fb4e6e03b26c3c753 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Wed, 25 Sep 2024 15:31:20 -0300 Subject: [PATCH 13/23] Fix --- crates/rpc/engine/fork_choice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index 7db3572e6..59a9a727c 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -140,7 +140,7 @@ fn set_finalized_block<'a>( finalized_block_hash: &H256, storage: &'a Store, ) -> Result, StoreError> { - if finalized_block_hash.is_zero() { + if !finalized_block_hash.is_zero() { // If the finalized block is not in our canonical tree, something is wrong let Some(finalized_block) = storage.get_block_by_hash(*finalized_block_hash)? else { return Ok(Some("final block not available in database")); @@ -162,7 +162,7 @@ fn set_safe_block<'a>( safe_block_hash: &H256, storage: &'a Store, ) -> Result, StoreError> { - if safe_block_hash.is_zero() { + if !safe_block_hash.is_zero() { // If the safe block is not in our canonical tree, something is wrong let Some(safe_block) = storage.get_block_by_hash(*safe_block_hash)? else { return Ok(Some("safe block not available in database")); From 9737674c2ce01fe1fdad8ee3ebcf355ad574e210 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Thu, 26 Sep 2024 13:11:36 -0300 Subject: [PATCH 14/23] Integrate previous changes (part 1) --- crates/rpc/engine/fork_choice.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index c491514c9..cc34a385c 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -1,5 +1,5 @@ use ethereum_rust_blockchain::payload::{build_payload, BuildPayloadArgs}; -use ethereum_rust_core::{types::Block, H256, U256}; +use ethereum_rust_core::{types::{Block, BlockHash, BlockNumber}, H256, U256}; use ethereum_rust_storage::{error::StoreError, Store}; use serde_json::Value; use tracing::warn; @@ -64,8 +64,13 @@ impl RpcHandler for ForkChoiceUpdatedV3 { storage.get_canonical_block_hash(current_block_number)? }; if canonical_block.is_some_and(|h| h != self.fork_choice_state.head_block_hash) { - // TODO: We don't handle re-orgs yet - return Err(RpcErr::Internal); + // We are still under the assumption that the blocks are only added if they are connected + // to the canonical chain. That means that for the state to be consistent we only need to + // check that the safe and finalized ones are in the canonical chain and that the heads parent is too. + if storage.get_canonical_block_hash(head_block.header.number.saturating_sub(1))? + .is_some_and(|h| h == head_block.header.parent_hash) { + storage.set_canonical_block(head_block.header.number, self.fork_choice_state.head_block_hash)?; + } } else if current_block_hash.is_some_and(|h| h != self.fork_choice_state.head_block_hash) { // If the head block is already in our canonical chain, the beacon client is // probably resyncing. Ignore the update. From 1e3d710f519f4690a8ff9326c1d11a01ed6acecf Mon Sep 17 00:00:00 2001 From: fmoletta Date: Thu, 26 Sep 2024 13:14:26 -0300 Subject: [PATCH 15/23] fetch only header instead of block --- crates/rpc/engine/fork_choice.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index cc34a385c..9f051a731 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -1,5 +1,5 @@ use ethereum_rust_blockchain::payload::{build_payload, BuildPayloadArgs}; -use ethereum_rust_core::{types::{Block, BlockHash, BlockNumber}, H256, U256}; +use ethereum_rust_core::{types::BlockHeader, H256, U256}; use ethereum_rust_storage::{error::StoreError, Store}; use serde_json::Value; use tracing::warn; @@ -43,7 +43,8 @@ impl RpcHandler for ForkChoiceUpdatedV3 { return error_response("forkchoice requested update to zero hash"); } // Check if we have the block stored - let Some(head_block) = storage.get_block_by_hash(self.fork_choice_state.head_block_hash)? + let Some(head_block) = + storage.get_block_header_by_hash(self.fork_choice_state.head_block_hash)? else { // TODO: We don't yet support syncing warn!("[Engine - ForkChoiceUpdatedV3] Fork choice head block not found in store (hash {}).", self.fork_choice_state.head_block_hash); @@ -57,7 +58,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { )? { return error_response(error); } - let canonical_block = storage.get_canonical_block_hash(head_block.header.number)?; + let canonical_block = storage.get_canonical_block_hash(head_block.number)?; let current_block_hash = { let current_block_number = storage.get_latest_block_number()?.ok_or(RpcErr::Internal)?; @@ -67,10 +68,15 @@ impl RpcHandler for ForkChoiceUpdatedV3 { // We are still under the assumption that the blocks are only added if they are connected // to the canonical chain. That means that for the state to be consistent we only need to // check that the safe and finalized ones are in the canonical chain and that the heads parent is too. - if storage.get_canonical_block_hash(head_block.header.number.saturating_sub(1))? - .is_some_and(|h| h == head_block.header.parent_hash) { - storage.set_canonical_block(head_block.header.number, self.fork_choice_state.head_block_hash)?; - } + if storage + .get_canonical_block_hash(head_block.number.saturating_sub(1))? + .is_some_and(|h| h == head_block.parent_hash) + { + storage.set_canonical_block( + head_block.number, + self.fork_choice_state.head_block_hash, + )?; + } } else if current_block_hash.is_some_and(|h| h != self.fork_choice_state.head_block_hash) { // If the head block is already in our canonical chain, the beacon client is // probably resyncing. Ignore the update. @@ -115,17 +121,16 @@ impl RpcHandler for ForkChoiceUpdatedV3 { fn total_difficulty_check<'a>( head_block_hash: &'a H256, - head_block: &'a Block, + head_block: &'a BlockHeader, storage: &'a Store, ) -> Result, StoreError> { - if !head_block.header.difficulty.is_zero() || head_block.header.number == 0 { + if !head_block.difficulty.is_zero() || head_block.number == 0 { let total_difficulty = storage.get_block_total_difficulty(*head_block_hash)?; - let parent_total_difficulty = - storage.get_block_total_difficulty(head_block.header.parent_hash)?; + let parent_total_difficulty = storage.get_block_total_difficulty(head_block.parent_hash)?; let terminal_total_difficulty = storage.get_chain_config()?.terminal_total_difficulty; if terminal_total_difficulty.is_none() || total_difficulty.is_none() - || head_block.header.number > 0 && parent_total_difficulty.is_none() + || head_block.number > 0 && parent_total_difficulty.is_none() { return Ok(Some( "total difficulties unavailable for terminal total difficulty check", @@ -134,7 +139,7 @@ fn total_difficulty_check<'a>( if total_difficulty.unwrap() < terminal_total_difficulty.unwrap().into() { return Ok(Some("refusing beacon update to pre-merge")); } - if head_block.header.number > 0 && parent_total_difficulty.unwrap() >= U256::zero() { + if head_block.number > 0 && parent_total_difficulty.unwrap() >= U256::zero() { return Ok(Some( "parent block is already post terminal total difficulty", )); From a634c1381afda9ad1e7a7708863f9e1a1b140575 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Thu, 26 Sep 2024 13:22:30 -0300 Subject: [PATCH 16/23] Fix typo + remove unwrap --- crates/blockchain/payload.rs | 8 ++++++-- crates/rpc/engine/fork_choice.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index a832e60dd..a74eb579e 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -44,8 +44,12 @@ impl BuildPayloadArgs { // Basic payload block building, can and should be improved pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result { - let parent_block = storage.get_block_by_hash(args.parent).unwrap().unwrap(); - let chain_config = storage.get_chain_config().unwrap(); + // Presence of a parent block should have been checked or guaranteed before calling this function + // So we can treat a missing parent block as an internal storage error + let parent_block = storage + .get_block_by_hash(args.parent)? + .ok_or_else(|| StoreError::Custom("unexpected missing parent block".to_string()))?; + let chain_config = storage.get_chain_config()?; let gas_limit = calc_gas_limit(parent_block.header.gas_limit, 30_000_000); Ok(Block { header: BlockHeader { diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index 9f051a731..aa5bc21c8 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -67,7 +67,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { if canonical_block.is_some_and(|h| h != self.fork_choice_state.head_block_hash) { // We are still under the assumption that the blocks are only added if they are connected // to the canonical chain. That means that for the state to be consistent we only need to - // check that the safe and finalized ones are in the canonical chain and that the heads parent is too. + // check that the safe and finalized ones are in the canonical chain and that the head's parent is too. if storage .get_canonical_block_hash(head_block.number.saturating_sub(1))? .is_some_and(|h| h == head_block.parent_hash) From 995aca8c3b9c4bcf46129beade76d81f88f29cdb Mon Sep 17 00:00:00 2001 From: fmoletta Date: Thu, 26 Sep 2024 13:23:34 -0300 Subject: [PATCH 17/23] fetch only header instead of block --- crates/blockchain/payload.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index a74eb579e..427455e67 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -47,21 +47,21 @@ pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result Result Result Date: Thu, 26 Sep 2024 13:31:42 -0300 Subject: [PATCH 18/23] Clarify hardcoded values --- crates/blockchain/payload.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 427455e67..9854d597e 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -44,13 +44,15 @@ impl BuildPayloadArgs { // Basic payload block building, can and should be improved pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result { + // TODO: check where we should get miner values from + const DEFAULT_MINER_GAS_CEIL: u64 = 30_000_000; // Presence of a parent block should have been checked or guaranteed before calling this function // So we can treat a missing parent block as an internal storage error let parent_block = storage .get_block_header_by_hash(args.parent)? .ok_or_else(|| StoreError::Custom("unexpected missing parent block".to_string()))?; let chain_config = storage.get_chain_config()?; - let gas_limit = calc_gas_limit(parent_block.gas_limit, 30_000_000); + let gas_limit = calc_gas_limit(parent_block.gas_limit, DEFAULT_MINER_GAS_CEIL); Ok(Block { header: BlockHeader { parent_hash: args.parent, @@ -65,6 +67,7 @@ pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result Date: Thu, 26 Sep 2024 13:32:36 -0300 Subject: [PATCH 19/23] Add doc --- crates/blockchain/payload.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 9854d597e..d0a333e1f 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -25,7 +25,7 @@ pub struct BuildPayloadArgs { } impl BuildPayloadArgs { - // Id computes an 8-byte identifier by hashing the components of the payload arguments. + /// Computes an 8-byte identifier by hashing the components of the payload arguments. pub fn id(&self) -> u64 { let mut hasher = Keccak256::new(); hasher.update(self.parent); @@ -42,6 +42,7 @@ impl BuildPayloadArgs { } } +/// Builds a new payload based on the payload arguments // Basic payload block building, can and should be improved pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result { // TODO: check where we should get miner values from From 8adfe2896bc877f304dcb4b5783633767ab41987 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Thu, 26 Sep 2024 13:44:14 -0300 Subject: [PATCH 20/23] Add InvalidForkChoiceState RpcErr --- crates/rpc/engine/fork_choice.rs | 43 ++++++++++++++------------------ crates/rpc/utils.rs | 6 +++++ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index aa5bc21c8..bd65b356f 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -83,17 +83,10 @@ impl RpcHandler for ForkChoiceUpdatedV3 { return serde_json::to_value(PayloadStatus::valid()).map_err(|_| RpcErr::Internal); } - // Set finalized block - if let Some(error) = - set_finalized_block(&self.fork_choice_state.finalized_block_hash, &storage)? - { - return error_response(error); - } + // Set finalized & safe blocks + set_finalized_block(&self.fork_choice_state.finalized_block_hash, &storage)?; + set_safe_block(&self.fork_choice_state.safe_block_hash, &storage)?; - // Set safe block - if let Some(error) = set_safe_block(&self.fork_choice_state.safe_block_hash, &storage)? { - return error_response(error); - } let mut response = ForkChoiceResponse::from(PayloadStatus::valid_with_hash( self.fork_choice_state.head_block_hash, )); @@ -148,46 +141,48 @@ fn total_difficulty_check<'a>( Ok(None) } -fn set_finalized_block<'a>( - finalized_block_hash: &H256, - storage: &'a Store, -) -> Result, StoreError> { +fn set_finalized_block(finalized_block_hash: &H256, storage: &Store) -> Result<(), RpcErr> { if !finalized_block_hash.is_zero() { // If the finalized block is not in our canonical tree, something is wrong let Some(finalized_block) = storage.get_block_by_hash(*finalized_block_hash)? else { - return Ok(Some("final block not available in database")); + return Err(RpcErr::InvalidForkChoiceState( + "final block not available in database".to_string(), + )); }; if !storage .get_canonical_block_hash(finalized_block.header.number)? .is_some_and(|ref h| h == finalized_block_hash) { - return Ok(Some("final block not in canonical chain")); + return Err(RpcErr::InvalidForkChoiceState( + "final block not in canonical chain".to_string(), + )); } // Set the finalized block storage.update_finalized_block_number(finalized_block.header.number)?; } - Ok(None) + Ok(()) } -fn set_safe_block<'a>( - safe_block_hash: &H256, - storage: &'a Store, -) -> Result, StoreError> { +fn set_safe_block(safe_block_hash: &H256, storage: &Store) -> Result<(), RpcErr> { if !safe_block_hash.is_zero() { // If the safe block is not in our canonical tree, something is wrong let Some(safe_block) = storage.get_block_by_hash(*safe_block_hash)? else { - return Ok(Some("safe block not available in database")); + return Err(RpcErr::InvalidForkChoiceState( + "safe block not available in database".to_string(), + )); }; if !storage .get_canonical_block_hash(safe_block.header.number)? .is_some_and(|ref h| h == safe_block_hash) { - return Ok(Some("safe block not in canonical chain")); + return Err(RpcErr::InvalidForkChoiceState( + "safe block not in canonical chain".to_string(), + )); } // Set the safe block storage.update_safe_block_number(safe_block.header.number)?; } - Ok(None) + Ok(()) } diff --git a/crates/rpc/utils.rs b/crates/rpc/utils.rs index 79f427be4..2331a16ea 100644 --- a/crates/rpc/utils.rs +++ b/crates/rpc/utils.rs @@ -17,6 +17,7 @@ pub enum RpcErr { Revert { data: String }, Halt { reason: String, gas_used: u64 }, AuthenticationError(AuthenticationError), + InvalidForkChoiceState(String), } impl From for RpcErrorMetadata { @@ -86,6 +87,11 @@ impl From for RpcErrorMetadata { message: "Auth failed: Missing authentication header".to_string(), }, }, + RpcErr::InvalidForkChoiceState(data) => RpcErrorMetadata { + code: -38002, + data: Some(data), + message: "Invalid forkchoice state".to_string(), + }, } } } From 69607aed163a219eb2c9e44166acf2c4be4e6a2d Mon Sep 17 00:00:00 2001 From: fmoletta Date: Thu, 26 Sep 2024 13:46:41 -0300 Subject: [PATCH 21/23] revert uneeded changes --- crates/rpc/types/payload.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/rpc/types/payload.rs b/crates/rpc/types/payload.rs index 5860db1c2..b005dac80 100644 --- a/crates/rpc/types/payload.rs +++ b/crates/rpc/types/payload.rs @@ -14,31 +14,31 @@ use ethereum_rust_core::{ #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExecutionPayloadV3 { - pub parent_hash: H256, - pub fee_recipient: Address, - pub state_root: H256, - pub receipts_root: H256, - pub logs_bloom: Bloom, - pub prev_randao: H256, + parent_hash: H256, + fee_recipient: Address, + state_root: H256, + receipts_root: H256, + logs_bloom: Bloom, + prev_randao: H256, #[serde(with = "serde_utils::u64::hex_str")] - pub block_number: u64, + block_number: u64, #[serde(with = "serde_utils::u64::hex_str")] - pub gas_limit: u64, + gas_limit: u64, #[serde(with = "serde_utils::u64::hex_str")] - pub gas_used: u64, + gas_used: u64, #[serde(with = "serde_utils::u64::hex_str")] - pub timestamp: u64, + timestamp: u64, #[serde(with = "serde_utils::bytes")] - pub extra_data: Bytes, + extra_data: Bytes, #[serde(with = "serde_utils::u64::hex_str")] - pub base_fee_per_gas: u64, + base_fee_per_gas: u64, pub block_hash: H256, - pub transactions: Vec, - pub withdrawals: Vec, + transactions: Vec, + withdrawals: Vec, #[serde(with = "serde_utils::u64::hex_str")] - pub blob_gas_used: u64, + blob_gas_used: u64, #[serde(with = "serde_utils::u64::hex_str")] - pub excess_blob_gas: u64, + excess_blob_gas: u64, } #[derive(Clone, Debug)] From 9618e321372c1298906aa14a7807fb0ca7d2bc55 Mon Sep 17 00:00:00 2001 From: fmoletta Date: Thu, 26 Sep 2024 15:18:00 -0300 Subject: [PATCH 22/23] Change miner to builder --- crates/blockchain/payload.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index d0a333e1f..a1a458cf3 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -45,15 +45,15 @@ impl BuildPayloadArgs { /// Builds a new payload based on the payload arguments // Basic payload block building, can and should be improved pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result { - // TODO: check where we should get miner values from - const DEFAULT_MINER_GAS_CEIL: u64 = 30_000_000; + // TODO: check where we should get builder values from + const DEFAULT_BUILDER_GAS_CEIL: u64 = 30_000_000; // Presence of a parent block should have been checked or guaranteed before calling this function // So we can treat a missing parent block as an internal storage error let parent_block = storage .get_block_header_by_hash(args.parent)? .ok_or_else(|| StoreError::Custom("unexpected missing parent block".to_string()))?; let chain_config = storage.get_chain_config()?; - let gas_limit = calc_gas_limit(parent_block.gas_limit, DEFAULT_MINER_GAS_CEIL); + let gas_limit = calc_gas_limit(parent_block.gas_limit, DEFAULT_BUILDER_GAS_CEIL); Ok(Block { header: BlockHeader { parent_hash: args.parent, @@ -68,7 +68,7 @@ pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result Date: Thu, 26 Sep 2024 15:31:25 -0300 Subject: [PATCH 23/23] rename: local_block -> payload --- crates/rpc/engine/fork_choice.rs | 2 +- crates/storage/engines/api.rs | 4 ++-- crates/storage/engines/in_memory.rs | 10 +++++----- crates/storage/engines/libmdbx.rs | 14 +++++++------- crates/storage/storage.rs | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/crates/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index bd65b356f..244bc05fc 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -105,7 +105,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { let payload_id = args.id(); response.set_id(payload_id); let payload = build_payload(&args, &storage)?; - storage.add_local_block(payload_id, payload)?; + storage.add_payload(payload_id, payload)?; } serde_json::to_value(response).map_err(|_| RpcErr::Internal) diff --git a/crates/storage/engines/api.rs b/crates/storage/engines/api.rs index 1eb5eb702..5a8d8833e 100644 --- a/crates/storage/engines/api.rs +++ b/crates/storage/engines/api.rs @@ -220,7 +220,7 @@ pub trait StoreEngine: Debug + Send + Sync + RefUnwindSafe { // Set the canonical block hash for a given block number. fn set_canonical_block(&self, number: BlockNumber, hash: BlockHash) -> Result<(), StoreError>; - fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError>; + fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError>; - fn get_local_block(&self, payload_id: u64) -> Result, StoreError>; + fn get_payload(&self, payload_id: u64) -> Result, StoreError>; } diff --git a/crates/storage/engines/in_memory.rs b/crates/storage/engines/in_memory.rs index 4ed9676f5..a54def9ab 100644 --- a/crates/storage/engines/in_memory.rs +++ b/crates/storage/engines/in_memory.rs @@ -37,7 +37,7 @@ struct StoreInner { // TODO (#307): Remove TotalDifficulty. block_total_difficulties: HashMap, // Stores local blocks by payload id - local_blocks: HashMap, + payloads: HashMap, } #[derive(Default, Debug)] @@ -346,13 +346,13 @@ impl StoreEngine for Store { Ok(self.inner().canonical_hashes.get(&block_number).cloned()) } - fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { - self.inner().local_blocks.insert(payload_id, block); + fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.inner().payloads.insert(payload_id, block); Ok(()) } - fn get_local_block(&self, payload_id: u64) -> Result, StoreError> { - Ok(self.inner().local_blocks.get(&payload_id).cloned()) + fn get_payload(&self, payload_id: u64) -> Result, StoreError> { + Ok(self.inner().payloads.get(&payload_id).cloned()) } } diff --git a/crates/storage/engines/libmdbx.rs b/crates/storage/engines/libmdbx.rs index bc5b3ef4f..82f42e6cd 100644 --- a/crates/storage/engines/libmdbx.rs +++ b/crates/storage/engines/libmdbx.rs @@ -372,12 +372,12 @@ impl StoreEngine for Store { .map(|o| o.map(|hash_rlp| hash_rlp.to())) } - fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { - self.write::(payload_id, block.into()) + fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.write::(payload_id, block.into()) } - fn get_local_block(&self, payload_id: u64) -> Result, StoreError> { - Ok(self.read::(payload_id)?.map(|b| b.to())) + fn get_payload(&self, payload_id: u64) -> Result, StoreError> { + Ok(self.read::(payload_id)?.map(|b| b.to())) } } @@ -455,8 +455,8 @@ table!( // Local Blocks table!( - /// state trie nodes - ( LocalBlocks ) u64 => BlockRLP + /// payload id to payload block table + ( Payloads ) u64 => BlockRLP ); // Storage values are stored as bytes instead of using their rlp encoding @@ -556,7 +556,7 @@ pub fn init_db(path: Option>) -> Database { table_info!(StateTrieNodes), table_info!(StorageTriesNodes), table_info!(CanonicalBlockHashes), - table_info!(LocalBlocks), + table_info!(Payloads), ] .into_iter() .collect(); diff --git a/crates/storage/storage.rs b/crates/storage/storage.rs index ba67804e6..12662b1ca 100644 --- a/crates/storage/storage.rs +++ b/crates/storage/storage.rs @@ -580,12 +580,12 @@ impl Store { Ok(trie.get_proof(&hash_key(storage_key))?) } - pub fn add_local_block(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { - self.engine.add_local_block(payload_id, block) + pub fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.engine.add_payload(payload_id, block) } - pub fn get_local_block(&self, payload_id: u64) -> Result, StoreError> { - self.engine.get_local_block(payload_id) + pub fn get_payload(&self, payload_id: u64) -> Result, StoreError> { + self.engine.get_payload(payload_id) } }