Make it compilable (still bunch to fix!)

This commit is contained in:
sprites0
2025-06-17 18:06:53 -04:00
parent 5e531b7260
commit 821bf7a775
38 changed files with 3652 additions and 31 deletions

View File

@ -9,7 +9,7 @@ static GENESIS_HASH: B256 =
b256!("d8fcc13b6a195b88b7b2da3722ff6cad767b13a8c1e9ffb1c73aa9d216d895f0");
/// The Hyperliqiud Mainnet spec
pub static HL_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
pub fn hl_mainnet() -> ChainSpec {
ChainSpec {
chain: Chain::from_named(NamedChain::Hyperliquid),
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,
..Default::default()
}
.into()
});
}
/// Empty genesis header for Hyperliquid Mainnet.
///

View File

@ -1,5 +1,6 @@
use super::hl::HL_MAINNET;
use reth_chainspec::ChainSpec;
use crate::chainspec::HlChainSpec;
use super::hl::hl_mainnet;
use reth_cli::chainspec::ChainSpecParser;
use std::sync::Arc;
@ -11,21 +12,23 @@ pub const SUPPORTED_CHAINS: &[&str] = &["mainnet"];
pub struct HlChainSpecParser;
impl ChainSpecParser for HlChainSpecParser {
type ChainSpec = ChainSpec;
type ChainSpec = HlChainSpec;
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)
}
}
/// Clap value parser for [`ChainSpec`]s.
/// Clap value parser for [`HlChainSpec`]s.
///
/// 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 {
"mainnet" => Ok(HL_MAINNET.clone().into()),
"mainnet" => Ok(Arc::new(HlChainSpec {
inner: hl_mainnet(),
})),
_ => Err(eyre::eyre!("Unsupported chain: {}", s)),
}
}

39
src/evm/api/builder.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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));
}
}

View File

@ -5,7 +5,7 @@ use reth_chainspec::ForkCondition;
use reth_ethereum_forks::{hardfork, ChainHardforks, EthereumHardfork, 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`].
/// 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.
pub fn bsc_mainnet() -> ChainHardforks {
/// Hl mainnet list of hardforks.
pub fn hl_mainnet() -> ChainHardforks {
ChainHardforks::new(vec![
(EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)),
@ -86,14 +86,10 @@ impl HlHardfork {
EthereumHardfork::MuirGlacier.boxed(),
ForkCondition::Block(0),
),
(
EthereumHardfork::Berlin.boxed(),
ForkCondition::Block(31302048),
),
(
EthereumHardfork::London.boxed(),
ForkCondition::Block(31302048),
),
(EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::London.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Shanghai.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Cancun.boxed(), ForkCondition::Block(0)),
(Self::V1.boxed(), ForkCondition::Block(0)),
(Self::V2.boxed(), ForkCondition::Block(0)),
(Self::V3.boxed(), ForkCondition::Block(0)),

View File

@ -1,6 +1,6 @@
mod chainspec;
mod consensus;
pub mod chainspec;
pub mod consensus;
mod evm;
mod hardforks;
mod node;
pub use node::primitives;
pub mod node;
pub use node::primitives::{HlBlock, HlBlockBody, HlPrimitives};

View File

@ -1,7 +1,7 @@
use clap::{Args, Parser};
use reth::builder::NodeHandle;
use reth_hl::{
chainspec::HlChainSpecParser,
chainspec::parser::HlChainSpecParser,
node::{cli::Cli, HlNode},
};

View File

@ -1,7 +1,7 @@
use crate::{
hardforks::HlHardforks,
node::HlNode,
primitives::{HlBlock, HlBlockBody, HlPrimitives},
{HlBlock, HlBlockBody, HlPrimitives},
};
use reth::{
api::FullNodeTypes,

117
src/node/engine.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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(())
}

View 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)
}
}

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

View 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
View 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
View 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
View 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))
}
}

View 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())
}
}

View 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;

View 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())
}
}

View 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
View 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 }) })
}
}

View 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)
}
}

View File

@ -1,6 +1,6 @@
use crate::{
node::types::ReadPrecompileCalls,
primitives::{HlBlock, HlBlockBody, HlPrimitives},
{HlBlock, HlBlockBody, HlPrimitives},
};
use alloy_consensus::BlockHeader;
use alloy_primitives::Bytes;