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
609 changes: 363 additions & 246 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ version = "0.1.0"
edition = "2024"

[dependencies]
op-alloy-rpc-types-engine = "0.11.1"
alloy-rpc-types-engine = "0.12.5"
alloy-eips = { version = "0.12.5", features = ["serde"], optional = true }
op-alloy-rpc-types-engine = "0.12.0"
alloy-rpc-types-engine = "0.13.0"
alloy-eips = { version = "0.13.0", features = ["serde"], optional = true }
alloy-primitives = { version = "0.8.10", features = ["rand"] }
tokio = { version = "1", features = ["full"] }
tracing = "0.1.4"
Expand Down Expand Up @@ -49,14 +49,15 @@ time = { version = "0.3.36", features = ["macros", "formatting", "parsing"], opt
lazy_static = {version = "1.5.0", optional = true }

[dev-dependencies]
alloy-rpc-types-eth = "0.12.5"
op-alloy-consensus = "0.12.0"
alloy-rpc-types-eth = "0.13.0"
anyhow = "1.0"
assert_cmd = "2.0.10"
predicates = "3.1.2"
tokio-util = { version = "0.7.13" }
nix = "0.15.0"
bytes = "1.2"
reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", rev = "v1.3.0" }
reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", rev = "v1.3.7" }
ctor = "0.4.1"

[features]
Expand Down
3 changes: 2 additions & 1 deletion scripts/ci/kurtosis-params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ optimism_package:
- participants:
- el_type: op-geth
el_builder_type: op-reth
el_builder_image: "ghcr.io/paradigmxyz/op-reth:nightly"
cl_builder_type: op-node
network_params:
network: "kurtosis"
fund_dev_accounts: true
seconds_per_slot: 2
fjord_time_offset: 0
granite_time_offset: 0
# isthmus_time_offset: 0
isthmus_time_offset: 5
fund_dev_accounts: true
mev_params:
rollup_boost_image: "flashbots/rollup-boost:develop"
Expand Down
111 changes: 108 additions & 3 deletions src/client/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::client::auth::{AuthClientLayer, AuthClientService};
use crate::server::{EngineApiClient, PayloadSource};
use alloy_primitives::B256;
use crate::server::{
EngineApiClient, NewPayload, OpExecutionPayloadEnvelope, PayloadSource, Version,
};
use alloy_primitives::{B256, Bytes};
use alloy_rpc_types_engine::{
ExecutionPayload, ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, JwtError, JwtSecret,
PayloadId, PayloadStatus,
Expand All @@ -10,7 +12,10 @@ use http::Uri;
use jsonrpsee::http_client::transport::HttpBackend;
use jsonrpsee::http_client::{HttpClient, HttpClientBuilder};
use jsonrpsee::types::ErrorObjectOwned;
use op_alloy_rpc_types_engine::{OpExecutionPayloadEnvelopeV3, OpPayloadAttributes};
use op_alloy_rpc_types_engine::{
OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4,
OpPayloadAttributes,
};
use opentelemetry::trace::SpanKind;
use paste::paste;
use std::path::PathBuf;
Expand Down Expand Up @@ -213,6 +218,106 @@ impl RpcClient {

Ok(res)
}

#[instrument(
skip(self),
err,
fields(
otel.kind = ?SpanKind::Client,
target = self.payload_source.to_string(),
url = %self.auth_rpc,
%payload_id,
)
)]
pub async fn get_payload_v4(
&self,
payload_id: PayloadId,
) -> ClientResult<OpExecutionPayloadEnvelopeV4> {
info!("Sending get_payload_v4 to {}", self.payload_source);
Ok(self
.auth_client
.get_payload_v4(payload_id)
.await
.set_code()?)
}

pub async fn get_payload(
&self,
payload_id: PayloadId,
version: Version,
) -> ClientResult<OpExecutionPayloadEnvelope> {
match version {
Version::V3 => Ok(OpExecutionPayloadEnvelope::V3(
self.get_payload_v3(payload_id).await.set_code()?,
)),
Version::V4 => Ok(OpExecutionPayloadEnvelope::V4(
self.get_payload_v4(payload_id).await.set_code()?,
)),
}
}

