From 9805589e7bf432cc6358760f5adc5364b07b9103 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Wed, 16 Apr 2025 04:19:51 +0000 Subject: [PATCH 01/14] chore: Cleanup diffs before changes --- crates/ethereum/primitives/src/transaction.rs | 20 +++++++++---------- crates/rpc/rpc/src/eth/helpers/types.rs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/ethereum/primitives/src/transaction.rs b/crates/ethereum/primitives/src/transaction.rs index bbd71f9de..ffc0145e1 100644 --- a/crates/ethereum/primitives/src/transaction.rs +++ b/crates/ethereum/primitives/src/transaction.rs @@ -332,9 +332,9 @@ impl Hash for TransactionSigned { impl PartialEq for TransactionSigned { fn eq(&self, other: &Self) -> bool { - self.signature == other.signature - && self.transaction == other.transaction - && self.tx_hash() == other.tx_hash() + self.signature == other.signature && + self.transaction == other.transaction && + self.tx_hash() == other.tx_hash() } } @@ -582,13 +582,13 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned { ) .unwrap(); - Ok(Self { transaction, signature, ..Default::default() }) + Ok(Self { transaction, signature, hash: Default::default() }) } } impl InMemorySize for TransactionSigned { fn size(&self) -> usize { - let Self { hash: _, signature, transaction, .. } = self; + let Self { hash: _, signature, transaction } = self; self.tx_hash().size() + signature.size() + transaction.size() } } @@ -617,26 +617,26 @@ impl Decodable2718 for TransactionSigned { TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)), TxType::Eip2930 => { let (tx, signature) = TxEip2930::rlp_decode_with_signature(buf)?; - Ok(Self { transaction: Transaction::Eip2930(tx), signature, ..Default::default() }) + Ok(Self { transaction: Transaction::Eip2930(tx), signature, hash: Default::default() }) } TxType::Eip1559 => { let (tx, signature) = TxEip1559::rlp_decode_with_signature(buf)?; - Ok(Self { transaction: Transaction::Eip1559(tx), signature, ..Default::default() }) + Ok(Self { transaction: Transaction::Eip1559(tx), signature, hash: Default::default() }) } TxType::Eip4844 => { let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?; - Ok(Self { transaction: Transaction::Eip4844(tx), signature, ..Default::default() }) + Ok(Self { transaction: Transaction::Eip4844(tx), signature, hash: Default::default() }) } TxType::Eip7702 => { let (tx, signature) = TxEip7702::rlp_decode_with_signature(buf)?; - Ok(Self { transaction: Transaction::Eip7702(tx), signature, ..Default::default() }) + Ok(Self { transaction: Transaction::Eip7702(tx), signature, hash: Default::default() }) } } } fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result { let (tx, signature) = TxLegacy::rlp_decode_with_signature(buf)?; - Ok(Self { transaction: Transaction::Legacy(tx), signature, ..Default::default() }) + Ok(Self { transaction: Transaction::Legacy(tx), signature, hash: Default::default() }) } } diff --git a/crates/rpc/rpc/src/eth/helpers/types.rs b/crates/rpc/rpc/src/eth/helpers/types.rs index 6ecce995a..cfd966441 100644 --- a/crates/rpc/rpc/src/eth/helpers/types.rs +++ b/crates/rpc/rpc/src/eth/helpers/types.rs @@ -72,7 +72,7 @@ where request: TransactionRequest, ) -> Result { let Ok(tx) = request.build_typed_tx() else { - return Err(EthApiError::TransactionConversionError); + return Err(EthApiError::TransactionConversionError) }; // Create an empty signature for the transaction. From aa8cffd78af566c63c0c93e8f04bc994ca47e40a Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Wed, 16 Apr 2025 04:55:21 +0000 Subject: [PATCH 02/14] refactor: Sync with hyper-evm-sync before changes --- bin/reth/src/block_ingest.rs | 109 ++++++++--------------------- bin/reth/src/main.rs | 1 + bin/reth/src/serialized.rs | 129 +++++++++++------------------------ bin/reth/src/spot_meta.rs | 61 +++++++++++++++++ 4 files changed, 131 insertions(+), 169 deletions(-) create mode 100644 bin/reth/src/spot_meta.rs diff --git a/bin/reth/src/block_ingest.rs b/bin/reth/src/block_ingest.rs index 6152c103a..f5ef56c95 100644 --- a/bin/reth/src/block_ingest.rs +++ b/bin/reth/src/block_ingest.rs @@ -15,59 +15,18 @@ use reth_node_builder::EngineTypes; use reth_node_builder::NodeTypesWithEngine; use reth_node_builder::{rpc::RethRpcAddOns, FullNode}; use reth_payload_builder::{EthBuiltPayload, EthPayloadBuilderAttributes, PayloadId}; -use reth_primitives::TransactionSigned; +use reth_primitives::{Transaction as TypedTransaction, TransactionSigned}; use reth_provider::{BlockHashReader, StageCheckpointReader}; use reth_rpc_api::EngineApiClient; use reth_rpc_layer::AuthClientService; use reth_stages::StageId; -use serde::{Deserialize, Serialize}; use tracing::{debug, info}; -use crate::serialized::TypedTransaction; -use crate::serialized::{self, BlockInner}; +use crate::serialized::{BlockAndReceipts, EvmBlock}; +use crate::spot_meta::erc20_contract_to_spot_token; pub(crate) struct BlockIngest(pub PathBuf); -#[derive(Debug, Clone, Serialize, Deserialize)] -struct EvmContract { - pub address: Address, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct SpotToken { - pub index: u64, - #[serde(rename = "evmContract")] - pub evm_contract: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct SpotMeta { - tokens: Vec, -} - -async fn fetch_spot_meta(is_testnet: bool) -> Result> { - let url = if is_testnet { - "https://api.hyperliquid-testnet.xyz" - } else { - "https://api.hyperliquid.xyz" - }; - let url = format!("{}/info", url); - // post body: {"type": "spotMeta"} - let client = reqwest::Client::new(); - let response = client.post(url).json(&serde_json::json!({"type": "spotMeta"})).send().await?; - Ok(response.json().await?) -} - -fn to_evm_map(meta: &SpotMeta) -> std::collections::HashMap { - let mut map = std::collections::HashMap::new(); - for token in &meta.tokens { - if let Some(evm_contract) = &token.evm_contract { - map.insert(evm_contract.address, token.index); - } - } - map -} - async fn submit_payload( engine_api_client: &HttpClient>, payload: EthBuiltPayload, @@ -95,7 +54,7 @@ async fn submit_payload( } impl BlockIngest { - pub(crate) fn collect_block(&self, height: u64) -> Option { + pub(crate) fn collect_block(&self, height: u64) -> Option { let f = ((height - 1) / 1_000_000) * 1_000_000; let s = ((height - 1) / 1_000) * 1_000; let path = format!("{}/{f}/{s}/{height}.rmp.lz4", self.0.to_string_lossy()); @@ -103,7 +62,7 @@ impl BlockIngest { let file = std::fs::File::open(path).unwrap(); let file = std::io::BufReader::new(file); let mut decoder = lz4_flex::frame::FrameDecoder::new(file); - let blocks: Vec = rmp_serde::from_read(&mut decoder).unwrap(); + let blocks: Vec = rmp_serde::from_read(&mut decoder).unwrap(); Some(blocks[0].clone()) } else { None @@ -135,14 +94,14 @@ impl BlockIngest { std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(); let engine_api = node.auth_server_handle().http_client(); - let mut evm_map = to_evm_map(&fetch_spot_meta(node.chain_spec().chain_id() == 998).await?); + let mut evm_map = erc20_contract_to_spot_token(node.chain_spec().chain_id()).await?; loop { let Some(original_block) = self.collect_block(height) else { tokio::time::sleep(std::time::Duration::from_millis(200)).await; continue; }; - let BlockInner::Reth115(mut block) = original_block.block; + let EvmBlock::Reth115(mut block) = original_block.block; { debug!(target: "reth::cli", ?block, "Built new payload"); let timestamp = block.header().timestamp(); @@ -153,38 +112,28 @@ impl BlockIngest { std::mem::take(block.body_mut()); let mut system_txs = vec![]; for transaction in original_block.system_txs { - let s = match &transaction.tx { - TypedTransaction::Legacy(tx) => match tx.input().len() { - 0 => U256::from(0x1), - _ => { - let TxKind::Call(to) = tx.to else { - panic!("Unexpected contract creation"); - }; - loop { - match evm_map.get(&to).cloned() { - Some(s) => { - break { - let mut addr = [0u8; 32]; - addr[12] = 0x20; - addr[24..32].copy_from_slice(s.to_be_bytes().as_ref()); - U256::from_be_bytes(addr) - } - } - None => { - info!("Contract not found: {:?}, fetching again...", to); - evm_map = to_evm_map( - &fetch_spot_meta( - node.chain_spec().chain_id() == 998, - ) - .await?, - ); - continue; - } - } - } + let TypedTransaction::Legacy(tx) = &transaction.tx else { + panic!("Unexpected transaction type"); + }; + let TxKind::Call(to) = tx.to else { + panic!("Unexpected contract creation"); + }; + let s = if tx.input().is_empty() { + U256::from(0x1) + } else { + loop { + if let Some(spot) = evm_map.get(&to) { + break spot.to_s(); } - }, - _ => unreachable!(), + + info!( + "Contract not found: {:?} from spot mapping, fetching again...", + to + ); + evm_map = + erc20_contract_to_spot_token(node.chain_spec().chain_id()) + .await?; + } }; let signature = PrimitiveSignature::new( // from anvil @@ -192,7 +141,7 @@ impl BlockIngest { s, true, ); - let typed_transaction = transaction.tx.to_reth(); + let typed_transaction = transaction.tx; let tx = TransactionSigned::new( typed_transaction, signature, diff --git a/bin/reth/src/main.rs b/bin/reth/src/main.rs index f1c8331d2..31509187c 100644 --- a/bin/reth/src/main.rs +++ b/bin/reth/src/main.rs @@ -6,6 +6,7 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::ne mod block_ingest; mod forwarder; mod serialized; +mod spot_meta; use std::path::PathBuf; diff --git a/bin/reth/src/serialized.rs b/bin/reth/src/serialized.rs index d351a377c..14ad2d571 100644 --- a/bin/reth/src/serialized.rs +++ b/bin/reth/src/serialized.rs @@ -1,105 +1,56 @@ -use alloy_consensus::{TxEip1559, TxEip2930, TxLegacy}; -use reth_primitives::{Log, SealedBlock, Transaction}; +use alloy_primitives::{Address, Bytes, Log}; +use reth_primitives::{SealedBlock, Transaction}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct SerializedTransaction { - pub transaction: TypedTransaction, - pub signature: SerializedSignature, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct BlockAndReceipts { + pub(crate) block: EvmBlock, + pub(crate) receipts: Vec, + #[serde(default)] + pub(crate) system_txs: Vec, + #[serde(default)] + pub(crate) read_precompile_calls: + Vec<(Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>)>, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct SerializedSignature { - pub r: [u8; 32], - pub s: [u8; 32], - pub v: [u8; 8], -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) enum BlockInner { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) enum EvmBlock { Reth115(SealedBlock), } -/// A raw transaction. -/// -/// Transaction types were introduced in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718). -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub(crate) enum TypedTransaction { - /// Legacy transaction (type `0x0`). - /// - /// Traditional Ethereum transactions, containing parameters `nonce`, `gasPrice`, `gasLimit`, - /// `to`, `value`, `data`, `v`, `r`, and `s`. - /// - /// These transactions do not utilize access lists nor do they incorporate EIP-1559 fee market - /// changes. - Legacy(TxLegacy), - /// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)), type `0x1`. - /// - /// The `accessList` specifies an array of addresses and storage keys that the transaction - /// plans to access, enabling gas savings on cross-contract calls by pre-declaring the accessed - /// contract and storage slots. - Eip2930(TxEip2930), - /// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)), type `0x2`. - /// - /// Unlike traditional transactions, EIP-1559 transactions use an in-protocol, dynamically - /// changing base fee per gas, adjusted at each block to manage network congestion. - /// - /// - `maxPriorityFeePerGas`, specifying the maximum fee above the base fee the sender is - /// willing to pay - /// - `maxFeePerGas`, setting the maximum total fee the sender is willing to pay. - /// - /// The base fee is burned, while the priority fee is paid to the miner who includes the - /// transaction, incentivizing miners to include transactions with higher priority fees per - /// gas. - Eip1559(TxEip1559), +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct LegacyReceipt { + tx_type: LegacyTxType, + success: bool, + cumulative_gas_used: u64, + logs: Vec, } -impl TypedTransaction { - pub(crate) fn to_reth(self) -> Transaction { - match self { - Self::Legacy(tx) => Transaction::Legacy(tx), - Self::Eip2930(tx) => Transaction::Eip2930(tx), - Self::Eip1559(tx) => Transaction::Eip1559(tx), - } - } +#[derive(Debug, Clone, Serialize, Deserialize)] +enum LegacyTxType { + Legacy = 0, + Eip2930 = 1, + Eip1559 = 2, + Eip4844 = 3, + Eip7702 = 4, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) enum TxType { - /// Legacy transaction type. - Legacy, - /// EIP-2930 transaction type. - Eip2930, - /// EIP-1559 transaction type. - Eip1559, - /// EIP-4844 transaction type. - Eip4844, - /// EIP-7702 transaction type. - Eip7702, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct SystemTx { + pub(crate) tx: Transaction, + pub(crate) receipt: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct Receipt { - /// Receipt type. - pub tx_type: TxType, - /// If transaction is executed successfully. - /// - /// This is the `statusCode` - pub success: bool, - /// Gas used - pub cumulative_gas_used: u64, - /// Log send from contracts. - pub logs: Vec, +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub(crate) struct ReadPrecompileInput { + pub(crate) input: Bytes, + pub(crate) gas_limit: u64, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct SystemTransaction { - pub receipt: Receipt, - pub tx: TypedTransaction, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct Block { - pub block: BlockInner, - pub system_txs: Vec, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) enum ReadPrecompileResult { + Ok { gas_used: u64, bytes: Bytes }, + OutOfGas, + Error, + UnexpectedError, } diff --git a/bin/reth/src/spot_meta.rs b/bin/reth/src/spot_meta.rs new file mode 100644 index 000000000..599a44b54 --- /dev/null +++ b/bin/reth/src/spot_meta.rs @@ -0,0 +1,61 @@ +use alloy_primitives::{Address, U256}; +use eyre::{Error, Result}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +pub(crate) const MAINNET_CHAIN_ID: u64 = 999; +pub(crate) const TESTNET_CHAIN_ID: u64 = 998; + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct EvmContract { + address: Address, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SpotToken { + index: u64, + #[serde(rename = "evmContract")] + evm_contract: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SpotMeta { + tokens: Vec, +} + +pub(crate) struct SpotId { + pub index: u64, +} + +impl SpotId { + pub(crate) fn to_s(&self) -> U256 { + let mut addr = [0u8; 32]; + addr[12] = 0x20; + addr[24..32].copy_from_slice(self.index.to_be_bytes().as_ref()); + U256::from_be_bytes(addr) + } +} + +async fn fetch_spot_meta(chain_id: u64) -> Result { + let url = match chain_id { + MAINNET_CHAIN_ID => "https://api.hyperliquid.xyz/info", + TESTNET_CHAIN_ID => "https://api.hyperliquid-testnet.xyz/info", + _ => return Err(Error::msg("unknown chain id")), + }; + let client = reqwest::Client::new(); + let response = client.post(url).json(&serde_json::json!({"type": "spotMeta"})).send().await?; + Ok(response.json().await?) +} + +pub(crate) async fn erc20_contract_to_spot_token( + chain_id: u64, +) -> Result> { + let meta = fetch_spot_meta(chain_id).await?; + let mut map = BTreeMap::new(); + for token in &meta.tokens { + if let Some(evm_contract) = &token.evm_contract { + map.insert(evm_contract.address, SpotId { index: token.index }); + } + } + Ok(map) +} From 6047b3d1233e21617df6ecc1e337d01d29c9cbc8 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Thu, 17 Apr 2025 07:17:07 +0000 Subject: [PATCH 03/14] fix: read precompile draft (dirty) --- Cargo.lock | 17 ++ Cargo.toml | 3 + bin/reth/Cargo.toml | 1 + bin/reth/src/main.rs | 12 +- bin/reth/src/serialized.rs | 31 +--- crates/cli/commands/src/node.rs | 6 + crates/engine/primitives/Cargo.toml | 3 + crates/ethereum/evm/Cargo.toml | 6 + crates/ethereum/evm/src/lib.rs | 168 +++++++------------ crates/ethereum/evm/src/precompile_replay.rs | 85 ++++++++++ crates/ethereum/node/src/node.rs | 2 +- crates/ethereum/node/src/payload.rs | 2 +- crates/hyperliquid-types/Cargo.toml | 19 +++ crates/hyperliquid-types/src/lib.rs | 16 ++ crates/node/builder/src/builder/mod.rs | 6 +- crates/node/core/src/node_config.rs | 6 + 16 files changed, 245 insertions(+), 138 deletions(-) create mode 100644 crates/ethereum/evm/src/precompile_replay.rs create mode 100644 crates/hyperliquid-types/Cargo.toml create mode 100644 crates/hyperliquid-types/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 24793a311..11d01dcfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6686,6 +6686,7 @@ dependencies = [ "reth-execution-types", "reth-exex", "reth-fs-util", + "reth-hyperliquid-types", "reth-network", "reth-network-api", "reth-network-p2p", @@ -7394,6 +7395,7 @@ dependencies = [ "reth-chain-state", "reth-errors", "reth-execution-types", + "reth-hyperliquid-types", "reth-payload-builder-primitives", "reth-payload-primitives", "reth-primitives", @@ -7803,15 +7805,20 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "alloy-sol-types", + "lz4_flex", + "parking_lot", "reth-chainspec", "reth-ethereum-forks", "reth-evm", "reth-execution-types", + "reth-hyperliquid-types", "reth-primitives", "reth-primitives-traits", "reth-revm", "reth-testing-utils", + "rmp-serde", "secp256k1 0.30.0", + "serde", "serde_json", "sha2 0.10.8", ] @@ -7954,6 +7961,16 @@ dependencies = [ "thiserror 2.0.11", ] +[[package]] +name = "reth-hyperliquid-types" +version = "1.2.0" +dependencies = [ + "alloy-primitives", + "clap", + "reth-cli-commands", + "serde", +] + [[package]] name = "reth-invalid-block-hooks" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index fd5fa8cb2..98b44ae1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,6 +129,7 @@ members = [ "crates/trie/parallel/", "crates/trie/sparse", "crates/trie/trie", + "crates/hyperliquid-types", "examples/beacon-api-sidecar-fetcher/", "examples/beacon-api-sse/", "examples/bsc-p2p", @@ -622,6 +623,8 @@ snmalloc-rs = { version = "0.3.7", features = ["build_cc"] } # See: https://github.com/eira-fransham/crunchy/issues/13 crunchy = "=0.2.2" +# hyperliquid +reth-hyperliquid-types = { path = "crates/hyperliquid-types" } lz4_flex = "0.11.3" rmp-serde = "1.3.0" diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 6523f70f7..9ffce7347 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -64,6 +64,7 @@ reth-node-events.workspace = true reth-node-metrics.workspace = true reth-consensus.workspace = true reth-prune.workspace = true +reth-hyperliquid-types.workspace = true # crypto alloy-eips = { workspace = true, features = ["kzg"] } diff --git a/bin/reth/src/main.rs b/bin/reth/src/main.rs index 31509187c..46aae12da 100644 --- a/bin/reth/src/main.rs +++ b/bin/reth/src/main.rs @@ -8,8 +8,6 @@ mod forwarder; mod serialized; mod spot_meta; -use std::path::PathBuf; - use block_ingest::BlockIngest; use clap::{Args, Parser}; use forwarder::EthForwarderApiServer; @@ -20,10 +18,6 @@ use tracing::info; #[derive(Args, Debug, Clone)] struct HyperliquidExtArgs { - /// EVM blocks base directory - #[arg(long, default_value = "/tmp/evm-blocks")] - pub ingest_dir: PathBuf, - /// Upstream RPC URL to forward incoming transactions. #[arg(long, default_value = "https://rpc.hyperliquid.xyz/evm")] pub upstream_rpc_url: String, @@ -38,12 +32,13 @@ fn main() { } if let Err(err) = Cli::::parse().run( - |builder, ingest_args| async move { + |builder, ext_args| async move { + let ingest_dir = builder.config().ingest_dir.clone().expect("ingest dir not set"); info!(target: "reth::cli", "Launching node"); let handle = builder .node(EthereumNode::default()) .extend_rpc_modules(move |ctx| { - let upstream_rpc_url = ingest_args.upstream_rpc_url.clone(); + let upstream_rpc_url = ext_args.upstream_rpc_url.clone(); let rpc = forwarder::EthForwarderExt::new(upstream_rpc_url).into_rpc(); for method_name in rpc.method_names() { ctx.modules.remove_method_from_configured(method_name); @@ -56,7 +51,6 @@ fn main() { .launch() .await?; - let ingest_dir = ingest_args.ingest_dir; let ingest = BlockIngest(ingest_dir); ingest.run(handle.node).await.unwrap(); handle.node_exit_future.await diff --git a/bin/reth/src/serialized.rs b/bin/reth/src/serialized.rs index 14ad2d571..49dec5740 100644 --- a/bin/reth/src/serialized.rs +++ b/bin/reth/src/serialized.rs @@ -1,16 +1,16 @@ -use alloy_primitives::{Address, Bytes, Log}; +use alloy_primitives::{Address, Log}; +use reth_hyperliquid_types::{ReadPrecompileInput, ReadPrecompileResult}; use reth_primitives::{SealedBlock, Transaction}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct BlockAndReceipts { - pub(crate) block: EvmBlock, - pub(crate) receipts: Vec, + pub block: EvmBlock, + pub receipts: Vec, #[serde(default)] - pub(crate) system_txs: Vec, + pub system_txs: Vec, #[serde(default)] - pub(crate) read_precompile_calls: - Vec<(Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>)>, + pub read_precompile_calls: Vec<(Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>)>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -18,6 +18,7 @@ pub(crate) enum EvmBlock { Reth115(SealedBlock), } + #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct LegacyReceipt { tx_type: LegacyTxType, @@ -37,20 +38,6 @@ enum LegacyTxType { #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct SystemTx { - pub(crate) tx: Transaction, - pub(crate) receipt: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] -pub(crate) struct ReadPrecompileInput { - pub(crate) input: Bytes, - pub(crate) gas_limit: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) enum ReadPrecompileResult { - Ok { gas_used: u64, bytes: Bytes }, - OutOfGas, - Error, - UnexpectedError, + pub tx: Transaction, + pub receipt: Option, } diff --git a/crates/cli/commands/src/node.rs b/crates/cli/commands/src/node.rs index 189ca6b79..5ce7dba0f 100644 --- a/crates/cli/commands/src/node.rs +++ b/crates/cli/commands/src/node.rs @@ -114,6 +114,10 @@ pub struct NodeCommand< /// Additional cli arguments #[command(flatten, next_help_heading = "Extension")] pub ext: Ext, + + /// EVM blocks base directory + #[arg(long, default_value = "/tmp/evm-blocks")] + pub ingest_dir: PathBuf, } impl NodeCommand { @@ -165,6 +169,7 @@ impl< pruning, ext, engine, + ingest_dir, } = self; // set up node config @@ -183,6 +188,7 @@ impl< dev, pruning, engine, + ingest_dir: Some(ingest_dir), }; let data_dir = node_config.datadir(); diff --git a/crates/engine/primitives/Cargo.toml b/crates/engine/primitives/Cargo.toml index 7f5c6c1cb..1e0f1a71c 100644 --- a/crates/engine/primitives/Cargo.toml +++ b/crates/engine/primitives/Cargo.toml @@ -31,6 +31,9 @@ alloy-eips.workspace = true tokio = { workspace = true, features = ["sync"] } futures.workspace = true +# hyperevm +reth-hyperliquid-types.workspace = true + # misc auto_impl.workspace = true serde.workspace = true diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index 4ef4f6282..3387ae561 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -31,6 +31,12 @@ alloy-consensus.workspace = true sha2.workspace = true serde_json.workspace = true +serde = { workspace = true, features = ["derive"] } +rmp-serde.workspace = true +lz4_flex.workspace = true + +reth-hyperliquid-types.workspace = true +parking_lot.workspace = true [dev-dependencies] reth-testing-utils.workspace = true diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 772659ab7..798cda040 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -23,31 +23,33 @@ use alloy_evm::eth::EthEvmContext; pub use alloy_evm::EthEvm; use alloy_primitives::bytes::BufMut; use alloy_primitives::hex::{FromHex, ToHexExt}; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_primitives::{Address, B256}; +use alloy_primitives::{Bytes, U256}; use core::{convert::Infallible, fmt::Debug}; +use parking_lot::RwLock; use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET}; use reth_evm::Database; use reth_evm::{ConfigureEvm, ConfigureEvmEnv, EvmEnv, EvmFactory, NextBlockEnvAttributes}; +use reth_hyperliquid_types::{ReadPrecompileInput, ReadPrecompileResult}; use reth_primitives::TransactionSigned; +use reth_primitives::{SealedBlock, Transaction}; use reth_revm::context::result::{EVMError, HaltReason}; -use reth_revm::context::{Block, Cfg, ContextTr}; -use reth_revm::handler::{EthPrecompiles, PrecompileProvider}; +use reth_revm::context::Cfg; +use reth_revm::handler::EthPrecompiles; use reth_revm::inspector::NoOpInspector; use reth_revm::interpreter::interpreter::EthInterpreter; -use reth_revm::interpreter::{Gas, InstructionResult, InterpreterResult}; -use reth_revm::precompile::{ - PrecompileError, PrecompileErrors, PrecompileFn, PrecompileOutput, PrecompileResult, - Precompiles, -}; +use reth_revm::precompile::{PrecompileError, PrecompileErrors, Precompiles}; +use reth_revm::MainBuilder; use reth_revm::{ context::{BlockEnv, CfgEnv, TxEnv}, context_interface::block::BlobExcessGasAndPrice, specification::hardfork::SpecId, }; -use reth_revm::{revm, Context, Inspector, MainBuilder, MainContext}; -use sha2::Digest; +use reth_revm::{Context, Inspector, MainContext}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::io::Write; -use std::sync::OnceLock; +use std::path::PathBuf; mod config; mod fix; @@ -65,15 +67,23 @@ pub mod eip6110; /// Ethereum-related EVM configuration. #[derive(Debug, Clone)] + pub struct EthEvmConfig { chain_spec: Arc, evm_factory: HyperliquidEvmFactory, + ingest_dir: Option, } impl EthEvmConfig { /// Creates a new Ethereum EVM configuration with the given chain spec. pub fn new(chain_spec: Arc) -> Self { - Self { chain_spec, evm_factory: Default::default() } + Self { chain_spec, ingest_dir: None, evm_factory: Default::default() } + } + + pub fn with_ingest_dir(mut self, ingest_dir: PathBuf) -> Self { + self.ingest_dir = Some(ingest_dir.clone()); + self.evm_factory.ingest_dir = Some(ingest_dir); + self } /// Creates a new Ethereum EVM configuration for the ethereum mainnet. @@ -182,97 +192,15 @@ impl ConfigureEvmEnv for EthEvmConfig { } } -/// A custom precompile that contains static precompiles. -#[allow(missing_debug_implementations)] -#[derive(Clone)] -pub struct L1ReadPrecompiles { - precompiles: EthPrecompiles, - warm_addresses: Vec
, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct BlockAndReceipts { + #[serde(default)] + pub read_precompile_calls: Vec<(Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>)>, } -impl L1ReadPrecompiles { - fn new() -> Self { - let mut this = Self { precompiles: EthPrecompiles::default(), warm_addresses: vec![] }; - this.update_warm_addresses(false); - this - } - - fn update_warm_addresses(&mut self, precompile_enabled: bool) { - self.warm_addresses = if !precompile_enabled { - self.precompiles.warm_addresses().collect() - } else { - self.precompiles - .warm_addresses() - .chain((0..=9).into_iter().map(|x| { - let mut addr = [0u8; 20]; - addr[18] = 0x8; - addr[19] = x; - Address::from_slice(&addr) - })) - .collect() - } - } -} - -impl PrecompileProvider for L1ReadPrecompiles { - type Context = CTX; - type Output = InterpreterResult; - - fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { - self.precompiles.set_spec(spec); - // TODO: How to pass block number and chain id? - self.update_warm_addresses(false); - } - - fn run( - &mut self, - context: &mut Self::Context, - address: &Address, - bytes: &Bytes, - gas_limit: u64, - ) -> Result, revm::precompile::PrecompileErrors> { - if address[..18] == [0u8; 18] { - let maybe_precompile_index = u16::from_be_bytes([address[18], address[19]]); - let precompile_base = - std::env::var("PRECOMPILE_BASE").unwrap_or("/tmp/precompiles".to_string()); - if 0x800 <= maybe_precompile_index && maybe_precompile_index <= 0x809 { - let block_number = context.block().number(); - let input = vec![]; - let mut writer = input.writer(); - writer.write(&address.as_slice()).unwrap(); - writer.write(bytes).unwrap(); - writer.flush().unwrap(); - let hash = sha2::Sha256::digest(writer.get_ref()); - let file = - format!("{}/{}/{}.json", precompile_base, block_number, hash.encode_hex()); - let (output, gas) = match load_result(file) { - Ok(Some(value)) => value, - Ok(None) => { - return Ok(Some(InterpreterResult { - result: InstructionResult::Return, - gas: Gas::new(gas_limit), - output: Bytes::new(), - })) - } - Err(value) => return Err(value), - }; - return Ok(Some(InterpreterResult { - result: InstructionResult::Return, - gas: Gas::new(gas_limit - gas), - output, - })); - } - } - self.precompiles.run(context, address, bytes, gas_limit) - } - - fn contains(&self, address: &Address) -> bool { - self.precompiles.contains(address) - } - - fn warm_addresses(&self) -> Box + '_> { - Box::new(self.warm_addresses.iter().cloned()) - } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) enum EvmBlock { + Reth115(SealedBlock), } fn load_result(file: String) -> Result, PrecompileErrors> { @@ -296,23 +224,51 @@ fn load_result(file: String) -> Result, PrecompileErrors> { /// Custom EVM configuration. #[derive(Debug, Clone, Default)] #[non_exhaustive] -pub struct HyperliquidEvmFactory; +pub struct HyperliquidEvmFactory { + ingest_dir: Option, +} + +pub(crate) fn collect_block(ingest_path: PathBuf, height: u64) -> Option { + let f = ((height - 1) / 1_000_000) * 1_000_000; + let s = ((height - 1) / 1_000) * 1_000; + let path = format!("{}/{f}/{s}/{height}.rmp.lz4", ingest_path.to_string_lossy()); + if std::path::Path::new(&path).exists() { + let file = std::fs::File::open(path).unwrap(); + let file = std::io::BufReader::new(file); + let mut decoder = lz4_flex::frame::FrameDecoder::new(file); + let blocks: Vec = rmp_serde::from_read(&mut decoder).unwrap(); + Some(blocks[0].clone()) + } else { + None + } +} impl EvmFactory for HyperliquidEvmFactory { type Evm, EthInterpreter>> = - EthEvm>>; + EthEvm>>; type Tx = TxEnv; type Error = EVMError; type HaltReason = HaltReason; type Context = EthEvmContext; fn create_evm(&self, db: DB, input: EvmEnv) -> Self::Evm { + let cache = collect_block(self.ingest_dir.clone().unwrap(), input.block_env.number) + .unwrap() + .read_precompile_calls; let evm = Context::mainnet() .with_db(db) .with_cfg(input.cfg_env) .with_block(input.block_env) .build_mainnet_with_inspector(NoOpInspector {}) - .with_precompiles(L1ReadPrecompiles::new()); + .with_precompiles(ReplayPrecompile::new( + EthPrecompiles::default(), + Arc::new(RwLock::new( + cache + .into_iter() + .map(|(address, calls)| (address, HashMap::from_iter(calls.into_iter()))) + .collect(), + )), + )); EthEvm::new(evm, false) } @@ -509,3 +465,7 @@ mod tests { assert_eq!(evm.tx, Default::default()); } } + +mod precompile_replay; + +pub use precompile_replay::ReplayPrecompile; diff --git a/crates/ethereum/evm/src/precompile_replay.rs b/crates/ethereum/evm/src/precompile_replay.rs new file mode 100644 index 000000000..51a13405c --- /dev/null +++ b/crates/ethereum/evm/src/precompile_replay.rs @@ -0,0 +1,85 @@ +use alloy_primitives::{Address, Bytes}; +use parking_lot::RwLock; +use reth_hyperliquid_types::{ReadPrecompileInput, ReadPrecompileResult}; +use reth_revm::{ + context::{Cfg, ContextTr}, + handler::{EthPrecompiles, PrecompileProvider}, + interpreter::{Gas, InstructionResult, InterpreterResult}, + precompile::{PrecompileError, PrecompileErrors}, +}; +use std::{collections::HashMap, sync::Arc}; + +/// Precompile that replays cached results. +#[derive(Clone)] +pub struct ReplayPrecompile { + precompiles: EthPrecompiles, + cache: Arc>>>, +} + +impl std::fmt::Debug for ReplayPrecompile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ReplayPrecompile").finish() + } +} + +impl ReplayPrecompile { + /// Creates a new replay precompile with the given precompiles and cache. + pub fn new( + precompiles: EthPrecompiles, + cache: Arc>>>, + ) -> Self { + Self { precompiles, cache } + } +} + +impl PrecompileProvider for ReplayPrecompile { + type Context = CTX; + type Output = InterpreterResult; + + fn set_spec(&mut self, spec: <::Cfg as Cfg>::Spec) { + self.precompiles.set_spec(spec); + } + + fn run( + &mut self, + context: &mut Self::Context, + address: &Address, + bytes: &Bytes, + gas_limit: u64, + ) -> Result, PrecompileErrors> { + let cache = self.cache.read(); + if let Some(precompile_calls) = cache.get(address) { + let input = ReadPrecompileInput { input: bytes.clone(), gas_limit }; + let mut result = InterpreterResult { + result: InstructionResult::Return, + gas: Gas::new(gas_limit), + output: Bytes::new(), + }; + + return match *precompile_calls.get(&input).expect("missing precompile call") { + ReadPrecompileResult::Ok { gas_used, ref bytes } => { + let underflow = result.gas.record_cost(gas_used); + assert!(underflow, "Gas underflow is not possible"); + result.output = bytes.clone(); + Ok(Some(result)) + } + ReadPrecompileResult::OutOfGas => Err(PrecompileError::OutOfGas.into()), + ReadPrecompileResult::Error => { + Err(PrecompileError::other("precompile failed").into()) + } + ReadPrecompileResult::UnexpectedError => panic!("unexpected precompile error"), + }; + } + + // If no cached result, fall back to normal precompile execution + self.precompiles.run(context, address, bytes, gas_limit) + } + + fn contains(&self, address: &Address) -> bool { + self.precompiles.contains(address) + } + + fn warm_addresses(&self) -> Box + '_> { + Box::new(self.precompiles.warm_addresses()) + } +} diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 6baa88f45..7aeb7615c 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -248,7 +248,7 @@ where ctx: &BuilderContext, ) -> eyre::Result<(Self::EVM, Self::Executor)> { let chain_spec = ctx.chain_spec(); - let evm_config = EthEvmConfig::new(ctx.chain_spec()); + let evm_config = EthEvmConfig::new(ctx.chain_spec()).with_ingest_dir(ctx.ingest_dir()); let strategy_factory = EthExecutionStrategyFactory::new(chain_spec, evm_config.clone()); let executor = BasicBlockExecutorProvider::new(strategy_factory); diff --git a/crates/ethereum/node/src/payload.rs b/crates/ethereum/node/src/payload.rs index ad156bf52..27c2e1517 100644 --- a/crates/ethereum/node/src/payload.rs +++ b/crates/ethereum/node/src/payload.rs @@ -74,6 +74,6 @@ where ctx: &BuilderContext, pool: Pool, ) -> eyre::Result { - self.build(EthEvmConfig::new(ctx.chain_spec()), ctx, pool) + self.build(EthEvmConfig::new(ctx.chain_spec()).with_ingest_dir(ctx.ingest_dir()), ctx, pool) } } diff --git a/crates/hyperliquid-types/Cargo.toml b/crates/hyperliquid-types/Cargo.toml new file mode 100644 index 000000000..cbedfecb0 --- /dev/null +++ b/crates/hyperliquid-types/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "reth-hyperliquid-types" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +alloy-primitives.workspace = true +serde.workspace = true + +[dev-dependencies] +clap.workspace = true +reth-cli-commands.workspace = true diff --git a/crates/hyperliquid-types/src/lib.rs b/crates/hyperliquid-types/src/lib.rs new file mode 100644 index 000000000..c7d60349a --- /dev/null +++ b/crates/hyperliquid-types/src/lib.rs @@ -0,0 +1,16 @@ +use alloy_primitives::Bytes; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub struct ReadPrecompileInput { + pub input: Bytes, + pub gas_limit: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ReadPrecompileResult { + Ok { gas_used: u64, bytes: Bytes }, + OutOfGas, + Error, + UnexpectedError, +} diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index ada14bd79..89d0917c8 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -37,7 +37,7 @@ use reth_provider::{ use reth_tasks::TaskExecutor; use reth_transaction_pool::{PoolConfig, PoolTransaction, TransactionPool}; use secp256k1::SecretKey; -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; use tracing::{info, trace, warn}; pub mod add_ons; @@ -750,6 +750,10 @@ impl BuilderContext { { network_builder.build(self.provider.clone()) } + + pub fn ingest_dir(&self) -> PathBuf { + self.config().ingest_dir.clone().expect("ingest dir not set") + } } impl>> BuilderContext { diff --git a/crates/node/core/src/node_config.rs b/crates/node/core/src/node_config.rs index ff36528dc..1e0064471 100644 --- a/crates/node/core/src/node_config.rs +++ b/crates/node/core/src/node_config.rs @@ -145,6 +145,9 @@ pub struct NodeConfig { /// All engine related arguments pub engine: EngineArgs, + + /// The ingest directory for the node. + pub ingest_dir: Option, } impl NodeConfig { @@ -174,6 +177,7 @@ impl NodeConfig { pruning: PruningArgs::default(), datadir: DatadirArgs::default(), engine: EngineArgs::default(), + ingest_dir: None, } } @@ -465,6 +469,7 @@ impl NodeConfig { dev: self.dev, pruning: self.pruning, engine: self.engine, + ingest_dir: self.ingest_dir, } } } @@ -492,6 +497,7 @@ impl Clone for NodeConfig { pruning: self.pruning.clone(), datadir: self.datadir.clone(), engine: self.engine.clone(), + ingest_dir: self.ingest_dir.clone(), } } } From f1e5bac52f72c9d87901555003acf4ebc28d265b Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Thu, 17 Apr 2025 07:20:36 +0000 Subject: [PATCH 04/14] chore: Sync with hyper-evm-sync --- crates/primitives-traits/src/transaction/signed.rs | 4 ++-- crates/revm/src/database.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/primitives-traits/src/transaction/signed.rs b/crates/primitives-traits/src/transaction/signed.rs index 41ae8f7d3..ff61dc208 100644 --- a/crates/primitives-traits/src/transaction/signed.rs +++ b/crates/primitives-traits/src/transaction/signed.rs @@ -20,14 +20,14 @@ pub trait FullSignedTx: SignedTransaction + MaybeCompact + MaybeSerdeBincodeComp impl FullSignedTx for T where T: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {} /// Hyperliquid system transaction from address. -pub const HL_SYSTEM_TX_FROM_ADDR: Address = address!("2222222222222222222222222222222222222222"); +pub const NATIVE_TOKEN_SYSTEM_ADDRESS: Address = address!("2222222222222222222222222222222222222222"); /// Check if the transaction is impersonated. /// Signature part is introduced in block_ingest, while the gas_price is trait of hyperliquid system transactions. pub fn is_impersonated_tx(signature: &Signature, gas_price: Option) -> Option
{ if signature.r() == U256::from(1) && signature.v() == true && gas_price == Some(0u128) { if signature.s() == U256::from(1) { - Some(HL_SYSTEM_TX_FROM_ADDR) + Some(NATIVE_TOKEN_SYSTEM_ADDRESS) } else { let s = signature.s().reduce_mod(U256::from(U160::MAX).add(U256::from(1))); let s = U160::from(s); diff --git a/crates/revm/src/database.rs b/crates/revm/src/database.rs index a6998b028..9a0c295f2 100644 --- a/crates/revm/src/database.rs +++ b/crates/revm/src/database.rs @@ -159,7 +159,8 @@ impl DatabaseRef for StateProviderDatabase { /// /// Returns `Ok` with the block hash if found, or the default hash otherwise. fn block_hash_ref(&self, number: u64) -> Result { - if number >= 270000 { + const NON_PLACEHOLDER_BLOCK_HASH_HEIGHT: u64 = 243_538; + if number >= NON_PLACEHOLDER_BLOCK_HASH_HEIGHT { // Get the block hash or default hash with an attempt to convert U256 block number to u64 Ok(self.0.block_hash(number)?.unwrap_or_default()) } else { From 0cc126476df9c58f9a992a363ca73578d6a15d9d Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Thu, 17 Apr 2025 07:20:43 +0000 Subject: [PATCH 05/14] chore: Cleanup diffs --- crates/storage/provider/src/providers/database/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 04f07adbf..314ba9f83 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -3067,7 +3067,7 @@ impl BlockWrite return Ok(()) } - let first_number: u64 = blocks.first().unwrap().number(); + let first_number = blocks.first().unwrap().number(); let last = blocks.last().unwrap(); let last_block_number = last.number(); From 9254e315b0d6acc6f2cd1c4460812897a9fa765c Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 18 Apr 2025 05:41:24 +0000 Subject: [PATCH 06/14] fix: Fix testnet genesis url --- crates/ethereum/cli/src/hl_testnet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ethereum/cli/src/hl_testnet.rs b/crates/ethereum/cli/src/hl_testnet.rs index 4f52ad5b1..9337f20cb 100644 --- a/crates/ethereum/cli/src/hl_testnet.rs +++ b/crates/ethereum/cli/src/hl_testnet.rs @@ -10,7 +10,7 @@ use std::fs::File; use std::io::{Read, Write}; pub(crate) fn load_hl_testnet() -> ChainSpec { - const TESTNET_GENESIS_URL: &str = "https://raw.githubusercontent.com/sprites0/hl-testnet-genesis/main/19386700.rlp"; + const TESTNET_GENESIS_URL: &str = "https://raw.githubusercontent.com/sprites0/hl-testnet-genesis/main/21043587.rlp"; fn download_testnet_genesis() -> Result<&'static str, Box> { let path = "/tmp/hl_testnet.rmp.lz4"; From ffa084a159275bc7b21496b12b22e2589e9af27b Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 18 Apr 2025 05:41:45 +0000 Subject: [PATCH 07/14] fix: Fix warm address --- crates/ethereum/evm/src/precompile_replay.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ethereum/evm/src/precompile_replay.rs b/crates/ethereum/evm/src/precompile_replay.rs index 51a13405c..e466c4db9 100644 --- a/crates/ethereum/evm/src/precompile_replay.rs +++ b/crates/ethereum/evm/src/precompile_replay.rs @@ -76,10 +76,12 @@ impl PrecompileProvider for ReplayPrecompile { } fn contains(&self, address: &Address) -> bool { - self.precompiles.contains(address) + self.precompiles.contains(address) || self.cache.read().get(address).is_some() } fn warm_addresses(&self) -> Box + '_> { - Box::new(self.precompiles.warm_addresses()) + let addresses: Vec
= + self.precompiles.warm_addresses().chain(self.cache.read().keys().cloned()).collect(); + Box::new(addresses.into_iter()) } } From b2adb0f31daf3f9a21cb06f069f9db5142a70bb2 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 18 Apr 2025 05:47:41 +0000 Subject: [PATCH 08/14] doc: Add testnet instruction --- README.md | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fc6386d32..59a7116d7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,13 @@ Hyperliquid archive node based on [reth](https://github.com/paradigmxyz/reth). -## How to run +## System Transactions Appear as Pseudo Transactions + +Deposit transactions from `0x222..22` to user addresses are intentionally recorded as pseudo transactions. +This change simplifies block explorers, making it easier to track deposit timestamps. +Ensure careful handling when indexing. + +## How to run (mainnet) ```sh # Fetch EVM blocks @@ -11,11 +17,27 @@ $ goofys --region=ap-northeast-1 --requester-pays hl-mainnet-evm-blocks evm-bloc # Run node $ make install -$ reth node --http --http.addr 0.0.0.0 --http.api eth,ots,net,web3 --ws --ws.addr 0.0.0.0 --ws.origins '*' --ws.api eth,ots,net,web3 --ingest-dir ~/evm-blocks --ws.port 8545 +$ reth node --http --http.addr 0.0.0.0 --http.api eth,ots,net,web3 \ + --ws --ws.addr 0.0.0.0 --ws.origins '*' --ws.api eth,ots,net,web3 --ingest-dir ~/evm-blocks --ws.port 8545 ``` -## System Transactions Appear as Pseudo Transactions +## How to run (testnet) -Deposit transactions from `0x222..22` to user addresses are intentionally recorded as pseudo transactions. -This change simplifies block explorers, making it easier to track deposit timestamps. -Ensure careful handling when indexing. +Testnet is supported since block 21043587. + +```sh +# Get testnet genesis at block 21043587 +$ cd ~ +$ git clone https://github.com/sprites0/hl-testnet-genesis +$ zstd --rm -d ~/hl-testnet-genesis/*.zst + +# Init node +$ make install +$ reth init-state --without-evm --chain testnet --header ~/hl-testnet-genesis/21043587.rlp \ + --header-hash 0x2404e9a38b87e9028295df91e922c2e804c3ca75b550289cf5b353a9c61c34ea \ + ~/hl-testnet-genesis/21043587.jsonl --total-difficulty 0 + +# Run node +$ reth node --http --http.addr 0.0.0.0 --http.api eth,ots,net,web3 \ + --ws --ws.addr 0.0.0.0 --ws.origins '*' --ws.api eth,ots,net,web3 --ingest-dir ~/evm-blocks --ws.port 8546 +``` From abfd8656c3812be0ffab749a9c5a5c30fd10709f Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Fri, 18 Apr 2025 06:39:59 +0000 Subject: [PATCH 09/14] doc: Add warning mark --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59a7116d7..6e0e699fe 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Hyperliquid archive node based on [reth](https://github.com/paradigmxyz/reth). -## System Transactions Appear as Pseudo Transactions +## ⚠️ IMPORTANT: System Transactions Appear as Pseudo Transactions Deposit transactions from `0x222..22` to user addresses are intentionally recorded as pseudo transactions. This change simplifies block explorers, making it easier to track deposit timestamps. From f9ee4a3197bcd02a7df902e2615ef75634c1bbe4 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Mon, 21 Apr 2025 06:16:32 +0000 Subject: [PATCH 10/14] fix: Fix out-of-gas logic --- crates/ethereum/evm/src/precompile_replay.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/ethereum/evm/src/precompile_replay.rs b/crates/ethereum/evm/src/precompile_replay.rs index e466c4db9..5756a3179 100644 --- a/crates/ethereum/evm/src/precompile_replay.rs +++ b/crates/ethereum/evm/src/precompile_replay.rs @@ -63,7 +63,12 @@ impl PrecompileProvider for ReplayPrecompile { result.output = bytes.clone(); Ok(Some(result)) } - ReadPrecompileResult::OutOfGas => Err(PrecompileError::OutOfGas.into()), + ReadPrecompileResult::OutOfGas => { + // Use all the gas passed to this precompile + result.gas.spend_all(); + result.result = InstructionResult::OutOfGas; + Ok(Some(result)) + } ReadPrecompileResult::Error => { Err(PrecompileError::other("precompile failed").into()) } From aee8058cd7084c48a349458ceb6991e74eedc556 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Mon, 21 Apr 2025 08:07:21 +0000 Subject: [PATCH 11/14] fix: Fix error logic --- crates/ethereum/evm/src/precompile_replay.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ethereum/evm/src/precompile_replay.rs b/crates/ethereum/evm/src/precompile_replay.rs index 5756a3179..dd8758dad 100644 --- a/crates/ethereum/evm/src/precompile_replay.rs +++ b/crates/ethereum/evm/src/precompile_replay.rs @@ -70,7 +70,9 @@ impl PrecompileProvider for ReplayPrecompile { Ok(Some(result)) } ReadPrecompileResult::Error => { - Err(PrecompileError::other("precompile failed").into()) + result.gas.spend_all(); + result.result = InstructionResult::PrecompileError; + Ok(Some(result)) } ReadPrecompileResult::UnexpectedError => panic!("unexpected precompile error"), }; From 1adedc7184619336218c872823720d203b0ba084 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Tue, 22 Apr 2025 03:03:48 +0000 Subject: [PATCH 12/14] remove: Remove unused code --- crates/ethereum/evm/src/lib.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 798cda040..38d774e7a 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -203,24 +203,6 @@ pub(crate) enum EvmBlock { Reth115(SealedBlock), } -fn load_result(file: String) -> Result, PrecompileErrors> { - let Ok(file) = std::fs::File::open(file) else { - return Ok(None); - }; - let reader = std::io::BufReader::new(file); - let json: serde_json::Value = serde_json::from_reader(reader).unwrap(); - let object = json.as_object().unwrap().clone(); - let success = object.get("success").unwrap().as_bool().unwrap(); - if !success { - return Err(PrecompileErrors::Error(PrecompileError::other("Invalid input"))); - } - let output = - Bytes::from_hex(object.get("output").unwrap().as_str().unwrap().to_owned()).unwrap(); - let gas = object.get("gas").unwrap_or(&serde_json::json!(0)).as_u64().unwrap_or_default(); - println!("output: {}, gas: {}", output.encode_hex(), gas); - Ok(Some((output, gas))) -} - /// Custom EVM configuration. #[derive(Debug, Clone, Default)] #[non_exhaustive] From 7c130d62fdbc7d1a81dd5aaad5bc94674bcea726 Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Thu, 24 Apr 2025 07:47:01 +0000 Subject: [PATCH 13/14] fix: Fix edge case logic --- crates/ethereum/evm/src/precompile_replay.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/ethereum/evm/src/precompile_replay.rs b/crates/ethereum/evm/src/precompile_replay.rs index dd8758dad..09b24d511 100644 --- a/crates/ethereum/evm/src/precompile_replay.rs +++ b/crates/ethereum/evm/src/precompile_replay.rs @@ -56,7 +56,13 @@ impl PrecompileProvider for ReplayPrecompile { output: Bytes::new(), }; - return match *precompile_calls.get(&input).expect("missing precompile call") { + let Some(get) = precompile_calls.get(&input) else { + result.gas.spend_all(); + result.result = InstructionResult::PrecompileError; + return Ok(Some(result)) + }; + + return match *get { ReadPrecompileResult::Ok { gas_used, ref bytes } => { let underflow = result.gas.record_cost(gas_used); assert!(underflow, "Gas underflow is not possible"); From 6e97ed01601c06a0dab960bddf005ff3119320bb Mon Sep 17 00:00:00 2001 From: sprites0 <199826320+sprites0@users.noreply.github.com> Date: Thu, 24 Apr 2025 07:47:14 +0000 Subject: [PATCH 14/14] fix: Fix testnet cutoff block --- crates/ethereum/cli/src/hl_testnet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ethereum/cli/src/hl_testnet.rs b/crates/ethereum/cli/src/hl_testnet.rs index 9337f20cb..380ec52ea 100644 --- a/crates/ethereum/cli/src/hl_testnet.rs +++ b/crates/ethereum/cli/src/hl_testnet.rs @@ -10,7 +10,7 @@ use std::fs::File; use std::io::{Read, Write}; pub(crate) fn load_hl_testnet() -> ChainSpec { - const TESTNET_GENESIS_URL: &str = "https://raw.githubusercontent.com/sprites0/hl-testnet-genesis/main/21043587.rlp"; + const TESTNET_GENESIS_URL: &str = "https://raw.githubusercontent.com/sprites0/hl-testnet-genesis/main/21304281.rlp"; fn download_testnet_genesis() -> Result<&'static str, Box> { let path = "/tmp/hl_testnet.rmp.lz4";