From 412c38a8cdeba83b4980c34c11d3ba12311dada0 Mon Sep 17 00:00:00 2001 From: Quertyy <98064975+Quertyy@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:24:28 +0200 Subject: [PATCH 1/3] chore(rpc): add eth_getSystemTxsByBlockNumber and eth_getSystemTxsByBlockNumber rpc method --- src/addons/hl_node_compliance.rs | 94 +++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/src/addons/hl_node_compliance.rs b/src/addons/hl_node_compliance.rs index 41e618d29..6c44487bf 100644 --- a/src/addons/hl_node_compliance.rs +++ b/src/addons/hl_node_compliance.rs @@ -7,13 +7,14 @@ //! For non-system transactions, we can just return the log as is, and the client will //! adjust the transaction index accordingly. -use alloy_consensus::{transaction::TransactionMeta, TxReceipt}; +use alloy_consensus::{transaction::TransactionMeta, BlockHeader, TxReceipt}; use alloy_eips::{BlockId, BlockNumberOrTag}; use alloy_json_rpc::RpcObject; use alloy_primitives::{B256, U256}; use alloy_rpc_types::{ pubsub::{Params, SubscriptionKind}, BlockTransactions, Filter, FilterChanges, FilterId, Log, PendingTransactionFilterKind, + TransactionInfo, }; use jsonrpsee::{proc_macros::rpc, PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink}; use jsonrpsee_core::{async_trait, RpcResult}; @@ -71,6 +72,88 @@ impl EthWrapper for T where { } +#[rpc(server, namespace = "eth")] +#[async_trait] +pub trait EthSystemTransactionApi { + #[method(name = "getSystemTxsByBlockHash")] + async fn get_system_txs_by_block_hash(&self, hash: B256) -> RpcResult>>; + + #[method(name = "getSystemTxsByBlockNumber")] + async fn get_system_txs_by_block_number( + &self, + block_id: Option, + ) -> RpcResult>>; +} + +pub struct HlSystemTransactionExt { + eth_api: Eth, + _marker: PhantomData, +} + +impl HlSystemTransactionExt { + pub fn new(eth_api: Eth) -> Self { + Self { eth_api, _marker: PhantomData } + } +} + +#[async_trait] +impl EthSystemTransactionApiServer> + for HlSystemTransactionExt +where + jsonrpsee_types::ErrorObject<'static>: From<::Error>, +{ + /// Returns the system transactions for a given block hash. + /// Compliance with the `eth_getSystemTxsByBlockHash` RPC method introduced by hl-node. + /// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc + async fn get_system_txs_by_block_hash( + &self, + hash: B256, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getSystemTxsByBlockHash"); + self.get_system_txs_by_block_number(Some(BlockId::Hash(hash.into()))).await + } + + /// Returns the system transactions for a given block number, or the latest block if no block + /// number is provided. Compliance with the `eth_getSystemTxsByBlockNumber` RPC method + /// introduced by hl-node. https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc + async fn get_system_txs_by_block_number( + &self, + id: Option, + ) -> RpcResult>>> { + trace!(target: "rpc::eth", ?id, "Serving eth_getSystemTxsByBlockNumber"); + + if let Some(block) = self.eth_api.recovered_block(id.unwrap_or_default()).await? { + let block_hash = block.hash(); + let block_number = block.number(); + let base_fee_per_gas = block.base_fee_per_gas(); + let system_txs = block + .transactions_with_sender() + .enumerate() + .filter_map(|(index, (signer, tx))| { + if tx.is_system_transaction() { + let tx_info = TransactionInfo { + hash: Some(*tx.tx_hash()), + block_hash: Some(block_hash), + block_number: Some(block_number), + base_fee: base_fee_per_gas, + index: Some(index as u64), + }; + self.eth_api + .tx_resp_builder() + .fill(tx.clone().with_signer(*signer), tx_info) + .ok() + } else { + None + } + }) + .collect(); + Ok(Some(system_txs)) + } else { + Ok(None) + } + } +} + pub struct HlNodeFilterHttp { filter: Arc>, provider: Arc, @@ -146,8 +229,9 @@ impl HlNodeFilterWs { } #[async_trait] -impl EthPubSubApiServer> - for HlNodeFilterWs +impl EthPubSubApiServer> for HlNodeFilterWs +where + jsonrpsee_types::error::ErrorObject<'static>: From<::Error>, { async fn subscribe( &self, @@ -473,5 +557,9 @@ where ctx.modules.replace_configured( HlNodeBlockFilterHttp::new(Arc::new(ctx.registry.eth_api().clone())).into_rpc(), )?; + + ctx.modules + .merge_configured(HlSystemTransactionExt::new(ctx.registry.eth_api().clone()).into_rpc())?; + Ok(()) } From 62dd5a71b546d7faf0a1162d264de082ccd86029 Mon Sep 17 00:00:00 2001 From: Quertyy <98064975+Quertyy@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:02:31 +0200 Subject: [PATCH 2/3] chore(rpc): change methods name --- src/addons/hl_node_compliance.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/addons/hl_node_compliance.rs b/src/addons/hl_node_compliance.rs index 6c44487bf..6d7b74a03 100644 --- a/src/addons/hl_node_compliance.rs +++ b/src/addons/hl_node_compliance.rs @@ -75,11 +75,11 @@ impl EthWrapper for T where #[rpc(server, namespace = "eth")] #[async_trait] pub trait EthSystemTransactionApi { - #[method(name = "getSystemTxsByBlockHash")] - async fn get_system_txs_by_block_hash(&self, hash: B256) -> RpcResult>>; + #[method(name = "getEvmSystemTxsByBlockHash")] + async fn get_evm_system_txs_by_block_hash(&self, hash: B256) -> RpcResult>>; - #[method(name = "getSystemTxsByBlockNumber")] - async fn get_system_txs_by_block_number( + #[method(name = "getEvmSystemTxsByBlockNumber")] + async fn get_evm_system_txs_by_block_number( &self, block_id: Option, ) -> RpcResult>>; @@ -103,24 +103,30 @@ where jsonrpsee_types::ErrorObject<'static>: From<::Error>, { /// Returns the system transactions for a given block hash. - /// Compliance with the `eth_getSystemTxsByBlockHash` RPC method introduced by hl-node. + /// Semi-compliance with the `eth_getSystemTxsByBlockHash` RPC method introduced by hl-node. /// https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc - async fn get_system_txs_by_block_hash( + /// + /// 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_getSystemTxsByBlockHash"); - self.get_system_txs_by_block_number(Some(BlockId::Hash(hash.into()))).await + trace!(target: "rpc::eth", ?hash, "Serving eth_getEvmSystemTxsByBlockHash"); + self.get_evm_system_txs_by_block_number(Some(BlockId::Hash(hash.into()))).await } /// Returns the system transactions for a given block number, or the latest block if no block - /// number is provided. Compliance with the `eth_getSystemTxsByBlockNumber` RPC method + /// 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 - async fn get_system_txs_by_block_number( + /// + /// 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_getSystemTxsByBlockNumber"); + trace!(target: "rpc::eth", ?id, "Serving eth_getEvmSystemTxsByBlockNumber"); if let Some(block) = self.eth_api.recovered_block(id.unwrap_or_default()).await? { let block_hash = block.hash(); From 707b4fb70991ef1d5415841a4151db93e74ac0c4 Mon Sep 17 00:00:00 2001 From: Quertyy <98064975+Quertyy@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:34:34 +0200 Subject: [PATCH 3/3] chore(rpc): return types compliance --- src/addons/hl_node_compliance.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/addons/hl_node_compliance.rs b/src/addons/hl_node_compliance.rs index 6d7b74a03..b76010450 100644 --- a/src/addons/hl_node_compliance.rs +++ b/src/addons/hl_node_compliance.rs @@ -18,7 +18,7 @@ use alloy_rpc_types::{ }; use jsonrpsee::{proc_macros::rpc, PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink}; use jsonrpsee_core::{async_trait, RpcResult}; -use jsonrpsee_types::ErrorObject; +use jsonrpsee_types::{error::INTERNAL_ERROR_CODE, ErrorObject}; use reth::{api::FullNodeComponents, builder::rpc::RpcContext, tasks::TaskSpawner}; use reth_primitives_traits::{BlockBody as _, SignedTransaction}; use reth_provider::{BlockIdReader, BlockReader, BlockReaderIdExt, ReceiptProvider}; @@ -113,7 +113,11 @@ where hash: B256, ) -> RpcResult>>> { trace!(target: "rpc::eth", ?hash, "Serving eth_getEvmSystemTxsByBlockHash"); - self.get_evm_system_txs_by_block_number(Some(BlockId::Hash(hash.into()))).await + 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 @@ -155,7 +159,12 @@ where .collect(); Ok(Some(system_txs)) } else { - Ok(None) + // hl-node returns an error if the block is not found + Err(ErrorObject::owned( + INTERNAL_ERROR_CODE, + format!("invalid block height: {id:?}"), + Some(()), + )) } } }