Skip to content

feat: simplify ExecutionMode #198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
12 changes: 0 additions & 12 deletions src/debug_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,5 @@ mod tests {
// Verify again with get_execution_mode
let status = client.get_execution_mode().await.unwrap();
assert_eq!(status.execution_mode, ExecutionMode::Enabled);

// Test setting fallback execution mode
let result = client
.set_execution_mode(ExecutionMode::Fallback)
.await
.unwrap();

assert_eq!(result.execution_mode, ExecutionMode::Fallback);

// Verify again with get_execution_mode
let status = client.get_execution_mode().await.unwrap();
assert_eq!(status.execution_mode, ExecutionMode::Fallback);
}
}
126 changes: 66 additions & 60 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,23 +140,16 @@ pub enum ExecutionMode {
DryRun,
// Not sending any requests
Disabled,
// Defaulting to op-geth payloads
Fallback,
}

impl ExecutionMode {
fn is_get_payload_enabled(&self) -> bool {
// get payload is only enabled in 'enabled' mode
matches!(self, ExecutionMode::Enabled)
fn is_dry_run(&self) -> bool {
matches!(self, ExecutionMode::DryRun)
}

fn is_disabled(&self) -> bool {
matches!(self, ExecutionMode::Disabled)
}

fn is_fallback_enabled(&self) -> bool {
matches!(self, ExecutionMode::Fallback)
}
}

pub struct RollupBoostServer {
Expand Down Expand Up @@ -610,66 +603,79 @@ impl RollupBoostServer {
payload_id: PayloadId,
version: Version,
) -> RpcResult<OpExecutionPayloadEnvelope> {
let execution_mode = self.execution_mode();
let l2_client_future = self.l2_client.get_payload(payload_id, version);
let builder_client_future = Box::pin(async move {
let execution_mode = self.execution_mode();
if !execution_mode.is_get_payload_enabled() {
info!(message = "dry run mode is enabled, skipping get payload builder call");

// We are in dry run mode, so we do not want to call the builder.
return Err(ErrorObject::owned(
INVALID_REQUEST_CODE,
"Dry run mode is enabled",
None::<String>,
));
}

if let Some(cause) = self.payload_trace_context.trace_id(&payload_id) {
tracing::Span::current().follows_from(cause);
}
// if mode is `disabled` dont await the builder payload
let (payload, context) = if execution_mode.is_disabled() {
let result = match l2_client_future.await {
Ok(payload) => {
self.probes.set_health(Health::Healthy);
Ok((payload, PayloadSource::L2))
}
Err(e) => {
self.probes.set_health(Health::ServiceUnavailable);
Err(e)
}
};
result
} else {
// if mode is `enabled` or `dry_run` await the builder + L2 payload
let builder_client_future = Box::pin(async move {
if let Some(cause) = self.payload_trace_context.trace_id(&payload_id) {
tracing::Span::current().follows_from(cause);
}

if !self.payload_trace_context.has_builder_payload(&payload_id) {
// block builder won't build a block without attributes
info!(message = "builder has no payload, skipping get_payload call to builder");
return Ok(None);
}
if !self.payload_trace_context.has_builder_payload(&payload_id) {
// block builder won't build a block without attributes
info!(message = "builder has no payload, skipping get_payload call to builder");
return RpcResult::Ok(None);
}

let builder = self.builder_client.clone();
let payload = builder.get_payload(payload_id, version).await?;
let builder = self.builder_client.clone();
let payload = builder.get_payload(payload_id, version).await?;

// Send the payload to the local execution engine with engine_newPayload to validate the block from the builder.
// Otherwise, we do not want to risk the network to a halt since op-node will not be able to propose the block.
// If validation fails, return the local block since that one has already been validated.
let _ = self
.l2_client
.new_payload(NewPayload::from(payload.clone()))
.await?;
// Send the payload to the local execution engine with engine_newPayload to validate the block from the builder.
// Otherwise, we do not want to risk the network to a halt since op-node will not be able to propose the block.
// If validation fails, return the local block since that one has already been validated.
let _ = self
.l2_client
.new_payload(NewPayload::from(payload.clone()))
.await?;

Ok(Some(payload))
});
Ok(Some(payload))
});

let (l2_payload, builder_payload) = tokio::join!(l2_client_future, builder_client_future);
let (payload, context) = match (builder_payload, l2_payload) {
(Ok(Some(builder)), Ok(l2_payload)) => {
// builder successfully returned a payload
self.probes.set_health(Health::Healthy);
if self.execution_mode().is_fallback_enabled() {
// Default to op-geth's payload
Ok((l2_payload, PayloadSource::L2))
} else {
let (l2_payload, builder_payload) =
tokio::join!(l2_client_future, builder_client_future);
let result = match (builder_payload, l2_payload, execution_mode.is_dry_run()) {
(Ok(Some(_)), Ok(l2), true) => {
// if builder and l2 is okay and `is_dry_run`, then always return L2 payload with signal `Healthy`
self.probes.set_health(Health::Healthy);
Ok((l2, PayloadSource::L2))
}
(_, Ok(l2), true) => {
// if builder has failed/errored and l2 is okay and `is_dry_run`, then always return L2 payload, but signal `PartialContent`
self.probes.set_health(Health::PartialContent);
Ok((l2, PayloadSource::L2))
}
(Ok(Some(builder)), Ok(_), false) => {
// if builder and L2 is okay and `is_dry_run` is false, then always return builder payload i.e. `Enabled` Mode with signal `Healthy`
self.probes.set_health(Health::Healthy);
Ok((builder, PayloadSource::Builder))
}
}
(_, Ok(l2)) => {
// builder failed to return a payload
self.probes.set_health(Health::PartialContent);
Ok((l2, PayloadSource::L2))
}
(_, Err(e)) => {
// builder and l2 failed to return a payload
self.probes.set_health(Health::ServiceUnavailable);
Err(e)
}
(_, Ok(l2), false) => {
// builder failed to return/errored and `l2_payload` is ok and `is_dry_run` is false, then always return L2 payload with signal `PartialContent`
self.probes.set_health(Health::PartialContent);
Ok((l2, PayloadSource::L2))
}
(_, Err(e), _) => {
// l2 failed to return a payload
self.probes.set_health(Health::ServiceUnavailable);
Err(e)
}
};
result
}?;

tracing::Span::current().record("payload_source", context.to_string());
Expand Down
Loading