Merge pull request #3 from sprites0/feat/read-precompiles

Read precompiles and some refactors
This commit is contained in:
sprites0
2025-04-28 00:25:48 -04:00
committed by GitHub
25 changed files with 418 additions and 329 deletions

17
Cargo.lock generated
View File

@ -6686,6 +6686,7 @@ dependencies = [
"reth-execution-types", "reth-execution-types",
"reth-exex", "reth-exex",
"reth-fs-util", "reth-fs-util",
"reth-hyperliquid-types",
"reth-network", "reth-network",
"reth-network-api", "reth-network-api",
"reth-network-p2p", "reth-network-p2p",
@ -7394,6 +7395,7 @@ dependencies = [
"reth-chain-state", "reth-chain-state",
"reth-errors", "reth-errors",
"reth-execution-types", "reth-execution-types",
"reth-hyperliquid-types",
"reth-payload-builder-primitives", "reth-payload-builder-primitives",
"reth-payload-primitives", "reth-payload-primitives",
"reth-primitives", "reth-primitives",
@ -7803,15 +7805,20 @@ dependencies = [
"alloy-genesis", "alloy-genesis",
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
"lz4_flex",
"parking_lot",
"reth-chainspec", "reth-chainspec",
"reth-ethereum-forks", "reth-ethereum-forks",
"reth-evm", "reth-evm",
"reth-execution-types", "reth-execution-types",
"reth-hyperliquid-types",
"reth-primitives", "reth-primitives",
"reth-primitives-traits", "reth-primitives-traits",
"reth-revm", "reth-revm",
"reth-testing-utils", "reth-testing-utils",
"rmp-serde",
"secp256k1 0.30.0", "secp256k1 0.30.0",
"serde",
"serde_json", "serde_json",
"sha2 0.10.8", "sha2 0.10.8",
] ]
@ -7954,6 +7961,16 @@ dependencies = [
"thiserror 2.0.11", "thiserror 2.0.11",
] ]
[[package]]
name = "reth-hyperliquid-types"
version = "1.2.0"
dependencies = [
"alloy-primitives",
"clap",
"reth-cli-commands",
"serde",
]
[[package]] [[package]]
name = "reth-invalid-block-hooks" name = "reth-invalid-block-hooks"
version = "1.2.0" version = "1.2.0"

View File

