Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
417 changes: 384 additions & 33 deletions crates/rpc/src/debug/endpoints.rs

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion crates/rpc/src/debug/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ pub enum DebugError {
/// Transaction not found.
#[error("transaction not found: {0}")]
TransactionNotFound(B256),
/// RLP decoding failed (malformed input).
#[error("RLP decode: {0}")]
RlpDecode(String),
/// Transaction sender recovery failed.
#[error("sender recovery failed")]
SenderRecovery,
}

impl ajj::IntoErrorPayload for DebugError {
Expand All @@ -42,9 +48,10 @@ impl ajj::IntoErrorPayload for DebugError {
match self {
Self::Cold(_) | Self::Hot(_) | Self::EvmHalt { .. } => -32000,
Self::Resolve(r) => crate::eth::error::resolve_error_code(r),
Self::InvalidTracerConfig => -32602,
Self::InvalidTracerConfig | Self::RlpDecode(_) => -32602,
Self::Unsupported(_) => -32601,
Self::BlockNotFound(_) | Self::TransactionNotFound(_) => -32001,
Self::SenderRecovery => -32000,
}
}

Expand All @@ -57,10 +64,32 @@ impl ajj::IntoErrorPayload for DebugError {
Self::EvmHalt { reason } => format!("execution halted: {reason}").into(),
Self::BlockNotFound(id) => format!("block not found: {id}").into(),
Self::TransactionNotFound(h) => format!("transaction not found: {h}").into(),
Self::RlpDecode(msg) => format!("RLP decode error: {msg}").into(),
Self::SenderRecovery => "sender recovery failed".into(),
}
}

fn error_data(self) -> Option<Self::ErrData> {
None
}
}

#[cfg(test)]
mod tests {
use super::DebugError;

#[test]
fn rlp_decode_error_code() {
use ajj::IntoErrorPayload;
let err = DebugError::RlpDecode("invalid block RLP".into());
assert_eq!(err.error_code(), -32602);
assert!(err.error_message().contains("RLP"));
}

#[test]
fn sender_recovery_error_code() {
use ajj::IntoErrorPayload;
let err = DebugError::SenderRecovery;
assert_eq!(err.error_code(), -32000);
}
}
11 changes: 10 additions & 1 deletion crates/rpc/src/debug/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Debug namespace RPC router backed by storage.

mod endpoints;
use endpoints::{trace_block, trace_transaction};
use endpoints::{
debug_trace_call, get_raw_block, get_raw_header, get_raw_receipts, get_raw_transaction,
trace_block, trace_block_rlp, trace_transaction,
};
mod error;
pub use error::DebugError;
pub(crate) mod tracer;
Expand All @@ -22,4 +25,10 @@ where
.route("traceBlockByNumber", trace_block::<BlockNumberOrTag, H>)
.route("traceBlockByHash", trace_block::<B256, H>)
.route("traceTransaction", trace_transaction::<H>)
.route("traceBlock", trace_block_rlp::<H>)
.route("getRawBlock", get_raw_block::<H>)
.route("getRawHeader", get_raw_header::<H>)
.route("getRawReceipts", get_raw_receipts::<H>)
.route("getRawTransaction", get_raw_transaction::<H>)
.route("traceCall", debug_trace_call::<H>)
}
8 changes: 8 additions & 0 deletions crates/rpc/src/eth/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ pub(crate) async fn uncle_block() -> Result<Option<()>, ()> {
Ok(None)
}

/// `eth_protocolVersion` — returns the Ethereum wire protocol version.
///
/// Signet does not implement devp2p. Returns a fixed value corresponding
/// to eth/68.
pub(crate) async fn protocol_version() -> Result<String, ()> {
Ok("0x44".to_owned())
}

