diff --git a/Cargo.lock b/Cargo.lock index e8a743d34a..8ada11f279 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3336,13 +3336,16 @@ dependencies = [ "async-trait", "auto_impl", "eyre", + "futures", "reth-interfaces", "reth-primitives", "reth-provider", "reth-rlp", + "reth-rpc-types", "serde", "thiserror", "tokio", + "tokio-stream", ] [[package]] @@ -3721,6 +3724,7 @@ dependencies = [ "async-trait", "hex", "jsonrpsee", + "reth-consensus", "reth-interfaces", "reth-network", "reth-primitives", @@ -3731,6 +3735,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "tokio", ] [[package]] diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml index 0e0f34fb1a..9397390d6e 100644 --- a/crates/consensus/Cargo.toml +++ b/crates/consensus/Cargo.toml @@ -11,14 +11,19 @@ readme = "README.md" reth-primitives = { path = "../primitives" } reth-interfaces = { path = "../interfaces" } reth-provider = { path = "../storage/provider" } -reth-rlp = {path = "../common/rlp"} +reth-rlp = { path = "../common/rlp" } +reth-rpc-types = { path = "../net/rpc-types" } + +# async +futures = "0.3" +async-trait = "0.1.57" +tokio = { version = "1", features = ["sync"] } +tokio-stream = "0.1" # common -async-trait = "0.1.57" thiserror = "1.0.37" eyre = "0.6.8" auto_impl = "1.0" -tokio = { version = "1.21.2", features = ["sync"] } # io serde = { version = "1.0", optional = true } diff --git a/crates/consensus/src/engine/error.rs b/crates/consensus/src/engine/error.rs new file mode 100644 index 0000000000..bc1e2720f2 --- /dev/null +++ b/crates/consensus/src/engine/error.rs @@ -0,0 +1,67 @@ +use reth_primitives::{Bytes, H256, U256}; +use thiserror::Error; + +/// The Engine API result type +pub type EngineApiResult = Result; + +/// Error returned by [`EngineApi`][crate::engine::EngineApi] +#[derive(Error, Debug)] +pub enum EngineApiError { + /// Invalid payload extra data. + #[error("Invalid payload extra data: {0}")] + PayloadExtraData(Bytes), + /// Invalid payload base fee. + #[error("Invalid payload base fee: {0}")] + PayloadBaseFee(U256), + /// Invalid payload block hash. + #[error("Invalid payload block hash. Execution: {execution}. Consensus: {consensus}")] + PayloadBlockHash { + /// The block hash computed from the payload. + execution: H256, + /// The block hash provided with the payload. + consensus: H256, + }, + /// Invalid payload block hash. + #[error("Invalid payload timestamp: {invalid}. Latest: {latest}")] + PayloadTimestamp { + /// The payload timestamp. + invalid: u64, + /// Latest available timestamp. + latest: u64, + }, + /// Received pre-merge payload. + #[error("Received pre-merge payload.")] + PayloadPreMerge, + /// Unknown payload requested. + #[error("Unknown payload")] + PayloadUnknown, + /// Terminal total difficulty mismatch during transition configuration exchange. + #[error( + "Invalid transition terminal total difficulty. Execution: {execution}. Consensus: {consensus}" + )] + TerminalTD { + /// Execution terminal total difficulty value. + execution: U256, + /// Consensus terminal total difficulty value. + consensus: U256, + }, + /// Terminal block hash mismatch during transition configuration exchange. + #[error( + "Invalid transition terminal block hash. Execution: {execution:?}. Consensus: {consensus}" + )] + TerminalBlockHash { + /// Execution terminal block hash. `None` if block number is not found in the database. + execution: Option, + /// Consensus terminal block hash. + consensus: H256, + }, + /// Forkchoice zero hash head received. + #[error("Received zero hash as forkchoice head")] + ForkchoiceEmptyHead, + /// Encountered decoding error. + #[error(transparent)] + Decode(#[from] reth_rlp::DecodeError), + /// API encountered an internal error. + #[error(transparent)] + Internal(#[from] reth_interfaces::Error), +} diff --git a/crates/consensus/src/engine/mod.rs b/crates/consensus/src/engine/mod.rs new file mode 100644 index 0000000000..40be8650be --- /dev/null +++ b/crates/consensus/src/engine/mod.rs @@ -0,0 +1,293 @@ +use futures::StreamExt; +use reth_interfaces::consensus::ForkchoiceState; +use reth_primitives::{ + proofs::{self, EMPTY_LIST_HASH}, + rpc::BlockId, + BlockLocked, Header, TransactionSigned, H64, +}; +use reth_provider::{BlockProvider, HeaderProvider}; +use reth_rlp::Decodable; +use reth_rpc_types::engine::{ + ExecutionPayload, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, PayloadStatusEnum, + TransitionConfiguration, +}; +use std::{ + collections::HashMap, + future::Future, + pin::Pin, + sync::Arc, + task::{ready, Context, Poll}, +}; +use tokio::sync::oneshot; +use tokio_stream::wrappers::UnboundedReceiverStream; + +mod error; +use crate::Config; +pub use error::{EngineApiError, EngineApiResult}; + +/// The Engine API response sender +pub type EngineApiSender = oneshot::Sender>; + +/// Consensus engine API trait. +pub trait ConsensusEngine { + /// Retrieves payload from local cache. + fn get_payload(&self, payload_id: H64) -> Option; + + /// Receives a payload to validate and execute. + fn new_payload(&mut self, payload: ExecutionPayload) -> EngineApiResult; + + /// Updates the fork choice state + fn fork_choice_updated( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> EngineApiResult; + + /// Verifies the transition configuration between execution and consensus clients. + fn exchange_transition_configuration( + &self, + config: TransitionConfiguration, + ) -> EngineApiResult; +} + +/// Message type for communicating with [EthConsensusEngine] +pub enum EngineMessage { + /// New payload message + NewPayload(ExecutionPayload, EngineApiSender), + /// Get payload message + GetPayload(H64, EngineApiSender), + /// Forkchoice updated message + ForkchoiceUpdated( + ForkchoiceState, + Option, + EngineApiSender, + ), + /// Exchange transition configuration message + ExchangeTransitionConfiguration( + TransitionConfiguration, + EngineApiSender, + ), +} + +/// The consensus engine API implementation +#[must_use = "EthConsensusEngine does nothing unless polled."] +pub struct EthConsensusEngine { + /// Consensus configuration + config: Config, + client: Arc, + /// Placeholder for storing future blocks + local_store: HashMap, // TODO: bound + // remote_store: HashMap, + rx: UnboundedReceiverStream, +} + +impl EthConsensusEngine { + fn on_message(&mut self, msg: EngineMessage) { + match msg { + EngineMessage::GetPayload(payload_id, tx) => { + // NOTE: Will always result in `PayloadUnknown` since we don't support block + // building for now. + let _ = tx.send(self.get_payload(payload_id).ok_or(EngineApiError::PayloadUnknown)); + } + EngineMessage::NewPayload(payload, tx) => { + let _ = tx.send(self.new_payload(payload)); + } + EngineMessage::ForkchoiceUpdated(state, attrs, tx) => { + let _ = tx.send(self.fork_choice_updated(state, attrs)); + } + EngineMessage::ExchangeTransitionConfiguration(config, tx) => { + let _ = tx.send(self.exchange_transition_configuration(config)); + } + } + } + + /// Try to construct a block from given payload. Perform addition validation of `extra_data` and + /// `base_fee_per_gas` fields. + /// + /// NOTE: The log bloom is assumed to be validated during serialization. + /// NOTE: Ommers hash is validated upon computing block hash and comparing the value with + /// `payload.block_hash`. + /// Ref: https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145 + fn try_construct_block(&self, payload: ExecutionPayload) -> EngineApiResult { + if payload.extra_data.len() > 32 { + return Err(EngineApiError::PayloadExtraData(payload.extra_data)) + } + + if payload.base_fee_per_gas.is_zero() { + return Err(EngineApiError::PayloadBaseFee(payload.base_fee_per_gas)) + } + + let transactions = payload + .transactions + .iter() + .map(|tx| TransactionSigned::decode(&mut tx.as_ref())) + .collect::, _>>()?; + let transactions_root = proofs::calculate_transaction_root(transactions.iter()); + let header = Header { + parent_hash: payload.parent_hash, + beneficiary: payload.fee_recipient, + state_root: payload.state_root, + transactions_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + number: payload.block_number.as_u64(), + gas_limit: payload.gas_limit.as_u64(), + gas_used: payload.gas_used.as_u64(), + timestamp: payload.timestamp.as_u64(), + mix_hash: payload.prev_randao, + base_fee_per_gas: Some(payload.base_fee_per_gas.as_u64()), + extra_data: payload.extra_data.0, + // Defaults + ommers_hash: EMPTY_LIST_HASH, + difficulty: Default::default(), + nonce: Default::default(), + }; + let header = header.seal(); + + if payload.block_hash != header.hash() { + return Err(EngineApiError::PayloadBlockHash { + execution: header.hash(), + consensus: payload.block_hash, + }) + } + + Ok(BlockLocked { header, body: transactions, ommers: Default::default() }) + } +} + +impl ConsensusEngine for EthConsensusEngine { + fn get_payload(&self, payload_id: H64) -> Option { + self.local_store.get(&payload_id).cloned() + } + + fn new_payload(&mut self, payload: ExecutionPayload) -> EngineApiResult { + let block = match self.try_construct_block(payload) { + Ok(b) => b, + Err(err) => { + return Ok(PayloadStatus::from_status(PayloadStatusEnum::InvalidBlockHash { + validation_error: err.to_string(), + })) + } + }; + + // The block already exists in our database + if self.client.is_known(&block.hash())? { + return Ok(PayloadStatus::new(PayloadStatusEnum::Valid, block.hash())) + } + + let Some(parent) = self.client.block(BlockId::Hash(block.parent_hash))? else { + // TODO: cache block for storing later + return Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing)) + }; + + let parent_td = self.client.header_td(&block.parent_hash)?; + if parent_td.unwrap_or_default() <= self.config.merge_terminal_total_difficulty.into() { + return Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid { + validation_error: EngineApiError::PayloadPreMerge.to_string(), + })) + } + + if block.timestamp <= parent.timestamp { + return Err(EngineApiError::PayloadTimestamp { + invalid: block.timestamp, + latest: parent.timestamp, + }) + } + + // TODO: execute block + + Ok(PayloadStatus::new(PayloadStatusEnum::Valid, block.hash())) + } + + fn fork_choice_updated( + &self, + fork_choice_state: ForkchoiceState, + _payload_attributes: Option, + ) -> EngineApiResult { + let ForkchoiceState { head_block_hash, finalized_block_hash, .. } = fork_choice_state; + + if head_block_hash.is_zero() { + return Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Invalid { + validation_error: EngineApiError::ForkchoiceEmptyHead.to_string(), + })) + } + + // Block is not known, nothing to do. + if !self.client.is_known(&head_block_hash)? { + return Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Syncing)) + } + + // The finalized block hash is not known, we are still syncing + if !finalized_block_hash.is_zero() && !self.client.is_known(&finalized_block_hash)? { + return Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Syncing)) + } + + let chain_info = self.client.chain_info()?; + Ok(ForkchoiceUpdated::from_status(PayloadStatusEnum::Valid) + .with_latest_valid_hash(chain_info.best_hash)) + } + + fn exchange_transition_configuration( + &self, + config: TransitionConfiguration, + ) -> EngineApiResult { + let TransitionConfiguration { + terminal_total_difficulty, + terminal_block_hash, + terminal_block_number, + } = config; + + // Compare total difficulty values + let merge_terminal_td = self.config.merge_terminal_total_difficulty.into(); + if merge_terminal_td != terminal_total_difficulty { + return Err(EngineApiError::TerminalTD { + execution: merge_terminal_td, + consensus: terminal_total_difficulty, + }) + } + + // Short circuit if communicated block hash is zero + if terminal_block_hash.is_zero() { + return Ok(TransitionConfiguration { + terminal_total_difficulty: merge_terminal_td, + ..Default::default() + }) + } + + // Attempt to look up terminal block hash + let local_hash = self.client.block_hash(terminal_block_number.into())?; + + // Transition configuration exchange is successful if block hashes match + match local_hash { + Some(hash) if hash == terminal_block_hash => Ok(TransitionConfiguration { + terminal_total_difficulty: merge_terminal_td, + terminal_block_hash, + terminal_block_number, + }), + _ => Err(EngineApiError::TerminalBlockHash { + execution: local_hash, + consensus: terminal_block_hash, + }), + } + } +} + +impl Future for EthConsensusEngine +where + Client: HeaderProvider + BlockProvider + Unpin, +{ + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + loop { + match ready!(this.rx.poll_next_unpin(cx)) { + Some(msg) => this.on_message(msg), + None => { + // channel closed + return Poll::Ready(()) + } + } + } + } +} diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs index 65797f2c32..17493a6b1c 100644 --- a/crates/consensus/src/lib.rs +++ b/crates/consensus/src/lib.rs @@ -13,6 +13,9 @@ pub mod config; pub mod consensus; pub mod verification; +/// Engine API module. +pub mod engine; + pub use config::Config; pub use consensus::BeaconConsensus; pub use reth_interfaces::consensus::Error; diff --git a/crates/consensus/src/verification.rs b/crates/consensus/src/verification.rs index e45c252eaa..c5c4e93b61 100644 --- a/crates/consensus/src/verification.rs +++ b/crates/consensus/src/verification.rs @@ -431,6 +431,10 @@ mod tests { fn header_by_number(&self, _num: u64) -> Result> { Ok(self.parent.clone()) } + + fn header_td(&self, _hash: &BlockHash) -> Result> { + Ok(None) + } } fn mock_tx(nonce: u64) -> TransactionSignedEcRecovered { diff --git a/crates/net/network/tests/it/testnet.rs b/crates/net/network/tests/it/testnet.rs index 37e6934a5d..ba0807fdac 100644 --- a/crates/net/network/tests/it/testnet.rs +++ b/crates/net/network/tests/it/testnet.rs @@ -364,6 +364,10 @@ impl HeaderProvider for MockEthProvider { let lock = self.headers.lock(); Ok(lock.values().find(|h| h.number == num).cloned()) } + + fn header_td(&self, _hash: &BlockHash) -> reth_interfaces::Result> { + todo!() + } } impl BlockProvider for MockEthProvider { diff --git a/crates/net/rpc-types/Cargo.toml b/crates/net/rpc-types/Cargo.toml index 20f8d902af..d15be8923b 100644 --- a/crates/net/rpc-types/Cargo.toml +++ b/crates/net/rpc-types/Cargo.toml @@ -11,7 +11,7 @@ Reth RPC types [dependencies] # reth reth-primitives = { path = "../../primitives" } -reth-rlp = {path = "../../common/rlp"} +reth-rlp = { path = "../../common/rlp" } # misc serde = { version = "1.0", features = ["derive"] } diff --git a/crates/net/rpc-types/src/eth/engine.rs b/crates/net/rpc-types/src/eth/engine.rs index 743dcbb936..0715c59e73 100644 --- a/crates/net/rpc-types/src/eth/engine.rs +++ b/crates/net/rpc-types/src/eth/engine.rs @@ -66,7 +66,7 @@ pub struct PayloadAttributes { /// Array of [`Withdrawal`] enabled with V2 /// See #[serde(default, skip_serializing_if = "Option::is_none")] - pub withdrawal: Option, + pub withdrawal: Option, // TODO: should be a vec } /// This structure contains the result of processing a payload @@ -79,6 +79,16 @@ pub struct PayloadStatus { pub latest_valid_hash: Option, } +impl PayloadStatus { + pub fn new(status: PayloadStatusEnum, latest_valid_hash: H256) -> Self { + Self { status, latest_valid_hash: Some(latest_valid_hash) } + } + + pub fn from_status(status: PayloadStatusEnum) -> Self { + Self { status, latest_valid_hash: None } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] pub enum PayloadStatusEnum { @@ -96,7 +106,7 @@ pub enum PayloadStatusEnum { } /// This structure contains configurable settings of the transition process. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransitionConfiguration { /// Maps on the TERMINAL_TOTAL_DIFFICULTY parameter of EIP-3675 @@ -113,3 +123,23 @@ pub struct ForkchoiceUpdated { pub payload_status: PayloadStatus, pub payload_id: Option, } + +impl ForkchoiceUpdated { + pub fn new(payload_status: PayloadStatus) -> Self { + Self { payload_status, payload_id: None } + } + + pub fn from_status(status: PayloadStatusEnum) -> Self { + Self { payload_status: PayloadStatus::from_status(status), payload_id: None } + } + + pub fn with_latest_valid_hash(mut self, hash: H256) -> Self { + self.payload_status.latest_valid_hash = Some(hash); + self + } + + pub fn with_payload_id(mut self, id: H64) -> Self { + self.payload_id = Some(id); + self + } +} diff --git a/crates/net/rpc/Cargo.toml b/crates/net/rpc/Cargo.toml index a941980240..ffffe1f5ce 100644 --- a/crates/net/rpc/Cargo.toml +++ b/crates/net/rpc/Cargo.toml @@ -14,15 +14,19 @@ reth-interfaces = { path = "../../interfaces" } reth-primitives = { path = "../../primitives" } reth-rpc-api = { path = "../rpc-api" } reth-rpc-types = { path = "../rpc-types" } -reth-provider = { path = "../../storage/provider"} +reth-provider = { path = "../../storage/provider" } reth-transaction-pool = { path = "../../transaction-pool" } reth-network = { path = "../network" } +reth-consensus = { path = "../../consensus", features = ["serde"] } # rpc jsonrpsee = { version = "0.16" } -# misc +# async async-trait = "0.1" +tokio = { version = "1", features = ["sync"] } + +# misc serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" diff --git a/crates/net/rpc/src/engine/mod.rs b/crates/net/rpc/src/engine/mod.rs new file mode 100644 index 0000000000..9732f71672 --- /dev/null +++ b/crates/net/rpc/src/engine/mod.rs @@ -0,0 +1,106 @@ +use crate::result::rpc_err; +use async_trait::async_trait; +use jsonrpsee::core::{Error, RpcResult as Result}; +use reth_consensus::engine::{EngineApiError, EngineApiResult, EngineMessage}; +use reth_interfaces::consensus::ForkchoiceState; +use reth_primitives::H64; +use reth_rpc_api::EngineApiServer; +use reth_rpc_types::engine::{ + ExecutionPayload, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, TransitionConfiguration, +}; +use tokio::sync::{ + mpsc::UnboundedSender, + oneshot::{self, Receiver}, +}; + +/// The server implementation of Engine API +pub struct EngineApi { + /// Handle to the consensus engine + engine_tx: UnboundedSender, +} + +impl std::fmt::Debug for EngineApi { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EngineApi").finish_non_exhaustive() + } +} + +impl EngineApi { + async fn delegate_request( + &self, + msg: EngineMessage, + rx: Receiver>, + ) -> Result { + let _ = self.engine_tx.send(msg); + rx.await.map_err(|err| Error::Custom(err.to_string()))?.map_err(|err| { + let code = match err { + EngineApiError::PayloadUnknown => -38001, + // Any other server error + _ => jsonrpsee::types::error::INTERNAL_ERROR_CODE, + }; + rpc_err(code, err.to_string(), None) + }) + } +} + +#[async_trait] +impl EngineApiServer for EngineApi { + /// See also + /// Caution: This should not accept the `withdrawals` field + async fn new_payload_v1(&self, payload: ExecutionPayload) -> Result { + let (tx, rx) = oneshot::channel(); + self.delegate_request(EngineMessage::NewPayload(payload, tx), rx).await + } + + /// See also + async fn new_payload_v2(&self, _payload: ExecutionPayload) -> Result { + todo!() + } + + /// See also + /// + /// Caution: This should not accept the `withdrawals` field + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> Result { + let (tx, rx) = oneshot::channel(); + self.delegate_request( + EngineMessage::ForkchoiceUpdated(fork_choice_state, payload_attributes, tx), + rx, + ) + .await + } + + /// See also + async fn fork_choice_updated_v2( + &self, + _fork_choice_state: ForkchoiceState, + _payload_attributes: Option, + ) -> Result { + todo!() + } + + /// See also + /// + /// Caution: This should not return the `withdrawals` field + async fn get_payload_v1(&self, payload_id: H64) -> Result { + let (tx, rx) = oneshot::channel(); + self.delegate_request(EngineMessage::GetPayload(payload_id, tx), rx).await + } + + /// See also + async fn get_payload_v2(&self, _payload_id: H64) -> Result { + todo!() + } + + /// See also + async fn exchange_transition_configuration( + &self, + config: TransitionConfiguration, + ) -> Result { + let (tx, rx) = oneshot::channel(); + self.delegate_request(EngineMessage::ExchangeTransitionConfiguration(config, tx), rx).await + } +} diff --git a/crates/net/rpc/src/eth/api/mod.rs b/crates/net/rpc/src/eth/api/mod.rs index 2d747deab4..355a049325 100644 --- a/crates/net/rpc/src/eth/api/mod.rs +++ b/crates/net/rpc/src/eth/api/mod.rs @@ -1,8 +1,8 @@ //! Provides everything related to `eth_` namespace use reth_interfaces::Result; -use reth_primitives::{U256, U64}; -use reth_provider::{BlockProvider, StateProviderFactory}; +use reth_primitives::U64; +use reth_provider::{BlockProvider, ChainInfo, StateProviderFactory}; use reth_rpc_types::Transaction; use reth_transaction_pool::TransactionPool; use std::sync::Arc; @@ -16,11 +16,11 @@ pub trait EthApiSpec: Send + Sync { /// Returns the current ethereum protocol version. fn protocol_version(&self) -> U64; - /// Returns the best block number - fn block_number(&self) -> Result; - /// Returns the chain id fn chain_id(&self) -> U64; + + /// Returns client chain info + fn chain_info(&self) -> Result; } /// `Eth` API implementation. @@ -66,15 +66,15 @@ where 1u64.into() } - /// Returns the best block number - fn block_number(&self) -> Result { - Ok(self.client().chain_info()?.best_number.into()) - } - /// Returns the chain id fn chain_id(&self) -> U64 { todo!() } + + /// Returns the current info for the chain + fn chain_info(&self) -> Result { + self.client().chain_info() + } } /// Container type `EthApi` diff --git a/crates/net/rpc/src/eth/api/server.rs b/crates/net/rpc/src/eth/api/server.rs index fa2235ab3f..ce5f02f70a 100644 --- a/crates/net/rpc/src/eth/api/server.rs +++ b/crates/net/rpc/src/eth/api/server.rs @@ -42,11 +42,14 @@ where } fn block_number(&self) -> Result { - EthApiSpec::block_number(self).with_message("Failed to read block number") + Ok(EthApiSpec::chain_info(self) + .with_message("failed to read chain info")? + .best_number + .into()) } async fn chain_id(&self) -> Result> { - todo!() + Ok(Some(EthApiSpec::chain_id(self))) } async fn block_by_hash(&self, _hash: H256, _full: bool) -> Result> { diff --git a/crates/net/rpc/src/lib.rs b/crates/net/rpc/src/lib.rs index 646f962657..dedf3da738 100644 --- a/crates/net/rpc/src/lib.rs +++ b/crates/net/rpc/src/lib.rs @@ -11,9 +11,11 @@ //! //! Provides the implementation of all RPC interfaces. +mod engine; mod eth; mod net; +pub use engine::EngineApi; pub use eth::{EthApi, EthApiSpec, EthPubSub}; pub use net::NetApi; diff --git a/crates/storage/provider/src/block.rs b/crates/storage/provider/src/block.rs index 9decc095e6..aad4a6aa60 100644 --- a/crates/storage/provider/src/block.rs +++ b/crates/storage/provider/src/block.rs @@ -31,6 +31,9 @@ pub trait HeaderProvider: Send + Sync { BlockHashOrNumber::Number(num) => self.header_by_number(num), } } + + /// Get total difficulty by block hash. + fn header_td(&self, hash: &BlockHash) -> Result>; } /// Api trait for fetching `Block` related data. diff --git a/crates/storage/provider/src/db_provider/block.rs b/crates/storage/provider/src/db_provider/block.rs index 6f93a8e69c..63b5d0ee93 100644 --- a/crates/storage/provider/src/db_provider/block.rs +++ b/crates/storage/provider/src/db_provider/block.rs @@ -1,10 +1,10 @@ use crate::{BlockProvider, ChainInfo, HeaderProvider, ProviderImpl}; use reth_db::{database::Database, tables, transaction::DbTx}; use reth_interfaces::Result; -use reth_primitives::{rpc::BlockId, Block, BlockNumber, Header, H256, U256}; +use reth_primitives::{rpc::BlockId, Block, BlockHash, BlockNumber, Header, H256, U256}; impl HeaderProvider for ProviderImpl { - fn header(&self, block_hash: &reth_primitives::BlockHash) -> Result> { + fn header(&self, block_hash: &BlockHash) -> Result> { self.db.view(|tx| tx.get::((0, *block_hash).into()))?.map_err(Into::into) } @@ -15,6 +15,15 @@ impl HeaderProvider for ProviderImpl { Ok(None) } } + + fn header_td(&self, hash: &BlockHash) -> Result> { + if let Some(num) = self.db.view(|tx| tx.get::(*hash))?? { + let td = self.db.view(|tx| tx.get::((num, *hash).into()))??; + Ok(td.map(|v| v.0)) + } else { + Ok(None) + } + } } impl BlockProvider for ProviderImpl { diff --git a/crates/storage/provider/src/test_utils/api.rs b/crates/storage/provider/src/test_utils/api.rs index 6637ab7d7b..7fa4bad223 100644 --- a/crates/storage/provider/src/test_utils/api.rs +++ b/crates/storage/provider/src/test_utils/api.rs @@ -39,4 +39,8 @@ impl HeaderProvider for TestApi { fn header_by_number(&self, _num: u64) -> Result> { Ok(None) } + + fn header_td(&self, _hash: &BlockHash) -> Result> { + Ok(None) + } }