Skip to content

Commit e116487

Browse files
ferranbtSolar MithrilSozinM
authored
Add engine_api v4 support (#159)
* Add engine_api v4 support * Fix lint * Remove print statements * Typo? * Update deps? * Update Kurtsosi * Fix stuff * bump alloys version * Remove isthmus integration test * Missing * Missing * Remove verbosity --------- Co-authored-by: Solar Mithril <[email protected]> Co-authored-by: Solar Mithril <[email protected]>
1 parent eca9266 commit e116487

File tree

8 files changed

+822
-356
lines changed

8 files changed

+822
-356
lines changed

Cargo.lock

Lines changed: 363 additions & 246 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7-
op-alloy-rpc-types-engine = "0.11.1"
8-
alloy-rpc-types-engine = "0.12.5"
9-
alloy-eips = { version = "0.12.5", features = ["serde"], optional = true }
7+
op-alloy-rpc-types-engine = "0.12.0"
8+
alloy-rpc-types-engine = "0.13.0"
9+
alloy-eips = { version = "0.13.0", features = ["serde"], optional = true }
1010
alloy-primitives = { version = "0.8.10", features = ["rand"] }
1111
tokio = { version = "1", features = ["full"] }
1212
tracing = "0.1.4"
@@ -49,14 +49,15 @@ time = { version = "0.3.36", features = ["macros", "formatting", "parsing"], opt
4949
lazy_static = {version = "1.5.0", optional = true }
5050

5151
[dev-dependencies]
52-
alloy-rpc-types-eth = "0.12.5"
52+
op-alloy-consensus = "0.12.0"
53+
alloy-rpc-types-eth = "0.13.0"
5354
anyhow = "1.0"
5455
assert_cmd = "2.0.10"
5556
predicates = "3.1.2"
5657
tokio-util = { version = "0.7.13" }
5758
nix = "0.15.0"
5859
bytes = "1.2"
59-
reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", rev = "v1.3.0" }
60+
reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", rev = "v1.3.7" }
6061
ctor = "0.4.1"
6162

6263
[features]

scripts/ci/kurtosis-params.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ optimism_package:
55
- participants:
66
- el_type: op-geth
77
el_builder_type: op-reth
8+
el_builder_image: "ghcr.io/paradigmxyz/op-reth:nightly"
89
cl_builder_type: op-node
910
network_params:
1011
network: "kurtosis"
1112
fund_dev_accounts: true
1213
seconds_per_slot: 2
1314
fjord_time_offset: 0
1415
granite_time_offset: 0
15-
# isthmus_time_offset: 0
16+
isthmus_time_offset: 5
1617
fund_dev_accounts: true
1718
mev_params:
1819
rollup_boost_image: "flashbots/rollup-boost:develop"

src/client/rpc.rs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::client::auth::{AuthClientLayer, AuthClientService};
2-
use crate::server::{EngineApiClient, PayloadSource};
3-
use alloy_primitives::B256;
2+
use crate::server::{
3+
EngineApiClient, NewPayload, OpExecutionPayloadEnvelope, PayloadSource, Version,
4+
};
5+
use alloy_primitives::{B256, Bytes};
46
use alloy_rpc_types_engine::{
57
ExecutionPayload, ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, JwtError, JwtSecret,
68
PayloadId, PayloadStatus,
@@ -10,7 +12,10 @@ use http::Uri;
1012
use jsonrpsee::http_client::transport::HttpBackend;
1113
use jsonrpsee::http_client::{HttpClient, HttpClientBuilder};
1214
use jsonrpsee::types::ErrorObjectOwned;
13-
use op_alloy_rpc_types_engine::{OpExecutionPayloadEnvelopeV3, OpPayloadAttributes};
15+
use op_alloy_rpc_types_engine::{
16+
OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4,
17+
OpPayloadAttributes,
18+
};
1419
use opentelemetry::trace::SpanKind;
1520
use paste::paste;
1621
use std::path::PathBuf;
@@ -213,6 +218,106 @@ impl RpcClient {
213218

214219
Ok(res)
215220
}
221+
222+
#[instrument(
223+
skip(self),
224+
err,
225+
fields(
226+
otel.kind = ?SpanKind::Client,
227+
target = self.payload_source.to_string(),
228+
url = %self.auth_rpc,
229+
%payload_id,
230+
)
231+
)]
232+
pub async fn get_payload_v4(
233+
&self,
234+
payload_id: PayloadId,
235+
) -> ClientResult<OpExecutionPayloadEnvelopeV4> {
236+
info!("Sending get_payload_v4 to {}", self.payload_source);
237+
Ok(self
238+
.auth_client
239+
.get_payload_v4(payload_id)
240+
.await
241+
.set_code()?)
242+
}
243+
244+
pub async fn get_payload(
245+
&self,
246+
payload_id: PayloadId,
247+
version: Version,
248+
) -> ClientResult<OpExecutionPayloadEnvelope> {
249+
match version {
250+
Version::V3 => Ok(OpExecutionPayloadEnvelope::V3(
251+
self.get_payload_v3(payload_id).await.set_code()?,
252+
)),
253+
Version::V4 => Ok(OpExecutionPayloadEnvelope::V4(
254+
self.get_payload_v4(payload_id).await.set_code()?,
255+
)),
256+
}
257+
}
258+
259+
#[instrument(
260+
skip_all,
261+
err,
262+
fields(
263+
otel.kind = ?SpanKind::Client,
264+
target = self.payload_source.to_string(),
265+
url = %self.auth_rpc,
266+
block_hash,
267+
code,
268+
)
269+
)]
270+
async fn new_payload_v4(
271+
&self,
272+
payload: OpExecutionPayloadV4,
273+
versioned_hashes: Vec<B256>,
274+
parent_beacon_block_root: B256,
275+
execution_requests: Vec<Bytes>,
276+
) -> ClientResult<PayloadStatus> {
277+
info!("Sending new_payload_v4 to {}", self.payload_source);
278+
let execution_payload = ExecutionPayload::from(payload.payload_inner.clone());
279+
let block_hash = execution_payload.block_hash();
280+
tracing::Span::current().record("block_hash", block_hash.to_string());
281+
282+
let res = self
283+
.auth_client
284+
.new_payload_v4(
285+
payload,
286+
versioned_hashes,
287+
parent_beacon_block_root,
288+
execution_requests,
289+
)
290+
.await
291+
.set_code()?;
292+
293+
if res.is_invalid() {
294+
return Err(RpcClientError::InvalidPayload(res.status.to_string()).set_code());
295+
}
296+
297+
Ok(res)
298+
}
299+
300+
pub async fn new_payload(&self, new_payload: NewPayload) -> ClientResult<PayloadStatus> {
301+
match new_payload {
302+
NewPayload::V3(new_payload) => {
303+
self.new_payload_v3(
304+
new_payload.payload,
305+
new_payload.versioned_hashes,
306+
new_payload.parent_beacon_block_root,
307+
)
308+
.await
309+
}
310+
NewPayload::V4(new_payload) => {
311+
self.new_payload_v4(
312+
new_payload.payload,
313+
new_payload.versioned_hashes,
314+
new_payload.parent_beacon_block_root,
315+
new_payload.execution_requests,
316+
)
317+
.await
318+
}
319+
}
320+
}
216321
}
217322

