mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Make it compilable (still bunch to fix!)
This commit is contained in:
67
.github/workflows/lint.yaml
vendored
Normal file
67
.github/workflows/lint.yaml
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Cargo Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Check
|
||||||
|
run: cargo check
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Cargo Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --all -- --test-threads=1
|
||||||
|
|
||||||
|
udeps:
|
||||||
|
name: udeps
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- uses: taiki-e/install-action@cargo-udeps
|
||||||
|
- name: Run cargo udeps
|
||||||
|
run: cargo +nightly udeps --workspace --lib --examples --tests --benches --all-features --locked
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
name: clippy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
components: clippy
|
||||||
|
override: true
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: clippy
|
||||||
|
run: cargo clippy --workspace --tests --all-features
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: "-D warnings"
|
||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -8720,7 +8720,6 @@ dependencies = [
|
|||||||
"derive_more 0.99.20",
|
"derive_more 0.99.20",
|
||||||
"eyre",
|
"eyre",
|
||||||
"futures",
|
"futures",
|
||||||
"itertools 0.14.0",
|
|
||||||
"jsonrpsee",
|
"jsonrpsee",
|
||||||
"jsonrpsee-core",
|
"jsonrpsee-core",
|
||||||
"jsonrpsee-types",
|
"jsonrpsee-types",
|
||||||
|
|||||||
@ -90,7 +90,6 @@ tokio = { version = "1.36", features = ["full"] }
|
|||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
rmp-serde = "1.0.0"
|
rmp-serde = "1.0.0"
|
||||||
itertools = "0.14.0"
|
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
|||||||
@ -9,7 +9,7 @@ static GENESIS_HASH: B256 =
|
|||||||
b256!("d8fcc13b6a195b88b7b2da3722ff6cad767b13a8c1e9ffb1c73aa9d216d895f0");
|
b256!("d8fcc13b6a195b88b7b2da3722ff6cad767b13a8c1e9ffb1c73aa9d216d895f0");
|
||||||
|
|
||||||
/// The Hyperliqiud Mainnet spec
|
/// The Hyperliqiud Mainnet spec
|
||||||
pub static HL_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
pub fn hl_mainnet() -> ChainSpec {
|
||||||
ChainSpec {
|
ChainSpec {
|
||||||
chain: Chain::from_named(NamedChain::Hyperliquid),
|
chain: Chain::from_named(NamedChain::Hyperliquid),
|
||||||
genesis: serde_json::from_str(include_str!("genesis.json"))
|
genesis: serde_json::from_str(include_str!("genesis.json"))
|
||||||
@ -20,8 +20,7 @@ pub static HL_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
|
|||||||
prune_delete_limit: 10000,
|
prune_delete_limit: 10000,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into()
|
}
|
||||||
});
|
|
||||||
|
|
||||||
/// Empty genesis header for Hyperliquid Mainnet.
|
/// Empty genesis header for Hyperliquid Mainnet.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use super::hl::HL_MAINNET;
|
use crate::chainspec::HlChainSpec;
|
||||||
use reth_chainspec::ChainSpec;
|
|
||||||
|
use super::hl::hl_mainnet;
|
||||||
use reth_cli::chainspec::ChainSpecParser;
|
use reth_cli::chainspec::ChainSpecParser;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -11,21 +12,23 @@ pub const SUPPORTED_CHAINS: &[&str] = &["mainnet"];
|
|||||||
pub struct HlChainSpecParser;
|
pub struct HlChainSpecParser;
|
||||||
|
|
||||||
impl ChainSpecParser for HlChainSpecParser {
|
impl ChainSpecParser for HlChainSpecParser {
|
||||||
type ChainSpec = ChainSpec;
|
type ChainSpec = HlChainSpec;
|
||||||
|
|
||||||
const SUPPORTED_CHAINS: &'static [&'static str] = SUPPORTED_CHAINS;
|
const SUPPORTED_CHAINS: &'static [&'static str] = SUPPORTED_CHAINS;
|
||||||
|
|
||||||
fn parse(s: &str) -> eyre::Result<Arc<ChainSpec>> {
|
fn parse(s: &str) -> eyre::Result<Arc<HlChainSpec>> {
|
||||||
chain_value_parser(s)
|
chain_value_parser(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clap value parser for [`ChainSpec`]s.
|
/// Clap value parser for [`HlChainSpec`]s.
|
||||||
///
|
///
|
||||||
/// Currently only mainnet is supported.
|
/// Currently only mainnet is supported.
|
||||||
pub fn chain_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>> {
|
pub fn chain_value_parser(s: &str) -> eyre::Result<Arc<HlChainSpec>> {
|
||||||
match s {
|
match s {
|
||||||
"mainnet" => Ok(HL_MAINNET.clone().into()),
|
"mainnet" => Ok(Arc::new(HlChainSpec {
|
||||||
|
inner: hl_mainnet(),
|
||||||
|
})),
|
||||||
_ => Err(eyre::eyre!("Unsupported chain: {}", s)),
|
_ => Err(eyre::eyre!("Unsupported chain: {}", s)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/evm/api/builder.rs
Normal file
39
src/evm/api/builder.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use super::HlEvmInner;
|
||||||
|
use crate::evm::{spec::HlSpecId, transaction::HlTxTr};
|
||||||
|
use revm::{
|
||||||
|
context::{Cfg, JournalOutput},
|
||||||
|
context_interface::{Block, JournalTr},
|
||||||
|
handler::instructions::EthInstructions,
|
||||||
|
interpreter::interpreter::EthInterpreter,
|
||||||
|
Context, Database,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Trait that allows for hl HlEvm to be built.
|
||||||
|
pub trait HlBuilder: Sized {
|
||||||
|
/// Type of the context.
|
||||||
|
type Context;
|
||||||
|
|
||||||
|
/// Build the hl with an inspector.
|
||||||
|
fn build_hl_with_inspector<INSP>(
|
||||||
|
self,
|
||||||
|
inspector: INSP,
|
||||||
|
) -> HlEvmInner<Self::Context, INSP, EthInstructions<EthInterpreter, Self::Context>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<BLOCK, TX, CFG, DB, JOURNAL> HlBuilder for Context<BLOCK, TX, CFG, DB, JOURNAL>
|
||||||
|
where
|
||||||
|
BLOCK: Block,
|
||||||
|
TX: HlTxTr,
|
||||||
|
CFG: Cfg<Spec = HlSpecId>,
|
||||||
|
DB: Database,
|
||||||
|
JOURNAL: JournalTr<Database = DB, FinalOutput = JournalOutput>,
|
||||||
|
{
|
||||||
|
type Context = Self;
|
||||||
|
|
||||||
|
fn build_hl_with_inspector<INSP>(
|
||||||
|
self,
|
||||||
|
inspector: INSP,
|
||||||
|
) -> HlEvmInner<Self::Context, INSP, EthInstructions<EthInterpreter, Self::Context>> {
|
||||||
|
HlEvmInner::new(self, inspector)
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/evm/api/ctx.rs
Normal file
23
src/evm/api/ctx.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use crate::evm::{spec::HlSpecId, transaction::HlTxEnv};
|
||||||
|
use revm::{
|
||||||
|
context::{BlockEnv, CfgEnv, TxEnv},
|
||||||
|
database_interface::EmptyDB,
|
||||||
|
Context, Journal, MainContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Type alias for the default context type of the HlEvm.
|
||||||
|
pub type HlContext<DB> = Context<BlockEnv, HlTxEnv<TxEnv>, CfgEnv<HlSpecId>, DB, Journal<DB>>;
|
||||||
|
|
||||||
|
/// Trait that allows for a default context to be created.
|
||||||
|
pub trait DefaultHl {
|
||||||
|
/// Create a default context.
|
||||||
|
fn hl() -> HlContext<EmptyDB>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultHl for HlContext<EmptyDB> {
|
||||||
|
fn hl() -> Self {
|
||||||
|
Context::mainnet()
|
||||||
|
.with_tx(HlTxEnv::default())
|
||||||
|
.with_cfg(CfgEnv::new_with_spec(HlSpecId::default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
107
src/evm/api/exec.rs
Normal file
107
src/evm/api/exec.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
use super::HlEvmInner;
|
||||||
|
use crate::evm::{handler::HlHandler, spec::HlSpecId, transaction::HlTxTr};
|
||||||
|
use revm::{
|
||||||
|
context::{ContextSetters, JournalOutput},
|
||||||
|
context_interface::{
|
||||||
|
result::{EVMError, ExecutionResult, ResultAndState},
|
||||||
|
Cfg, ContextTr, Database, JournalTr,
|
||||||
|
},
|
||||||
|
handler::{instructions::EthInstructions, EthFrame, EvmTr, Handler, PrecompileProvider},
|
||||||
|
inspector::{InspectCommitEvm, InspectEvm, Inspector, InspectorHandler, JournalExt},
|
||||||
|
interpreter::{interpreter::EthInterpreter, InterpreterResult},
|
||||||
|
DatabaseCommit, ExecuteCommitEvm, ExecuteEvm,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type alias for HL context
|
||||||
|
pub trait HlContextTr:
|
||||||
|
ContextTr<Journal: JournalTr<FinalOutput = JournalOutput>, Tx: HlTxTr, Cfg: Cfg<Spec = HlSpecId>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HlContextTr for T where
|
||||||
|
T: ContextTr<
|
||||||
|
Journal: JournalTr<FinalOutput = JournalOutput>,
|
||||||
|
Tx: HlTxTr,
|
||||||
|
Cfg: Cfg<Spec = HlSpecId>,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type alias for the error type of the HlEvm.
|
||||||
|
type HlError<CTX> = EVMError<<<CTX as ContextTr>::Db as Database>::Error>;
|
||||||
|
|
||||||
|
impl<CTX, INSP, PRECOMPILE> ExecuteEvm
|
||||||
|
for HlEvmInner<CTX, INSP, EthInstructions<EthInterpreter, CTX>, PRECOMPILE>
|
||||||
|
where
|
||||||
|
CTX: HlContextTr + ContextSetters,
|
||||||
|
PRECOMPILE: PrecompileProvider<CTX, Output = InterpreterResult>,
|
||||||
|
{
|
||||||
|
type Output = Result<ResultAndState, HlError<CTX>>;
|
||||||
|
|
||||||
|
type Tx = <CTX as ContextTr>::Tx;
|
||||||
|
|
||||||
|
type Block = <CTX as ContextTr>::Block;
|
||||||
|
|
||||||
|
fn set_tx(&mut self, tx: Self::Tx) {
|
||||||
|
self.0.ctx.set_tx(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_block(&mut self, block: Self::Block) {
|
||||||
|
self.0.ctx.set_block(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replay(&mut self) -> Self::Output {
|
||||||
|
let mut h = HlHandler::<_, _, EthFrame<_, _, _>>::new();
|
||||||
|
h.run(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CTX, INSP, PRECOMPILE> ExecuteCommitEvm
|
||||||
|
for HlEvmInner<CTX, INSP, EthInstructions<EthInterpreter, CTX>, PRECOMPILE>
|
||||||
|
where
|
||||||
|
CTX: HlContextTr<Db: DatabaseCommit> + ContextSetters,
|
||||||
|
PRECOMPILE: PrecompileProvider<CTX, Output = InterpreterResult>,
|
||||||
|
{
|
||||||
|
type CommitOutput = Result<ExecutionResult, HlError<CTX>>;
|
||||||
|
|
||||||
|
fn replay_commit(&mut self) -> Self::CommitOutput {
|
||||||
|
self.replay().map(|r| {
|
||||||
|
self.ctx().db().commit(r.state);
|
||||||
|
r.result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CTX, INSP, PRECOMPILE> InspectEvm
|
||||||
|
for HlEvmInner<CTX, INSP, EthInstructions<EthInterpreter, CTX>, PRECOMPILE>
|
||||||
|
where
|
||||||
|
CTX: HlContextTr<Journal: JournalExt> + ContextSetters,
|
||||||
|
INSP: Inspector<CTX, EthInterpreter>,
|
||||||
|
PRECOMPILE: PrecompileProvider<CTX, Output = InterpreterResult>,
|
||||||
|
{
|
||||||
|
type Inspector = INSP;
|
||||||
|
|
||||||
|
fn set_inspector(&mut self, inspector: Self::Inspector) {
|
||||||
|
self.0.inspector = inspector;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inspect_replay(&mut self) -> Self::Output {
|
||||||
|
let mut h = HlHandler::<_, _, EthFrame<_, _, _>>::new();
|
||||||
|
h.inspect_run(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CTX, INSP, PRECOMPILE> InspectCommitEvm
|
||||||
|
for HlEvmInner<CTX, INSP, EthInstructions<EthInterpreter, CTX>, PRECOMPILE>
|
||||||
|
where
|
||||||
|
CTX: HlContextTr<Journal: JournalExt, Db: DatabaseCommit> + ContextSetters,
|
||||||
|
INSP: Inspector<CTX, EthInterpreter>,
|
||||||
|
PRECOMPILE: PrecompileProvider<CTX, Output = InterpreterResult>,
|
||||||
|
{
|
||||||
|
fn inspect_replay_commit(&mut self) -> Self::CommitOutput {
|
||||||
|
self.inspect_replay().map(|r| {
|
||||||
|
self.ctx().db().commit(r.state);
|
||||||
|
r.result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/evm/api/mod.rs
Normal file
134
src/evm/api/mod.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use super::precompiles::HlPrecompiles;
|
||||||
|
use revm::{
|
||||||
|
context::{ContextSetters, Evm as EvmCtx},
|
||||||
|
context_interface::ContextTr,
|
||||||
|
handler::{
|
||||||
|
instructions::{EthInstructions, InstructionProvider},
|
||||||
|
EvmTr, PrecompileProvider,
|
||||||
|
},
|
||||||
|
inspector::{InspectorEvmTr, JournalExt},
|
||||||
|
interpreter::{interpreter::EthInterpreter, Interpreter, InterpreterAction, InterpreterTypes},
|
||||||
|
Inspector,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod builder;
|
||||||
|
pub mod ctx;
|
||||||
|
mod exec;
|
||||||
|
|
||||||
|
pub struct HlEvmInner<CTX, INSP, I = EthInstructions<EthInterpreter, CTX>, P = HlPrecompiles>(
|
||||||
|
pub EvmCtx<CTX, INSP, I, P>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<CTX: ContextTr, INSP>
|
||||||
|
HlEvmInner<CTX, INSP, EthInstructions<EthInterpreter, CTX>, HlPrecompiles>
|
||||||
|
{
|
||||||
|
pub fn new(ctx: CTX, inspector: INSP) -> Self {
|
||||||
|
Self(EvmCtx {
|
||||||
|
ctx,
|
||||||
|
inspector,
|
||||||
|
instruction: EthInstructions::new_mainnet(),
|
||||||
|
precompiles: HlPrecompiles::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes self and returns a new Evm type with given Precompiles.
|
||||||
|
pub fn with_precompiles<OP>(
|
||||||
|
self,
|
||||||
|
precompiles: OP,
|
||||||
|
) -> HlEvmInner<CTX, INSP, EthInstructions<EthInterpreter, CTX>, OP> {
|
||||||
|
HlEvmInner(self.0.with_precompiles(precompiles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CTX, INSP, I, P> InspectorEvmTr for HlEvmInner<CTX, INSP, I, P>
|
||||||
|
where
|
||||||
|
CTX: ContextTr<Journal: JournalExt> + ContextSetters,
|
||||||
|
I: InstructionProvider<
|
||||||
|
Context = CTX,
|
||||||
|
InterpreterTypes: InterpreterTypes<Output = InterpreterAction>,
|
||||||
|
>,
|
||||||
|
INSP: Inspector<CTX, I::InterpreterTypes>,
|
||||||
|
P: PrecompileProvider<CTX>,
|
||||||
|
{
|
||||||
|
type Inspector = INSP;
|
||||||
|
|
||||||
|
fn inspector(&mut self) -> &mut Self::Inspector {
|
||||||
|
&mut self.0.inspector
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ctx_inspector(&mut self) -> (&mut Self::Context, &mut Self::Inspector) {
|
||||||
|
(&mut self.0.ctx, &mut self.0.inspector)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_inspect_interpreter(
|
||||||
|
&mut self,
|
||||||
|
interpreter: &mut Interpreter<
|
||||||
|
<Self::Instructions as InstructionProvider>::InterpreterTypes,
|
||||||
|
>,
|
||||||
|
) -> <<Self::Instructions as InstructionProvider>::InterpreterTypes as InterpreterTypes>::Output
|
||||||
|
{
|
||||||
|
self.0.run_inspect_interpreter(interpreter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CTX, INSP, I, P> EvmTr for HlEvmInner<CTX, INSP, I, P>
|
||||||
|
where
|
||||||
|
CTX: ContextTr,
|
||||||
|
I: InstructionProvider<
|
||||||
|
Context = CTX,
|
||||||
|
InterpreterTypes: InterpreterTypes<Output = InterpreterAction>,
|
||||||
|
>,
|
||||||
|
P: PrecompileProvider<CTX>,
|
||||||
|
{
|
||||||
|
type Context = CTX;
|
||||||
|
type Instructions = I;
|
||||||
|
type Precompiles = P;
|
||||||
|
|
||||||
|
fn run_interpreter(
|
||||||
|
&mut self,
|
||||||
|
interpreter: &mut Interpreter<
|
||||||
|
<Self::Instructions as InstructionProvider>::InterpreterTypes,
|
||||||
|
>,
|
||||||
|
) -> <<Self::Instructions as InstructionProvider>::InterpreterTypes as InterpreterTypes>::Output
|
||||||
|
{
|
||||||
|
let context = &mut self.0.ctx;
|
||||||
|
let instructions = &mut self.0.instruction;
|
||||||
|
interpreter.run_plain(instructions.instruction_table(), context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ctx(&mut self) -> &mut Self::Context {
|
||||||
|
&mut self.0.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ctx_ref(&self) -> &Self::Context {
|
||||||
|
&self.0.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ctx_instructions(&mut self) -> (&mut Self::Context, &mut Self::Instructions) {
|
||||||
|
(&mut self.0.ctx, &mut self.0.instruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ctx_precompiles(&mut self) -> (&mut Self::Context, &mut Self::Precompiles) {
|
||||||
|
(&mut self.0.ctx, &mut self.0.precompiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
|
// mod test {
|
||||||
|
// use super::{builder::HlBuilder, ctx::DefaultHl};
|
||||||
|
// use revm::{
|
||||||
|
// inspector::{InspectEvm, NoOpInspector},
|
||||||
|
// Context, ExecuteEvm,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn default_run_bsc() {
|
||||||
|
// let ctx = Context::bsc();
|
||||||
|
// let mut evm = ctx.build_bsc_with_inspector(NoOpInspector {});
|
||||||
|
|
||||||
|
// // execute
|
||||||
|
// let _ = evm.replay();
|
||||||
|
// // inspect
|
||||||
|
// let _ = evm.inspect_replay();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
148
src/evm/handler.rs
Normal file
148
src/evm/handler.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
//! EVM Handler related to Hl chain
|
||||||
|
|
||||||
|
use super::{spec::HlSpecId, transaction::HlTxTr};
|
||||||
|
use revm::{
|
||||||
|
context::{
|
||||||
|
result::{ExecutionResult, HaltReason},
|
||||||
|
Cfg, ContextTr, JournalOutput, LocalContextTr,
|
||||||
|
},
|
||||||
|
context_interface::{result::ResultAndState, JournalTr},
|
||||||
|
handler::{handler::EvmTrError, EvmTr, Frame, FrameResult, Handler, MainnetHandler},
|
||||||
|
inspector::{Inspector, InspectorEvmTr, InspectorFrame, InspectorHandler},
|
||||||
|
interpreter::{interpreter::EthInterpreter, FrameInput, InitialAndFloorGas, SuccessOrHalt},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct HlHandler<EVM, ERROR, FRAME> {
|
||||||
|
pub mainnet: MainnetHandler<EVM, ERROR, FRAME>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EVM, ERROR, FRAME> HlHandler<EVM, ERROR, FRAME> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
mainnet: MainnetHandler::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EVM, ERROR, FRAME> Default for HlHandler<EVM, ERROR, FRAME> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HlContextTr:
|
||||||
|
ContextTr<Journal: JournalTr<FinalOutput = JournalOutput>, Tx: HlTxTr, Cfg: Cfg<Spec = HlSpecId>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HlContextTr for T where
|
||||||
|
T: ContextTr<
|
||||||
|
Journal: JournalTr<FinalOutput = JournalOutput>,
|
||||||
|
Tx: HlTxTr,
|
||||||
|
Cfg: Cfg<Spec = HlSpecId>,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EVM, ERROR, FRAME> Handler for HlHandler<EVM, ERROR, FRAME>
|
||||||
|
where
|
||||||
|
EVM: EvmTr<Context: HlContextTr>,
|
||||||
|
ERROR: EvmTrError<EVM>,
|
||||||
|
FRAME: Frame<Evm = EVM, Error = ERROR, FrameResult = FrameResult, FrameInit = FrameInput>,
|
||||||
|
{
|
||||||
|
type Evm = EVM;
|
||||||
|
type Error = ERROR;
|
||||||
|
type Frame = FRAME;
|
||||||
|
type HaltReason = HaltReason;
|
||||||
|
|
||||||
|
fn validate_initial_tx_gas(
|
||||||
|
&self,
|
||||||
|
evm: &Self::Evm,
|
||||||
|
) -> Result<revm::interpreter::InitialAndFloorGas, Self::Error> {
|
||||||
|
let ctx = evm.ctx_ref();
|
||||||
|
let tx = ctx.tx();
|
||||||
|
|
||||||
|
if tx.is_system_transaction() {
|
||||||
|
return Ok(InitialAndFloorGas {
|
||||||
|
initial_gas: 0,
|
||||||
|
floor_gas: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mainnet.validate_initial_tx_gas(evm)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reward_beneficiary(
|
||||||
|
&self,
|
||||||
|
evm: &mut Self::Evm,
|
||||||
|
exec_result: &mut <Self::Frame as Frame>::FrameResult,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output(
|
||||||
|
&self,
|
||||||
|
evm: &mut Self::Evm,
|
||||||
|
result: <Self::Frame as Frame>::FrameResult,
|
||||||
|
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
|
||||||
|
let ctx = evm.ctx();
|
||||||
|
ctx.error();
|
||||||
|
let tx = ctx.tx();
|
||||||
|
// used gas with refund calculated.
|
||||||
|
let gas_refunded = if tx.is_system_transaction() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
result.gas().refunded() as u64
|
||||||
|
};
|
||||||
|
let final_gas_used = result.gas().spent() - gas_refunded;
|
||||||
|
let output = result.output();
|
||||||
|
let instruction_result = result.into_interpreter_result();
|
||||||
|
|
||||||
|
// Reset journal and return present state.
|
||||||
|
let JournalOutput { state, logs } = evm.ctx().journal().finalize();
|
||||||
|
|
||||||
|
let result = match SuccessOrHalt::from(instruction_result.result) {
|
||||||
|
SuccessOrHalt::Success(reason) => ExecutionResult::Success {
|
||||||
|
reason,
|
||||||
|
gas_used: final_gas_used,
|
||||||
|
gas_refunded,
|
||||||
|
logs,
|
||||||
|
output,
|
||||||
|
},
|
||||||
|
SuccessOrHalt::Revert => ExecutionResult::Revert {
|
||||||
|
gas_used: final_gas_used,
|
||||||
|
output: output.into_data(),
|
||||||
|
},
|
||||||
|
SuccessOrHalt::Halt(reason) => ExecutionResult::Halt {
|
||||||
|
reason,
|
||||||
|
gas_used: final_gas_used,
|
||||||
|
},
|
||||||
|
// Only two internal return flags.
|
||||||
|
flag @ (SuccessOrHalt::FatalExternalError | SuccessOrHalt::Internal(_)) => {
|
||||||
|
panic!(
|
||||||
|
"Encountered unexpected internal return flag: {flag:?} with instruction result: {instruction_result:?}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear local context
|
||||||
|
evm.ctx().local().clear();
|
||||||
|
// Clear journal
|
||||||
|
evm.ctx().journal().clear();
|
||||||
|
|
||||||
|
Ok(ResultAndState { result, state })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<EVM, ERROR, FRAME> InspectorHandler for HlHandler<EVM, ERROR, FRAME>
|
||||||
|
where
|
||||||
|
EVM: InspectorEvmTr<
|
||||||
|
Context: HlContextTr,
|
||||||
|
Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
|
||||||
|
>,
|
||||||
|
ERROR: EvmTrError<EVM>,
|
||||||
|
FRAME: Frame<Evm = EVM, Error = ERROR, FrameResult = FrameResult, FrameInit = FrameInput>
|
||||||
|
+ InspectorFrame<IT = EthInterpreter>,
|
||||||
|
{
|
||||||
|
type IT = EthInterpreter;
|
||||||
|
}
|
||||||
5
src/evm/mod.rs
Normal file
5
src/evm/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod api;
|
||||||
|
mod handler;
|
||||||
|
pub mod precompiles;
|
||||||
|
pub mod spec;
|
||||||
|
pub mod transaction;
|
||||||
87
src/evm/precompiles.rs
Normal file
87
src/evm/precompiles.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
use super::spec::HlSpecId;
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use once_cell::{race::OnceBox, sync::Lazy};
|
||||||
|
use revm::{
|
||||||
|
context::Cfg,
|
||||||
|
context_interface::ContextTr,
|
||||||
|
handler::{EthPrecompiles, PrecompileProvider},
|
||||||
|
interpreter::{InputsImpl, InterpreterResult},
|
||||||
|
precompile::{bls12_381, kzg_point_evaluation, modexp, secp256r1, Precompiles},
|
||||||
|
primitives::{hardfork::SpecId, Address},
|
||||||
|
};
|
||||||
|
use std::boxed::Box;
|
||||||
|
|
||||||
|
// HL precompile provider
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HlPrecompiles {
|
||||||
|
/// Inner precompile provider is same as Ethereums.
|
||||||
|
inner: EthPrecompiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlPrecompiles {
|
||||||
|
/// Create a new precompile provider with the given hl spec.
|
||||||
|
#[inline]
|
||||||
|
pub fn new(spec: HlSpecId) -> Self {
|
||||||
|
let precompiles = cancun();
|
||||||
|
|
||||||
|
Self { inner: EthPrecompiles { precompiles, spec: spec.into_eth_spec() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn precompiles(&self) -> &'static Precompiles {
|
||||||
|
self.inner.precompiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns precompiles for Istanbul spec.
|
||||||
|
pub fn cancun() -> &'static Precompiles {
|
||||||
|
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
|
||||||
|
INSTANCE.get_or_init(|| {
|
||||||
|
let mut precompiles = Precompiles::cancun().clone();
|
||||||
|
// precompiles.extend([tendermint::TENDERMINT_HEADER_VALIDATION, iavl::IAVL_PROOF_VALIDATION]);
|
||||||
|
Box::new(precompiles)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CTX> PrecompileProvider<CTX> for HlPrecompiles
|
||||||
|
where
|
||||||
|
CTX: ContextTr<Cfg: Cfg<Spec = HlSpecId>>,
|
||||||
|
{
|
||||||
|
type Output = InterpreterResult;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
|
||||||
|
*self = Self::new(spec);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
context: &mut CTX,
|
||||||
|
address: &Address,
|
||||||
|
inputs: &InputsImpl,
|
||||||
|
is_static: bool,
|
||||||
|
gas_limit: u64,
|
||||||
|
) -> Result<Option<Self::Output>, String> {
|
||||||
|
self.inner.run(context, address, inputs, is_static, gas_limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
|
||||||
|
self.inner.warm_addresses()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn contains(&self, address: &Address) -> bool {
|
||||||
|
self.inner.contains(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HlPrecompiles {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(HlSpecId::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/evm/spec.rs
Normal file
55
src/evm/spec.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use revm::primitives::hardfork::SpecId;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
pub enum HlSpecId {
|
||||||
|
#[default]
|
||||||
|
V1, // V1
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlSpecId {
|
||||||
|
pub const fn is_enabled_in(self, other: HlSpecId) -> bool {
|
||||||
|
other as u8 <= self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the [`HlSpecId`] into a [`SpecId`].
|
||||||
|
pub const fn into_eth_spec(self) -> SpecId {
|
||||||
|
match self {
|
||||||
|
Self::V1 => SpecId::CANCUN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HlSpecId> for SpecId {
|
||||||
|
fn from(spec: HlSpecId) -> Self {
|
||||||
|
spec.into_eth_spec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// String identifiers for HL hardforks
|
||||||
|
pub mod name {
|
||||||
|
pub const V1: &str = "V1";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for HlSpecId {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
name::V1 => Self::V1,
|
||||||
|
_ => return Err(format!("Unknown HL spec: {s}")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HlSpecId> for &'static str {
|
||||||
|
fn from(spec_id: HlSpecId) -> Self {
|
||||||
|
match spec_id {
|
||||||
|
HlSpecId::V1 => name::V1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
203
src/evm/transaction.rs
Normal file
203
src/evm/transaction.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
use alloy_consensus::Transaction as AlloyTransaction;
|
||||||
|
use alloy_rpc_types::AccessList;
|
||||||
|
use auto_impl::auto_impl;
|
||||||
|
use reth_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, TransactionEnv};
|
||||||
|
use reth_primitives::TransactionSigned;
|
||||||
|
use revm::{
|
||||||
|
context::TxEnv,
|
||||||
|
context_interface::transaction::Transaction,
|
||||||
|
primitives::{Address, Bytes, TxKind, B256, U256},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[auto_impl(&, &mut, Box, Arc)]
|
||||||
|
pub trait HlTxTr: Transaction {
|
||||||
|
/// Whether the transaction is a system transaction
|
||||||
|
fn is_system_transaction(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct HlTxEnv<T: Transaction> {
|
||||||
|
pub base: T,
|
||||||
|
pub is_system_transaction: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Transaction> HlTxEnv<T> {
|
||||||
|
pub fn new(base: T) -> Self {
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
is_system_transaction: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HlTxEnv<TxEnv> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
base: TxEnv::default(),
|
||||||
|
is_system_transaction: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Transaction> Transaction for HlTxEnv<T> {
|
||||||
|
type AccessListItem<'a>
|
||||||
|
= T::AccessListItem<'a>
|
||||||
|
where
|
||||||
|
T: 'a;
|
||||||
|
type Authorization<'a>
|
||||||
|
= T::Authorization<'a>
|
||||||
|
where
|
||||||
|
T: 'a;
|
||||||
|
|
||||||
|
fn tx_type(&self) -> u8 {
|
||||||
|
self.base.tx_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn caller(&self) -> Address {
|
||||||
|
self.base.caller()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gas_limit(&self) -> u64 {
|
||||||
|
self.base.gas_limit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> U256 {
|
||||||
|
self.base.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input(&self) -> &Bytes {
|
||||||
|
self.base.input()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonce(&self) -> u64 {
|
||||||
|
self.base.nonce()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> TxKind {
|
||||||
|
self.base.kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chain_id(&self) -> Option<u64> {
|
||||||
|
self.base.chain_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gas_price(&self) -> u128 {
|
||||||
|
self.base.gas_price()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
|
||||||
|
self.base.access_list()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blob_versioned_hashes(&self) -> &[B256] {
|
||||||
|
self.base.blob_versioned_hashes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_fee_per_blob_gas(&self) -> u128 {
|
||||||
|
self.base.max_fee_per_blob_gas()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authorization_list_len(&self) -> usize {
|
||||||
|
self.base.authorization_list_len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
|
||||||
|
self.base.authorization_list()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_fee_per_gas(&self) -> u128 {
|
||||||
|
self.base.max_fee_per_gas()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_priority_fee_per_gas(&self) -> Option<u128> {
|
||||||
|
self.base.max_priority_fee_per_gas()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn effective_gas_price(&self, base_fee: u128) -> u128 {
|
||||||
|
self.base.effective_gas_price(base_fee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Transaction> HlTxTr for HlTxEnv<T> {
|
||||||
|
fn is_system_transaction(&self) -> bool {
|
||||||
|
self.is_system_transaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: revm::context::Transaction> IntoTxEnv<Self> for HlTxEnv<T> {
|
||||||
|
fn into_tx_env(self) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRecoveredTx<TransactionSigned> for HlTxEnv<TxEnv> {
|
||||||
|
fn from_recovered_tx(tx: &TransactionSigned, sender: Address) -> Self {
|
||||||
|
Self::new(TxEnv::from_recovered_tx(tx, sender))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromTxWithEncoded<TransactionSigned> for HlTxEnv<TxEnv> {
|
||||||
|
fn from_encoded_tx(tx: &TransactionSigned, sender: Address, _encoded: Bytes) -> Self {
|
||||||
|
let base = match tx.clone().into_typed_transaction() {
|
||||||
|
reth_primitives::Transaction::Legacy(tx) => TxEnv::from_recovered_tx(&tx, sender),
|
||||||
|
reth_primitives::Transaction::Eip2930(tx) => TxEnv::from_recovered_tx(&tx, sender),
|
||||||
|
reth_primitives::Transaction::Eip1559(tx) => TxEnv::from_recovered_tx(&tx, sender),
|
||||||
|
reth_primitives::Transaction::Eip4844(tx) => TxEnv::from_recovered_tx(&tx, sender),
|
||||||
|
reth_primitives::Transaction::Eip7702(tx) => TxEnv::from_recovered_tx(&tx, sender),
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_system_transaction = match tx.gas_price() {
|
||||||
|
Some(x) => x == 0u128,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
is_system_transaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransactionEnv> TransactionEnv for HlTxEnv<T> {
|
||||||
|
fn set_gas_limit(&mut self, gas_limit: u64) {
|
||||||
|
self.base.set_gas_limit(gas_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nonce(&self) -> u64 {
|
||||||
|
TransactionEnv::nonce(&self.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_nonce(&mut self, nonce: u64) {
|
||||||
|
self.base.set_nonce(nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_access_list(&mut self, access_list: AccessList) {
|
||||||
|
self.base.set_access_list(access_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use revm::primitives::Address;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hl_transaction_fields() {
|
||||||
|
let hl_tx = HlTxEnv {
|
||||||
|
base: TxEnv {
|
||||||
|
tx_type: 0,
|
||||||
|
gas_limit: 10,
|
||||||
|
gas_price: 100,
|
||||||
|
gas_priority_fee: Some(5),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
is_system_transaction: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(hl_tx.tx_type(), 0);
|
||||||
|
assert_eq!(hl_tx.gas_limit(), 10);
|
||||||
|
assert_eq!(hl_tx.kind(), revm::primitives::TxKind::Call(Address::ZERO));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,7 +5,7 @@ use reth_chainspec::ForkCondition;
|
|||||||
use reth_ethereum_forks::{hardfork, ChainHardforks, EthereumHardfork, Hardfork};
|
use reth_ethereum_forks::{hardfork, ChainHardforks, EthereumHardfork, Hardfork};
|
||||||
|
|
||||||
hardfork!(
|
hardfork!(
|
||||||
/// The name of a bsc hardfork.
|
/// The name of a hl hardfork.
|
||||||
///
|
///
|
||||||
/// When building a list of hardforks for a chain, it's still expected to mix with [`EthereumHardfork`].
|
/// When building a list of hardforks for a chain, it's still expected to mix with [`EthereumHardfork`].
|
||||||
/// There is no name for these hardforks; just some bugfixes on the evm chain.
|
/// There is no name for these hardforks; just some bugfixes on the evm chain.
|
||||||
@ -62,8 +62,8 @@ impl HlHardfork {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bsc mainnet list of hardforks.
|
/// Hl mainnet list of hardforks.
|
||||||
pub fn bsc_mainnet() -> ChainHardforks {
|
pub fn hl_mainnet() -> ChainHardforks {
|
||||||
ChainHardforks::new(vec![
|
ChainHardforks::new(vec![
|
||||||
(EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)),
|
(EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)),
|
||||||
(EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)),
|
(EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)),
|
||||||
@ -86,14 +86,10 @@ impl HlHardfork {
|
|||||||
EthereumHardfork::MuirGlacier.boxed(),
|
EthereumHardfork::MuirGlacier.boxed(),
|
||||||
ForkCondition::Block(0),
|
ForkCondition::Block(0),
|
||||||
),
|
),
|
||||||
(
|
(EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)),
|
||||||
EthereumHardfork::Berlin.boxed(),
|
(EthereumHardfork::London.boxed(), ForkCondition::Block(0)),
|
||||||
ForkCondition::Block(31302048),
|
(EthereumHardfork::Shanghai.boxed(), ForkCondition::Block(0)),
|
||||||
),
|
(EthereumHardfork::Cancun.boxed(), ForkCondition::Block(0)),
|
||||||
(
|
|
||||||
EthereumHardfork::London.boxed(),
|
|
||||||
ForkCondition::Block(31302048),
|
|
||||||
),
|
|
||||||
(Self::V1.boxed(), ForkCondition::Block(0)),
|
(Self::V1.boxed(), ForkCondition::Block(0)),
|
||||||
(Self::V2.boxed(), ForkCondition::Block(0)),
|
(Self::V2.boxed(), ForkCondition::Block(0)),
|
||||||
(Self::V3.boxed(), ForkCondition::Block(0)),
|
(Self::V3.boxed(), ForkCondition::Block(0)),
|
||||||
|
|||||||
10
src/lib.rs
10
src/lib.rs
@ -1,6 +1,6 @@
|
|||||||
mod chainspec;
|
pub mod chainspec;
|
||||||
mod consensus;
|
pub mod consensus;
|
||||||
|
mod evm;
|
||||||
mod hardforks;
|
mod hardforks;
|
||||||
mod node;
|
pub mod node;
|
||||||
|
pub use node::primitives::{HlBlock, HlBlockBody, HlPrimitives};
|
||||||
pub use node::primitives;
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use clap::{Args, Parser};
|
use clap::{Args, Parser};
|
||||||
use reth::builder::NodeHandle;
|
use reth::builder::NodeHandle;
|
||||||
use reth_hl::{
|
use reth_hl::{
|
||||||
chainspec::HlChainSpecParser,
|
chainspec::parser::HlChainSpecParser,
|
||||||
node::{cli::Cli, HlNode},
|
node::{cli::Cli, HlNode},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
hardforks::HlHardforks,
|
hardforks::HlHardforks,
|
||||||
node::HlNode,
|
node::HlNode,
|
||||||
primitives::{HlBlock, HlBlockBody, HlPrimitives},
|
{HlBlock, HlBlockBody, HlPrimitives},
|
||||||
};
|
};
|
||||||
use reth::{
|
use reth::{
|
||||||
api::FullNodeTypes,
|
api::FullNodeTypes,
|
||||||
|
|||||||
117
src/node/engine.rs
Normal file
117
src/node/engine.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
node::{rpc::engine_api::payload::HlPayloadTypes, HlNode},
|
||||||
|
HlBlock, HlPrimitives,
|
||||||
|
};
|
||||||
|
use alloy_eips::eip7685::Requests;
|
||||||
|
use alloy_primitives::U256;
|
||||||
|
use reth::{
|
||||||
|
api::FullNodeTypes,
|
||||||
|
builder::{components::PayloadServiceBuilder, BuilderContext},
|
||||||
|
payload::{PayloadBuilderHandle, PayloadServiceCommand},
|
||||||
|
transaction_pool::TransactionPool,
|
||||||
|
};
|
||||||
|
use reth_evm::ConfigureEvm;
|
||||||
|
use reth_payload_primitives::BuiltPayload;
|
||||||
|
use reth_primitives::SealedBlock;
|
||||||
|
use tokio::sync::{broadcast, mpsc};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
/// Built payload for Hl. This is similar to [`EthBuiltPayload`] but without sidecars as those
|
||||||
|
/// included into [`HlBlock`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HlBuiltPayload {
|
||||||
|
/// The built block
|
||||||
|
pub(crate) block: Arc<SealedBlock<HlBlock>>,
|
||||||
|
/// The fees of the block
|
||||||
|
pub(crate) fees: U256,
|
||||||
|
/// The requests of the payload
|
||||||
|
pub(crate) requests: Option<Requests>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuiltPayload for HlBuiltPayload {
|
||||||
|
type Primitives = HlPrimitives;
|
||||||
|
|
||||||
|
fn block(&self) -> &SealedBlock<HlBlock> {
|
||||||
|
self.block.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fees(&self) -> U256 {
|
||||||
|
self.fees
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requests(&self) -> Option<Requests> {
|
||||||
|
self.requests.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct HlPayloadServiceBuilder;
|
||||||
|
|
||||||
|
impl<Node, Pool, Evm> PayloadServiceBuilder<Node, Pool, Evm> for HlPayloadServiceBuilder
|
||||||
|
where
|
||||||
|
Node: FullNodeTypes<Types = HlNode>,
|
||||||
|
Pool: TransactionPool,
|
||||||
|
Evm: ConfigureEvm,
|
||||||
|
{
|
||||||
|
async fn spawn_payload_builder_service(
|
||||||
|
self,
|
||||||
|
ctx: &BuilderContext<Node>,
|
||||||
|
_pool: Pool,
|
||||||
|
_evm_config: Evm,
|
||||||
|
) -> eyre::Result<PayloadBuilderHandle<HlPayloadTypes>> {
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
ctx.task_executor().spawn_critical("payload builder", async move {
|
||||||
|
let mut subscriptions = Vec::new();
|
||||||
|
|
||||||
|
while let Some(message) = rx.recv().await {
|
||||||
|
match message {
|
||||||
|
PayloadServiceCommand::Subscribe(tx) => {
|
||||||
|
let (events_tx, events_rx) = broadcast::channel(100);
|
||||||
|
// Retain senders to make sure that channels are not getting closed
|
||||||
|
subscriptions.push(events_tx);
|
||||||
|
let _ = tx.send(events_rx);
|
||||||
|
}
|
||||||
|
message => warn!(?message, "Noop payload service received a message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(PayloadBuilderHandle::new(tx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl From<EthBuiltPayload> for HlBuiltPayload {
|
||||||
|
// fn from(value: EthBuiltPayload) -> Self {
|
||||||
|
// let EthBuiltPayload { id, block, fees, sidecars, requests } = value;
|
||||||
|
// HlBuiltPayload {
|
||||||
|
// id,
|
||||||
|
// block: block.into(),
|
||||||
|
// fees,
|
||||||
|
// requests,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub struct HlPayloadBuilder<Inner> {
|
||||||
|
// inner: Inner,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<Inner> PayloadBuilder for HlPayloadBuilder<Inner>
|
||||||
|
// where
|
||||||
|
// Inner: PayloadBuilder<BuiltPayload = EthBuiltPayload>,
|
||||||
|
// {
|
||||||
|
// type Attributes = Inner::Attributes;
|
||||||
|
// type BuiltPayload = HlBuiltPayload;
|
||||||
|
// type Error = Inner::Error;
|
||||||
|
|
||||||
|
// fn try_build(
|
||||||
|
// &self,
|
||||||
|
// args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
|
||||||
|
// ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError> {
|
||||||
|
// let outcome = self.inner.try_build(args)?;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
32
src/node/evm/assembler.rs
Normal file
32
src/node/evm/assembler.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use crate::{
|
||||||
|
node::evm::config::{HlBlockExecutorFactory, HlEvmConfig},
|
||||||
|
HlBlock, HlBlockBody,
|
||||||
|
};
|
||||||
|
use alloy_consensus::{Block, Header};
|
||||||
|
use reth_evm::{
|
||||||
|
block::BlockExecutionError,
|
||||||
|
execute::{BlockAssembler, BlockAssemblerInput},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl BlockAssembler<HlBlockExecutorFactory> for HlEvmConfig {
|
||||||
|
type Block = HlBlock;
|
||||||
|
|
||||||
|
fn assemble_block(
|
||||||
|
&self,
|
||||||
|
input: BlockAssemblerInput<'_, '_, HlBlockExecutorFactory, Header>,
|
||||||
|
) -> Result<Self::Block, BlockExecutionError> {
|
||||||
|
let Block { header, body: inner } = self.block_assembler.assemble_block(input)?;
|
||||||
|
Ok(HlBlock {
|
||||||
|
header,
|
||||||
|
body: HlBlockBody {
|
||||||
|
inner,
|
||||||
|
// HACK: we're setting sidecars to `None` here but ideally we should somehow get
|
||||||
|
// them from the payload builder.
|
||||||
|
//
|
||||||
|
// Payload building is out of scope of reth-bsc for now, so this is not critical
|
||||||
|
sidecars: None,
|
||||||
|
read_precompile_calls: None,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
316
src/node/evm/config.rs
Normal file
316
src/node/evm/config.rs
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
use super::{executor::HlBlockExecutor, factory::HlEvmFactory};
|
||||||
|
use crate::{
|
||||||
|
chainspec::HlChainSpec,
|
||||||
|
evm::{spec::HlSpecId, transaction::HlTxEnv},
|
||||||
|
hardforks::HlHardforks,
|
||||||
|
HlPrimitives,
|
||||||
|
};
|
||||||
|
use alloy_consensus::{BlockHeader, Header, TxReceipt};
|
||||||
|
use alloy_primitives::{Log, U256};
|
||||||
|
use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
|
||||||
|
use reth_ethereum_forks::EthereumHardfork;
|
||||||
|
use reth_evm::{
|
||||||
|
block::{BlockExecutorFactory, BlockExecutorFor},
|
||||||
|
eth::{receipt_builder::ReceiptBuilder, EthBlockExecutionCtx},
|
||||||
|
ConfigureEvm, EvmEnv, EvmFactory, ExecutionCtxFor, FromRecoveredTx, FromTxWithEncoded,
|
||||||
|
IntoTxEnv, NextBlockEnvAttributes,
|
||||||
|
};
|
||||||
|
use reth_evm_ethereum::{EthBlockAssembler, RethReceiptBuilder};
|
||||||
|
use reth_primitives::{BlockTy, HeaderTy, SealedBlock, SealedHeader, TransactionSigned};
|
||||||
|
use reth_revm::State;
|
||||||
|
use revm::{
|
||||||
|
context::{BlockEnv, CfgEnv, TxEnv},
|
||||||
|
context_interface::block::BlobExcessGasAndPrice,
|
||||||
|
primitives::hardfork::SpecId,
|
||||||
|
Inspector,
|
||||||
|
};
|
||||||
|
use std::{borrow::Cow, convert::Infallible, sync::Arc};
|
||||||
|
|
||||||
|
/// Ethereum-related EVM configuration.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HlEvmConfig {
|
||||||
|
/// Inner [`HlBlockExecutorFactory`].
|
||||||
|
pub executor_factory:
|
||||||
|
HlBlockExecutorFactory<RethReceiptBuilder, Arc<HlChainSpec>, HlEvmFactory>,
|
||||||
|
/// Ethereum block assembler.
|
||||||
|
pub block_assembler: EthBlockAssembler<HlChainSpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlEvmConfig {
|
||||||
|
/// Creates a new Ethereum EVM configuration with the given chain spec.
|
||||||
|
pub fn new(chain_spec: Arc<HlChainSpec>) -> Self {
|
||||||
|
Self::hl(chain_spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Ethereum EVM configuration.
|
||||||
|
pub fn hl(chain_spec: Arc<HlChainSpec>) -> Self {
|
||||||
|
Self::new_with_evm_factory(chain_spec, HlEvmFactory::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlEvmConfig {
|
||||||
|
/// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory.
|
||||||
|
pub fn new_with_evm_factory(chain_spec: Arc<HlChainSpec>, evm_factory: HlEvmFactory) -> Self {
|
||||||
|
Self {
|
||||||
|
block_assembler: EthBlockAssembler::new(chain_spec.clone()),
|
||||||
|
executor_factory: HlBlockExecutorFactory::new(
|
||||||
|
RethReceiptBuilder::default(),
|
||||||
|
chain_spec,
|
||||||
|
evm_factory,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the chain spec associated with this configuration.
|
||||||
|
pub const fn chain_spec(&self) -> &Arc<HlChainSpec> {
|
||||||
|
self.executor_factory.spec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ethereum block executor factory.
|
||||||
|
#[derive(Debug, Clone, Default, Copy)]
|
||||||
|
pub struct HlBlockExecutorFactory<
|
||||||
|
R = RethReceiptBuilder,
|
||||||
|
Spec = Arc<HlChainSpec>,
|
||||||
|
EvmFactory = HlEvmFactory,
|
||||||
|
> {
|
||||||
|
/// Receipt builder.
|
||||||
|
receipt_builder: R,
|
||||||
|
/// Chain specification.
|
||||||
|
spec: Spec,
|
||||||
|
/// EVM factory.
|
||||||
|
evm_factory: EvmFactory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, Spec, EvmFactory> HlBlockExecutorFactory<R, Spec, EvmFactory> {
|
||||||
|
/// Creates a new [`HlBlockExecutorFactory`] with the given spec, [`EvmFactory`], and
|
||||||
|
/// [`ReceiptBuilder`].
|
||||||
|
pub const fn new(receipt_builder: R, spec: Spec, evm_factory: EvmFactory) -> Self {
|
||||||
|
Self {
|
||||||
|
receipt_builder,
|
||||||
|
spec,
|
||||||
|
evm_factory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposes the receipt builder.
|
||||||
|
pub const fn receipt_builder(&self) -> &R {
|
||||||
|
&self.receipt_builder
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exposes the chain specification.
|
||||||
|
pub const fn spec(&self) -> &Spec {
|
||||||
|
&self.spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, Spec, EvmF> BlockExecutorFactory for HlBlockExecutorFactory<R, Spec, EvmF>
|
||||||
|
where
|
||||||
|
R: ReceiptBuilder<Transaction = TransactionSigned, Receipt: TxReceipt<Log = Log>>,
|
||||||
|
Spec: EthereumHardforks + HlHardforks + EthChainSpec + Hardforks + Clone,
|
||||||
|
EvmF: EvmFactory<Tx: FromRecoveredTx<TransactionSigned> + FromTxWithEncoded<TransactionSigned>>,
|
||||||
|
R::Transaction: From<TransactionSigned> + Clone,
|
||||||
|
Self: 'static,
|
||||||
|
HlTxEnv<TxEnv>: IntoTxEnv<<EvmF as EvmFactory>::Tx>,
|
||||||
|
{
|
||||||
|
type EvmFactory = EvmF;
|
||||||
|
type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>;
|
||||||
|
type Transaction = TransactionSigned;
|
||||||
|
type Receipt = R::Receipt;
|
||||||
|
|
||||||
|
fn evm_factory(&self) -> &Self::EvmFactory {
|
||||||
|
&self.evm_factory
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_executor<'a, DB, I>(
|
||||||
|
&'a self,
|
||||||
|
evm: <Self::EvmFactory as EvmFactory>::Evm<&'a mut State<DB>, I>,
|
||||||
|
ctx: Self::ExecutionCtx<'a>,
|
||||||
|
) -> impl BlockExecutorFor<'a, Self, DB, I>
|
||||||
|
where
|
||||||
|
DB: alloy_evm::Database + 'a,
|
||||||
|
I: Inspector<<Self::EvmFactory as EvmFactory>::Context<&'a mut State<DB>>> + 'a,
|
||||||
|
{
|
||||||
|
HlBlockExecutor::new(evm, ctx, self.spec().clone(), self.receipt_builder())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EIP1559_INITIAL_BASE_FEE: u64 = 0;
|
||||||
|
|
||||||
|
impl ConfigureEvm for HlEvmConfig
|
||||||
|
where
|
||||||
|
Self: Send + Sync + Unpin + Clone + 'static,
|
||||||
|
{
|
||||||
|
type Primitives = HlPrimitives;
|
||||||
|
type Error = Infallible;
|
||||||
|
type NextBlockEnvCtx = NextBlockEnvAttributes;
|
||||||
|
type BlockExecutorFactory = HlBlockExecutorFactory;
|
||||||
|
type BlockAssembler = Self;
|
||||||
|
|
||||||
|
fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
|
||||||
|
&self.executor_factory
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_assembler(&self) -> &Self::BlockAssembler {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evm_env(&self, header: &Header) -> EvmEnv<HlSpecId> {
|
||||||
|
let blob_params = self.chain_spec().blob_params_at_timestamp(header.timestamp);
|
||||||
|
let spec = revm_spec_by_timestamp_and_block_number(
|
||||||
|
self.chain_spec().clone(),
|
||||||
|
header.timestamp(),
|
||||||
|
header.number(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// configure evm env based on parent block
|
||||||
|
let mut cfg_env = CfgEnv::new()
|
||||||
|
.with_chain_id(self.chain_spec().chain().id())
|
||||||
|
.with_spec(spec);
|
||||||
|
|
||||||
|
if let Some(blob_params) = &blob_params {
|
||||||
|
cfg_env.set_blob_max_count(blob_params.max_blob_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current
|
||||||
|
// blobparams
|
||||||
|
let blob_excess_gas_and_price =
|
||||||
|
header
|
||||||
|
.excess_blob_gas
|
||||||
|
.zip(blob_params)
|
||||||
|
.map(|(excess_blob_gas, params)| {
|
||||||
|
let blob_gasprice = params.calc_blob_fee(excess_blob_gas);
|
||||||
|
BlobExcessGasAndPrice {
|
||||||
|
excess_blob_gas,
|
||||||
|
blob_gasprice,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let eth_spec = spec.into_eth_spec();
|
||||||
|
|
||||||
|
let block_env = BlockEnv {
|
||||||
|
number: header.number(),
|
||||||
|
beneficiary: header.beneficiary(),
|
||||||
|
timestamp: header.timestamp(),
|
||||||
|
difficulty: if eth_spec >= SpecId::MERGE {
|
||||||
|
U256::ZERO
|
||||||
|
} else {
|
||||||
|
header.difficulty()
|
||||||
|
},
|
||||||
|
prevrandao: if eth_spec >= SpecId::MERGE {
|
||||||
|
header.mix_hash()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
gas_limit: header.gas_limit(),
|
||||||
|
basefee: header.base_fee_per_gas().unwrap_or_default(),
|
||||||
|
blob_excess_gas_and_price,
|
||||||
|
};
|
||||||
|
|
||||||
|
EvmEnv { cfg_env, block_env }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_evm_env(
|
||||||
|
&self,
|
||||||
|
parent: &Header,
|
||||||
|
attributes: &Self::NextBlockEnvCtx,
|
||||||
|
) -> Result<EvmEnv<HlSpecId>, Self::Error> {
|
||||||
|
// ensure we're not missing any timestamp based hardforks
|
||||||
|
let spec_id = revm_spec_by_timestamp_and_block_number(
|
||||||
|
self.chain_spec().clone(),
|
||||||
|
attributes.timestamp,
|
||||||
|
parent.number() + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
// configure evm env based on parent block
|
||||||
|
let cfg_env = CfgEnv::new()
|
||||||
|
.with_chain_id(self.chain_spec().chain().id())
|
||||||
|
.with_spec(spec_id);
|
||||||
|
|
||||||
|
// if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is
|
||||||
|
// cancun now, we need to set the excess blob gas to the default value(0)
|
||||||
|
let blob_excess_gas_and_price = parent
|
||||||
|
.maybe_next_block_excess_blob_gas(
|
||||||
|
self.chain_spec()
|
||||||
|
.blob_params_at_timestamp(attributes.timestamp),
|
||||||
|
)
|
||||||
|
.or_else(|| (spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN)).then_some(0))
|
||||||
|
.map(|gas| BlobExcessGasAndPrice::new(gas, false));
|
||||||
|
|
||||||
|
let mut basefee = parent.next_block_base_fee(
|
||||||
|
self.chain_spec()
|
||||||
|
.base_fee_params_at_timestamp(attributes.timestamp),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut gas_limit = U256::from(parent.gas_limit);
|
||||||
|
|
||||||
|
// If we are on the London fork boundary, we need to multiply the parent's gas limit by the
|
||||||
|
// elasticity multiplier to get the new gas limit.
|
||||||
|
if self
|
||||||
|
.chain_spec()
|
||||||
|
.inner
|
||||||
|
.fork(EthereumHardfork::London)
|
||||||
|
.transitions_at_block(parent.number + 1)
|
||||||
|
{
|
||||||
|
let elasticity_multiplier = self
|
||||||
|
.chain_spec()
|
||||||
|
.base_fee_params_at_timestamp(attributes.timestamp)
|
||||||
|
.elasticity_multiplier;
|
||||||
|
|
||||||
|
// multiply the gas limit by the elasticity multiplier
|
||||||
|
gas_limit *= U256::from(elasticity_multiplier);
|
||||||
|
|
||||||
|
// set the base fee to the initial base fee from the EIP-1559 spec
|
||||||
|
basefee = Some(EIP1559_INITIAL_BASE_FEE)
|
||||||
|
}
|
||||||
|
|
||||||
|
let block_env = BlockEnv {
|
||||||
|
number: parent.number() + 1,
|
||||||
|
beneficiary: attributes.suggested_fee_recipient,
|
||||||
|
timestamp: attributes.timestamp,
|
||||||
|
difficulty: U256::ZERO,
|
||||||
|
prevrandao: Some(attributes.prev_randao),
|
||||||
|
gas_limit: attributes.gas_limit,
|
||||||
|
// calculate basefee based on parent block's gas usage
|
||||||
|
basefee: basefee.unwrap_or_default(),
|
||||||
|
// calculate excess gas based on parent block's blob gas usage
|
||||||
|
blob_excess_gas_and_price,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EvmEnv { cfg_env, block_env })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn context_for_block<'a>(
|
||||||
|
&self,
|
||||||
|
block: &'a SealedBlock<BlockTy<Self::Primitives>>,
|
||||||
|
) -> ExecutionCtxFor<'a, Self> {
|
||||||
|
EthBlockExecutionCtx {
|
||||||
|
parent_hash: block.header().parent_hash,
|
||||||
|
parent_beacon_block_root: block.header().parent_beacon_block_root,
|
||||||
|
ommers: &block.body().ommers,
|
||||||
|
withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn context_for_next_block(
|
||||||
|
&self,
|
||||||
|
parent: &SealedHeader<HeaderTy<Self::Primitives>>,
|
||||||
|
attributes: Self::NextBlockEnvCtx,
|
||||||
|
) -> ExecutionCtxFor<'_, Self> {
|
||||||
|
EthBlockExecutionCtx {
|
||||||
|
parent_hash: parent.hash(),
|
||||||
|
parent_beacon_block_root: attributes.parent_beacon_block_root,
|
||||||
|
ommers: &[],
|
||||||
|
withdrawals: attributes.withdrawals.map(Cow::Owned),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map the latest active hardfork at the given timestamp or block number to a [`HlSpecId`].
|
||||||
|
pub fn revm_spec_by_timestamp_and_block_number(
|
||||||
|
chain_spec: impl HlHardforks,
|
||||||
|
timestamp: u64,
|
||||||
|
block_number: u64,
|
||||||
|
) -> HlSpecId {
|
||||||
|
HlSpecId::V1
|
||||||
|
}
|
||||||
211
src/node/evm/executor.rs
Normal file
211
src/node/evm/executor.rs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
use super::patch::patch_mainnet_after_tx;
|
||||||
|
use crate::{evm::transaction::HlTxEnv, hardforks::HlHardforks};
|
||||||
|
use alloy_consensus::{Transaction, TxReceipt};
|
||||||
|
use alloy_eips::{eip7685::Requests, Encodable2718};
|
||||||
|
use alloy_evm::{block::ExecutableTx, eth::receipt_builder::ReceiptBuilderCtx};
|
||||||
|
use alloy_primitives::Address;
|
||||||
|
use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
|
||||||
|
use reth_evm::{
|
||||||
|
block::{BlockValidationError, CommitChanges},
|
||||||
|
eth::{receipt_builder::ReceiptBuilder, EthBlockExecutionCtx},
|
||||||
|
execute::{BlockExecutionError, BlockExecutor},
|
||||||
|
Database, Evm, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, OnStateHook, RecoveredTx,
|
||||||
|
};
|
||||||
|
use reth_primitives::TransactionSigned;
|
||||||
|
use reth_provider::BlockExecutionResult;
|
||||||
|
use reth_revm::State;
|
||||||
|
use revm::{
|
||||||
|
context::{
|
||||||
|
result::{ExecutionResult, ResultAndState},
|
||||||
|
TxEnv,
|
||||||
|
},
|
||||||
|
state::Bytecode,
|
||||||
|
Database as _, DatabaseCommit,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn is_system_transaction(tx: &TransactionSigned) -> bool {
|
||||||
|
let Some(gas_price) = tx.gas_price() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
gas_price == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HlBlockExecutor<'a, EVM, Spec, R: ReceiptBuilder>
|
||||||
|
where
|
||||||
|
Spec: EthChainSpec,
|
||||||
|
{
|
||||||
|
/// Reference to the specification object.
|
||||||
|
spec: Spec,
|
||||||
|
/// Inner EVM.
|
||||||
|
evm: EVM,
|
||||||
|
/// Gas used in the block.
|
||||||
|
gas_used: u64,
|
||||||
|
/// Receipts of executed transactions.
|
||||||
|
receipts: Vec<R::Receipt>,
|
||||||
|
/// System txs
|
||||||
|
system_txs: Vec<R::Transaction>,
|
||||||
|
/// Receipt builder.
|
||||||
|
receipt_builder: R,
|
||||||
|
/// System contracts used to trigger fork specific logic.
|
||||||
|
// system_contracts: SystemContract<Spec>,
|
||||||
|
/// Context for block execution.
|
||||||
|
_ctx: EthBlockExecutionCtx<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, DB, EVM, Spec, R: ReceiptBuilder> HlBlockExecutor<'a, EVM, Spec, R>
|
||||||
|
where
|
||||||
|
DB: Database + 'a,
|
||||||
|
EVM: Evm<
|
||||||
|
DB = &'a mut State<DB>,
|
||||||
|
Tx: FromRecoveredTx<R::Transaction>
|
||||||
|
+ FromRecoveredTx<TransactionSigned>
|
||||||
|
+ FromTxWithEncoded<TransactionSigned>,
|
||||||
|
>,
|
||||||
|
Spec: EthereumHardforks + HlHardforks + EthChainSpec + Hardforks + Clone,
|
||||||
|
R: ReceiptBuilder<Transaction = TransactionSigned, Receipt: TxReceipt>,
|
||||||
|
<R as ReceiptBuilder>::Transaction: Unpin + From<TransactionSigned>,
|
||||||
|
<EVM as alloy_evm::Evm>::Tx: FromTxWithEncoded<<R as ReceiptBuilder>::Transaction>,
|
||||||
|
HlTxEnv<TxEnv>: IntoTxEnv<<EVM as alloy_evm::Evm>::Tx>,
|
||||||
|
R::Transaction: Into<TransactionSigned>,
|
||||||
|
{
|
||||||
|
/// Creates a new HlBlockExecutor.
|
||||||
|
pub fn new(
|
||||||
|
evm: EVM,
|
||||||
|
_ctx: EthBlockExecutionCtx<'a>,
|
||||||
|
spec: Spec,
|
||||||
|
receipt_builder: R,
|
||||||
|
// system_contracts: SystemContract<Spec>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
spec,
|
||||||
|
evm,
|
||||||
|
gas_used: 0,
|
||||||
|
receipts: vec![],
|
||||||
|
system_txs: vec![],
|
||||||
|
receipt_builder,
|
||||||
|
// system_contracts,
|
||||||
|
_ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the genesis contracts
|
||||||
|
fn deploy_genesis_contracts(
|
||||||
|
&mut self,
|
||||||
|
beneficiary: Address,
|
||||||
|
) -> Result<(), BlockExecutionError> {
|
||||||
|
todo!("Deploy WETH, System contract");
|
||||||
|
// Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, DB, E, Spec, R> BlockExecutor for HlBlockExecutor<'a, E, Spec, R>
|
||||||
|
where
|
||||||
|
DB: Database + 'a,
|
||||||
|
E: Evm<
|
||||||
|
DB = &'a mut State<DB>,
|
||||||
|
Tx: FromRecoveredTx<R::Transaction>
|
||||||
|
+ FromRecoveredTx<TransactionSigned>
|
||||||
|
+ FromTxWithEncoded<TransactionSigned>,
|
||||||
|
>,
|
||||||
|
Spec: EthereumHardforks + HlHardforks + EthChainSpec + Hardforks,
|
||||||
|
R: ReceiptBuilder<Transaction = TransactionSigned, Receipt: TxReceipt>,
|
||||||
|
<R as ReceiptBuilder>::Transaction: Unpin + From<TransactionSigned>,
|
||||||
|
<E as alloy_evm::Evm>::Tx: FromTxWithEncoded<<R as ReceiptBuilder>::Transaction>,
|
||||||
|
HlTxEnv<TxEnv>: IntoTxEnv<<E as alloy_evm::Evm>::Tx>,
|
||||||
|
R::Transaction: Into<TransactionSigned>,
|
||||||
|
{
|
||||||
|
type Transaction = TransactionSigned;
|
||||||
|
type Receipt = R::Receipt;
|
||||||
|
type Evm = E;
|
||||||
|
|
||||||
|
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
|
||||||
|
// If first block deploy genesis contracts
|
||||||
|
if self.evm.block().number == 1 {
|
||||||
|
self.deploy_genesis_contracts(self.evm.block().beneficiary)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_transaction_with_commit_condition(
|
||||||
|
&mut self,
|
||||||
|
_tx: impl ExecutableTx<Self>,
|
||||||
|
_f: impl FnOnce(&ExecutionResult<<Self::Evm as Evm>::HaltReason>) -> CommitChanges,
|
||||||
|
) -> Result<Option<u64>, BlockExecutionError> {
|
||||||
|
Ok(Some(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_transaction_with_result_closure(
|
||||||
|
&mut self,
|
||||||
|
tx: impl ExecutableTx<Self>
|
||||||
|
+ IntoTxEnv<<E as alloy_evm::Evm>::Tx>
|
||||||
|
+ RecoveredTx<TransactionSigned>,
|
||||||
|
f: impl for<'b> FnOnce(&'b ExecutionResult<<E as alloy_evm::Evm>::HaltReason>),
|
||||||
|
) -> Result<u64, BlockExecutionError> {
|
||||||
|
// Check if it's a system transaction
|
||||||
|
// let signer = tx.signer();
|
||||||
|
// let is_system_transaction = is_system_transaction(tx.tx());
|
||||||
|
|
||||||
|
let block_available_gas = self.evm.block().gas_limit - self.gas_used;
|
||||||
|
if tx.tx().gas_limit() > block_available_gas {
|
||||||
|
return Err(
|
||||||
|
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
|
||||||
|
transaction_gas_limit: tx.tx().gas_limit(),
|
||||||
|
block_available_gas,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result_and_state = self
|
||||||
|
.evm
|
||||||
|
.transact(tx)
|
||||||
|
.map_err(|err| BlockExecutionError::evm(err, tx.tx().trie_hash()))?;
|
||||||
|
let ResultAndState { result, mut state } = result_and_state;
|
||||||
|
f(&result);
|
||||||
|
let gas_used = result.gas_used();
|
||||||
|
self.gas_used += gas_used;
|
||||||
|
self.receipts
|
||||||
|
.push(self.receipt_builder.build_receipt(ReceiptBuilderCtx {
|
||||||
|
tx: tx.tx(),
|
||||||
|
evm: &self.evm,
|
||||||
|
result,
|
||||||
|
state: &state,
|
||||||
|
cumulative_gas_used: self.gas_used,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// apply patches after
|
||||||
|
patch_mainnet_after_tx(
|
||||||
|
self.evm.block().number,
|
||||||
|
self.receipts.len() as u64,
|
||||||
|
is_system_transaction(tx.tx()),
|
||||||
|
&mut state,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.evm.db_mut().commit(state);
|
||||||
|
|
||||||
|
Ok(gas_used)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(
|
||||||
|
mut self,
|
||||||
|
) -> Result<(Self::Evm, BlockExecutionResult<R::Receipt>), BlockExecutionError> {
|
||||||
|
Ok((
|
||||||
|
self.evm,
|
||||||
|
BlockExecutionResult {
|
||||||
|
receipts: self.receipts,
|
||||||
|
requests: Requests::default(),
|
||||||
|
gas_used: self.gas_used,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_state_hook(&mut self, _hook: Option<Box<dyn OnStateHook>>) {}
|
||||||
|
|
||||||
|
fn evm_mut(&mut self) -> &mut Self::Evm {
|
||||||
|
&mut self.evm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evm(&self) -> &Self::Evm {
|
||||||
|
&self.evm
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/node/evm/factory.rs
Normal file
74
src/node/evm/factory.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use super::HlEvm;
|
||||||
|
use crate::evm::{
|
||||||
|
api::{
|
||||||
|
builder::HlBuilder,
|
||||||
|
ctx::{HlContext, DefaultHl},
|
||||||
|
},
|
||||||
|
precompiles::HlPrecompiles,
|
||||||
|
spec::HlSpecId,
|
||||||
|
transaction::HlTxEnv,
|
||||||
|
};
|
||||||
|
use reth_evm::{precompiles::PrecompilesMap, EvmEnv, EvmFactory};
|
||||||
|
use reth_revm::{Context, Database};
|
||||||
|
use revm::{
|
||||||
|
context::{
|
||||||
|
result::{EVMError, HaltReason},
|
||||||
|
TxEnv,
|
||||||
|
},
|
||||||
|
inspector::NoOpInspector,
|
||||||
|
Inspector,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Factory producing [`HlEvm`].
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct HlEvmFactory;
|
||||||
|
|
||||||
|
impl EvmFactory for HlEvmFactory {
|
||||||
|
type Evm<DB: Database<Error: Send + Sync + 'static>, I: Inspector<HlContext<DB>>> =
|
||||||
|
HlEvm<DB, I, Self::Precompiles>;
|
||||||
|
type Context<DB: Database<Error: Send + Sync + 'static>> = HlContext<DB>;
|
||||||
|
type Tx = HlTxEnv<TxEnv>;
|
||||||
|
type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
|
||||||
|
type HaltReason = HaltReason;
|
||||||
|
type Spec = HlSpecId;
|
||||||
|
type Precompiles = PrecompilesMap;
|
||||||
|
|
||||||
|
fn create_evm<DB: Database<Error: Send + Sync + 'static>>(
|
||||||
|
&self,
|
||||||
|
db: DB,
|
||||||
|
input: EvmEnv<HlSpecId>,
|
||||||
|
) -> Self::Evm<DB, NoOpInspector> {
|
||||||
|
let precompiles = HlPrecompiles::new(input.cfg_env.spec).precompiles();
|
||||||
|
HlEvm {
|
||||||
|
inner: Context::hl()
|
||||||
|
.with_block(input.block_env)
|
||||||
|
.with_cfg(input.cfg_env)
|
||||||
|
.with_db(db)
|
||||||
|
.build_hl_with_inspector(NoOpInspector {})
|
||||||
|
.with_precompiles(PrecompilesMap::from_static(precompiles)),
|
||||||
|
inspect: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_evm_with_inspector<
|
||||||
|
DB: Database<Error: Send + Sync + 'static>,
|
||||||
|
I: Inspector<Self::Context<DB>>,
|
||||||
|
>(
|
||||||
|
&self,
|
||||||
|
db: DB,
|
||||||
|
input: EvmEnv<HlSpecId>,
|
||||||
|
inspector: I,
|
||||||
|
) -> Self::Evm<DB, I> {
|
||||||
|
let precompiles = HlPrecompiles::new(input.cfg_env.spec).precompiles();
|
||||||
|
HlEvm {
|
||||||
|
inner: Context::hl()
|
||||||
|
.with_block(input.block_env)
|
||||||
|
.with_cfg(input.cfg_env)
|
||||||
|
.with_db(db)
|
||||||
|
.build_hl_with_inspector(inspector)
|
||||||
|
.with_precompiles(PrecompilesMap::from_static(precompiles)),
|
||||||
|
inspect: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
165
src/node/evm/mod.rs
Normal file
165
src/node/evm/mod.rs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
use crate::{
|
||||||
|
evm::{
|
||||||
|
api::{ctx::HlContext, HlEvmInner},
|
||||||
|
precompiles::HlPrecompiles,
|
||||||
|
spec::HlSpecId,
|
||||||
|
transaction::{HlTxEnv, HlTxTr},
|
||||||
|
},
|
||||||
|
node::HlNode,
|
||||||
|
};
|
||||||
|
use alloy_primitives::{Address, Bytes};
|
||||||
|
use config::HlEvmConfig;
|
||||||
|
use reth::{
|
||||||
|
api::FullNodeTypes,
|
||||||
|
builder::{components::ExecutorBuilder, BuilderContext},
|
||||||
|
};
|
||||||
|
use reth_evm::{Evm, EvmEnv};
|
||||||
|
use revm::{
|
||||||
|
context::{
|
||||||
|
result::{EVMError, HaltReason, ResultAndState},
|
||||||
|
BlockEnv, TxEnv,
|
||||||
|
},
|
||||||
|
handler::{instructions::EthInstructions, PrecompileProvider},
|
||||||
|
interpreter::{interpreter::EthInterpreter, InterpreterResult},
|
||||||
|
Context, Database, ExecuteEvm, InspectEvm, Inspector,
|
||||||
|
};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
mod assembler;
|
||||||
|
pub mod config;
|
||||||
|
mod executor;
|
||||||
|
mod factory;
|
||||||
|
mod patch;
|
||||||
|
|
||||||
|
/// HL EVM implementation.
|
||||||
|
///
|
||||||
|
/// This is a wrapper type around the `revm` evm with optional [`Inspector`] (tracing)
|
||||||
|
/// support. [`Inspector`] support is configurable at runtime because it's part of the underlying
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct HlEvm<DB: Database, I, P = HlPrecompiles> {
|
||||||
|
pub inner: HlEvmInner<HlContext<DB>, I, EthInstructions<EthInterpreter, HlContext<DB>>, P>,
|
||||||
|
pub inspect: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB: Database, I, P> HlEvm<DB, I, P> {
|
||||||
|
/// Provides a reference to the EVM context.
|
||||||
|
pub const fn ctx(&self) -> &HlContext<DB> {
|
||||||
|
&self.inner.0.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides a mutable reference to the EVM context.
|
||||||
|
pub fn ctx_mut(&mut self) -> &mut HlContext<DB> {
|
||||||
|
&mut self.inner.0.ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB: Database, I, P> Deref for HlEvm<DB, I, P> {
|
||||||
|
type Target = HlContext<DB>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.ctx()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB: Database, I, P> DerefMut for HlEvm<DB, I, P> {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.ctx_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB, I, P> Evm for HlEvm<DB, I, P>
|
||||||
|
where
|
||||||
|
DB: Database,
|
||||||
|
I: Inspector<HlContext<DB>>,
|
||||||
|
P: PrecompileProvider<HlContext<DB>, Output = InterpreterResult>,
|
||||||
|
<DB as revm::Database>::Error: std::marker::Send + std::marker::Sync + 'static,
|
||||||
|
{
|
||||||
|
type DB = DB;
|
||||||
|
type Tx = HlTxEnv<TxEnv>;
|
||||||
|
type Error = EVMError<DB::Error>;
|
||||||
|
type HaltReason = HaltReason;
|
||||||
|
type Spec = HlSpecId;
|
||||||
|
type Precompiles = P;
|
||||||
|
type Inspector = I;
|
||||||
|
|
||||||
|
fn chain_id(&self) -> u64 {
|
||||||
|
self.cfg.chain_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block(&self) -> &BlockEnv {
|
||||||
|
&self.block
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transact_raw(
|
||||||
|
&mut self,
|
||||||
|
tx: Self::Tx,
|
||||||
|
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
|
||||||
|
if self.inspect {
|
||||||
|
self.inner.set_tx(tx);
|
||||||
|
self.inner.inspect_replay()
|
||||||
|
} else if tx.is_system_transaction() {
|
||||||
|
self.inner.set_tx(tx);
|
||||||
|
self.inner.inspect_replay()
|
||||||
|
} else {
|
||||||
|
self.inner.transact(tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transact_system_call(
|
||||||
|
&mut self,
|
||||||
|
_caller: Address,
|
||||||
|
_contract: Address,
|
||||||
|
_data: Bytes,
|
||||||
|
) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn db_mut(&mut self) -> &mut Self::DB {
|
||||||
|
&mut self.journaled_state.database
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
|
||||||
|
let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx;
|
||||||
|
|
||||||
|
(journaled_state.database, EvmEnv { block_env, cfg_env })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_inspector_enabled(&mut self, enabled: bool) {
|
||||||
|
self.inspect = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn precompiles_mut(&mut self) -> &mut Self::Precompiles {
|
||||||
|
&mut self.inner.0.precompiles
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inspector_mut(&mut self) -> &mut Self::Inspector {
|
||||||
|
&mut self.inner.0.inspector
|
||||||
|
}
|
||||||
|
|
||||||
|
fn precompiles(&self) -> &Self::Precompiles {
|
||||||
|
&self.inner.0.precompiles
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inspector(&self) -> &Self::Inspector {
|
||||||
|
&self.inner.0.inspector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A regular hl evm and executor builder.
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct HlExecutorBuilder;
|
||||||
|
|
||||||
|
impl<Node> ExecutorBuilder<Node> for HlExecutorBuilder
|
||||||
|
where
|
||||||
|
Node: FullNodeTypes<Types = HlNode>,
|
||||||
|
{
|
||||||
|
type EVM = HlEvmConfig;
|
||||||
|
|
||||||
|
async fn build_evm(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
|
||||||
|
let evm_config = HlEvmConfig::hl(ctx.chain_spec());
|
||||||
|
Ok(evm_config)
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/node/evm/patch.rs
Normal file
145
src/node/evm/patch.rs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
use alloy_primitives::{address, b256, Address, B256, U256};
|
||||||
|
use reth_evm::block::BlockExecutionError;
|
||||||
|
use reth_primitives_traits::SignedTransaction;
|
||||||
|
use reth_revm::{db::states::StorageSlot, State};
|
||||||
|
use revm::{primitives::HashMap, state::Account, Database};
|
||||||
|
use std::{str::FromStr, sync::LazyLock};
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
/// Applies storage patches to the state after a transaction is executed.
|
||||||
|
/// See https://github.com/hyperliquid-dex/hyper-evm-sync/commit/39047242b6260f7764527a2f5057dd9c3a75aa89 for more details.
|
||||||
|
static MAINNET_PATCHES_AFTER_TX: &[(u64, u64, bool, Address)] = &[
|
||||||
|
(
|
||||||
|
1_467_569,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0x33f6fe38c55cb100ce27b3138e5d2d041648364f"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_467_631,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0x33f6fe38c55cb100ce27b3138e5d2d041648364f"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_499_313,
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
address!("0xe27bfc0a812b38927ff646f24af9149f45deb550"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_499_406,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0xe27bfc0a812b38927ff646f24af9149f45deb550"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_499_685,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0xfee3932b75a87e86930668a6ab3ed43b404c8a30"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_514_843,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0x723e5fbbeed025772a91240fd0956a866a41a603"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_514_936,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0x723e5fbbeed025772a91240fd0956a866a41a603"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_530_529,
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
address!("0xa694e8fd8f4a177dd23636d838e9f1fb2138d87a"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_530_622,
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
address!("0xa694e8fd8f4a177dd23636d838e9f1fb2138d87a"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_530_684,
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
address!("0xa694e8fd8f4a177dd23636d838e9f1fb2138d87a"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_530_777,
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
address!("0xa694e8fd8f4a177dd23636d838e9f1fb2138d87a"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_530_839,
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
address!("0x692a343fc401a7755f8fc2facf61af426adaf061"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_530_901,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0xfd9716f16596715ce765dabaee11787870e04b8a"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_530_994,
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
address!("0xfd9716f16596715ce765dabaee11787870e04b8a"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_531_056,
|
||||||
|
4,
|
||||||
|
false,
|
||||||
|
address!("0xdc67c2b8349ca20f58760e08371fc9271e82b5a4"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_531_149,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
address!("0xdc67c2b8349ca20f58760e08371fc9271e82b5a4"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_531_211,
|
||||||
|
3,
|
||||||
|
false,
|
||||||
|
address!("0xdc67c2b8349ca20f58760e08371fc9271e82b5a4"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1_531_366,
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
address!("0x9a90a517d27a9e60e454c96fefbbe94ff244ed6f"),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) fn patch_mainnet_after_tx(
|
||||||
|
block_number: u64,
|
||||||
|
tx_index: u64,
|
||||||
|
is_system_tx: bool,
|
||||||
|
changes: &mut HashMap<Address, Account>,
|
||||||
|
) -> Result<(), BlockExecutionError> {
|
||||||
|
if MAINNET_PATCHES_AFTER_TX.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let first = MAINNET_PATCHES_AFTER_TX.first().unwrap().0;
|
||||||
|
let last = MAINNET_PATCHES_AFTER_TX.last().unwrap().0;
|
||||||
|
if first > block_number || last < block_number {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (block_num, idx, is_system, address) in MAINNET_PATCHES_AFTER_TX {
|
||||||
|
if block_number == *block_num && tx_index == *idx && is_system_tx == *is_system {
|
||||||
|
changes.remove(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
46
src/node/network/block_import/handle.rs
Normal file
46
src/node/network/block_import/handle.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use reth_engine_primitives::EngineTypes;
|
||||||
|
use reth_network::import::BlockImportError;
|
||||||
|
use reth_network_api::PeerId;
|
||||||
|
use reth_payload_primitives::PayloadTypes;
|
||||||
|
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
|
|
||||||
|
use super::service::{BlockMsg, ImportEvent, IncomingBlock, Outcome};
|
||||||
|
|
||||||
|
/// A handle for interacting with the block import service.
|
||||||
|
///
|
||||||
|
/// This handle provides a bidirectional communication channel with the
|
||||||
|
/// [`super::service::ImportService`]:
|
||||||
|
/// - Blocks can be sent to the service for import via [`send_block`](ImportHandle::send_block)
|
||||||
|
/// - Import outcomes can be received via [`poll_outcome`](ImportHandle::poll_outcome)`
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ImportHandle {
|
||||||
|
/// Send the new block to the service
|
||||||
|
to_import: UnboundedSender<IncomingBlock>,
|
||||||
|
/// Receive the event(Announcement/Outcome) of the import
|
||||||
|
import_outcome: UnboundedReceiver<ImportEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImportHandle {
|
||||||
|
/// Create a new handle with the provided channels
|
||||||
|
pub fn new(
|
||||||
|
to_import: UnboundedSender<IncomingBlock>,
|
||||||
|
import_outcome: UnboundedReceiver<ImportEvent>,
|
||||||
|
) -> Self {
|
||||||
|
Self { to_import, import_outcome }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the block to import to the service.
|
||||||
|
/// Returns a [`BlockImportError`] if the channel to the import service is closed.
|
||||||
|
pub fn send_block(&self, block: BlockMsg, peer_id: PeerId) -> Result<(), BlockImportError> {
|
||||||
|
self.to_import
|
||||||
|
.send((block, peer_id))
|
||||||
|
.map_err(|_| BlockImportError::Other("block import service channel closed".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll for the next import event
|
||||||
|
pub fn poll_outcome(&mut self, cx: &mut Context<'_>) -> Poll<Option<ImportEvent>> {
|
||||||
|
self.import_outcome.poll_recv(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/node/network/block_import/mod.rs
Normal file
41
src/node/network/block_import/mod.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#![allow(unused)]
|
||||||
|
use handle::ImportHandle;
|
||||||
|
use reth_engine_primitives::EngineTypes;
|
||||||
|
use reth_network::import::{BlockImport, BlockImportOutcome, NewBlockEvent};
|
||||||
|
use reth_network_peers::PeerId;
|
||||||
|
use reth_payload_primitives::{BuiltPayload, PayloadTypes};
|
||||||
|
use reth_primitives::NodePrimitives;
|
||||||
|
use service::{BlockMsg, ImportEvent, Outcome};
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
task::{ready, Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::node::network::HlNewBlock;
|
||||||
|
|
||||||
|
pub mod handle;
|
||||||
|
pub mod service;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HlBlockImport {
|
||||||
|
handle: ImportHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlBlockImport {
|
||||||
|
pub fn new(handle: ImportHandle) -> Self {
|
||||||
|
Self { handle }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockImport<HlNewBlock> for HlBlockImport {
|
||||||
|
fn on_new_block(&mut self, peer_id: PeerId, incoming_block: NewBlockEvent<HlNewBlock>) {
|
||||||
|
unreachable!("reth-hl does not use network, but uses poller for files");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self, cx: &mut Context<'_>) -> Poll<ImportEvent> {
|
||||||
|
match ready!(self.handle.poll_outcome(cx)) {
|
||||||
|
Some(outcome) => Poll::Ready(outcome),
|
||||||
|
None => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
418
src/node/network/block_import/service.rs
Normal file
418
src/node/network/block_import/service.rs
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
use super::handle::ImportHandle;
|
||||||
|
use crate::{
|
||||||
|
consensus::{HlConsensus, HlConsensusErr},
|
||||||
|
node::{network::HlNewBlock, rpc::engine_api::payload::HlPayloadTypes},
|
||||||
|
HlBlock, HlBlockBody,
|
||||||
|
};
|
||||||
|
use alloy_consensus::{BlockBody, Header};
|
||||||
|
use alloy_primitives::U128;
|
||||||
|
use alloy_rpc_types::engine::{ForkchoiceState, PayloadStatusEnum};
|
||||||
|
use futures::{future::Either, stream::FuturesUnordered, StreamExt};
|
||||||
|
use reth_engine_primitives::{BeaconConsensusEngineHandle, EngineTypes};
|
||||||
|
use reth_network::{
|
||||||
|
import::{BlockImportError, BlockImportEvent, BlockImportOutcome, BlockValidation},
|
||||||
|
message::NewBlockMessage,
|
||||||
|
};
|
||||||
|
use reth_network_api::PeerId;
|
||||||
|
use reth_node_ethereum::EthEngineTypes;
|
||||||
|
use reth_payload_primitives::{BuiltPayload, EngineApiMessageVersion, PayloadTypes};
|
||||||
|
use reth_primitives::NodePrimitives;
|
||||||
|
use reth_primitives_traits::{AlloyBlockHeader, Block};
|
||||||
|
use reth_provider::{BlockHashReader, BlockNumReader};
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||||
|
|
||||||
|
/// Network message containing a new block
|
||||||
|
pub(crate) type BlockMsg = NewBlockMessage<HlNewBlock>;
|
||||||
|
|
||||||
|
/// Import outcome for a block
|
||||||
|
pub(crate) type Outcome = BlockImportOutcome<HlNewBlock>;
|
||||||
|
|
||||||
|
/// Import event for a block
|
||||||
|
pub(crate) type ImportEvent = BlockImportEvent<HlNewBlock>;
|
||||||
|
|
||||||
|
/// Future that processes a block import and returns its outcome
|
||||||
|
type ImportFut = Pin<Box<dyn Future<Output = Option<Outcome>> + Send + Sync>>;
|
||||||
|
|
||||||
|
/// Channel message type for incoming blocks
|
||||||
|
pub(crate) type IncomingBlock = (BlockMsg, PeerId);
|
||||||
|
|
||||||
|
/// A service that handles bidirectional block import communication with the network.
|
||||||
|
/// It receives new blocks from the network via `from_network` channel and sends back
|
||||||
|
/// import outcomes via `to_network` channel.
|
||||||
|
pub struct ImportService<Provider>
|
||||||
|
where
|
||||||
|
Provider: BlockNumReader + Clone,
|
||||||
|
{
|
||||||
|
/// The handle to communicate with the engine service
|
||||||
|
engine: BeaconConsensusEngineHandle<HlPayloadTypes>,
|
||||||
|
/// The consensus implementation
|
||||||
|
consensus: Arc<HlConsensus<Provider>>,
|
||||||
|
/// Receive the new block from the network
|
||||||
|
from_network: UnboundedReceiver<IncomingBlock>,
|
||||||
|
/// Send the event of the import to the network
|
||||||
|
to_network: UnboundedSender<ImportEvent>,
|
||||||
|
/// Pending block imports.
|
||||||
|
pending_imports: FuturesUnordered<ImportFut>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Provider> ImportService<Provider>
|
||||||
|
where
|
||||||
|
Provider: BlockNumReader + Clone + 'static,
|
||||||
|
{
|
||||||
|
/// Create a new block import service
|
||||||
|
pub fn new(
|
||||||
|
consensus: Arc<HlConsensus<Provider>>,
|
||||||
|
engine: BeaconConsensusEngineHandle<HlPayloadTypes>,
|
||||||
|
from_network: UnboundedReceiver<IncomingBlock>,
|
||||||
|
to_network: UnboundedSender<ImportEvent>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
engine,
|
||||||
|
consensus,
|
||||||
|
from_network,
|
||||||
|
to_network,
|
||||||
|
pending_imports: FuturesUnordered::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a new payload and return the outcome
|
||||||
|
fn new_payload(&self, block: BlockMsg, peer_id: PeerId) -> ImportFut {
|
||||||
|
let engine = self.engine.clone();
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
let sealed_block = block.block.0.block.clone().seal();
|
||||||
|
let payload = HlPayloadTypes::block_to_payload(sealed_block);
|
||||||
|
|
||||||
|
match engine.new_payload(payload).await {
|
||||||
|
Ok(payload_status) => match payload_status.status {
|
||||||
|
PayloadStatusEnum::Valid => {
|
||||||
|
Outcome { peer: peer_id, result: Ok(BlockValidation::ValidBlock { block }) }
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
PayloadStatusEnum::Invalid { validation_error } => Outcome {
|
||||||
|
peer: peer_id,
|
||||||
|
result: Err(BlockImportError::Other(validation_error.into())),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Err(err) => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a forkchoice update and return the outcome
|
||||||
|
fn update_fork_choice(&self, block: BlockMsg, peer_id: PeerId) -> ImportFut {
|
||||||
|
let engine = self.engine.clone();
|
||||||
|
let consensus = self.consensus.clone();
|
||||||
|
let sealed_block = block.block.0.block.clone().seal();
|
||||||
|
let hash = sealed_block.hash();
|
||||||
|
let number = sealed_block.number();
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
let (head_block_hash, current_hash) = match consensus.canonical_head(hash, number) {
|
||||||
|
Ok(hash) => hash,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = ForkchoiceState {
|
||||||
|
head_block_hash,
|
||||||
|
safe_block_hash: head_block_hash,
|
||||||
|
finalized_block_hash: head_block_hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
match engine.fork_choice_updated(state, None, EngineApiMessageVersion::default()).await
|
||||||
|
{
|
||||||
|
Ok(response) => match response.payload_status.status {
|
||||||
|
PayloadStatusEnum::Valid => {
|
||||||
|
Outcome { peer: peer_id, result: Ok(BlockValidation::ValidBlock { block }) }
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
PayloadStatusEnum::Invalid { validation_error } => Outcome {
|
||||||
|
peer: peer_id,
|
||||||
|
result: Err(BlockImportError::Other(validation_error.into())),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Err(err) => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new block import task to the pending imports
|
||||||
|
fn on_new_block(&mut self, block: BlockMsg, peer_id: PeerId) {
|
||||||
|
let payload_fut = self.new_payload(block.clone(), peer_id);
|
||||||
|
self.pending_imports.push(payload_fut);
|
||||||
|
|
||||||
|
let fcu_fut = self.update_fork_choice(block, peer_id);
|
||||||
|
self.pending_imports.push(fcu_fut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Provider> Future for ImportService<Provider>
|
||||||
|
where
|
||||||
|
Provider: BlockNumReader + BlockHashReader + Clone + 'static + Unpin,
|
||||||
|
{
|
||||||
|
type Output = Result<(), Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.get_mut();
|
||||||
|
|
||||||
|
// Receive new blocks from network
|
||||||
|
while let Poll::Ready(Some((block, peer_id))) = this.from_network.poll_recv(cx) {
|
||||||
|
this.on_new_block(block, peer_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process completed imports and send events to network
|
||||||
|
while let Poll::Ready(Some(outcome)) = this.pending_imports.poll_next_unpin(cx) {
|
||||||
|
if let Some(outcome) = outcome {
|
||||||
|
if let Err(e) = this.to_network.send(BlockImportEvent::Outcome(outcome)) {
|
||||||
|
return Poll::Ready(Err(Box::new(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::chainspec::bsc::bsc_mainnet;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use alloy_primitives::{B256, U128};
|
||||||
|
use alloy_rpc_types::engine::PayloadStatus;
|
||||||
|
use reth_chainspec::ChainInfo;
|
||||||
|
use reth_engine_primitives::{BeaconEngineMessage, OnForkChoiceUpdated};
|
||||||
|
use reth_eth_wire::NewBlock;
|
||||||
|
use reth_node_ethereum::EthEngineTypes;
|
||||||
|
use reth_primitives::Block;
|
||||||
|
use reth_provider::ProviderError;
|
||||||
|
use std::{
|
||||||
|
sync::Arc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_handle_valid_block() {
|
||||||
|
let mut fixture = TestFixture::new(EngineResponses::both_valid()).await;
|
||||||
|
fixture
|
||||||
|
.assert_block_import(|outcome| {
|
||||||
|
matches!(
|
||||||
|
outcome,
|
||||||
|
BlockImportEvent::Outcome(BlockImportOutcome {
|
||||||
|
peer: _,
|
||||||
|
result: Ok(BlockValidation::ValidBlock { .. })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_handle_invalid_new_payload() {
|
||||||
|
let mut fixture = TestFixture::new(EngineResponses::invalid_new_payload()).await;
|
||||||
|
fixture
|
||||||
|
.assert_block_import(|outcome| {
|
||||||
|
matches!(
|
||||||
|
outcome,
|
||||||
|
BlockImportEvent::Outcome(BlockImportOutcome {
|
||||||
|
peer: _,
|
||||||
|
result: Err(BlockImportError::Other(_))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_handle_invalid_fcu() {
|
||||||
|
let mut fixture = TestFixture::new(EngineResponses::invalid_fcu()).await;
|
||||||
|
fixture
|
||||||
|
.assert_block_import(|outcome| {
|
||||||
|
matches!(
|
||||||
|
outcome,
|
||||||
|
BlockImportEvent::Outcome(BlockImportOutcome {
|
||||||
|
peer: _,
|
||||||
|
result: Err(BlockImportError::Other(_))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MockProvider;
|
||||||
|
|
||||||
|
impl BlockNumReader for MockProvider {
|
||||||
|
fn chain_info(&self) -> Result<ChainInfo, ProviderError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn best_block_number(&self) -> Result<u64, ProviderError> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_block_number(&self) -> Result<u64, ProviderError> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_number(&self, _hash: B256) -> Result<Option<u64>, ProviderError> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockHashReader for MockProvider {
|
||||||
|
fn block_hash(&self, _number: u64) -> Result<Option<B256>, ProviderError> {
|
||||||
|
Ok(Some(B256::ZERO))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonical_hashes_range(
|
||||||
|
&self,
|
||||||
|
_start: u64,
|
||||||
|
_end: u64,
|
||||||
|
) -> Result<Vec<B256>, ProviderError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response configuration for engine messages
|
||||||
|
struct EngineResponses {
|
||||||
|
new_payload: PayloadStatusEnum,
|
||||||
|
fcu: PayloadStatusEnum,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EngineResponses {
|
||||||
|
fn both_valid() -> Self {
|
||||||
|
Self { new_payload: PayloadStatusEnum::Valid, fcu: PayloadStatusEnum::Valid }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_new_payload() -> Self {
|
||||||
|
Self {
|
||||||
|
new_payload: PayloadStatusEnum::Invalid { validation_error: "test error".into() },
|
||||||
|
fcu: PayloadStatusEnum::Valid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_fcu() -> Self {
|
||||||
|
Self {
|
||||||
|
new_payload: PayloadStatusEnum::Valid,
|
||||||
|
fcu: PayloadStatusEnum::Invalid { validation_error: "fcu error".into() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test fixture for block import tests
|
||||||
|
struct TestFixture {
|
||||||
|
handle: ImportHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestFixture {
|
||||||
|
/// Create a new test fixture with the given engine responses
|
||||||
|
async fn new(responses: EngineResponses) -> Self {
|
||||||
|
let consensus = Arc::new(HlConsensus { provider: MockProvider });
|
||||||
|
let (to_engine, from_engine) = mpsc::unbounded_channel();
|
||||||
|
let engine_handle = BeaconConsensusEngineHandle::new(to_engine);
|
||||||
|
|
||||||
|
handle_engine_msg(from_engine, responses).await;
|
||||||
|
|
||||||
|
let (to_import, from_network) = mpsc::unbounded_channel();
|
||||||
|
let (to_network, import_outcome) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let handle = ImportHandle::new(to_import, import_outcome);
|
||||||
|
|
||||||
|
let service = ImportService::new(consensus, engine_handle, from_network, to_network);
|
||||||
|
tokio::spawn(Box::pin(async move {
|
||||||
|
service.await.unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
Self { handle }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a block import test with the given event assertion
|
||||||
|
async fn assert_block_import<F>(&mut self, assert_fn: F)
|
||||||
|
where
|
||||||
|
F: Fn(&BlockImportEvent<HlNewBlock>) -> bool,
|
||||||
|
{
|
||||||
|
let block_msg = create_test_block();
|
||||||
|
self.handle.send_block(block_msg, PeerId::random()).unwrap();
|
||||||
|
|
||||||
|
let waker = futures::task::noop_waker();
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
let mut outcomes = Vec::new();
|
||||||
|
|
||||||
|
// Wait for both NewPayload and FCU outcomes
|
||||||
|
while outcomes.len() < 2 {
|
||||||
|
match self.handle.poll_outcome(&mut cx) {
|
||||||
|
Poll::Ready(Some(outcome)) => {
|
||||||
|
outcomes.push(outcome);
|
||||||
|
}
|
||||||
|
Poll::Ready(None) => break,
|
||||||
|
Poll::Pending => tokio::task::yield_now().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that at least one outcome matches our criteria
|
||||||
|
assert!(
|
||||||
|
outcomes.iter().any(assert_fn),
|
||||||
|
"No outcome matched the expected criteria. Outcomes: {outcomes:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a test block message
|
||||||
|
fn create_test_block() -> NewBlockMessage<HlNewBlock> {
|
||||||
|
let block = HlBlock {
|
||||||
|
header: Header::default(),
|
||||||
|
body: HlBlockBody {
|
||||||
|
inner: BlockBody {
|
||||||
|
transactions: Vec::new(),
|
||||||
|
ommers: Vec::new(),
|
||||||
|
withdrawals: None,
|
||||||
|
},
|
||||||
|
sidecars: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let new_block = HlNewBlock(NewBlock { block, td: U128::from(1) });
|
||||||
|
let hash = new_block.0.block.header.hash_slow();
|
||||||
|
NewBlockMessage { hash, block: Arc::new(new_block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to handle engine messages with specified payload statuses
|
||||||
|
async fn handle_engine_msg(
|
||||||
|
mut from_engine: mpsc::UnboundedReceiver<BeaconEngineMessage<HlPayloadTypes>>,
|
||||||
|
responses: EngineResponses,
|
||||||
|
) {
|
||||||
|
tokio::spawn(Box::pin(async move {
|
||||||
|
while let Some(message) = from_engine.recv().await {
|
||||||
|
match message {
|
||||||
|
BeaconEngineMessage::NewPayload { payload: _, tx } => {
|
||||||
|
tx.send(Ok(PayloadStatus::new(responses.new_payload.clone(), None)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
BeaconEngineMessage::ForkchoiceUpdated {
|
||||||
|
state: _,
|
||||||
|
payload_attrs: _,
|
||||||
|
version: _,
|
||||||
|
tx,
|
||||||
|
} => {
|
||||||
|
tx.send(Ok(OnForkChoiceUpdated::valid(PayloadStatus::new(
|
||||||
|
responses.fcu.clone(),
|
||||||
|
None,
|
||||||
|
))))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
255
src/node/network/mod.rs
Normal file
255
src/node/network/mod.rs
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
#![allow(clippy::owned_cow)]
|
||||||
|
use crate::{
|
||||||
|
consensus::HlConsensus,
|
||||||
|
node::{
|
||||||
|
network::block_import::{handle::ImportHandle, service::ImportService, HlBlockImport},
|
||||||
|
primitives::HlPrimitives,
|
||||||
|
rpc::engine_api::payload::HlPayloadTypes,
|
||||||
|
types::ReadPrecompileCalls,
|
||||||
|
HlNode,
|
||||||
|
},
|
||||||
|
HlBlock,
|
||||||
|
};
|
||||||
|
use alloy_rlp::{Decodable, Encodable};
|
||||||
|
// use handshake::HlHandshake;
|
||||||
|
use reth::{
|
||||||
|
api::{FullNodeTypes, TxTy},
|
||||||
|
builder::{components::NetworkBuilder, BuilderContext},
|
||||||
|
transaction_pool::{PoolTransaction, TransactionPool},
|
||||||
|
};
|
||||||
|
use reth_discv4::{Discv4Config, NodeRecord};
|
||||||
|
use reth_engine_primitives::BeaconConsensusEngineHandle;
|
||||||
|
use reth_eth_wire::{BasicNetworkPrimitives, NewBlock, NewBlockPayload};
|
||||||
|
use reth_ethereum_primitives::PooledTransactionVariant;
|
||||||
|
use reth_network::{NetworkConfig, NetworkHandle, NetworkManager};
|
||||||
|
use reth_network_api::PeersInfo;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use tokio::sync::{mpsc, oneshot, Mutex};
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
pub mod block_import;
|
||||||
|
// pub mod handshake;
|
||||||
|
// pub(crate) mod upgrade_status;
|
||||||
|
/// HL `NewBlock` message value.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct HlNewBlock(pub NewBlock<HlBlock>);
|
||||||
|
|
||||||
|
mod rlp {
|
||||||
|
use super::*;
|
||||||
|
use crate::HlBlockBody;
|
||||||
|
use alloy_consensus::{BlobTransactionSidecar, BlockBody, Header};
|
||||||
|
use alloy_primitives::U128;
|
||||||
|
use alloy_rlp::{RlpDecodable, RlpEncodable};
|
||||||
|
use alloy_rpc_types::Withdrawals;
|
||||||
|
use reth_primitives::TransactionSigned;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(RlpEncodable, RlpDecodable)]
|
||||||
|
#[rlp(trailing)]
|
||||||
|
struct BlockHelper<'a> {
|
||||||
|
header: Cow<'a, Header>,
|
||||||
|
transactions: Cow<'a, Vec<TransactionSigned>>,
|
||||||
|
ommers: Cow<'a, Vec<Header>>,
|
||||||
|
withdrawals: Option<Cow<'a, Withdrawals>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RlpEncodable, RlpDecodable)]
|
||||||
|
#[rlp(trailing)]
|
||||||
|
struct HlNewBlockHelper<'a> {
|
||||||
|
block: BlockHelper<'a>,
|
||||||
|
td: U128,
|
||||||
|
sidecars: Option<Cow<'a, Vec<BlobTransactionSidecar>>>,
|
||||||
|
read_precompile_calls: Option<Cow<'a, ReadPrecompileCalls>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a HlNewBlock> for HlNewBlockHelper<'a> {
|
||||||
|
fn from(value: &'a HlNewBlock) -> Self {
|
||||||
|
let HlNewBlock(NewBlock {
|
||||||
|
block:
|
||||||
|
HlBlock {
|
||||||
|
header,
|
||||||
|
body:
|
||||||
|
HlBlockBody {
|
||||||
|
inner:
|
||||||
|
BlockBody {
|
||||||
|
transactions,
|
||||||
|
ommers,
|
||||||
|
withdrawals,
|
||||||
|
},
|
||||||
|
sidecars,
|
||||||
|
read_precompile_calls,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
td,
|
||||||
|
}) = value;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
block: BlockHelper {
|
||||||
|
header: Cow::Borrowed(header),
|
||||||
|
transactions: Cow::Borrowed(transactions),
|
||||||
|
ommers: Cow::Borrowed(ommers),
|
||||||
|
withdrawals: withdrawals.as_ref().map(Cow::Borrowed),
|
||||||
|
},
|
||||||
|
td: *td,
|
||||||
|
sidecars: sidecars.as_ref().map(Cow::Borrowed),
|
||||||
|
read_precompile_calls: read_precompile_calls.as_ref().map(Cow::Borrowed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encodable for HlNewBlock {
|
||||||
|
fn encode(&self, out: &mut dyn bytes::BufMut) {
|
||||||
|
HlNewBlockHelper::from(self).encode(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn length(&self) -> usize {
|
||||||
|
HlNewBlockHelper::from(self).length()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decodable for HlNewBlock {
|
||||||
|
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||||
|
let HlNewBlockHelper {
|
||||||
|
block:
|
||||||
|
BlockHelper {
|
||||||
|
header,
|
||||||
|
transactions,
|
||||||
|
ommers,
|
||||||
|
withdrawals,
|
||||||
|
},
|
||||||
|
td,
|
||||||
|
sidecars,
|
||||||
|
read_precompile_calls,
|
||||||
|
} = HlNewBlockHelper::decode(buf)?;
|
||||||
|
|
||||||
|
Ok(HlNewBlock(NewBlock {
|
||||||
|
block: HlBlock {
|
||||||
|
header: header.into_owned(),
|
||||||
|
body: HlBlockBody {
|
||||||
|
inner: BlockBody {
|
||||||
|
transactions: transactions.into_owned(),
|
||||||
|
ommers: ommers.into_owned(),
|
||||||
|
withdrawals: withdrawals.map(|w| w.into_owned()),
|
||||||
|
},
|
||||||
|
sidecars: sidecars.map(|s| s.into_owned()),
|
||||||
|
read_precompile_calls: read_precompile_calls.map(|s| s.into_owned()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
td,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewBlockPayload for HlNewBlock {
|
||||||
|
type Block = HlBlock;
|
||||||
|
|
||||||
|
fn block(&self) -> &Self::Block {
|
||||||
|
&self.0.block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Network primitives for HL.
|
||||||
|
pub type HlNetworkPrimitives =
|
||||||
|
BasicNetworkPrimitives<HlPrimitives, PooledTransactionVariant, HlNewBlock>;
|
||||||
|
|
||||||
|
/// A basic hl network builder.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HlNetworkBuilder {
|
||||||
|
pub(crate) engine_handle_rx:
|
||||||
|
Arc<Mutex<Option<oneshot::Receiver<BeaconConsensusEngineHandle<HlPayloadTypes>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlNetworkBuilder {
|
||||||
|
/// Returns the [`NetworkConfig`] that contains the settings to launch the p2p network.
|
||||||
|
///
|
||||||
|
/// This applies the configured [`HlNetworkBuilder`] settings.
|
||||||
|
pub fn network_config<Node>(
|
||||||
|
self,
|
||||||
|
ctx: &BuilderContext<Node>,
|
||||||
|
) -> eyre::Result<NetworkConfig<Node::Provider, HlNetworkPrimitives>>
|
||||||
|
where
|
||||||
|
Node: FullNodeTypes<Types = HlNode>,
|
||||||
|
{
|
||||||
|
let Self { engine_handle_rx } = self;
|
||||||
|
|
||||||
|
let network_builder = ctx.network_config_builder()?;
|
||||||
|
|
||||||
|
let (to_import, from_network) = mpsc::unbounded_channel();
|
||||||
|
let (to_network, import_outcome) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
let handle = ImportHandle::new(to_import, import_outcome);
|
||||||
|
let consensus = Arc::new(HlConsensus {
|
||||||
|
provider: ctx.provider().clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.task_executor()
|
||||||
|
.spawn_critical("block import", async move {
|
||||||
|
let handle = engine_handle_rx
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.take()
|
||||||
|
.expect("node should only be launched once")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ImportService::new(consensus, handle, from_network, to_network)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let network_builder = network_builder
|
||||||
|
// .boot_nodes(boot_nodes())
|
||||||
|
.set_head(ctx.head())
|
||||||
|
// .with_pow()
|
||||||
|
.block_import(Box::new(HlBlockImport::new(handle)));
|
||||||
|
// .discovery(discv4)
|
||||||
|
// .eth_rlpx_handshake(Arc::new(HlHandshake::default()));
|
||||||
|
|
||||||
|
let network_config = ctx.build_network_config(network_builder);
|
||||||
|
|
||||||
|
Ok(network_config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Node, Pool> NetworkBuilder<Node, Pool> for HlNetworkBuilder
|
||||||
|
where
|
||||||
|
Node: FullNodeTypes<Types = HlNode>,
|
||||||
|
Pool: TransactionPool<
|
||||||
|
Transaction: PoolTransaction<
|
||||||
|
Consensus = TxTy<Node::Types>,
|
||||||
|
Pooled = PooledTransactionVariant,
|
||||||
|
>,
|
||||||
|
> + Unpin
|
||||||
|
+ 'static,
|
||||||
|
{
|
||||||
|
type Network = NetworkHandle<HlNetworkPrimitives>;
|
||||||
|
|
||||||
|
async fn build_network(
|
||||||
|
self,
|
||||||
|
ctx: &BuilderContext<Node>,
|
||||||
|
pool: Pool,
|
||||||
|
) -> eyre::Result<Self::Network> {
|
||||||
|
let network_config = self.network_config(ctx)?;
|
||||||
|
let network = NetworkManager::builder(network_config).await?;
|
||||||
|
let handle = ctx.start_network(network, pool);
|
||||||
|
info!(target: "reth::cli", enode=%handle.local_node_record(), "P2P networking initialized");
|
||||||
|
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HL mainnet bootnodes <https://github.com/bnb-chain/hl/blob/master/params/bootnodes.go#L23>
|
||||||
|
static BOOTNODES : [&str; 6] = [
|
||||||
|
"enode://433c8bfdf53a3e2268ccb1b829e47f629793291cbddf0c76ae626da802f90532251fc558e2e0d10d6725e759088439bf1cd4714716b03a259a35d4b2e4acfa7f@52.69.102.73:30311",
|
||||||
|
"enode://571bee8fb902a625942f10a770ccf727ae2ba1bab2a2b64e121594a99c9437317f6166a395670a00b7d93647eacafe598b6bbcef15b40b6d1a10243865a3e80f@35.73.84.120:30311",
|
||||||
|
"enode://fac42fb0ba082b7d1eebded216db42161163d42e4f52c9e47716946d64468a62da4ba0b1cac0df5e8bf1e5284861d757339751c33d51dfef318be5168803d0b5@18.203.152.54:30311",
|
||||||
|
"enode://3063d1c9e1b824cfbb7c7b6abafa34faec6bb4e7e06941d218d760acdd7963b274278c5c3e63914bd6d1b58504c59ec5522c56f883baceb8538674b92da48a96@34.250.32.100:30311",
|
||||||
|
"enode://ad78c64a4ade83692488aa42e4c94084516e555d3f340d9802c2bf106a3df8868bc46eae083d2de4018f40e8d9a9952c32a0943cd68855a9bc9fd07aac982a6d@34.204.214.24:30311",
|
||||||
|
"enode://5db798deb67df75d073f8e2953dad283148133acb520625ea804c9c4ad09a35f13592a762d8f89056248f3889f6dcc33490c145774ea4ff2966982294909b37a@107.20.191.97:30311",
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn boot_nodes() -> Vec<NodeRecord> {
|
||||||
|
BOOTNODES[..].iter().map(|s| s.parse().unwrap()).collect()
|
||||||
|
}
|
||||||
173
src/node/rpc/block.rs
Normal file
173
src/node/rpc/block.rs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
use crate::{
|
||||||
|
chainspec::HlChainSpec,
|
||||||
|
node::rpc::{HlEthApi, HlNodeCore},
|
||||||
|
node::{HlBlock, HlPrimitives},
|
||||||
|
};
|
||||||
|
use alloy_consensus::BlockHeader;
|
||||||
|
use alloy_primitives::B256;
|
||||||
|
use reth::{
|
||||||
|
api::NodeTypes,
|
||||||
|
builder::FullNodeComponents,
|
||||||
|
primitives::{Receipt, SealedHeader, TransactionMeta, TransactionSigned},
|
||||||
|
providers::{BlockReaderIdExt, ProviderHeader, ReceiptProvider, TransactionsProvider},
|
||||||
|
rpc::{
|
||||||
|
eth::EthApiTypes,
|
||||||
|
server_types::eth::{error::FromEvmError, EthApiError, EthReceiptBuilder, PendingBlock},
|
||||||
|
types::{BlockId, TransactionReceipt},
|
||||||
|
},
|
||||||
|
transaction_pool::{PoolTransaction, TransactionPool},
|
||||||
|
};
|
||||||
|
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||||
|
use reth_evm::{ConfigureEvm, NextBlockEnvAttributes};
|
||||||
|
use reth_primitives_traits::BlockBody as _;
|
||||||
|
use reth_provider::{
|
||||||
|
BlockReader, ChainSpecProvider, HeaderProvider, ProviderBlock, ProviderReceipt, ProviderTx,
|
||||||
|
StateProviderFactory,
|
||||||
|
};
|
||||||
|
use reth_rpc_eth_api::{
|
||||||
|
helpers::{EthBlocks, LoadBlock, LoadPendingBlock, LoadReceipt, SpawnBlocking},
|
||||||
|
types::RpcTypes,
|
||||||
|
FromEthApiError, RpcNodeCore, RpcNodeCoreExt, RpcReceipt,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<N> EthBlocks for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: LoadBlock<
|
||||||
|
Error = EthApiError,
|
||||||
|
NetworkTypes: RpcTypes<Receipt = TransactionReceipt>,
|
||||||
|
Provider: BlockReader<Transaction = TransactionSigned, Receipt = Receipt>,
|
||||||
|
>,
|
||||||
|
N: HlNodeCore<Provider: ChainSpecProvider<ChainSpec = HlChainSpec> + HeaderProvider>,
|
||||||
|
{
|
||||||
|
async fn block_receipts(
|
||||||
|
&self,
|
||||||
|
block_id: BlockId,
|
||||||
|
) -> Result<Option<Vec<RpcReceipt<Self::NetworkTypes>>>, Self::Error>
|
||||||
|
where
|
||||||
|
Self: LoadReceipt,
|
||||||
|
{
|
||||||
|
if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? {
|
||||||
|
let block_number = block.number();
|
||||||
|
let base_fee = block.base_fee_per_gas();
|
||||||
|
let block_hash = block.hash();
|
||||||
|
let excess_blob_gas = block.excess_blob_gas();
|
||||||
|
let timestamp = block.timestamp();
|
||||||
|
let blob_params = self
|
||||||
|
.provider()
|
||||||
|
.chain_spec()
|
||||||
|
.blob_params_at_timestamp(timestamp);
|
||||||
|
|
||||||
|
return block
|
||||||
|
.body()
|
||||||
|
.transactions()
|
||||||
|
.iter()
|
||||||
|
.zip(receipts.iter())
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, (tx, receipt))| {
|
||||||
|
let meta = TransactionMeta {
|
||||||
|
tx_hash: *tx.tx_hash(),
|
||||||
|
index: idx as u64,
|
||||||
|
block_hash,
|
||||||
|
block_number,
|
||||||
|
base_fee,
|
||||||
|
excess_blob_gas,
|
||||||
|
timestamp,
|
||||||
|
};
|
||||||
|
EthReceiptBuilder::new(tx, meta, receipt, &receipts, blob_params)
|
||||||
|
.map(|builder| builder.build())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, Self::Error>>()
|
||||||
|
.map(Some);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> LoadBlock for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: LoadPendingBlock
|
||||||
|
+ SpawnBlocking
|
||||||
|
+ RpcNodeCoreExt<
|
||||||
|
Pool: TransactionPool<
|
||||||
|
Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> LoadPendingBlock for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: SpawnBlocking
|
||||||
|
+ EthApiTypes<
|
||||||
|
NetworkTypes: RpcTypes<
|
||||||
|
Header = alloy_rpc_types_eth::Header<ProviderHeader<Self::Provider>>,
|
||||||
|
>,
|
||||||
|
Error: FromEvmError<Self::Evm>,
|
||||||
|
>,
|
||||||
|
N: RpcNodeCore<
|
||||||
|
Provider: BlockReaderIdExt<
|
||||||
|
Transaction = TransactionSigned,
|
||||||
|
Block = HlBlock,
|
||||||
|
Receipt = Receipt,
|
||||||
|
Header = alloy_consensus::Header,
|
||||||
|
> + ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
|
||||||
|
+ StateProviderFactory,
|
||||||
|
Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<N::Provider>>>,
|
||||||
|
Evm: ConfigureEvm<Primitives = HlPrimitives, NextBlockEnvCtx = NextBlockEnvAttributes>,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn pending_block(
|
||||||
|
&self,
|
||||||
|
) -> &tokio::sync::Mutex<
|
||||||
|
Option<PendingBlock<ProviderBlock<Self::Provider>, ProviderReceipt<Self::Provider>>>,
|
||||||
|
> {
|
||||||
|
self.inner.eth_api.pending_block()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_env_attributes(
|
||||||
|
&self,
|
||||||
|
parent: &SealedHeader<ProviderHeader<Self::Provider>>,
|
||||||
|
) -> Result<<Self::Evm as reth_evm::ConfigureEvm>::NextBlockEnvCtx, Self::Error> {
|
||||||
|
Ok(NextBlockEnvAttributes {
|
||||||
|
timestamp: parent.timestamp().saturating_add(12),
|
||||||
|
suggested_fee_recipient: parent.beneficiary(),
|
||||||
|
prev_randao: B256::random(),
|
||||||
|
gas_limit: parent.gas_limit(),
|
||||||
|
parent_beacon_block_root: parent.parent_beacon_block_root(),
|
||||||
|
withdrawals: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> LoadReceipt for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: Send + Sync,
|
||||||
|
N: FullNodeComponents<Types: NodeTypes<ChainSpec = HlChainSpec>>,
|
||||||
|
Self::Provider:
|
||||||
|
TransactionsProvider<Transaction = TransactionSigned> + ReceiptProvider<Receipt = Receipt>,
|
||||||
|
{
|
||||||
|
async fn build_transaction_receipt(
|
||||||
|
&self,
|
||||||
|
tx: TransactionSigned,
|
||||||
|
meta: TransactionMeta,
|
||||||
|
receipt: Receipt,
|
||||||
|
) -> Result<RpcReceipt<Self::NetworkTypes>, Self::Error> {
|
||||||
|
let hash = meta.block_hash;
|
||||||
|
// get all receipts for the block
|
||||||
|
let all_receipts = self
|
||||||
|
.cache()
|
||||||
|
.get_receipts(hash)
|
||||||
|
.await
|
||||||
|
.map_err(Self::Error::from_eth_err)?
|
||||||
|
.ok_or(EthApiError::HeaderNotFound(hash.into()))?;
|
||||||
|
let blob_params = self
|
||||||
|
.provider()
|
||||||
|
.chain_spec()
|
||||||
|
.blob_params_at_timestamp(meta.timestamp);
|
||||||
|
|
||||||
|
Ok(EthReceiptBuilder::new(&tx, meta, &receipt, &all_receipts, blob_params)?.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
164
src/node/rpc/call.rs
Normal file
164
src/node/rpc/call.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
use super::{HlEthApi, HlNodeCore};
|
||||||
|
use crate::evm::transaction::HlTxEnv;
|
||||||
|
use alloy_consensus::TxType;
|
||||||
|
use alloy_primitives::{TxKind, U256};
|
||||||
|
use alloy_rpc_types::TransactionRequest;
|
||||||
|
use alloy_signer::Either;
|
||||||
|
use reth::rpc::server_types::eth::{revm_utils::CallFees, EthApiError, RpcInvalidTransactionError};
|
||||||
|
use reth_evm::{block::BlockExecutorFactory, ConfigureEvm, EvmEnv, EvmFactory, SpecFor};
|
||||||
|
use reth_primitives::NodePrimitives;
|
||||||
|
use reth_provider::{ProviderHeader, ProviderTx};
|
||||||
|
use reth_rpc_eth_api::{
|
||||||
|
helpers::{estimate::EstimateCall, Call, EthCall, LoadBlock, LoadState, SpawnBlocking},
|
||||||
|
FromEthApiError, FromEvmError, FullEthApiTypes, IntoEthApiError,
|
||||||
|
};
|
||||||
|
use revm::{
|
||||||
|
context::{Block as _, TxEnv},
|
||||||
|
Database,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<N> EthCall for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: EstimateCall + LoadBlock + FullEthApiTypes,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> EstimateCall for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: Call,
|
||||||
|
Self::Error: From<EthApiError>,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> Call for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: LoadState<
|
||||||
|
Evm: ConfigureEvm<
|
||||||
|
Primitives: NodePrimitives<
|
||||||
|
BlockHeader = ProviderHeader<Self::Provider>,
|
||||||
|
SignedTx = ProviderTx<Self::Provider>,
|
||||||
|
>,
|
||||||
|
BlockExecutorFactory: BlockExecutorFactory<
|
||||||
|
EvmFactory: EvmFactory<Tx = HlTxEnv<TxEnv>>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
Error: FromEvmError<Self::Evm>,
|
||||||
|
> + SpawnBlocking,
|
||||||
|
Self::Error: From<EthApiError>,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn call_gas_limit(&self) -> u64 {
|
||||||
|
self.inner.eth_api.gas_cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn max_simulate_blocks(&self) -> u64 {
|
||||||
|
self.inner.eth_api.max_simulate_blocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_txn_env(
|
||||||
|
&self,
|
||||||
|
evm_env: &EvmEnv<SpecFor<Self::Evm>>,
|
||||||
|
request: TransactionRequest,
|
||||||
|
mut db: impl Database<Error: Into<EthApiError>>,
|
||||||
|
) -> Result<HlTxEnv<TxEnv>, Self::Error> {
|
||||||
|
// Ensure that if versioned hashes are set, they're not empty
|
||||||
|
if request.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) {
|
||||||
|
return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into_eth_err())
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_type = if request.authorization_list.is_some() {
|
||||||
|
TxType::Eip7702
|
||||||
|
} else if request.sidecar.is_some() || request.max_fee_per_blob_gas.is_some() {
|
||||||
|
TxType::Eip4844
|
||||||
|
} else if request.max_fee_per_gas.is_some() || request.max_priority_fee_per_gas.is_some() {
|
||||||
|
TxType::Eip1559
|
||||||
|
} else if request.access_list.is_some() {
|
||||||
|
TxType::Eip2930
|
||||||
|
} else {
|
||||||
|
TxType::Legacy
|
||||||
|
} as u8;
|
||||||
|
|
||||||
|
let TransactionRequest {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
gas_price,
|
||||||
|
max_fee_per_gas,
|
||||||
|
max_priority_fee_per_gas,
|
||||||
|
gas,
|
||||||
|
value,
|
||||||
|
input,
|
||||||
|
nonce,
|
||||||
|
access_list,
|
||||||
|
chain_id,
|
||||||
|
blob_versioned_hashes,
|
||||||
|
max_fee_per_blob_gas,
|
||||||
|
authorization_list,
|
||||||
|
transaction_type: _,
|
||||||
|
sidecar: _,
|
||||||
|
} = request;
|
||||||
|
|
||||||
|
let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } =
|
||||||
|
CallFees::ensure_fees(
|
||||||
|
gas_price.map(U256::from),
|
||||||
|
max_fee_per_gas.map(U256::from),
|
||||||
|
max_priority_fee_per_gas.map(U256::from),
|
||||||
|
U256::from(evm_env.block_env.basefee),
|
||||||
|
blob_versioned_hashes.as_deref(),
|
||||||
|
max_fee_per_blob_gas.map(U256::from),
|
||||||
|
evm_env.block_env.blob_gasprice().map(U256::from),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let gas_limit = gas.unwrap_or(
|
||||||
|
// Use maximum allowed gas limit. The reason for this
|
||||||
|
// is that both Erigon and Geth use pre-configured gas cap even if
|
||||||
|
// it's possible to derive the gas limit from the block:
|
||||||
|
// <https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/cmd/rpcdaemon/commands/trace_adhoc.go#L956
|
||||||
|
// https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/eth/ethconfig/config.go#L94>
|
||||||
|
evm_env.block_env.gas_limit,
|
||||||
|
);
|
||||||
|
|
||||||
|
let chain_id = chain_id.unwrap_or(evm_env.cfg_env.chain_id);
|
||||||
|
|
||||||
|
let caller = from.unwrap_or_default();
|
||||||
|
|
||||||
|
let nonce = if let Some(nonce) = nonce {
|
||||||
|
nonce
|
||||||
|
} else {
|
||||||
|
db.basic(caller).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let env = TxEnv {
|
||||||
|
tx_type,
|
||||||
|
gas_limit,
|
||||||
|
nonce,
|
||||||
|
caller,
|
||||||
|
gas_price: gas_price.saturating_to(),
|
||||||
|
gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()),
|
||||||
|
kind: to.unwrap_or(TxKind::Create),
|
||||||
|
value: value.unwrap_or_default(),
|
||||||
|
data: input
|
||||||
|
.try_into_unique_input()
|
||||||
|
.map_err(Self::Error::from_eth_err)?
|
||||||
|
.unwrap_or_default(),
|
||||||
|
chain_id: Some(chain_id),
|
||||||
|
access_list: access_list.unwrap_or_default(),
|
||||||
|
// EIP-4844 fields
|
||||||
|
blob_hashes: blob_versioned_hashes.unwrap_or_default(),
|
||||||
|
max_fee_per_blob_gas: max_fee_per_blob_gas
|
||||||
|
.map(|v| v.saturating_to())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
// EIP-7702 fields
|
||||||
|
authorization_list: authorization_list
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(Either::Left)
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HlTxEnv::new(env))
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/node/rpc/engine_api/builder.rs
Normal file
20
src/node/rpc/engine_api/builder.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use super::HlEngineApi;
|
||||||
|
use reth::{
|
||||||
|
api::{AddOnsContext, FullNodeComponents},
|
||||||
|
builder::rpc::EngineApiBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Builder for mocked [`HlEngineApi`] implementation.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct HlEngineApiBuilder;
|
||||||
|
|
||||||
|
impl<N> EngineApiBuilder<N> for HlEngineApiBuilder
|
||||||
|
where
|
||||||
|
N: FullNodeComponents,
|
||||||
|
{
|
||||||
|
type EngineApi = HlEngineApi;
|
||||||
|
|
||||||
|
async fn build_engine_api(self, _ctx: &AddOnsContext<'_, N>) -> eyre::Result<Self::EngineApi> {
|
||||||
|
Ok(HlEngineApi::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/node/rpc/engine_api/mod.rs
Normal file
16
src/node/rpc/engine_api/mod.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use jsonrpsee_core::server::RpcModule;
|
||||||
|
use reth::rpc::api::IntoEngineApiRpcModule;
|
||||||
|
|
||||||
|
pub mod builder;
|
||||||
|
pub mod payload;
|
||||||
|
pub mod validator;
|
||||||
|
|
||||||
|
impl IntoEngineApiRpcModule for HlEngineApi {
|
||||||
|
fn into_rpc_module(self) -> RpcModule<()> {
|
||||||
|
RpcModule::new(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct HlEngineApi;
|
||||||
27
src/node/rpc/engine_api/payload.rs
Normal file
27
src/node/rpc/engine_api/payload.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::node::{engine::HlBuiltPayload, rpc::engine_api::validator::HlExecutionData};
|
||||||
|
use reth::{
|
||||||
|
payload::EthPayloadBuilderAttributes,
|
||||||
|
primitives::{NodePrimitives, SealedBlock},
|
||||||
|
};
|
||||||
|
use reth_node_ethereum::engine::EthPayloadAttributes;
|
||||||
|
use reth_payload_primitives::{BuiltPayload, PayloadTypes};
|
||||||
|
|
||||||
|
/// A default payload type for [`HlPayloadTypes`]
|
||||||
|
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct HlPayloadTypes;
|
||||||
|
|
||||||
|
impl PayloadTypes for HlPayloadTypes {
|
||||||
|
type BuiltPayload = HlBuiltPayload;
|
||||||
|
type PayloadAttributes = EthPayloadAttributes;
|
||||||
|
type PayloadBuilderAttributes = EthPayloadBuilderAttributes;
|
||||||
|
type ExecutionData = HlExecutionData;
|
||||||
|
|
||||||
|
fn block_to_payload(
|
||||||
|
block: SealedBlock<
|
||||||
|
<<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block,
|
||||||
|
>,
|
||||||
|
) -> Self::ExecutionData {
|
||||||
|
HlExecutionData(block.into_block())
|
||||||
|
}
|
||||||
|
}
|
||||||
174
src/node/rpc/engine_api/validator.rs
Normal file
174
src/node/rpc/engine_api/validator.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use crate::{
|
||||||
|
chainspec::HlChainSpec,
|
||||||
|
hardforks::HlHardforks,
|
||||||
|
node::{HlBlock, HlPrimitives},
|
||||||
|
};
|
||||||
|
use alloy_consensus::BlockHeader;
|
||||||
|
use alloy_eips::eip4895::Withdrawal;
|
||||||
|
use alloy_primitives::B256;
|
||||||
|
use alloy_rpc_types_engine::{PayloadAttributes, PayloadError};
|
||||||
|
use reth::{
|
||||||
|
api::{FullNodeComponents, NodeTypes},
|
||||||
|
builder::{rpc::EngineValidatorBuilder, AddOnsContext},
|
||||||
|
consensus::ConsensusError,
|
||||||
|
};
|
||||||
|
use reth_engine_primitives::{EngineValidator, ExecutionPayload, PayloadValidator};
|
||||||
|
use reth_payload_primitives::{
|
||||||
|
EngineApiMessageVersion, EngineObjectValidationError, NewPayloadError, PayloadOrAttributes,
|
||||||
|
PayloadTypes,
|
||||||
|
};
|
||||||
|
use reth_primitives::{RecoveredBlock, SealedBlock};
|
||||||
|
use reth_primitives_traits::Block as _;
|
||||||
|
use reth_trie_common::HashedPostState;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::payload::HlPayloadTypes;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct HlEngineValidatorBuilder;
|
||||||
|
|
||||||
|
impl<Node, Types> EngineValidatorBuilder<Node> for HlEngineValidatorBuilder
|
||||||
|
where
|
||||||
|
Types: NodeTypes<ChainSpec = HlChainSpec, Payload = HlPayloadTypes, Primitives = HlPrimitives>,
|
||||||
|
Node: FullNodeComponents<Types = Types>,
|
||||||
|
{
|
||||||
|
type Validator = HlEngineValidator;
|
||||||
|
|
||||||
|
async fn build(self, ctx: &AddOnsContext<'_, Node>) -> eyre::Result<Self::Validator> {
|
||||||
|
Ok(HlEngineValidator::new(Arc::new(
|
||||||
|
ctx.config.chain.clone().as_ref().clone(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validator for Optimism engine API.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HlEngineValidator {
|
||||||
|
inner: HlExecutionPayloadValidator<HlChainSpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HlEngineValidator {
|
||||||
|
/// Instantiates a new validator.
|
||||||
|
pub fn new(chain_spec: Arc<HlChainSpec>) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: HlExecutionPayloadValidator { inner: chain_spec },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct HlExecutionData(pub HlBlock);
|
||||||
|
|
||||||
|
impl ExecutionPayload for HlExecutionData {
|
||||||
|
fn parent_hash(&self) -> B256 {
|
||||||
|
self.0.header.parent_hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_hash(&self) -> B256 {
|
||||||
|
self.0.header.hash_slow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_number(&self) -> u64 {
|
||||||
|
self.0.header.number()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp(&self) -> u64 {
|
||||||
|
self.0.header.timestamp()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gas_used(&self) -> u64 {
|
||||||
|
self.0.header.gas_used()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PayloadValidator for HlEngineValidator {
|
||||||
|
type Block = HlBlock;
|
||||||
|
type ExecutionData = HlExecutionData;
|
||||||
|
|
||||||
|
fn ensure_well_formed_payload(
|
||||||
|
&self,
|
||||||
|
payload: Self::ExecutionData,
|
||||||
|
) -> Result<RecoveredBlock<Self::Block>, NewPayloadError> {
|
||||||
|
let sealed_block = self
|
||||||
|
.inner
|
||||||
|
.ensure_well_formed_payload(payload)
|
||||||
|
.map_err(NewPayloadError::other)?;
|
||||||
|
sealed_block
|
||||||
|
.try_recover()
|
||||||
|
.map_err(|e| NewPayloadError::Other(e.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_block_post_execution_with_hashed_state(
|
||||||
|
&self,
|
||||||
|
_state_updates: &HashedPostState,
|
||||||
|
_block: &RecoveredBlock<Self::Block>,
|
||||||
|
) -> Result<(), ConsensusError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Types> EngineValidator<Types> for HlEngineValidator
|
||||||
|
where
|
||||||
|
Types: PayloadTypes<PayloadAttributes = PayloadAttributes, ExecutionData = HlExecutionData>,
|
||||||
|
{
|
||||||
|
fn validate_version_specific_fields(
|
||||||
|
&self,
|
||||||
|
_version: EngineApiMessageVersion,
|
||||||
|
_payload_or_attrs: PayloadOrAttributes<'_, Self::ExecutionData, PayloadAttributes>,
|
||||||
|
) -> Result<(), EngineObjectValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_well_formed_attributes(
|
||||||
|
&self,
|
||||||
|
_version: EngineApiMessageVersion,
|
||||||
|
_attributes: &PayloadAttributes,
|
||||||
|
) -> Result<(), EngineObjectValidationError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execution payload validator.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct HlExecutionPayloadValidator<ChainSpec> {
|
||||||
|
/// Chain spec to validate against.
|
||||||
|
#[allow(unused)]
|
||||||
|
inner: Arc<ChainSpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ChainSpec> HlExecutionPayloadValidator<ChainSpec>
|
||||||
|
where
|
||||||
|
ChainSpec: HlHardforks,
|
||||||
|
{
|
||||||
|
pub fn ensure_well_formed_payload(
|
||||||
|
&self,
|
||||||
|
payload: HlExecutionData,
|
||||||
|
) -> Result<SealedBlock<HlBlock>, PayloadError> {
|
||||||
|
let block = payload.0;
|
||||||
|
|
||||||
|
let expected_hash = block.header.hash_slow();
|
||||||
|
|
||||||
|
// First parse the block
|
||||||
|
let sealed_block = block.seal_slow();
|
||||||
|
|
||||||
|
// Ensure the hash included in the payload matches the block hash
|
||||||
|
if expected_hash != sealed_block.hash() {
|
||||||
|
return Err(PayloadError::BlockHash {
|
||||||
|
execution: sealed_block.hash(),
|
||||||
|
consensus: expected_hash,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(sealed_block)
|
||||||
|
}
|
||||||
|
}
|
||||||
273
src/node/rpc/mod.rs
Normal file
273
src/node/rpc/mod.rs
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
use alloy_network::Ethereum;
|
||||||
|
use alloy_primitives::U256;
|
||||||
|
use reth::{
|
||||||
|
builder::{
|
||||||
|
rpc::{EthApiBuilder, EthApiCtx},
|
||||||
|
FullNodeComponents,
|
||||||
|
},
|
||||||
|
chainspec::EthChainSpec,
|
||||||
|
primitives::EthereumHardforks,
|
||||||
|
providers::ChainSpecProvider,
|
||||||
|
rpc::{
|
||||||
|
eth::{DevSigner, FullEthApiServer},
|
||||||
|
server_types::eth::{EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle},
|
||||||
|
},
|
||||||
|
tasks::{
|
||||||
|
pool::{BlockingTaskGuard, BlockingTaskPool},
|
||||||
|
TaskSpawner,
|
||||||
|
},
|
||||||
|
transaction_pool::TransactionPool,
|
||||||
|
};
|
||||||
|
use reth_evm::ConfigureEvm;
|
||||||
|
use reth_network::NetworkInfo;
|
||||||
|
use reth_optimism_rpc::eth::EthApiNodeBackend;
|
||||||
|
use reth_primitives::NodePrimitives;
|
||||||
|
use reth_provider::{
|
||||||
|
BlockNumReader, BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt,
|
||||||
|
ProviderTx, StageCheckpointReader, StateProviderFactory,
|
||||||
|
};
|
||||||
|
use reth_rpc_eth_api::{
|
||||||
|
helpers::{
|
||||||
|
AddDevSigners, EthApiSpec, EthFees, EthSigner, EthState, LoadBlock, LoadFee, LoadState,
|
||||||
|
SpawnBlocking, Trace,
|
||||||
|
},
|
||||||
|
EthApiTypes, FromEvmError, RpcNodeCore, RpcNodeCoreExt,
|
||||||
|
};
|
||||||
|
use std::{fmt, sync::Arc};
|
||||||
|
|
||||||
|
use crate::HlPrimitives;
|
||||||
|
|
||||||
|
mod block;
|
||||||
|
mod call;
|
||||||
|
pub mod engine_api;
|
||||||
|
mod transaction;
|
||||||
|
|
||||||
|
/// A helper trait with requirements for [`RpcNodeCore`] to be used in [`HlEthApi`].
|
||||||
|
pub trait HlNodeCore: RpcNodeCore<Provider: BlockReader> {}
|
||||||
|
impl<T> HlNodeCore for T where T: RpcNodeCore<Provider: BlockReader> {}
|
||||||
|
|
||||||
|
/// Container type `HlEthApi`
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub(crate) struct HlEthApiInner<N: HlNodeCore> {
|
||||||
|
/// Gateway to node's core components.
|
||||||
|
pub(crate) eth_api: EthApiNodeBackend<N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HlEthApi<N: HlNodeCore> {
|
||||||
|
/// Gateway to node's core components.
|
||||||
|
pub(crate) inner: Arc<HlEthApiInner<N>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: HlNodeCore> fmt::Debug for HlEthApi<N> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("HlEthApi").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> EthApiTypes for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: Send + Sync,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
type Error = EthApiError;
|
||||||
|
type NetworkTypes = Ethereum;
|
||||||
|
type TransactionCompat = Self;
|
||||||
|
|
||||||
|
fn tx_resp_builder(&self) -> &Self::TransactionCompat {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> RpcNodeCore for HlEthApi<N>
|
||||||
|
where
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
type Primitives = HlPrimitives;
|
||||||
|
type Provider = N::Provider;
|
||||||
|
type Pool = N::Pool;
|
||||||
|
type Evm = <N as RpcNodeCore>::Evm;
|
||||||
|
type Network = <N as RpcNodeCore>::Network;
|
||||||
|
type PayloadBuilder = ();
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn pool(&self) -> &Self::Pool {
|
||||||
|
self.inner.eth_api.pool()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn evm_config(&self) -> &Self::Evm {
|
||||||
|
self.inner.eth_api.evm_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn network(&self) -> &Self::Network {
|
||||||
|
self.inner.eth_api.network()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn payload_builder(&self) -> &Self::PayloadBuilder {
|
||||||
|
&()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn provider(&self) -> &Self::Provider {
|
||||||
|
self.inner.eth_api.provider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> RpcNodeCoreExt for HlEthApi<N>
|
||||||
|
where
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn cache(&self) -> &EthStateCache<ProviderBlock<N::Provider>, ProviderReceipt<N::Provider>> {
|
||||||
|
self.inner.eth_api.cache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> EthApiSpec for HlEthApi<N>
|
||||||
|
where
|
||||||
|
N: HlNodeCore<
|
||||||
|
Provider: ChainSpecProvider<ChainSpec: EthereumHardforks>
|
||||||
|
+ BlockNumReader
|
||||||
|
+ StageCheckpointReader,
|
||||||
|
Network: NetworkInfo,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
type Transaction = ProviderTx<Self::Provider>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn starting_block(&self) -> U256 {
|
||||||
|
self.inner.eth_api.starting_block()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> {
|
||||||
|
self.inner.eth_api.signers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> SpawnBlocking for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: Send + Sync + Clone + 'static,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn io_task_spawner(&self) -> impl TaskSpawner {
|
||||||
|
self.inner.eth_api.task_spawner()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tracing_task_pool(&self) -> &BlockingTaskPool {
|
||||||
|
self.inner.eth_api.blocking_task_pool()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tracing_task_guard(&self) -> &BlockingTaskGuard {
|
||||||
|
self.inner.eth_api.blocking_task_guard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> LoadFee for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: LoadBlock<Provider = N::Provider>,
|
||||||
|
N: HlNodeCore<
|
||||||
|
Provider: BlockReaderIdExt
|
||||||
|
+ ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
|
||||||
|
+ StateProviderFactory,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
|
||||||
|
self.inner.eth_api.gas_oracle()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn fee_history_cache(&self) -> &FeeHistoryCache {
|
||||||
|
self.inner.eth_api.fee_history_cache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> LoadState for HlEthApi<N> where
|
||||||
|
N: HlNodeCore<
|
||||||
|
Provider: StateProviderFactory + ChainSpecProvider<ChainSpec: EthereumHardforks>,
|
||||||
|
Pool: TransactionPool,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> EthState for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: LoadState + SpawnBlocking,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn max_proof_window(&self) -> u64 {
|
||||||
|
self.inner.eth_api.eth_proof_window()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> EthFees for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: LoadFee,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> Trace for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: RpcNodeCore<Provider: BlockReader>
|
||||||
|
+ LoadState<
|
||||||
|
Evm: ConfigureEvm<
|
||||||
|
Primitives: NodePrimitives<
|
||||||
|
BlockHeader = ProviderHeader<Self::Provider>,
|
||||||
|
SignedTx = ProviderTx<Self::Provider>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
Error: FromEvmError<Self::Evm>,
|
||||||
|
>,
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> AddDevSigners for HlEthApi<N>
|
||||||
|
where
|
||||||
|
N: HlNodeCore,
|
||||||
|
{
|
||||||
|
fn with_dev_accounts(&self) {
|
||||||
|
*self.inner.eth_api.signers().write() = DevSigner::random_signers(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds [`HlEthApi`] for HL.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct HlEthApiBuilder;
|
||||||
|
|
||||||
|
impl<N> EthApiBuilder<N> for HlEthApiBuilder
|
||||||
|
where
|
||||||
|
N: FullNodeComponents,
|
||||||
|
HlEthApi<N>: FullEthApiServer<Provider = N::Provider, Pool = N::Pool>,
|
||||||
|
{
|
||||||
|
type EthApi = HlEthApi<N>;
|
||||||
|
|
||||||
|
async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result<Self::EthApi> {
|
||||||
|
let eth_api = reth::rpc::eth::EthApiBuilder::new(
|
||||||
|
ctx.components.provider().clone(),
|
||||||
|
ctx.components.pool().clone(),
|
||||||
|
ctx.components.network().clone(),
|
||||||
|
ctx.components.evm_config().clone(),
|
||||||
|
)
|
||||||
|
.eth_cache(ctx.cache)
|
||||||
|
.task_spawner(ctx.components.task_executor().clone())
|
||||||
|
.gas_cap(ctx.config.rpc_gas_cap.into())
|
||||||
|
.max_simulate_blocks(ctx.config.rpc_max_simulate_blocks)
|
||||||
|
.eth_proof_window(ctx.config.eth_proof_window)
|
||||||
|
.fee_history_cache_config(ctx.config.fee_history_cache)
|
||||||
|
.proof_permits(ctx.config.proof_permits)
|
||||||
|
.build_inner();
|
||||||
|
|
||||||
|
Ok(HlEthApi { inner: Arc::new(HlEthApiInner { eth_api }) })
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/node/rpc/transaction.rs
Normal file
90
src/node/rpc/transaction.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use super::HlNodeCore;
|
||||||
|
use crate::{node::rpc::HlEthApi, HlPrimitives};
|
||||||
|
use alloy_network::{Ethereum, Network};
|
||||||
|
use alloy_primitives::{Bytes, Signature, B256};
|
||||||
|
use reth::{
|
||||||
|
builder::FullNodeComponents,
|
||||||
|
primitives::{Receipt, Recovered, TransactionSigned},
|
||||||
|
providers::ReceiptProvider,
|
||||||
|
rpc::{
|
||||||
|
eth::helpers::types::EthRpcConverter,
|
||||||
|
server_types::eth::{utils::recover_raw_transaction, EthApiError},
|
||||||
|
types::{TransactionInfo, TransactionRequest},
|
||||||
|
},
|
||||||
|
transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool},
|
||||||
|
};
|
||||||
|
use reth_provider::{BlockReader, BlockReaderIdExt, ProviderTx, TransactionsProvider};
|
||||||
|
use reth_rpc_eth_api::{
|
||||||
|
helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
|
||||||
|
FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TransactionCompat,
|
||||||
|
};
|
||||||
|
impl<N> LoadTransaction for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt,
|
||||||
|
N: HlNodeCore<Provider: TransactionsProvider, Pool: TransactionPool>,
|
||||||
|
Self::Pool: TransactionPool,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> TransactionCompat for HlEthApi<N>
|
||||||
|
where
|
||||||
|
N: FullNodeComponents<Provider: ReceiptProvider<Receipt = Receipt>>,
|
||||||
|
{
|
||||||
|
type Primitives = HlPrimitives;
|
||||||
|
type Transaction = <Ethereum as Network>::TransactionResponse;
|
||||||
|
|
||||||
|
type Error = EthApiError;
|
||||||
|
|
||||||
|
fn fill(
|
||||||
|
&self,
|
||||||
|
tx: Recovered<TransactionSigned>,
|
||||||
|
tx_info: TransactionInfo,
|
||||||
|
) -> Result<Self::Transaction, Self::Error> {
|
||||||
|
let builder = EthRpcConverter::default();
|
||||||
|
builder.fill(tx, tx_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_simulate_v1_transaction(
|
||||||
|
&self,
|
||||||
|
request: TransactionRequest,
|
||||||
|
) -> Result<TransactionSigned, Self::Error> {
|
||||||
|
let Ok(tx) = request.build_typed_tx() else {
|
||||||
|
return Err(EthApiError::TransactionConversionError)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create an empty signature for the transaction.
|
||||||
|
let signature = Signature::new(Default::default(), Default::default(), false);
|
||||||
|
Ok(TransactionSigned::new_unhashed(tx.into(), signature))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> EthTransactions for HlEthApi<N>
|
||||||
|
where
|
||||||
|
Self: LoadTransaction<Provider: BlockReaderIdExt>,
|
||||||
|
N: HlNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>,
|
||||||
|
{
|
||||||
|
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> {
|
||||||
|
self.inner.eth_api.signers()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes and recovers the transaction and submits it to the pool.
|
||||||
|
///
|
||||||
|
/// Returns the hash of the transaction.
|
||||||
|
async fn send_raw_transaction(&self, tx: Bytes) -> Result<B256, Self::Error> {
|
||||||
|
let recovered = recover_raw_transaction(&tx)?;
|
||||||
|
|
||||||
|
// broadcast raw transaction to subscribers if there is any.
|
||||||
|
self.inner.eth_api.broadcast_raw_transaction(tx);
|
||||||
|
|
||||||
|
let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
|
||||||
|
|
||||||
|
// submit the transaction to the pool with a `Local` origin
|
||||||
|
let hash = self
|
||||||
|
.pool()
|
||||||
|
.add_transaction(TransactionOrigin::Local, pool_transaction)
|
||||||
|
.await
|
||||||
|
.map_err(Self::Error::from_eth_err)?;
|
||||||
|
|
||||||
|
Ok(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
node::types::ReadPrecompileCalls,
|
node::types::ReadPrecompileCalls,
|
||||||
primitives::{HlBlock, HlBlockBody, HlPrimitives},
|
{HlBlock, HlBlockBody, HlPrimitives},
|
||||||
};
|
};
|
||||||
use alloy_consensus::BlockHeader;
|
use alloy_consensus::BlockHeader;
|
||||||
use alloy_primitives::Bytes;
|
use alloy_primitives::Bytes;
|
||||||
|
|||||||
Reference in New Issue
Block a user