Skip to content
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

feat(rpc): add engine api abstraction, rm AttributesValidator #102

Merged
merged 9 commits into from
Oct 18, 2024
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
46 changes: 30 additions & 16 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ op-alloy-genesis = { git = "https://github.com/alloy-rs/op-alloy", branch = "mai
op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", branch = "main" }
op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", branch = "main" }
op-alloy-rpc-jsonrpsee = { git = "https://github.com/alloy-rs/op-alloy", branch = "main" }
op-alloy-provider = { git = "https://github.com/alloy-rs/op-alloy", branch = "main" }

[workspace.dependencies]
# Workspace
Expand Down Expand Up @@ -60,6 +61,7 @@ alloy = { version = "0.4.2", features = [
"network",
"ssz"
] }
alloy-transport-http = { version = "0.4.2", default-features = false, features = ["jwt-auth"] }
alloy-primitives = { version = "0.8", features = ["serde"] }
alloy-rlp = "0.3"

Expand All @@ -70,6 +72,7 @@ op-alloy-genesis = { version = "0.4.0", default-features = false }
op-alloy-rpc-types = { version = "0.4.0", default-features = false }
op-alloy-rpc-types-engine = { version = "0.4.0", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.4.0", features = ["client"] }
op-alloy-provider = { version = "0.4.0", default-features = false }

# Tokio
tokio = { version = "1.21", default-features = false }
Expand Down Expand Up @@ -119,6 +122,8 @@ parking_lot = "0.12.3"
unsigned-varint = "0.8.0"
rand = { version = "0.8.5", features = ["small_rng", "alloc", "getrandom"], default-features = false }
url = "2.5.2"
tower = "0.4"
http-body-util = "0.1.2"

[workspace.metadata.docs.rs]
all-features = true
Expand Down
12 changes: 10 additions & 2 deletions crates/rollup/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ kona-derive.workspace = true
op-alloy-genesis.workspace = true
op-alloy-protocol.workspace = true
op-alloy-rpc-types-engine.workspace = true
op-alloy-provider.workspace = true
superchain = { workspace = true, default-features = false }

# Alloy Dependencies
alloy = { workspace = true, features = ["hyper"] }
alloy-transport-http.workspace = true

# Reth Dependencies
reth.workspace = true
reth-exex.workspace = true
Expand All @@ -32,13 +37,16 @@ metrics-exporter-prometheus = { version = "0.15.3", features = ["http-listener"]

# Misc
url.workspace = true
reqwest.workspace = true
serde_json.workspace = true
eyre.workspace = true
tracing.workspace = true
clap.workspace = true
async-trait.workspace = true
tokio.workspace = true
futures.workspace = true
alloy.workspace = true
hashbrown.workspace = true
tower.workspace = true
http-body-util.workspace = true

[dev-dependencies]
reqwest.workspace = true
2 changes: 1 addition & 1 deletion crates/rollup/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub struct HeraArgsExt {
/// happens by sending the `new_payload` to the API and expecting a VALID response.
#[clap(
long = "hera.validation-mode",
default_value = "trusted",
default_value = "engine-api",
requires_ifs([("engine-api", "l2_engine_api_url"), ("engine-api", "l2_engine_jwt_secret")]),
)]
pub validation_mode: ValidationMode,
Expand Down
85 changes: 53 additions & 32 deletions crates/rollup/src/driver/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Rollup Node Driver

use alloy::eips::eip1898::BlockNumHash;
use reth::rpc::types::engine::JwtSecret;
use std::{fmt::Debug, sync::Arc};

use eyre::{bail, eyre, Result};
Expand All @@ -14,16 +15,13 @@ use kona_providers_alloy::{AlloyChainProvider, AlloyL2ChainProvider, OnlineBlobP
use kona_providers_local::{DurableBlobProvider, InMemoryChainProvider, LayeredBlobProvider};
use op_alloy_genesis::RollupConfig;
use op_alloy_protocol::{BlockInfo, L2BlockInfo};
use reth::rpc::types::engine::JwtSecret;
use reth_exex::ExExContext;
use reth_node_api::FullNodeComponents;
use tracing::{debug, error, info, trace, warn};

use crate::{
cli::ValidationMode,
new_rollup_pipeline,
validator::{EngineApiValidator, TrustedValidator},
AttributesValidator, HeraArgsExt, RollupPipeline,
cli::ValidationMode, new_rollup_pipeline, validator::TrustedPayloadValidator, Engine,
HeraArgsExt, RollupPipeline,
};

mod context;
Expand All @@ -48,7 +46,9 @@ pub struct Driver<DC, CP, BP> {
/// Cursor to keep track of the L2 tip
cursor: SyncCursor,
/// The validator to verify newly derived L2 attributes
validator: Box<dyn AttributesValidator>,
trusted_validator: Option<TrustedPayloadValidator>,
/// The engine API handler
engine: Engine,
}

impl<N: FullNodeComponents> Driver<ExExHeraContext<N>, InMemoryChainProvider, LayeredBlobProvider> {
Expand Down Expand Up @@ -77,7 +77,7 @@ impl Driver<StandaloneHeraContext, AlloyChainProvider, DurableBlobProvider> {
.with_primary(args.l1_beacon_client_url.as_str().trim_end_matches('/').to_string())
.with_fallback(
args.l1_blob_archiver_url
.clone()
.as_ref()
.map(|url| url.as_str().trim_end_matches('/').to_string()),
)
.build();
Expand Down Expand Up @@ -105,23 +105,32 @@ where
l1_chain_provider: CP,
blob_provider: BP,
) -> Self {
let cursor = SyncCursor::new(cfg.channel_timeout);
let validator: Box<dyn AttributesValidator> = match args.validation_mode {
ValidationMode::Trusted => Box::new(TrustedValidator::new_http(
args.l2_rpc_url.clone(),
cfg.canyon_time.unwrap_or(0),
)),
ValidationMode::EngineApi => Box::new(EngineApiValidator::new_http(
args.l2_engine_api_url.expect("Missing L2 engine API URL"),
match args.l2_engine_jwt_secret.as_ref() {
Some(fpath) => JwtSecret::from_file(fpath).expect("Invalid L2 JWT secret file"),
None => panic!("Missing L2 engine JWT secret"),
},
)),
let trusted_validator = if matches!(args.validation_mode, ValidationMode::Trusted) {
let canyon_activation = cfg.canyon_time.unwrap_or(0);
Some(TrustedPayloadValidator::new_http(args.l2_rpc_url.clone(), canyon_activation))
} else {
None
};

// TODO(nico): at this point we should make these flags mandatory imo
merklefruit marked this conversation as resolved.
Show resolved Hide resolved
let engine_url = args.l2_engine_api_url.expect("engine api url");
let l2_jwt_file = args.l2_engine_jwt_secret.expect("jwt secret file");
let l2_jwt_secret = JwtSecret::from_file(&l2_jwt_file).expect("jwt secret");
let engine = Engine::new_http(engine_url, l2_jwt_secret);

let cursor = SyncCursor::new(cfg.channel_timeout);
let l2_chain_provider = AlloyL2ChainProvider::new_http(args.l2_rpc_url, cfg.clone());

Self { cfg, ctx, l1_chain_provider, blob_provider, l2_chain_provider, cursor, validator }
Self {
cfg,
ctx,
l1_chain_provider,
l2_chain_provider,
blob_provider,
cursor,
trusted_validator,
engine,
}
}

/// Wait for the L2 genesis' corresponding L1 block to be available in the L1 chain.
Expand Down Expand Up @@ -184,11 +193,17 @@ where
},
}

let derived_attributes = if let Some(attributes) = pipeline.peek() {
match self.validator.validate(attributes).await {
let Some(derived_attributes) = pipeline.peek() else {
debug!("No attributes available to validate");
return false;
};
let derived_block_number = derived_attributes.parent.block_info.number + 1;

if let Some(trusted_validator) = &self.trusted_validator {
match trusted_validator.validate_payload(derived_attributes).await {
Ok(true) => {
trace!("Validated payload attributes");
pipeline.next().expect("Peeked attributes must be available")
pipeline.next().expect("Peeked attributes must be available");
}
Ok(false) => {
error!("Failed payload attributes validation");
Expand All @@ -202,12 +217,15 @@ where
}
}
} else {
debug!("No attributes available to validate");
return false;
};
// TODO: implement engine api validation here. if successful, extract next attributes
if let Err(err) = self.engine.validate_payload_fcu(derived_attributes).await {
error!("Failed to validate payload attributes: {:?}", err);
return false;
}
pipeline.next().expect("Peeked attributes must be available");
}

let derived = derived_attributes.parent.block_info.number + 1;
let (new_l1_origin, new_l2_tip) = match self.fetch_new_tip(derived).await {
let (new_l1_origin, new_l2_tip) = match self.fetch_new_tip(derived_block_number).await {
Ok(tip_info) => tip_info,
Err(err) => {
// TODO: add a retry mechanism?
Expand All @@ -217,14 +235,17 @@ where
};

// Perform a sanity check on the new tip
if new_l2_tip.block_info.number != derived {
error!("Expected L2 block number {} but got {}", derived, new_l2_tip.block_info.number);
if new_l2_tip.block_info.number != derived_block_number {
error!(
"Expected L2 block {} but got {}",
derived_block_number, new_l2_tip.block_info.number
);
return false;
}

// Advance the cursor to the new L2 block
self.cursor.advance(new_l1_origin, new_l2_tip);
info!("Advanced derivation pipeline to L2 block: {}", derived);
info!("Advanced derivation pipeline to L2 block: {}", derived_block_number);
true
}

Expand Down
Loading