Skip to content

graph: don't panic on out-of-range block time - #6748

Open
parthod0x wants to merge 1 commit into
graphprotocol:masterfrom
parthod0x:graph/no-panic-on-out-of-range-block-time
Open

parthod0x wants to merge 1 commit into
graphprotocol:masterfrom
parthod0x:graph/no-panic-on-out-of-range-block-time

Conversation

@parthod0x

Copy link
Copy Markdown

What

BlockTime::since_epoch panics on an out-of-range block time, and that value comes from provider-supplied input. This makes the conversion defensive input validation: return errors where there's an error channel, and clamp (never panic) where there isn't.

Detail

pub fn since_epoch(secs: i64, nanos: u32) -> Self {
    Self(Timestamp::since_epoch(secs, nanos)
        .ok_or_else(|| anyhow!("invalid block time: ..."))
        .unwrap())   // panics
}

Timestamp::since_epoch wraps DateTime::from_timestamp, which returns None for an out-of-range second (year beyond ~262143) or nanos >= 2_000_000_000. The .unwrap() then panics. Both the firehose/substreams decode path (chain/ethereum/src/codec.rs) and the JSON-RPC header deserialization (deserialize_block_time, ExtendedBlockPtr::try_from) pass provider-supplied secs/nanos into this — and a protobuf i32 nanos cast as u32 can become a large value — so a malformed header crashes the node instead of being rejected.

Change

  • Add BlockTime::try_since_epoch(secs, nanos) -> Result<Self, _> that returns the error instead of panicking.
  • deserialize_block_time and ExtendedBlockPtr's TryFrom (both already fallible) now return an error for an out-of-range block time.
  • The infallible since_epoch (used where there's no error channel — the firehose decode path, From impls, tests) now drops a bad sub-second component and clamps an out-of-range second to BlockTime::MIN/MAX rather than panicking. Valid inputs are unchanged.

A maintainer may prefer the firehose path to reject such blocks upstream instead of clamping, which would mean plumbing a Result through the decoder — happy to follow up if that's the preferred direction.

Tests

  • An out-of-range timestamp in ExtendedBlockPtr deserialization is now rejected as an error (previously panicked).
  • try_since_epoch surfaces the error, and since_epoch clamps, for out-of-range seconds and nanos.

Verification note

The graph crate needs system OpenSSL to build, which my environment lacks, so I verified the conversion/clamp behaviour against the pinned chrono = 0.4.45 in a standalone crate: from_timestamp(i64::MAX, 0) and from_timestamp(0, 2_000_000_000) both return None, and the clamp composes on top to yield MIN/MAX (both cases pass). CI will run the added in-crate tests.

BlockTime::since_epoch unwrapped the Option from Timestamp::since_epoch, so an
out-of-range block time (seconds beyond the representable range, or nanos >= 2e9)
panicked the node. This is reachable from untrusted sources: the firehose/
substreams decode path and the JSON-RPC header deserialization both feed it
provider-supplied values (and a protobuf i32 `nanos` cast `as u32` turns a
negative value into a huge one).

Make it defensive input validation:
- Add BlockTime::try_since_epoch, which returns an error instead of panicking.
- deserialize_block_time and ExtendedBlockPtr's TryFrom (both already fallible)
  now return an error for an out-of-range block time rather than panicking.
- The infallible since_epoch (used where there is no error channel, e.g. the
  firehose decode path and From impls) now drops a bad sub-second component and
  clamps an out-of-range second to BlockTime::MIN/MAX instead of panicking.

Adds tests: an out-of-range timestamp in ExtendedBlockPtr deserialization is
rejected as an error, and since_epoch/try_since_epoch handle out-of-range inputs
without panicking.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant