Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/optimism/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ derive_more = { workspace = true, features = ["constructor"] }
reth-metrics.workspace = true
metrics.workspace = true

# enum
strum.workspace = true

[dev-dependencies]
reth-optimism-chainspec.workspace = true
alloy-op-hardforks.workspace = true
Expand Down
220 changes: 119 additions & 101 deletions crates/optimism/rpc/src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Historical proofs RPC server implementation for `debug_` namespace.

use crate::state::OpStateProviderFactory;
use crate::{
metrics::{DebugApiExtMetrics, DebugApis},
state::OpStateProviderFactory,
};
use alloy_consensus::BlockHeader;
use alloy_eips::{BlockId, BlockNumberOrTag};
use alloy_primitives::B256;
Expand Down Expand Up @@ -109,6 +112,7 @@ pub struct DebugApiExtInner<Eth: FullEthApi, Storage, Provider, EvmConfig, Attrs
task_spawner: Box<dyn TaskSpawner>,
semaphore: Semaphore,
_attrs: PhantomData<Attrs>,
metrics: DebugApiExtMetrics,
}

impl<Eth, P, Provider, EvmConfig, Attrs> DebugApiExtInner<Eth, P, Provider, EvmConfig, Attrs>
Expand All @@ -134,6 +138,7 @@ where
task_spawner,
semaphore: Semaphore::new(3),
_attrs: PhantomData,
metrics: DebugApiExtMetrics::new(),
}
}
}
Expand Down Expand Up @@ -187,114 +192,127 @@ where
parent_block_hash: B256,
attributes: Attrs::RpcPayloadAttributes,
) -> RpcResult<ExecutionWitness> {
let _permit = self.inner.semaphore.acquire().await;

let parent_header = self.parent_header(parent_block_hash).to_rpc_result()?;

let (tx, rx) = oneshot::channel();
let this = self.inner.clone();
self.inner.task_spawner.spawn_blocking(Box::pin(async move {
let result = async {
let parent_hash = parent_header.hash();
let attributes = Attrs::try_new(parent_hash, attributes, 3)
.map_err(PayloadBuilderError::other)?;

let config = PayloadConfig { parent_header: Arc::new(parent_header), attributes };
let ctx = OpPayloadBuilderCtx {
evm_config: this.evm_config.clone(),
chain_spec: this.provider.chain_spec(),
config,
cancel: Default::default(),
best_payload: Default::default(),
builder_config: Default::default(),
};

let state_provider = this
.state_provider_factory
.state_provider(Some(BlockId::Hash(parent_hash.into())))
.await
.map_err(PayloadBuilderError::other)?;

let builder = OpBuilder::new(|_| {
NoopPayloadTransactions::<
OpPooledTx2<
<N as OpPayloadPrimitives>::_TX,
op_alloy_consensus::OpPooledTransaction,
>,
>::default()
});

builder.witness(state_provider, &ctx).map_err(PayloadBuilderError::other)
};

let _ = tx.send(result.await);
}));

rx.await
.map_err(|err| internal_rpc_err(err.to_string()))?
.map_err(|err| internal_rpc_err(err.to_string()))
self.inner
.metrics
.record_operation_async(DebugApis::DebugExecutePayload, async {
let _permit = self.inner.semaphore.acquire().await;

let parent_header = self.parent_header(parent_block_hash).to_rpc_result()?;

let (tx, rx) = oneshot::channel();
let this = self.inner.clone();
self.inner.task_spawner.spawn_blocking(Box::pin(async move {
let result = async {
let parent_hash = parent_header.hash();
let attributes = Attrs::try_new(parent_hash, attributes, 3)
.map_err(PayloadBuilderError::other)?;

let config =
PayloadConfig { parent_header: Arc::new(parent_header), attributes };
let ctx = OpPayloadBuilderCtx {
evm_config: this.evm_config.clone(),
chain_spec: this.provider.chain_spec(),
config,
cancel: Default::default(),
best_payload: Default::default(),
builder_config: Default::default(),
};

let state_provider = this
.state_provider_factory
.state_provider(Some(BlockId::Hash(parent_hash.into())))
.await
.map_err(PayloadBuilderError::other)?;

let builder = OpBuilder::new(|_| {
NoopPayloadTransactions::<
OpPooledTx2<
<N as OpPayloadPrimitives>::_TX,
op_alloy_consensus::OpPooledTransaction,
>,
>::default()
});

builder.witness(state_provider, &ctx).map_err(PayloadBuilderError::other)
};

let _ = tx.send(result.await);
}));

rx.await
.map_err(|err| internal_rpc_err(err.to_string()))?
.map_err(|err| internal_rpc_err(err.to_string()))
})
.await
}

