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
4 changes: 2 additions & 2 deletions chain/ethereum/src/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use graph::{
self, Block as BlockchainBlock, BlockPtr, BlockTime, ChainStoreBlock, ChainStoreData,
},
components::ethereum::{
AnyBlock, AnyHeader, AnyRpcHeader, AnyTransactionReceiptBare, AnyTxEnvelope,
AnyBlock, AnyHeader, AnyTransactionReceiptBare, AnyTxEnvelope, LenientHeader,
},
prelude::{
BlockNumber, Error, EthereumBlock, EthereumBlockWithCalls, EthereumCall,
Expand Down Expand Up @@ -564,7 +564,7 @@ impl TryInto<AnyBlock> for &Block {

use alloy::rpc::types::Block;

let any_header: AnyRpcHeader = rpc_header.map(AnyHeader::from);
let any_header = rpc_header.map(|h| LenientHeader::from(AnyHeader::from(h)));

Ok(Block {
header: any_header,
Expand Down
3 changes: 2 additions & 1 deletion gnd/src/commands/test/trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,8 @@ fn create_block_with_triggers(
parent_hash,
timestamp,
..Default::default()
}),
})
.into(),
total_difficulty: None,
size: None,
})
Expand Down
119 changes: 119 additions & 0 deletions graph/src/components/ethereum/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use alloy::consensus::BlockHeader;
use alloy::network::AnyHeader;
use alloy::primitives::{Address, B64, B256, BlockNumber, Bloom, Bytes, U256};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::ops::{Deref, DerefMut};

/// Alloy's [`AnyHeader`], except that a missing `sha3Uncles` deserializes as
/// the zero hash, as it did with graph-node's rust-web3 fork.
/// The zero hash is kept for compatibility: mappings can read it as
/// `ethereum.Block.unclesHash`. Serializes exactly like `AnyHeader`.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize)]
#[serde(transparent)]
pub struct LenientHeader(pub AnyHeader);

impl<'de> Deserialize<'de> for LenientHeader {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let mut header = Value::deserialize(deserializer)?;
if let Value::Object(fields) = &mut header
&& !fields.contains_key("sha3Uncles")
{
fields.insert("sha3Uncles".to_string(), B256::ZERO.to_string().into());
}
Comment thread
dimitrovmaksim marked this conversation as resolved.
AnyHeader::deserialize(header)
.map(LenientHeader)
.map_err(serde::de::Error::custom)
}
}

impl Deref for LenientHeader {
type Target = AnyHeader;

fn deref(&self) -> &AnyHeader {
&self.0
}
}

impl DerefMut for LenientHeader {
fn deref_mut(&mut self) -> &mut AnyHeader {
&mut self.0
}
}

impl From<AnyHeader> for LenientHeader {
fn from(header: AnyHeader) -> Self {
LenientHeader(header)
}
}

macro_rules! delegate {
($($method:ident -> $ret:ty),* $(,)?) => {
$(fn $method(&self) -> $ret { self.0.$method() })*
};
}