218323
/// Generates Clap argument structs with a prefix to create a unique namespace when specifying RPC client config via the CLI.

src/integration/mod.rs

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
use crate::client::auth::{AuthClientLayer, AuthClientService};
22
use crate::debug_api::DebugClient;
3-
use crate::server::EngineApiClient;
4-
use crate::server::PayloadSource;
3+
use crate::server::{EngineApiClient, OpExecutionPayloadEnvelope, Version};
4+
use crate::server::{NewPayload, PayloadSource};
55
use alloy_eips::BlockNumberOrTag;
6-
use alloy_primitives::B256;
7-
use alloy_rpc_types_engine::JwtSecret;
6+
use alloy_eips::eip2718::Encodable2718;
7+
use alloy_primitives::{B256, Bytes, TxKind, U256, address, hex};
8+
use alloy_rpc_types_engine::{ExecutionPayload, JwtSecret};
89
use alloy_rpc_types_engine::{
9-
ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadId,
10-
PayloadStatus, PayloadStatusEnum,
10+
ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadId, PayloadStatus,
11+
PayloadStatusEnum,
1112
};
13+
use bytes::BytesMut;
1214
use jsonrpsee::http_client::{HttpClient, transport::HttpBackend};
1315
use jsonrpsee::proc_macros::rpc;
1416
use lazy_static::lazy_static;
15-
use op_alloy_rpc_types_engine::{OpExecutionPayloadEnvelopeV3, OpPayloadAttributes};
17+
use op_alloy_consensus::TxDeposit;
18+
use op_alloy_rpc_types_engine::OpPayloadAttributes;
1619
use proxy::{DynHandlerFn, start_proxy_server};
1720
use serde_json::Value;
1821
use std::collections::{HashMap, HashSet};
@@ -469,6 +472,7 @@ pub struct EngineApi {
469472
pub engine_api_client: HttpClient<AuthClientService<HttpBackend>>,
470473
}
471474

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

