fix(op): fixes impl of eth api for OP (#9377)

This commit is contained in:
Emilia Hane
2024-07-09 20:49:00 +02:00
committed by GitHub
parent 7dab636b04
commit 63c8a82873
12 changed files with 417 additions and 91 deletions

6
Cargo.lock generated
View File

@ -7981,16 +7981,22 @@ name = "reth-optimism-rpc"
version = "1.0.1"
dependencies = [
"alloy-primitives",
"jsonrpsee",
"parking_lot 0.12.3",
"reth-chainspec",
"reth-errors",
"reth-evm",
"reth-evm-optimism",
"reth-primitives",
"reth-provider",
"reth-rpc-eth-api",
"reth-rpc-eth-types",
"reth-rpc-server-types",
"reth-rpc-types",
"reth-tasks",
"reth-transaction-pool",
"revm",
"thiserror",
"tokio",
]

View File

@ -6,25 +6,47 @@ rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Ethereum RPC implementation for optimism."
[lints]
workspace = true
[dependencies]
# reth
reth-chainspec.workspace = true
reth-errors.workspace = true
reth-evm-optimism.workspace = true
reth-evm.workspace = true
reth-primitives.workspace = true
reth-provider.workspace = true
reth-rpc-eth-api.workspace = true
reth-rpc-eth-types.workspace = true
reth-rpc-server-types.workspace = true
reth-rpc-types.workspace = true
reth-chainspec.workspace = true
reth-provider.workspace = true
reth-tasks = { workspace = true, features = ["rayon"] }
reth-transaction-pool.workspace = true
# ethereum
alloy-primitives.workspace = true
revm.workspace = true
# async
parking_lot.workspace = true
tokio.workspace = true
tokio.workspace = true
# rpc
jsonrpsee.workspace = true
# misc
thiserror.workspace = true
[features]
optimism = [
"reth-chainspec/optimism",
"reth-evm-optimism/optimism",
"reth-primitives/optimism",
"reth-provider/optimism",
"reth-rpc-eth-api/optimism",
"reth-rpc-eth-types/optimism",
"revm/optimism"
]

View File

@ -0,0 +1,31 @@
//! RPC errors specific to OP.
use jsonrpsee::types::ErrorObject;
use reth_rpc_eth_types::EthApiError;
use reth_rpc_server_types::result::internal_rpc_err;
use reth_rpc_types::ToRpcError;
/// Optimism specific errors, that extend [`EthApiError`].
#[derive(Debug, thiserror::Error)]
pub enum OpEthApiError {
/// Thrown when calculating L1 gas fee.
#[error("failed to calculate l1 gas fee")]
L1BlockFeeError,
/// Thrown when calculating L1 gas used
#[error("failed to calculate l1 gas used")]
L1BlockGasError,
}
impl ToRpcError for OpEthApiError {
fn to_rpc_error(&self) -> ErrorObject<'static> {
match self {
Self::L1BlockFeeError | Self::L1BlockGasError => internal_rpc_err(self.to_string()),
}
}
}
impl From<OpEthApiError> for EthApiError {
fn from(err: OpEthApiError) -> Self {
Self::other(err)
}
}

View File

