diff --git a/crates/node-interaction/src/lib.rs b/crates/node-interaction/src/lib.rs index fdb21d8..284f932 100644 --- a/crates/node-interaction/src/lib.rs +++ b/crates/node-interaction/src/lib.rs @@ -1,6 +1,6 @@ //! This crate implements all node interactions. -use alloy::primitives::{Address, ChainId, U256}; +use alloy::primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, ChainId, U256}; use alloy::rpc::types::trace::geth::{DiffMode, GethTrace}; use alloy::rpc::types::{TransactionReceipt, TransactionRequest}; use anyhow::Result; @@ -35,4 +35,13 @@ pub trait EthereumNode { /// Returns the difficulty of the last block. fn block_difficulty(&self) -> Result; + + /// Returns the hash of the last block. + fn block_hash(&self) -> Result; + + /// Returns the timestamp of the last block, + fn block_timestamp(&self) -> Result; + + /// Returns the number of the last block. + fn last_block_number(&self) -> Result; } diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index 3c4a54d..02abf62 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -16,7 +16,7 @@ use std::{ use alloy::{ eips::BlockNumberOrTag, network::{Ethereum, EthereumWallet}, - primitives::{Address, U256}, + primitives::{Address, BlockHash, BlockNumber, BlockTimestamp, U256}, providers::{ Provider, ProviderBuilder, ext::DebugApi, @@ -400,6 +400,40 @@ impl EthereumNode for Instance { .map(|block| block.header.difficulty) })? } + + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + fn block_hash(&self) -> anyhow::Result { + let provider = self.provider(); + BlockingExecutor::execute(async move { + provider + .await? + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .map(|block| block.header.hash) + })? + } + + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + fn block_timestamp(&self) -> anyhow::Result { + let provider = self.provider(); + BlockingExecutor::execute(async move { + provider + .await? + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .map(|block| block.header.timestamp) + })? + } + + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + fn last_block_number(&self) -> anyhow::Result { + let provider = self.provider(); + BlockingExecutor::execute(async move { + provider.await?.get_block_number().await.map_err(Into::into) + })? + } } impl Node for Instance { @@ -581,4 +615,41 @@ mod tests { let block_difficulty = block_difficulty.expect("Failed to get the block difficulty"); assert_eq!(block_difficulty, U256::ZERO) } + + #[test] + fn can_get_block_hash_from_node() { + // Arrange + let (node, _temp_dir) = new_node(); + + // Act + let block_hash = node.block_hash(); + + // Assert + let _ = block_hash.expect("Failed to get the block hash"); + } + + #[test] + fn can_get_block_timestamp_from_node() { + // Arrange + let (node, _temp_dir) = new_node(); + + // Act + let block_timestamp = node.block_timestamp(); + + // Assert + let _ = block_timestamp.expect("Failed to get the block timestamp"); + } + + #[test] + fn can_get_block_number_from_node() { + // Arrange + let (node, _temp_dir) = new_node(); + + // Act + let block_number = node.last_block_number(); + + // Assert + let block_number = block_number.expect("Failed to get the block number"); + assert_eq!(block_number, 0) + } } diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index 957eec8..b32464a 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -19,7 +19,7 @@ use alloy::{ Ethereum, EthereumWallet, Network, TransactionBuilder, TransactionBuilderError, UnbuiltTransactionError, }, - primitives::{Address, B64, B256, BlockNumber, Bloom, Bytes, U256}, + primitives::{Address, B64, B256, BlockHash, BlockNumber, BlockTimestamp, Bloom, Bytes, U256}, providers::{ Provider, ProviderBuilder, ext::DebugApi, @@ -476,6 +476,40 @@ impl EthereumNode for KitchensinkNode { .map(|block| block.header.difficulty) })? } + + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + fn block_hash(&self) -> anyhow::Result { + let provider = self.provider(); + BlockingExecutor::execute(async move { + provider + .await? + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .map(|block| block.header.hash) + })? + } + + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + fn block_timestamp(&self) -> anyhow::Result { + let provider = self.provider(); + BlockingExecutor::execute(async move { + provider + .await? + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or(anyhow::Error::msg("Blockchain has no blocks")) + .map(|block| block.header.timestamp) + })? + } + + #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] + fn last_block_number(&self) -> anyhow::Result { + let provider = self.provider(); + BlockingExecutor::execute(async move { + provider.await?.get_block_number().await.map_err(Into::into) + })? + } } impl Node for KitchensinkNode { @@ -1254,4 +1288,41 @@ mod tests { let block_difficulty = block_difficulty.expect("Failed to get the block difficulty"); assert_eq!(block_difficulty, U256::ZERO) } + + #[test] + fn can_get_block_hash_from_node() { + // Arrange + let (node, _temp_dir) = new_node(); + + // Act + let block_hash = node.block_hash(); + + // Assert + let _ = block_hash.expect("Failed to get the block hash"); + } + + #[test] + fn can_get_block_timestamp_from_node() { + // Arrange + let (node, _temp_dir) = new_node(); + + // Act + let block_timestamp = node.block_timestamp(); + + // Assert + let _ = block_timestamp.expect("Failed to get the block timestamp"); + } + + #[test] + fn can_get_block_number_from_node() { + // Arrange + let (node, _temp_dir) = new_node(); + + // Act + let block_number = node.last_block_number(); + + // Assert + let block_number = block_number.expect("Failed to get the block number"); + assert_eq!(block_number, 0) + } }