486-
pub async fn get_payload_v3(
490+
pub async fn get_payload(
487491
&self,
492+
version: Version,
488493
payload_id: PayloadId,
489-
) -> eyre::Result<OpExecutionPayloadEnvelopeV3> {
490-
Ok(EngineApiClient::get_payload_v3(&self.engine_api_client, payload_id).await?)
494+
) -> eyre::Result<OpExecutionPayloadEnvelope> {
495+
match version {
496+
Version::V3 => Ok(OpExecutionPayloadEnvelope::V3(
497+
EngineApiClient::get_payload_v3(&self.engine_api_client, payload_id).await?,
498+
)),
499+
Version::V4 => Ok(OpExecutionPayloadEnvelope::V4(
500+
EngineApiClient::get_payload_v4(&self.engine_api_client, payload_id).await?,
501+
)),
502+
}
491503
}
492504

493-
pub async fn new_payload(
494-
&self,
495-
payload: ExecutionPayloadV3,
496-
versioned_hashes: Vec<B256>,
497-
parent_beacon_block_root: B256,
498-
) -> eyre::Result<PayloadStatus> {
499-
Ok(EngineApiClient::new_payload_v3(
500-
&self.engine_api_client,
501-
payload,
502-
versioned_hashes,
503-
parent_beacon_block_root,
504-
)
505-
.await?)
505+
pub async fn new_payload(&self, payload: NewPayload) -> eyre::Result<PayloadStatus> {
506+
match payload {
507+
NewPayload::V3(new_payload) => Ok(EngineApiClient::new_payload_v3(
508+
&self.engine_api_client,
509+
new_payload.payload,
510+
new_payload.versioned_hashes,
511+
new_payload.parent_beacon_block_root,
512+
)
513+
.await?),
514+
NewPayload::V4(new_payload) => Ok(EngineApiClient::new_payload_v4(
515+
&self.engine_api_client,
516+
new_payload.payload,
517+
new_payload.versioned_hashes,
518+
new_payload.parent_beacon_block_root,
519+
new_payload.execution_requests,
520+
)
521+
.await?),
522+
}
506523
}
507524

508525
pub async fn update_forkchoice(
@@ -686,6 +703,7 @@ pub struct SimpleBlockGenerator {
686703
engine_api: EngineApi,
687704
latest_hash: B256,
688705
timestamp: u64,
706+
version: Version,
689707
}
690708

691709
impl SimpleBlockGenerator {
@@ -695,6 +713,7 @@ impl SimpleBlockGenerator {
695713
engine_api,
696714
latest_hash: B256::ZERO, // temporary value
697715
timestamp: 0, // temporary value
716+
version: Version::V3,
698717
}
699718
}
700719

@@ -711,6 +730,14 @@ impl SimpleBlockGenerator {
711730
&mut self,
712731
empty_blocks: bool,
713732
) -> eyre::Result<(B256, PayloadSource)> {
733+
let txns = match self.version {
734+
Version::V4 => {
735+
let tx = create_deposit_tx();
736+
Some(vec![tx])
737+
}
738+
_ => None,
739+
};
740+
714741
// Submit forkchoice update with payload attributes for the next block
715742
let result = self
716743
.engine_api
@@ -725,7 +752,7 @@ impl SimpleBlockGenerator {
725752
prev_randao: B256::ZERO,
726753
suggested_fee_recipient: Default::default(),
727754
},
728-
transactions: None,
755+
transactions: txns,
729756
no_tx_pool: Some(empty_blocks),
730757
gas_limit: Some(10000000000),
731758
eip_1559_params: None,
@@ -739,23 +766,23 @@ impl SimpleBlockGenerator {
739766
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
740767
}
741768

742-
let payload = self.engine_api.get_payload_v3(payload_id).await?;
769+
let payload = self
770+
.engine_api
771+
.get_payload(self.version, payload_id)
772+
.await?;
743773

744774
// Submit the new payload to the node
745775
let validation_status = self
746776
.engine_api
747-
.new_payload(payload.execution_payload.clone(), vec![], B256::ZERO)
777+
.new_payload(NewPayload::from(payload.clone()))
748778
.await?;
749779

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

754-
let new_block_hash = payload
755-
.execution_payload
756-
.payload_inner
757-
.payload_inner
758-
.block_hash;
784+
let execution_payload = ExecutionPayload::from(payload);
785+
let new_block_hash = execution_payload.block_hash();
759786

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

765792
// Update internal state
766793
self.latest_hash = new_block_hash;
767-
self.timestamp = payload
768-
.execution_payload
769-
.payload_inner
770-
.payload_inner
771-
.timestamp;
794+
self.timestamp = execution_payload.timestamp();
772795

773796
// Check who built the block in the rollup-boost logs
774797
let block_creator = self
@@ -826,3 +849,25 @@ impl BlockBuilderCreatorValidator {
826849
Ok(None)
827850
}
828851
}
852+
853+
fn create_deposit_tx() -> Bytes {
854+
const ISTHMUS_DATA: &[u8] = &hex!(
855+
"098999be00000558000c5fc500000000000000030000000067a9f765000000000000002900000000000000000000000000000000000000000000000000000000006a6d09000000000000000000000000000000000000000000000000000000000000000172fcc8e8886636bdbe96ba0e4baab67ea7e7811633f52b52e8cf7a5123213b6f000000000000000000000000d3f2c5afb2d76f5579f326b0cd7da5f5a4126c3500004e2000000000000001f4"
856+
);
857+
858+
let deposit_tx = TxDeposit {
859+
source_hash: B256::default(),
860+
from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"),
861+
to: TxKind::Call(address!("4200000000000000000000000000000000000015")),
862+
mint: None,
863+
value: U256::default(),
864+
gas_limit: 210000,
865+
is_system_transaction: true,
866+
input: ISTHMUS_DATA.into(),
867+
};
868+
869+
let mut buffer_without_header = BytesMut::new();
870+
deposit_tx.encode_2718(&mut buffer_without_header);
871+
872+
buffer_without_header.to_vec().into()
873+
}

0 commit comments

Comments
 (0)