Skip to content

Fix block cache deserialization for old ethereum blocks#6330

Merged
incrypto32 merged 7 commits intomasterfrom
krishna/unified-json-patching
Feb 6, 2026
Merged

Fix block cache deserialization for old ethereum blocks#6330
incrypto32 merged 7 commits intomasterfrom
krishna/unified-json-patching

Conversation

@incrypto32
Copy link
Member

No description provided.

Add a dedicated module for JSON patching utilities that handle missing
`type` fields in Ethereum transactions and receipts. This consolidates
patching logic that will be used by both the HTTP transport layer (for
RPC responses) and cache deserialization (for stored blocks).

The module provides:
- patch_type_field: Adds "type": "0x0" to JSON objects missing the field
- patch_block_transactions: Patches all transactions in a block
- patch_receipts: Patches single receipts or arrays of receipts
Refactor the HTTP transport's receipt patching to use the shared
json_patch module instead of duplicating the patching logic.

This removes the patch_receipt and patch_result methods from
PatchingHttp and replaces them with calls to json_patch::patch_receipts.
The patch_rpc_response and patch_response methods remain as they handle
RPC-specific JSON-RPC response structure.
Add patching for cached blocks before deserialization to handle blocks
that were cached before March 2022 when graph-node's rust-web3 fork
didn't capture the transaction type field.

This patches transactions and receipts in cached blocks at two locations:
- ancestor_block(): For full block deserialization (EthereumBlock),
  patches both transactions and transaction_receipts
- parent_ptr(): For light block deserialization (LightEthereumBlock),
  patches only transactions (light blocks don't include receipts)

The patching adds type: 0x0 (legacy) to transactions/receipts missing
the field, allowing alloy to deserialize blocks that would otherwise
fail due to the missing required field.
@incrypto32 incrypto32 force-pushed the krishna/unified-json-patching branch from 5eaa074 to c52a748 Compare February 5, 2026 14:08
@dwerner dwerner requested review from dwerner and lutter February 5, 2026 16:34
Copy link
Collaborator

@lutter lutter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but I would tighten up the code by introducing a new JsonBlock type that helps with determining what the block is and conversion into one of our blocks.

if json.get("block").is_none() {
warn!(
// Shallow blocks have "data": null - no block data to deserialize
if json.get("data") == Some(&json::Value::Null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this check is repeated a few times and a little noisy, I wonder if it wouldn't be better to introduce a newtype JsonBlock(json::Value) so that you could have methods like is_shallow(&self) and patch_transactions on that

let mut json = json;
if let Some(block) = json.get_mut("block") {
json_patch::patch_block_transactions(block);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put the entire if statement into a json_block.path_block_transactions() method

}
if let Some(receipts) = json.get_mut("transaction_receipts") {
json_patch::patch_receipts(receipts);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this if statement

if let Some(receipts) = json.get_mut("transaction_receipts") {
json_patch::patch_receipts(receipts);
}
match json::from_value::<EthereumBlock>(json) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or since patching happens here and on line 1205, just have a JsonBlock::patched_block<T>(self) -> Result<T, SomeError> that does the patching and conversion

Introduce EthereumJsonBlock newtype with helper methods for format
detection (is_shallow, is_legacy_format) and deserialization
(into_full_block, into_light_block). Cleans up repeated logic and
avoids unnecessary clones by taking ownership of the JSON data.
@incrypto32 incrypto32 force-pushed the krishna/unified-json-patching branch from 1b3569c to 7fa4a54 Compare February 6, 2026 12:51
use crate::json_patch;

#[derive(Debug)]
pub struct EthereumJsonBlock(Value);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if we should call it just JsonBlock named it this because we already have a JsonBlock in chainstore and this one is very ethereum specific

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with either, but since we are in the ethereum crate, the Ethereum prefix isn't strictly necessary. But not a big deal either way.

@incrypto32 incrypto32 requested a review from lutter February 6, 2026 12:54
Copy link
Collaborator

@lutter lutter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Left some suggestions for minor improvements, but totally fine to merge as-is

pub fn into_full_block<T: DeserializeOwned>(mut self) -> Result<T, json::Error> {
self.patch();
json::from_value(self.0)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I suggested the type parameter T, I assumed we'd use this for different block forms. But since we need to have different methods for light and full blocks, I think we don't need T.

fn from(value: Value) -> Self {
Self::new(value)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely a matter of taste, but I generally try to not have From implementations that just call new or other constructors, since when you're reading code value.into() is much less clear than EthereumJsonBlock::new(value)

Remove generic type parameters from into_full_block() and
into_light_block(), returning EthereumBlock and LightEthereumBlock
directly instead.
@incrypto32 incrypto32 merged commit 3ca739f into master Feb 6, 2026
6 checks passed
@incrypto32 incrypto32 deleted the krishna/unified-json-patching branch February 6, 2026 17:49
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.

2 participants