From 09fcf0751fbc0017712527bb4d21b5f2742fa551 Mon Sep 17 00:00:00 2001 From: Quertyy <98064975+Quertyy@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:39:37 +0200 Subject: [PATCH 1/2] chore(rpc): add eth_getSystemTxsReceiptsByBlockNumber and eth_getSystemTxsReceiptsByBlockNumber rpc method --- src/addons/hl_node_compliance.rs | 93 +++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/src/addons/hl_node_compliance.rs b/src/addons/hl_node_compliance.rs index b76010450..4c1bcdd7f 100644 --- a/src/addons/hl_node_compliance.rs +++ b/src/addons/hl_node_compliance.rs @@ -74,7 +74,7 @@ impl EthWrapper for T where #[rpc(server, namespace = "eth")] #[async_trait] -pub trait EthSystemTransactionApi { +pub trait EthSystemTransactionApi { #[method(name = "getEvmSystemTxsByBlockHash")] async fn get_evm_system_txs_by_block_hash(&self, hash: B256) -> RpcResult>>; @@ -83,6 +83,18 @@ pub trait EthSystemTransactionApi { &self, block_id: Option, ) -> RpcResult>>; + + #[method(name = "getEvmSystemTxsReceiptsByBlockHash")] + async fn get_evm_system_txs_receipts_by_block_hash( + &self, + hash: B256, + ) -> RpcResult>>; + + #[method(name = "getEvmSystemTxsReceiptsByBlockNumber")] + async fn get_evm_system_txs_receipts_by_block_number( + &self, + block_id: Option, + ) -> RpcResult>>; } pub struct HlSystemTransactionExt { @@ -97,7 +109,8 @@ impl HlSystemTransactionExt { } #[async_trait] -impl EthSystemTransactionApiServer> +impl + EthSystemTransactionApiServer, RpcReceipt> for HlSystemTransactionExt where jsonrpsee_types::ErrorObject<'static>: From<::Error>, @@ -167,6 +180,82 @@ where )) } } + + /// Returns the receipts for the system transactions for a given block hash. + async fn get_evm_system_txs_receipts_by_block_hash( + &self, + hash: B256, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getEvmSystemTxsReceiptsByBlockHash"); + match self + .get_evm_system_txs_receipts_by_block_number(Some(BlockId::Hash(hash.into()))) + .await + { + Ok(Some(receipts)) => Ok(Some(receipts)), + _ => Ok(None), + } + } + + /// Returns the receipts for the system transactions for a given block number, or the latest + /// block if no block + async fn get_evm_system_txs_receipts_by_block_number( + &self, + block_id: Option, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?block_id, "Serving eth_getEvmSystemTxsReceiptsByBlockNumber"); + if let Some((block, receipts)) = + EthBlocks::load_block_and_receipts(&self.eth_api, block_id.unwrap_or_default()).await? + { + let block_number = block.number; + let base_fee = block.base_fee_per_gas; + let block_hash = block.hash(); + let excess_blob_gas = block.excess_blob_gas; + let timestamp = block.timestamp; + let mut gas_used = 0; + let mut next_log_index = 0; + + let mut inputs = Vec::new(); + for (idx, (tx, receipt)) in + block.transactions_recovered().zip(receipts.iter()).enumerate() + { + if receipt.cumulative_gas_used() != 0 { + break; + } + + let meta = TransactionMeta { + tx_hash: *tx.tx_hash(), + index: idx as u64, + block_hash, + block_number, + base_fee, + excess_blob_gas, + timestamp, + }; + + let input = ConvertReceiptInput { + receipt: Cow::Borrowed(receipt), + tx, + gas_used: receipt.cumulative_gas_used() - gas_used, + next_log_index, + meta, + }; + + gas_used = receipt.cumulative_gas_used(); + next_log_index += receipt.logs().len(); + + inputs.push(input); + } + + let receipts = self.eth_api.tx_resp_builder().convert_receipts(inputs)?; + Ok(Some(receipts)) + } else { + Err(ErrorObject::owned( + INTERNAL_ERROR_CODE, + format!("invalid block height: {block_id:?}"), + Some(()), + )) + } + } } pub struct HlNodeFilterHttp { From 74e27b5ee2ffebef603bf329c27121b4d936890d Mon Sep 17 00:00:00 2001 From: Quertyy <98064975+Quertyy@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:10:41 +0200 Subject: [PATCH 2/2] refactor(rpc): extract common logic for getting system txs --- src/addons/hl_node_compliance.rs | 161 ++++++++++++++++++------------- 1 file changed, 93 insertions(+), 68 deletions(-) diff --git a/src/addons/hl_node_compliance.rs b/src/addons/hl_node_compliance.rs index 4c1bcdd7f..621e2443b 100644 --- a/src/addons/hl_node_compliance.rs +++ b/src/addons/hl_node_compliance.rs @@ -106,46 +106,15 @@ impl HlSystemTransactionExt { pub fn new(eth_api: Eth) -> Self { Self { eth_api, _marker: PhantomData } } -} -#[async_trait] -impl - EthSystemTransactionApiServer, RpcReceipt> - for HlSystemTransactionExt -where - jsonrpsee_types::ErrorObject<'static>: From<::Error>, -{ - /// Returns the system transactions for a given block hash. - /// Semi-compliance with the `eth_getSystemTxsByBlockHash` RPC method introduced by hl-node. - /// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc - /// - /// NOTE: Method name differs from hl-node because we retrieve transaction data from EVM - /// (signature recovery for 'from' address, EVM hash calculation) rather than HyperCore. - async fn get_evm_system_txs_by_block_hash( + async fn get_system_txs_by_block_id( &self, - hash: B256, - ) -> RpcResult>>> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getEvmSystemTxsByBlockHash"); - match self.get_evm_system_txs_by_block_number(Some(BlockId::Hash(hash.into()))).await { - Ok(Some(txs)) => Ok(Some(txs)), - // hl-node returns none if the block is not found - _ => Ok(None), - } - } - - /// Returns the system transactions for a given block number, or the latest block if no block - /// number is provided. Semi-compliance with the `eth_getSystemTxsByBlockNumber` RPC method - /// introduced by hl-node. https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc - /// - /// NOTE: Method name differs from hl-node because we retrieve transaction data from EVM - /// (signature recovery for 'from' address, EVM hash calculation) rather than HyperCore. - async fn get_evm_system_txs_by_block_number( - &self, - id: Option, - ) -> RpcResult>>> { - trace!(target: "rpc::eth", ?id, "Serving eth_getEvmSystemTxsByBlockNumber"); - - if let Some(block) = self.eth_api.recovered_block(id.unwrap_or_default()).await? { + block_id: BlockId, + ) -> RpcResult>>> + where + jsonrpsee_types::ErrorObject<'static>: From<::Error>, + { + if let Some(block) = self.eth_api.recovered_block(block_id).await? { let block_hash = block.hash(); let block_number = block.number(); let base_fee_per_gas = block.base_fee_per_gas(); @@ -172,39 +141,19 @@ where .collect(); Ok(Some(system_txs)) } else { - // hl-node returns an error if the block is not found - Err(ErrorObject::owned( - INTERNAL_ERROR_CODE, - format!("invalid block height: {id:?}"), - Some(()), - )) + Ok(None) } } - /// Returns the receipts for the system transactions for a given block hash. - async fn get_evm_system_txs_receipts_by_block_hash( + async fn get_system_txs_receipts_by_block_id( &self, - hash: B256, - ) -> RpcResult>>> { - trace!(target: "rpc::eth", ?hash, "Serving eth_getEvmSystemTxsReceiptsByBlockHash"); - match self - .get_evm_system_txs_receipts_by_block_number(Some(BlockId::Hash(hash.into()))) - .await - { - Ok(Some(receipts)) => Ok(Some(receipts)), - _ => Ok(None), - } - } - - /// Returns the receipts for the system transactions for a given block number, or the latest - /// block if no block - async fn get_evm_system_txs_receipts_by_block_number( - &self, - block_id: Option, - ) -> RpcResult>>> { - trace!(target: "rpc::eth", ?block_id, "Serving eth_getEvmSystemTxsReceiptsByBlockNumber"); + block_id: BlockId, + ) -> RpcResult>>> + where + jsonrpsee_types::ErrorObject<'static>: From<::Error>, + { if let Some((block, receipts)) = - EthBlocks::load_block_and_receipts(&self.eth_api, block_id.unwrap_or_default()).await? + EthBlocks::load_block_and_receipts(&self.eth_api, block_id).await? { let block_number = block.number; let base_fee = block.base_fee_per_gas; @@ -249,11 +198,87 @@ where let receipts = self.eth_api.tx_resp_builder().convert_receipts(inputs)?; Ok(Some(receipts)) } else { - Err(ErrorObject::owned( + Ok(None) + } + } +} + +#[async_trait] +impl + EthSystemTransactionApiServer, RpcReceipt> + for HlSystemTransactionExt +where + jsonrpsee_types::ErrorObject<'static>: From<::Error>, +{ + /// Returns the system transactions for a given block hash. + /// Semi-compliance with the `eth_getSystemTxsByBlockHash` RPC method introduced by hl-node. + /// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc + /// + /// NOTE: Method name differs from hl-node because we retrieve transaction data from EVM + /// (signature recovery for 'from' address, EVM hash calculation) rather than HyperCore. + async fn get_evm_system_txs_by_block_hash( + &self, + hash: B256, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getEvmSystemTxsByBlockHash"); + match self.get_system_txs_by_block_id(BlockId::Hash(hash.into())).await { + Ok(txs) => Ok(txs), + // hl-node returns none if the block is not found + Err(_) => Ok(None), + } + } + + /// Returns the system transactions for a given block number, or the latest block if no block + /// number is provided. Semi-compliance with the `eth_getSystemTxsByBlockNumber` RPC method + /// introduced by hl-node. https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc + /// + /// NOTE: Method name differs from hl-node because we retrieve transaction data from EVM + /// (signature recovery for 'from' address, EVM hash calculation) rather than HyperCore. + async fn get_evm_system_txs_by_block_number( + &self, + id: Option, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?id, "Serving eth_getEvmSystemTxsByBlockNumber"); + match self.get_system_txs_by_block_id(id.unwrap_or_default()).await? { + Some(txs) => Ok(Some(txs)), + None => { + // hl-node returns an error if the block is not found + Err(ErrorObject::owned( + INTERNAL_ERROR_CODE, + format!("invalid block height: {id:?}"), + Some(()), + )) + } + } + } + + /// Returns the receipts for the system transactions for a given block hash. + async fn get_evm_system_txs_receipts_by_block_hash( + &self, + hash: B256, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getEvmSystemTxsReceiptsByBlockHash"); + match self.get_system_txs_receipts_by_block_id(BlockId::Hash(hash.into())).await { + Ok(receipts) => Ok(receipts), + // hl-node returns none if the block is not found + Err(_) => Ok(None), + } + } + + /// Returns the receipts for the system transactions for a given block number, or the latest + /// block if no block + async fn get_evm_system_txs_receipts_by_block_number( + &self, + block_id: Option, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?block_id, "Serving eth_getEvmSystemTxsReceiptsByBlockNumber"); + match self.get_system_txs_receipts_by_block_id(block_id.unwrap_or_default()).await? { + Some(receipts) => Ok(Some(receipts)), + None => Err(ErrorObject::owned( INTERNAL_ERROR_CODE, format!("invalid block height: {block_id:?}"), Some(()), - )) + )), } } }