#[instrument(
skip_all,
err,
fields(
otel.kind = ?SpanKind::Client,
target = self.payload_source.to_string(),
url = %self.auth_rpc,
block_hash,
code,
)
)]
async fn new_payload_v4(
&self,
payload: OpExecutionPayloadV4,
versioned_hashes: Vec<B256>,
parent_beacon_block_root: B256,
execution_requests: Vec<Bytes>,
) -> ClientResult<PayloadStatus> {
info!("Sending new_payload_v4 to {}", self.payload_source);
let execution_payload = ExecutionPayload::from(payload.payload_inner.clone());
let block_hash = execution_payload.block_hash();
tracing::Span::current().record("block_hash", block_hash.to_string());

let res = self
.auth_client
.new_payload_v4(
payload,
versioned_hashes,
parent_beacon_block_root,
execution_requests,
)
.await
.set_code()?;

if res.is_invalid() {
return Err(RpcClientError::InvalidPayload(res.status.to_string()).set_code());
}

Ok(res)
}

pub async fn new_payload(&self, new_payload: NewPayload) -> ClientResult<PayloadStatus> {
match new_payload {
NewPayload::V3(new_payload) => {
self.new_payload_v3(
new_payload.payload,
new_payload.versioned_hashes,
new_payload.parent_beacon_block_root,
)
.await
}
NewPayload::V4(new_payload) => {
self.new_payload_v4(
new_payload.payload,
new_payload.versioned_hashes,
new_payload.parent_beacon_block_root,
new_payload.execution_requests,
)
.await
}
}
}
}

/// Generates Clap argument structs with a prefix to create a unique namespace when specifying RPC client config via the CLI.
Expand Down
117 changes: 81 additions & 36 deletions src/integration/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
use crate::client::auth::{AuthClientLayer, AuthClientService};
use crate::debug_api::DebugClient;
use crate::server::EngineApiClient;
use crate::server::PayloadSource;
use crate::server::{EngineApiClient, OpExecutionPayloadEnvelope, Version};
use crate::server::{NewPayload, PayloadSource};
use alloy_eips::BlockNumberOrTag;
use alloy_primitives::B256;
use alloy_rpc_types_engine::JwtSecret;
use alloy_eips::eip2718::Encodable2718;
use alloy_primitives::{B256, Bytes, TxKind, U256, address, hex};
use alloy_rpc_types_engine::{ExecutionPayload, JwtSecret};
use alloy_rpc_types_engine::{
ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadId,
PayloadStatus, PayloadStatusEnum,
ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadId, PayloadStatus,
PayloadStatusEnum,
};
use bytes::BytesMut;
use jsonrpsee::http_client::{HttpClient, transport::HttpBackend};
use jsonrpsee::proc_macros::rpc;
use lazy_static::lazy_static;
use op_alloy_rpc_types_engine::{OpExecutionPayloadEnvelopeV3, OpPayloadAttributes};
use op_alloy_consensus::TxDeposit;
use op_alloy_rpc_types_engine::OpPayloadAttributes;
use proxy::{DynHandlerFn, start_proxy_server};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -469,6 +472,7 @@ pub struct EngineApi {
pub engine_api_client: HttpClient<AuthClientService<HttpBackend>>,
}

// TODO: Use client/rpc.rs instead
impl EngineApi {
pub fn new(url: &str, secret: &str) -> Result<Self, Box<dyn std::error::Error>> {
let secret_layer = AuthClientLayer::new(JwtSecret::from_str(secret)?);
Expand All @@ -483,26 +487,39 @@ impl EngineApi {
})
}