@ -129,6 +129,7 @@ members = [
"crates/trie/parallel/", "crates/trie/parallel/",
"crates/trie/sparse", "crates/trie/sparse",
"crates/trie/trie", "crates/trie/trie",
"crates/hyperliquid-types",
"examples/beacon-api-sidecar-fetcher/", "examples/beacon-api-sidecar-fetcher/",
"examples/beacon-api-sse/", "examples/beacon-api-sse/",
"examples/bsc-p2p", "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 # See: https://github.com/eira-fransham/crunchy/issues/13
crunchy = "=0.2.2" crunchy = "=0.2.2"
# hyperliquid
reth-hyperliquid-types = { path = "crates/hyperliquid-types" }
lz4_flex = "0.11.3" lz4_flex = "0.11.3"
rmp-serde = "1.3.0" rmp-serde = "1.3.0"

View File

@ -2,7 +2,13 @@
Hyperliquid archive node based on [reth](https://github.com/paradigmxyz/reth). Hyperliquid archive node based on [reth](https://github.com/paradigmxyz/reth).
## How to run ## ⚠️ 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.
Ensure careful handling when indexing.
## How to run (mainnet)
```sh ```sh
# Fetch EVM blocks # Fetch EVM blocks
@ -11,11 +17,27 @@ $ goofys --region=ap-northeast-1 --requester-pays hl-mainnet-evm-blocks evm-bloc
# Run node # Run node
$ make install $ 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. Testnet is supported since block 21043587.
This change simplifies block explorers, making it easier to track deposit timestamps.
Ensure careful handling when indexing. ```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
```

View File

@ -64,6 +64,7 @@ reth-node-events.workspace = true
reth-node-metrics.workspace = true reth-node-metrics.workspace = true
reth-consensus.workspace = true reth-consensus.workspace = true
reth-prune.workspace = true reth-prune.workspace = true
reth-hyperliquid-types.workspace = true
# crypto # crypto
alloy-eips = { workspace = true, features = ["kzg"] } alloy-eips = { workspace = true, features = ["kzg"] }

View File

@ -15,59 +15,18 @@ use reth_node_builder::EngineTypes;
use reth_node_builder::NodeTypesWithEngine; use reth_node_builder::NodeTypesWithEngine;
use reth_node_builder::{rpc::RethRpcAddOns, FullNode}; use reth_node_builder::{rpc::RethRpcAddOns, FullNode};
use reth_payload_builder::{EthBuiltPayload, EthPayloadBuilderAttributes, PayloadId}; 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_provider::{BlockHashReader, StageCheckpointReader};
use reth_rpc_api::EngineApiClient; use reth_rpc_api::EngineApiClient;
use reth_rpc_layer::AuthClientService; use reth_rpc_layer::AuthClientService;
use reth_stages::StageId; use reth_stages::StageId;
use serde::{Deserialize, Serialize};
use tracing::{debug, info}; use tracing::{debug, info};
use crate::serialized::TypedTransaction; use crate::serialized::{BlockAndReceipts, EvmBlock};
use crate::serialized::{self, BlockInner}; use crate::spot_meta::erc20_contract_to_spot_token;
pub(crate) struct BlockIngest(pub PathBuf); 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<EvmContract>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SpotMeta {
tokens: Vec<SpotToken>,
}
async fn fetch_spot_meta(is_testnet: bool) -> Result<SpotMeta, Box<dyn std::error::Error>> {
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<Address, u64> {
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: PayloadTypes + EngineTypes>( async fn submit_payload<Engine: PayloadTypes + EngineTypes>(
engine_api_client: &HttpClient<AuthClientService<HttpBackend>>, engine_api_client: &HttpClient<AuthClientService<HttpBackend>>,
payload: EthBuiltPayload, payload: EthBuiltPayload,
@ -95,7 +54,7 @@ async fn submit_payload<Engine: PayloadTypes + EngineTypes>(
} }
impl BlockIngest { impl BlockIngest {
pub(crate) fn collect_block(&self, height: u64) -> Option<super::serialized::Block> { pub(crate) fn collect_block(&self, height: u64) -> Option<BlockAndReceipts> {
let f = ((height - 1) / 1_000_000) * 1_000_000; let f = ((height - 1) / 1_000_000) * 1_000_000;
let s = ((height - 1) / 1_000) * 1_000; let s = ((height - 1) / 1_000) * 1_000;
let path = format!("{}/{f}/{s}/{height}.rmp.lz4", self.0.to_string_lossy()); 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::fs::File::open(path).unwrap();
let file = std::io::BufReader::new(file); let file = std::io::BufReader::new(file);
let mut decoder = lz4_flex::frame::FrameDecoder::new(file); let mut decoder = lz4_flex::frame::FrameDecoder::new(file);
let blocks: Vec<serialized::Block> = rmp_serde::from_read(&mut decoder).unwrap(); let blocks: Vec<BlockAndReceipts> = rmp_serde::from_read(&mut decoder).unwrap();
Some(blocks[0].clone()) Some(blocks[0].clone())
} else { } else {
None None
@ -135,14 +94,14 @@ impl BlockIngest {
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(); std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis();
let engine_api = node.auth_server_handle().http_client(); 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 { loop {
let Some(original_block) = self.collect_block(height) else { let Some(original_block) = self.collect_block(height) else {
tokio::time::sleep(std::time::Duration::from_millis(200)).await; tokio::time::sleep(std::time::Duration::from_millis(200)).await;
continue; 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"); debug!(target: "reth::cli", ?block, "Built new payload");
let timestamp = block.header().timestamp(); let timestamp = block.header().timestamp();
@ -153,38 +112,28 @@ impl BlockIngest {
std::mem::take(block.body_mut()); std::mem::take(block.body_mut());
let mut system_txs = vec![]; let mut system_txs = vec![];
for transaction in original_block.system_txs { for transaction in original_block.system_txs {
let s = match &transaction.tx { let TypedTransaction::Legacy(tx) = &transaction.tx else {
TypedTransaction::Legacy(tx) => match tx.input().len() { panic!("Unexpected transaction type");
0 => U256::from(0x1), };
_ => { let TxKind::Call(to) = tx.to else {
let TxKind::Call(to) = tx.to else { panic!("Unexpected contract creation");
panic!("Unexpected contract creation"); };
}; let s = if tx.input().is_empty() {
loop { U256::from(0x1)
match evm_map.get(&to).cloned() { } else {
Some(s) => { loop {
break { if let Some(spot) = evm_map.get(&to) {
let mut addr = [0u8; 32]; break spot.to_s();
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;
}
}
}
} }
},
_ => 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( let signature = PrimitiveSignature::new(
// from anvil // from anvil
@ -192,7 +141,7 @@ impl BlockIngest {
s, s,
true, true,
); );
let typed_transaction = transaction.tx.to_reth(); let typed_transaction = transaction.tx;
let tx = TransactionSigned::new( let tx = TransactionSigned::new(
typed_transaction, typed_transaction,
signature, signature,

View File

@ -6,8 +6,7 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::ne
mod block_ingest; mod block_ingest;
mod forwarder; mod forwarder;
mod serialized; mod serialized;
mod spot_meta;
use std::path::PathBuf;
use block_ingest::BlockIngest; use block_ingest::BlockIngest;
use clap::{Args, Parser}; use clap::{Args, Parser};
@ -19,10 +18,6 @@ use tracing::info;
#[derive(Args, Debug, Clone)] #[derive(Args, Debug, Clone)]
struct HyperliquidExtArgs { struct HyperliquidExtArgs {
/// EVM blocks base directory
#[arg(long, default_value = "/tmp/evm-blocks")]
pub ingest_dir: PathBuf,
/// Upstream RPC URL to forward incoming transactions. /// Upstream RPC URL to forward incoming transactions.
#[arg(long, default_value = "https://rpc.hyperliquid.xyz/evm")] #[arg(long, default_value = "https://rpc.hyperliquid.xyz/evm")]
pub upstream_rpc_url: String, pub upstream_rpc_url: String,
@ -37,12 +32,13 @@ fn main() {
} }
if let Err(err) = Cli::<EthereumChainSpecParser, HyperliquidExtArgs>::parse().run( if let Err(err) = Cli::<EthereumChainSpecParser, HyperliquidExtArgs>::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"); info!(target: "reth::cli", "Launching node");
let handle = builder let handle = builder
.node(EthereumNode::default()) .node(EthereumNode::default())
.extend_rpc_modules(move |ctx| { .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(); let rpc = forwarder::EthForwarderExt::new(upstream_rpc_url).into_rpc();
for method_name in rpc.method_names() { for method_name in rpc.method_names() {
ctx.modules.remove_method_from_configured(method_name); ctx.modules.remove_method_from_configured(method_name);
@ -55,7 +51,6 @@ fn main() {
.launch() .launch()
.await?; .await?;
let ingest_dir = ingest_args.ingest_dir;
let ingest = BlockIngest(ingest_dir); let ingest = BlockIngest(ingest_dir);
ingest.run(handle.node).await.unwrap(); ingest.run(handle.node).await.unwrap();
handle.node_exit_future.await handle.node_exit_future.await

View File

@ -1,105 +1,43 @@
use alloy_consensus::{TxEip1559, TxEip2930, TxLegacy}; use alloy_primitives::{Address, Log};
use reth_primitives::{Log, SealedBlock, Transaction}; use reth_hyperliquid_types::{ReadPrecompileInput, ReadPrecompileResult};
use reth_primitives::{SealedBlock, Transaction};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SerializedTransaction { pub(crate) struct BlockAndReceipts {
pub transaction: TypedTransaction, pub block: EvmBlock,
pub signature: SerializedSignature, pub receipts: Vec<LegacyReceipt>,
#[serde(default)]
pub system_txs: Vec<SystemTx>,
#[serde(default)]
pub read_precompile_calls: Vec<(Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>)>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SerializedSignature { pub(crate) enum EvmBlock {
pub r: [u8; 32],
pub s: [u8; 32],
pub v: [u8; 8],
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) enum BlockInner {
Reth115(SealedBlock), Reth115(SealedBlock),
} }
/// A raw transaction.
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// Transaction types were introduced in [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718). pub(crate) struct LegacyReceipt {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] tx_type: LegacyTxType,
pub(crate) enum TypedTransaction { success: bool,
/// Legacy transaction (type `0x0`). cumulative_gas_used: u64,
/// logs: Vec<Log>,
/// 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),
} }
impl TypedTransaction { #[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) fn to_reth(self) -> Transaction { enum LegacyTxType {
match self { Legacy = 0,
Self::Legacy(tx) => Transaction::Legacy(tx), Eip2930 = 1,
Self::Eip2930(tx) => Transaction::Eip2930(tx), Eip1559 = 2,
Self::Eip1559(tx) => Transaction::Eip1559(tx), Eip4844 = 3,
} Eip7702 = 4,
}
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) enum TxType { pub(crate) struct SystemTx {
/// Legacy transaction type. pub tx: Transaction,
Legacy, pub receipt: Option<LegacyReceipt>,
/// EIP-2930 transaction type.
Eip2930,
/// EIP-1559 transaction type.
Eip1559,
/// EIP-4844 transaction type.
Eip4844,
/// EIP-7702 transaction type.
Eip7702,
}
#[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<Log>,
}
#[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<SystemTransaction>,
} }

61
bin/reth/src/spot_meta.rs Normal file
View File

@ -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<EvmContract>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SpotMeta {
tokens: Vec<SpotToken>,
}
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<SpotMeta> {
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<BTreeMap<Address, SpotId>> {
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)
}

View File

@ -114,6 +114,10 @@ pub struct NodeCommand<
/// Additional cli arguments /// Additional cli arguments
#[command(flatten, next_help_heading = "Extension")] #[command(flatten, next_help_heading = "Extension")]
pub ext: Ext, pub ext: Ext,
/// EVM blocks base directory
#[arg(long, default_value = "/tmp/evm-blocks")]
pub ingest_dir: PathBuf,
} }
impl<C: ChainSpecParser> NodeCommand<C> { impl<C: ChainSpecParser> NodeCommand<C> {
@ -165,6 +169,7 @@ impl<
pruning, pruning,
ext, ext,
engine, engine,
ingest_dir,
} = self; } = self;
// set up node config // set up node config
@ -183,6 +188,7 @@ impl<
dev, dev,
pruning, pruning,
engine, engine,
ingest_dir: Some(ingest_dir),
}; };
let data_dir = node_config.datadir(); let data_dir = node_config.datadir();

View File

@ -31,6 +31,9 @@ alloy-eips.workspace = true
tokio = { workspace = true, features = ["sync"] } tokio = { workspace = true, features = ["sync"] }
futures.workspace = true futures.workspace = true
# hyperevm
reth-hyperliquid-types.workspace = true
# misc # misc
auto_impl.workspace = true auto_impl.workspace = true
serde.workspace = true serde.workspace = true

View File

@ -10,7 +10,7 @@ use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
pub(crate) fn load_hl_testnet() -> ChainSpec { 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/21304281.rlp";
fn download_testnet_genesis() -> Result<&'static str, Box<dyn std::error::Error>> { fn download_testnet_genesis() -> Result<&'static str, Box<dyn std::error::Error>> {
let path = "/tmp/hl_testnet.rmp.lz4"; let path = "/tmp/hl_testnet.rmp.lz4";

View File

@ -31,6 +31,12 @@ alloy-consensus.workspace = true
sha2.workspace = true sha2.workspace = true
serde_json.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] [dev-dependencies]
reth-testing-utils.workspace = true reth-testing-utils.workspace = true

View File

@ -23,31 +23,33 @@ use alloy_evm::eth::EthEvmContext;
pub use alloy_evm::EthEvm; pub use alloy_evm::EthEvm;
use alloy_primitives::bytes::BufMut; use alloy_primitives::bytes::BufMut;
use alloy_primitives::hex::{FromHex, ToHexExt}; 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 core::{convert::Infallible, fmt::Debug};
use parking_lot::RwLock;
use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET}; use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
use reth_evm::Database; use reth_evm::Database;
use reth_evm::{ConfigureEvm, ConfigureEvmEnv, EvmEnv, EvmFactory, NextBlockEnvAttributes}; use reth_evm::{ConfigureEvm, ConfigureEvmEnv, EvmEnv, EvmFactory, NextBlockEnvAttributes};
use reth_hyperliquid_types::{ReadPrecompileInput, ReadPrecompileResult};
use reth_primitives::TransactionSigned; use reth_primitives::TransactionSigned;
use reth_primitives::{SealedBlock, Transaction};
use reth_revm::context::result::{EVMError, HaltReason}; use reth_revm::context::result::{EVMError, HaltReason};
use reth_revm::context::{Block, Cfg, ContextTr}; use reth_revm::context::Cfg;
use reth_revm::handler::{EthPrecompiles, PrecompileProvider}; use reth_revm::handler::EthPrecompiles;
use reth_revm::inspector::NoOpInspector; use reth_revm::inspector::NoOpInspector;
use reth_revm::interpreter::interpreter::EthInterpreter; use reth_revm::interpreter::interpreter::EthInterpreter;
use reth_revm::interpreter::{Gas, InstructionResult, InterpreterResult}; use reth_revm::precompile::{PrecompileError, PrecompileErrors, Precompiles};
use reth_revm::precompile::{ use reth_revm::MainBuilder;
PrecompileError, PrecompileErrors, PrecompileFn, PrecompileOutput, PrecompileResult,
Precompiles,
};
use reth_revm::{ use reth_revm::{
context::{BlockEnv, CfgEnv, TxEnv}, context::{BlockEnv, CfgEnv, TxEnv},
context_interface::block::BlobExcessGasAndPrice, context_interface::block::BlobExcessGasAndPrice,
specification::hardfork::SpecId, specification::hardfork::SpecId,
}; };
use reth_revm::{revm, Context, Inspector, MainBuilder, MainContext}; use reth_revm::{Context, Inspector, MainContext};
use sha2::Digest; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::sync::OnceLock; use std::path::PathBuf;
mod config; mod config;
mod fix; mod fix;
@ -65,15 +67,23 @@ pub mod eip6110;
/// Ethereum-related EVM configuration. /// Ethereum-related EVM configuration.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EthEvmConfig { pub struct EthEvmConfig {
chain_spec: Arc<ChainSpec>, chain_spec: Arc<ChainSpec>,
evm_factory: HyperliquidEvmFactory, evm_factory: HyperliquidEvmFactory,
ingest_dir: Option<PathBuf>,
} }
impl EthEvmConfig { impl EthEvmConfig {
/// Creates a new Ethereum EVM configuration with the given chain spec. /// Creates a new Ethereum EVM configuration with the given chain spec.
pub fn new(chain_spec: Arc<ChainSpec>) -> Self { pub fn new(chain_spec: Arc<ChainSpec>) -> 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. /// Creates a new Ethereum EVM configuration for the ethereum mainnet.
@ -182,137 +192,65 @@ impl ConfigureEvmEnv for EthEvmConfig {
} }
} }
/// A custom precompile that contains static precompiles. #[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(missing_debug_implementations)] pub(crate) struct BlockAndReceipts {
#[derive(Clone)] #[serde(default)]
pub struct L1ReadPrecompiles<CTX> { pub read_precompile_calls: Vec<(Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>)>,
precompiles: EthPrecompiles<CTX>,
warm_addresses: Vec<Address>,
} }
impl<CTX: ContextTr> L1ReadPrecompiles<CTX> { #[derive(Debug, Clone, Serialize, Deserialize)]
fn new() -> Self { pub(crate) enum EvmBlock {
let mut this = Self { precompiles: EthPrecompiles::default(), warm_addresses: vec![] }; Reth115(SealedBlock),
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<CTX: ContextTr> PrecompileProvider for L1ReadPrecompiles<CTX> {
type Context = CTX;
type Output = InterpreterResult;
fn set_spec(&mut self, spec: <<Self::Context as ContextTr>::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<Option<Self::Output>, 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<impl Iterator<Item = Address> + '_> {
Box::new(self.warm_addresses.iter().cloned())
}
}
fn load_result(file: String) -> Result<Option<(Bytes, u64)>, 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. /// Custom EVM configuration.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
#[non_exhaustive] #[non_exhaustive]
pub struct HyperliquidEvmFactory; pub struct HyperliquidEvmFactory {
ingest_dir: Option<PathBuf>,
}
pub(crate) fn collect_block(ingest_path: PathBuf, height: u64) -> Option<BlockAndReceipts> {
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<BlockAndReceipts> = rmp_serde::from_read(&mut decoder).unwrap();
Some(blocks[0].clone())
} else {
None
}
}
impl EvmFactory<EvmEnv> for HyperliquidEvmFactory { impl EvmFactory<EvmEnv> for HyperliquidEvmFactory {
type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> = type Evm<DB: Database, I: Inspector<EthEvmContext<DB>, EthInterpreter>> =
EthEvm<DB, I, L1ReadPrecompiles<EthEvmContext<DB>>>; EthEvm<DB, I, ReplayPrecompile<EthEvmContext<DB>>>;
type Tx = TxEnv; type Tx = TxEnv;
type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>; type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
type HaltReason = HaltReason; type HaltReason = HaltReason;
type Context<DB: Database> = EthEvmContext<DB>; type Context<DB: Database> = EthEvmContext<DB>;
fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> { fn create_evm<DB: Database>(&self, db: DB, input: EvmEnv) -> Self::Evm<DB, NoOpInspector> {
let cache = collect_block(self.ingest_dir.clone().unwrap(), input.block_env.number)
.unwrap()
.read_precompile_calls;
let evm = Context::mainnet() let evm = Context::mainnet()
.with_db(db) .with_db(db)
.with_cfg(input.cfg_env) .with_cfg(input.cfg_env)
.with_block(input.block_env) .with_block(input.block_env)
.build_mainnet_with_inspector(NoOpInspector {}) .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) EthEvm::new(evm, false)
} }
@ -509,3 +447,7 @@ mod tests {
assert_eq!(evm.tx, Default::default()); assert_eq!(evm.tx, Default::default());
} }
} }
mod precompile_replay;
pub use precompile_replay::ReplayPrecompile;

View File

@ -0,0 +1,100 @@
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<CTX: ContextTr> {
precompiles: EthPrecompiles<CTX>,
cache: Arc<RwLock<HashMap<Address, HashMap<ReadPrecompileInput, ReadPrecompileResult>>>>,
}
impl<CTX: ContextTr> std::fmt::Debug for ReplayPrecompile<CTX> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReplayPrecompile").finish()
}
}
impl<CTX: ContextTr> ReplayPrecompile<CTX> {
/// Creates a new replay precompile with the given precompiles and cache.
pub fn new(
precompiles: EthPrecompiles<CTX>,
cache: Arc<RwLock<HashMap<Address, HashMap<ReadPrecompileInput, ReadPrecompileResult>>>>,
) -> Self {
Self { precompiles, cache }
}
}
impl<CTX: ContextTr> PrecompileProvider for ReplayPrecompile<CTX> {
type Context = CTX;
type Output = InterpreterResult;
fn set_spec(&mut self, spec: <<Self::Context as ContextTr>::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<Option<Self::Output>, 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(),
};
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");
result.output = bytes.clone();
Ok(Some(result))
}
ReadPrecompileResult::OutOfGas => {
// Use all the gas passed to this precompile
result.gas.spend_all();
result.result = InstructionResult::OutOfGas;
Ok(Some(result))
}
ReadPrecompileResult::Error => {
result.gas.spend_all();
result.result = InstructionResult::PrecompileError;
Ok(Some(result))
}
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) || self.cache.read().get(address).is_some()
}
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address> + '_> {
let addresses: Vec<Address> =
self.precompiles.warm_addresses().chain(self.cache.read().keys().cloned()).collect();
Box::new(addresses.into_iter())
}
}

View File

@ -248,7 +248,7 @@ where
ctx: &BuilderContext<Node>, ctx: &BuilderContext<Node>,
) -> eyre::Result<(Self::EVM, Self::Executor)> { ) -> eyre::Result<(Self::EVM, Self::Executor)> {
let chain_spec = ctx.chain_spec(); 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 strategy_factory = EthExecutionStrategyFactory::new(chain_spec, evm_config.clone());
let executor = BasicBlockExecutorProvider::new(strategy_factory); let executor = BasicBlockExecutorProvider::new(strategy_factory);

View File

@ -74,6 +74,6 @@ where
ctx: &BuilderContext<Node>, ctx: &BuilderContext<Node>,
pool: Pool, pool: Pool,
) -> eyre::Result<Self::PayloadBuilder> { ) -> eyre::Result<Self::PayloadBuilder> {
self.build(EthEvmConfig::new(ctx.chain_spec()), ctx, pool) self.build(EthEvmConfig::new(ctx.chain_spec()).with_ingest_dir(ctx.ingest_dir()), ctx, pool)
} }
} }

View File

@ -332,9 +332,9 @@ impl Hash for TransactionSigned {
impl PartialEq for TransactionSigned { impl PartialEq for TransactionSigned {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.signature == other.signature self.signature == other.signature &&
&& self.transaction == other.transaction self.transaction == other.transaction &&
&& self.tx_hash() == other.tx_hash() self.tx_hash() == other.tx_hash()
} }
} }
@ -582,13 +582,13 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
) )
.unwrap(); .unwrap();
Ok(Self { transaction, signature, ..Default::default() }) Ok(Self { transaction, signature, hash: Default::default() })
} }
} }
impl InMemorySize for TransactionSigned { impl InMemorySize for TransactionSigned {
fn size(&self) -> usize { fn size(&self) -> usize {
let Self { hash: _, signature, transaction, .. } = self; let Self { hash: _, signature, transaction } = self;
self.tx_hash().size() + signature.size() + transaction.size() self.tx_hash().size() + signature.size() + transaction.size()
} }
} }
@ -617,26 +617,26 @@ impl Decodable2718 for TransactionSigned {
TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)), TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
TxType::Eip2930 => { TxType::Eip2930 => {
let (tx, signature) = TxEip2930::rlp_decode_with_signature(buf)?; 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 => { TxType::Eip1559 => {
let (tx, signature) = TxEip1559::rlp_decode_with_signature(buf)?; 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 => { TxType::Eip4844 => {
let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?; 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 => { TxType::Eip7702 => {
let (tx, signature) = TxEip7702::rlp_decode_with_signature(buf)?; 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<Self> { fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
let (tx, signature) = TxLegacy::rlp_decode_with_signature(buf)?; 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() })
} }
} }

View File

@ -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

View File

@ -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,
}

View File

@ -37,7 +37,7 @@ use reth_provider::{
use reth_tasks::TaskExecutor; use reth_tasks::TaskExecutor;
use reth_transaction_pool::{PoolConfig, PoolTransaction, TransactionPool}; use reth_transaction_pool::{PoolConfig, PoolTransaction, TransactionPool};
use secp256k1::SecretKey; use secp256k1::SecretKey;
use std::sync::Arc; use std::{path::PathBuf, sync::Arc};
use tracing::{info, trace, warn}; use tracing::{info, trace, warn};
pub mod add_ons; pub mod add_ons;
@ -750,6 +750,10 @@ impl<Node: FullNodeTypes> BuilderContext<Node> {
{ {
network_builder.build(self.provider.clone()) network_builder.build(self.provider.clone())
} }
pub fn ingest_dir(&self) -> PathBuf {
self.config().ingest_dir.clone().expect("ingest dir not set")
}
} }
impl<Node: FullNodeTypes<Types: NodeTypes<ChainSpec: Hardforks>>> BuilderContext<Node> { impl<Node: FullNodeTypes<Types: NodeTypes<ChainSpec: Hardforks>>> BuilderContext<Node> {

View File

@ -145,6 +145,9 @@ pub struct NodeConfig<ChainSpec> {
/// All engine related arguments /// All engine related arguments
pub engine: EngineArgs, pub engine: EngineArgs,
/// The ingest directory for the node.
pub ingest_dir: Option<PathBuf>,
} }
impl NodeConfig<ChainSpec> { impl NodeConfig<ChainSpec> {
@ -174,6 +177,7 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
pruning: PruningArgs::default(), pruning: PruningArgs::default(),
datadir: DatadirArgs::default(), datadir: DatadirArgs::default(),
engine: EngineArgs::default(), engine: EngineArgs::default(),
ingest_dir: None,
} }
} }
@ -465,6 +469,7 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
dev: self.dev, dev: self.dev,
pruning: self.pruning, pruning: self.pruning,
engine: self.engine, engine: self.engine,
ingest_dir: self.ingest_dir,
} }
} }
} }
@ -492,6 +497,7 @@ impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
pruning: self.pruning.clone(), pruning: self.pruning.clone(),
datadir: self.datadir.clone(), datadir: self.datadir.clone(),
engine: self.engine.clone(), engine: self.engine.clone(),
ingest_dir: self.ingest_dir.clone(),
} }
} }
} }

