diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs
index 15ecc0d8..91d2c915 100644
--- a/crates/op-rbuilder/src/builders/builder_tx.rs
+++ b/crates/op-rbuilder/src/builders/builder_tx.rs
@@ -1,14 +1,18 @@
-use alloy_consensus::TxEip1559;
+use alloy_consensus::{Transaction, TxEip1559};
use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN};
use alloy_evm::Database;
+use alloy_op_evm::OpEvm;
use alloy_primitives::{
- Address, B256, Log, TxKind, U256,
- map::foldhash::{HashSet, HashSetExt},
+ Address, B256, Bytes, TxKind, U256,
+ map::foldhash::{HashMap, HashSet, HashSetExt},
};
use core::fmt::Debug;
use op_alloy_consensus::OpTypedTransaction;
-use op_revm::OpTransactionError;
-use reth_evm::{ConfigureEvm, Evm, eth::receipt_builder::ReceiptBuilderCtx};
+use op_revm::{OpHaltReason, OpTransactionError};
+use reth_evm::{
+ ConfigureEvm, Evm, EvmError, eth::receipt_builder::ReceiptBuilderCtx,
+ precompiles::PrecompilesMap,
+};
use reth_node_api::PayloadBuilderError;
use reth_optimism_primitives::OpTransactionSigned;
use reth_primitives::Recovered;
@@ -16,7 +20,9 @@ use reth_provider::{ProviderError, StateProvider};
use reth_revm::{State, database::StateProviderDatabase};
use revm::{
DatabaseCommit,
- context::result::{EVMError, ResultAndState},
+ context::result::{EVMError, ExecutionResult, ResultAndState},
+ inspector::NoOpInspector,
+ state::Account,
};
use tracing::warn;
@@ -24,6 +30,13 @@ use crate::{
builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer,
};
+#[derive(Debug, Default)]
+pub struct SimulationSuccessResult {
+ pub gas_used: u64,
+ pub output: Bytes,
+ pub state_changes: HashMap
,
+}
+
#[derive(Debug, Clone)]
pub struct BuilderTransactionCtx {
pub gas_used: u64,
@@ -46,6 +59,14 @@ impl BuilderTransactionCtx {
}
}
+#[derive(Debug, thiserror::Error)]
+pub enum InvalidContractDataError {
+ #[error("did not find expected log {0:?} in emitted logs")]
+ InvalidLogs(B256),
+ #[error("could not decode output from contract call")]
+ OutputAbiDecodeError,
+}
+
/// Possible error variants during construction of builder txs.
#[derive(Debug, thiserror::Error)]
pub enum BuilderTransactionError {
@@ -55,6 +76,15 @@ pub enum BuilderTransactionError {
/// Signature signing fails
#[error("failed to sign transaction: {0}")]
SigningError(secp256k1::Error),
+ /// Invalid contract errors indicating the contract is incorrect
+ #[error("contract {0} may be incorrect, invalid contract data: {1}")]
+ InvalidContract(Address, InvalidContractDataError),
+ /// Transaction halted execution
+ #[error("transaction halted {0:?}")]
+ TransactionHalted(OpHaltReason),
+ /// Transaction reverted
+ #[error("transaction reverted {0}")]
+ TransactionReverted(Box),
/// Invalid tx errors during evm execution.
#[error("invalid transaction error {0}")]
InvalidTransactionError(Box),
@@ -90,24 +120,26 @@ impl From for PayloadBuilderError {
}
impl BuilderTransactionError {
- pub fn other(error: E) -> Self
- where
- E: core::error::Error + Send + Sync + 'static,
- {
+ pub fn other(error: impl core::error::Error + Send + Sync + 'static) -> Self {
BuilderTransactionError::Other(Box::new(error))
}
+
+ pub fn msg(msg: impl core::fmt::Display) -> Self {
+ Self::Other(msg.to_string().into())
+ }
}
-pub trait BuilderTransactions: Debug {
- fn simulate_builder_txs(
+pub trait BuilderTransactions {
+ fn simulate_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
info: &mut ExecutionInfo,
ctx: &OpPayloadBuilderCtx,
db: &mut State,
+ top_of_block: bool,
) -> Result, BuilderTransactionError>;
- fn add_builder_txs(
+ fn add_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
info: &mut ExecutionInfo,
@@ -122,8 +154,13 @@ pub trait BuilderTransactions: Debug {
let mut invalid: HashSet = HashSet::new();
- let builder_txs =
- self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?;
+ let builder_txs = self.simulate_builder_txs(
+ state_provider,
+ info,
+ builder_ctx,
+ evm.db_mut(),
+ top_of_block,
+ )?;
for builder_tx in builder_txs.iter() {
if builder_tx.is_top_of_block != top_of_block {
// don't commit tx if the buidler tx is not being added in the intended
@@ -140,7 +177,7 @@ pub trait BuilderTransactions: Debug {
.map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?;
if !result.is_success() {
- warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted");
+ warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), result = ?result, "builder tx reverted");
invalid.insert(builder_tx.signed_tx.signer());
continue;
}
@@ -174,7 +211,7 @@ pub trait BuilderTransactions: Debug {
}
}
- fn simulate_builder_txs_state(
+ fn simulate_builder_txs_state(
&self,
state_provider: impl StateProvider + Clone,
builder_txs: Vec<&BuilderTransactionCtx>,
@@ -201,19 +238,101 @@ pub trait BuilderTransactions: Debug {
Ok(simulation_state)
}
+
+ fn sign_tx(
+ &self,
+ to: Address,
+ from: Signer,
+ gas_used: Option,
+ calldata: Bytes,
+ ctx: &OpPayloadBuilderCtx,
+ db: &mut State,
+ ) -> Result, BuilderTransactionError> {
+ let nonce = get_nonce(db, from.address)?;
+ // Create the EIP-1559 transaction
+ let tx = OpTypedTransaction::Eip1559(TxEip1559 {
+ chain_id: ctx.chain_id(),
+ nonce,
+ // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer
+ gas_limit: gas_used
+ .map(|gas| gas * 64 / 63)
+ .unwrap_or(ctx.block_gas_limit()),
+ max_fee_per_gas: ctx.base_fee().into(),
+ to: TxKind::Call(to),
+ input: calldata,
+ ..Default::default()
+ });
+ Ok(from.sign_tx(tx)?)
+ }
+
+ fn simulate_call(
+ &self,
+ signed_tx: Recovered,
+ expected_topic: Option,
+ revert_handler: impl FnOnce(Bytes) -> BuilderTransactionError,
+ evm: &mut OpEvm<
+ &mut State>,
+ NoOpInspector,
+ PrecompilesMap,
+ >,
+ ) -> Result {
+ let ResultAndState { result, state } = match evm.transact(&signed_tx) {
+ Ok(res) => res,
+ Err(err) => {
+ if err.is_invalid_tx_err() {
+ return Err(BuilderTransactionError::InvalidTransactionError(Box::new(
+ err,
+ )));
+ } else {
+ return Err(BuilderTransactionError::EvmExecutionError(Box::new(err)));
+ }
+ }
+ };
+
+ match result {
+ ExecutionResult::Success {
+ logs,
+ gas_used,
+ output,
+ ..
+ } => {
+ if let Some(topic) = expected_topic
+ && !logs.iter().any(|log| log.topics().first() == Some(&topic))
+ {
+ return Err(BuilderTransactionError::InvalidContract(
+ signed_tx.to().unwrap_or_default(),
+ InvalidContractDataError::InvalidLogs(topic),
+ ));
+ }
+ Ok(SimulationSuccessResult {
+ gas_used,
+ output: output.into_data(),
+ state_changes: state,
+ })
+ }
+ ExecutionResult::Revert { output, .. } => Err(revert_handler(output)),
+ ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other(
+ BuilderTransactionError::TransactionHalted(reason),
+ )),
+ }
+ }
}
#[derive(Debug, Clone)]
-pub(super) struct BuilderTxBase {
+pub(super) struct BuilderTxBase {
pub signer: Option,
+ _marker: std::marker::PhantomData,
}
-impl BuilderTxBase {
+impl BuilderTxBase {
pub(super) fn new(signer: Option) -> Self {
- Self { signer }
+ Self {
+ signer,
+ _marker: std::marker::PhantomData,
+ }
}
- pub(super) fn simulate_builder_tx(
+ pub(super) fn simulate_builder_tx(
&self,
ctx: &OpPayloadBuilderCtx,
db: &mut State,
@@ -258,7 +377,7 @@ impl BuilderTxBase {
std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas)
}
- fn signed_builder_tx(
+ fn signed_builder_tx(
&self,
ctx: &OpPayloadBuilderCtx,
db: &mut State,
@@ -309,7 +428,3 @@ pub fn get_balance(
.map(|acc| acc.account_info().unwrap_or_default().balance)
.map_err(|_| BuilderTransactionError::AccountLoadFailed(address))
}
-
-pub fn log_exists(logs: &[Log], topic: &B256) -> bool {
- logs.iter().any(|log| log.topics().first() == Some(topic))
-}
diff --git a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs
index b1b16906..7635cee8 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs
@@ -2,7 +2,7 @@ use alloy_consensus::TxEip1559;
use alloy_eips::Encodable2718;
use alloy_evm::{Database, Evm};
use alloy_op_evm::OpEvm;
-use alloy_primitives::{Address, B256, TxKind};
+use alloy_primitives::{Address, TxKind};
use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, sol};
use core::fmt::Debug;
use op_alloy_consensus::OpTypedTransaction;
@@ -21,9 +21,9 @@ use tracing::warn;
use crate::{
builders::{
BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions,
- builder_tx::{BuilderTxBase, get_nonce, log_exists},
+ builder_tx::{BuilderTxBase, get_nonce},
context::OpPayloadBuilderCtx,
- flashblocks::payload::FlashblocksExtraCtx,
+ flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx},
},
flashtestations::builder_tx::FlashtestationsBuilderTx,
primitives::reth::ExecutionInfo,
@@ -53,8 +53,6 @@ sol!(
pub(super) enum FlashblockNumberError {
#[error("flashblocks number contract tx reverted: {0:?}")]
Revert(IFlashblockNumber::IFlashblockNumberErrors),
- #[error("contract may be invalid, mismatch in log emitted: expected {0:?}")]
- LogMismatch(B256),
#[error("unknown revert: {0} err: {1}")]
Unknown(String, Error),
#[error("halt: {0:?}")]
@@ -64,14 +62,17 @@ pub(super) enum FlashblockNumberError {
// This will be the end of block transaction of a regular block
#[derive(Debug, Clone)]
pub(super) struct FlashblocksBuilderTx {
- pub base_builder_tx: BuilderTxBase,
- pub flashtestations_builder_tx: Option,
+ pub base_builder_tx: BuilderTxBase,
+ pub flashtestations_builder_tx:
+ Option>,
}
impl FlashblocksBuilderTx {
pub(super) fn new(
signer: Option,
- flashtestations_builder_tx: Option,
+ flashtestations_builder_tx: Option<
+ FlashtestationsBuilderTx,
+ >,
) -> Self {
let base_builder_tx = BuilderTxBase::new(signer);
Self {
@@ -81,13 +82,14 @@ impl FlashblocksBuilderTx {
}
}
-impl BuilderTransactions for FlashblocksBuilderTx {
- fn simulate_builder_txs(
+impl BuilderTransactions for FlashblocksBuilderTx {
+ fn simulate_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
- info: &mut ExecutionInfo,
+ info: &mut ExecutionInfo,
ctx: &OpPayloadBuilderCtx,
db: &mut State,
+ top_of_block: bool,
) -> Result, BuilderTransactionError> {
let mut builder_txs = Vec::::new();
@@ -102,19 +104,30 @@ impl BuilderTransactions for FlashblocksBuilderTx {
if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx {
// We only include flashtestations txs in the last flashblock
- let mut simulation_state = self.simulate_builder_txs_state::(
+ let mut simulation_state = self.simulate_builder_txs_state(
state_provider.clone(),
- base_tx.iter().collect(),
+ base_tx
+ .iter()
+ .filter(|tx| tx.is_top_of_block == top_of_block)
+ .collect(),
ctx,
db,
)?;
- let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs(
+ // We only include flashtestations txs in the last flashblock
+ match flashtestations_builder_tx.simulate_builder_txs(
state_provider,
info,
ctx,
&mut simulation_state,
- )?;
- builder_txs.extend(flashtestations_builder_txs);
+ top_of_block,
+ ) {
+ Ok(flashtestations_builder_txs) => {
+ builder_txs.extend(flashtestations_builder_txs)
+ }
+ Err(e) => {
+ warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx")
+ }
+ }
}
}
Ok(builder_txs)
@@ -126,15 +139,18 @@ impl BuilderTransactions for FlashblocksBuilderTx {
pub(super) struct FlashblocksNumberBuilderTx {
pub signer: Option,
pub flashblock_number_address: Address,
- pub base_builder_tx: BuilderTxBase,
- pub flashtestations_builder_tx: Option,
+ pub base_builder_tx: BuilderTxBase,
+ pub flashtestations_builder_tx:
+ Option>,
}
impl FlashblocksNumberBuilderTx {
pub(super) fn new(
signer: Option,
flashblock_number_address: Address,
- flashtestations_builder_tx: Option,
+ flashtestations_builder_tx: Option<
+ FlashtestationsBuilderTx,
+ >,
) -> Self {
let base_builder_tx = BuilderTxBase::new(signer);
Self {
@@ -166,14 +182,15 @@ impl FlashblocksNumberBuilderTx {
match result {
ExecutionResult::Success { gas_used, logs, .. } => {
- if log_exists(
- &logs,
- &IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH,
- ) {
+ if logs.iter().any(|log| {
+ log.topics().first()
+ == Some(&IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH)
+ }) {
Ok(gas_used)
} else {
- Err(BuilderTransactionError::other(
- FlashblockNumberError::LogMismatch(
+ Err(BuilderTransactionError::InvalidContract(
+ self.flashblock_number_address,
+ crate::builders::InvalidContractDataError::InvalidLogs(
IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH,
),
))
@@ -213,13 +230,16 @@ impl FlashblocksNumberBuilderTx {
}
}
-impl BuilderTransactions for FlashblocksNumberBuilderTx {
- fn simulate_builder_txs(
+impl BuilderTransactions
+ for FlashblocksNumberBuilderTx
+{
+ fn simulate_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
- info: &mut ExecutionInfo,
+ info: &mut ExecutionInfo,
ctx: &OpPayloadBuilderCtx,
db: &mut State,
+ top_of_block: bool,
) -> Result, BuilderTransactionError> {
let mut builder_txs = Vec::::new();
let state = StateProviderDatabase::new(state_provider.clone());
@@ -240,6 +260,7 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx {
.evm_with_env(simulation_state, ctx.evm_env.clone());
evm.modify_cfg(|cfg| {
cfg.disable_balance_check = true;
+ cfg.disable_block_gas_limit = true;
});
let nonce = get_nonce(evm.db_mut(), signer.address)?;
@@ -280,20 +301,30 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx {
if ctx.is_last_flashblock() {
if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx {
let flashblocks_builder_txs = builder_txs.clone();
- let mut simulation_state = self.simulate_builder_txs_state::(
+ let mut simulation_state = self.simulate_builder_txs_state(
state_provider.clone(),
- flashblocks_builder_txs.iter().collect(),
+ flashblocks_builder_txs
+ .iter()
+ .filter(|tx| tx.is_top_of_block == top_of_block)
+ .collect(),
ctx,
db,
)?;
// We only include flashtestations txs in the last flashblock
- let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs(
+ match flashtestations_builder_tx.simulate_builder_txs(
state_provider,
info,
ctx,
&mut simulation_state,
- )?;
- builder_txs.extend(flashtestations_builder_txs);
+ top_of_block,
+ ) {
+ Ok(flashtestations_builder_txs) => {
+ builder_txs.extend(flashtestations_builder_txs)
+ }
+ Err(e) => {
+ warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx")
+ }
+ }
}
}
diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs
index 6a46e4c7..9217548c 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs
@@ -67,13 +67,13 @@ type NextBestFlashblocksTxs = BestFlashblocksTxs<
>,
>;
-#[derive(Debug, Default)]
-struct ExtraExecutionInfo {
+#[derive(Debug, Default, Clone)]
+pub(super) struct FlashblocksExecutionInfo {
/// Index of the last consumed flashblock
pub last_flashblock_index: usize,
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
pub struct FlashblocksExtraCtx {
/// Current flashblock index
flashblock_index: u64,
@@ -211,7 +211,7 @@ impl OpPayloadBuilder
where
Pool: PoolBounds,
Client: ClientBounds,
- BuilderTx: BuilderTransactions + Send + Sync,
+ BuilderTx: BuilderTransactions + Send + Sync,
{
/// Constructs an Optimism payload from the transactions sent via the
/// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in
@@ -542,7 +542,7 @@ where
>(
&self,
ctx: &mut OpPayloadBuilderCtx,
- info: &mut ExecutionInfo,
+ info: &mut ExecutionInfo,
state: &mut State,
state_provider: impl reth::providers::StateProvider + Clone,
best_txs: &mut NextBestFlashblocksTxs,
@@ -756,7 +756,7 @@ where
fn record_flashblocks_metrics(
&self,
ctx: &OpPayloadBuilderCtx,
- info: &ExecutionInfo,
+ info: &ExecutionInfo,
flashblocks_per_block: u64,
span: &tracing::Span,
message: &str,
@@ -872,7 +872,8 @@ impl PayloadBuilder for OpPayloadBuilder + Clone + Send + Sync,
+ BuilderTx:
+ BuilderTransactions + Clone + Send + Sync,
{
type Attributes = OpPayloadBuilderAttributes;
type BuiltPayload = OpBuiltPayload;
@@ -896,7 +897,7 @@ struct FlashblocksMetadata {
fn execute_pre_steps(
state: &mut State,
ctx: &OpPayloadBuilderCtx,
-) -> Result, PayloadBuilderError>
+) -> Result, PayloadBuilderError>
where
DB: Database + std::fmt::Debug,
ExtraCtx: std::fmt::Debug + Default,
@@ -916,7 +917,7 @@ where
fn build_block(
state: &mut State,
ctx: &OpPayloadBuilderCtx,
- info: &mut ExecutionInfo,
+ info: &mut ExecutionInfo,
calculate_state_root: bool,
) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError>
where
diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs
index 46ee8ae8..584252df 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/service.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs
@@ -5,7 +5,7 @@ use crate::{
builder_tx::BuilderTransactions,
flashblocks::{
builder_tx::{FlashblocksBuilderTx, FlashblocksNumberBuilderTx},
- payload::FlashblocksExtraCtx,
+ payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx},
},
generator::BlockPayloadJobGenerator,
},
@@ -32,7 +32,12 @@ impl FlashblocksServiceBuilder {
where
Node: NodeBounds,
Pool: PoolBounds,
- BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static,
+ BuilderTx: BuilderTransactions
+ + Unpin
+ + Clone
+ + Send
+ + Sync
+ + 'static,
{
let once_lock = Arc::new(std::sync::OnceLock::new());
@@ -84,8 +89,12 @@ where
_: OpEvmConfig,
) -> eyre::Result::Payload>> {
let signer = self.0.builder_signer;
- let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled {
- match bootstrap_flashtestations(self.0.flashtestations_config.clone(), ctx).await {
+ let flashtestations_builder_tx = if let Some(builder_key) = signer
+ && self.0.flashtestations_config.flashtestations_enabled
+ {
+ match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key, ctx)
+ .await
+ {
Ok(builder_tx) => Some(builder_tx),
Err(e) => {
tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builder will not include flashtestations txs");
diff --git a/crates/op-rbuilder/src/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs
index 9dbd949c..c733d111 100644
--- a/crates/op-rbuilder/src/builders/mod.rs
+++ b/crates/op-rbuilder/src/builders/mod.rs
@@ -22,8 +22,8 @@ mod generator;
mod standard;
pub use builder_tx::{
- BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, get_balance, get_nonce,
- log_exists,
+ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError,
+ SimulationSuccessResult, get_balance, get_nonce,
};
pub use context::OpPayloadBuilderCtx;
pub use flashblocks::FlashblocksBuilder;
diff --git a/crates/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/op-rbuilder/src/builders/standard/builder_tx.rs
index 75a159ad..db69906b 100644
--- a/crates/op-rbuilder/src/builders/standard/builder_tx.rs
+++ b/crates/op-rbuilder/src/builders/standard/builder_tx.rs
@@ -2,6 +2,7 @@ use alloy_evm::Database;
use core::fmt::Debug;
use reth_provider::StateProvider;
use reth_revm::State;
+use tracing::warn;
use crate::{
builders::{
@@ -34,30 +35,39 @@ impl StandardBuilderTx {
}
impl BuilderTransactions for StandardBuilderTx {
- fn simulate_builder_txs(
+ fn simulate_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
- info: &mut ExecutionInfo,
+ info: &mut ExecutionInfo,
ctx: &OpPayloadBuilderCtx,
db: &mut State,
+ top_of_block: bool,
) -> Result, BuilderTransactionError> {
let mut builder_txs = Vec::::new();
let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?;
builder_txs.extend(standard_builder_tx.clone());
if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx {
- let mut simulation_state = self.simulate_builder_txs_state::<()>(
+ let mut simulation_state = self.simulate_builder_txs_state(
state_provider.clone(),
- standard_builder_tx.iter().collect(),
+ standard_builder_tx
+ .iter()
+ .filter(|tx| tx.is_top_of_block == top_of_block)
+ .collect(),
ctx,
db,
)?;
- let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs(
+ match flashtestations_builder_tx.simulate_builder_txs(
state_provider,
info,
ctx,
&mut simulation_state,
- )?;
- builder_txs.extend(flashtestations_builder_txs);
+ top_of_block,
+ ) {
+ Ok(flashtestations_builder_txs) => builder_txs.extend(flashtestations_builder_txs),
+ Err(e) => {
+ warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx")
+ }
+ }
}
Ok(builder_txs)
}
diff --git a/crates/op-rbuilder/src/builders/standard/service.rs b/crates/op-rbuilder/src/builders/standard/service.rs
index c713b69c..faf252b1 100644
--- a/crates/op-rbuilder/src/builders/standard/service.rs
+++ b/crates/op-rbuilder/src/builders/standard/service.rs
@@ -72,8 +72,10 @@ where
evm_config: OpEvmConfig,
) -> eyre::Result::Payload>> {
let signer = self.0.builder_signer;
- let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled {
- match bootstrap_flashtestations::(self.0.flashtestations_config.clone(), ctx)
+ let flashtestations_builder_tx = if let Some(builder_key) = signer
+ && self.0.flashtestations_config.flashtestations_enabled
+ {
+ match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key, ctx)
.await
{
Ok(builder_tx) => Some(builder_tx),
diff --git a/crates/op-rbuilder/src/flashtestations/args.rs b/crates/op-rbuilder/src/flashtestations/args.rs
index 54d5d837..cd0a5583 100644
--- a/crates/op-rbuilder/src/flashtestations/args.rs
+++ b/crates/op-rbuilder/src/flashtestations/args.rs
@@ -92,6 +92,14 @@ pub struct FlashtestationsArgs {
default_value = "1"
)]
pub builder_proof_version: u8,
+
+ /// Use permit for the flashtestation builder tx
+ #[arg(
+ long = "flashtestations.use-permit",
+ env = "FLASHTESTATIONS_USE_PERMIT",
+ default_value = "false"
+ )]
+ pub flashtestations_use_permit: bool,
}
impl Default for FlashtestationsArgs {
diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs
index ee5e793c..d18b4f1f 100644
--- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs
+++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs
@@ -2,8 +2,10 @@ use alloy_consensus::TxEip1559;
use alloy_eips::Encodable2718;
use alloy_evm::Database;
use alloy_op_evm::OpEvm;
-use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256, map::foldhash::HashMap};
-use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, SolValue};
+use alloy_primitives::{
+ Address, B256, Bytes, Signature, TxKind, U256, keccak256, map::foldhash::HashMap,
+};
+use alloy_sol_types::{SolCall, SolEvent, SolInterface, SolValue};
use core::fmt::Debug;
use op_alloy_consensus::OpTypedTransaction;
use reth_evm::{ConfigureEvm, Evm, EvmError, precompiles::PrecompilesMap};
@@ -18,16 +20,18 @@ use revm::{
state::Account,
};
use std::sync::OnceLock;
-use tracing::{debug, info};
+use tracing::{debug, info, warn};
use crate::{
builders::{
- BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx,
- get_balance, get_nonce,
+ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions,
+ InvalidContractDataError, OpPayloadBuilderCtx, SimulationSuccessResult, get_balance,
+ get_nonce,
},
flashtestations::{
BlockData, FlashtestationRevertReason,
IBlockBuilderPolicy::{self, BlockBuilderProofVerified},
+ IERC20Permit,
IFlashtestationRegistry::{self, TEEServiceRegistered},
},
primitives::reth::ExecutionInfo,
@@ -45,10 +49,16 @@ pub struct FlashtestationsBuilderTxArgs {
pub builder_proof_version: u8,
pub enable_block_proofs: bool,
pub registered: bool,
+ pub use_permit: bool,
+ pub builder_key: Signer,
}
#[derive(Debug, Clone)]
-pub struct FlashtestationsBuilderTx {
+pub struct FlashtestationsBuilderTx
+where
+ ExtraCtx: Debug + Default,
+ Extra: Debug + Default,
+{
// Attestation for the builder
attestation: Vec,
// Extra registration data for the builder
@@ -69,6 +79,12 @@ pub struct FlashtestationsBuilderTx {
registered: OnceLock,
// Whether block proofs are enabled
enable_block_proofs: bool,
+ // Whether to use permit for the flashtestation builder tx
+ use_permit: bool,
+ // Builder key for the flashtestation permit tx
+ builder_signer: Signer,
+ // Extra context and data
+ _marker: std::marker::PhantomData<(ExtraCtx, Extra)>,
}
#[derive(Debug, Default)]
@@ -80,7 +96,11 @@ pub struct TxSimulateResult {
pub logs: Vec,
}
-impl FlashtestationsBuilderTx {
+impl FlashtestationsBuilderTx
+where
+ ExtraCtx: Debug + Default,
+ Extra: Debug + Default,
+{
pub fn new(args: FlashtestationsBuilderTxArgs) -> Self {
Self {
attestation: args.attestation,
@@ -93,6 +113,9 @@ impl FlashtestationsBuilderTx {
builder_proof_version: args.builder_proof_version,
registered: OnceLock::new(),
enable_block_proofs: args.enable_block_proofs,
+ use_permit: args.use_permit,
+ builder_signer: args.builder_key,
+ _marker: std::marker::PhantomData,
}
}
@@ -148,7 +171,7 @@ impl FlashtestationsBuilderTx {
self.tee_service_signer.sign_tx(tx)
}
- fn signed_block_builder_proof_tx(
+ fn signed_block_builder_proof_tx(
&self,
block_content_hash: B256,
ctx: &OpPayloadBuilderCtx,
@@ -206,7 +229,7 @@ impl FlashtestationsBuilderTx {
keccak256(&encoded)
}
- fn simulate_register_tee_service_tx(
+ fn simulate_register_tee_service_tx(
&self,
ctx: &OpPayloadBuilderCtx,
evm: &mut OpEvm<
@@ -248,8 +271,8 @@ impl FlashtestationsBuilderTx {
let revert_reason =
IFlashtestationRegistry::IFlashtestationRegistryErrors::abi_decode(&output)
.map(FlashtestationRevertReason::FlashtestationRegistry)
- .unwrap_or_else(|e| {
- FlashtestationRevertReason::Unknown(hex::encode(output), e)
+ .unwrap_or_else(|_| {
+ FlashtestationRevertReason::Unknown(hex::encode(output))
});
Ok(TxSimulateResult {
gas_used,
@@ -263,26 +286,15 @@ impl FlashtestationsBuilderTx {
gas_used: 0,
success: false,
state_changes: state,
- revert_reason: Some(FlashtestationRevertReason::Halt(reason)),
+ revert_reason: Some(FlashtestationRevertReason::Unknown(format!(
+ "block proof transaction halted {reason:?}"
+ ))),
logs: vec![],
}),
}
}
- fn check_tee_address_registered_log(&self, logs: &[Log], address: Address) -> bool {
- for log in logs {
- if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) {
- if let Ok(decoded) = TEEServiceRegistered::decode_log(log) {
- if decoded.teeAddress == address {
- return true;
- }
- };
- }
- }
- false
- }
-
- fn simulate_verify_block_proof_tx(
+ fn simulate_verify_block_proof_tx(
&self,
block_content_hash: B256,
ctx: &OpPayloadBuilderCtx,
@@ -324,8 +336,8 @@ impl FlashtestationsBuilderTx {
let revert_reason =
IBlockBuilderPolicy::IBlockBuilderPolicyErrors::abi_decode(&output)
.map(FlashtestationRevertReason::BlockBuilderPolicy)
- .unwrap_or_else(|e| {
- FlashtestationRevertReason::Unknown(hex::encode(output), e)
+ .unwrap_or_else(|_| {
+ FlashtestationRevertReason::Unknown(hex::encode(output))
});
Ok(TxSimulateResult {
gas_used,
@@ -339,22 +351,15 @@ impl FlashtestationsBuilderTx {
gas_used: 0,
success: false,
state_changes: state,
- revert_reason: Some(FlashtestationRevertReason::Halt(reason)),
+ revert_reason: Some(FlashtestationRevertReason::Unknown(format!(
+ "register tee transaction halted {reason:?}"
+ ))),
logs: vec![],
}),
}
}
- fn check_verify_block_proof_log(&self, logs: &[Log]) -> bool {
- for log in logs {
- if log.topics().first() == Some(&BlockBuilderProofVerified::SIGNATURE_HASH) {
- return true;
- }
- }
- false
- }
-
- fn fund_tee_service_tx(
+ fn fund_tee_service_tx(
&self,
ctx: &OpPayloadBuilderCtx,
evm: &mut OpEvm<
@@ -401,7 +406,7 @@ impl FlashtestationsBuilderTx {
}
}
- fn register_tee_service_tx(
+ fn register_tee_service_tx(
&self,
ctx: &OpPayloadBuilderCtx,
evm: &mut OpEvm<
@@ -418,12 +423,13 @@ impl FlashtestationsBuilderTx {
logs,
} = self.simulate_register_tee_service_tx(ctx, evm)?;
if success {
- if !self.check_tee_address_registered_log(&logs, self.tee_service_signer.address) {
- Err(BuilderTransactionError::other(
- FlashtestationRevertReason::LogMismatch(
- self.registry_address,
- TEEServiceRegistered::SIGNATURE_HASH,
- ),
+ if !logs
+ .iter()
+ .any(|log| log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH))
+ {
+ Err(BuilderTransactionError::InvalidContract(
+ self.registry_address,
+ InvalidContractDataError::InvalidLogs(TEEServiceRegistered::SIGNATURE_HASH),
))
} else {
let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?;
@@ -456,15 +462,12 @@ impl FlashtestationsBuilderTx {
Ok((None, true))
} else {
Err(BuilderTransactionError::other(revert_reason.unwrap_or(
- FlashtestationRevertReason::Unknown(
- "unknown revert".into(),
- Error::Other("unknown revert".into()),
- ),
+ FlashtestationRevertReason::Unknown("unknown revert".to_string()),
)))
}
}
- fn verify_block_proof_tx(
+ fn verify_block_proof_tx(
&self,
transactions: Vec,
ctx: &OpPayloadBuilderCtx,
@@ -489,10 +492,13 @@ impl FlashtestationsBuilderTx {
..
} = self.simulate_verify_block_proof_tx(block_content_hash, ctx, evm)?;
if success {
- if !self.check_verify_block_proof_log(&logs) {
- Err(BuilderTransactionError::other(
- FlashtestationRevertReason::LogMismatch(
- self.builder_policy_address,
+ if !logs
+ .iter()
+ .any(|log| log.topics().first() == Some(&BlockBuilderProofVerified::SIGNATURE_HASH))
+ {
+ Err(BuilderTransactionError::InvalidContract(
+ self.builder_policy_address,
+ InvalidContractDataError::InvalidLogs(
BlockBuilderProofVerified::SIGNATURE_HASH,
),
))
@@ -518,15 +524,12 @@ impl FlashtestationsBuilderTx {
}
} else {
Err(BuilderTransactionError::other(revert_reason.unwrap_or(
- FlashtestationRevertReason::Unknown(
- "unknown revert".into(),
- Error::Other("unknown revert".into()),
- ),
+ FlashtestationRevertReason::Unknown("unknown revert".to_string()),
)))
}
}
- fn set_registered(
+ fn set_registered(
&self,
state_provider: impl StateProvider + Clone,
ctx: &OpPayloadBuilderCtx,
@@ -541,36 +544,332 @@ impl FlashtestationsBuilderTx {
.evm_with_env(&mut simulation_state, ctx.evm_env.clone());
evm.modify_cfg(|cfg| {
cfg.disable_balance_check = true;
+ cfg.disable_nonce_check = true;
});
- match self.register_tee_service_tx(ctx, &mut evm) {
- Ok((_, registered)) => {
- if registered {
- let _ = self.registered.set(registered);
+ let calldata = IFlashtestationRegistry::getRegistrationStatusCall {
+ teeAddress: self.tee_service_signer.address,
+ }
+ .abi_encode();
+ match self.simulate_flashtestation_call(
+ self.registry_address,
+ calldata,
+ None,
+ ctx,
+ &mut evm,
+ ) {
+ Ok(SimulationSuccessResult { output, .. }) => {
+ let result =
+ IFlashtestationRegistry::getRegistrationStatusCall::abi_decode_returns(&output)
+ .map_err(|_| {
+ BuilderTransactionError::InvalidContract(
+ self.registry_address,
+ InvalidContractDataError::OutputAbiDecodeError,
+ )
+ })?;
+ if result.isValid {
+ let _ = self.registered.set(true);
}
Ok(())
}
- Err(e) => Err(BuilderTransactionError::other(e)),
+ Err(e) => Err(e),
+ }
+ }
+
+ fn get_permit_nonce(
+ &self,
+ contract_address: Address,
+ ctx: &OpPayloadBuilderCtx,
+ evm: &mut OpEvm<
+ &mut State>,
+ NoOpInspector,
+ PrecompilesMap,
+ >,
+ ) -> Result {
+ let calldata = IERC20Permit::noncesCall {
+ owner: self.tee_service_signer.address,
+ }
+ .abi_encode();
+ let SimulationSuccessResult { output, .. } =
+ self.simulate_flashtestation_call(contract_address, calldata, None, ctx, evm)?;
+ U256::abi_decode(&output).map_err(|_| {
+ BuilderTransactionError::InvalidContract(
+ contract_address,
+ InvalidContractDataError::OutputAbiDecodeError,
+ )
+ })
+ }
+
+ fn registration_permit_signature(
+ &self,
+ permit_nonce: U256,
+ ctx: &OpPayloadBuilderCtx,
+ evm: &mut OpEvm<
+ &mut State>,
+ NoOpInspector,
+ PrecompilesMap,
+ >,
+ ) -> Result {
+ let struct_hash_calldata = IFlashtestationRegistry::computeStructHashCall {
+ rawQuote: self.attestation.clone().into(),
+ extendedRegistrationData: self.extra_registration_data.clone(),
+ nonce: permit_nonce,
+ deadline: U256::from(ctx.timestamp()),
+ }
+ .abi_encode();
+ let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call(
+ self.registry_address,
+ struct_hash_calldata,
+ None,
+ ctx,
+ evm,
+ )?;
+ let struct_hash = B256::abi_decode(&output).map_err(|_| {
+ BuilderTransactionError::InvalidContract(
+ self.registry_address,
+ InvalidContractDataError::OutputAbiDecodeError,
+ )
+ })?;
+ let typed_data_hash_calldata = IFlashtestationRegistry::hashTypedDataV4Call {
+ structHash: struct_hash,
+ }
+ .abi_encode();
+ let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call(
+ self.registry_address,
+ typed_data_hash_calldata,
+ None,
+ ctx,
+ evm,
+ )?;
+ let typed_data_hash = B256::abi_decode(&output).map_err(|_| {
+ BuilderTransactionError::InvalidContract(
+ self.registry_address,
+ InvalidContractDataError::OutputAbiDecodeError,
+ )
+ })?;
+ let signature = self.tee_service_signer.sign_message(typed_data_hash)?;
+ Ok(signature)
+ }
+
+ fn signed_registration_permit_tx(
+ &self,
+ ctx: &OpPayloadBuilderCtx,
+ evm: &mut OpEvm<
+ &mut State>,
+ NoOpInspector,
+ PrecompilesMap,
+ >,
+ ) -> Result {
+ let permit_nonce = self.get_permit_nonce(self.registry_address, ctx, evm)?;
+ let signature = self.registration_permit_signature(permit_nonce, ctx, evm)?;
+ let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall {
+ rawQuote: self.attestation.clone().into(),
+ extendedRegistrationData: self.extra_registration_data.clone(),
+ nonce: permit_nonce,
+ deadline: U256::from(ctx.timestamp()),
+ signature: signature.as_bytes().into(),
+ }
+ .abi_encode();
+ let SimulationSuccessResult { gas_used, .. } = self.simulate_flashtestation_call(
+ self.registry_address,
+ calldata.clone(),
+ Some(TEEServiceRegistered::SIGNATURE_HASH),
+ ctx,
+ evm,
+ )?;
+ let signed_tx = self.sign_tx(
+ self.registry_address,
+ self.builder_signer,
+ Some(gas_used),
+ calldata.into(),
+ ctx,
+ evm.db_mut(),
+ )?;
+ let da_size =
+ op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice());
+ Ok(BuilderTransactionCtx {
+ gas_used,
+ da_size,
+ signed_tx,
+ is_top_of_block: false,
+ })
+ }
+
+ fn block_proof_permit_signature(
+ &self,
+ permit_nonce: U256,
+ block_content_hash: B256,
+ ctx: &OpPayloadBuilderCtx,
+ evm: &mut OpEvm<
+ &mut State>,
+ NoOpInspector,
+ PrecompilesMap,
+ >,
+ ) -> Result {
+ let struct_hash_calldata = IBlockBuilderPolicy::computeStructHashCall {
+ version: self.builder_proof_version,
+ blockContentHash: block_content_hash,
+ nonce: permit_nonce,
+ }
+ .abi_encode();
+ let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call(
+ self.builder_policy_address,
+ struct_hash_calldata,
+ None,
+ ctx,
+ evm,
+ )?;
+ let struct_hash = B256::abi_decode(&output).map_err(|_| {
+ BuilderTransactionError::InvalidContract(
+ self.builder_policy_address,
+ InvalidContractDataError::OutputAbiDecodeError,
+ )
+ })?;
+ let typed_data_hash_calldata = IBlockBuilderPolicy::getHashedTypeDataV4Call {
+ structHash: struct_hash,
+ }
+ .abi_encode();
+ let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call(
+ self.builder_policy_address,
+ typed_data_hash_calldata,
+ None,
+ ctx,
+ evm,
+ )?;
+ let typed_data_hash = B256::abi_decode(&output).map_err(|_| {
+ BuilderTransactionError::InvalidContract(
+ self.builder_policy_address,
+ InvalidContractDataError::OutputAbiDecodeError,
+ )
+ })?;
+ let signature = self.tee_service_signer.sign_message(typed_data_hash)?;
+ Ok(signature)
+ }
+
+ fn signed_block_proof_permit_tx(
+ &self,
+ transactions: Vec,
+ ctx: &OpPayloadBuilderCtx,
+ evm: &mut OpEvm<
+ &mut State>,
+ NoOpInspector,
+ PrecompilesMap,
+ >,
+ ) -> Result {
+ let permit_nonce = self.get_permit_nonce(self.builder_policy_address, ctx, evm)?;
+ let block_content_hash = Self::compute_block_content_hash(
+ transactions.clone(),
+ ctx.parent_hash(),
+ ctx.block_number(),
+ ctx.timestamp(),
+ );
+ let signature =
+ self.block_proof_permit_signature(permit_nonce, block_content_hash, ctx, evm)?;
+ let calldata = IBlockBuilderPolicy::permitVerifyBlockBuilderProofCall {
+ blockContentHash: block_content_hash,
+ nonce: U256::from(permit_nonce),
+ version: self.builder_proof_version,
+ eip712Sig: signature.as_bytes().into(),
}
+ .abi_encode();
+ let SimulationSuccessResult { gas_used, .. } = self.simulate_flashtestation_call(
+ self.builder_policy_address,
+ calldata.clone(),
+ Some(BlockBuilderProofVerified::SIGNATURE_HASH),
+ ctx,
+ evm,
+ )?;
+ let signed_tx = self.sign_tx(
+ self.builder_policy_address,
+ self.builder_signer,
+ Some(gas_used),
+ calldata.into(),
+ ctx,
+ evm.db_mut(),
+ )?;
+ let da_size =
+ op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice());
+ Ok(BuilderTransactionCtx {
+ gas_used,
+ da_size,
+ signed_tx,
+ is_top_of_block: false,
+ })
+ }
+
+ fn simulate_flashtestation_call(
+ &self,
+ contract_address: Address,
+ calldata: Vec,
+ expected_topic: Option,
+ ctx: &OpPayloadBuilderCtx,
+ evm: &mut OpEvm<
+ &mut State>,
+ NoOpInspector,
+ PrecompilesMap,
+ >,
+ ) -> Result {
+ let signed_tx = self.sign_tx(
+ contract_address,
+ self.builder_signer,
+ None,
+ calldata.into(),
+ ctx,
+ evm.db_mut(),
+ )?;
+ let revert_handler = if contract_address == self.registry_address {
+ Self::handle_registry_reverts
+ } else {
+ Self::handle_block_builder_policy_reverts
+ };
+ self.simulate_call(signed_tx, expected_topic, revert_handler, evm)
+ }
+
+ fn handle_registry_reverts(revert_output: Bytes) -> BuilderTransactionError {
+ let revert_reason =
+ IFlashtestationRegistry::IFlashtestationRegistryErrors::abi_decode(&revert_output)
+ .map(FlashtestationRevertReason::FlashtestationRegistry)
+ .unwrap_or_else(|_| {
+ FlashtestationRevertReason::Unknown(hex::encode(revert_output))
+ });
+ BuilderTransactionError::TransactionReverted(Box::new(revert_reason))
+ }
+
+ fn handle_block_builder_policy_reverts(revert_output: Bytes) -> BuilderTransactionError {
+ let revert_reason =
+ IBlockBuilderPolicy::IBlockBuilderPolicyErrors::abi_decode(&revert_output)
+ .map(FlashtestationRevertReason::BlockBuilderPolicy)
+ .unwrap_or_else(|_| {
+ FlashtestationRevertReason::Unknown(hex::encode(revert_output))
+ });
+ BuilderTransactionError::TransactionReverted(Box::new(revert_reason))
}
}
-impl BuilderTransactions for FlashtestationsBuilderTx {
- fn simulate_builder_txs(
+impl BuilderTransactions
+ for FlashtestationsBuilderTx
+where
+ ExtraCtx: Debug + Default,
+ Extra: Debug + Default,
+{
+ fn simulate_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
info: &mut ExecutionInfo,
ctx: &OpPayloadBuilderCtx,
db: &mut State,
+ _top_of_block: bool,
) -> Result, BuilderTransactionError> {
// set registered simulating against the committed state
if !self.registered.get().unwrap_or(&false) {
- self.set_registered(state_provider.clone(), ctx)?;
+ self.set_registered(state_provider.clone(), ctx).inspect_err(|e| {
+ warn!(target: "flashtestations", error = ?e, "failed to check if builder tee address is registered");
+ })?;
}
let state = StateProviderDatabase::new(state_provider.clone());
let mut simulation_state = State::builder()
.with_database(state)
- .with_bundle_prestate(db.bundle_state.clone())
+ .with_cached_prestate(db.cache.clone())
.with_bundle_update()
.build();
@@ -579,24 +878,47 @@ impl BuilderTransactions for Flashtestation
.evm_with_env(&mut simulation_state, ctx.evm_env.clone());
evm.modify_cfg(|cfg| {
cfg.disable_balance_check = true;
+ cfg.disable_block_gas_limit = true;
});
let mut builder_txs = Vec::::new();
if !self.registered.get().unwrap_or(&false) {
- info!(target: "flashtestations", "tee service not registered yet, attempting to register");
- builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?);
- let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?;
- builder_txs.extend(register_tx);
+ if self.use_permit {
+ info!(target: "flashtestations", "tee service not registered yet, adding permit registration tx");
+ let register_tx = self.signed_registration_permit_tx(ctx, &mut evm)?;
+ builder_txs.push(register_tx);
+ } else {
+ builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?);
+ let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?;
+ builder_txs.extend(register_tx);
+ }
}
+ // don't return on error for block proof as previous txs in builder_txs will not be returned
if self.enable_block_proofs {
- // add verify block proof tx
- builder_txs.extend(self.verify_block_proof_tx(
- info.executed_transactions.clone(),
- ctx,
- &mut evm,
- )?);
+ if self.use_permit {
+ debug!(target: "flashtestations", "adding permit verify block proof tx");
+ match self.signed_block_proof_permit_tx(
+ info.executed_transactions.clone(),
+ ctx,
+ &mut evm,
+ ) {
+ Ok(block_proof_tx) => builder_txs.push(block_proof_tx),
+ Err(e) => {
+ warn!(target: "flashtestations", error = ?e, "failed to add permit block proof transaction")
+ }
+ }
+ } else {
+ // add verify block proof tx
+ match self.verify_block_proof_tx(info.executed_transactions.clone(), ctx, &mut evm)
+ {
+ Ok(block_proof_tx) => builder_txs.extend(block_proof_tx),
+ Err(e) => {
+ warn!(target: "flashtestations", error = ?e, "failed to add block proof transaction")
+ }
+ };
+ }
}
Ok(builder_txs)
}
diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs
index a2a50611..618abcc5 100644
--- a/crates/op-rbuilder/src/flashtestations/mod.rs
+++ b/crates/op-rbuilder/src/flashtestations/mod.rs
@@ -1,6 +1,4 @@
-use alloy_primitives::{Address, B256};
-use alloy_sol_types::{Error, sol};
-use op_revm::OpHaltReason;
+use alloy_sol_types::sol;
// https://github.com/flashbots/flashtestations/commit/7cc7f68492fe672a823dd2dead649793aac1f216
sol!(
@@ -9,6 +7,25 @@ sol!(
interface IFlashtestationRegistry {
function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external;
+ function permitRegisterTEEService(
+ bytes calldata rawQuote,
+ bytes calldata extendedRegistrationData,
+ uint256 nonce,
+ uint256 deadline,
+ bytes calldata signature
+ ) external payable;
+
+ function computeStructHash(
+ bytes calldata rawQuote,
+ bytes calldata extendedRegistrationData,
+ uint256 nonce,
+ uint256 deadline
+ ) external pure returns (bytes32);
+
+ function hashTypedDataV4(bytes32 structHash) external view returns (bytes32);
+
+ function getRegistrationStatus(address teeAddress) external view returns (bool isValid, bytes32 quoteHash);
+
/// @notice Emitted when a TEE service is registered
/// @param teeAddress The address of the TEE service
/// @param rawQuote The raw quote from the TEE device
@@ -46,6 +63,20 @@ sol!(
interface IBlockBuilderPolicy {
function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external;
+ function permitVerifyBlockBuilderProof(
+ uint8 version,
+ bytes32 blockContentHash,
+ uint256 nonce,
+ bytes calldata eip712Sig
+ ) external;
+
+ function computeStructHash(uint8 version, bytes32 blockContentHash, uint256 nonce)
+ external
+ pure
+ returns (bytes32);
+
+ function getHashedTypeDataV4(bytes32 structHash) external view returns (bytes32);
+
/// @notice Emitted when a block builder proof is successfully verified
/// @param caller The address that called the verification function (TEE address)
/// @param workloadId The workload identifier of the TEE
@@ -72,6 +103,10 @@ sol!(
error EmptySourceLocators();
}
+ interface IERC20Permit {
+ function nonces(address owner) external view returns (uint256);
+ }
+
struct BlockData {
bytes32 parentHash;
uint256 blockNumber;
@@ -88,12 +123,8 @@ pub enum FlashtestationRevertReason {
FlashtestationRegistry(IFlashtestationRegistry::IFlashtestationRegistryErrors),
#[error("block builder policy error: {0:?}")]
BlockBuilderPolicy(IBlockBuilderPolicy::IBlockBuilderPolicyErrors),
- #[error("contract {0:?} may be invalid, mismatch in log emitted: expected {1:?}")]
- LogMismatch(Address, B256),
- #[error("unknown revert: {0} err: {1}")]
- Unknown(String, Error),
- #[error("halt: {0:?}")]
- Halt(OpHaltReason),
+ #[error("unknown revert {0}")]
+ Unknown(String),
}
pub mod args;
diff --git a/crates/op-rbuilder/src/flashtestations/service.rs b/crates/op-rbuilder/src/flashtestations/service.rs
index 5f0871be..b10fb22e 100644
--- a/crates/op-rbuilder/src/flashtestations/service.rs
+++ b/crates/op-rbuilder/src/flashtestations/service.rs
@@ -2,24 +2,27 @@ use alloy_primitives::{Bytes, keccak256};
use reth_node_builder::BuilderContext;
use tracing::{info, warn};
-use crate::{
- flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs},
- traits::NodeBounds,
- tx_signer::{Signer, generate_ethereum_keypair, generate_key_from_seed},
-};
-
use super::{
args::FlashtestationsArgs,
attestation::{AttestationConfig, get_attestation_provider},
tx_manager::TxManager,
};
+use crate::{
+ flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs},
+ traits::NodeBounds,
+ tx_signer::{Signer, generate_ethereum_keypair, generate_key_from_seed},
+};
+use std::fmt::Debug;
-pub async fn bootstrap_flashtestations(
+pub async fn bootstrap_flashtestations(
args: FlashtestationsArgs,
+ builder_key: Signer,
ctx: &BuilderContext,
-) -> eyre::Result
+) -> eyre::Result>
where
Node: NodeBounds,
+ ExtraCtx: Debug + Default,
+ Extra: Debug + Default,
{
let (private_key, public_key, address) = if args.debug {
info!("Flashtestations debug mode enabled, generating debug key");
@@ -77,7 +80,11 @@ where
info!(target: "flashtestations", "requesting TDX attestation");
let attestation = attestation_provider.get_attestation(report_data).await?;
- let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url {
+ // TODO: support permit with an external rpc, skip this step if using permit signatures
+ // since the permit txs are signed by the builder key and will result in nonce issues
+ let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url
+ && !args.flashtestations_use_permit
+ {
let tx_manager = TxManager::new(
tee_service_signer,
funding_key,
@@ -114,6 +121,8 @@ where
builder_proof_version: args.builder_proof_version,
enable_block_proofs: args.enable_block_proofs,
registered,
+ use_permit: args.flashtestations_use_permit,
+ builder_key,
});
ctx.task_executor()
diff --git a/crates/op-rbuilder/src/tests/flashtestations.rs b/crates/op-rbuilder/src/tests/flashtestations.rs
index c4e0eaed..c3ecd749 100644
--- a/crates/op-rbuilder/src/tests/flashtestations.rs
+++ b/crates/op-rbuilder/src/tests/flashtestations.rs
@@ -11,7 +11,7 @@ use crate::{
tests::{
BLOCK_BUILDER_POLICY_ADDRESS, BundleOpts, ChainDriver, ChainDriverExt,
FLASHBLOCKS_NUMBER_ADDRESS, FLASHTESTATION_REGISTRY_ADDRESS, LocalInstance,
- MOCK_DCAP_ADDRESS, TEE_DEBUG_ADDRESS, TransactionBuilderExt,
+ MOCK_DCAP_ADDRESS, TEE_DEBUG_ADDRESS, TransactionBuilderExt, builder_signer,
flashblocks_number_contract::FlashblocksNumber,
flashtestation_registry::FlashtestationRegistry, flashtestations_signer,
},
@@ -33,45 +33,7 @@ use crate::{
async fn test_flashtestations_registrations(rbuilder: LocalInstance) -> eyre::Result<()> {
let driver = rbuilder.driver().await?;
let provider = rbuilder.provider().await?;
- setup_flashtestation_contracts(&driver, &provider, true).await?;
- let block: alloy_rpc_types_eth::Block =
- driver.build_new_block_with_current_timestamp(None).await?;
- // check the builder tx, funding tx and registration tx is in the block
- let num_txs = block.transactions.len();
- assert!(num_txs >= 3, "Expected at least 3 transactions in block");
- println!(
- "block transactions {:#?}",
- &block.transactions.clone().into_transactions_vec()
- );
- let last_3_txs = &block.transactions.into_transactions_vec()[num_txs - 3..];
- // Check builder tx
- assert_eq!(
- last_3_txs[0].to(),
- Some(Address::ZERO),
- "builder tx should send to zero address"
- );
- // Check funding tx
- assert_eq!(
- last_3_txs[1].to(),
- Some(TEE_DEBUG_ADDRESS),
- "funding tx should send to tee address"
- );
- assert!(
- last_3_txs[1]
- .value()
- .eq(&rbuilder.args().flashtestations.funding_amount),
- "funding tx should have correct amount"
- );
- // Check registration tx
- assert_eq!(
- last_3_txs[2].to(),
- Some(FLASHTESTATION_REGISTRY_ADDRESS),
- "registration tx should call registry"
- );
- let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone());
- let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?;
- assert!(result._1.isValid, "The tee key is not registered");
-
+ setup_flashtestation_contracts(&driver, &provider, true, true).await?;
// check builder does not try to register again
let block = driver.build_new_block_with_current_timestamp(None).await?;
let num_txs = block.transactions.len();
@@ -102,20 +64,12 @@ async fn test_flashtestations_registrations(rbuilder: LocalInstance) -> eyre::Re
async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Result<()> {
let driver = rbuilder.driver().await?;
let provider = rbuilder.provider().await?;
- setup_flashtestation_contracts(&driver, &provider, true).await?;
- driver.build_new_block_with_current_timestamp(None).await?;
-
- // check registered
- let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone());
- let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?;
- assert!(result._1.isValid, "The tee key is not registered");
-
+ setup_flashtestation_contracts(&driver, &provider, true, true).await?;
// check that only the builder tx and block proof is in the block
let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?;
let txs = block.transactions.into_transactions_vec();
-
if_flashblocks!(
- assert_eq!(txs.len(), 5, "Expected at 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof
+ assert_eq!(txs.len(), 5, "Expected 5 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof
// Check builder tx
assert_eq!(
txs[1].to(),
@@ -124,7 +78,7 @@ async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Res
);
);
if_standard!(
- assert_eq!(txs.len(), 4, "Expected at 4 transactions in block"); // deposit + valid tx + builder tx + end of block proof
+ assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + builder tx + end of block proof
);
let last_3_txs = &txs[txs.len() - 3..];
// Check valid transaction
@@ -148,6 +102,116 @@ async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Res
Ok(())
}
+#[rb_test(args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ enable_revert_protection: true,
+ flashtestations: FlashtestationsArgs {
+ flashtestations_enabled: true,
+ registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS),
+ builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS),
+ funding_key: Some(flashtestations_signer()),
+ debug: true,
+ enable_block_proofs: true,
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn test_flashtestations_invalid_quote(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let provider = rbuilder.provider().await?;
+ setup_flashtestation_contracts(&driver, &provider, false, true).await?;
+ // verify not registered
+ let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone());
+ let result = contract
+ .getRegistrationStatus(TEE_DEBUG_ADDRESS)
+ .call()
+ .await?;
+ assert!(
+ !result.isValid,
+ "The tee key is registered for invalid quote"
+ );
+ // check that only regular builder tx is in the block
+ let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?;
+ let txs = block.transactions.into_transactions_vec();
+
+ if_flashblocks!(
+ assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof
+ // Check builder tx
+ assert_eq!(
+ txs[1].to(),
+ Some(Address::ZERO),
+ "builder tx should send to zero address"
+ );
+ );
+ if_standard!(
+ assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof
+ );
+ let last_txs = &txs[txs.len() - 2..];
+ // Check user transaction
+ assert_eq!(
+ last_txs[0].inner.tx_hash(),
+ tx_hash,
+ "tx hash for user transaction should match"
+ );
+ // Check builder tx
+ assert_eq!(
+ last_txs[1].to(),
+ Some(Address::ZERO),
+ "builder tx should send to zero address"
+ );
+ Ok(())
+}
+
+#[rb_test(args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ enable_revert_protection: true,
+ flashtestations: FlashtestationsArgs {
+ flashtestations_enabled: true,
+ registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS),
+ builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS),
+ funding_key: Some(flashtestations_signer()),
+ debug: true,
+ enable_block_proofs: true,
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let provider = rbuilder.provider().await?;
+ setup_flashtestation_contracts(&driver, &provider, true, false).await?;
+ // check that only the regular builder tx is in the block
+ let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?;
+ let txs = block.transactions.into_transactions_vec();
+
+ if_flashblocks!(
+ assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof
+ // Check builder tx
+ assert_eq!(
+ txs[1].to(),
+ Some(Address::ZERO),
+ "builder tx should send to zero address"
+ );
+ );
+ if_standard!(
+ assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof
+ );
+ let last_txs = &txs[txs.len() - 2..];
+ // Check user transaction
+ assert_eq!(
+ last_txs[0].inner.tx_hash(),
+ tx_hash,
+ "tx hash for user transaction should match"
+ );
+ // Check builder tx
+ assert_eq!(
+ last_txs[1].to(),
+ Some(Address::ZERO),
+ "builder tx should send to zero address"
+ );
+ Ok(())
+}
+
#[rb_test(flashblocks, args = OpRbuilderArgs {
chain_block_time: 1000,
enable_revert_protection: true,
@@ -170,7 +234,7 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e
let driver = rbuilder.driver().await?;
let provider = rbuilder.provider().await?;
setup_flashblock_number_contract(&driver, &provider, true).await?;
- setup_flashtestation_contracts(&driver, &provider, true).await?;
+ setup_flashtestation_contracts(&driver, &provider, true, true).await?;
let tx = driver
.create_transaction()
.random_valid_transfer()
@@ -178,9 +242,9 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e
.send()
.await?;
let block = driver.build_new_block_with_current_timestamp(None).await?;
- // 1 deposit tx, 1 fallback builder tx, 4 flashblocks number tx, valid tx, funding tx, registration tx, block proof
+ // 1 deposit tx, 1 fallback builder tx, 4 flashblocks number tx, valid tx, block proof
let txs = block.transactions.into_transactions_vec();
- assert_eq!(txs.len(), 10, "Expected at 10 transactions in block");
+ assert_eq!(txs.len(), 8, "Expected at 8 transactions in block");
// Check builder tx
assert_eq!(
txs[1].to(),
@@ -192,7 +256,8 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e
assert_eq!(
txs[i].to(),
Some(FLASHBLOCKS_NUMBER_ADDRESS),
- "builder tx should send to flashblocks number contract"
+ "builder tx should send to flashblocks number contract at index {}",
+ i
);
}
// check regular tx
@@ -201,24 +266,138 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e
*tx.tx_hash(),
"bundle tx was not in block"
);
- // check funding, registration and block proof tx
+ // check block proof tx
assert_eq!(
txs[7].to(),
- Some(TEE_DEBUG_ADDRESS),
- "funding tx should send to tee address"
+ Some(BLOCK_BUILDER_POLICY_ADDRESS),
+ "block proof tx should call block policy address"
);
+ // Verify flashblock number incremented correctly
+ let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone());
+ let current_number = contract.getFlashblockNumber().call().await?;
+ assert!(
+ current_number.gt(&U256::from(8)), // contract deployments incremented the number but we built at least 2 full blocks
+ "Flashblock number not incremented"
+ );
+ Ok(())
+}
+
+#[rb_test(args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ enable_revert_protection: true,
+ flashtestations: FlashtestationsArgs {
+ flashtestations_enabled: true,
+ registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS),
+ builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS),
+ funding_key: Some(flashtestations_signer()),
+ debug: true,
+ flashtestations_use_permit: true,
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn test_flashtestations_permit_registration(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let provider = rbuilder.provider().await?;
+ setup_flashtestation_contracts(&driver, &provider, true, true).await?;
+ // check builder does not try to register again
+ let block = driver.build_new_block_with_current_timestamp(None).await?;
+ let num_txs = block.transactions.len();
+ if_flashblocks!(
+ assert!(num_txs == 3, "Expected at 3 transactions in block"); // deposit + 2 builder tx
+ );
+ if_standard!(
+ assert!(num_txs == 2, "Expected at 2 transactions in block"); // deposit + builder tx
+ );
+ // check that the tee signer did not send any transactions
+ let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?;
+ assert!(balance.is_zero());
+ let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?;
+ assert_eq!(nonce, 0);
+ Ok(())
+}
+
+#[rb_test(args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ enable_revert_protection: true,
+ flashtestations: FlashtestationsArgs {
+ flashtestations_enabled: true,
+ registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS),
+ builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS),
+ funding_key: Some(flashtestations_signer()),
+ debug: true,
+ enable_block_proofs: true,
+ flashtestations_use_permit: true,
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn test_flashtestations_permit_block_proof(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let provider = rbuilder.provider().await?;
+ setup_flashtestation_contracts(&driver, &provider, true, true).await?;
+ // check builder does not try to register again
+ let block = driver.build_new_block_with_current_timestamp(None).await?;
+ let num_txs = block.transactions.len();
+ if_flashblocks!(
+ assert!(num_txs == 4, "Expected at 4 transactions in block"); // deposit + 2 builder tx
+ );
+ if_standard!(
+ assert!(num_txs == 3, "Expected at 3 transactions in block"); // deposit + builder tx
+ );
+ let last_2_txs = &block.transactions.into_transactions_vec()[num_txs - 2..];
+ // Check builder tx
assert_eq!(
- txs[8].to(),
- Some(FLASHTESTATION_REGISTRY_ADDRESS),
- "registration tx should call registry"
+ last_2_txs[0].to(),
+ Some(Address::ZERO),
+ "builder tx should send to zero address"
);
+ // check builder proof
assert_eq!(
- txs[9].to(),
+ last_2_txs[1].to(),
Some(BLOCK_BUILDER_POLICY_ADDRESS),
- "block proof tx should call block policy address"
+ "builder tx should send to flashtestations builder policy address"
);
+ assert_eq!(
+ last_2_txs[1].from(),
+ builder_signer().address,
+ "block proof tx should come from builder address"
+ );
+ // check that the tee signer did not send any transactions
+ let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?;
+ assert!(balance.is_zero());
+ let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?;
+ assert_eq!(nonce, 0);
+
+ Ok(())
+}
- // add a user transaciton to ensure the flashblock number builder tx is top of block
+#[rb_test(flashblocks, args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ enable_revert_protection: true,
+ flashblocks: FlashblocksArgs {
+ flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS),
+ ..Default::default()
+ },
+ flashtestations: FlashtestationsArgs {
+ flashtestations_enabled: true,
+ registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS),
+ builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS),
+ funding_key: Some(flashtestations_signer()),
+ debug: true,
+ flashtestations_use_permit: true,
+ enable_block_proofs: true,
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn test_flashtestations_permit_with_flashblocks_number_contract(
+ rbuilder: LocalInstance,
+) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let provider = rbuilder.provider().await?;
+ setup_flashtestation_contracts(&driver, &provider, true, true).await?;
+ setup_flashblock_number_contract(&driver, &provider, true).await?;
let tx = driver
.create_transaction()
.random_valid_transfer()
@@ -226,44 +405,47 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e
.send()
.await?;
let block = driver.build_new_block_with_current_timestamp(None).await?;
- // check the flashblocks number tx and block proof is in the block
+ // check the builder tx, funding tx and registration tx is in the block
+ let num_txs = block.transactions.len();
let txs = block.transactions.into_transactions_vec();
- assert_eq!(txs.len(), 8, "Expected at 5 transactions in block");
+ // // 1 deposit tx, 1 regular builder tx, 4 flashblocks number tx, 1 user tx, 1 block proof tx
+ assert_eq!(num_txs, 8, "Expected 8 transactions in block");
// Check builder tx
assert_eq!(
txs[1].to(),
Some(Address::ZERO),
- "fallback builder tx should send to zero address"
+ "builder tx should send to zero address"
);
// flashblocks number contract
for i in 2..6 {
assert_eq!(
txs[i].to(),
Some(FLASHBLOCKS_NUMBER_ADDRESS),
- "builder tx should send to flashblocks number contract"
+ "builder tx should send to flashblocks number contract at index {}",
+ i
);
}
// user tx
assert_eq!(
txs[6].tx_hash(),
*tx.tx_hash(),
- "bundle tx was not in block"
+ "user tx should be in correct position in block"
);
- // block proof
assert_eq!(
txs[7].to(),
Some(BLOCK_BUILDER_POLICY_ADDRESS),
- "block proof tx should call block policy address"
+ "builder tx should send verify block builder proof"
);
-
- let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone());
- let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?;
- assert!(result._1.isValid, "The tee key is not registered");
+ // check that the tee signer did not send any transactions
+ let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?;
+ assert!(balance.is_zero());
+ let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?;
+ assert_eq!(nonce, 0);
// Verify flashblock number incremented correctly
let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone());
let current_number = contract.getFlashblockNumber().call().await?;
assert!(
- current_number.gt(&U256::from(8)), // contract deployments incremented the number but we built at least 2 full blocks
+ current_number.gt(&U256::from(4)), // contract deployments incremented the number but we built at least 1 full block
"Flashblock number not incremented"
);
Ok(())
@@ -272,6 +454,7 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e
async fn setup_flashtestation_contracts(
driver: &ChainDriver,
provider: &RootProvider,
+ add_quote: bool,
authorize_workload: bool,
) -> eyre::Result<()> {
// deploy the mock contract and register a mock quote
@@ -282,15 +465,6 @@ async fn setup_flashtestation_contracts(
.send()
.await?;
- // Add test quote
- let mock_quote_tx = driver
- .create_transaction()
- .add_mock_quote()
- .with_to(MOCK_DCAP_ADDRESS)
- .with_bundle(BundleOpts::default())
- .send()
- .await?;
-
// deploy the flashtestations registry contract
let flashtestations_registry_tx = driver
.create_transaction()
@@ -328,6 +502,30 @@ async fn setup_flashtestation_contracts(
// include the deployment and initialization in a block
driver.build_new_block_with_current_timestamp(None).await?;
+ if add_quote {
+ // Add test quote
+ let mock_quote_tx = driver
+ .create_transaction()
+ .add_mock_quote()
+ .with_to(MOCK_DCAP_ADDRESS)
+ .with_bundle(BundleOpts::default())
+ .send()
+ .await?;
+ driver.build_new_block_with_current_timestamp(None).await?;
+ provider
+ .get_transaction_receipt(*mock_quote_tx.tx_hash())
+ .await?
+ .expect("add mock quote not mined");
+ // verify registered
+ let contract =
+ FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone());
+ let result = contract
+ .getRegistrationStatus(TEE_DEBUG_ADDRESS)
+ .call()
+ .await?;
+ assert!(result.isValid, "The tee key is not registered");
+ }
+
if authorize_workload {
// add the workload id to the block builder policy
let add_workload = driver
@@ -357,11 +555,6 @@ async fn setup_flashtestation_contracts(
mock_dcap_address, MOCK_DCAP_ADDRESS,
"mock dcap contract address mismatch"
);
- // verify mock quote added
- provider
- .get_transaction_receipt(*mock_quote_tx.tx_hash())
- .await?
- .expect("add mock quote not mined");
// verify flashtestations registry contract deployment
let receipt = provider
.get_transaction_receipt(*flashtestations_registry_tx.tx_hash())
diff --git a/crates/op-rbuilder/src/tests/mod.rs b/crates/op-rbuilder/src/tests/mod.rs
index cb5646f8..17be8d09 100644
--- a/crates/op-rbuilder/src/tests/mod.rs
+++ b/crates/op-rbuilder/src/tests/mod.rs
@@ -35,7 +35,7 @@ const MOCK_DCAP_ADDRESS: alloy_primitives::Address =
alloy_primitives::address!("700b6a60ce7eaaea56f065753d8dcb9653dbad35");
#[cfg(test)]
const FLASHTESTATION_REGISTRY_ADDRESS: alloy_primitives::Address =
- alloy_primitives::address!("b19b36b1456e65e3a6d514d3f715f204bd59f431");
+ alloy_primitives::address!("a15bb66138824a1c7167f5e85b957d04dd34e468");
#[cfg(test)]
const BLOCK_BUILDER_POLICY_ADDRESS: alloy_primitives::Address =
- alloy_primitives::address!("e1aa25618fa0c7a1cfdab5d6b456af611873b629");
+ alloy_primitives::address!("8ce361602b935680e8dec218b820ff5056beb7af");