Skip to content
Closed
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
81 changes: 78 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions crates/anvil/core/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,18 @@ pub enum EthRequest {
#[serde(rename = "debug_getRawTransaction", with = "sequence")]
DebugGetRawTransaction(TxHash),

/// reth's `debug_getRawReceipts` endpoint.
#[serde(rename = "debug_getRawReceipts", with = "sequence")]
DebugGetRawReceipts(BlockId),

/// reth's `debug_getRawTransactions` endpoint.
#[serde(rename = "debug_getRawTransactions", with = "sequence")]
DebugGetRawTransactions(BlockId),

/// geth's `debug_getRawHeader` endpoint.
#[serde(rename = "debug_getRawHeader", with = "sequence")]
DebugGetRawHeader(BlockId),

/// geth's `debug_traceTransaction` endpoint
#[serde(rename = "debug_traceTransaction")]
DebugTraceTransaction(B256, #[serde(default)] GethDebugTracingOptions),
Expand Down Expand Up @@ -1424,6 +1436,40 @@ mod tests {
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
}

#[test]
fn test_serde_debug_raw_receipts() {
let s = r#"{"jsonrpc":"2.0","method":"debug_getRawReceipts","params":["latest"],"id":1}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();

let s = r#"{"jsonrpc":"2.0","method":"debug_getRawReceipts","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c"],"id":1}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
}

#[test]
fn test_serde_debug_raw_transactions() {
let s =
r#"{"jsonrpc":"2.0","method":"debug_getRawTransactions","params":["latest"],"id":1}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();

let s = r#"{"jsonrpc":"2.0","method":"debug_getRawTransactions","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c"],"id":1}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
}

#[test]
fn test_serde_debug_raw_header() {
let s = r#"{"jsonrpc":"2.0","method":"debug_getRawHeader","params":["latest"],"id":1}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();

let s = r#"{"jsonrpc":"2.0","method":"debug_getRawHeader","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c"],"id":1}"#;
let value: serde_json::Value = serde_json::from_str(s).unwrap();
let _req = serde_json::from_value::<EthRequest>(value).unwrap();
}

#[test]
fn test_serde_debug_trace_transaction() {
let s = r#"{"method": "debug_traceTransaction", "params":
Expand Down
55 changes: 55 additions & 0 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ impl EthApi {
EthRequest::DebugGetRawTransaction(hash) => {
self.raw_transaction(hash).await.to_rpc_result()
}
EthRequest::DebugGetRawReceipts(block) => {
self.raw_receipts(block).await.to_rpc_result()
}
EthRequest::DebugGetRawTransactions(block) => {
self.raw_transactions(block).await.to_rpc_result()
}
EthRequest::DebugGetRawHeader(block) => self.raw_header(block).await.to_rpc_result(),
// non eth-standard rpc calls
EthRequest::DebugTraceTransaction(tx, opts) => {
self.debug_trace_transaction(tx, opts).await.to_rpc_result()
Expand Down Expand Up @@ -1883,6 +1890,54 @@ impl EthApi {
self.inner_raw_transaction(hash).await
}

/// Returns EIP-2718 encoded raw receipts for the block.
///
/// Handler for RPC call: `debug_getRawReceipts`.
pub async fn raw_receipts(&self, block: BlockId) -> Result<Vec<Bytes>> {
node_info!("debug_getRawReceipts");

// In fork mode, serve pre-fork blocks from the upstream provider.
if let BlockRequest::Number(number) = self.block_request(Some(block)).await?
&& let Some(fork) = self.get_fork()
&& fork.predates_fork_inclusive(number)
{
let receipts = fork.block_receipts(number).await?.unwrap_or_default();
return Ok(receipts
.into_iter()
.map(|receipt| {
receipt.0.inner.inner.map_logs(|log| log.inner).encoded_2718().into()
})
.collect());
}

let block = self.backend.get_block(block).ok_or(BlockchainError::BlockNotFound)?;
let receipts = self
.backend
.mined_receipts(block.header.hash_slow())
.ok_or(BlockchainError::BlockNotFound)?;
Ok(receipts.into_iter().map(|receipt| receipt.encoded_2718().into()).collect())
}

/// Returns EIP-2718 encoded raw transactions for the block.
///
/// Handler for RPC call: `debug_getRawTransactions`.
pub async fn raw_transactions(&self, block: BlockId) -> Result<Vec<Bytes>> {
node_info!("debug_getRawTransactions");
let Some(block) = self.backend.get_block(block) else {
return Ok(Vec::new());
};
Ok(block.body.transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect())
}

/// Returns RLP encoded raw block header.
///
/// Handler for RPC call: `debug_getRawHeader`.
pub async fn raw_header(&self, block: BlockId) -> Result<Bytes> {
node_info!("debug_getRawHeader");
let block = self.backend.get_block(block).ok_or(BlockchainError::BlockNotFound)?;
Ok(alloy_rlp::encode(&block.header).into())
}

/// Returns EIP-2718 encoded raw transaction by block hash and index
///
/// Handler for RPC call: `eth_getRawTransactionByBlockHashAndIndex`
Expand Down
15 changes: 15 additions & 0 deletions crates/anvil/src/server/beacon/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ pub async fn handle_get_genesis(State(api): State<EthApi>) -> Response {
Err(_) => BeaconError::internal_error().into_response(),
}
}

/// Handles requests for the Beacon chain configuration used by Base clients.
///
/// GET /eth/v1/config/spec
pub async fn handle_get_spec(State(api): State<EthApi>) -> Response {
match api.anvil_get_interval_mining().ok().flatten().filter(|interval| *interval > 0) {
Some(interval) => Json(serde_json::json!({
"data": {
"SECONDS_PER_SLOT": interval.to_string()
}
}))
.into_response(),
None => BeaconError::internal_error().into_response(),
}
}
#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions crates/anvil/src/server/beacon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ pub fn router(api: EthApi) -> Router {
.route("/eth/v1/beacon/blob_sidecars/{block_id}", get(handlers::handle_get_blob_sidecars))
.route("/eth/v1/beacon/blobs/{block_id}", get(handlers::handle_get_blobs))
.route("/eth/v1/beacon/genesis", get(handlers::handle_get_genesis))
.route("/eth/v1/config/spec", get(handlers::handle_get_spec))
.with_state(api)
}
18 changes: 18 additions & 0 deletions crates/anvil/tests/it/beacon_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use alloy_serde::WithOtherFields;
use anvil::{NodeConfig, spawn};
use foundry_evm::hardfork::EthereumHardfork;
use ssz::Decode;
use std::time::Duration;

#[tokio::test(flavor = "multi_thread")]
async fn test_beacon_api_get_blob_sidecars() {
Expand Down Expand Up @@ -258,3 +259,20 @@ async fn test_beacon_api_get_genesis() {
FixedBytes::from([0x00, 0x00, 0x00, 0x00])
);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_beacon_api_get_spec() {
let (_api, handle) =
spawn(NodeConfig::test().with_blocktime(Some(Duration::from_secs(4)))).await;

let response: serde_json::Value = reqwest::Client::new()
.get(format!("{}/eth/v1/config/spec", handle.http_endpoint()))
.send()
.await
.unwrap()
.json()
.await
.unwrap();

assert_eq!(response, serde_json::json!({ "data": { "SECONDS_PER_SLOT": "4" } }));
}
36 changes: 36 additions & 0 deletions crates/anvil/tests/it/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
};
use alloy_chains::NamedChain;
use alloy_eips::{
eip2718::Decodable2718,
eip7840::BlobParams,
eip7910::{EthConfig, SystemContract},
};
Expand All @@ -24,6 +25,7 @@ use anvil::{EthereumHardfork, NodeConfig, NodeHandle, PrecompileFactory, eth::Et
use foundry_common::provider::get_http_provider;
use foundry_config::Config;
use foundry_evm_networks::NetworkConfigs;
use foundry_primitives::FoundryReceiptEnvelope;
use foundry_test_utils::rpc::{self, next_http_rpc_endpoint, next_rpc_endpoint};
use futures::StreamExt;
use std::{
Expand Down Expand Up @@ -101,6 +103,40 @@ async fn test_fork_gas_limit_disabled_from_config() {
let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap();
}

// `debug_getRawReceipts` must serve pre-fork blocks from the upstream provider.
#[tokio::test(flavor = "multi_thread")]
async fn test_fork_debug_get_raw_receipts() {
let (_api, handle) = spawn(fork_config()).await;
let provider = handle.http_provider();

// A pre-fork block known to contain transactions.
let block_number = BLOCK_NUMBER - 1;
let rpc_receipts =
provider.get_block_receipts(BlockId::number(block_number)).await.unwrap().unwrap();
assert!(!rpc_receipts.is_empty());

let block = provider.get_block(BlockId::number(block_number)).await.unwrap().unwrap();
let raw_by_number: Vec<Bytes> = provider
.client()
.request("debug_getRawReceipts", (BlockId::number(block_number),))
.await
.unwrap();
let raw_by_hash: Vec<Bytes> = provider
.client()
.request("debug_getRawReceipts", (BlockId::hash(block.header.hash),))
.await
.unwrap();

assert_eq!(raw_by_number, raw_by_hash);
assert_eq!(raw_by_number.len(), rpc_receipts.len());

// Each entry decodes back into a receipt envelope matching the RPC receipt.
for (raw, rpc) in raw_by_number.iter().zip(rpc_receipts.iter()) {
let decoded = FoundryReceiptEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
assert_eq!(decoded.status(), rpc.status());
}
}

#[tokio::test(flavor = "multi_thread")]
async fn test_spawn_fork() {
let (api, _handle) = spawn(fork_config()).await;
Expand Down
Loading
Loading