async fn execution_witness(&self, block_id: BlockNumberOrTag) -> RpcResult<ExecutionWitness> {
let _permit = self.inner.semaphore.acquire().await;

let block = self
.inner
.eth_api
.recovered_block(block_id.into())
.await?
.ok_or(EthApiError::HeaderNotFound(block_id.into()))?;
self.inner
.metrics
.record_operation_async(DebugApis::DebugExecutionWitness, async {
let _permit = self.inner.semaphore.acquire().await;

let this = self.inner.clone();
let block_number = block.header().number();
let block = self
.inner
.eth_api
.recovered_block(block_id.into())
.await?
.ok_or(EthApiError::HeaderNotFound(block_id.into()))?;

let state_provider = this
.state_provider_factory
.state_provider(Some(BlockId::Number(block.parent_num_hash().number.into())))
.await
.map_err(EthApiError::from)?;
let db = StateProviderDatabase::new(&state_provider);
let block_executor = this.eth_api.evm_config().executor(db);
let this = self.inner.clone();
let block_number = block.header().number();

let mut witness_record = ExecutionWitnessRecord::default();
let state_provider = this
.state_provider_factory
.state_provider(Some(BlockId::Number(block.parent_num_hash().number.into())))
.await
.map_err(EthApiError::from)?;
let db = StateProviderDatabase::new(&state_provider);
let block_executor = this.eth_api.evm_config().executor(db);

let mut witness_record = ExecutionWitnessRecord::default();

let _ = block_executor
.execute_with_state_closure(&block, |statedb: &State<_>| {
witness_record.record_executed_state(statedb);
})
.map_err(EthApiError::from)?;

let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } =
witness_record;

let state = state_provider
.witness(Default::default(), hashed_state)
.map_err(EthApiError::from)?;
let mut exec_witness =
ExecutionWitness { state, codes, keys, ..Default::default() };

let smallest = match lowest_block_number {
Some(smallest) => smallest,
None => {
// Return only the parent header, if there were no calls to the
// BLOCKHASH opcode.
block_number.saturating_sub(1)
}
};

let _ = block_executor
.execute_with_state_closure(&block, |statedb: &State<_>| {
witness_record.record_executed_state(statedb);
let range = smallest..block_number;
exec_witness.headers = self
.inner
.provider
.headers_range(range)
.map_err(EthApiError::from)?
.into_iter()
.map(|header| {
let mut serialized_header = Vec::new();
header.encode(&mut serialized_header);
serialized_header.into()
})
.collect();

Ok(exec_witness)
})
.map_err(EthApiError::from)?;

let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } =
witness_record;

let state =
state_provider.witness(Default::default(), hashed_state).map_err(EthApiError::from)?;
let mut exec_witness = ExecutionWitness { state, codes, keys, ..Default::default() };

let smallest = match lowest_block_number {
Some(smallest) => smallest,
None => {
// Return only the parent header, if there were no calls to the
// BLOCKHASH opcode.
block_number.saturating_sub(1)
}
};

let range = smallest..block_number;
exec_witness.headers = self
.inner
.provider
.headers_range(range)
.map_err(EthApiError::from)?
.into_iter()
.map(|header| {
let mut serialized_header = Vec::new();
header.encode(&mut serialized_header);
serialized_header.into()
})
.collect();

Ok(exec_witness)
.await
}

async fn proofs_sync_status(&self) -> RpcResult<ProofsSyncStatus> {
Expand Down
97 changes: 97 additions & 0 deletions crates/optimism/rpc/src/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//! RPC metrics unique for OP-stack.

use alloy_primitives::map::HashMap;
use core::time::Duration;
use metrics::{Counter, Histogram};
use reth_metrics::Metrics;
use std::time::Instant;
use strum::{EnumCount, EnumIter, IntoEnumIterator};

/// Optimism sequencer metrics
#[derive(Metrics, Clone)]
Expand Down Expand Up @@ -36,3 +39,97 @@ pub struct EthApiExtMetrics {
/// Total number of failures handling `eth_getProof` requests
pub(crate) get_proof_failures: Counter,
}

/// Types of debug apis
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, EnumCount, EnumIter)]
pub enum DebugApis {
/// `DebugExecutePayload` Api
DebugExecutePayload,
/// `DebugExecutionWitness` Api
DebugExecutionWitness,
}

impl DebugApis {
/// Returns the operation as a string for metrics labels.
pub const fn as_str(&self) -> &'static str {
match self {
Self::DebugExecutePayload => "debug_execute_payload",
Self::DebugExecutionWitness => "debug_execution_witness",
}
}
}

/// Metrics for Debug API extension calls.
#[derive(Debug)]
pub struct DebugApiExtMetrics {
/// Per-api metrics handles
apis: HashMap<DebugApis, DebugApiExtRpcMetrics>,
}

impl DebugApiExtMetrics {
/// Initializes new `DebugApiExtMetrics`
pub fn new() -> Self {
let mut apis = HashMap::default();
for api in DebugApis::iter() {
apis.insert(api, DebugApiExtRpcMetrics::new_with_labels(&[("api", api.as_str())]));
}
Self { apis }
}

/// Record a Debug API call async (tracks latency, requests, success, failures).
pub async fn record_operation_async<F, T, E>(&self, api: DebugApis, f: F) -> Result<T, E>
where
F: Future<Output = Result<T, E>>,
{
if let Some(metrics) = self.apis.get(&api) {
metrics.record_async(f).await
} else {
f.await
}
}
}

impl Default for DebugApiExtMetrics {
fn default() -> Self {
Self::new()
}
}

/// Optimism Debug API extension metrics
#[derive(Metrics, Clone)]
#[metrics(scope = "optimism_rpc.debug_api_ext")]
pub struct DebugApiExtRpcMetrics {
/// End-to-end time to handle this API call
pub(crate) latency: Histogram,

/// Total number of requests for this API
pub(crate) requests: Counter,

/// Total number of successful responses for this API
pub(crate) successful_responses: Counter,

/// Total number of failures for this API
pub(crate) failures: Counter,
}

impl DebugApiExtRpcMetrics {
/// Record rpc api call async.
async fn record_async<T, E, F>(&self, f: F) -> Result<T, E>
where
F: Future<Output = Result<T, E>>,
{
let start = Instant::now();
let result = f.await;

self.latency.record(start.elapsed().as_secs_f64());
self.requests.increment(1);

if result.is_ok() {
self.successful_responses.increment(1);
} else {
self.failures.increment(1);
}

result
}
}
Loading
Loading