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 6f5957a38..bca23d9ad 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..a1a458cf3 --- /dev/null +++ b/crates/blockchain/payload.rs @@ -0,0 +1,129 @@ +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: u64, + pub fee_recipient: Address, + pub random: H256, + pub withdrawals: Vec, + pub beacon_root: Option, + pub version: u8, +} + +impl BuildPayloadArgs { + /// 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); + hasher.update(self.timestamp.to_be_bytes()); + 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()) + } +} + +/// 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 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_BUILDER_GAS_CEIL); + Ok(Block { + header: BlockHeader { + parent_hash: args.parent, + ommers_hash: *DEFAULT_OMMERS_HASH, + coinbase: args.fee_recipient, + state_root: parent_block.state_root, + transactions_root: compute_transactions_root(&[]), + receipts_root: compute_receipts_root(&[]), + logs_bloom: Bloom::default(), + difficulty: U256::zero(), + number: parent_block.number.saturating_add(1), + gas_limit, + gas_used: 0, + timestamp: args.timestamp, + // TODO: should use builder config's extra_data + extra_data: Bytes::new(), + prev_randao: args.random, + nonce: 0, + base_fee_per_gas: calculate_base_fee_per_gas( + gas_limit, + parent_block.gas_limit, + parent_block.gas_used, + parent_block.base_fee_per_gas.unwrap_or_default(), + ), + withdrawals_root: chain_config + .is_shanghai_activated(args.timestamp) + .then_some(compute_withdrawals_root(&args.withdrawals)), + blob_gas_used: Some(0), + excess_blob_gas: chain_config.is_cancun_activated(args.timestamp).then_some( + calc_excess_blob_gas( + parent_block.excess_blob_gas.unwrap_or_default(), + parent_block.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/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/core/types/block.rs b/crates/core/types/block.rs index 4c9c2f2dc..06655523c 100644 --- a/crates/core/types/block.rs +++ b/crates/core/types/block.rs @@ -339,7 +339,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/rpc/engine/fork_choice.rs b/crates/rpc/engine/fork_choice.rs index 650ba6509..244bc05fc 100644 --- a/crates/rpc/engine/fork_choice.rs +++ b/crates/rpc/engine/fork_choice.rs @@ -1,10 +1,14 @@ -use ethereum_rust_core::types::{BlockHash, BlockNumber}; +use ethereum_rust_blockchain::payload::{build_payload, BuildPayloadArgs}; +use ethereum_rust_core::{types::BlockHeader, H256, U256}; use ethereum_rust_storage::{error::StoreError, Store}; -use serde_json::{json, Value}; +use serde_json::Value; use tracing::warn; use crate::{ - types::fork_choice::{ForkChoiceState, PayloadAttributesV3}, + types::{ + fork_choice::{ForkChoiceResponse, ForkChoiceState, PayloadAttributesV3}, + payload::PayloadStatus, + }, RpcErr, RpcHandler, }; @@ -28,83 +32,157 @@ 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) }; - // Check if the payload is available for the new head hash. - let header = match storage.get_block_header_by_hash(self.fork_choice_state.head_block_hash) - { - Ok(Some(header)) => header, - Ok(None) => { - warn!("[Engine - ForkChoiceUpdatedV3] Fork choice head block not found in store (hash {}).", self.fork_choice_state.head_block_hash); - return syncing_response(); - } - _ => return Err(RpcErr::Internal), + if self.fork_choice_state.head_block_hash.is_zero() { + return error_response("forkchoice requested update to zero hash"); + } + // Check if we have the block stored + 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); + return Err(RpcErr::Internal); + }; + // Check that we are not being pushed pre-merge + 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_hash(head_block.number)?; + let current_block_hash = { + let current_block_number = + storage.get_latest_block_number()?.ok_or(RpcErr::Internal)?; + storage.get_canonical_block_hash(current_block_number)? }; + 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 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) + { + 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. + return serde_json::to_value(PayloadStatus::valid()).map_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. + // 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)?; - let head_valid = is_canonical(&storage, header.number - 1, header.parent_hash) - .map_err(|_| RpcErr::Internal)?; + let mut response = ForkChoiceResponse::from(PayloadStatus::valid_with_hash( + self.fork_choice_state.head_block_hash, + )); - let safe_valid = is_canonical( - &storage, - safe_block_number, - self.fork_choice_state.safe_block_hash, - ) - .map_err(|_| RpcErr::Internal)?; + // Build block from received payload + if let Some(attributes) = &self.payload_attributes { + let args = 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, + }; + let payload_id = args.id(); + response.set_id(payload_id); + let payload = build_payload(&args, &storage)?; + storage.add_payload(payload_id, payload)?; + } - let finalized_valid = is_canonical( - &storage, - finalized_block_number, - self.fork_choice_state.finalized_block_hash, - ) - .map_err(|_| RpcErr::Internal)?; + serde_json::to_value(response).map_err(|_| RpcErr::Internal) + } +} - if head_valid && safe_valid && finalized_valid { - storage.set_canonical_block(header.number, self.fork_choice_state.head_block_hash)?; - storage.update_finalized_block_number(finalized_block_number)?; - storage.update_safe_block_number(safe_block_number)?; - syncing_response() - } else { - invalid_fork_choice_state() +fn total_difficulty_check<'a>( + head_block_hash: &'a H256, + head_block: &'a BlockHeader, + storage: &'a Store, +) -> Result, StoreError> { + 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.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.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.number > 0 && parent_total_difficulty.unwrap() >= U256::zero() { + return Ok(Some( + "parent block is already post terminal total difficulty", + )); } } + Ok(None) } -fn syncing_response() -> Result { - serde_json::to_value(json!({ - "payloadId": null, - "payloadStatus": { - "latestValidHash": null, - "status": "SYNCING", - "validationError": null - }})) - .map_err(|_| RpcErr::Internal) -} +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 Err(RpcErr::InvalidForkChoiceState( + "final block not available in database".to_string(), + )); + }; -fn invalid_fork_choice_state() -> Result { - serde_json::to_value(json!({"error": {"code": -38002, "message": "Invalid forkchoice state"}})) - .map_err(|_| RpcErr::Internal) + if !storage + .get_canonical_block_hash(finalized_block.header.number)? + .is_some_and(|ref h| h == finalized_block_hash) + { + 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(()) } -fn is_canonical( - store: &Store, - block_number: BlockNumber, - block_hash: BlockHash, -) -> Result { - match store.get_canonical_block_hash(block_number)? { - Some(hash) if hash == block_hash => Ok(true), - _ => Ok(false), +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 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 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(()) } diff --git a/crates/rpc/types/fork_choice.rs b/crates/rpc/types/fork_choice.rs index 2d856d22f..68d20f8b5 100644 --- a/crates/rpc/types/fork_choice.rs +++ b/crates/rpc/types/fork_choice.rs @@ -1,5 +1,6 @@ -use ethereum_rust_core::{types::Withdrawal, Address, H256, U256}; -use serde::Deserialize; +use super::payload::PayloadStatus; +use ethereum_rust_core::{serde_utils, types::Withdrawal, Address, H256}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -14,9 +15,33 @@ 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, + #[serde(with = "serde_utils::u64::hex_str")] + pub timestamp: u64, + 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, + #[serde(with = "serde_utils::u64::hex_str_opt_padded")] + pub payload_id: Option, +} + +impl ForkChoiceResponse { + pub fn set_id(&mut self, id: u64) { + 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/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(), + }, } } } diff --git a/crates/storage/engines/api.rs b/crates/storage/engines/api.rs index 3e16ce9d8..5a8d8833e 100644 --- a/crates/storage/engines/api.rs +++ b/crates/storage/engines/api.rs @@ -151,6 +151,7 @@ pub trait StoreEngine: Debug + Send + Sync + RefUnwindSafe { Ok(Some(Block { header, body })) } + // Get the canonical block hash for a given block number. fn get_canonical_block_hash( &self, block_number: BlockNumber, @@ -216,6 +217,10 @@ 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>; + + fn add_payload(&self, payload_id: u64, block: Block) -> 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 c4e5b6490..a54def9ab 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 + payloads: HashMap, } #[derive(Default, Debug)] @@ -343,6 +345,15 @@ impl StoreEngine for Store { ) -> Result, StoreError> { Ok(self.inner().canonical_hashes.get(&block_number).cloned()) } + + fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.inner().payloads.insert(payload_id, block); + Ok(()) + } + + fn get_payload(&self, payload_id: u64) -> Result, StoreError> { + Ok(self.inner().payloads.get(&payload_id).cloned()) + } } impl Debug for Store { diff --git a/crates/storage/engines/libmdbx.rs b/crates/storage/engines/libmdbx.rs index e5601017e..82f42e6cd 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 { self.read::(number) .map(|o| o.map(|hash_rlp| hash_rlp.to())) } + + fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.write::(payload_id, block.into()) + } + + fn get_payload(&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!( + /// payload id to payload block table + ( Payloads ) 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!(Payloads), ] .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 daef24f97..12662b1ca 100644 --- a/crates/storage/storage.rs +++ b/crates/storage/storage.rs @@ -579,6 +579,14 @@ impl Store { let trie = self.engine.open_storage_trie(address, storage_root); Ok(trie.get_proof(&hash_key(storage_key))?) } + + pub fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { + self.engine.add_payload(payload_id, block) + } + + pub fn get_payload(&self, payload_id: u64) -> Result, StoreError> { + self.engine.get_payload(payload_id) + } } fn hash_address(address: &Address) -> Vec {