View File

@ -20,14 +20,14 @@ pub trait FullSignedTx: SignedTransaction + MaybeCompact + MaybeSerdeBincodeComp
impl<T> FullSignedTx for T where T: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {} impl<T> FullSignedTx for T where T: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {}
/// Hyperliquid system transaction from address. /// 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. /// Check if the transaction is impersonated.
/// Signature part is introduced in block_ingest, while the gas_price is trait of hyperliquid system transactions. /// 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<u128>) -> Option<Address> { pub fn is_impersonated_tx(signature: &Signature, gas_price: Option<u128>) -> Option<Address> {
if signature.r() == U256::from(1) && signature.v() == true && gas_price == Some(0u128) { if signature.r() == U256::from(1) && signature.v() == true && gas_price == Some(0u128) {
if signature.s() == U256::from(1) { if signature.s() == U256::from(1) {
Some(HL_SYSTEM_TX_FROM_ADDR) Some(NATIVE_TOKEN_SYSTEM_ADDRESS)
} else { } else {
let s = signature.s().reduce_mod(U256::from(U160::MAX).add(U256::from(1))); let s = signature.s().reduce_mod(U256::from(U160::MAX).add(U256::from(1)));
let s = U160::from(s); let s = U160::from(s);

View File

@ -159,7 +159,8 @@ impl<DB: EvmStateProvider> DatabaseRef for StateProviderDatabase<DB> {
/// ///
/// Returns `Ok` with the block hash if found, or the default hash otherwise. /// Returns `Ok` with the block hash if found, or the default hash otherwise.
fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> { fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
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 // 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()) Ok(self.0.block_hash(number)?.unwrap_or_default())
} else { } else {

View File

@ -72,7 +72,7 @@ where
request: TransactionRequest, request: TransactionRequest,
) -> Result<TransactionSigned, Self::Error> { ) -> Result<TransactionSigned, Self::Error> {
let Ok(tx) = request.build_typed_tx() else { let Ok(tx) = request.build_typed_tx() else {
return Err(EthApiError::TransactionConversionError); return Err(EthApiError::TransactionConversionError)
}; };
// Create an empty signature for the transaction. // Create an empty signature for the transaction.

View File

@ -3067,7 +3067,7 @@ impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider + 'static> BlockWrite
return Ok(()) return Ok(())
} }
let first_number: u64 = blocks.first().unwrap().number(); let first_number = blocks.first().unwrap().number();
let last = blocks.last().unwrap(); let last = blocks.last().unwrap();
let last_block_number = last.number(); let last_block_number = last.number();