// ---------------------------------------------------------------------------
// Simple Queries
// ---------------------------------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions crates/rpc/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod endpoints;
use endpoints::{
addr_tx_count, balance, block, block_number, block_receipts, block_tx_count, call, chain_id,
code_at, create_access_list, estimate_gas, fee_history, gas_price, get_filter_changes,
get_logs, header_by, max_priority_fee_per_gas, new_block_filter, new_filter,
get_logs, header_by, max_priority_fee_per_gas, new_block_filter, new_filter, protocol_version,
raw_transaction_by_block_and_index, raw_transaction_by_hash, send_raw_transaction, storage_at,
subscribe, syncing, transaction_by_block_and_index, transaction_by_hash, transaction_receipt,
uncle_block, uncle_count, uninstall_filter, unsubscribe,
Expand Down Expand Up @@ -79,8 +79,9 @@ where
.route("getUncleCountByBlockNumber", uncle_count)
.route("getUncleByBlockHashAndIndex", uncle_block)
.route("getUncleByBlockNumberAndIndex", uncle_block)
.route("protocolVersion", protocol_version)
// Unsupported methods (return method_not_found by default):
// - protocolVersion, coinbase, accounts, blobBaseFee
// - coinbase, accounts, blobBaseFee
// - getWork, hashrate, mining, submitHashrate, submitWork
// - sendTransaction, sign, signTransaction, signTypedData
// - getProof, newPendingTransactionFilter
Expand Down
9 changes: 7 additions & 2 deletions crates/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ pub use debug::DebugError;
mod signet;
pub use signet::error::SignetError;

mod net;
mod web3;

pub mod serve;
pub use serve::{RpcServerGuard, ServeConfig, ServeConfigEnv, ServeError};

/// Instantiate a combined router with `eth`, `debug`, and `signet`
/// namespaces.
/// Instantiate a combined router with `eth`, `debug`, `signet`, `web3`, and
/// `net` namespaces.
pub fn router<H>() -> ajj::Router<StorageRpcCtx<H>>
where
H: signet_hot::HotKv + Send + Sync + 'static,
Expand All @@ -42,4 +45,6 @@ where
.nest("eth", eth::eth())
.nest("debug", debug::debug())
.nest("signet", signet::signet())
.nest("web3", web3::web3())
.nest("net", net::net())
}
24 changes: 24 additions & 0 deletions crates/rpc/src/net/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! `net` namespace RPC handlers.

use crate::config::StorageRpcCtx;
use signet_hot::{HotKv, model::HotKvRead};
use trevm::revm::database::DBErrorMarker;

/// Instantiate the `net` API router.
pub(crate) fn net<H>() -> ajj::Router<StorageRpcCtx<H>>
where
H: HotKv + Send + Sync + 'static,
<H::RoTx as HotKvRead>::Error: DBErrorMarker,
{
ajj::Router::new().route("version", version::<H>).route("listening", listening)
}

/// `net_version` — returns the chain ID as a decimal string.
pub(crate) async fn version<H: HotKv>(ctx: StorageRpcCtx<H>) -> Result<String, ()> {
Ok(ctx.chain_id().to_string())
}

/// `net_listening` — always returns true (the server is listening).
pub(crate) async fn listening() -> Result<bool, ()> {
Ok(true)
}
50 changes: 50 additions & 0 deletions crates/rpc/src/web3/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! `web3` namespace RPC handlers.

use crate::config::StorageRpcCtx;
use alloy::primitives::{B256, Bytes, keccak256};
use signet_hot::{HotKv, model::HotKvRead};
use trevm::revm::database::DBErrorMarker;

/// Instantiate the `web3` API router.
pub(crate) fn web3<H>() -> ajj::Router<StorageRpcCtx<H>>
where
H: HotKv + Send + Sync + 'static,
<H::RoTx as HotKvRead>::Error: DBErrorMarker,
{
ajj::Router::new().route("clientVersion", client_version).route("sha3", sha3)
}

/// `web3_clientVersion` — returns the signet client version string.
pub(crate) async fn client_version() -> Result<String, ()> {
Ok(format!("signet/v{}/{}", env!("CARGO_PKG_VERSION"), std::env::consts::OS,))
}

/// `web3_sha3` — returns the keccak256 hash of the given data.
pub(crate) async fn sha3((data,): (Bytes,)) -> Result<B256, ()> {
Ok(keccak256(&data))
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn client_version_format() {
let version = client_version().await.unwrap();
assert!(version.starts_with("signet/v"), "got: {version}");
assert!(version.contains('/'), "expected platform suffix, got: {version}");
}

#[tokio::test]
async fn sha3_empty_input() {
let result = sha3((Bytes::new(),)).await.unwrap();
assert_eq!(result, keccak256(b""));
}

#[tokio::test]
async fn sha3_nonempty_input() {
let input = Bytes::from_static(b"hello");
let result = sha3((input.clone(),)).await.unwrap();
assert_eq!(result, keccak256(&input));
}
}
6 changes: 2 additions & 4 deletions crates/rpc/tests/eth_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,12 +598,10 @@ async fn test_get_logs_empty() {
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_not_supported() {
async fn test_protocol_version() {
let h = TestHarness::new(0).await;
let resp = rpc_call_raw(&h.app, "eth_protocolVersion", json!([])).await;
assert!(resp.get("error").is_some());
let msg = resp["error"]["message"].as_str().unwrap();
assert!(msg.contains("not found"), "unexpected error: {msg}");
assert_eq!(resp["result"].as_str().unwrap(), "0x44");
}

#[tokio::test]
Expand Down
Loading