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-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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
34
README.md
34
README.md
@ -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
|
||||||
|
```
|
||||||
|
|||||||
@ -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"] }
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
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
|
/// 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();
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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";
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
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>,
|
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);
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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_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> {
|
||||||
|
|||||||
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user