pub async fn get_payload_v3(
pub async fn get_payload(
&self,
version: Version,
payload_id: PayloadId,
) -> eyre::Result<OpExecutionPayloadEnvelopeV3> {
Ok(EngineApiClient::get_payload_v3(&self.engine_api_client, payload_id).await?)
) -> eyre::Result<OpExecutionPayloadEnvelope> {
match version {
Version::V3 => Ok(OpExecutionPayloadEnvelope::V3(
EngineApiClient::get_payload_v3(&self.engine_api_client, payload_id).await?,
)),
Version::V4 => Ok(OpExecutionPayloadEnvelope::V4(
EngineApiClient::get_payload_v4(&self.engine_api_client, payload_id).await?,
)),
}
}

pub async fn new_payload(
&self,
payload: ExecutionPayloadV3,
versioned_hashes: Vec<B256>,
parent_beacon_block_root: B256,
) -> eyre::Result<PayloadStatus> {
Ok(EngineApiClient::new_payload_v3(
&self.engine_api_client,
payload,
versioned_hashes,
parent_beacon_block_root,
)
.await?)
pub async fn new_payload(&self, payload: NewPayload) -> eyre::Result<PayloadStatus> {
match payload {
NewPayload::V3(new_payload) => Ok(EngineApiClient::new_payload_v3(
&self.engine_api_client,
new_payload.payload,
new_payload.versioned_hashes,
new_payload.parent_beacon_block_root,
)
.await?),
NewPayload::V4(new_payload) => Ok(EngineApiClient::new_payload_v4(
&self.engine_api_client,
new_payload.payload,
new_payload.versioned_hashes,
new_payload.parent_beacon_block_root,
new_payload.execution_requests,
)
.await?),
}
}

pub async fn update_forkchoice(
Expand Down Expand Up @@ -686,6 +703,7 @@ pub struct SimpleBlockGenerator {
engine_api: EngineApi,
latest_hash: B256,
timestamp: u64,
version: Version,
}

impl SimpleBlockGenerator {
Expand All @@ -695,6 +713,7 @@ impl SimpleBlockGenerator {
engine_api,
latest_hash: B256::ZERO, // temporary value
timestamp: 0, // temporary value
version: Version::V3,
}
}

Expand All @@ -711,6 +730,14 @@ impl SimpleBlockGenerator {
&mut self,
empty_blocks: bool,
) -> eyre::Result<(B256, PayloadSource)> {
let txns = match self.version {
Version::V4 => {
let tx = create_deposit_tx();
Some(vec![tx])
}
_ => None,
};

// Submit forkchoice update with payload attributes for the next block
let result = self
.engine_api
Expand All @@ -725,7 +752,7 @@ impl SimpleBlockGenerator {
prev_randao: B256::ZERO,
suggested_fee_recipient: Default::default(),
},
transactions: None,
transactions: txns,
no_tx_pool: Some(empty_blocks),
gas_limit: Some(10000000000),
eip_1559_params: None,
Expand All @@ -739,23 +766,23 @@ impl SimpleBlockGenerator {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}

let payload = self.engine_api.get_payload_v3(payload_id).await?;
let payload = self
.engine_api
.get_payload(self.version, payload_id)
.await?;

// Submit the new payload to the node
let validation_status = self
.engine_api
.new_payload(payload.execution_payload.clone(), vec![], B256::ZERO)
.new_payload(NewPayload::from(payload.clone()))
.await?;

if validation_status.status != PayloadStatusEnum::Valid {
return Err(eyre::eyre!("Invalid payload status"));
}

let new_block_hash = payload
.execution_payload
.payload_inner
.payload_inner
.block_hash;
let execution_payload = ExecutionPayload::from(payload);
let new_block_hash = execution_payload.block_hash();

// Update the chain's head
self.engine_api
Expand All @@ -764,11 +791,7 @@ impl SimpleBlockGenerator {

// Update internal state
self.latest_hash = new_block_hash;
self.timestamp = payload
.execution_payload
.payload_inner
.payload_inner
.timestamp;
self.timestamp = execution_payload.timestamp();

// Check who built the block in the rollup-boost logs
let block_creator = self
Expand Down Expand Up @@ -826,3 +849,25 @@ impl BlockBuilderCreatorValidator {
Ok(None)
}
}

fn create_deposit_tx() -> Bytes {
const ISTHMUS_DATA: &[u8] = &hex!(
"098999be00000558000c5fc500000000000000030000000067a9f765000000000000002900000000000000000000000000000000000000000000000000000000006a6d09000000000000000000000000000000000000000000000000000000000000000172fcc8e8886636bdbe96ba0e4baab67ea7e7811633f52b52e8cf7a5123213b6f000000000000000000000000d3f2c5afb2d76f5579f326b0cd7da5f5a4126c3500004e2000000000000001f4"
);

let deposit_tx = TxDeposit {
source_hash: B256::default(),
from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"),
to: TxKind::Call(address!("4200000000000000000000000000000000000015")),
mint: None,
value: U256::default(),
gas_limit: 210000,
is_system_transaction: true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this deposit tx work? had to set is_system_transaction: false for it to work in op-rbuilder

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our integration tests are running in an old (two weeks) version of Reth

input: ISTHMUS_DATA.into(),
};

let mut buffer_without_header = BytesMut::new();
deposit_tx.encode_2718(&mut buffer_without_header);

buffer_without_header.to_vec().into()
}
Loading
Loading