Skip to content

Commit

Permalink
feat: add rpc endpoints eth_getBlockByNumber & eth_getBlockByHash (
Browse files Browse the repository at this point in the history
…#145)

Based on #150, please merge it first

**Motivation**

Comply with rpc spec for `eth_getBlockByNumber` & `eth_getBlockByHash`

<!-- Why does this pull request exist? What are its goals? -->

**Description**
* Integrate `Store` as a state for the rpc api

* Add RPC endpoints `eth_getBlockByNumber` & `eth_getBlockByHash`

<!-- A clear and concise general description of the changes this PR
introduces -->

<!-- Link to issues: Resolves #111, Resolves #222 -->

Closes #31
  • Loading branch information
fmoletta authored Jul 18, 2024
1 parent 822d8b6 commit 5820059
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 15 deletions.
2 changes: 2 additions & 0 deletions crates/core/src/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use std::cmp::{max, Ordering};

use super::Transaction;

pub use serializable::BlockSerializable;

pub type BlockNumber = u64;
pub type BlockHash = H256;

Expand Down
1 change: 1 addition & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
ethereum_rust-core.workspace = true
ethereum_rust-storage.workspace = true
118 changes: 116 additions & 2 deletions crates/rpc/src/eth/block.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,121 @@
use std::fmt::Display;

use ethereum_rust_storage::Store;
use serde::Deserialize;
use serde_json::Value;
use tracing::info;

use crate::utils::RpcErr;
use ethereum_rust_core::types::{BlockHash, BlockSerializable};

pub struct GetBlockByNumberRequest {
pub block: BlockIdentifier,
pub hydrated: bool,
}

pub struct GetBlockByHashRequest {
pub block: BlockHash,
pub hydrated: bool,
}

#[derive(Deserialize)]
pub enum BlockIdentifier {
Number(u64),
#[allow(unused)]
Tag(BlockTag),
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BlockTag {
Earliest,
Finalized,
Safe,
Latest,
Pending,
}

impl GetBlockByNumberRequest {
pub fn parse(params: &Option<Vec<Value>>) -> Option<GetBlockByNumberRequest> {
let params = params.as_ref()?;
if params.len() != 2 {
return None;
};
Some(GetBlockByNumberRequest {
block: serde_json::from_value(params[0].clone()).ok()?,
hydrated: serde_json::from_value(params[1].clone()).ok()?,
})
}
}

impl GetBlockByHashRequest {
pub fn parse(params: &Option<Vec<Value>>) -> Option<GetBlockByHashRequest> {
let params = params.as_ref()?;
if params.len() != 2 {
return None;
};
Some(GetBlockByHashRequest {
block: serde_json::from_value(params[0].clone()).ok()?,
hydrated: serde_json::from_value(params[1].clone()).ok()?,
})
}
}

pub fn get_block_by_number(
request: &GetBlockByNumberRequest,
storage: Store,
) -> Result<Value, RpcErr> {
info!("Requested block with number: {}", request.block);
let block_number = match request.block {
BlockIdentifier::Tag(_) => unimplemented!("Obtain block number from tag"),
BlockIdentifier::Number(block_number) => block_number,
};
let header = storage.get_block_header(block_number);
let body = storage.get_block_body(block_number);
let (header, body) = match (header, body) {
(Ok(Some(header)), Ok(Some(body))) => (header, body),
// Block not found
(Ok(_), Ok(_)) => return Ok(Value::Null),
// DB error
_ => return Err(RpcErr::Internal),
};
let block = BlockSerializable::from_block(header, body, request.hydrated);

serde_json::to_value(&block).map_err(|_| RpcErr::Internal)
}

pub fn get_block_by_hash(request: &GetBlockByHashRequest, storage: Store) -> Result<Value, RpcErr> {
info!("Requested block with hash: {}", request.block);
let block_number = match storage.get_block_number(request.block) {
Ok(Some(number)) => number,
Ok(_) => return Ok(Value::Null),
_ => return Err(RpcErr::Internal),
};
let header = storage.get_block_header(block_number);
let body = storage.get_block_body(block_number);
let (header, body) = match (header, body) {
(Ok(Some(header)), Ok(Some(body))) => (header, body),
// Block not found
(Ok(_), Ok(_)) => return Ok(Value::Null),
// DB error
_ => return Err(RpcErr::Internal),
};
let block = BlockSerializable::from_block(header, body, request.hydrated);

serde_json::to_value(&block).map_err(|_| RpcErr::Internal)
}

pub fn get_block_by_number() -> Result<Value, RpcErr> {
Ok(Value::Null)
impl Display for BlockIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BlockIdentifier::Number(num) => num.fmt(f),
BlockIdentifier::Tag(tag) => match tag {
BlockTag::Earliest => "Earliest".fmt(f),
BlockTag::Finalized => "Finalized".fmt(f),
BlockTag::Safe => "Safe".fmt(f),
BlockTag::Latest => "Latest".fmt(f),
BlockTag::Pending => "Pending".fmt(f),
},
}
}
}
41 changes: 29 additions & 12 deletions crates/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::{future::IntoFuture, net::SocketAddr};

