From 54f376468bdf0ea67cdc34bf07b141532dd8cc35 Mon Sep 17 00:00:00 2001 From: neriumpete Date: Thu, 25 Jun 2026 15:17:05 +0300 Subject: [PATCH 1/2] chain/ethereum: send eth_getBlockReceipts block hash as plain string param --- chain/ethereum/src/ethereum_adapter.rs | 52 ++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index 9c85f0ac551..f2769c2f7d3 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -2315,13 +2315,31 @@ async fn batch_get_transaction_receipts( Ok(results) } +/// Fetch block receipts by hash, sending the block hash as a plain string +/// param (`["0x.."]`) rather than alloy's default EIP-1898 object +/// (`[{"blockHash":".."}]`). +/// +/// `eth_getBlockReceipts` accepts both forms on most clients, but some (e.g. +/// taraxa-node) only accept the plain-string hash and reject the object with +/// `INVALID_PARAMS`. The string form is accepted by all known implementations, +/// so we always use it; alloy's typed `get_block_receipts` would always emit +/// the object form. See issue #5835. +async fn get_block_receipts_by_hash( + alloy: &AlloyProvider, + block_hash: B256, +) -> Result>, RpcError> { + alloy + .client() + .request("eth_getBlockReceipts", (block_hash,)) + .await +} + pub(crate) async fn check_block_receipt_support( alloy: Arc, block_hash: B256, supports_eip_1898: bool, call_only: bool, ) -> Result<(), Error> { - use alloy::rpc::types::BlockId; if call_only { return Err(anyhow!("Provider is call-only")); } @@ -2331,7 +2349,7 @@ pub(crate) async fn check_block_receipt_support( } // Fetch block receipts from the provider for the latest block. - let block_receipts_result = alloy.get_block_receipts(BlockId::from(block_hash)).await; + let block_receipts_result = get_block_receipts_by_hash(&alloy, block_hash).await; // Determine if the provider supports block receipts based on the fetched result. match block_receipts_result { @@ -2406,7 +2424,6 @@ async fn fetch_block_receipts_with_retry( logger: ProviderLogger, settings: &ChainSettings, ) -> Result>, IngestorError> { - use graph::prelude::alloy::rpc::types::BlockId; let retry_log_message = format!("eth_getBlockReceipts RPC call for block {:?}", block_hash); // Perform the retry operation @@ -2414,7 +2431,10 @@ async fn fetch_block_receipts_with_retry( .redact_log_urls(true) .limit(settings.request_retries) .timeout_secs(settings.json_rpc_timeout.as_secs()) - .run(move || alloy.get_block_receipts(BlockId::from(block_hash)).boxed()) + .run(move || { + let alloy = alloy.clone(); + async move { get_block_receipts_by_hash(&alloy, block_hash).await }.boxed() + }) .await .map_err(|_timeout| -> IngestorError { anyhow!(block_hash).into() })?; @@ -2707,6 +2727,30 @@ mod tests { use std::iter::FromIterator; use std::sync::Arc; + #[test] + fn block_receipts_param_is_plain_hash_string() { + use graph::prelude::alloy::rpc::types::BlockId; + + let hash = B256::repeat_byte(0xab); + + // The fix: the block hash is sent as a bare string param, i.e. `["0x.."]`. + // This is the form all known clients accept (issue #5835). + let new_params: Value = serde_json::to_value((hash,)).unwrap(); + let arr = new_params.as_array().expect("params must be an array"); + assert_eq!(arr.len(), 1); + assert!( + arr[0].is_string(), + "param must be a plain hash string, got {new_params}" + ); + assert_eq!(arr[0].as_str().unwrap(), format!("{hash:#x}")); + + // Regression guard: alloy's typed call serializes the hash to an + // EIP-1898 object `[{"blockHash":".."}]`, which strict nodes reject. + let old_params: Value = serde_json::to_value((BlockId::from(hash),)).unwrap(); + assert!(old_params[0].is_object()); + assert!(old_params[0].get("blockHash").is_some()); + } + #[test] fn parse_block_triggers_every_block() { let block = create_minimal_block_for_test(2, hash(2)); From 18e9c5305643cfb0b1679c3efe26c561fd12e1fb Mon Sep 17 00:00:00 2001 From: neriumpete Date: Sat, 27 Jun 2026 09:17:00 +0300 Subject: [PATCH 2/2] chain/ethereum: drop redundant block-receipts serialization test --- chain/ethereum/src/ethereum_adapter.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index f2769c2f7d3..d30f578b773 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -2727,30 +2727,6 @@ mod tests { use std::iter::FromIterator; use std::sync::Arc; - #[test] - fn block_receipts_param_is_plain_hash_string() { - use graph::prelude::alloy::rpc::types::BlockId; - - let hash = B256::repeat_byte(0xab); - - // The fix: the block hash is sent as a bare string param, i.e. `["0x.."]`. - // This is the form all known clients accept (issue #5835). - let new_params: Value = serde_json::to_value((hash,)).unwrap(); - let arr = new_params.as_array().expect("params must be an array"); - assert_eq!(arr.len(), 1); - assert!( - arr[0].is_string(), - "param must be a plain hash string, got {new_params}" - ); - assert_eq!(arr[0].as_str().unwrap(), format!("{hash:#x}")); - - // Regression guard: alloy's typed call serializes the hash to an - // EIP-1898 object `[{"blockHash":".."}]`, which strict nodes reject. - let old_params: Value = serde_json::to_value((BlockId::from(hash),)).unwrap(); - assert!(old_params[0].is_object()); - assert!(old_params[0].get("blockHash").is_some()); - } - #[test] fn parse_block_triggers_every_block() { let block = create_minimal_block_for_test(2, hash(2));