Skip to content

First flashblocks integration test #337

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
83 changes: 71 additions & 12 deletions crates/rollup-boost/src/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![allow(dead_code)]
use crate::DebugClient;
use crate::tests::common::services::op_rbuilder::{
FLASHBLOCKS_PORT, OpRbuilderConfig, OpRbuilderImage, OpRbuilderMehods,
};
use crate::{AuthLayer, AuthService};
use crate::{EngineApiClient, OpExecutionPayloadEnvelope, PayloadVersion};
use crate::{NewPayload, PayloadSource};
Expand All @@ -15,6 +18,7 @@ use bytes::BytesMut;
use eyre::{Context, ContextCompat};
use futures::FutureExt;
use futures::future::BoxFuture;
use http::Uri;
use jsonrpsee::core::middleware::layer::RpcLogger;
use jsonrpsee::http_client::RpcService;
use jsonrpsee::http_client::{HttpClient, transport::HttpBackend};
Expand Down Expand Up @@ -218,10 +222,38 @@ impl Genesis {
}
}

pub enum Builder {
Flashblocks(ContainerAsync<OpRbuilderImage>),
Reth(ContainerAsync<OpRethImage>),
}

impl Builder {
pub fn id(&self) -> &str {
match self {
Builder::Flashblocks(builder) => builder.id(),
Builder::Reth(builder) => builder.id(),
}
}

pub async fn auth_rpc_port(&self) -> eyre::Result<u16> {
match self {
Builder::Flashblocks(builder) => builder.auth_rpc_port().await,
Builder::Reth(builder) => builder.auth_rpc_port().await,
}
}

pub async fn auth_rpc(&self) -> eyre::Result<Uri> {
match self {
Builder::Flashblocks(builder) => builder.auth_rpc().await,
Builder::Reth(builder) => builder.auth_rpc().await,
}
}
}

/// Test flavor that sets up one Rollup-boost instance connected to two Reth nodes
pub struct RollupBoostTestHarness {
pub l2: ContainerAsync<OpRethImage>,
pub builder: ContainerAsync<OpRethImage>,
pub builder: Builder,
pub rollup_boost: RollupBoost,
pub genesis: Genesis,
}
Expand All @@ -231,6 +263,7 @@ pub struct RollupBoostTestHarnessBuilder {
proxy_handler: Option<Arc<dyn BuilderProxyHandler>>,
isthmus_block: Option<u64>,
block_time: u64,
flashblocks: bool,
}

impl RollupBoostTestHarnessBuilder {
Expand All @@ -240,9 +273,15 @@ impl RollupBoostTestHarnessBuilder {
proxy_handler: None,
isthmus_block: None,
block_time: 1,
flashblocks: false,
}
}

pub fn with_flashblocks(mut self, flashblocks: bool) -> Self {
self.flashblocks = flashblocks;
self
}

