Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@

- [#6849](https://github.com/ChainSafe/forest/pull/6849): Included strict bound in blocks included for calculating gas premium `GasEstimateGasPremium`.

- [#6856](https://github.com/ChainSafe/forest/pull/6856): Return ethereum compatible error `BlockRangeExceeded` with code `-32005` when block range exceeds in the eth filter and logs API.

## Forest v0.32.4 "Mild Inconvenience"

This is a non-mandatory release for all node operators. It enables F3 finality resolution on ETH v1 RPC methods.
Expand Down
21 changes: 19 additions & 2 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3161,11 +3161,20 @@ impl RpcMethod<1> for EthGetLogs {
) -> Result<Self::Ok, ServerError> {
let pf = ctx
.eth_event_handler
.parse_eth_filter_spec(&ctx, &eth_filter)?;
.parse_eth_filter_spec(&ctx, &eth_filter)
.map_err(|e| {
if e.downcast_ref::<EthErrors>()
.is_some_and(|eth_err| matches!(eth_err, EthErrors::BlockRangeExceeded { .. }))
{
return e;
}
e.context("failed to parse events for filter")
})?;
let events = ctx
.eth_event_handler
.get_events_for_parsed_filter(&ctx, &pf, SkipEvent::OnUnresolvedAddress)
.await?;
.await
.context("failed to get events for filter")?;
Ok(eth_filter_result_from_events(&ctx, &events)?)
}
}
Expand Down Expand Up @@ -3858,6 +3867,14 @@ impl RpcMethod<1> for EthTraceFilter {
.await
.context("cannot parse toBlock")?;

let max_block_range = ctx.eth_event_handler.max_filter_height_range;
if max_block_range > 0 && to_block.0 > from_block.0 {
let range = i64::try_from(to_block.0.saturating_sub(from_block.0))
.context("block range overflow")?;
if range > max_block_range {
return Err(EthErrors::limit_exceeded(max_block_range as u64, range as u64).into());
}
}
Ok(trace_filter(ctx, filter, from_block, to_block, ext).await?)
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/rpc/methods/eth/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ use thiserror::Error;
/// This error indicates that the execution reverted while executing the message.
/// Error code 3 was introduced in geth v1.9.15 and is now expected by most Ethereum ecosystem tooling for automatic ABI decoding of revert reasons from the error data field.
pub const EXECUTION_REVERTED_CODE: i32 = 3;
/// This error indicates that the block range provided in the RPC exceeds the configured maximum
/// It was introduced in EIP-1474
pub const LIMIT_EXCEEDED_CODE: i32 = -32005;

#[derive(Clone, Debug, Error, Serialize)]
pub enum EthErrors {
#[error("{message}")]
ExecutionReverted { message: String, data: String },
#[error("{message}")]
BlockRangeExceeded {
max: u64,
given: u64,
message: String,
},
}

impl EthErrors {
Expand All @@ -33,18 +42,28 @@ impl EthErrors {
data: format!("0x{}", hex::encode(data)),
}
}

pub fn limit_exceeded(max_block_range: u64, given: u64) -> Self {
Self::BlockRangeExceeded {
max: max_block_range,
given,
message: format!("block range exceeds maximum of {max_block_range} (got {given})"),
}
}
}

impl RpcErrorData for EthErrors {
fn error_code(&self) -> Option<i32> {
match self {
EthErrors::ExecutionReverted { .. } => Some(EXECUTION_REVERTED_CODE),
EthErrors::BlockRangeExceeded { .. } => Some(LIMIT_EXCEEDED_CODE),
}
}

fn error_message(&self) -> Option<String> {
match self {
EthErrors::ExecutionReverted { message, .. } => Some(message.clone()),
EthErrors::BlockRangeExceeded { message, .. } => Some(message.clone()),
}
}

Expand All @@ -53,6 +72,38 @@ impl RpcErrorData for EthErrors {
EthErrors::ExecutionReverted { data, .. } => {
Some(serde_json::Value::String(data.clone()))
}
EthErrors::BlockRangeExceeded { .. } => None,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::rpc::error::ServerError;

#[test]
fn test_block_range_exceeded_converts_to_server_error_with_correct_code() {
let err = EthErrors::limit_exceeded(100, 500);
let server_err: ServerError = err.into();

assert_eq!(server_err.inner().code(), LIMIT_EXCEEDED_CODE);
assert_eq!(
server_err.message(),
"block range exceeds maximum of 100 (got 500)"
);
}

#[test]
fn test_block_range_exceeded_via_anyhow_preserves_code() {
let eth_err = EthErrors::limit_exceeded(2880, 5000);
let anyhow_err: anyhow::Error = eth_err.into();
let server_err: ServerError = anyhow_err.into();

assert_eq!(server_err.inner().code(), LIMIT_EXCEEDED_CODE);
assert_eq!(
server_err.message(),
"block range exceeds maximum of 2880 (got 5000)"
);
}
}
66 changes: 51 additions & 15 deletions src/rpc/methods/eth/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::blocks::TipsetKey;
use crate::chain::index::ResolveNullTipset;
use crate::cli_shared::cli::EventsConfig;
use crate::rpc::eth::EVM_WORD_LENGTH;
use crate::rpc::eth::errors::EthErrors;
use crate::rpc::eth::filter::event::*;
use crate::rpc::eth::filter::mempool::*;
use crate::rpc::eth::filter::tipset::*;
Expand Down Expand Up @@ -552,14 +553,12 @@ fn parse_block_range(
if min_height == -1 && max_height > 0 {
ensure!(
max_height - heaviest <= max_range,
"invalid epoch range: to block is too far in the future (maximum: {})",
max_range
EthErrors::limit_exceeded(max_range as u64, (max_height - heaviest) as u64),
);
} else if min_height >= 0 && max_height == -1 {
ensure!(
heaviest - min_height <= max_range,
"invalid epoch range: from block is too far in the past (maximum: {})",
max_range
EthErrors::limit_exceeded(max_range as u64, (heaviest - min_height) as u64)
);
} else if min_height >= 0 && max_height >= 0 {
ensure!(
Expand All @@ -570,8 +569,7 @@ fn parse_block_range(
);
ensure!(
max_height - min_height <= max_range,
"invalid epoch range: range between to and from blocks is too large (maximum: {})",
max_range
EthErrors::limit_exceeded(max_range as u64, (max_height - min_height) as u64)
);
}

Expand Down Expand Up @@ -929,6 +927,9 @@ mod tests {

#[test]
fn test_parse_block_range() {
use crate::rpc::error::RpcErrorData;
use crate::rpc::eth::errors::LIMIT_EXCEEDED_CODE;

let heaviest = 50;
let max_range = 100;

Expand Down Expand Up @@ -956,14 +957,17 @@ mod tests {
assert_eq!(min_height, 1); // hex_str_to_epoch("0x1") = 1
assert_eq!(max_height, 10); // hex_str_to_epoch("0xA") = 10

// Test case 3: Range too large
let result = parse_block_range(
// Test case 3: Range too large — returns BlockRangeExceeded with -32005 code
let err = parse_block_range(
heaviest,
Some(BlockNumberOrHash::from_str("earliest").unwrap()),
Some(BlockNumberOrHash::from_str("0x100").unwrap()),
Some(BlockNumberOrHash::from_str("0x100").unwrap()), // epoch 256, range = 256 > 100
max_range,
);
assert!(result.is_err());
)
.unwrap_err();
let eth_err = err.downcast_ref::<EthErrors>().expect("expected EthErrors");
assert!(matches!(eth_err, EthErrors::BlockRangeExceeded { .. }));
assert_eq!(eth_err.error_code(), Some(LIMIT_EXCEEDED_CODE));

// Test case 4: from_block = "latest", to_block = "earliest"
let result = parse_block_range(
Expand Down Expand Up @@ -1008,13 +1012,23 @@ mod tests {
assert!(result.is_err());

// Test case 8: Both blocks are non-negative, order is correct, but the range is too large.
let result = parse_block_range(
let err = parse_block_range(
heaviest,
Some(BlockNumberOrHash::from_str("earliest").unwrap()),
Some(BlockNumberOrHash::from_str("0x65").unwrap()),
Some(BlockNumberOrHash::from_str("0x65").unwrap()), // epoch 101, range = 101 > 100
max_range,
);
assert!(result.is_err());
)
.unwrap_err();
let eth_err = err.downcast_ref::<EthErrors>().expect("expected EthErrors");
assert!(matches!(
eth_err,
EthErrors::BlockRangeExceeded {
max: 100,
given: 101,
..
}
));
assert_eq!(eth_err.error_code(), Some(LIMIT_EXCEEDED_CODE));

// Test case 9: Range exactly equal to max_range (boundary, should succeed).
let result = parse_block_range(
Expand Down Expand Up @@ -1081,6 +1095,28 @@ mod tests {
let (min_height, max_height) = result.unwrap();
assert_eq!(min_height, heaviest);
assert_eq!(max_height, -1);

// Test case 15: from_block too far in past (min >= 0, max == -1, heaviest - min > max_range)
let err = parse_block_range(
500, // heaviest
Some(BlockNumberOrHash::from_str("0x1").unwrap()), // epoch 1, range = 500 - 1 = 499
Some(BlockNumberOrHash::from_str("latest").unwrap()),
max_range,
)
.unwrap_err();
let eth_err = err.downcast_ref::<EthErrors>().expect("expected EthErrors");
assert!(matches!(
eth_err,
EthErrors::BlockRangeExceeded {
max: 100,
given: 499,
..
}
));
assert_eq!(
eth_err.error_message().unwrap(),
"block range exceeds maximum of 100 (got 499)"
);
}

#[test]
Expand Down
Loading