mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Compare commits
17 Commits
nb-2025100
...
010d056aad
| Author | SHA1 | Date | |
|---|---|---|---|
| 010d056aad | |||
| 821c63494e | |||
| f915aba568 | |||
| 1fe03bfc41 | |||
| 893822e5b0 | |||
| c2528ce223 | |||
| d46e808b8d | |||
| 497353fd2f | |||
| eee6eeb2fc | |||
| 611e6867bf | |||
| 6c3ed63c3c | |||
| 51924e9671 | |||
| 8f15aa311f | |||
| bc66716a41 | |||
| fc819dbba2 | |||
| 1c5a22a814 | |||
| 852e186b1a |
@ -19,62 +19,23 @@ use alloy_rpc_types::{
|
||||
TransactionInfo,
|
||||
pubsub::{Params, SubscriptionKind},
|
||||
};
|
||||
use jsonrpsee::{PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink, proc_macros::rpc};
|
||||
use jsonrpsee::{PendingSubscriptionSink, proc_macros::rpc};
|
||||
use jsonrpsee_core::{RpcResult, async_trait};
|
||||
use jsonrpsee_types::{ErrorObject, error::INTERNAL_ERROR_CODE};
|
||||
use reth::{api::FullNodeComponents, builder::rpc::RpcContext, tasks::TaskSpawner};
|
||||
use reth_primitives_traits::SignedTransaction;
|
||||
use reth_provider::{BlockIdReader, BlockReader, BlockReaderIdExt, ReceiptProvider};
|
||||
use reth_rpc::{EthFilter, EthPubSub, RpcTypes, eth::pubsub::SubscriptionSerializeError};
|
||||
use reth_rpc::{EthFilter, EthPubSub};
|
||||
use reth_rpc_eth_api::{
|
||||
EthApiServer, EthApiTypes, EthFilterApiServer, EthPubSubApiServer, FullEthApiTypes, RpcBlock,
|
||||
RpcConvert, RpcHeader, RpcNodeCoreExt, RpcReceipt, RpcTransaction, RpcTxReq,
|
||||
helpers::{EthBlocks, EthTransactions, LoadReceipt},
|
||||
transaction::ConvertReceiptInput,
|
||||
EthApiTypes, EthFilterApiServer, EthPubSubApiServer, RpcBlock, RpcConvert, RpcReceipt,
|
||||
RpcTransaction, helpers::EthBlocks, transaction::ConvertReceiptInput,
|
||||
};
|
||||
use reth_rpc_eth_types::EthApiError;
|
||||
use serde::Serialize;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
use tokio_stream::StreamExt;
|
||||
use tracing::{Instrument, trace};
|
||||
|
||||
use crate::{HlBlock, node::primitives::HlPrimitives};
|
||||
|
||||
pub trait EthWrapper:
|
||||
EthApiServer<
|
||||
RpcTxReq<Self::NetworkTypes>,
|
||||
RpcTransaction<Self::NetworkTypes>,
|
||||
RpcBlock<Self::NetworkTypes>,
|
||||
RpcReceipt<Self::NetworkTypes>,
|
||||
RpcHeader<Self::NetworkTypes>,
|
||||
> + FullEthApiTypes<
|
||||
Primitives = HlPrimitives,
|
||||
NetworkTypes: RpcTypes<TransactionResponse = alloy_rpc_types_eth::Transaction>,
|
||||
> + RpcNodeCoreExt<Provider: BlockReader<Block = HlBlock>>
|
||||
+ EthBlocks
|
||||
+ EthTransactions
|
||||
+ LoadReceipt
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> EthWrapper for T where
|
||||
T: EthApiServer<
|
||||
RpcTxReq<Self::NetworkTypes>,
|
||||
RpcTransaction<Self::NetworkTypes>,
|
||||
RpcBlock<Self::NetworkTypes>,
|
||||
RpcReceipt<Self::NetworkTypes>,
|
||||
RpcHeader<Self::NetworkTypes>,
|
||||
> + FullEthApiTypes<
|
||||
Primitives = HlPrimitives,
|
||||
NetworkTypes: RpcTypes<TransactionResponse = alloy_rpc_types_eth::Transaction>,
|
||||
> + RpcNodeCoreExt<Provider: BlockReader<Block = HlBlock>>
|
||||
+ EthBlocks
|
||||
+ EthTransactions
|
||||
+ LoadReceipt
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
use crate::addons::utils::{EthWrapper, new_headers_stream, pipe_from_stream};
|
||||
|
||||
#[rpc(server, namespace = "eth")]
|
||||
#[async_trait]
|
||||
@ -386,6 +347,8 @@ where
|
||||
pubsub.log_stream(filter).filter_map(|log| adjust_log::<Eth>(log, &provider)),
|
||||
)
|
||||
.await;
|
||||
} else if kind == SubscriptionKind::NewHeads {
|
||||
let _ = pipe_from_stream(sink, new_headers_stream::<Eth>(&provider)).await;
|
||||
} else {
|
||||
let _ = pubsub.handle_accepted(sink, kind, params).await;
|
||||
}
|
||||
@ -412,23 +375,6 @@ fn adjust_log<Eth: EthWrapper>(mut log: Log, provider: &Eth::Provider) -> Option
|
||||
Some(log)
|
||||
}
|
||||
|
||||
async fn pipe_from_stream<T: Serialize, St: Stream<Item = T> + Unpin>(
|
||||
sink: SubscriptionSink,
|
||||
mut stream: St,
|
||||
) -> Result<(), ErrorObject<'static>> {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = sink.closed() => break Ok(()),
|
||||
maybe_item = stream.next() => {
|
||||
let Some(item) = maybe_item else { break Ok(()) };
|
||||
let msg = SubscriptionMessage::new(sink.method_name(), sink.subscription_id(), &item)
|
||||
.map_err(SubscriptionSerializeError::from)?;
|
||||
if sink.send(msg).await.is_err() { break Ok(()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HlNodeBlockFilterHttp<Eth: EthWrapper> {
|
||||
eth_api: Arc<Eth>,
|
||||
_marker: PhantomData<Eth>,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
pub mod call_forwarder;
|
||||
pub mod hl_node_compliance;
|
||||
pub mod tx_forwarder;
|
||||
pub mod subscribe_fixup;
|
||||
mod utils;
|
||||
|
||||
54
src/addons/subscribe_fixup.rs
Normal file
54
src/addons/subscribe_fixup.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use crate::addons::utils::{EthWrapper, new_headers_stream, pipe_from_stream};
|
||||
use alloy_rpc_types::pubsub::{Params, SubscriptionKind};
|
||||
use async_trait::async_trait;
|
||||
use jsonrpsee::PendingSubscriptionSink;
|
||||
use jsonrpsee_types::ErrorObject;
|
||||
use reth::tasks::TaskSpawner;
|
||||
use reth_rpc::EthPubSub;
|
||||
use reth_rpc_convert::RpcTransaction;
|
||||
use reth_rpc_eth_api::{EthApiTypes, EthPubSubApiServer};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct SubscribeFixup<Eth: EthWrapper> {
|
||||
pubsub: Arc<EthPubSub<Eth>>,
|
||||
provider: Arc<Eth::Provider>,
|
||||
subscription_task_spawner: Box<dyn TaskSpawner + 'static>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Eth: EthWrapper> EthPubSubApiServer<RpcTransaction<Eth::NetworkTypes>> for SubscribeFixup<Eth>
|
||||
where
|
||||
ErrorObject<'static>: From<<Eth as EthApiTypes>::Error>,
|
||||
{
|
||||
async fn subscribe(
|
||||
&self,
|
||||
pending: PendingSubscriptionSink,
|
||||
kind: SubscriptionKind,
|
||||
params: Option<Params>,
|
||||
) -> jsonrpsee::core::SubscriptionResult {
|
||||
let sink = pending.accept().await?;
|
||||
let (pubsub, provider) = (self.pubsub.clone(), self.provider.clone());
|
||||
self.subscription_task_spawner.spawn(Box::pin(async move {
|
||||
if kind == SubscriptionKind::NewHeads {
|
||||
let _ = pipe_from_stream(sink, new_headers_stream::<Eth>(&provider)).await;
|
||||
} else {
|
||||
let _ = pubsub.handle_accepted(sink, kind, params).await;
|
||||
}
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Eth: EthWrapper> SubscribeFixup<Eth> {
|
||||
pub fn new(
|
||||
pubsub: Arc<EthPubSub<Eth>>,
|
||||
provider: Arc<Eth::Provider>,
|
||||
subscription_task_spawner: Box<dyn TaskSpawner + 'static>,
|
||||
) -> Self
|
||||
where
|
||||
Eth: EthWrapper,
|
||||
ErrorObject<'static>: From<Eth::Error>,
|
||||
{
|
||||
Self { pubsub, provider, subscription_task_spawner }
|
||||
}
|
||||
}
|
||||
90
src/addons/utils.rs
Normal file
90
src/addons/utils.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{HlBlock, HlPrimitives};
|
||||
use alloy_primitives::U256;
|
||||
use alloy_rpc_types::Header;
|
||||
use futures::StreamExt;
|
||||
use jsonrpsee::{SubscriptionMessage, SubscriptionSink};
|
||||
use jsonrpsee_types::ErrorObject;
|
||||
use reth_primitives::SealedHeader;
|
||||
use reth_provider::{BlockReader, CanonStateSubscriptions};
|
||||
use reth_rpc::{RpcTypes, eth::pubsub::SubscriptionSerializeError};
|
||||
use reth_rpc_convert::{RpcBlock, RpcHeader, RpcReceipt, RpcTransaction, RpcTxReq};
|
||||
use reth_rpc_eth_api::{
|
||||
EthApiServer, FullEthApiTypes, RpcNodeCoreExt,
|
||||
helpers::{EthBlocks, EthTransactions, LoadReceipt},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tokio_stream::Stream;
|
||||
|
||||
pub trait EthWrapper:
|
||||
EthApiServer<
|
||||
RpcTxReq<Self::NetworkTypes>,
|
||||
RpcTransaction<Self::NetworkTypes>,
|
||||
RpcBlock<Self::NetworkTypes>,
|
||||
RpcReceipt<Self::NetworkTypes>,
|
||||
RpcHeader<Self::NetworkTypes>,
|
||||
> + FullEthApiTypes<
|
||||
Primitives = HlPrimitives,
|
||||
NetworkTypes: RpcTypes<TransactionResponse = alloy_rpc_types_eth::Transaction>,
|
||||
> + RpcNodeCoreExt<Provider: BlockReader<Block = HlBlock>>
|
||||
+ EthBlocks
|
||||
+ EthTransactions
|
||||
+ LoadReceipt
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> EthWrapper for T where
|
||||
T: EthApiServer<
|
||||
RpcTxReq<Self::NetworkTypes>,
|
||||
RpcTransaction<Self::NetworkTypes>,
|
||||
RpcBlock<Self::NetworkTypes>,
|
||||
RpcReceipt<Self::NetworkTypes>,
|
||||
RpcHeader<Self::NetworkTypes>,
|
||||
> + FullEthApiTypes<
|
||||
Primitives = HlPrimitives,
|
||||
NetworkTypes: RpcTypes<TransactionResponse = alloy_rpc_types_eth::Transaction>,
|
||||
> + RpcNodeCoreExt<Provider: BlockReader<Block = HlBlock>>
|
||||
+ EthBlocks
|
||||
+ EthTransactions
|
||||
+ LoadReceipt
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
|
||||
pub(super) async fn pipe_from_stream<T: Serialize, St: Stream<Item = T> + Unpin>(
|
||||
sink: SubscriptionSink,
|
||||
mut stream: St,
|
||||
) -> Result<(), ErrorObject<'static>> {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = sink.closed() => break Ok(()),
|
||||
maybe_item = stream.next() => {
|
||||
let Some(item) = maybe_item else { break Ok(()) };
|
||||
let msg = SubscriptionMessage::new(sink.method_name(), sink.subscription_id(), &item)
|
||||
.map_err(SubscriptionSerializeError::from)?;
|
||||
if sink.send(msg).await.is_err() { break Ok(()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn new_headers_stream<Eth: EthWrapper>(
|
||||
provider: &Arc<Eth::Provider>,
|
||||
) -> impl Stream<Item = Header<alloy_consensus::Header>> {
|
||||
provider.canonical_state_stream().flat_map(|new_chain| {
|
||||
let headers = new_chain
|
||||
.committed()
|
||||
.blocks_iter()
|
||||
.map(|block| {
|
||||
Header::from_consensus(
|
||||
SealedHeader::new(block.header().inner.clone(), block.hash()).into(),
|
||||
None,
|
||||
Some(U256::from(block.rlp_length())),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
futures::stream::iter(headers)
|
||||
})
|
||||
}
|
||||
17
src/main.rs
17
src/main.rs
@ -1,12 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use reth::builder::{NodeBuilder, NodeHandle, WithLaunchContext};
|
||||
use reth::{
|
||||
builder::{NodeBuilder, NodeHandle, WithLaunchContext},
|
||||
rpc::{api::EthPubSubApiServer, eth::RpcNodeCore},
|
||||
};
|
||||
use reth_db::DatabaseEnv;
|
||||
use reth_hl::{
|
||||
addons::{
|
||||
call_forwarder::{self, CallForwarderApiServer},
|
||||
hl_node_compliance::install_hl_node_compliance,
|
||||
subscribe_fixup::SubscribeFixup,
|
||||
tx_forwarder::{self, EthForwarderApiServer},
|
||||
},
|
||||
chainspec::{HlChainSpec, parser::HlChainSpecParser},
|
||||
@ -59,6 +63,17 @@ fn main() -> eyre::Result<()> {
|
||||
info!("Call/gas estimation will be forwarded to {}", upstream_rpc_url);
|
||||
}
|
||||
|
||||
// This is a temporary workaround to fix the issue with custom headers
|
||||
// affects `eth_subscribe[type=newHeads]`
|
||||
ctx.modules.replace_configured(
|
||||
SubscribeFixup::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(),
|
||||
)?;
|
||||
|
||||
if ext.hl_node_compliant {
|
||||
install_hl_node_compliance(&mut ctx)?;
|
||||
info!("hl-node compliant mode enabled");
|
||||
|
||||
@ -145,8 +145,12 @@ where
|
||||
|
||||
match self.command {
|
||||
Commands::Node(command) => runner.run_command_until_exit(|ctx| {
|
||||
// NOTE: This is for one time migration around Oct 10 upgrade:
|
||||
// It's not necessary anymore, an environment variable gate is added here.
|
||||
if std::env::var("CHECK_DB_MIGRATION").is_ok() {
|
||||
Self::migrate_db(&command.chain, &command.datadir, &command.db)
|
||||
.expect("Failed to migrate database");
|
||||
}
|
||||
command.execute(ctx, FnLauncher::new::<C, Ext>(launcher))
|
||||
}),
|
||||
Commands::Init(command) => {
|
||||
|
||||
@ -346,7 +346,7 @@ fn migrate_single_static_file<N: HlNodeType>(
|
||||
|
||||
// block_ranges into chunks of 50000 blocks
|
||||
const CHUNK_SIZE: u64 = 50000;
|
||||
for chunk in (0..=block_range.end()).step_by(CHUNK_SIZE as usize) {
|
||||
for chunk in (block_range.start()..=block_range.end()).step_by(CHUNK_SIZE as usize) {
|
||||
let end = std::cmp::min(chunk + CHUNK_SIZE - 1, block_range.end());
|
||||
let block_range = chunk..=end;
|
||||
let headers = old_headers_range(sf_in, block_range.clone())?;
|
||||
|
||||
@ -127,6 +127,19 @@ pub struct SystemTx {
|
||||
pub receipt: Option<LegacyReceipt>,
|
||||
}
|
||||
|
||||
impl SystemTx {
|
||||
pub fn gas_limit(&self) -> u64 {
|
||||
use reth_compat::Transaction;
|
||||
match &self.tx {
|
||||
Transaction::Legacy(tx) => tx.gas_limit,
|
||||
Transaction::Eip2930(tx) => tx.gas_limit,
|
||||
Transaction::Eip1559(tx) => tx.gas_limit,
|
||||
Transaction::Eip4844(tx) => tx.gas_limit,
|
||||
Transaction::Eip7702(tx) => tx.gas_limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
//! Copy of reth codebase to preserve serialization compatibility
|
||||
use crate::chainspec::TESTNET_CHAIN_ID;
|
||||
use alloy_consensus::{Header, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip7702, TxLegacy};
|
||||
use alloy_primitives::{Address, BlockHash, Signature, TxKind, U256};
|
||||
use reth_primitives::TransactionSigned as RethTxSigned;
|
||||
@ -113,10 +114,16 @@ impl SealedBlock {
|
||||
&self,
|
||||
read_precompile_calls: ReadPrecompileCalls,
|
||||
highest_precompile_address: Option<Address>,
|
||||
system_txs: Vec<super::SystemTx>,
|
||||
mut system_txs: Vec<super::SystemTx>,
|
||||
receipts: Vec<LegacyReceipt>,
|
||||
chain_id: u64,
|
||||
) -> HlBlock {
|
||||
// NOTE: Filter out system transactions that may be rejected by the EVM (tracked by #97,
|
||||
// testnet only).
|
||||
if chain_id == TESTNET_CHAIN_ID {
|
||||
system_txs = system_txs.into_iter().filter(|tx| tx.receipt.is_some()).collect();
|
||||
}
|
||||
|
||||
let mut merged_txs = vec![];
|
||||
merged_txs.extend(system_txs.iter().map(|tx| system_tx_to_reth_transaction(tx, chain_id)));
|
||||
merged_txs.extend(self.body.transactions.iter().map(|tx| tx.to_reth_transaction()));
|
||||
|
||||
@ -81,13 +81,13 @@ impl BlockPoller {
|
||||
.await
|
||||
.ok_or(eyre::eyre!("Failed to find latest block number"))?;
|
||||
|
||||
if let Some(debug_cutoff_height) = debug_cutoff_height &&
|
||||
next_block_number > debug_cutoff_height
|
||||
loop {
|
||||
if let Some(debug_cutoff_height) = debug_cutoff_height
|
||||
&& next_block_number > debug_cutoff_height
|
||||
{
|
||||
next_block_number = debug_cutoff_height;
|
||||
}
|
||||
|
||||
loop {
|
||||
match block_source.collect_block(next_block_number).await {
|
||||
Ok(block) => {
|
||||
block_tx.send((next_block_number, block)).await?;
|
||||
|
||||
49
tests/run_tests.sh
Normal file
49
tests/run_tests.sh
Normal file
@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export ETH_RPC_URL="${ETH_RPC_URL:-wss://hl-archive-node.xyz}"
|
||||
|
||||
success() {
|
||||
echo "Success: $1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "Failed: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_cmd() {
|
||||
command -v "$1" > /dev/null 2>&1 || fail "$1 is required"
|
||||
}
|
||||
|
||||
ensure_cmd jq
|
||||
ensure_cmd cast
|
||||
ensure_cmd wscat
|
||||
|
||||
if [[ ! "$ETH_RPC_URL" =~ ^wss?:// ]]; then
|
||||
fail "ETH_RPC_URL must be a websocket url"
|
||||
fi
|
||||
|
||||
TITLE="Issue #78 - eth_getLogs should return system transactions"
|
||||
cast logs \
|
||||
--rpc-url "$ETH_RPC_URL" \
|
||||
--from-block 15312567 \
|
||||
--to-block 15312570 \
|
||||
--address 0x9fdbda0a5e284c32744d2f17ee5c74b284993463 \
|
||||
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef \
|
||||
| grep -q "0x00000000000000000000000020000000000000000000000000000000000000c5" \
|
||||
&& success "$TITLE" || fail "$TITLE"
|
||||
|
||||
TITLE="Issue #78 - eth_getBlockByNumber should return the same logsBloom as official RPC"
|
||||
OFFICIAL_RPC="https://rpc.hyperliquid.xyz/evm"
|
||||
A=$(cast block 1394092 --rpc-url "$ETH_RPC_URL" -f logsBloom | md5sum)
|
||||
B=$(cast block 1394092 --rpc-url "$OFFICIAL_RPC" -f logsBloom | md5sum)
|
||||
echo node "$A"
|
||||
echo rpc\ "$B"
|
||||
[[ "$A" == "$B" ]] && success "$TITLE" || fail "$TITLE"
|
||||
|
||||
TITLE="eth_subscribe newHeads via wscat"
|
||||
CMD='{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}'
|
||||
wscat -w 2 -c "$ETH_RPC_URL" -x "$CMD" | tail -1 | jq -r .params.result.nonce | grep 0x \
|
||||
&& success "$TITLE" || fail "$TITLE"
|
||||
Reference in New Issue
Block a user