pub fn with_isthmus_block(mut self, isthmus_block: u64) -> Self {
self.isthmus_block = Some(isthmus_block);
self
Expand Down Expand Up @@ -332,17 +371,37 @@ impl RollupBoostTestHarnessBuilder {
let l2_enode = format!("enode://{}@{}:{}", L2_P2P_ENODE, name, P2P_PORT);

let builder_p2p_port = get_available_port();
let builder = OpRethConfig::default()
.set_trusted_peers(vec![l2_enode])
.set_genesis(genesis_str)
.build()?
.with_mapped_port(builder_p2p_port, ContainerPort::Tcp(P2P_PORT))
.with_mapped_port(builder_p2p_port, ContainerPort::Udp(P2P_PORT))
.with_mapped_port(get_available_port(), ContainerPort::Tcp(AUTH_RPC_PORT))
.with_network(&network)
.with_log_consumer(builder_log_consumer)
.start()
.await?;
let builder = if self.flashblocks {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not ideal to me what I wanted to keep the difference between the normal non-flashblocks cycle which can use Reth and the flashblocks protocol which must use op-rbuilder (or any flashblocks enabled builder)

let builder = OpRbuilderConfig::default()
.set_trusted_peers(vec![l2_enode])
.set_genesis(genesis_str)
.set_flashblocks(self.flashblocks)
.build()?
.with_mapped_port(builder_p2p_port, ContainerPort::Tcp(P2P_PORT))
.with_mapped_port(builder_p2p_port, ContainerPort::Udp(P2P_PORT))
.with_mapped_port(get_available_port(), ContainerPort::Tcp(AUTH_RPC_PORT))
.with_mapped_port(get_available_port(), ContainerPort::Tcp(FLASHBLOCKS_PORT))
.with_network(&network)
.with_log_consumer(builder_log_consumer)
.start()
.await?;

Builder::Flashblocks(builder)
} else {
let builder = OpRethConfig::default()
.set_trusted_peers(vec![l2_enode])
.set_genesis(genesis_str)
.build()?
.with_mapped_port(builder_p2p_port, ContainerPort::Tcp(P2P_PORT))
.with_mapped_port(builder_p2p_port, ContainerPort::Udp(P2P_PORT))
.with_mapped_port(get_available_port(), ContainerPort::Tcp(AUTH_RPC_PORT))
.with_network(&network)
.with_log_consumer(builder_log_consumer)
.start()
.await?;

Builder::Reth(builder)
};

println!("l2 authrpc: {}", l2.auth_rpc().await?);
println!("builder authrpc: {}", builder.auth_rpc().await?);
Expand Down
1 change: 1 addition & 0 deletions crates/rollup-boost/src/tests/common/services/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod op_rbuilder;
pub mod op_reth;
pub mod rollup_boost;
206 changes: 206 additions & 0 deletions crates/rollup-boost/src/tests/common/services/op_rbuilder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use http::Uri;
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
use testcontainers::{
ContainerAsync, CopyToContainer, Image,
core::{ContainerPort, WaitFor},
};

use crate::tests::common::TEST_DATA;

const NAME: &str = "ghcr.io/flashbots/op-rbuilder";
const TAG: &str = "sha-4f1931b"; // temporal

pub const AUTH_RPC_PORT: u16 = 8551;
pub const P2P_PORT: u16 = 30303;
pub const FLASHBLOCKS_PORT: u16 = 1112;

#[derive(Debug, Clone)]
pub struct OpRbuilderConfig {
jwt_secret: PathBuf,
p2p_secret: Option<PathBuf>,
pub trusted_peers: Vec<String>,
pub color: String,
pub ipcdisable: bool,
pub env_vars: HashMap<String, String>,
pub genesis: Option<String>,
flashblocks: bool,
}

impl Default for OpRbuilderConfig {
fn default() -> Self {
Self {
jwt_secret: PathBuf::from(format!("{}/jwt_secret.hex", *TEST_DATA)),
p2p_secret: None,
trusted_peers: vec![],
color: "never".to_string(),
ipcdisable: true,
env_vars: Default::default(),
genesis: None,
flashblocks: false,
}
}
}

impl OpRbuilderConfig {
pub fn set_trusted_peers(mut self, trusted_peers: Vec<String>) -> Self {
self.trusted_peers = trusted_peers;
self
}

pub fn set_jwt_secret(mut self, jwt_secret: PathBuf) -> Self {
self.jwt_secret = jwt_secret;
self
}

pub fn set_p2p_secret(mut self, p2p_secret: Option<PathBuf>) -> Self {
self.p2p_secret = p2p_secret;
self
}

pub fn set_genesis(mut self, genesis: String) -> Self {
self.genesis = Some(genesis);
self
}

pub fn set_flashblocks(mut self, flashblocks: bool) -> Self {
self.flashblocks = flashblocks;
self
}

pub fn build(self) -> eyre::Result<OpRbuilderImage> {
let genesis = self
.genesis
.clone()
.ok_or_else(|| eyre::eyre!("Genesis configuration not found"))?;

let mut copy_to_sources = vec![
CopyToContainer::new(
std::fs::read_to_string(&self.jwt_secret)?.into_bytes(),
"/jwt_secret.hex".to_string(),
),
CopyToContainer::new(genesis.into_bytes(), "/genesis.json".to_string()),
];

if let Some(p2p_secret) = &self.p2p_secret {
let p2p_string = std::fs::read_to_string(p2p_secret)
.unwrap()
.replace("\n", "");
copy_to_sources.push(CopyToContainer::new(
p2p_string.into_bytes(),
"/p2p_secret.hex".to_string(),
));
}

let expose_ports = vec![];

Ok(OpRbuilderImage {
config: self,
copy_to_sources,
expose_ports,
})
}
}

impl OpRbuilderImage {
pub fn config(&self) -> &OpRbuilderConfig {
&self.config
}
}

#[derive(Debug, Clone)]
pub struct OpRbuilderImage {
config: OpRbuilderConfig,
copy_to_sources: Vec<CopyToContainer>,
expose_ports: Vec<ContainerPort>,
}

impl Image for OpRbuilderImage {
fn name(&self) -> &str {
NAME
}

fn tag(&self) -> &str {
TAG
}

fn ready_conditions(&self) -> Vec<WaitFor> {
vec![WaitFor::message_on_stdout("Starting consensus")]
}

fn env_vars(
&self,
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
&self.config.env_vars
}

fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
self.copy_to_sources.iter()
}

fn cmd(&self) -> impl IntoIterator<Item = impl Into<std::borrow::Cow<'_, str>>> {
let mut cmd = vec![
"node".to_string(),
"--port=30303".to_string(),
"--addr=0.0.0.0".to_string(),
"--http".to_string(),
"--http.addr=0.0.0.0".to_string(),
"--http.api=eth,net,web3,debug,miner".to_string(),
"--authrpc.port=8551".to_string(),
"--authrpc.addr=0.0.0.0".to_string(),
"--authrpc.jwtsecret=/jwt_secret.hex".to_string(),
"--chain=/genesis.json".to_string(),
"-vvv".to_string(),
"--disable-discovery".to_string(),
"--color".to_string(),
self.config.color.clone(),
];
if self.config.flashblocks {
cmd.extend([
"--flashblocks.enabled".to_string(),
"--flashblocks.addr=0.0.0.0".to_string(),
"--flashblocks.port=1112".to_string(),
]);
}
if self.config.p2p_secret.is_some() {
cmd.push("--p2p-secret-key=/p2p_secret.hex".to_string());
}
if !self.config.trusted_peers.is_empty() {
println!("Trusted peers: {:?}", self.config.trusted_peers);
cmd.extend([
"--trusted-peers".to_string(),
self.config.trusted_peers.join(","),
]);
}
if self.config.ipcdisable {
cmd.push("--ipcdisable".to_string());
}

println!("cmd: {}", cmd.join(" "));

cmd
}

fn expose_ports(&self) -> &[ContainerPort] {
&self.expose_ports
}
}

