From 159653e93069dc606162d49b6c2887753476c3c8 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:17:34 +0000 Subject: [PATCH 1/7] chore: Add reth-rpc as dependency --- Cargo.lock | 319 +---------------------------------------------------- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 319 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b29972749..6948a82a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,34 +349,6 @@ dependencies = [ "serde", ] -[[package]] -name = "alloy-op-evm" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588a87b77b30452991151667522d2f2f724cec9c2ec6602e4187bc97f66d8095" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-evm", - "alloy-op-hardforks", - "alloy-primitives", - "auto_impl", - "op-alloy-consensus", - "op-revm", - "revm", -] - -[[package]] -name = "alloy-op-hardforks" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9a510692bef4871797062ca09ec7873c45dc68c7f3f72291165320f53606a3" -dependencies = [ - "alloy-chains", - "alloy-hardforks", - "auto_impl", -] - [[package]] name = "alloy-primitives" version = "1.2.1" @@ -5831,10 +5803,8 @@ checksum = "a8719d9b783b29cfa1cf8d591b894805786b9ab4940adc700a57fd0d5b721cf5" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-network", "alloy-primitives", "alloy-rlp", - "alloy-rpc-types-eth", "alloy-serde", "arbitrary", "derive_more", @@ -5843,57 +5813,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "op-alloy-flz" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" - -[[package]] -name = "op-alloy-network" -version = "0.18.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839a7a1826dc1d38fdf9c6d30d1f4ed8182c63816c97054e5815206f1ebf08c7" -dependencies = [ - "alloy-consensus", - "alloy-network", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-types-eth", - "alloy-signer", - "op-alloy-consensus", - "op-alloy-rpc-types", -] - -[[package]] -name = "op-alloy-rpc-jsonrpsee" -version = "0.18.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9d3de5348e2b34366413412f1f1534dc6b10d2cf6e8e1d97c451749c0c81c0" -dependencies = [ - "alloy-primitives", - "jsonrpsee", -] - -[[package]] -name = "op-alloy-rpc-types" -version = "0.18.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9640f9e78751e13963762a4a44c846e9ec7974b130c29a51706f40503fe49152" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network-primitives", - "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-serde", - "derive_more", - "op-alloy-consensus", - "serde", - "serde_json", - "thiserror 2.0.12", -] - [[package]] name = "op-alloy-rpc-types-engine" version = "0.18.9" @@ -5905,12 +5824,10 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", "op-alloy-consensus", - "serde", "snap", "thiserror 2.0.12", ] @@ -8419,128 +8336,6 @@ dependencies = [ "reth-trie-db", ] -[[package]] -name = "reth-optimism-chainspec" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-chains", - "alloy-consensus", - "alloy-eips", - "alloy-genesis", - "alloy-hardforks", - "alloy-primitives", - "derive_more", - "op-alloy-rpc-types", - "reth-chainspec", - "reth-ethereum-forks", - "reth-network-peers", - "reth-optimism-forks", - "reth-optimism-primitives", - "reth-primitives-traits", - "serde_json", -] - -[[package]] -name = "reth-optimism-consensus" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "alloy-trie", - "op-alloy-consensus", - "reth-chainspec", - "reth-consensus", - "reth-consensus-common", - "reth-execution-types", - "reth-optimism-forks", - "reth-optimism-primitives", - "reth-primitives-traits", - "reth-storage-api", - "reth-storage-errors", - "reth-trie-common", - "revm", - "thiserror 2.0.12", - "tracing", -] - -[[package]] -name = "reth-optimism-evm" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-evm", - "alloy-op-evm", - "alloy-primitives", - "op-alloy-consensus", - "op-revm", - "reth-chainspec", - "reth-evm", - "reth-execution-errors", - "reth-execution-types", - "reth-optimism-chainspec", - "reth-optimism-consensus", - "reth-optimism-forks", - "reth-optimism-primitives", - "reth-primitives-traits", - "revm", - "thiserror 2.0.12", -] - -[[package]] -name = "reth-optimism-forks" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-op-hardforks", - "alloy-primitives", - "once_cell", - "reth-ethereum-forks", -] - -[[package]] -name = "reth-optimism-payload-builder" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "alloy-rlp", - "alloy-rpc-types-debug", - "alloy-rpc-types-engine", - "derive_more", - "op-alloy-consensus", - "op-alloy-rpc-types-engine", - "reth-basic-payload-builder", - "reth-chain-state", - "reth-chainspec", - "reth-evm", - "reth-execution-types", - "reth-optimism-evm", - "reth-optimism-forks", - "reth-optimism-primitives", - "reth-optimism-txpool", - "reth-payload-builder", - "reth-payload-builder-primitives", - "reth-payload-primitives", - "reth-payload-util", - "reth-payload-validator", - "reth-primitives-traits", - "reth-revm", - "reth-storage-api", - "reth-transaction-pool", - "revm", - "serde", - "sha2 0.10.9", - "thiserror 2.0.12", - "tracing", -] - [[package]] name = "reth-optimism-primitives" version = "1.5.0" @@ -8552,7 +8347,6 @@ dependencies = [ "alloy-rlp", "arbitrary", "bytes", - "modular-bitfield", "op-alloy-consensus", "reth-codecs", "reth-primitives-traits", @@ -8561,102 +8355,6 @@ dependencies = [ "serde_with", ] -[[package]] -name = "reth-optimism-rpc" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-json-rpc", - "alloy-primitives", - "alloy-rpc-client", - "alloy-rpc-types-debug", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-transport", - "alloy-transport-http", - "async-trait", - "derive_more", - "eyre", - "jsonrpsee", - "jsonrpsee-core", - "jsonrpsee-types", - "metrics", - "op-alloy-consensus", - "op-alloy-network", - "op-alloy-rpc-jsonrpsee", - "op-alloy-rpc-types", - "op-alloy-rpc-types-engine", - "op-revm", - "parking_lot", - "reqwest", - "reth-chain-state", - "reth-chainspec", - "reth-evm", - "reth-metrics", - "reth-network-api", - "reth-node-api", - "reth-node-builder", - "reth-optimism-evm", - "reth-optimism-forks", - "reth-optimism-payload-builder", - "reth-optimism-primitives", - "reth-optimism-txpool", - "reth-primitives-traits", - "reth-rpc", - "reth-rpc-api", - "reth-rpc-engine-api", - "reth-rpc-eth-api", - "reth-rpc-eth-types", - "reth-rpc-server-types", - "reth-storage-api", - "reth-tasks", - "reth-transaction-pool", - "revm", - "serde_json", - "thiserror 2.0.12", - "tokio", - "tower", - "tracing", -] - -[[package]] -name = "reth-optimism-txpool" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-json-rpc", - "alloy-primitives", - "alloy-rpc-client", - "alloy-rpc-types-eth", - "alloy-serde", - "c-kzg", - "derive_more", - "futures-util", - "metrics", - "op-alloy-consensus", - "op-alloy-flz", - "op-alloy-rpc-types", - "op-revm", - "parking_lot", - "reth-chain-state", - "reth-chainspec", - "reth-metrics", - "reth-optimism-evm", - "reth-optimism-forks", - "reth-optimism-primitives", - "reth-primitives-traits", - "reth-storage-api", - "reth-transaction-pool", - "serde", - "thiserror 2.0.12", - "tokio", - "tracing", -] - [[package]] name = "reth-payload-builder" version = "1.5.0" @@ -8708,16 +8406,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "reth-payload-util" -version = "1.5.0" -source = "git+https://github.com/sprites0/reth?rev=fc754e5983f055365325dc9a04632d5ba2c4a8bc#fc754e5983f055365325dc9a04632d5ba2c4a8bc" -dependencies = [ - "alloy-consensus", - "alloy-primitives", - "reth-transaction-pool", -] - [[package]] name = "reth-payload-validator" version = "1.5.0" @@ -9074,13 +8762,8 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "jsonrpsee-types", - "op-alloy-consensus", - "op-alloy-rpc-types", - "op-revm", "reth-evm", - "reth-optimism-primitives", "reth-primitives-traits", - "reth-storage-api", "revm-context", "thiserror 2.0.12", ] @@ -9649,12 +9332,12 @@ dependencies = [ "reth-network-peers", "reth-node-core", "reth-node-ethereum", - "reth-optimism-rpc", "reth-payload-primitives", "reth-primitives", "reth-primitives-traits", "reth-provider", "reth-revm", + "reth-rpc", "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-tracing", diff --git a/Cargo.toml b/Cargo.toml index 46ea479b1..260af59e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,11 +36,11 @@ reth-network-p2p = { git = "https://github.com/sprites0/reth", rev = "fc754e5983 reth-network-api = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-node-ethereum = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-network-peers = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } -reth-optimism-rpc = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-payload-primitives = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-primitives = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-primitives-traits = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-provider = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc", features = ["test-utils"] } +reth-rpc = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-rpc-eth-api = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-rpc-engine-api = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } reth-tracing = { git = "https://github.com/sprites0/reth", rev = "fc754e5983f055365325dc9a04632d5ba2c4a8bc" } From b050f3fc1823c27d383d000fa54b655048412e56 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:18:11 +0000 Subject: [PATCH 2/7] feat: compliance on http/ws logs --- src/hl_node_compliance.rs | 304 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 305 insertions(+) create mode 100644 src/hl_node_compliance.rs diff --git a/src/hl_node_compliance.rs b/src/hl_node_compliance.rs new file mode 100644 index 000000000..a26274c62 --- /dev/null +++ b/src/hl_node_compliance.rs @@ -0,0 +1,304 @@ +/// We need to override the following methods: +/// Filter: +/// - eth_getLogs +/// - eth_subscribe +/// +/// Block (handled by HlEthApi already): +/// - eth_getBlockByNumber/eth_getBlockByHash +/// - eth_getBlockReceipts +use crate::HlBlock; +use alloy_rpc_types::{ + pubsub::{Params, SubscriptionKind}, + Filter, FilterChanges, FilterId, Log, PendingTransactionFilterKind, +}; +use jsonrpsee::{PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink}; +use jsonrpsee_core::{async_trait, RpcResult}; +use jsonrpsee_types::ErrorObject; +use reth::{ + api::FullNodeComponents, builder::rpc::RpcContext, rpc::result::internal_rpc_err, + tasks::TaskSpawner, +}; +use reth_network::NetworkInfo; +use reth_primitives::NodePrimitives; +use reth_primitives_traits::SignedTransaction as _; +use reth_provider::{BlockIdReader, BlockReader, TransactionsProvider}; +use reth_rpc::{EthFilter, EthPubSub}; +use reth_rpc_eth_api::{ + EthApiServer, EthFilterApiServer, EthPubSubApiServer, FullEthApiTypes, RpcBlock, RpcHeader, + RpcNodeCore, RpcNodeCoreExt, RpcReceipt, RpcTransaction, RpcTxReq, +}; +use serde::Serialize; +use std::{collections::HashSet, sync::Arc}; +use tokio_stream::{Stream, StreamExt}; +use tracing::{info, trace}; + +pub trait EthWrapper: + EthApiServer< + RpcTxReq, + RpcTransaction, + RpcBlock, + RpcReceipt, + RpcHeader, + > + FullEthApiTypes + + RpcNodeCoreExt< + Provider: BlockIdReader + BlockReader, + Primitives: NodePrimitives< + SignedTx = <::Provider as TransactionsProvider>::Transaction, + >, + Network: NetworkInfo, + > + 'static +{ +} + +impl < + T: + EthApiServer< + RpcTxReq, + RpcTransaction, + RpcBlock, + RpcReceipt, + RpcHeader, + > + FullEthApiTypes + + RpcNodeCoreExt< + Provider: BlockIdReader + BlockReader, + Primitives: NodePrimitives< + SignedTx = <::Provider as TransactionsProvider>::Transaction, + >, + Network: NetworkInfo, + > + 'static +> EthWrapper for T { +} + +pub struct HlNodeFilterHttp { + filter: Arc>, + provider: Arc, +} + +impl HlNodeFilterHttp { + pub fn new(filter: Arc>, provider: Arc) -> Self { + Self { filter, provider } + } +} + +#[async_trait] +impl EthFilterApiServer> + for HlNodeFilterHttp +{ + /// Handler for `eth_newFilter` + async fn new_filter(&self, filter: Filter) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_newFilter"); + self.filter.new_filter(filter).await + } + + /// Handler for `eth_newBlockFilter` + async fn new_block_filter(&self) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_newBlockFilter"); + self.filter.new_block_filter().await + } + + /// Handler for `eth_newPendingTransactionFilter` + async fn new_pending_transaction_filter( + &self, + kind: Option, + ) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_newPendingTransactionFilter"); + self.filter.new_pending_transaction_filter(kind).await + } + + /// Handler for `eth_getFilterChanges` + async fn filter_changes( + &self, + id: FilterId, + ) -> RpcResult>> { + trace!(target: "rpc::eth", "Serving eth_getFilterChanges"); + self.filter.filter_changes(id).await.map_err(ErrorObject::from) + } + + /// Returns an array of all logs matching filter with given id. + /// + /// Returns an error if no matching log filter exists. + /// + /// Handler for `eth_getFilterLogs` + async fn filter_logs(&self, id: FilterId) -> RpcResult> { + trace!(target: "rpc::eth", "Serving eth_getFilterLogs"); + self.filter.filter_logs(id).await.map_err(ErrorObject::from) + } + + /// Handler for `eth_uninstallFilter` + async fn uninstall_filter(&self, id: FilterId) -> RpcResult { + trace!(target: "rpc::eth", "Serving eth_uninstallFilter"); + self.filter.uninstall_filter(id).await + } + + /// Returns logs matching given filter object. + /// + /// Handler for `eth_getLogs` + async fn logs(&self, filter: Filter) -> RpcResult> { + trace!(target: "rpc::eth", "Serving eth_getLogs"); + let mut logs = self.filter.logs(filter).await?; + + let block_numbers: HashSet<_> = logs.iter().map(|log| log.block_number.unwrap()).collect(); + info!("block_numbers: {:?}", block_numbers); + let system_tx_hashes: HashSet<_> = block_numbers + .into_iter() + .flat_map(|block_number| { + let block = self.provider.block_by_number(block_number).unwrap().unwrap(); + let transactions = block.body.transactions().collect::>(); + transactions + .iter() + .filter(|tx| tx.is_system_transaction()) + .map(|tx| tx.tx_hash().clone()) + .collect::>() + }) + .collect(); + + logs.retain(|log| !system_tx_hashes.contains(&log.transaction_hash.unwrap())); + Ok(logs) + } +} + +pub struct HlNodeFilterWs { + pubsub: Arc>, + provider: Arc, + subscription_task_spawner: Box, +} + +impl HlNodeFilterWs { + pub fn new( + pubsub: Arc>, + provider: Arc, + subscription_task_spawner: Box, + ) -> Self { + Self { pubsub, provider, subscription_task_spawner } + } +} + +#[async_trait] +impl EthPubSubApiServer> + for HlNodeFilterWs +{ + /// Handler for `eth_subscribe` + async fn subscribe( + &self, + pending: PendingSubscriptionSink, + kind: SubscriptionKind, + params: Option, + ) -> jsonrpsee::core::SubscriptionResult { + let sink = pending.accept().await?; + let pubsub = self.pubsub.clone(); + let provider = self.provider.clone(); + self.subscription_task_spawner.spawn(Box::pin(async move { + if kind == SubscriptionKind::Logs { + // if no params are provided, used default filter params + let filter = match params { + Some(Params::Logs(filter)) => *filter, + Some(Params::Bool(_)) => { + return; + } + _ => Default::default(), + }; + let _ = pipe_from_stream( + sink, + pubsub + .log_stream(filter) + .filter(|log| not_from_system_tx::(log, &provider)), + ) + .await; + } else { + let _ = pubsub.handle_accepted(sink, kind, params).await; + }; + })); + + Ok(()) + } +} + +fn not_from_system_tx(log: &Log, provider: &Eth::Provider) -> bool { + let block = provider.block_by_number(log.block_number.unwrap()).unwrap().unwrap(); + let transactions = block.body.transactions().collect::>(); + transactions + .iter() + .filter(|tx| tx.is_system_transaction()) + .map(|tx| tx.tx_hash().clone()) + .find(|tx_hash| tx_hash == &log.transaction_hash.unwrap()) + .is_none() +} + +/// Helper to convert a serde error into an [`ErrorObject`] +#[derive(Debug, thiserror::Error)] +#[error("Failed to serialize subscription item: {0}")] +pub struct SubscriptionSerializeError(#[from] serde_json::Error); + +impl SubscriptionSerializeError { + const fn new(err: serde_json::Error) -> Self { + Self(err) + } +} + +impl From for ErrorObject<'static> { + fn from(value: SubscriptionSerializeError) -> Self { + internal_rpc_err(value.to_string()) + } +} + +async fn pipe_from_stream( + sink: SubscriptionSink, + mut stream: St, +) -> Result<(), ErrorObject<'static>> +where + St: Stream + Unpin, + T: Serialize, +{ + loop { + tokio::select! { + _ = sink.closed() => { + // connection dropped + break Ok(()) + }, + maybe_item = stream.next() => { + let item = match maybe_item { + Some(item) => item, + None => { + // stream ended + break Ok(()) + }, + }; + let msg = SubscriptionMessage::new( + sink.method_name(), + sink.subscription_id(), + &item + ).map_err(SubscriptionSerializeError::new)?; + + if sink.send(msg).await.is_err() { + break Ok(()); + } + } + } + } +} + +pub fn install_hl_node_compliance( + ctx: RpcContext, +) -> Result<(), eyre::Error> +where + Node: FullNodeComponents, + Node::Provider: BlockIdReader + BlockReader, + EthApi: EthWrapper, +{ + ctx.modules.replace_configured( + HlNodeFilterHttp::new( + Arc::new(ctx.registry.eth_handlers().filter.clone()), + Arc::new(ctx.registry.eth_api().provider().clone()), + ) + .into_rpc(), + )?; + ctx.modules.replace_configured( + HlNodeFilterWs::new( + Arc::new(ctx.registry.eth_handlers().pubsub.clone()), + Arc::new(ctx.registry.eth_api().provider().clone()), + Box::new(ctx.node().task_executor().clone()), + ) + .into_rpc(), + )?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index cbfdf5569..df6fa7c63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod chainspec; pub mod consensus; mod evm; mod hardforks; +pub mod hl_node_compliance; pub mod node; pub mod pseudo_peer; pub mod tx_forwarder; From 77320a2b0395e80ed76f855662e7e27efd7c1e92 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:18:29 +0000 Subject: [PATCH 3/7] feat: compliance on http/ws transaction list --- src/node/rpc/block.rs | 79 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/src/node/rpc/block.rs b/src/node/rpc/block.rs index 98495ce66..95b3bdd20 100644 --- a/src/node/rpc/block.rs +++ b/src/node/rpc/block.rs @@ -1,9 +1,12 @@ +use std::{future::Future, sync::Arc}; + use crate::{ chainspec::HlChainSpec, node::{ primitives::TransactionSigned, rpc::{HlEthApi, HlNodeCore}, }, + HlBlock, }; use alloy_consensus::{BlockHeader, ReceiptEnvelope, TxType}; use alloy_primitives::B256; @@ -23,11 +26,11 @@ use reth::{ }; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_evm::{ConfigureEvm, NextBlockEnvAttributes}; -use reth_primitives::NodePrimitives; -use reth_primitives_traits::{BlockBody as _, SignedTransaction as _}; +use reth_primitives::{NodePrimitives, SealedBlock}; +use reth_primitives_traits::{BlockBody as _, RecoveredBlock, SignedTransaction as _}; use reth_provider::{ - BlockReader, ChainSpecProvider, HeaderProvider, ProviderBlock, ProviderReceipt, ProviderTx, - StateProviderFactory, + BlockIdReader, BlockReader, ChainSpecProvider, HeaderProvider, ProviderBlock, ProviderReceipt, + ProviderTx, StateProviderFactory, }; use reth_rpc_eth_api::{ helpers::{EthBlocks, LoadBlock, LoadPendingBlock, LoadReceipt, SpawnBlocking}, @@ -35,6 +38,10 @@ use reth_rpc_eth_api::{ FromEthApiError, RpcConvert, RpcNodeCore, RpcNodeCoreExt, RpcReceipt, }; +fn is_system_tx(tx: &TransactionSigned) -> bool { + tx.is_system_transaction() +} + impl EthBlocks for HlEthApi where Self: LoadBlock< @@ -64,6 +71,7 @@ where .transactions() .iter() .zip(receipts.iter()) + .filter(|(tx, _)| !is_system_tx(tx)) .enumerate() .map(|(idx, (tx, receipt))| { let meta = TransactionMeta { @@ -101,9 +109,70 @@ where Pool: TransactionPool< Transaction: PoolTransaction>, >, - >, + > + RpcNodeCore>, N: HlNodeCore, { + fn recovered_block( + &self, + block_id: BlockId, + ) -> impl Future< + Output = Result< + Option::Block>>>, + Self::Error, + >, + > + Send { + let hl_node_compliant = self.hl_node_compliant; + async move { + // Copy of LoadBlock::recovered_block, but with --hl-node-compliant support + if block_id.is_pending() { + return Ok(None); + } + + let block_hash = match self + .provider() + .block_hash_for_id(block_id) + .map_err(Self::Error::from_eth_err)? + { + Some(block_hash) => block_hash, + None => return Ok(None), + }; + + let recovered_block = self + .cache() + .get_recovered_block(block_hash) + .await + .map_err(Self::Error::from_eth_err)?; + + if let Some(recovered_block) = recovered_block { + let recovered_block = if hl_node_compliant { + filter_if_hl_node_compliant(&*recovered_block) + } else { + (*recovered_block).clone() + }; + return Ok(Some(std::sync::Arc::new(recovered_block))); + } + + Ok(None) + } + } +} + +fn filter_if_hl_node_compliant( + recovered_block: &RecoveredBlock, +) -> RecoveredBlock { + let sealed_block = recovered_block.sealed_block(); + let transactions = sealed_block.body().transactions(); + let to_skip = transactions + .iter() + .position(|tx| !tx.is_system_transaction()) + .unwrap_or(transactions.len()); + + let mut new_block: HlBlock = sealed_block.clone_block().into(); + new_block.body.transactions.drain(..to_skip); + let new_sealed_block = SealedBlock::new_unchecked(new_block, sealed_block.hash()); + let new_senders = recovered_block.senders()[to_skip..].to_vec(); + + RecoveredBlock::new_sealed(new_sealed_block, new_senders) } impl LoadPendingBlock for HlEthApi From 7918101d65b8d6876a0ff3ad71097629e453d3e7 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:19:32 +0000 Subject: [PATCH 4/7] feat: Add --hl-node-compliant --- src/main.rs | 11 +++++++++-- src/node/cli.rs | 3 +++ src/node/mod.rs | 18 ++++++++++++++++-- src/node/rpc/mod.rs | 14 ++++++++++++-- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1f91537ec..8a31f6dbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use clap::Parser; use reth::builder::NodeHandle; use reth_hl::{ chainspec::parser::HlChainSpecParser, + hl_node_compliance::install_hl_node_compliance, node::{ cli::{Cli, HlNodeArgs}, storage::tables::Tables, @@ -26,10 +27,11 @@ fn main() -> eyre::Result<()> { Cli::::parse().run(|builder, ext| async move { builder.builder.database.create_tables_for::()?; - let (node, engine_handle_tx) = HlNode::new(ext.block_source_args.parse().await?); + let (node, engine_handle_tx) = + HlNode::new(ext.block_source_args.parse().await?, ext.hl_node_compliant); let NodeHandle { node, node_exit_future: exit_future } = builder .node(node) - .extend_rpc_modules(|ctx| { + .extend_rpc_modules(move |ctx| { let upstream_rpc_url = ext.upstream_rpc_url; if let Some(upstream_rpc_url) = upstream_rpc_url { ctx.modules.replace_configured( @@ -38,6 +40,11 @@ fn main() -> eyre::Result<()> { info!("Transaction forwarding enabled"); } + + if ext.hl_node_compliant { + install_hl_node_compliance(ctx)?; + } + Ok(()) }) .launch() diff --git a/src/node/cli.rs b/src/node/cli.rs index fc63a4ef2..48c0ecc70 100644 --- a/src/node/cli.rs +++ b/src/node/cli.rs @@ -34,6 +34,9 @@ pub struct HlNodeArgs { #[arg(long, env = "UPSTREAM_RPC_URL")] pub upstream_rpc_url: Option, + + #[arg(long, env = "HL_NODE_COMPLIANT")] + pub hl_node_compliant: bool, } /// The main reth_hl cli interface. diff --git a/src/node/mod.rs b/src/node/mod.rs index d59850870..535592cc9 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -51,14 +51,23 @@ pub struct HlNode { engine_handle_rx: Arc>>>>, block_source_config: BlockSourceConfig, + hl_node_compliant: bool, } impl HlNode { pub fn new( block_source_config: BlockSourceConfig, + hl_node_compliant: bool, ) -> (Self, oneshot::Sender>) { let (tx, rx) = oneshot::channel(); - (Self { engine_handle_rx: Arc::new(Mutex::new(Some(rx))), block_source_config }, tx) + ( + Self { + engine_handle_rx: Arc::new(Mutex::new(Some(rx))), + block_source_config, + hl_node_compliant, + }, + tx, + ) } } @@ -121,7 +130,12 @@ where } fn add_ons(&self) -> Self::AddOns { - HlNodeAddOns::default() + HlNodeAddOns::new( + HlEthApiBuilder { hl_node_compliant: self.hl_node_compliant }, + Default::default(), + Default::default(), + Default::default(), + ) } } diff --git a/src/node/rpc/mod.rs b/src/node/rpc/mod.rs index 42ffaf1f8..2c2a8c402 100644 --- a/src/node/rpc/mod.rs +++ b/src/node/rpc/mod.rs @@ -250,9 +250,18 @@ where } /// Builds [`HlEthApi`] for HL. -#[derive(Debug, Default)] +#[derive(Debug)] #[non_exhaustive] -pub struct HlEthApiBuilder; +pub struct HlEthApiBuilder { + /// Whether the node is in HL node compliant mode. + pub(crate) hl_node_compliant: bool, +} + +impl Default for HlEthApiBuilder { + fn default() -> Self { + Self { hl_node_compliant: false } + } +} impl EthApiBuilder for HlEthApiBuilder where @@ -280,6 +289,7 @@ where Ok(HlEthApi { inner: Arc::new(HlEthApiInner { eth_api }), tx_resp_builder: Default::default(), + hl_node_compliant: self.hl_node_compliant, }) } } From 052a889c0ee16e533ae914770fa0250b6223975a Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:19:39 +0000 Subject: [PATCH 5/7] refactor: Remove unnecessary deps --- src/node/rpc/mod.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/node/rpc/mod.rs b/src/node/rpc/mod.rs index 2c2a8c402..ef09f7118 100644 --- a/src/node/rpc/mod.rs +++ b/src/node/rpc/mod.rs @@ -9,7 +9,7 @@ use reth::{ primitives::EthereumHardforks, providers::ChainSpecProvider, rpc::{ - eth::{DevSigner, FullEthApiServer}, + eth::{core::EthApiInner, DevSigner, FullEthApiServer}, server_types::eth::{EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle}, }, tasks::{ @@ -34,8 +34,6 @@ use reth_rpc_eth_api::{ }; use std::{fmt, sync::Arc}; -use reth_optimism_rpc::eth::EthApiNodeBackend; - mod block; mod call; pub mod engine_api; @@ -45,6 +43,14 @@ mod transaction; pub trait HlNodeCore: RpcNodeCore {} impl HlNodeCore for T where T: RpcNodeCore {} +/// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API. +pub type EthApiNodeBackend = EthApiInner< + ::Provider, + ::Pool, + ::Network, + ::Evm, +>; + /// Container type `HlEthApi` #[allow(missing_debug_implementations)] pub(crate) struct HlEthApiInner { @@ -58,6 +64,8 @@ pub struct HlEthApi { pub(crate) inner: Arc>, /// Converter for RPC types. tx_resp_builder: RpcConverter, + /// Whether the node is in HL node compliant mode. + pub(crate) hl_node_compliant: bool, } impl fmt::Debug for HlEthApi { From dba4557140d8ac0377e54942faa6df4e69fcf23c Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:21:37 +0000 Subject: [PATCH 6/7] doc: Add cli arg descriptions --- src/node/cli.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/node/cli.rs b/src/node/cli.rs index 48c0ecc70..7fd95038d 100644 --- a/src/node/cli.rs +++ b/src/node/cli.rs @@ -32,9 +32,16 @@ pub struct HlNodeArgs { #[command(flatten)] pub block_source_args: BlockSourceArgs, + /// Upstream RPC URL to forward incoming transactions. #[arg(long, env = "UPSTREAM_RPC_URL")] pub upstream_rpc_url: Option, + /// Enable hl-node compliant mode. + /// + /// This option + /// 1. filters out system transactions from block transaction list. + /// 2. filters out logs that are not from the block's transactions. + /// 3. filters out logs and transactions from subscription. #[arg(long, env = "HL_NODE_COMPLIANT")] pub hl_node_compliant: bool, } From c31b0c4b8a466297d936534824b92a52e382a6c0 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:22:09 +0000 Subject: [PATCH 7/7] chore: clippy --- src/hl_node_compliance.rs | 8 +++----- src/node/rpc/block.rs | 4 ++-- src/node/rpc/mod.rs | 8 +------- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/hl_node_compliance.rs b/src/hl_node_compliance.rs index a26274c62..6f29e7d98 100644 --- a/src/hl_node_compliance.rs +++ b/src/hl_node_compliance.rs @@ -147,7 +147,7 @@ impl EthFilterApiServer> transactions .iter() .filter(|tx| tx.is_system_transaction()) - .map(|tx| tx.tx_hash().clone()) + .map(|tx| *tx.tx_hash()) .collect::>() }) .collect(); @@ -216,12 +216,10 @@ impl EthPubSubApiServer> fn not_from_system_tx(log: &Log, provider: &Eth::Provider) -> bool { let block = provider.block_by_number(log.block_number.unwrap()).unwrap().unwrap(); let transactions = block.body.transactions().collect::>(); - transactions + !transactions .iter() .filter(|tx| tx.is_system_transaction()) - .map(|tx| tx.tx_hash().clone()) - .find(|tx_hash| tx_hash == &log.transaction_hash.unwrap()) - .is_none() + .map(|tx| *tx.tx_hash()).any(|tx_hash| tx_hash == log.transaction_hash.unwrap()) } /// Helper to convert a serde error into an [`ErrorObject`] diff --git a/src/node/rpc/block.rs b/src/node/rpc/block.rs index 95b3bdd20..65220afb2 100644 --- a/src/node/rpc/block.rs +++ b/src/node/rpc/block.rs @@ -145,7 +145,7 @@ where if let Some(recovered_block) = recovered_block { let recovered_block = if hl_node_compliant { - filter_if_hl_node_compliant(&*recovered_block) + filter_if_hl_node_compliant(&recovered_block) } else { (*recovered_block).clone() }; @@ -167,7 +167,7 @@ fn filter_if_hl_node_compliant( .position(|tx| !tx.is_system_transaction()) .unwrap_or(transactions.len()); - let mut new_block: HlBlock = sealed_block.clone_block().into(); + let mut new_block: HlBlock = sealed_block.clone_block(); new_block.body.transactions.drain(..to_skip); let new_sealed_block = SealedBlock::new_unchecked(new_block, sealed_block.hash()); let new_senders = recovered_block.senders()[to_skip..].to_vec(); diff --git a/src/node/rpc/mod.rs b/src/node/rpc/mod.rs index ef09f7118..06fc4e680 100644 --- a/src/node/rpc/mod.rs +++ b/src/node/rpc/mod.rs @@ -258,19 +258,13 @@ where } /// Builds [`HlEthApi`] for HL. -#[derive(Debug)] +#[derive(Debug, Default)] #[non_exhaustive] pub struct HlEthApiBuilder { /// Whether the node is in HL node compliant mode. pub(crate) hl_node_compliant: bool, } -impl Default for HlEthApiBuilder { - fn default() -> Self { - Self { hl_node_compliant: false } - } -} - impl EthApiBuilder for HlEthApiBuilder where N: FullNodeComponents,