@ -0,0 +1,75 @@
//! Loads and formats OP block RPC response.
use reth_primitives::TransactionMeta;
use reth_provider::{BlockReaderIdExt, HeaderProvider};
use reth_rpc_eth_api::helpers::{EthApiSpec, EthBlocks, LoadBlock, LoadReceipt, LoadTransaction};
use reth_rpc_eth_types::{EthResult, EthStateCache, ReceiptBuilder};
use reth_rpc_types::{AnyTransactionReceipt, BlockId};
use crate::{op_receipt_fields, OpEthApi};
impl<Eth> EthBlocks for OpEthApi<Eth>
where
Eth: EthBlocks + EthApiSpec + LoadTransaction,
{
fn provider(&self) -> impl HeaderProvider {
EthBlocks::provider(&self.inner)
}
async fn block_receipts(
&self,
block_id: BlockId,
) -> EthResult<Option<Vec<AnyTransactionReceipt>>>
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 block = block.unseal();
let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok();
let receipts = block
.body
.into_iter()
.zip(receipts.iter())
.enumerate()
.map(|(idx, (ref tx, receipt))| {
let meta = TransactionMeta {
tx_hash: tx.hash,
index: idx as u64,
block_hash,
block_number,
base_fee,
excess_blob_gas,
timestamp,
};
let optimism_tx_meta =
self.build_op_tx_meta(tx, l1_block_info.clone(), timestamp)?;
ReceiptBuilder::new(tx, meta, receipt, &receipts).map(|builder| {
op_receipt_fields(builder, tx, receipt, optimism_tx_meta).build()
})
})
.collect::<EthResult<Vec<_>>>();
return receipts.map(Some)
}
Ok(None)
}
}
impl<Eth: LoadBlock> LoadBlock for OpEthApi<Eth> {
fn provider(&self) -> impl BlockReaderIdExt {
LoadBlock::provider(&self.inner)
}
fn cache(&self) -> &EthStateCache {
self.inner.cache()
}
}

View File

