Skip to content

Commit

Permalink
Merge pull request #41 from bitfinity-network/chore/add-certified
Browse files Browse the repository at this point in the history
chore: add `ic_getLastCertifiedBlock` endpoint
  • Loading branch information
blutooth authored Oct 28, 2024
2 parents 06cb28e + 7ff2c95 commit 277c4b8
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 65 deletions.
176 changes: 122 additions & 54 deletions bin/reth/tests/commands/bitfinity_node_it.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ use super::utils::*;
use did::keccak;
use eth_server::{EthImpl, EthServer};
use ethereum_json_rpc_client::{reqwest::ReqwestClient, EthJsonRpcClient};
use ethereum_json_rpc_client::{Block, CertifiedResult, H256};
use jsonrpsee::{
server::{Server, ServerHandle},
Methods, RpcModule,
};
use rand::RngCore;
use reth::{args::{DatadirArgs, RpcServerArgs}, dirs::{DataDirPath, MaybePlatformPath}};
use reth::{
args::{DatadirArgs, RpcServerArgs},
dirs::{DataDirPath, MaybePlatformPath},
};
use reth_consensus::Consensus;
use reth_db::{init_db, test_utils::tempdir_path};
use reth_node_builder::{NodeBuilder, NodeConfig, NodeHandle};
Expand All @@ -20,7 +24,6 @@ use reth_tasks::TaskManager;
use revm_primitives::{Address, U256};
use std::{net::SocketAddr, str::FromStr, sync::Arc};