impl BlockHeader for LenientHeader {
delegate! {
parent_hash -> B256,
ommers_hash -> B256,
beneficiary -> Address,
state_root -> B256,
transactions_root -> B256,
receipts_root -> B256,
withdrawals_root -> Option<B256>,
logs_bloom -> Bloom,
difficulty -> U256,
number -> BlockNumber,
gas_limit -> u64,
gas_used -> u64,
timestamp -> u64,
mix_hash -> Option<B256>,
nonce -> Option<B64>,
base_fee_per_gas -> Option<u64>,
blob_gas_used -> Option<u64>,
excess_blob_gas -> Option<u64>,
parent_beacon_block_root -> Option<B256>,
requests_hash -> Option<B256>,
block_access_list_hash -> Option<B256>,
slot_number -> Option<u64>,
extra_data -> &Bytes,
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::components::ethereum::AnyBlock;

/// Celo mainnet block 18946750 (before Gingerbread), as returned by
/// forno.celo.org. It has no `sha3Uncles` field.
const CELO_PRE_GINGERBREAD_BLOCK: &str = r#"{"baseFeePerGas":"0x12a05f200","difficulty":"0x0","epochSnarkData":null,"extraData":"0xd983010700846765746889676f312e31372e3133856c696e7578000000000000f8ccc0c080b841a2dabeefc7d980e7da2dc0279a009f071892f2da13513fad2c78564b02ac62ad1846f48b2c374cecdb3008fbe58d3381809d62d0542a8468b94f4e9b22be904300f8418e303ed3fcd8eaff6d5f7fbc5f79bcb0f84388e266f10d9cb40a8aa194fd4d496e0481f03e0f0115b090c8510253278495ea59cd861dfd688fd62a7a263d290180f8418e3fffffffffffffffffffefffffffb0450595e2afb27d47dbababe99a3f093d4c0f9e7a4b555fff11980cf43ca2a7a5583c75877c4ed993908a49a1b8d4290080","gasLimit":"0x1312d00","gasUsed":"0xd647e","hash":"0x8e4a323d009fdd25a67f58d883abbc5b5f029f213a2e64f39dd73e0efecc083a","logsBloom":"0x00800000000200000000240040020000000000000000000000000000000000000000080000000000000000002000000800000000000000000000000000200000000000100002000200000008000000000840002004000000000000000000000000000000000400000004400000008008000000000000000040910010000001000402000080000000400080000400000800000000000040800000000000008100000000800000000000000000000010000000000000000000000000000400010000000002000000020000100000002000000800000000000000000000400000000000000000000000020004020000000000000240000020000000000000000000","miner":"0x8e5db67c3ca13c3487fa291953ade27b503ed6bd","number":"0x1211abe","parentHash":"0x265cb829255b379e0812f857b162450e8330dd34cd21082af15b502afb960539","randomness":{"committed":"0x5a6dd907afeb4dbe106605e77f3eee2d8778dee1e99d9bc74705fec15540d61c","revealed":"0xf8158ddf7f63993ecefc39d4ca064aeb987c15d62b24807b079a1c8b29d3d8f6"},"receiptsRoot":"0xf06c82b478ebd9f5281756a1876d7526a6a952e7ecad6b040748c700378bfca2","size":"0x64b","stateRoot":"0x55d3594e686901a30be71777d8cc11575da5cfa2c78c57303933a3db6cf18b87","timestamp":"0x64476aa4","totalDifficulty":"0x1211abf","transactions":["0xc06b5d3f226083ea603fdfffaf910aef6794709f0b7ac2226f828ba053fbc015","0xd46049a51bbd0baf617d63cc264694d2c9ea4cda11ac348ea5bb813844611d0a","0x2422aea2e4815963e7c6d2fefa0698cb827ef180dd662364203cf416262419f8","0x973372c269483c298d40d111d5b342f6d30d66bb46cfab6cf910e5252ab9cb9e"],"transactionsRoot":"0x7fc667ab6e209550d53bd09f04fbd8fd12f277cc12733ecf4af1c95b99b51fd3"}"#;

#[test]
fn missing_uncles_hash_is_zero() {
let block: AnyBlock = serde_json::from_str(CELO_PRE_GINGERBREAD_BLOCK).unwrap();
assert_eq!(block.header.number, 18946750);
assert_eq!(block.header.ommers_hash, B256::ZERO);
assert_eq!(block.transactions.len(), 4);
}

#[test]
fn present_uncles_hash_is_kept() {
let uncles = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347";
let json =
CELO_PRE_GINGERBREAD_BLOCK.replacen('{', &format!(r#"{{"sha3Uncles":"{uncles}","#), 1);
let block: AnyBlock = serde_json::from_str(&json).unwrap();
assert_eq!(block.header.ommers_hash, uncles.parse::<B256>().unwrap());
}

#[test]
fn round_trips_through_json() {
let uncles = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347";
let json =
CELO_PRE_GINGERBREAD_BLOCK.replacen('{', &format!(r#"{{"sha3Uncles":"{uncles}","#), 1);
let block: AnyBlock = serde_json::from_str(&json).unwrap();
let back: AnyBlock = serde_json::from_value(serde_json::to_value(&block).unwrap()).unwrap();
assert_eq!(back.header, block.header);
}
}
4 changes: 3 additions & 1 deletion graph/src/components/ethereum/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod header;
pub mod json_patch;
mod network;
mod types;

pub use self::header::LenientHeader;
pub use self::network::AnyNetworkBare;
pub use self::types::{
AnyBlock, AnyTransaction, AnyTransactionReceiptBare, CachedBlock, EthereumBlock,
EthereumBlockWithCalls, EthereumCall, LightEthereumBlock, LightEthereumBlockExt,
};

// Re-export Alloy network types for convenience
pub use alloy::network::{AnyHeader, AnyRpcHeader, AnyTxEnvelope};
pub use alloy::network::{AnyHeader, AnyTxEnvelope};
13 changes: 7 additions & 6 deletions graph/src/components/ethereum/network.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use alloy::consensus::BlobTransactionSidecarVariant;
use alloy::eips::eip7702::SignedAuthorization;
use alloy::network::{
AnyHeader, AnyReceiptEnvelope, AnyRpcTransaction, AnyTxEnvelope, AnyTxType,
AnyTypedTransaction, BuildResult, Network, NetworkTransactionBuilder, NetworkWallet,
TransactionBuilder, TransactionBuilder4844, TransactionBuilder7702, TransactionBuilderError,
AnyReceiptEnvelope, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, AnyTypedTransaction,
BuildResult, Network, NetworkTransactionBuilder, NetworkWallet, TransactionBuilder,
TransactionBuilder4844, TransactionBuilder7702, TransactionBuilderError,
};
use alloy::primitives::{Address, Bytes, ChainId, TxKind, U256};
use alloy::providers::fillers::{
Expand All @@ -17,6 +17,7 @@ use alloy::serde::WithOtherFields;
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};

use super::header::LenientHeader;
use super::types::AnyTransaction;

/// Like alloy's [`AnyNetwork`] but without [`WithOtherFields`] on response types.
Expand Down Expand Up @@ -95,13 +96,13 @@ impl Network for AnyNetworkBare {
type TxEnvelope = AnyTxEnvelope;
type UnsignedTx = AnyTypedTransaction;
type ReceiptEnvelope = AnyReceiptEnvelope;
type Header = AnyHeader;
type Header = LenientHeader;

type TransactionRequest = AnyTransactionRequest;
type TransactionResponse = AnyTransaction;
type ReceiptResponse = TransactionReceipt<AnyReceiptEnvelope<Log>>;
type HeaderResponse = Header<AnyHeader>;
type BlockResponse = Block<AnyTransaction, Header<AnyHeader>>;
type HeaderResponse = Header<LenientHeader>;
type BlockResponse = Block<AnyTransaction, Header<LenientHeader>>;
}

impl RecommendedFillers for AnyNetworkBare {
Expand Down
10 changes: 4 additions & 6 deletions graph/src/components/ethereum/types.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use alloy::{
network::{
AnyHeader, AnyReceiptEnvelope, AnyRpcHeader, AnyTxEnvelope, ReceiptResponse,
TransactionResponse,
},
network::{AnyReceiptEnvelope, AnyTxEnvelope, ReceiptResponse, TransactionResponse},
primitives::{Address, B256, Bytes, U256},
rpc::types::{
Block, Header, Log, Transaction, TransactionReceipt,
Expand All @@ -17,10 +14,11 @@ use crate::{
prelude::BlockNumber,
};

use super::header::LenientHeader;
use super::json_patch;

pub type AnyTransaction = Transaction<AnyTxEnvelope>;
pub type AnyBlock = Block<AnyTransaction, Header<AnyHeader>>;
pub type AnyBlock = Block<AnyTransaction, Header<LenientHeader>>;
/// Like alloy's `AnyTransactionReceipt` but without the `WithOtherFields` wrapper,
/// avoiding `#[serde(flatten)]` overhead during deserialization.
pub type AnyTransactionReceiptBare = TransactionReceipt<AnyReceiptEnvelope<Log>>;
Expand All @@ -34,7 +32,7 @@ impl Default for LightEthereumBlock {
use alloy::rpc::types::BlockTransactions;

Self(Block {
header: AnyRpcHeader::default(),
header: Default::default(),
transactions: BlockTransactions::Full(vec![]),
uncles: vec![],
withdrawals: None,
Expand Down
2 changes: 1 addition & 1 deletion graph/src/util/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn create_minimal_block_for_test(block_number: u64, block_hash: B256) -> Any
// Create RPC header with the specific hash and wrap in AnyHeader
let rpc_header = Header {
hash: block_hash,
inner: AnyHeader::from(consensus_header),
inner: AnyHeader::from(consensus_header).into(),
total_difficulty: None,
size: None,
};
Expand Down
2 changes: 1 addition & 1 deletion store/test-store/src/block_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl FakeBlock {

let rpc_header = Header {
hash: block_hash,
inner: graph::components::ethereum::AnyHeader::from(consensus_header),
inner: graph::components::ethereum::AnyHeader::from(consensus_header).into(),
total_difficulty: None,
size: None,
};
Expand Down
Loading