pub trait OpRbuilderMehods {
async fn auth_rpc(&self) -> eyre::Result<Uri>;
async fn auth_rpc_port(&self) -> eyre::Result<u16>;
}

impl OpRbuilderMehods for ContainerAsync<OpRbuilderImage> {
async fn auth_rpc_port(&self) -> eyre::Result<u16> {
Ok(self.get_host_port_ipv4(AUTH_RPC_PORT).await?)
}

async fn auth_rpc(&self) -> eyre::Result<Uri> {
Ok(format!(
"http://{}:{}",
self.get_host().await?,
self.get_host_port_ipv4(AUTH_RPC_PORT).await?
)
.parse()?)
}
}
14 changes: 14 additions & 0 deletions crates/rollup-boost/src/tests/flashblocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use super::common::RollupBoostTestHarnessBuilder;

#[tokio::test]
async fn test_integration_flashblocks_simple() -> eyre::Result<()> {
let harness = RollupBoostTestHarnessBuilder::new("flashblocks_simple")
.with_flashblocks(true)
.with_isthmus_block(0)
.build()
.await?;
let mut block_generator = harness.block_generator().await?;
block_generator.generate_builder_blocks(10).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

this test doesn't make assertions if flashblocks are being built - I think you can make assertions by querying rollup-boost metrics

pub async fn get_metrics(&self) -> eyre::Result<String> {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It does, internally the function checks whether the block was built by the builder

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes but it doesn't distinguish between whether its a flashblock or a fallback builder block from get_payload.

Copy link
Collaborator

Choose a reason for hiding this comment

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

you could assert that source = builder and get_payload method is never called. Unless the purpose of the test is just to ensure the flashblocks flow returns any builder block?


Ok(())
}
1 change: 1 addition & 0 deletions crates/rollup-boost/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod builder_full_delay;
mod builder_returns_incorrect_block;
mod execution_mode;
mod fcu_no_block_time_delay;
mod flashblocks;
mod no_tx_pool;
mod remote_builder_down;
mod simple;
Expand Down
Loading