#[tokio::test]
async fn bitfinity_test_should_start_local_reth_node() {
// Arrange
Expand All @@ -31,6 +34,36 @@ async fn bitfinity_test_should_start_local_reth_node() {
assert!(reth_client.get_chain_id().await.is_ok());
}

#[tokio::test]
async fn bitfinity_test_node_forward_ic_or_eth_get_last_certified_block() {
// Arrange
let _log = init_logs();

let eth_server = EthImpl::new();
let (_server, eth_server_address) =
mock_eth_server_start(EthServer::into_rpc(eth_server)).await;
let (reth_client, _reth_node) =
start_reth_node(Some(format!("http://{}", eth_server_address)), None).await;

// Act
let result = reth_client.get_last_certified_block().await;

// Assert
assert!(result.is_ok());

// Try with `ic_getLastCertifiedBlock` alias
let result: CertifiedResult<Block<H256>> = reth_client
.single_request(
"ic_getLastCertifiedBlock".to_owned(),
ethereum_json_rpc_client::Params::None,
ethereum_json_rpc_client::Id::Num(1),
)
.await
.unwrap();

assert!(!result.certificate.is_empty());
}

#[tokio::test]
async fn bitfinity_test_node_forward_get_gas_price_requests() {
// Arrange
Expand Down Expand Up @@ -81,9 +114,14 @@ async fn bitfinity_test_node_forward_eth_get_genesis_balances() {
start_reth_node(Some(format!("http://{}", eth_server_address)), None).await;

// Act
let result: Vec<(ethereum_json_rpc_client::H160, ethereum_json_rpc_client::U256)> =
reth_client.single_request("eth_getGenesisBalances".to_owned(),
ethereum_json_rpc_client::Params::None, ethereum_json_rpc_client::Id::Num(1)).await.unwrap();
let result: Vec<(ethereum_json_rpc_client::H160, ethereum_json_rpc_client::U256)> = reth_client
.single_request(
"eth_getGenesisBalances".to_owned(),
ethereum_json_rpc_client::Params::None,
ethereum_json_rpc_client::Id::Num(1),
)
.await
.unwrap();

// Assert
assert_eq!(result.len(), 3);
Expand All @@ -96,7 +134,6 @@ async fn bitfinity_test_node_forward_eth_get_genesis_balances() {

assert_eq!(did::H160::from(result[2].0), Address::from_slice(&[3u8; 20]).into());
assert_eq!(did::U256::from(result[2].1), U256::from(30).into());

}

#[tokio::test]
Expand Down Expand Up @@ -124,7 +161,6 @@ async fn bitfinity_test_node_forward_ic_get_genesis_balances() {

assert_eq!(did::H160::from(result[2].0), Address::from_slice(&[3u8; 20]).into());
assert_eq!(did::U256::from(result[2].1), U256::from(30).into());

}

#[tokio::test]
Expand Down Expand Up @@ -154,54 +190,86 @@ async fn bitfinity_test_node_forward_send_raw_transaction_requests() {
/// Start a local reth node
async fn start_reth_node(
bitfinity_evm_url: Option<String>,
import_data: Option<ImportData>) -> (
import_data: Option<ImportData>,
) -> (
EthJsonRpcClient<ReqwestClient>,
NodeHandle<reth_node_builder::NodeAdapter<reth_node_api::FullNodeTypesAdapter<EthereumNode, Arc<reth_db::DatabaseEnv>, reth_provider::providers::BlockchainProvider<Arc<reth_db::DatabaseEnv>>>, reth_node_builder::components::Components<reth_node_api::FullNodeTypesAdapter<EthereumNode, Arc<reth_db::DatabaseEnv>, reth_provider::providers::BlockchainProvider<Arc<reth_db::DatabaseEnv>>>, reth_transaction_pool::Pool<reth_transaction_pool::TransactionValidationTaskExecutor<reth_transaction_pool::EthTransactionValidator<reth_provider::providers::BlockchainProvider<Arc<reth_db::DatabaseEnv>>, reth_transaction_pool::EthPooledTransaction>>, reth_transaction_pool::CoinbaseTipOrdering<reth_transaction_pool::EthPooledTransaction>, reth_transaction_pool::blobstore::DiskFileBlobStore>, reth_node_ethereum::EthEvmConfig, reth_node_ethereum::EthExecutorProvider, Arc<dyn Consensus>>>>,
) {

let tasks = TaskManager::current();

// create node config
let mut node_config =
NodeConfig::test().dev().with_rpc(RpcServerArgs::default().with_http()).with_unused_ports();
node_config.dev.dev = false;

let mut chain = node_config.chain.as_ref().clone();
chain.bitfinity_evm_url = bitfinity_evm_url;
let mut node_config = node_config.with_chain(chain);

let database = if let Some(import_data) = import_data {
let data_dir = MaybePlatformPath::<DataDirPath>::from_str(import_data.data_dir.data_dir().to_str().unwrap()).unwrap();
let mut data_dir_args = node_config.datadir.clone();
data_dir_args.datadir = data_dir;
data_dir_args.static_files_path = Some(import_data.data_dir.static_files());
node_config = node_config.with_datadir_args(data_dir_args);
node_config = node_config.with_chain(import_data.chain.clone());
import_data.database
} else {
let path = MaybePlatformPath::<DataDirPath>::from(tempdir_path());
node_config = node_config
.with_datadir_args(DatadirArgs { datadir: path.clone(), ..Default::default() });
let data_dir =
path.unwrap_or_chain_default(node_config.chain.chain, node_config.datadir.clone());
Arc::new(init_db(data_dir.db(), Default::default()).unwrap())
};

let node_handle = NodeBuilder::new(node_config)
.with_database(database)
.with_launch_context(tasks.executor())
.launch_node(EthereumNode::default())
.await
.unwrap();

let reth_address = node_handle.node.rpc_server_handle().http_local_addr().unwrap();

let client: EthJsonRpcClient<ReqwestClient> =
EthJsonRpcClient::new(ReqwestClient::new(format!("http://{}", reth_address)));

(client, node_handle)

}
NodeHandle<
reth_node_builder::NodeAdapter<
reth_node_api::FullNodeTypesAdapter<
EthereumNode,
Arc<reth_db::DatabaseEnv>,
reth_provider::providers::BlockchainProvider<Arc<reth_db::DatabaseEnv>>,
>,
reth_node_builder::components::Components<
reth_node_api::FullNodeTypesAdapter<
EthereumNode,
Arc<reth_db::DatabaseEnv>,
reth_provider::providers::BlockchainProvider<Arc<reth_db::DatabaseEnv>>,
>,
reth_transaction_pool::Pool<
reth_transaction_pool::TransactionValidationTaskExecutor<
reth_transaction_pool::EthTransactionValidator<
reth_provider::providers::BlockchainProvider<Arc<reth_db::DatabaseEnv>>,
reth_transaction_pool::EthPooledTransaction,
>,
>,
reth_transaction_pool::CoinbaseTipOrdering<
reth_transaction_pool::EthPooledTransaction,
>,
reth_transaction_pool::blobstore::DiskFileBlobStore,
>,
reth_node_ethereum::EthEvmConfig,
reth_node_ethereum::EthExecutorProvider,
Arc<dyn Consensus>,
>,
>,
>,
) {
let tasks = TaskManager::current();

// create node config
let mut node_config =
NodeConfig::test().dev().with_rpc(RpcServerArgs::default().with_http()).with_unused_ports();
node_config.dev.dev = false;

let mut chain = node_config.chain.as_ref().clone();
chain.bitfinity_evm_url = bitfinity_evm_url;
let mut node_config = node_config.with_chain(chain);

let database = if let Some(import_data) = import_data {
let data_dir = MaybePlatformPath::<DataDirPath>::from_str(
import_data.data_dir.data_dir().to_str().unwrap(),
)
.unwrap();
let mut data_dir_args = node_config.datadir.clone();
data_dir_args.datadir = data_dir;
data_dir_args.static_files_path = Some(import_data.data_dir.static_files());
node_config = node_config.with_datadir_args(data_dir_args);
node_config = node_config.with_chain(import_data.chain.clone());
import_data.database
} else {
let path = MaybePlatformPath::<DataDirPath>::from(tempdir_path());
node_config = node_config
.with_datadir_args(DatadirArgs { datadir: path.clone(), ..Default::default() });
let data_dir =
path.unwrap_or_chain_default(node_config.chain.chain, node_config.datadir.clone());
Arc::new(init_db(data_dir.db(), Default::default()).unwrap())
};

let node_handle = NodeBuilder::new(node_config)
.with_database(database)
.with_launch_context(tasks.executor())
.launch_node(EthereumNode::default())
.await
.unwrap();

let reth_address = node_handle.node.rpc_server_handle().http_local_addr().unwrap();

let client: EthJsonRpcClient<ReqwestClient> =
EthJsonRpcClient::new(ReqwestClient::new(format!("http://{}", reth_address)));

(client, node_handle)
}

/// Start a local Eth server.
/// Reth requests will be forwarded to this server
Expand Down
7 changes: 3 additions & 4 deletions crates/net/downloaders/src/bitfinity_evm_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ use tracing::{debug, error, info, trace, warn};
///
/// Blocks are assumed to have populated transactions, so reading headers will also buffer
/// transactions in memory for use in the bodies stage.

#[derive(Debug)]
pub struct BitfinityEvmClient {
/// The buffered headers retrieved when fetching new bodies.
Expand Down Expand Up @@ -75,7 +74,7 @@ pub struct CertificateCheckSettings {
}

impl BitfinityEvmClient {
/// BitfinityEvmClient from rpc url
/// `BitfinityEvmClient` from rpc url
pub async fn from_rpc_url(
rpc: &str,
start_block: u64,
Expand Down Expand Up @@ -247,7 +246,7 @@ impl BitfinityEvmClient {
let spec = ChainSpec {
chain,
genesis_hash: genesis_block.hash.map(|h| h.0.into()),
genesis: genesis.clone(),
genesis,
paris_block_and_final_difficulty: Some((0, Uint::ZERO)),
hardforks: ChainHardforks::new(vec![
(EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)),
Expand Down Expand Up @@ -366,7 +365,7 @@ impl BlockCertificateChecker {
}

fn validate_tree(canister_id: &[u8], certificate: &Certificate, tree: &HashTree) -> bool {
let certified_data_path = ["canister".as_bytes(), canister_id, "certified_data".as_bytes()];
let certified_data_path = [b"canister", canister_id, b"certified_data"];

let witness = match certificate.tree.lookup_path(&certified_data_path) {
LookupResult::Found(witness) => witness,
Expand Down
2 changes: 1 addition & 1 deletion crates/net/downloaders/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ pub mod test_utils;

/// Module managing remote data retrieval and buffering.
///
/// Contains [RemoteClient](remote_client::RemoteClient) to read block data from remote sources,
/// Contains [`RemoteClient`](remote_client::RemoteClient) to read block data from remote sources,
/// efficiently buffering headers and bodies for retrieval.
pub mod bitfinity_evm_client;
28 changes: 22 additions & 6 deletions crates/rpc/rpc-eth-api/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! the `eth_` namespace.

use alloy_dyn_abi::TypedData;
use ethereum_json_rpc_client::CertifiedResult;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64};
use reth_rpc_eth_types::EthApiError;
Expand All @@ -16,7 +17,8 @@ use reth_rpc_types::{
use tracing::trace;

use crate::helpers::{
bitfinity_evm_rpc::BitfinityEvmRpc, EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadReceipt, Trace
bitfinity_evm_rpc::BitfinityEvmRpc, EthApiSpec, EthBlocks, EthCall, EthFees, EthState,
EthTransactions, LoadReceipt, Trace,
};

/// Eth rpc interface: <https://ethereum.github.io/execution-apis/api-documentation/>
Expand Down Expand Up @@ -321,10 +323,16 @@ pub trait EthApi {
) -> RpcResult<EIP1186AccountProofResponse>;

/// Aliases don't use the namespace.
/// Thus, this will generate `eth_getGenesisBalances` and `ic_getGenesisBalances`.
#[method(name = "getGenesisBalances", aliases = ["ic_getGenesisBalances"])]
async fn get_genesis_balances(&self) -> RpcResult<Vec<(Address, U256)>>;
/// Thus, this will generate `eth_getGenesisBalances` and `ic_getGenesisBalances`.
#[method(name = "getGenesisBalances", aliases = ["ic_getGenesisBalances"])]
async fn get_genesis_balances(&self) -> RpcResult<Vec<(Address, U256)>>;

/// Aliases don't use the namespace.
/// Thus, this will generate `eth_getLastCertifiedBlock` and `ic_getLastCertifiedBlock`.
#[method(name = "getLastCertifiedBlock", aliases = ["ic_getLastCertifiedBlock"])]
async fn get_last_certified_block(
&self,
) -> RpcResult<CertifiedResult<ethereum_json_rpc_client::Block<ethereum_json_rpc_client::H256>>>;
}

#[async_trait::async_trait]
Expand Down Expand Up @@ -633,7 +641,7 @@ where
/// Handler for: `eth_blobBaseFee`
async fn blob_base_fee(&self) -> RpcResult<U256> {
trace!(target: "rpc::eth", "Serving eth_blobBaseFee");
return Ok(EthFees::blob_base_fee(self).await?)
return Ok(EthFees::blob_base_fee(self).await?);
}

// FeeHistory is calculated based on lazy evaluation of fees for historical blocks, and further
Expand All @@ -654,7 +662,7 @@ where
trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory");
return Ok(
EthFees::fee_history(self, block_count.to(), newest_block, reward_percentiles).await?
)
);
}

/// Handler for: `eth_mining`
Expand Down Expand Up @@ -740,4 +748,12 @@ where
trace!(target: "rpc::eth", "Serving eth_getGenesisBalances/ic_getGenesisBalances");
BitfinityEvmRpc::get_genesis_balances(self).await
}

async fn get_last_certified_block(
&self,
) -> RpcResult<CertifiedResult<ethereum_json_rpc_client::Block<ethereum_json_rpc_client::H256>>>
{
trace!(target: "rpc::eth", "Serving get_last_certified_block");
BitfinityEvmRpc::get_last_certified_block(self).await
}
}
20 changes: 20 additions & 0 deletions crates/rpc/rpc-eth-api/src/helpers/bitfinity_evm_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::sync::Arc;

use ethereum_json_rpc_client::{reqwest::ReqwestClient, EthJsonRpcClient};
use ethereum_json_rpc_client::{Block, CertifiedResult, H256};
use futures::Future;
use jsonrpsee::core::RpcResult;
use reth_chainspec::ChainSpec;
Expand Down Expand Up @@ -84,6 +85,25 @@ pub trait BitfinityEvmRpc {
.collect())
}
}

/// Forwards `ic_getLastCertifiedBlock` calls to the Bitfinity EVM
fn get_last_certified_block(
&self,
) -> impl Future<Output = RpcResult<CertifiedResult<Block<H256>>>> + Send {
let chain_spec = self.chain_spec();
async move {
let (rpc_url, client) = get_client(&chain_spec)?;

let certified_block = client.get_last_certified_block().await.map_err(|e| {
internal_rpc_err(format!(
"failed to forward get_last_certified_block request to {}: {}",
rpc_url, e
))
})?;

Ok(certified_block)
}
}
}

/// Returns a client for the Bitfinity EVM RPC.
Expand Down

0 comments on commit 277c4b8

Please sign in to comment.