use axum::{routing::post, Json, Router};
use engine::{ExchangeCapabilitiesRequest, NewPayloadV3Request};
use eth::{block, client};
use eth::{
block::{self, GetBlockByHashRequest, GetBlockByNumberRequest},
client,
};
use serde_json::Value;
use tokio::net::TcpListener;
use tracing::info;
Expand All @@ -13,11 +16,18 @@ mod engine;
mod eth;
mod utils;

pub async fn start_api(http_addr: SocketAddr, authrpc_addr: SocketAddr) {
let http_router = Router::new().route("/", post(handle_http_request));
use axum::extract::State;
use ethereum_rust_storage::Store;

pub async fn start_api(http_addr: SocketAddr, authrpc_addr: SocketAddr, storage: Store) {
let http_router = Router::new()
.route("/", post(handle_http_request))
.with_state(storage.clone());
let http_listener = TcpListener::bind(http_addr).await.unwrap();

let authrpc_router = Router::new().route("/", post(handle_authrpc_request));
let authrpc_router = Router::new()
.route("/", post(handle_authrpc_request))
.with_state(storage);
let authrpc_listener = TcpListener::bind(authrpc_addr).await.unwrap();

let authrpc_server = axum::serve(authrpc_listener, authrpc_router)
Expand All @@ -40,23 +50,23 @@ async fn shutdown_signal() {
.expect("failed to install Ctrl+C handler");
}

pub async fn handle_authrpc_request(body: String) -> Json<Value> {
pub async fn handle_authrpc_request(State(storage): State<Store>, body: String) -> Json<Value> {
let req: RpcRequest = serde_json::from_str(&body).unwrap();
let res = match map_requests(&req) {
let res = match map_requests(&req, storage.clone()) {
res @ Ok(_) => res,
_ => map_internal_requests(&req),
_ => map_internal_requests(&req, storage),
};
rpc_response(req.id, res)
}

pub async fn handle_http_request(body: String) -> Json<Value> {
pub async fn handle_http_request(State(storage): State<Store>, body: String) -> Json<Value> {
let req: RpcRequest = serde_json::from_str(&body).unwrap();
let res = map_requests(&req);
let res = map_requests(&req, storage);
rpc_response(req.id, res)
}

/// Handle requests that can come from either clients or other users
pub fn map_requests(req: &RpcRequest) -> Result<Value, RpcErr> {
pub fn map_requests(req: &RpcRequest, storage: Store) -> Result<Value, RpcErr> {
match req.method.as_str() {
"engine_exchangeCapabilities" => {
let capabilities: ExchangeCapabilitiesRequest = req
Expand All @@ -70,7 +80,14 @@ pub fn map_requests(req: &RpcRequest) -> Result<Value, RpcErr> {
}
"eth_chainId" => client::chain_id(),
"eth_syncing" => client::syncing(),
"eth_getBlockByNumber" => block::get_block_by_number(),
"eth_getBlockByNumber" => {
let request = GetBlockByNumberRequest::parse(&req.params).ok_or(RpcErr::BadParams)?;
block::get_block_by_number(&request, storage)
}
"eth_getBlockByHash" => {
let request = GetBlockByHashRequest::parse(&req.params).ok_or(RpcErr::BadParams)?;
block::get_block_by_hash(&request, storage)
}
"engine_forkchoiceUpdatedV3" => engine::forkchoice_updated_v3(),
"engine_newPayloadV3" => {
let request =
Expand All @@ -83,7 +100,7 @@ pub fn map_requests(req: &RpcRequest) -> Result<Value, RpcErr> {
}

/// Handle requests from other clients
pub fn map_internal_requests(_req: &RpcRequest) -> Result<Value, RpcErr> {
pub fn map_internal_requests(_req: &RpcRequest, _storage: Store) -> Result<Value, RpcErr> {
Err(RpcErr::MethodNotFound)
}

Expand Down
5 changes: 5 additions & 0 deletions crates/rpc/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub enum RpcErr {
MethodNotFound,
BadParams,
UnsuportedFork,
Internal,
}

impl From<RpcErr> for RpcErrorMetadata {
Expand All @@ -22,6 +23,10 @@ impl From<RpcErr> for RpcErrorMetadata {
code: -38005,
message: "Unsupported fork".to_string(),
},
RpcErr::Internal => RpcErrorMetadata {
code: -32603,
message: "Internal Error".to_string(),
},
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion ethereum_rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use ethereum_rust_core::types::Genesis;
use ethereum_rust_net::bootnode::BootNode;
use ethereum_rust_storage::{EngineType, Store};
use std::{
io::{self, BufReader},
net::{SocketAddr, ToSocketAddrs},
Expand Down Expand Up @@ -71,7 +72,8 @@ async fn main() {

let _genesis = read_genesis_file(genesis_file_path);

let rpc_api = ethereum_rust_rpc::start_api(http_socket_addr, authrpc_socket_addr);
let storage = Store::new("storage.db", EngineType::InMemory).unwrap();
let rpc_api = ethereum_rust_rpc::start_api(http_socket_addr, authrpc_socket_addr, storage);
let networking = ethereum_rust_net::start_network(udp_socket_addr, tcp_socket_addr, bootnodes);

try_join!(tokio::spawn(rpc_api), tokio::spawn(networking)).unwrap();
Expand Down

0 comments on commit 5820059

Please sign in to comment.