-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* cargo init + add reth dependency - failing to build * Copy paste ci lint workflow from reth repo * Copy example for CLI extension * Move Cli extension and rpc implementation to separate modules * Add implementation (not compiling) * Fix build errors * Change fork branch and rename MyRethCli to ValidationCli * Cargo fmt * Add test rpc payload * Add README * fix test data * Extend README * Add Disclaimer * Remove references to txpool example * Change heading of README
- Loading branch information
Showing
9 changed files
with
605 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
on: | ||
pull_request: | ||
merge_group: | ||
push: | ||
branches: [main] | ||
|
||
env: | ||
RUSTFLAGS: -D warnings | ||
CARGO_TERM_COLOR: always | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||
cancel-in-progress: true | ||
|
||
name: ci | ||
jobs: | ||
lint: | ||
name: code lint | ||
runs-on: ubuntu-20.04 | ||
steps: | ||
- name: Checkout sources | ||
uses: actions/checkout@v3 | ||
- name: Install toolchain | ||
uses: dtolnay/rust-toolchain@nightly | ||
with: | ||
components: rustfmt, clippy | ||
- uses: Swatinem/rust-cache@v2 | ||
with: | ||
cache-on-failure: true | ||
|
||
- name: cargo check | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: check | ||
args: --all --all-features --benches --tests | ||
|
||
- name: cargo fmt | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: fmt | ||
args: --all --check | ||
|
||
- name: cargo clippy | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: clippy | ||
args: --all --all-features --benches --tests | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "reth-block-validator" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
async-trait = "0.1.73" | ||
clap = "4.4.5" | ||
derivative = "2.2.0" | ||
eyre = "0.6.8" | ||
jsonrpsee = "0.20.1" | ||
reth = { git = "https://github.com/ultrasoundmoney/reth-block-validator", branch = "changes-to-enable-validation-api-extension" } | ||
serde = "1.0.188" | ||
serde-this-or-that = "0.4.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Reth based Builder Payload Validator Api | ||
Reth rpc extension to add an endpoint for validation of builder submissions as received by a relay. | ||
|
||
## Get Started | ||
Run extended reth full node with the added rpc endpoint with: | ||
`RUST_LOG=info cargo run -- node --full --metrics 127.0.0.1:9001 --http --enable-ext` | ||
|
||
## Test it | ||
While there are no automated tests yet you can execute a manual test using the provided testdata: | ||
`curl --location 'localhost:8545/' --header 'Content-Type: application/json' --data @test/data/rpc_payload.json` | ||
|
||
## Further Reading | ||
- [Guide to custom api development based on reth](https://www.libevm.com/2023/09/01/reth-custom-api/) | ||
- [Official example for adding rpc namespace](https://github.com/paradigmxyz/reth/blob/main/examples/additional-rpc-namespace-in-cli/src/main.rs) | ||
|
||
## Disclaimer | ||
This code is being provided as is. No guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of the code. It has not been audited and as such there can be no assurance it will work as intended, and users may experience delays, failures, errors, omissions or loss of transmitted information. | ||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use reth::{ | ||
cli::{ | ||
config::RethRpcConfig, | ||
ext::{RethCliExt, RethNodeCommandConfig}, | ||
}, | ||
network::{NetworkInfo, Peers}, | ||
providers::{ | ||
AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, | ||
ChangeSetReader, EvmEnvProvider, StateProviderFactory, | ||
}, | ||
rpc::builder::{RethModuleRegistry, TransportRpcModules}, | ||
tasks::TaskSpawner, | ||
transaction_pool::TransactionPool, | ||
}; | ||
|
||
use crate::rpc::ValidationApiServer; | ||
use crate::ValidationApi; | ||
|
||
/// The type that tells the reth CLI what extensions to use | ||
pub struct ValidationCliExt; | ||
|
||
impl RethCliExt for ValidationCliExt { | ||
/// This tells the reth CLI to install the `validation` rpc namespace via `RethCliValidationApi` | ||
type Node = RethCliValidationApi; | ||
} | ||
|
||
/// Our custom cli args extension that adds one flag to reth default CLI. | ||
#[derive(Debug, Clone, Copy, Default, clap::Args)] | ||
pub struct RethCliValidationApi { | ||
/// CLI flag to enable the validation extension namespace | ||
#[clap(long)] | ||
pub enable_ext: bool, | ||
} | ||
|
||
impl RethNodeCommandConfig for RethCliValidationApi { | ||
// This is the entrypoint for the CLI to extend the RPC server with custom rpc namespaces. | ||
fn extend_rpc_modules<Conf, Provider, Pool, Network, Tasks, Events>( | ||
&mut self, | ||
_config: &Conf, | ||
registry: &mut RethModuleRegistry<Provider, Pool, Network, Tasks, Events>, | ||
modules: &mut TransportRpcModules, | ||
) -> eyre::Result<()> | ||
where | ||
Conf: RethRpcConfig, | ||
Provider: BlockReaderIdExt | ||
+ AccountReader | ||
+ StateProviderFactory | ||
+ EvmEnvProvider | ||
+ ChainSpecProvider | ||
+ ChangeSetReader | ||
+ Clone | ||
+ Unpin | ||
+ 'static, | ||
Pool: TransactionPool + Clone + 'static, | ||
Network: NetworkInfo + Peers + Clone + 'static, | ||
Tasks: TaskSpawner + Clone + 'static, | ||
Events: CanonStateSubscriptions + Clone + 'static, | ||
{ | ||
if !self.enable_ext { | ||
return Ok(()); | ||
} | ||
|
||
// here we get the configured pool type from the CLI. | ||
let provider = registry.provider().clone(); | ||
let ext = ValidationApi::new(provider); | ||
|
||
// now we merge our extension namespace into all configured transports | ||
modules.merge_configured(ext.into_rpc())?; | ||
|
||
println!("validation extension enabled"); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
//! Reth RPC extension to add endpoint for builder payload validation | ||
//! | ||
//! Run with | ||
//! | ||
//! ```not_rust | ||
//! RUST_LOG=info cargo run -- node --full --metrics 127.0.0.1:9001 --http --enable-ext | ||
//! ``` | ||
//! | ||
//! This installs an additional RPC method that can be queried using the provided sample rpc | ||
//! payload | ||
//! | ||
//! ```sh | ||
//! curl --location 'localhost:8545/' --header 'Content-Type: application/json' --data @test/data/rpc_payload.json | ||
//! ``` | ||
use clap::Parser; | ||
use reth::cli::Cli; | ||
use std::sync::Arc; | ||
|
||
mod cli_ext; | ||
use cli_ext::ValidationCliExt; | ||
|
||
mod rpc; | ||
use rpc::ValidationApiInner; | ||
|
||
fn main() { | ||
Cli::<ValidationCliExt>::parse().run().unwrap(); | ||
} | ||
|
||
/// The type that implements the `validation` rpc namespace trait | ||
pub struct ValidationApi<Provider> { | ||
inner: Arc<ValidationApiInner<Provider>>, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
use async_trait::async_trait; | ||
use jsonrpsee::{core::RpcResult, proc_macros::rpc}; | ||
|
||
use reth::consensus_common::validation::full_validation; | ||
use reth::providers::{ | ||
AccountReader, BlockReaderIdExt, ChainSpecProvider, ChangeSetReader, HeaderProvider, | ||
StateProviderFactory, WithdrawalsProvider, | ||
}; | ||
use reth::rpc::result::ToRpcResultExt; | ||
use reth::rpc::types_compat::engine::payload::try_into_sealed_block; | ||
|
||
use std::sync::Arc; | ||
|
||
use crate::ValidationApi; | ||
|
||
mod types; | ||
use types::ExecutionPayloadValidation; | ||
|
||
/// trait interface for a custom rpc namespace: `validation` | ||
/// | ||
/// This defines an additional namespace where all methods are configured as trait functions. | ||
#[rpc(server, namespace = "validationExt")] | ||
#[async_trait] | ||
pub trait ValidationApi { | ||
/// Validates a block submitted to the relay | ||
#[method(name = "validateBuilderSubmissionV1")] | ||
async fn validate_builder_submission_v1( | ||
&self, | ||
execution_payload: ExecutionPayloadValidation, | ||
) -> RpcResult<()>; | ||
} | ||
|
||
impl<Provider> ValidationApi<Provider> { | ||
/// The provider that can interact with the chain. | ||
pub fn provider(&self) -> &Provider { | ||
&self.inner.provider | ||
} | ||
|
||
/// Create a new instance of the [ValidationApi] | ||
pub fn new(provider: Provider) -> Self { | ||
let inner = Arc::new(ValidationApiInner { provider }); | ||
Self { inner } | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl<Provider> ValidationApiServer for ValidationApi<Provider> | ||
where | ||
Provider: BlockReaderIdExt | ||
+ ChainSpecProvider | ||
+ ChangeSetReader | ||
+ StateProviderFactory | ||
+ HeaderProvider | ||
+ AccountReader | ||
+ WithdrawalsProvider | ||
+ 'static, | ||
{ | ||
/// Validates a block submitted to the relay | ||
async fn validate_builder_submission_v1( | ||
&self, | ||
execution_payload: ExecutionPayloadValidation, | ||
) -> RpcResult<()> { | ||
let block = try_into_sealed_block(execution_payload.into(), None).map_ok_or_rpc_err()?; | ||
let chain_spec = self.provider().chain_spec(); | ||
full_validation(&block, self.provider(), &chain_spec).map_ok_or_rpc_err() | ||
} | ||
} | ||
|
||
impl<Provider> std::fmt::Debug for ValidationApi<Provider> { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
f.debug_struct("ValidationApi").finish_non_exhaustive() | ||
} | ||
} | ||
|
||
impl<Provider> Clone for ValidationApi<Provider> { | ||
fn clone(&self) -> Self { | ||
Self { | ||
inner: Arc::clone(&self.inner), | ||
} | ||
} | ||
} | ||
|
||
pub struct ValidationApiInner<Provider> { | ||
/// The provider that can interact with the chain. | ||
provider: Provider, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use derivative::Derivative; | ||
use reth::primitives::{Address, Bloom, Bytes, H256, U256}; | ||
use reth::rpc::types::{ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, Withdrawal}; | ||
use serde::{Deserialize, Serialize}; | ||
use serde_this_or_that::as_u64; | ||
|
||
/// Structure to deserialize execution payloads sent according to the builder api spec | ||
/// Numeric fields deserialized as decimals (unlike crate::eth::engine::ExecutionPayload) | ||
#[derive(Derivative)] | ||
#[derivative(Debug)] | ||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
#[allow(missing_docs)] | ||
pub struct ExecutionPayloadValidation { | ||
pub parent_hash: H256, | ||
pub fee_recipient: Address, | ||
pub state_root: H256, | ||
pub receipts_root: H256, | ||
pub logs_bloom: Bloom, | ||
pub prev_randao: H256, | ||
#[serde(deserialize_with = "as_u64")] | ||
pub block_number: u64, | ||
#[serde(deserialize_with = "as_u64")] | ||
pub gas_limit: u64, | ||
#[serde(deserialize_with = "as_u64")] | ||
pub gas_used: u64, | ||
#[serde(deserialize_with = "as_u64")] | ||
pub timestamp: u64, | ||
pub extra_data: Bytes, | ||
pub base_fee_per_gas: U256, | ||
pub block_hash: H256, | ||
#[derivative(Debug = "ignore")] | ||
pub transactions: Vec<Bytes>, | ||
pub withdrawals: Vec<WithdrawalValidation>, | ||
} | ||
|
||
/// Withdrawal object with numbers deserialized as decimals | ||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||
pub struct WithdrawalValidation { | ||
/// Monotonically increasing identifier issued by consensus layer. | ||
#[serde(deserialize_with = "as_u64")] | ||
pub index: u64, | ||
/// Index of validator associated with withdrawal. | ||
#[serde(deserialize_with = "as_u64")] | ||
pub validator_index: u64, | ||
/// Target address for withdrawn ether. | ||
pub address: Address, | ||
/// Value of the withdrawal in gwei. | ||
#[serde(deserialize_with = "as_u64")] | ||
pub amount: u64, | ||
} | ||
|
||
impl From<ExecutionPayloadValidation> for ExecutionPayload { | ||
fn from(val: ExecutionPayloadValidation) -> Self { | ||
ExecutionPayload::V2(ExecutionPayloadV2 { | ||
payload_inner: ExecutionPayloadV1 { | ||
parent_hash: val.parent_hash, | ||
fee_recipient: val.fee_recipient, | ||
state_root: val.state_root, | ||
receipts_root: val.receipts_root, | ||
logs_bloom: val.logs_bloom, | ||
prev_randao: val.prev_randao, | ||
block_number: val.block_number.into(), | ||
gas_limit: val.gas_limit.into(), | ||
gas_used: val.gas_used.into(), | ||
timestamp: val.timestamp.into(), | ||
extra_data: val.extra_data, | ||
base_fee_per_gas: val.base_fee_per_gas, | ||
block_hash: val.block_hash, | ||
transactions: val.transactions, | ||
}, | ||
withdrawals: val.withdrawals.into_iter().map(|w| w.into()).collect(), | ||
}) | ||
} | ||
} | ||
|
||
impl From<WithdrawalValidation> for Withdrawal { | ||
fn from(val: WithdrawalValidation) -> Self { | ||
Withdrawal { | ||
index: val.index, | ||
validator_index: val.validator_index, | ||
address: val.address, | ||
amount: val.amount, | ||
} | ||
} | ||
} |
Oops, something went wrong.