mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Merge pull request #3 from sprites0/feat/read-precompiles
Read precompiles and some refactors
This commit is contained in:
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
34
README.md
34
README.md
@ -2,7 +2,13 @@
|
||||
|
||||
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
|
||||
# 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
|
||||
```
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -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<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>(
|
||||
engine_api_client: &HttpClient<AuthClientService<HttpBackend>>,
|
||||
payload: EthBuiltPayload,
|
||||
@ -95,7 +54,7 @@ async fn submit_payload<Engine: PayloadTypes + EngineTypes>(
|
||||
}
|
||||
|
||||
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 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<serialized::Block> = rmp_serde::from_read(&mut decoder).unwrap();
|
||||
let blocks: Vec<BlockAndReceipts> = 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,
|
||||
|
||||
@ -6,8 +6,7 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::ne
|
||||
mod block_ingest;
|
||||
mod forwarder;
|
||||
mod serialized;
|
||||
|
||||
use std::path::PathBuf;
|
||||
mod spot_meta;
|
||||
|
||||
use block_ingest::BlockIngest;
|
||||
use clap::{Args, Parser};
|
||||
@ -19,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,
|
||||
@ -37,12 +32,13 @@ fn main() {
|
||||
}
|
||||
|
||||
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");
|
||||
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);
|
||||
@ -55,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
|
||||
|
||||
@ -1,105 +1,43 @@
|
||||
use alloy_consensus::{TxEip1559, TxEip2930, TxLegacy};
|
||||
use reth_primitives::{Log, SealedBlock, Transaction};
|
||||
use alloy_primitives::{Address, Log};
|
||||
use reth_hyperliquid_types::{ReadPrecompileInput, ReadPrecompileResult};
|
||||
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 block: EvmBlock,
|
||||
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)]
|
||||
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<Log>,
|
||||
}
|
||||
|
||||
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(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>,
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct SystemTx {
|
||||
pub tx: Transaction,
|
||||
pub receipt: Option<LegacyReceipt>,
|
||||
}
|
||||
|
||||
61
bin/reth/src/spot_meta.rs
Normal file
61
bin/reth/src/spot_meta.rs
Normal 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)
|
||||
}
|
||||
@ -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<C: ChainSpecParser> NodeCommand<C> {
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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/21304281.rlp";
|
||||
|
||||
fn download_testnet_genesis() -> Result<&'static str, Box<dyn std::error::Error>> {
|
||||
let path = "/tmp/hl_testnet.rmp.lz4";
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<ChainSpec>,
|
||||
evm_factory: HyperliquidEvmFactory,
|
||||
ingest_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl EthEvmConfig {
|
||||
/// Creates a new Ethereum EVM configuration with the given chain spec.
|
||||
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.
|
||||
@ -182,137 +192,65 @@ impl ConfigureEvmEnv for EthEvmConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom precompile that contains static precompiles.
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone)]
|
||||
pub struct L1ReadPrecompiles<CTX> {
|
||||
precompiles: EthPrecompiles<CTX>,
|
||||
warm_addresses: Vec<Address>,
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct BlockAndReceipts {
|
||||
#[serde(default)]
|
||||
pub read_precompile_calls: Vec<(Address, Vec<(ReadPrecompileInput, ReadPrecompileResult)>)>,
|
||||
}
|
||||
|
||||
impl<CTX: ContextTr> L1ReadPrecompiles<CTX> {
|
||||
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<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)))
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) enum EvmBlock {
|
||||
Reth115(SealedBlock),
|
||||
}
|
||||
|
||||
/// Custom EVM configuration.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[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 {
|
||||
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 Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
|
||||
type HaltReason = HaltReason;
|
||||
type Context<DB: Database> = EthEvmContext<DB>;
|
||||
|
||||
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()
|
||||
.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 +447,7 @@ mod tests {
|
||||
assert_eq!(evm.tx, Default::default());
|
||||
}
|
||||
}
|
||||
|
||||
mod precompile_replay;
|
||||
|
||||
pub use precompile_replay::ReplayPrecompile;
|
||||
|
||||
100
crates/ethereum/evm/src/precompile_replay.rs
Normal file
100
crates/ethereum/evm/src/precompile_replay.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
@ -248,7 +248,7 @@ where
|
||||
ctx: &BuilderContext<Node>,
|
||||
) -> 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);
|
||||
|
||||
|
||||
@ -74,6 +74,6 @@ where
|
||||
ctx: &BuilderContext<Node>,
|
||||
pool: Pool,
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Self> {
|
||||
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() })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
crates/hyperliquid-types/Cargo.toml
Normal file
19
crates/hyperliquid-types/Cargo.toml
Normal 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
|
||||
16
crates/hyperliquid-types/src/lib.rs
Normal file
16
crates/hyperliquid-types/src/lib.rs
Normal 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,
|
||||
}
|
||||
@ -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<Node: FullNodeTypes> BuilderContext<Node> {
|
||||
{
|
||||
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> {
|
||||
|
||||
@ -145,6 +145,9 @@ pub struct NodeConfig<ChainSpec> {
|
||||
|
||||
/// All engine related arguments
|
||||
pub engine: EngineArgs,
|
||||
|
||||
/// The ingest directory for the node.
|
||||
pub ingest_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl NodeConfig<ChainSpec> {
|
||||
@ -174,6 +177,7 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
|
||||
pruning: PruningArgs::default(),
|
||||
datadir: DatadirArgs::default(),
|
||||
engine: EngineArgs::default(),
|
||||
ingest_dir: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -465,6 +469,7 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
|
||||
dev: self.dev,
|
||||
pruning: self.pruning,
|
||||
engine: self.engine,
|
||||
ingest_dir: self.ingest_dir,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -492,6 +497,7 @@ impl<ChainSpec> Clone for NodeConfig<ChainSpec> {
|
||||
pruning: self.pruning.clone(),
|
||||
datadir: self.datadir.clone(),
|
||||
engine: self.engine.clone(),
|
||||
ingest_dir: self.ingest_dir.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,14 +20,14 @@ pub trait FullSignedTx: SignedTransaction + MaybeCompact + MaybeSerdeBincodeComp
|
||||
impl<T> 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<u128>) -> Option<Address> {
|
||||
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);
|
||||
|
||||
@ -159,7 +159,8 @@ impl<DB: EvmStateProvider> DatabaseRef for StateProviderDatabase<DB> {
|
||||
///
|
||||
/// Returns `Ok` with the block hash if found, or the default hash otherwise.
|
||||
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
|
||||
Ok(self.0.block_hash(number)?.unwrap_or_default())
|
||||
} else {
|
||||
|
||||
@ -72,7 +72,7 @@ where
|
||||
request: TransactionRequest,
|
||||
) -> Result<TransactionSigned, Self::Error> {
|
||||
let Ok(tx) = request.build_typed_tx() else {
|
||||
return Err(EthApiError::TransactionConversionError);
|
||||
return Err(EthApiError::TransactionConversionError)
|
||||
};
|
||||
|
||||
// Create an empty signature for the transaction.
|
||||
|
||||
@ -3067,7 +3067,7 @@ impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider + 'static> 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();
|
||||
|
||||
Reference in New Issue
Block a user