@ -1,26 +1,26 @@
//! OP-Reth `eth_` endpoint implementation.
pub mod receipt;
pub mod transaction;
mod block;
mod pending_block;
use std::{future::Future, sync::Arc};
use alloy_primitives::{Address, U64};
use reth_chainspec::ChainInfo;
use reth_chainspec::{ChainInfo, ChainSpec};
use reth_errors::RethResult;
use reth_evm::ConfigureEvm;
use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory,
use reth_provider::{BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StateProviderFactory};
use reth_rpc_eth_api::helpers::{
Call, EthApiSpec, EthCall, EthFees, EthState, LoadFee, LoadState, SpawnBlocking, Trace,
};
use reth_rpc_eth_api::{
helpers::{
Call, EthApiSpec, EthBlocks, EthCall, EthFees, EthSigner, EthState, EthTransactions,
LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction,
SpawnBlocking, Trace,
},
RawTransactionForwarder,
};
use reth_rpc_eth_types::{EthStateCache, PendingBlock};
use reth_rpc_eth_types::EthStateCache;
use reth_rpc_types::SyncStatus;
use reth_tasks::{pool::BlockingTaskPool, TaskSpawner};
use reth_transaction_pool::TransactionPool;
use std::future::Future;
use tokio::sync::{AcquireError, Mutex, OwnedSemaphorePermit};
use tokio::sync::{AcquireError, OwnedSemaphorePermit};
/// OP-Reth `Eth` API implementation.
///
@ -68,35 +68,9 @@ impl<Eth: EthApiSpec> EthApiSpec for OpEthApi<Eth> {
fn sync_status(&self) -> RethResult<SyncStatus> {
self.inner.sync_status()
}
}
impl<Eth: LoadBlock> LoadBlock for OpEthApi<Eth> {
fn provider(&self) -> impl BlockReaderIdExt {
LoadBlock::provider(&self.inner)
}
fn cache(&self) -> &reth_rpc_eth_types::EthStateCache {
self.inner.cache()
}
}
impl<Eth: LoadPendingBlock> LoadPendingBlock for OpEthApi<Eth> {
fn provider(
&self,
) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory {
self.inner.provider()
}
fn pool(&self) -> impl TransactionPool {
self.inner.pool()
}
fn pending_block(&self) -> &Mutex<Option<PendingBlock>> {
self.inner.pending_block()
}
fn evm_config(&self) -> &impl ConfigureEvm {
self.inner.evm_config()
fn chain_spec(&self) -> Arc<ChainSpec> {
self.inner.chain_spec()
}
}
@ -123,12 +97,6 @@ impl<Eth: SpawnBlocking> SpawnBlocking for OpEthApi<Eth> {
}
}
impl<Eth: LoadReceipt> LoadReceipt for OpEthApi<Eth> {
fn cache(&self) -> &EthStateCache {
self.inner.cache()
}
}
impl<Eth: LoadFee> LoadFee for OpEthApi<Eth> {
fn provider(&self) -> impl reth_provider::BlockIdReader + HeaderProvider + ChainSpecProvider {
LoadFee::provider(&self.inner)
@ -171,42 +139,6 @@ impl<Eth: LoadState> LoadState for OpEthApi<Eth> {
}
}
impl<Eth: LoadTransaction> LoadTransaction for OpEthApi<Eth> {
type Pool = Eth::Pool;
fn provider(&self) -> impl reth_provider::TransactionsProvider {
LoadTransaction::provider(&self.inner)
}
fn cache(&self) -> &EthStateCache {
LoadTransaction::cache(&self.inner)
}
fn pool(&self) -> &Self::Pool {
LoadTransaction::pool(&self.inner)
}
}
impl<Eth: EthTransactions> EthTransactions for OpEthApi<Eth> {
fn provider(&self) -> impl BlockReaderIdExt {
EthTransactions::provider(&self.inner)
}
fn raw_tx_forwarder(&self) -> Option<std::sync::Arc<dyn RawTransactionForwarder>> {
self.inner.raw_tx_forwarder()
}
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner>>> {
self.inner.signers()
}
}
impl<Eth: EthBlocks> EthBlocks for OpEthApi<Eth> {
fn provider(&self) -> impl HeaderProvider {
EthBlocks::provider(&self.inner)
}
}
impl<Eth: EthState> EthState for OpEthApi<Eth> {
fn max_proof_window(&self) -> u64 {
self.inner.max_proof_window()

View File

@ -0,0 +1,72 @@
//! Loads OP pending block for a RPC response.
use reth_primitives::{
revm_primitives::{BlockEnv, ExecutionResult},
BlockNumber, Receipt, TransactionSignedEcRecovered, B256,
};
use reth_provider::{ChainSpecProvider, ExecutionOutcome};
use reth_rpc_eth_api::helpers::LoadPendingBlock;
use reth_rpc_eth_types::PendingBlock;
use crate::OpEthApi;
impl<Eth> LoadPendingBlock for OpEthApi<Eth>
where
Eth: LoadPendingBlock,
{
#[inline]
fn provider(
&self,
) -> impl reth_provider::BlockReaderIdExt
+ reth_provider::EvmEnvProvider
+ reth_provider::ChainSpecProvider
+ reth_provider::StateProviderFactory {
self.inner.provider()
}
#[inline]
fn pool(&self) -> impl reth_transaction_pool::TransactionPool {
self.inner.pool()
}
#[inline]
fn pending_block(&self) -> &tokio::sync::Mutex<Option<PendingBlock>> {
self.inner.pending_block()
}
#[inline]
fn evm_config(&self) -> &impl reth_evm::ConfigureEvm {
self.inner.evm_config()
}
fn assemble_receipt(
&self,
tx: &TransactionSignedEcRecovered,
result: ExecutionResult,
cumulative_gas_used: u64,
) -> Receipt {
Receipt {
tx_type: tx.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.into_logs().into_iter().map(Into::into).collect(),
deposit_nonce: None,
deposit_receipt_version: None,
}
}
fn receipts_root(
&self,
_block_env: &BlockEnv,
execution_outcome: &ExecutionOutcome,
block_number: BlockNumber,
) -> B256 {
execution_outcome
.optimism_receipts_root_slow(
block_number,
self.provider().chain_spec().as_ref(),
_block_env.timestamp.to::<u64>(),
)
.expect("Block is present")
}
}

View File

@ -0,0 +1,64 @@
//! Loads and formats OP receipt RPC response.
use reth_primitives::{Receipt, TransactionMeta, TransactionSigned};
use reth_rpc_eth_api::helpers::{EthApiSpec, LoadReceipt, LoadTransaction};
use reth_rpc_eth_types::{EthApiError, EthResult, EthStateCache, ReceiptBuilder};
use reth_rpc_types::{AnyTransactionReceipt, OptimismTransactionReceiptFields};
use crate::{OpEthApi, OptimismTxMeta};
impl<Eth> LoadReceipt for OpEthApi<Eth>
where
Eth: LoadReceipt + EthApiSpec + LoadTransaction,
{
#[inline]
fn cache(&self) -> &EthStateCache {
LoadReceipt::cache(&self.inner)
}
async fn build_transaction_receipt(
&self,
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
) -> EthResult<AnyTransactionReceipt> {
let (block, receipts) = LoadReceipt::cache(self)
.get_block_and_receipts(meta.block_hash)
.await?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block = block.unseal();
let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok();
let optimism_tx_meta = self.build_op_tx_meta(&tx, l1_block_info, block.timestamp)?;
let resp_builder = ReceiptBuilder::new(&tx, meta, &receipt, &receipts)?;
let resp_builder = op_receipt_fields(resp_builder, &tx, &receipt, optimism_tx_meta);
Ok(resp_builder.build())
}
}
/// Applies OP specific fields to a receipt.
pub fn op_receipt_fields(
resp_builder: ReceiptBuilder,
tx: &TransactionSigned,
receipt: &Receipt,
optimism_tx_meta: OptimismTxMeta,
) -> ReceiptBuilder {
let mut op_fields = OptimismTransactionReceiptFields::default();
if tx.is_deposit() {
op_fields.deposit_nonce = receipt.deposit_nonce.map(reth_primitives::U64::from);
op_fields.deposit_receipt_version =
receipt.deposit_receipt_version.map(reth_primitives::U64::from);
} else if let Some(l1_block_info) = optimism_tx_meta.l1_block_info {
op_fields.l1_fee = optimism_tx_meta.l1_fee;
op_fields.l1_gas_used = optimism_tx_meta.l1_data_gas.map(|dg| {
dg + l1_block_info.l1_fee_overhead.unwrap_or_default().saturating_to::<u128>()
});
op_fields.l1_fee_scalar = Some(f64::from(l1_block_info.l1_base_fee_scalar) / 1_000_000.0);
op_fields.l1_gas_price = Some(l1_block_info.l1_base_fee.saturating_to());
}
resp_builder.add_other_fields(op_fields.into())
}

View File

@ -0,0 +1,107 @@
//! Loads and formats OP transaction RPC response.
use reth_evm_optimism::RethL1BlockInfo;
use reth_primitives::TransactionSigned;
use reth_provider::BlockReaderIdExt;
use reth_rpc_eth_api::{
helpers::{EthApiSpec, EthSigner, EthTransactions, LoadTransaction},
RawTransactionForwarder,
};
use reth_rpc_eth_types::{EthResult, EthStateCache};
use revm::L1BlockInfo;
use crate::{OpEthApi, OpEthApiError};
impl<Eth: EthTransactions> EthTransactions for OpEthApi<Eth> {
fn provider(&self) -> impl BlockReaderIdExt {
EthTransactions::provider(&self.inner)
}
fn raw_tx_forwarder(&self) -> Option<std::sync::Arc<dyn RawTransactionForwarder>> {
self.inner.raw_tx_forwarder()
}
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner>>> {
self.inner.signers()
}
}
impl<Eth: LoadTransaction> LoadTransaction for OpEthApi<Eth> {
type Pool = Eth::Pool;
fn provider(&self) -> impl reth_provider::TransactionsProvider {
LoadTransaction::provider(&self.inner)
}
fn cache(&self) -> &EthStateCache {
LoadTransaction::cache(&self.inner)
}
fn pool(&self) -> &Self::Pool {
LoadTransaction::pool(&self.inner)
}
}
/// L1 fee and data gas for a transaction, along with the L1 block info.
#[derive(Debug, Default, Clone)]
pub struct OptimismTxMeta {
/// The L1 block info.
pub l1_block_info: Option<L1BlockInfo>,
/// The L1 fee for the block.
pub l1_fee: Option<u128>,
/// The L1 data gas for the block.
pub l1_data_gas: Option<u128>,
}
impl OptimismTxMeta {
/// Creates a new [`OptimismTxMeta`].
pub const fn new(
l1_block_info: Option<L1BlockInfo>,
l1_fee: Option<u128>,
l1_data_gas: Option<u128>,
) -> Self {
Self { l1_block_info, l1_fee, l1_data_gas }
}
}
impl<Eth> OpEthApi<Eth>
where
Eth: EthApiSpec + LoadTransaction,
{
/// Builds [`OptimismTxMeta`] object using the provided [`TransactionSigned`], L1 block
/// info and block timestamp. The [`L1BlockInfo`] is used to calculate the l1 fee and l1 data
/// gas for the transaction. If the [`L1BlockInfo`] is not provided, the meta info will be
/// empty.
pub fn build_op_tx_meta(
&self,
tx: &TransactionSigned,
l1_block_info: Option<L1BlockInfo>,
block_timestamp: u64,
) -> EthResult<OptimismTxMeta> {
let Some(l1_block_info) = l1_block_info else { return Ok(OptimismTxMeta::default()) };
let (l1_fee, l1_data_gas) = if !tx.is_deposit() {
let envelope_buf = tx.envelope_encoded();
let inner_l1_fee = l1_block_info
.l1_tx_data_fee(
&self.inner.chain_spec(),
block_timestamp,
&envelope_buf,
tx.is_deposit(),
)
.map_err(|_| OpEthApiError::L1BlockFeeError)?;
let inner_l1_data_gas = l1_block_info
.l1_data_gas(&self.inner.chain_spec(), block_timestamp, &envelope_buf)
.map_err(|_| OpEthApiError::L1BlockGasError)?;
(
Some(inner_l1_fee.saturating_to::<u128>()),
Some(inner_l1_data_gas.saturating_to::<u128>()),
)
} else {
(None, None)
};
Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas))
}
}

View File

@ -5,7 +5,13 @@
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
// The `optimism` feature must be enabled to use this crate.
#![cfg(feature = "optimism")]
pub mod error;
pub mod eth;
pub use error::OpEthApiError;
pub use eth::{receipt::op_receipt_fields, transaction::OptimismTxMeta, OpEthApi};

View File

@ -210,7 +210,7 @@ pub struct PeerStats {
/// https://github.com/ethereum-optimism/optimism/blob/8dd17a7b114a7c25505cd2e15ce4e3d0f7e3f7c1/op-node/node/api.go#L114
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "optimism"))]
#[cfg_attr(feature = "client", rpc(server, client, namespace = "optimism"))]
pub trait OptimismApi {
pub trait OpEthApi {
/// Get the output root at a specific block.
#[method(name = "outputAtBlock")]
async fn optimism_output_at_block(

View File

@ -1,7 +1,9 @@
//! Loads chain metadata.
use std::sync::Arc;
use futures::Future;
use reth_chainspec::ChainInfo;
use reth_chainspec::{ChainInfo, ChainSpec};
use reth_errors::RethResult;
use reth_primitives::{Address, U64};
use reth_rpc_types::SyncStatus;
@ -28,4 +30,7 @@ pub trait EthApiSpec: Send + Sync {
/// Returns the [`SyncStatus`] of the network
fn sync_status(&self) -> RethResult<SyncStatus>;
/// Returns the configured [`ChainSpec`].
fn chain_spec(&self) -> Arc<ChainSpec>;
}

View File

@ -1,4 +1,6 @@
use reth_chainspec::ChainInfo;
use std::sync::Arc;
use reth_chainspec::{ChainInfo, ChainSpec};
use reth_errors::{RethError, RethResult};
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
@ -62,4 +64,8 @@ where
};
Ok(status)
}
fn chain_spec(&self) -> Arc<ChainSpec> {
self.inner.provider().chain_spec()
}
}