From dcd1cd54eb663bfd1afcd915df0c913af9f465ad Mon Sep 17 00:00:00 2001 From: avalonche Date: Tue, 22 Jul 2025 10:39:36 +1000 Subject: [PATCH 01/14] Add flashblocks index to payload building context --- .../src/builders/flashblocks/payload.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index 1403b76..3e18434 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -415,14 +415,12 @@ where ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); - // If it is the last flashblock, we need to account for the builder tx - if ctx.is_last_flashblock() { - total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); - // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size - if let Some(da_limit) = total_da_per_batch.as_mut() { - *da_limit = da_limit.saturating_sub(builder_tx_da_size); - } + total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); + // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size + if let Some(da_limit) = total_da_per_batch.as_mut() { + *da_limit = da_limit.saturating_sub(builder_tx_da_size); } + let mut db = State::builder() .with_database(state) .with_bundle_update() @@ -474,10 +472,7 @@ where .payload_tx_simulation_gauge .set(payload_tx_simulation_time); - // If it is the last flashblocks, add the builder txn to the block if enabled - if ctx.is_last_flashblock() { - ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); - }; + ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); let total_block_built_duration = Instant::now(); let build_result = build_block(db, &ctx, &mut info); From 1e64801e6fddb2955d31d8dc684a1f5575e88385 Mon Sep 17 00:00:00 2001 From: avalonche Date: Wed, 25 Jun 2025 17:20:35 +1000 Subject: [PATCH 02/14] Add flashtestations builder transaction trait to payload builder --- crates/op-rbuilder/src/builders/builder_tx.rs | 223 ++++++++++- crates/op-rbuilder/src/builders/context.rs | 175 +------- .../src/builders/flashblocks/payload.rs | 56 +-- .../src/builders/flashblocks/service.rs | 18 +- crates/op-rbuilder/src/builders/mod.rs | 5 +- .../src/builders/standard/payload.rs | 81 ++-- .../op-rbuilder/src/flashtestations/args.rs | 8 +- .../src/flashtestations/builder_tx.rs | 379 ++++++++++++++++++ crates/op-rbuilder/src/flashtestations/mod.rs | 26 ++ .../src/flashtestations/service.rs | 181 ++++----- .../src/flashtestations/tx_manager.rs | 93 +---- 11 files changed, 817 insertions(+), 428 deletions(-) create mode 100644 crates/op-rbuilder/src/flashtestations/builder_tx.rs diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 3dcb898..e8f184b 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -1,12 +1,133 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718}; +use alloy_primitives::{ + map::foldhash::{HashSet, HashSetExt}, + Address, TxKind, +}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_evm::{eth::receipt_builder::ReceiptBuilderCtx, ConfigureEvm, Evm}; +use reth_node_api::PayloadBuilderError; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; +use reth_provider::ProviderError; +use reth_revm::State; +use revm::{context::result::ResultAndState, Database, DatabaseCommit}; +use tracing::{debug, warn}; -use crate::tx_signer::Signer; +use crate::{ + builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer, +}; -pub trait BuilderTx { - fn estimated_builder_tx_gas(&self) -> u64; - fn estimated_builder_tx_da_size(&self) -> Option; - fn signed_builder_tx(&self) -> Result, secp256k1::Error>; +pub struct BuilderTransactionCtx { + pub gas_used: u64, + pub da_size: u64, + pub signed_tx: Recovered, +} + +/// Possible error variants during construction of builder txs. +#[derive(Debug, thiserror::Error)] +pub enum BuilderTransactionError { + /// Thrown when builder account load fails to get builder nonce + #[error("failed to load account {0}")] + AccountLoadFailed(Address), + /// Thrown when signature signing fails + #[error("failed to sign transaction: {0}")] + SigningError(secp256k1::Error), + /// Unrecoverable error during evm execution. + #[error("evm execution error {0}")] + EvmExecutionError(Box), + /// Any other builder transaction errors. + #[error(transparent)] + Other(Box), +} + +impl From for BuilderTransactionError { + fn from(error: secp256k1::Error) -> Self { + BuilderTransactionError::SigningError(error) + } +} + +impl From for PayloadBuilderError { + fn from(error: BuilderTransactionError) -> Self { + match error { + BuilderTransactionError::EvmExecutionError(e) => { + PayloadBuilderError::EvmExecutionError(e) + } + _ => PayloadBuilderError::Other(Box::new(error)), + } + } +} + +pub trait BuilderTransactions { + fn simulate_builder_txs( + &self, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> + where + DB: Database; + + fn add_builder_txs( + &self, + info: &mut ExecutionInfo, + builder_ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result<(), BuilderTransactionError> + where + DB: Database, + { + { + let mut evm = builder_ctx + .evm_config + .evm_with_env(&mut *db, builder_ctx.evm_env.clone()); + let mut invalid: HashSet
= HashSet::new(); + let builder_txs = self.simulate_builder_txs(info, builder_ctx, evm.db_mut())?; + for builder_tx in builder_txs { + if invalid.contains(&builder_tx.signed_tx.signer()) { + debug!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); + continue; + } + + let ResultAndState { result, state } = evm + .transact(&builder_tx.signed_tx) + .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"); + invalid.insert(builder_tx.signed_tx.signer()); + continue; + } + + // Add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); + info.cumulative_gas_used += gas_used; + + let ctx = ReceiptBuilderCtx { + tx: builder_tx.signed_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(builder_ctx.build_receipt(ctx, None)); + + // Commit changes + evm.db_mut().commit(state); + + // Append sender and transaction to the respective lists + info.executed_senders.push(builder_tx.signed_tx.signer()); + info.executed_transactions + .push(builder_tx.signed_tx.into_inner()); + } + + // Release the db reference by dropping evm + drop(evm); + + Ok(()) + } + } } // Scaffolding for how to construct the end of block builder transaction @@ -17,16 +138,94 @@ pub struct StandardBuilderTx { pub signer: Option, } -impl BuilderTx for StandardBuilderTx { - fn estimated_builder_tx_gas(&self) -> u64 { - todo!() +impl StandardBuilderTx { + pub fn new(signer: Option) -> Self { + Self { signer } + } + + fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { + // Count zero and non-zero bytes + let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { + if byte == 0 { + (zeros + 1, nonzeros) + } else { + (zeros, nonzeros + 1) + } + }); + + // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) + let zero_cost = zero_bytes * 4; + let nonzero_cost = nonzero_bytes * 16; + + // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 + let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; + let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) } - fn estimated_builder_tx_da_size(&self) -> Option { - todo!() + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> + where + DB: Database, + { + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?; + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + gas_limit: gas_used, + max_fee_per_gas: ctx.base_fee().into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(Address::ZERO), + // Include the message as part of the transaction data + input: message.into(), + ..Default::default() + }); + // Sign the transaction + let builder_tx = signer + .sign_tx(tx) + .map_err(BuilderTransactionError::SigningError)?; + + Ok(builder_tx) } +} - fn signed_builder_tx(&self) -> Result, secp256k1::Error> { - todo!() +impl BuilderTransactions for StandardBuilderTx { + fn simulate_builder_txs( + &self, + _info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> + where + DB: Database, + { + match self.signer { + Some(signer) => { + let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); + let gas_used = self.estimate_builder_tx_gas(&message); + let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + signed_tx.encoded_2718().as_slice(), + ); + Ok(vec![BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + }]) + } + None => Ok(vec![]), + } } } diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index e440877..8ca514a 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -1,12 +1,10 @@ -use alloy_consensus::{ - conditional::BlockConditionalAttributes, Eip658Value, Transaction, TxEip1559, -}; -use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718, Typed2718}; +use alloy_consensus::{conditional::BlockConditionalAttributes, Eip658Value, Transaction}; +use alloy_eips::Typed2718; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_primitives::{BlockHash, Bytes, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; -use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; +use op_alloy_consensus::OpDepositReceipt; use op_revm::OpSpecId; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::PayloadConfig; @@ -27,7 +25,7 @@ use reth_optimism_txpool::{ interop::{is_valid_interop, MaybeInteropTransaction}, }; use reth_payload_builder::PayloadId; -use reth_primitives::{Recovered, SealedHeader}; +use reth_primitives::SealedHeader; use reth_primitives_traits::{InMemorySize, SignedTransaction}; use reth_provider::ProviderError; use reth_revm::{context::Block, State}; @@ -37,7 +35,7 @@ use revm::{ }; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; -use tracing::{debug, info, trace, warn}; +use tracing::{debug, info, trace}; use crate::{ metrics::OpRBuilderMetrics, @@ -191,6 +189,16 @@ impl OpPayloadBuilderCtx { self.chain_spec.chain_id() } + /// Returns the parent hash + pub fn parent_hash(&self) -> BlockHash { + self.parent().hash() + } + + /// Returns the timestamp + pub fn timestamp(&self) -> u64 { + self.attributes().timestamp() + } + /// Returns the builder signer pub fn builder_signer(&self) -> Option { self.builder_signer @@ -199,7 +207,7 @@ impl OpPayloadBuilderCtx { impl OpPayloadBuilderCtx { /// Constructs a receipt for the given transaction. - fn build_receipt( + pub fn build_receipt( &self, ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, deposit_nonce: Option, @@ -337,7 +345,11 @@ impl OpPayloadBuilderCtx { let mut num_bundles_reverted = 0; let base_fee = self.base_fee(); let tx_da_limit = self.da_config.max_da_tx_size(); - let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + let mut evm: alloy_op_evm::OpEvm< + &mut State, + revm::inspector::NoOpInspector, + reth_evm::precompiles::PrecompilesMap, + > = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); info!( target: "payload_builder", @@ -539,147 +551,4 @@ impl OpPayloadBuilderCtx { ); Ok(None) } - - pub fn add_builder_tx( - &self, - info: &mut ExecutionInfo, - db: &mut State, - builder_tx_gas: u64, - message: Vec, - ) -> Option<()> - where - DB: Database + std::fmt::Debug, - { - self.builder_signer() - .map(|signer| { - let base_fee = self.base_fee(); - let chain_id = self.chain_id(); - // Create and sign the transaction - let builder_tx = - signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - - let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); - - let ResultAndState { result, state } = evm - .transact(&builder_tx) - .map_err(|err| PayloadBuilderError::EvmExecutionError(Box::new(err)))?; - - // Add gas used by the transaction to cumulative gas used, before creating the receipt - let gas_used = result.gas_used(); - info.cumulative_gas_used += gas_used; - - let ctx = ReceiptBuilderCtx { - tx: builder_tx.inner(), - evm: &evm, - result, - state: &state, - cumulative_gas_used: info.cumulative_gas_used, - }; - info.receipts.push(self.build_receipt(ctx, None)); - - // Release the db reference by dropping evm - drop(evm); - // Commit changes - db.commit(state); - - // Append sender and transaction to the respective lists - info.executed_senders.push(builder_tx.signer()); - info.executed_transactions.push(builder_tx.into_inner()); - Ok(()) - }) - .transpose() - .unwrap_or_else(|err: PayloadBuilderError| { - warn!(target: "payload_builder", %err, "Failed to add builder transaction"); - None - }) - } - - /// Calculates EIP 2718 builder transaction size - // TODO: this function could be improved, ideally we shouldn't take mut ref to db and maybe - // it's possible to do this without db at all - pub fn estimate_builder_tx_da_size( - &self, - db: &mut State, - builder_tx_gas: u64, - message: Vec, - ) -> Option - where - DB: Database, - { - self.builder_signer() - .map(|signer| { - let base_fee = self.base_fee(); - let chain_id = self.chain_id(); - // Create and sign the transaction - let builder_tx = - signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - Ok(op_alloy_flz::tx_estimated_size_fjord_bytes( - builder_tx.encoded_2718().as_slice(), - )) - }) - .transpose() - .unwrap_or_else(|err: PayloadBuilderError| { - warn!(target: "payload_builder", %err, "Failed to add builder transaction"); - None - }) - } -} - -pub fn estimate_gas_for_builder_tx(input: Vec) -> u64 { - // Count zero and non-zero bytes - let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { - if byte == 0 { - (zeros + 1, nonzeros) - } else { - (zeros, nonzeros + 1) - } - }); - - // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) - let zero_cost = zero_bytes * 4; - let nonzero_cost = nonzero_bytes * 16; - - // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 - let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; - let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; - - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) -} - -/// Creates signed builder tx to Address::ZERO and specified message as input -pub fn signed_builder_tx( - db: &mut State, - builder_tx_gas: u64, - message: Vec, - signer: Signer, - base_fee: u64, - chain_id: u64, -) -> Result, PayloadBuilderError> -where - DB: Database, -{ - // Create message with block number for the builder to sign - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed(signer.address)) - })?; - - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit: builder_tx_gas, - max_fee_per_gas: base_fee.into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(Address::ZERO), - // Include the message as part of the transaction data - input: message.into(), - ..Default::default() - }); - // Sign the transaction - let builder_tx = signer.sign_tx(tx).map_err(PayloadBuilderError::other)?; - - Ok(builder_tx) } diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index 3e18434..a905f23 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -1,10 +1,11 @@ use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; use crate::{ builders::{ - context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}, + builder_tx::BuilderTransactions, + context::OpPayloadBuilderCtx, flashblocks::config::FlashBlocksConfigExt, generator::{BlockCell, BuildArguments}, - BuilderConfig, BuilderTx, + BuilderConfig, }, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, @@ -97,7 +98,7 @@ impl OpPayloadBuilderCtx { /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilder { +pub struct OpPayloadBuilder { /// The type responsible for creating the evm. pub evm_config: OpEvmConfig, /// The transaction pool @@ -113,17 +114,17 @@ pub struct OpPayloadBuilder { pub metrics: Arc, /// The end of builder transaction type #[allow(dead_code)] - pub builder_tx: BT, + pub builder_tx: BuilderTx, } -impl OpPayloadBuilder { +impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. pub fn new( evm_config: OpEvmConfig, pool: Pool, client: Client, config: BuilderConfig, - builder_tx: BT, + builder_tx: BuilderTx, ) -> eyre::Result { let metrics = Arc::new(OpRBuilderMetrics::default()); let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); @@ -139,12 +140,12 @@ impl OpPayloadBuilder { } } -impl reth_basic_payload_builder::PayloadBuilder - for OpPayloadBuilder +impl reth_basic_payload_builder::PayloadBuilder + for OpPayloadBuilder where Pool: Clone + Send + Sync, Client: Clone + Send + Sync, - BT: Clone + Send + Sync, + BuilderTx: Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; @@ -167,11 +168,11 @@ where } } -impl OpPayloadBuilder +impl OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BT: BuilderTx, + BuilderTx: BuilderTransactions, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -263,15 +264,6 @@ where .with_bundle_update() .build(); - // We subtract gas limit and da limit for builder transaction from the whole limit - let message = format!("Block Number: {}", ctx.block_number()).into_bytes(); - let builder_tx_gas = ctx - .builder_signer() - .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); - let builder_tx_da_size = ctx - .estimate_builder_tx_da_size(&mut db, builder_tx_gas, message.clone()) - .unwrap_or(0); - let mut info = execute_pre_steps(&mut db, &ctx)?; let sequencer_tx_time = sequencer_tx_start_time.elapsed(); ctx.metrics.sequencer_tx_duration.record(sequencer_tx_time); @@ -279,9 +271,17 @@ where // If we have payload with txpool we add first builder tx right after deposits if !ctx.attributes().no_tx_pool { - ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); + self.builder_tx.add_builder_txs(&mut info, &ctx, &mut db)?; } + // We subtract gas limit and da limit for builder transaction from the whole limit + let builder_txs = self + .builder_tx + .simulate_builder_txs(&mut info, &ctx, &mut db) + .map_err(|err| PayloadBuilderError::Other(Box::new(err)))?; + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + let (payload, fb_payload, mut bundle_state) = build_block(db, &ctx, &mut info)?; best_payload.set(payload.clone()); @@ -348,7 +348,9 @@ where if let Some(da_limit) = da_per_batch { // We error if we can't insert any tx aside from builder tx in flashblock if da_limit / 2 < builder_tx_da_size { - error!("Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included."); + error!( + "Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included." + ); } } let mut total_da_per_batch = da_per_batch; @@ -526,7 +528,9 @@ where if let Some(da) = total_da_per_batch.as_mut() { *da += da_limit; } else { - error!("Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set"); + error!( + "Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set" + ); } } @@ -672,12 +676,12 @@ where } } -impl crate::builders::generator::PayloadBuilder - for OpPayloadBuilder +impl crate::builders::generator::PayloadBuilder + for OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BT: BuilderTx + Clone + Send + Sync, + BuilderTx: BuilderTransactions + Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs index c045a0c..f016b55 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs @@ -1,10 +1,11 @@ use super::{payload::OpPayloadBuilder, FlashblocksConfig}; use crate::{ builders::{ - builder_tx::StandardBuilderTx, generator::BlockPayloadJobGenerator, BuilderConfig, - BuilderTx, + builder_tx::{BuilderTransactions, StandardBuilderTx}, + generator::BlockPayloadJobGenerator, + BuilderConfig, }, - flashtestations::service::spawn_flashtestations_service, + flashtestations::service::bootstrap_flashtestations, traits::{NodeBounds, PoolBounds}, }; use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; @@ -17,16 +18,16 @@ use reth_provider::CanonStateSubscriptions; pub struct FlashblocksServiceBuilder(pub BuilderConfig); impl FlashblocksServiceBuilder { - fn spawn_payload_builder_service( + fn spawn_payload_builder_service( self, ctx: &BuilderContext, pool: Pool, - builder_tx: BT, + builder_tx: BuilderTx, ) -> eyre::Result::Payload>> where Node: NodeBounds, Pool: PoolBounds, - BT: BuilderTx + Unpin + Clone + Send + Sync + 'static, + BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, { let payload_builder = OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), @@ -73,9 +74,10 @@ where tracing::debug!("Spawning flashblocks payload builder service"); let signer = self.0.builder_signer; if self.0.flashtestations_config.flashtestations_enabled { - let flashtestations_service = match spawn_flashtestations_service( + let flashtestations_builder_tx = match bootstrap_flashtestations( self.0.flashtestations_config.clone(), ctx, + signer, ) .await { @@ -91,7 +93,7 @@ where }; if self.0.flashtestations_config.enable_block_proofs { - return self.spawn_payload_builder_service(ctx, pool, flashtestations_service); + return self.spawn_payload_builder_service(ctx, pool, flashtestations_builder_tx); } } self.spawn_payload_builder_service(ctx, pool, StandardBuilderTx { signer }) diff --git a/crates/op-rbuilder/src/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs index 1c41f37..17b78b1 100644 --- a/crates/op-rbuilder/src/builders/mod.rs +++ b/crates/op-rbuilder/src/builders/mod.rs @@ -20,7 +20,10 @@ mod flashblocks; mod generator; mod standard; -pub use builder_tx::BuilderTx; +pub use builder_tx::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, StandardBuilderTx, +}; +pub use context::OpPayloadBuilderCtx; pub use flashblocks::FlashblocksBuilder; pub use standard::StandardBuilder; diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index 2a1b846..ae18933 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -1,6 +1,9 @@ use crate::{ - builders::{generator::BuildArguments, BuilderConfig}, - flashtestations::service::spawn_flashtestations_service, + builders::{ + builder_tx::StandardBuilderTx, generator::BuildArguments, BuilderConfig, + BuilderTransactions, + }, + flashtestations::service::bootstrap_flashtestations, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, @@ -38,7 +41,7 @@ use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; -use super::super::context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}; +use super::super::context::OpPayloadBuilderCtx; pub struct StandardPayloadBuilderBuilder(pub BuilderConfig<()>); @@ -47,7 +50,7 @@ where Node: NodeBounds, Pool: PoolBounds, { - type PayloadBuilder = StandardOpPayloadBuilder; + type PayloadBuilder = StandardOpPayloadBuilder; async fn build_payload_builder( self, @@ -55,9 +58,16 @@ where pool: Pool, _evm_config: OpEvmConfig, ) -> eyre::Result { + let signer = self.0.builder_signer; if self.0.flashtestations_config.flashtestations_enabled { - match spawn_flashtestations_service(self.0.flashtestations_config.clone(), ctx).await { - Ok(service) => service, + let _flashtestation_builder_tx = match bootstrap_flashtestations( + self.0.flashtestations_config.clone(), + ctx, + signer, + ) + .await + { + Ok(flashtestation_builder_tx) => flashtestation_builder_tx, Err(e) => { tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); return Ok(StandardOpPayloadBuilder::new( @@ -65,12 +75,19 @@ where pool, ctx.provider().clone(), self.0.clone(), + StandardBuilderTx::new(self.0.builder_signer), )); } }; if self.0.flashtestations_config.enable_block_proofs { - // TODO: flashtestations end of block transaction + // return Ok(StandardOpPayloadBuilder::new( + // OpEvmConfig::optimism(ctx.chain_spec()), + // pool, + // ctx.provider().clone(), + // self.0.clone(), + // flashtestation_builder_tx, + // )); } } @@ -79,13 +96,14 @@ where pool, ctx.provider().clone(), self.0.clone(), + StandardBuilderTx::new(self.0.builder_signer), )) } } /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct StandardOpPayloadBuilder { +pub struct StandardOpPayloadBuilder { /// The type responsible for creating the evm. pub evm_config: OpEvmConfig, /// The transaction pool @@ -99,15 +117,18 @@ pub struct StandardOpPayloadBuilder { pub best_transactions: Txs, /// The metrics for the builder pub metrics: Arc, + /// The type responsible for creating the builder transactions + pub builder_tx: BuilderTx, } -impl StandardOpPayloadBuilder { +impl StandardOpPayloadBuilder { /// `OpPayloadBuilder` constructor. pub fn new( evm_config: OpEvmConfig, pool: Pool, client: Client, config: BuilderConfig<()>, + builder_tx: BuilderTx, ) -> Self { Self { pool, @@ -116,6 +137,7 @@ impl StandardOpPayloadBuilder { evm_config, best_transactions: (), metrics: Default::default(), + builder_tx, } } } @@ -146,11 +168,12 @@ impl OpPayloadTransactions for () { } } -impl reth_basic_payload_builder::PayloadBuilder - for StandardOpPayloadBuilder +impl reth_basic_payload_builder::PayloadBuilder + for StandardOpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, + BuilderTx: BuilderTransactions + Clone + Send + Sync, Txs: OpPayloadTransactions, { type Attributes = OpPayloadBuilderAttributes; @@ -209,10 +232,11 @@ where } } -impl StandardOpPayloadBuilder +impl StandardOpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, + BuilderTx: BuilderTransactions + Clone, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -288,14 +312,14 @@ where .with_database(state) .with_bundle_update() .build(); - builder.build(db, ctx) + builder.build(db, ctx, self.builder_tx.clone()) } else { // sequencer mode we can reuse cachedreads from previous runs let db = State::builder() .with_database(cached_reads.as_db_mut(state)) .with_bundle_update() .build(); - builder.build(db, ctx) + builder.build(db, ctx, self.builder_tx.clone()) } .map(|out| { let total_block_building_time = block_build_start_time.elapsed(); @@ -349,14 +373,16 @@ pub struct ExecutedPayload { impl OpBuilder<'_, Txs> { /// Executes the payload and returns the outcome. - pub fn execute( + pub fn execute( self, state: &mut State, ctx: &OpPayloadBuilderCtx, + builder_tx: BuilderTx, ) -> Result, PayloadBuilderError> where DB: Database + AsRef

+ std::fmt::Debug, P: StorageRootProvider, + BuilderTx: BuilderTransactions, { let Self { best } = self; info!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); @@ -379,17 +405,14 @@ impl OpBuilder<'_, Txs> { // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx - let message = format!("Block Number: {}", ctx.block_number()) - .as_bytes() - .to_vec(); - let builder_tx_gas = ctx - .builder_signer() - .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); - let block_gas_limit = ctx.block_gas_limit() - builder_tx_gas; + let builder_txs = builder_tx.simulate_builder_txs(&mut info, ctx, state)?; + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + let block_gas_limit = ctx.block_gas_limit().saturating_sub(builder_tx_gas); + if block_gas_limit == 0 { + error!("Builder tx gas subtraction resulted in block gas limit to be 0. No transactions would be included"); + } // Save some space in the block_da_limit for builder tx - let builder_tx_da_size = ctx - .estimate_builder_tx_da_size(state, builder_tx_gas, message.clone()) - .unwrap_or(0); + let builder_tx_da_size = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); let block_da_limit = ctx .da_config .max_da_block_size() @@ -427,7 +450,7 @@ impl OpBuilder<'_, Txs> { } // Add builder tx to the block - ctx.add_builder_tx(&mut info, state, builder_tx_gas, message); + builder_tx.add_builder_txs(&mut info, ctx, state)?; let state_merge_start_time = Instant::now(); @@ -457,16 +480,18 @@ impl OpBuilder<'_, Txs> { } /// Builds the payload on top of the state. - pub fn build( + pub fn build( self, mut state: State, ctx: OpPayloadBuilderCtx, + builder_tx: BuilderTx, ) -> Result, PayloadBuilderError> where DB: Database + AsRef

+ std::fmt::Debug, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + BuilderTx: BuilderTransactions, { - let ExecutedPayload { info } = match self.execute(&mut state, &ctx)? { + let ExecutedPayload { info } = match self.execute(&mut state, &ctx, builder_tx)? { BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), BuildOutcomeKind::Aborted { fees } => return Ok(BuildOutcomeKind::Aborted { fees }), diff --git a/crates/op-rbuilder/src/flashtestations/args.rs b/crates/op-rbuilder/src/flashtestations/args.rs index 4af8342..3c242c5 100644 --- a/crates/op-rbuilder/src/flashtestations/args.rs +++ b/crates/op-rbuilder/src/flashtestations/args.rs @@ -28,12 +28,8 @@ pub struct FlashtestationsArgs { pub debug_url: Option, /// The rpc url to post the onchain attestation requests to - #[arg( - long = "flashtestations.rpc-url", - env = "FLASHTESTATIONS_RPC_URL", - default_value = "http://localhost:8545" - )] - pub rpc_url: String, + #[arg(long = "flashtestations.rpc-url", env = "FLASHTESTATIONS_RPC_URL")] + pub rpc_url: Option, /// Funding key for the TEE key #[arg( diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs new file mode 100644 index 0000000..068fa13 --- /dev/null +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -0,0 +1,379 @@ +use alloy::sol_types::{SolCall, SolEvent, SolValue}; +use alloy_consensus::TxEip1559; +use alloy_eips::Encodable2718; +use alloy_op_evm::OpEvm; +use alloy_primitives::{keccak256, Address, Bytes, TxKind, B256, U256}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::{Log, Recovered}; +use reth_revm::State; +use revm::{ + context::result::{ExecutionResult, ResultAndState}, + inspector::NoOpInspector, +}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use tracing::{debug, error, info}; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, + StandardBuilderTx, + }, + flashtestations::{ + BlockData, IBlockBuilderPolicy, IFlashtestationRegistry, TEEServiceRegistered, + }, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +pub struct FlashtestationsBuilderTxArgs { + pub attestation: Vec, + pub tee_service_signer: Signer, + pub funding_key: Signer, + pub funding_amount: U256, + pub registry_address: Address, + pub builder_policy_address: Address, + pub builder_proof_version: u8, + pub builder_signer: Option, + pub registered: bool, +} + +#[derive(Clone)] +pub struct FlashtestationsBuilderTx { + // Attestation for the builder + attestation: Vec, + // TEE service generated key + tee_service_signer: Signer, + // Funding key for the TEE signer + funding_key: Signer, + // Funding amount for the TEE signer + funding_amount: U256, + // Registry address for the attestation + registry_address: Address, + // Builder policy address for the block builder proof + builder_policy_address: Address, + // Builder proof version + builder_proof_version: u8, + // Whether the workload and address has been registered + registered: Arc, + // fallback builder transaction implementation + fallback_builder_tx: StandardBuilderTx, +} + +impl FlashtestationsBuilderTx { + pub fn new(args: FlashtestationsBuilderTxArgs) -> Self { + Self { + attestation: args.attestation, + tee_service_signer: args.tee_service_signer, + funding_key: args.funding_key, + funding_amount: args.funding_amount, + registry_address: args.registry_address, + builder_policy_address: args.builder_policy_address, + builder_proof_version: args.builder_proof_version, + registered: Arc::new(AtomicBool::new(args.registered)), + fallback_builder_tx: StandardBuilderTx::new(args.builder_signer), + } + } + + fn signed_funding_tx( + &self, + to: Address, + from: Signer, + amount: U256, + base_fee: u64, + chain_id: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + info!(target: "flashtestations", "signing funding transaction"); + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id, + nonce, + gas_limit: 21000, + max_fee_per_gas: base_fee.into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(to), + value: amount, + ..Default::default() + }); + from.sign_tx(tx) + } + + fn signed_register_tee_service_tx( + &self, + attestation: Vec, + gas_limit: u64, + base_fee: u64, + chain_id: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + info!(target: "flashtestations", "signing registering tee service transaction"); + + let quote_bytes = Bytes::from(attestation); + let calldata = IFlashtestationRegistry::registerTEEServiceCall { + rawQuote: quote_bytes, + } + .abi_encode(); + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas: base_fee.into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(self.registry_address), + input: calldata.into(), + ..Default::default() + }); + self.tee_service_signer.sign_tx(tx) + } + + fn signed_block_builder_proof_tx( + &self, + transactions: Vec, + ctx: &OpPayloadBuilderCtx, + nonce: u64, + ) -> Result, secp256k1::Error> { + let block_content_hash = Self::compute_block_content_hash( + transactions, + ctx.parent_hash(), + ctx.block_number(), + ctx.timestamp(), + ); + + debug!(target: "flashtestations", block_content_hash = ?block_content_hash, "signing block builder proof transaction"); + let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { + version: self.builder_proof_version, + blockContentHash: block_content_hash, + } + .abi_encode(); + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + gas_limit: ctx.block_gas_limit(), + max_fee_per_gas: ctx.base_fee().into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(self.builder_policy_address), + input: calldata.into(), + ..Default::default() + }); + self.tee_service_signer.sign_tx(tx) + } + + /// Computes the block content hash according to the formula: + /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) + fn compute_block_content_hash( + transactions: Vec, + parent_hash: B256, + block_number: u64, + timestamp: u64, + ) -> B256 { + // Create ordered list of transaction hashes + let transaction_hashes: Vec = transactions + .iter() + .map(|tx| { + // RLP encode the transaction and hash it + let mut encoded = Vec::new(); + tx.encode_2718(&mut encoded); + keccak256(&encoded) + }) + .collect(); + + // Create struct and ABI encode + let block_data = BlockData { + parentHash: parent_hash, + blockNumber: U256::from(block_number), + timestamp: U256::from(timestamp), + transactionHashes: transaction_hashes, + }; + + let encoded = block_data.abi_encode(); + keccak256(&encoded) + } + + fn simulate_register_tee_service_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, + nonce: u64, + ) -> Result<(u64, Option>, bool), BuilderTransactionError> + where + DB: revm::Database, + { + let register_tx = self.signed_register_tee_service_tx( + self.attestation.clone(), + ctx.block_gas_limit(), + ctx.base_fee(), + ctx.chain_id(), + nonce, + )?; + let ResultAndState { result, .. } = evm + .transact(®ister_tx) + .map_err(|e| BuilderTransactionError::EvmExecutionError(Box::new(e)))?; + match result { + ExecutionResult::Success { gas_used, logs, .. } => { + for log in logs { + // Check if this is the TEEServiceRegistered event + if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { + match TEEServiceRegistered::decode_log(&log) { + Ok(decoded_event) => return Ok((gas_used, Some(decoded_event), true)), + Err(e) => { + error!(target: "flashtestations", "Failed to decode TEEServiceRegistered event: {}", e); + } + } + } + } + Ok((gas_used, None, true)) + } + _ => { + error!(target: "flashtestations", "register tee tx halted or reverted during simulation"); + Ok((0, None, false)) + } + } + } + + fn simulate_verify_block_proof_tx( + &self, + transactions: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, + nonce: u64, + ) -> Result<(u64, bool), BuilderTransactionError> + where + DB: revm::Database, + { + let verify_block_proof_tx = self.signed_block_builder_proof_tx(transactions, ctx, nonce)?; + let ResultAndState { result, .. } = evm + .transact(&verify_block_proof_tx) + .map_err(|e| BuilderTransactionError::EvmExecutionError(Box::new(e)))?; + match result { + ExecutionResult::Success { gas_used, .. } => Ok((gas_used, true)), + _ => { + error!(target: "flashtestations", "verify block proof tx halted or reverted during simulation"); + Ok((0, false)) + } + } + } +} + +impl BuilderTransactions for FlashtestationsBuilderTx { + fn simulate_builder_txs( + &self, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut reth_revm::State, + ) -> Result, BuilderTransactionError> + where + DB: revm::Database, + { + let mut builder_txs = Vec::::new(); + + let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); + let mut nonce: u64 = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + if !self.registered.load(Ordering::Relaxed) { + let (gas_used, log, success) = + self.simulate_register_tee_service_tx(ctx, &mut evm, nonce)?; + let registered = if let Some(log) = log { + log.data.alreadyExists + } else { + false + }; + self.registered.store(registered, Ordering::Relaxed); + + if !registered && success { + let balance = get_balance(evm.db_mut(), self.tee_service_signer.address)?; + if balance.is_zero() { + // funding transaction + let funding_nonce = get_nonce(evm.db_mut(), self.funding_key.address)?; + let funding_tx = self.signed_funding_tx( + self.tee_service_signer.address, + self.funding_key, + self.funding_amount, + ctx.base_fee(), + ctx.chain_id(), + funding_nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + funding_tx.encoded_2718().as_slice(), + ); + builder_txs.push(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: funding_tx, + }); + } + + let register_tx = self.signed_register_tee_service_tx( + self.attestation.clone(), + gas_used, + ctx.base_fee(), + ctx.chain_id(), + nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + register_tx.encoded_2718().as_slice(), + ); + builder_txs.push(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: register_tx, + }); + nonce += 1; + } + } + + // add verify block proof tx + let (gas_used, success) = self.simulate_verify_block_proof_tx( + info.executed_transactions.clone(), + ctx, + &mut evm, + nonce, + )?; + if success { + let verify_block_proof_tx = + self.signed_block_builder_proof_tx(info.executed_transactions.clone(), ctx, nonce)?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + verify_block_proof_tx.encoded_2718().as_slice(), + ); + builder_txs.push(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: verify_block_proof_tx, + }); + } else { + // if verify block proof tx fails, fallback to standard builder tx + builder_txs.extend( + self.fallback_builder_tx + .simulate_builder_txs(info, ctx, db)?, + ); + } + + Ok(builder_txs) + } +} + +fn get_nonce(db: &mut State, address: Address) -> Result +where + DB: revm::Database, +{ + db.load_cache_account(address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) +} + +fn get_balance(db: &mut State, address: Address) -> Result +where + DB: revm::Database, +{ + db.load_cache_account(address) + .map(|acc| acc.account_info().unwrap_or_default().balance) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) +} diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index a5b16e7..177ccb5 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -1,4 +1,30 @@ +use alloy::sol; + +sol!( + #[sol(rpc, abi)] + interface IFlashtestationRegistry { + function registerTEEService(bytes calldata rawQuote) external; + } + + #[sol(rpc, abi)] + interface IBlockBuilderPolicy { + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + } + + struct BlockData { + bytes32 parentHash; + uint256 blockNumber; + uint256 timestamp; + bytes32[] transactionHashes; + } + + event TEEServiceRegistered( + address teeAddress, bytes32 workloadId, bytes rawQuote, bytes publicKey, bool alreadyExists + ); +); + pub mod args; pub mod attestation; +pub mod builder_tx; pub mod service; pub mod tx_manager; diff --git a/crates/op-rbuilder/src/flashtestations/service.rs b/crates/op-rbuilder/src/flashtestations/service.rs index 650bce0..f3885a8 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -1,137 +1,114 @@ -use std::sync::Arc; - -use alloy_primitives::U256; use reth_node_builder::BuilderContext; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; use tracing::{info, warn}; use crate::{ - builders::BuilderTx, + flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, traits::NodeBounds, tx_signer::{generate_ethereum_keypair, Signer}, }; use super::{ args::FlashtestationsArgs, - attestation::{get_attestation_provider, AttestationConfig, AttestationProvider}, + attestation::{get_attestation_provider, AttestationConfig}, tx_manager::TxManager, }; -#[derive(Clone)] -pub struct FlashtestationsService { - // Attestation provider generating attestations - attestation_provider: Arc>, - // Handles the onchain attestation and TEE block building proofs - tx_manager: TxManager, - // TEE service generated key - tee_service_signer: Signer, - // Funding amount for the TEE signer - funding_amount: U256, -} - -// TODO: FlashtestationsService error types -impl FlashtestationsService { - pub fn new(args: FlashtestationsArgs) -> Self { - let (private_key, public_key, address) = generate_ethereum_keypair(); - let tee_service_signer = Signer { - address, - pubkey: public_key, - secret: private_key, - }; - - let attestation_provider = Arc::new(get_attestation_provider(AttestationConfig { - debug: args.debug, - debug_url: args.debug_url, - })); - - let tx_manager = TxManager::new( - tee_service_signer, - args.funding_key - .expect("funding key required when flashtestations enabled"), - args.rpc_url, - args.registry_address - .expect("registry address required when flashtestations enabled"), - args.builder_policy_address - .expect("builder policy address required when flashtestations enabled"), - args.builder_proof_version, - ); - - Self { - attestation_provider, - tx_manager, - tee_service_signer, - funding_amount: args.funding_amount, - } - } - - pub async fn bootstrap(&self) -> eyre::Result<()> { - // Prepare report data with public key (64 bytes, no 0x04 prefix) - let mut report_data = [0u8; 64]; - let pubkey_uncompressed = self.tee_service_signer.pubkey.serialize_uncompressed(); - report_data.copy_from_slice(&pubkey_uncompressed[1..65]); // Skip 0x04 prefix - - // Request TDX attestation - info!(target: "flashtestations", "requesting TDX attestation"); - let attestation = self.attestation_provider.get_attestation(report_data)?; - - // Submit report onchain by registering the key of the tee service - self.tx_manager - .fund_and_register_tee_service(attestation, self.funding_amount) - .await - } - - pub async fn clean_up(&self) -> eyre::Result<()> { - self.tx_manager.clean_up().await - } -} - -impl BuilderTx for FlashtestationsService { - fn estimated_builder_tx_gas(&self) -> u64 { - todo!() - } - - fn estimated_builder_tx_da_size(&self) -> Option { - todo!() - } - - fn signed_builder_tx(&self) -> Result, secp256k1::Error> { - todo!() - } -} - -pub async fn spawn_flashtestations_service( +// TODO: Flashtestations error types +pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, ctx: &BuilderContext, -) -> eyre::Result + builder_signer: Option, +) -> eyre::Result where Node: NodeBounds, { info!("Flashtestations enabled"); - let flashtestations_service = FlashtestationsService::new(args.clone()); - // Generates new key and registers the attestation onchain - flashtestations_service.bootstrap().await?; + let (private_key, public_key, address) = generate_ethereum_keypair(); + let tee_service_signer = Signer { + address, + pubkey: public_key, + secret: private_key, + }; + + let funding_key = args + .funding_key + .expect("funding key required when flashtestations enabled"); + let registry_address = args + .registry_address + .expect("registry address required when flashtestations enabled"); + let builder_policy_address = args + .builder_policy_address + .expect("builder policy address required when flashtestations enabled"); + + let attestation_provider = get_attestation_provider(AttestationConfig { + debug: args.debug, + debug_url: args.debug_url, + }); + + // Prepare report data with public key (64 bytes, no 0x04 prefix) + let mut report_data = [0u8; 64]; + let pubkey_uncompressed = tee_service_signer.pubkey.serialize_uncompressed(); + report_data.copy_from_slice(&pubkey_uncompressed[1..65]); // Skip 0x04 prefix + + // Request TDX attestation + info!(target: "flashtestations", "requesting TDX attestation"); + let attestation = attestation_provider.get_attestation(report_data)?; + + let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url { + let tx_manager = TxManager::new( + tee_service_signer, + funding_key, + rpc_url.clone(), + registry_address, + ); + // Submit report onchain by registering the key of the tee service + match tx_manager + .fund_and_register_tee_service(attestation.clone(), args.funding_amount) + .await + { + Ok(_) => (Some(tx_manager), true), + Err(e) => { + warn!(error = %e, "Failed to register tee service via rpc"); + (Some(tx_manager), false) + } + } + } else { + (None, false) + }; + + let builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { + attestation, + tee_service_signer, + funding_key, + funding_amount: args.funding_amount, + registry_address, + builder_policy_address, + builder_proof_version: args.builder_proof_version, + builder_signer, + registered, + }); - let flashtestations_clone = flashtestations_service.clone(); ctx.task_executor() .spawn_critical_with_graceful_shutdown_signal( "flashtestations clean up task", |shutdown| { Box::pin(async move { let graceful_guard = shutdown.await; - if let Err(e) = flashtestations_clone.clean_up().await { - warn!( - error = %e, - "Failed to complete clean up for flashtestations service", - ) - }; + if let Some(tx_manager) = tx_manager { + if let Err(e) = tx_manager.clean_up().await { + warn!( + error = %e, + "Failed to complete clean up for flashtestations service", + ); + } + } drop(graceful_guard) }) }, ); - Ok(flashtestations_service) + Ok(builder_tx) } #[cfg(test)] diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs index d6c6345..0fc3571 100644 --- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,13 +1,7 @@ -use alloy_consensus::TxEip1559; -use alloy_eips::Encodable2718; use alloy_network::ReceiptResponse; use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_rpc_types_eth::TransactionRequest; use alloy_transport::TransportResult; -use op_alloy_consensus::OpTypedTransaction; -use reth_optimism_node::OpBuiltPayload; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; use std::time::Duration; use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; @@ -16,26 +10,7 @@ use alloy_sol_types::{sol, SolCall, SolValue}; use op_alloy_network::Optimism; use tracing::{debug, error, info}; -use crate::tx_signer::Signer; - -sol!( - #[sol(rpc, abi)] - interface IFlashtestationRegistry { - function registerTEEService(bytes calldata rawQuote) external; - } - - #[sol(rpc, abi)] - interface IBlockBuilderPolicy { - function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; - } - - struct BlockData { - bytes32 parentHash; - uint256 blockNumber; - uint256 timestamp; - bytes32[] transactionHashes; - } -); +use crate::{flashtestations::IFlashtestationRegistry, tx_signer::Signer}; #[derive(Debug, Clone)] pub struct TxManager { @@ -43,8 +18,6 @@ pub struct TxManager { funding_signer: Signer, rpc_url: String, registry_address: Address, - builder_policy_address: Address, - builder_proof_version: u8, } impl TxManager { @@ -53,16 +26,12 @@ impl TxManager { funding_signer: Signer, rpc_url: String, registry_address: Address, - builder_policy_address: Address, - builder_proof_version: u8, ) -> Self { Self { tee_service_signer, funding_signer, rpc_url, registry_address, - builder_policy_address, - builder_proof_version, } } @@ -136,7 +105,6 @@ impl TxManager { let tx = TransactionRequest { from: Some(self.tee_service_signer.address), to: Some(TxKind::Call(self.registry_address)), - // gas: Some(10_000_000), // Set gas limit manually as the contract is gas heavy nonce: Some(0), input: calldata.into(), ..Default::default() @@ -153,36 +121,6 @@ impl TxManager { } } - pub fn signed_block_builder_proof( - &self, - payload: OpBuiltPayload, - gas_limit: u64, - base_fee: u64, - chain_id: u64, - nonce: u64, - ) -> Result, secp256k1::Error> { - let block_content_hash = Self::compute_block_content_hash(payload); - - info!(target: "flashtestations", block_content_hash = ?block_content_hash, "submitting block builder proof transaction"); - let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { - version: self.builder_proof_version, - blockContentHash: block_content_hash, - } - .abi_encode(); - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas: base_fee.into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(self.builder_policy_address), - input: calldata.into(), - ..Default::default() - }); - self.tee_service_signer.sign_tx(tx) - } - pub async fn clean_up(&self) -> eyre::Result<()> { info!(target: "flashtestations", "sending funds back from TEE generated key to funding address"); let provider = ProviderBuilder::new() @@ -235,33 +173,4 @@ impl TxManager { Err(e) => Err(e.into()), } } - - /// Computes the block content hash according to the formula: - /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) - fn compute_block_content_hash(payload: OpBuiltPayload) -> B256 { - let block = payload.block(); - let body = block.clone().into_body(); - let transactions = body.transactions(); - - // Create ordered list of transaction hashes - let transaction_hashes: Vec = transactions - .map(|tx| { - // RLP encode the transaction and hash it - let mut encoded = Vec::new(); - tx.encode_2718(&mut encoded); - keccak256(&encoded) - }) - .collect(); - - // Create struct and ABI encode - let block_data = BlockData { - parentHash: block.parent_hash, - blockNumber: U256::from(block.number), - timestamp: U256::from(block.timestamp), - transactionHashes: transaction_hashes, - }; - - let encoded = block_data.abi_encode(); - keccak256(&encoded) - } } From 0f015d23ac42d71ceaa850d645dcc0a5ffc381bf Mon Sep 17 00:00:00 2001 From: avalonche Date: Thu, 26 Jun 2025 08:44:51 +1000 Subject: [PATCH 03/14] add builder tx in standard builder --- crates/op-rbuilder/src/builders/builder_tx.rs | 4 +- .../src/builders/flashblocks/service.rs | 3 +- .../op-rbuilder/src/builders/standard/mod.rs | 15 ++- .../src/builders/standard/payload.rs | 67 +---------- .../src/builders/standard/service.rs | 107 ++++++++++++++++++ .../src/flashtestations/builder_tx.rs | 2 +- 6 files changed, 120 insertions(+), 78 deletions(-) create mode 100644 crates/op-rbuilder/src/builders/standard/service.rs diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index e8f184b..96f2160 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -59,7 +59,7 @@ impl From for PayloadBuilderError { } } -pub trait BuilderTransactions { +pub trait BuilderTransactions: Debug { fn simulate_builder_txs( &self, info: &mut ExecutionInfo, @@ -132,7 +132,7 @@ pub trait BuilderTransactions { // Scaffolding for how to construct the end of block builder transaction // This will be the regular end of block transaction without the TEE key -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct StandardBuilderTx { #[allow(dead_code)] pub signer: Option, diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs index f016b55..27721db 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs @@ -71,7 +71,6 @@ where pool: Pool, _: OpEvmConfig, ) -> eyre::Result::Payload>> { - tracing::debug!("Spawning flashblocks payload builder service"); let signer = self.0.builder_signer; if self.0.flashtestations_config.flashtestations_enabled { let flashtestations_builder_tx = match bootstrap_flashtestations( @@ -83,7 +82,7 @@ where { Ok(service) => service, Err(e) => { - tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, falling back to standard builder tx"); return self.spawn_payload_builder_service( ctx, pool, diff --git a/crates/op-rbuilder/src/builders/standard/mod.rs b/crates/op-rbuilder/src/builders/standard/mod.rs index 98e9b44..157428d 100644 --- a/crates/op-rbuilder/src/builders/standard/mod.rs +++ b/crates/op-rbuilder/src/builders/standard/mod.rs @@ -1,11 +1,12 @@ -use payload::StandardPayloadBuilderBuilder; -use reth_node_builder::components::BasicPayloadServiceBuilder; - -use crate::traits::{NodeBounds, PoolBounds}; +use crate::{ + builders::standard::service::StandardServiceBuilder, + traits::{NodeBounds, PoolBounds}, +}; use super::BuilderConfig; mod payload; +mod service; /// Block building strategy that builds blocks using the standard approach by /// producing blocks every chain block time. @@ -15,7 +16,7 @@ impl super::PayloadBuilder for StandardBuilder { type Config = (); type ServiceBuilder - = BasicPayloadServiceBuilder + = StandardServiceBuilder where Node: NodeBounds, Pool: PoolBounds; @@ -27,8 +28,6 @@ impl super::PayloadBuilder for StandardBuilder { Node: NodeBounds, Pool: PoolBounds, { - Ok(BasicPayloadServiceBuilder::new( - StandardPayloadBuilderBuilder(config), - )) + Ok(StandardServiceBuilder(config)) } } diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index ae18933..2d7ed3d 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -1,12 +1,8 @@ use crate::{ - builders::{ - builder_tx::StandardBuilderTx, generator::BuildArguments, BuilderConfig, - BuilderTransactions, - }, - flashtestations::service::bootstrap_flashtestations, + builders::{generator::BuildArguments, BuilderConfig, BuilderTransactions}, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, - traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, + traits::{ClientBounds, PayloadTxsBounds, PoolBounds}, }; use alloy_consensus::{ constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, @@ -18,7 +14,6 @@ use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadB use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; use reth_evm::{execute::BlockBuilder, ConfigureEvm}; use reth_node_api::{Block, PayloadBuilderError}; -use reth_node_builder::{components::PayloadBuilderBuilder, BuilderContext}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; @@ -43,64 +38,6 @@ use tracing::{error, info, warn}; use super::super::context::OpPayloadBuilderCtx; -pub struct StandardPayloadBuilderBuilder(pub BuilderConfig<()>); - -impl PayloadBuilderBuilder for StandardPayloadBuilderBuilder -where - Node: NodeBounds, - Pool: PoolBounds, -{ - type PayloadBuilder = StandardOpPayloadBuilder; - - async fn build_payload_builder( - self, - ctx: &BuilderContext, - pool: Pool, - _evm_config: OpEvmConfig, - ) -> eyre::Result { - let signer = self.0.builder_signer; - if self.0.flashtestations_config.flashtestations_enabled { - let _flashtestation_builder_tx = match bootstrap_flashtestations( - self.0.flashtestations_config.clone(), - ctx, - signer, - ) - .await - { - Ok(flashtestation_builder_tx) => flashtestation_builder_tx, - Err(e) => { - tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); - return Ok(StandardOpPayloadBuilder::new( - OpEvmConfig::optimism(ctx.chain_spec()), - pool, - ctx.provider().clone(), - self.0.clone(), - StandardBuilderTx::new(self.0.builder_signer), - )); - } - }; - - if self.0.flashtestations_config.enable_block_proofs { - // return Ok(StandardOpPayloadBuilder::new( - // OpEvmConfig::optimism(ctx.chain_spec()), - // pool, - // ctx.provider().clone(), - // self.0.clone(), - // flashtestation_builder_tx, - // )); - } - } - - Ok(StandardOpPayloadBuilder::new( - OpEvmConfig::optimism(ctx.chain_spec()), - pool, - ctx.provider().clone(), - self.0.clone(), - StandardBuilderTx::new(self.0.builder_signer), - )) - } -} - /// Optimism's payload builder #[derive(Debug, Clone)] pub struct StandardOpPayloadBuilder { diff --git a/crates/op-rbuilder/src/builders/standard/service.rs b/crates/op-rbuilder/src/builders/standard/service.rs new file mode 100644 index 0000000..e26b641 --- /dev/null +++ b/crates/op-rbuilder/src/builders/standard/service.rs @@ -0,0 +1,107 @@ +use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; +use reth_node_api::NodeTypes; +use reth_node_builder::{components::PayloadServiceBuilder, BuilderContext}; +use reth_optimism_evm::OpEvmConfig; +use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; +use reth_provider::CanonStateSubscriptions; + +use crate::{ + builders::{ + standard::payload::StandardOpPayloadBuilder, BuilderConfig, BuilderTransactions, + StandardBuilderTx, + }, + flashtestations::service::bootstrap_flashtestations, + traits::{NodeBounds, PoolBounds}, +}; + +pub struct StandardServiceBuilder(pub BuilderConfig<()>); + +impl StandardServiceBuilder { + pub fn spawn_payload_builder_service( + self, + evm_config: OpEvmConfig, + ctx: &BuilderContext, + pool: Pool, + builder_tx: BuilderTx, + ) -> eyre::Result::Payload>> + where + Node: NodeBounds, + Pool: PoolBounds, + BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, + { + let payload_builder = StandardOpPayloadBuilder::new( + evm_config, + pool, + ctx.provider().clone(), + self.0.clone(), + builder_tx, + ); + + let conf = ctx.config().builder.clone(); + + let payload_job_config = BasicPayloadJobGeneratorConfig::default() + .interval(conf.interval) + .deadline(conf.deadline) + .max_payload_tasks(conf.max_payload_tasks); + + let payload_generator = BasicPayloadJobGenerator::with_builder( + ctx.provider().clone(), + ctx.task_executor().clone(), + payload_job_config, + payload_builder, + ); + let (payload_service, payload_service_handle) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("payload builder service", Box::pin(payload_service)); + + Ok(payload_service_handle) + } +} + +impl PayloadServiceBuilder for StandardServiceBuilder +where + Node: NodeBounds, + Pool: PoolBounds, +{ + async fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + pool: Pool, + evm_config: OpEvmConfig, + ) -> eyre::Result::Payload>> { + let signer = self.0.builder_signer; + if self.0.flashtestations_config.flashtestations_enabled { + let flashtestation_builder_tx = match bootstrap_flashtestations( + self.0.flashtestations_config.clone(), + ctx, + signer, + ) + .await + { + Ok(flashtestation_builder_tx) => flashtestation_builder_tx, + Err(e) => { + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, falling back to standard builder tx"); + return self.spawn_payload_builder_service( + evm_config, + ctx, + pool, + StandardBuilderTx::new(signer), + ); + } + }; + + if self.0.flashtestations_config.enable_block_proofs { + return self.spawn_payload_builder_service( + evm_config, + ctx, + pool, + flashtestation_builder_tx, + ); + } + } + + self.spawn_payload_builder_service(evm_config, ctx, pool, StandardBuilderTx::new(signer)) + } +} diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 068fa13..926bda8 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -43,7 +43,7 @@ pub struct FlashtestationsBuilderTxArgs { pub registered: bool, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct FlashtestationsBuilderTx { // Attestation for the builder attestation: Vec, From 030460dc0cc60bed169152a9e6460fd534f3fa33 Mon Sep 17 00:00:00 2001 From: avalonche Date: Tue, 1 Jul 2025 17:18:38 +1000 Subject: [PATCH 04/14] add tx simulation --- crates/op-rbuilder/src/builders/builder_tx.rs | 88 +++--- .../src/builders/flashblocks/payload.rs | 7 +- .../src/builders/flashblocks/service.rs | 4 +- .../src/builders/standard/payload.rs | 80 +++-- .../src/builders/standard/service.rs | 14 +- .../src/flashtestations/builder_tx.rs | 283 ++++++++++++------ crates/op-rbuilder/src/flashtestations/mod.rs | 165 +++++++++- .../src/flashtestations/service.rs | 16 +- .../src/flashtestations/tx_manager.rs | 70 +++-- 9 files changed, 509 insertions(+), 218 deletions(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 96f2160..71a581c 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -6,13 +6,17 @@ use alloy_primitives::{ }; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; +use op_revm::OpTransactionError; use reth_evm::{eth::receipt_builder::ReceiptBuilderCtx, ConfigureEvm, Evm}; use reth_node_api::PayloadBuilderError; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; -use reth_provider::ProviderError; +use reth_provider::{ProviderError, StateProvider}; use reth_revm::State; -use revm::{context::result::ResultAndState, Database, DatabaseCommit}; +use revm::{ + context::result::{EVMError, ResultAndState}, + Database, DatabaseCommit, +}; use tracing::{debug, warn}; use crate::{ @@ -48,6 +52,12 @@ impl From for BuilderTransactionError { } } +impl From> for BuilderTransactionError { + fn from(error: EVMError) -> Self { + BuilderTransactionError::EvmExecutionError(Box::new(error)) + } +} + impl From for PayloadBuilderError { fn from(error: BuilderTransactionError) -> Self { match error { @@ -60,30 +70,30 @@ impl From for PayloadBuilderError { } pub trait BuilderTransactions: Debug { - fn simulate_builder_txs( + fn simulate_builder_txs( &self, + state_provider: impl StateProvider, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result, BuilderTransactionError> - where - DB: Database; + db: &mut State>, + ) -> Result, BuilderTransactionError>; - fn add_builder_txs( + fn add_builder_txs( &self, + state_provider: impl StateProvider, info: &mut ExecutionInfo, builder_ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result<(), BuilderTransactionError> - where - DB: Database, - { + db: &mut State>, + ) -> Result<(), BuilderTransactionError> { { let mut evm = builder_ctx .evm_config .evm_with_env(&mut *db, builder_ctx.evm_env.clone()); + let mut invalid: HashSet

= HashSet::new(); - let builder_txs = self.simulate_builder_txs(info, builder_ctx, evm.db_mut())?; + // simulate builder txs on the top of block state + let builder_txs = + self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; for builder_tx in builder_txs { if invalid.contains(&builder_tx.signed_tx.signer()) { debug!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); @@ -143,6 +153,29 @@ impl StandardBuilderTx { Self { signer } } + pub fn simulate_builder_txs( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State>, + ) -> Result, BuilderTransactionError> { + match self.signer { + Some(signer) => { + let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); + let gas_used = self.estimate_builder_tx_gas(&message); + let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + signed_tx.encoded_2718().as_slice(), + ); + Ok(vec![BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + }]) + } + None => Ok(vec![]), + } + } + fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { // Count zero and non-zero bytes let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { @@ -202,30 +235,13 @@ impl StandardBuilderTx { } impl BuilderTransactions for StandardBuilderTx { - fn simulate_builder_txs( + fn simulate_builder_txs( &self, + _state_provider: impl StateProvider, _info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result, BuilderTransactionError> - where - DB: Database, - { - match self.signer { - Some(signer) => { - let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); - let gas_used = self.estimate_builder_tx_gas(&message); - let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - signed_tx.encoded_2718().as_slice(), - ); - Ok(vec![BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - }]) - } - None => Ok(vec![]), - } + db: &mut State>, + ) -> Result, BuilderTransactionError> { + self.simulate_builder_txs(ctx, db) } } diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index a905f23..f924c11 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -271,13 +271,14 @@ where // If we have payload with txpool we add first builder tx right after deposits if !ctx.attributes().no_tx_pool { - self.builder_tx.add_builder_txs(&mut info, &ctx, &mut db)?; + self.builder_tx + .add_builder_txs(&state_provider, &mut info, &ctx, &mut db)?; } // We subtract gas limit and da limit for builder transaction from the whole limit let builder_txs = self .builder_tx - .simulate_builder_txs(&mut info, &ctx, &mut db) + .simulate_builder_txs(&state_provider, &mut info, &ctx, &mut db) .map_err(|err| PayloadBuilderError::Other(Box::new(err)))?; let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); @@ -426,7 +427,7 @@ where let mut db = State::builder() .with_database(state) .with_bundle_update() - .with_bundle_prestate(bundle_state) + .with_bundle_prestate(bundle_state.clone()) .build(); let best_txs_start_time = Instant::now(); diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs index 27721db..0603466 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs @@ -91,9 +91,7 @@ where } }; - if self.0.flashtestations_config.enable_block_proofs { - return self.spawn_payload_builder_service(ctx, pool, flashtestations_builder_tx); - } + return self.spawn_payload_builder_service(ctx, pool, flashtestations_builder_tx); } self.spawn_payload_builder_service(ctx, pool, StandardBuilderTx { signer }) } diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index 2d7ed3d..0151909 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -1,3 +1,4 @@ +use super::super::context::OpPayloadBuilderCtx; use crate::{ builders::{generator::BuildArguments, BuilderConfig, BuilderTransactions}, metrics::OpRBuilderMetrics, @@ -21,10 +22,7 @@ use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; use reth_primitives::RecoveredBlock; -use reth_provider::{ - ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider, - StorageRootProvider, -}; +use reth_provider::{ExecutionOutcome, ProviderError, StateProvider}; use reth_revm::{ database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, }; @@ -36,8 +34,6 @@ use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; -use super::super::context::OpPayloadBuilderCtx; - /// Optimism's payload builder #[derive(Debug, Clone)] pub struct StandardOpPayloadBuilder { @@ -241,22 +237,18 @@ where let builder = OpBuilder::new(best); let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(state_provider); + let state = StateProviderDatabase::new(&state_provider); let metrics = ctx.metrics.clone(); - if ctx.attributes().no_tx_pool { - let db = State::builder() - .with_database(state) - .with_bundle_update() - .build(); - builder.build(db, ctx, self.builder_tx.clone()) + builder.build(state, &state_provider, ctx, self.builder_tx.clone()) } else { // sequencer mode we can reuse cachedreads from previous runs - let db = State::builder() - .with_database(cached_reads.as_db_mut(state)) - .with_bundle_update() - .build(); - builder.build(db, ctx, self.builder_tx.clone()) + builder.build( + cached_reads.as_db_mut(state), + &state_provider, + ctx, + self.builder_tx.clone(), + ) } .map(|out| { let total_block_building_time = block_build_start_time.elapsed(); @@ -310,15 +302,14 @@ pub struct ExecutedPayload { impl OpBuilder<'_, Txs> { /// Executes the payload and returns the outcome. - pub fn execute( + pub fn execute( self, - state: &mut State, + state_provider: impl StateProvider, + db: &mut State>, ctx: &OpPayloadBuilderCtx, builder_tx: BuilderTx, ) -> Result, PayloadBuilderError> where - DB: Database + AsRef

+ std::fmt::Debug, - P: StorageRootProvider, BuilderTx: BuilderTransactions, { let Self { best } = self; @@ -326,14 +317,14 @@ impl OpBuilder<'_, Txs> { // 1. apply pre-execution changes ctx.evm_config - .builder_for_next_block(state, ctx.parent(), ctx.block_env_attributes.clone()) + .builder_for_next_block(db, ctx.parent(), ctx.block_env_attributes.clone()) .map_err(PayloadBuilderError::other)? .apply_pre_execution_changes()?; let sequencer_tx_start_time = Instant::now(); // 3. execute sequencer transactions - let mut info = ctx.execute_sequencer_transactions(state)?; + let mut info = ctx.execute_sequencer_transactions(db)?; let sequencer_tx_time = sequencer_tx_start_time.elapsed(); ctx.metrics.sequencer_tx_duration.record(sequencer_tx_time); @@ -342,7 +333,7 @@ impl OpBuilder<'_, Txs> { // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx - let builder_txs = builder_tx.simulate_builder_txs(&mut info, ctx, state)?; + let builder_txs = builder_tx.simulate_builder_txs(&state_provider, &mut info, ctx, db)?; let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let block_gas_limit = ctx.block_gas_limit().saturating_sub(builder_tx_gas); if block_gas_limit == 0 { @@ -375,7 +366,7 @@ impl OpBuilder<'_, Txs> { if ctx .execute_best_transactions( &mut info, - state, + db, best_txs, block_gas_limit, block_da_limit, @@ -387,13 +378,13 @@ impl OpBuilder<'_, Txs> { } // Add builder tx to the block - builder_tx.add_builder_txs(&mut info, ctx, state)?; + builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db)?; let state_merge_start_time = Instant::now(); // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call - state.merge_transitions(BundleRetention::Reverts); + db.merge_transitions(BundleRetention::Reverts); let state_transition_merge_time = state_merge_start_time.elapsed(); ctx.metrics @@ -417,26 +408,32 @@ impl OpBuilder<'_, Txs> { } /// Builds the payload on top of the state. - pub fn build( + pub fn build( self, - mut state: State, + state: impl Database, + state_provider: impl StateProvider, ctx: OpPayloadBuilderCtx, builder_tx: BuilderTx, ) -> Result, PayloadBuilderError> where - DB: Database + AsRef

+ std::fmt::Debug, - P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, BuilderTx: BuilderTransactions, { - let ExecutedPayload { info } = match self.execute(&mut state, &ctx, builder_tx)? { - BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, - BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), - BuildOutcomeKind::Aborted { fees } => return Ok(BuildOutcomeKind::Aborted { fees }), - }; + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + let ExecutedPayload { info } = + match self.execute(&state_provider, &mut db, &ctx, builder_tx)? { + BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, + BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), + BuildOutcomeKind::Aborted { fees } => { + return Ok(BuildOutcomeKind::Aborted { fees }) + } + }; let block_number = ctx.block_number(); let execution_outcome = ExecutionOutcome::new( - state.take_bundle(), + db.take_bundle(), vec![info.receipts], block_number, Vec::new(), @@ -457,12 +454,9 @@ impl OpBuilder<'_, Txs> { // calculate the state root let state_root_start_time = Instant::now(); - let state_provider = state.database.as_ref(); let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); let (state_root, trie_output) = { - state - .database - .as_ref() + state_provider .state_root_with_updates(hashed_state.clone()) .inspect_err(|err| { warn!(target: "payload_builder", @@ -486,7 +480,7 @@ impl OpBuilder<'_, Txs> { // `l2tol1-message-passer` ( Some( - isthmus::withdrawals_root(execution_outcome.state(), state.database.as_ref()) + isthmus::withdrawals_root(execution_outcome.state(), state_provider) .map_err(PayloadBuilderError::other)?, ), Some(EMPTY_REQUESTS_HASH), diff --git a/crates/op-rbuilder/src/builders/standard/service.rs b/crates/op-rbuilder/src/builders/standard/service.rs index e26b641..6a1601c 100644 --- a/crates/op-rbuilder/src/builders/standard/service.rs +++ b/crates/op-rbuilder/src/builders/standard/service.rs @@ -92,14 +92,12 @@ where } }; - if self.0.flashtestations_config.enable_block_proofs { - return self.spawn_payload_builder_service( - evm_config, - ctx, - pool, - flashtestation_builder_tx, - ); - } + return self.spawn_payload_builder_service( + evm_config, + ctx, + pool, + flashtestation_builder_tx, + ); } self.spawn_payload_builder_service(evm_config, ctx, pool, StandardBuilderTx::new(signer)) diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 926bda8..fae9182 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -1,23 +1,26 @@ -use alloy::sol_types::{SolCall, SolEvent, SolValue}; +use alloy::sol_types::{SolCall, SolValue}; use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_op_evm::OpEvm; -use alloy_primitives::{keccak256, Address, Bytes, TxKind, B256, U256}; +use alloy_primitives::{keccak256, map::foldhash::HashMap, Address, Bytes, TxKind, B256, U256}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm}; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::{Log, Recovered}; -use reth_revm::State; +use reth_primitives::Recovered; +use reth_provider::{ProviderError, StateProvider}; +use reth_revm::{database::StateProviderDatabase, State}; use revm::{ context::result::{ExecutionResult, ResultAndState}, inspector::NoOpInspector, + state::Account, + Database, DatabaseCommit, }; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; -use tracing::{debug, error, info}; +use tracing::{debug, error, info, warn}; use crate::{ builders::{ @@ -25,7 +28,8 @@ use crate::{ StandardBuilderTx, }, flashtestations::{ - BlockData, IBlockBuilderPolicy, IFlashtestationRegistry, TEEServiceRegistered, + BlockBuilderPolicyError, BlockData, FlashtestationRegistryError, + FlashtestationRevertReason, IBlockBuilderPolicy, IFlashtestationRegistry, }, primitives::reth::ExecutionInfo, tx_signer::Signer, @@ -40,6 +44,7 @@ pub struct FlashtestationsBuilderTxArgs { pub builder_policy_address: Address, pub builder_proof_version: u8, pub builder_signer: Option, + pub enable_block_proofs: bool, pub registered: bool, } @@ -61,10 +66,20 @@ pub struct FlashtestationsBuilderTx { builder_proof_version: u8, // Whether the workload and address has been registered registered: Arc, + // Whether block proofs are enabled + enable_block_proofs: bool, // fallback builder transaction implementation fallback_builder_tx: StandardBuilderTx, } +#[derive(Debug)] +pub struct TxSimulateResult { + pub gas_used: u64, + pub success: bool, + pub state_changes: HashMap, + pub revert_reason: Option, +} + impl FlashtestationsBuilderTx { pub fn new(args: FlashtestationsBuilderTxArgs) -> Self { Self { @@ -76,6 +91,7 @@ impl FlashtestationsBuilderTx { builder_policy_address: args.builder_policy_address, builder_proof_version: args.builder_proof_version, registered: Arc::new(AtomicBool::new(args.registered)), + enable_block_proofs: args.enable_block_proofs, fallback_builder_tx: StandardBuilderTx::new(args.builder_signer), } } @@ -89,9 +105,8 @@ impl FlashtestationsBuilderTx { chain_id: u64, nonce: u64, ) -> Result, secp256k1::Error> { - info!(target: "flashtestations", "signing funding transaction"); - // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { chain_id, nonce, @@ -113,8 +128,6 @@ impl FlashtestationsBuilderTx { chain_id: u64, nonce: u64, ) -> Result, secp256k1::Error> { - info!(target: "flashtestations", "signing registering tee service transaction"); - let quote_bytes = Bytes::from(attestation); let calldata = IFlashtestationRegistry::registerTEEServiceCall { rawQuote: quote_bytes, @@ -139,6 +152,7 @@ impl FlashtestationsBuilderTx { &self, transactions: Vec, ctx: &OpPayloadBuilderCtx, + gas_limit: u64, nonce: u64, ) -> Result, secp256k1::Error> { let block_content_hash = Self::compute_block_content_hash( @@ -148,7 +162,6 @@ impl FlashtestationsBuilderTx { ctx.timestamp(), ); - debug!(target: "flashtestations", block_content_hash = ?block_content_hash, "signing block builder proof transaction"); let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { version: self.builder_proof_version, blockContentHash: block_content_hash, @@ -158,7 +171,7 @@ impl FlashtestationsBuilderTx { let tx = OpTypedTransaction::Eip1559(TxEip1559 { chain_id: ctx.chain_id(), nonce, - gas_limit: ctx.block_gas_limit(), + gas_limit, max_fee_per_gas: ctx.base_fee().into(), max_priority_fee_per_gas: 0, to: TxKind::Call(self.builder_policy_address), @@ -204,7 +217,7 @@ impl FlashtestationsBuilderTx { ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, nonce: u64, - ) -> Result<(u64, Option>, bool), BuilderTransactionError> + ) -> Result where DB: revm::Database, { @@ -215,105 +228,160 @@ impl FlashtestationsBuilderTx { ctx.chain_id(), nonce, )?; - let ResultAndState { result, .. } = evm + let ResultAndState { result, state } = evm .transact(®ister_tx) .map_err(|e| BuilderTransactionError::EvmExecutionError(Box::new(e)))?; match result { - ExecutionResult::Success { gas_used, logs, .. } => { - for log in logs { - // Check if this is the TEEServiceRegistered event - if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { - match TEEServiceRegistered::decode_log(&log) { - Ok(decoded_event) => return Ok((gas_used, Some(decoded_event), true)), - Err(e) => { - error!(target: "flashtestations", "Failed to decode TEEServiceRegistered event: {}", e); - } - } - } - } - Ok((gas_used, None, true)) + // TODO: check logs + ExecutionResult::Success { gas_used, .. } => Ok(TxSimulateResult { + gas_used, + success: true, + state_changes: state, + revert_reason: None, + }), + ExecutionResult::Revert { output, .. } => { + let revert_reason = FlashtestationRegistryError::from(output); + warn!(target: "flashtestations", "register tee tx reverted during simulation: {}", revert_reason); + Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::FlashtestationRegistry( + revert_reason, + )), + }) } _ => { error!(target: "flashtestations", "register tee tx halted or reverted during simulation"); - Ok((0, None, false)) + Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: None, + }) } } } + fn check_registered(&self, revert_reason: Option) -> bool { + if let Some(FlashtestationRevertReason::FlashtestationRegistry( + FlashtestationRegistryError::TEEServiceAlreadyRegistered(_, _), + )) = revert_reason + { + return true; + } + false + } + fn simulate_verify_block_proof_tx( &self, transactions: Vec, ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, nonce: u64, - ) -> Result<(u64, bool), BuilderTransactionError> + ) -> Result where DB: revm::Database, { - let verify_block_proof_tx = self.signed_block_builder_proof_tx(transactions, ctx, nonce)?; - let ResultAndState { result, .. } = evm - .transact(&verify_block_proof_tx) - .map_err(|e| BuilderTransactionError::EvmExecutionError(Box::new(e)))?; + let verify_block_proof_tx = + self.signed_block_builder_proof_tx(transactions, ctx, ctx.block_gas_limit(), nonce)?; + let ResultAndState { result, state, .. } = evm.transact(&verify_block_proof_tx)?; match result { - ExecutionResult::Success { gas_used, .. } => Ok((gas_used, true)), - _ => { - error!(target: "flashtestations", "verify block proof tx halted or reverted during simulation"); - Ok((0, false)) + // TODO: check logs + ExecutionResult::Success { gas_used, .. } => Ok(TxSimulateResult { + gas_used, + success: true, + state_changes: state, + revert_reason: None, + }), + ExecutionResult::Revert { output, .. } => { + let revert_reason = BlockBuilderPolicyError::from(output); + warn!(target: "flashtestations", "verify block proof tx reverted during simulation: {}", revert_reason); + Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::BlockBuilderPolicy( + revert_reason, + )), + }) + } + ExecutionResult::Halt { reason, .. } => { + warn!(target: "flashtestations", "verify block proof tx halted during simulation: {:?}", reason); + Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: None, + }) } } } } impl BuilderTransactions for FlashtestationsBuilderTx { - fn simulate_builder_txs( + fn simulate_builder_txs( &self, + state_provider: impl StateProvider, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut reth_revm::State, - ) -> Result, BuilderTransactionError> - where - DB: revm::Database, - { + db: &mut State>, + ) -> Result, BuilderTransactionError> { + let state = StateProviderDatabase::new(state_provider); + 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(); + + let mut evm = ctx + .evm_config + .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + }); + let mut builder_txs = Vec::::new(); - let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); let mut nonce: u64 = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; if !self.registered.load(Ordering::Relaxed) { - let (gas_used, log, success) = - self.simulate_register_tee_service_tx(ctx, &mut evm, nonce)?; - let registered = if let Some(log) = log { - log.data.alreadyExists - } else { - false - }; + let balance = get_balance(evm.db_mut(), self.tee_service_signer.address)?; + if balance.is_zero() { + // funding transaction + let funding_nonce = get_nonce(evm.db_mut(), self.funding_key.address)?; + let funding_tx = self.signed_funding_tx( + self.tee_service_signer.address, + self.funding_key, + self.funding_amount, + ctx.base_fee(), + ctx.chain_id(), + funding_nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + funding_tx.encoded_2718().as_slice(), + ); + let ResultAndState { state, .. } = evm.transact(&funding_tx)?; + info!(target: "flashtestations", "adding funding tx to builder txs"); + builder_txs.push(BuilderTransactionCtx { + gas_used: 21000, + da_size, + signed_tx: funding_tx, + }); + evm.db_mut().commit(state); + } + let TxSimulateResult { + gas_used, + success, + state_changes, + revert_reason, + } = self.simulate_register_tee_service_tx(ctx, &mut evm, nonce)?; + let registered = self.check_registered(revert_reason); self.registered.store(registered, Ordering::Relaxed); - - if !registered && success { - let balance = get_balance(evm.db_mut(), self.tee_service_signer.address)?; - if balance.is_zero() { - // funding transaction - let funding_nonce = get_nonce(evm.db_mut(), self.funding_key.address)?; - let funding_tx = self.signed_funding_tx( - self.tee_service_signer.address, - self.funding_key, - self.funding_amount, - ctx.base_fee(), - ctx.chain_id(), - funding_nonce, - )?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - funding_tx.encoded_2718().as_slice(), - ); - builder_txs.push(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx: funding_tx, - }); - } - + if success { let register_tx = self.signed_register_tee_service_tx( self.attestation.clone(), - gas_used, + gas_used * 64 / 63, // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer ctx.base_fee(), ctx.chain_id(), nonce, @@ -321,41 +389,58 @@ impl BuilderTransactions for FlashtestationsBuilderTx { let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( register_tx.encoded_2718().as_slice(), ); + info!(target: "flashtestations", "adding register tee tx to builder txs"); builder_txs.push(BuilderTransactionCtx { gas_used, da_size, signed_tx: register_tx, }); + evm.db_mut().commit(state_changes); nonce += 1; } } - // add verify block proof tx - let (gas_used, success) = self.simulate_verify_block_proof_tx( - info.executed_transactions.clone(), - ctx, - &mut evm, - nonce, - )?; - if success { - let verify_block_proof_tx = - self.signed_block_builder_proof_tx(info.executed_transactions.clone(), ctx, nonce)?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - verify_block_proof_tx.encoded_2718().as_slice(), - ); - builder_txs.push(BuilderTransactionCtx { + if self.enable_block_proofs { + // add verify block proof tx + let TxSimulateResult { gas_used, - da_size, - signed_tx: verify_block_proof_tx, - }); - } else { - // if verify block proof tx fails, fallback to standard builder tx - builder_txs.extend( - self.fallback_builder_tx - .simulate_builder_txs(info, ctx, db)?, - ); + success: should_include_tx, + .. + } = self.simulate_verify_block_proof_tx( + info.executed_transactions.clone(), + ctx, + &mut evm, + nonce, + )?; + if should_include_tx { + // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer + let verify_block_proof_tx = self.signed_block_builder_proof_tx( + info.executed_transactions.clone(), + ctx, + gas_used * 64 / 63, + nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + verify_block_proof_tx.encoded_2718().as_slice(), + ); + debug!(target: "flashtestations", "adding verify block proof tx to builder txs"); + builder_txs.push(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: verify_block_proof_tx, + }); + return Ok(builder_txs); + } else { + warn!(target: "flashtestations", "verify block proof tx reverted, falling back to standard builder tx"); + } } + // Fallback to standard builder tx (either when block proofs are disabled or when verify block proof tx fails) + builder_txs.extend( + self.fallback_builder_tx + .simulate_builder_txs(ctx, evm.db_mut())?, + ); + Ok(builder_txs) } } diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index 177ccb5..617264c 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -1,4 +1,5 @@ -use alloy::sol; +use alloy::{sol, sol_types::SolError}; +use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256}; sol!( #[sol(rpc, abi)] @@ -18,11 +19,171 @@ sol!( bytes32[] transactionHashes; } + type WorkloadId is bytes32; + event TEEServiceRegistered( - address teeAddress, bytes32 workloadId, bytes rawQuote, bytes publicKey, bool alreadyExists + address teeAddress, WorkloadId workloadId, bytes rawQuote, bytes publicKey, bool alreadyExists + ); + + event BlockBuilderProofVerified( + address caller, WorkloadId workloadId, uint256 blockNumber, uint8 version, bytes32 blockContentHash ); + + // FlashtestationRegistry errors + error InvalidQuote(bytes output); + error TEEServiceAlreadyRegistered(address teeAddress, WorkloadId workloadId); + error SenderMustMatchTEEAddress(address sender, address teeAddress); + error InvalidTEEType(bytes4 teeType); + error InvalidTEEVersion(uint16 version); + error InvalidReportDataLength(uint256 length); + error InvalidQuoteLength(uint256 length); + + // BlockBuilderPolicy errors + error UnauthorizedBlockBuilder(address caller); + error UnsupportedVersion(uint8 version); + + // EIP-712 permit errors + error InvalidSignature(); + error InvalidNonce(uint256 expected, uint256 provided); ); +#[derive(Debug, thiserror::Error)] +pub enum FlashtestationRevertReason { + #[error("flashtestation registry error: {0}")] + FlashtestationRegistry(FlashtestationRegistryError), + #[error("block builder policy error: {0}")] + BlockBuilderPolicy(BlockBuilderPolicyError), +} + +#[derive(Debug, thiserror::Error)] +pub enum FlashtestationRegistryError { + #[error("invalid quote: {0}")] + InvalidQuote(Bytes), + #[error("tee address {0} already registered with workload id {1}")] + TEEServiceAlreadyRegistered(Address, B256), + #[error("sender address {0} must match quote tee address {1}")] + SenderMustMatchTEEAddress(Address, Address), + #[error("invalid tee type: {0}")] + InvalidTEEType(FixedBytes<4>), + #[error("invalid tee version: {0}")] + InvalidTEEVersion(u16), + #[error("invalid report data length: {0}")] + InvalidReportDataLength(U256), + #[error("invalid quote length: {0}")] + InvalidQuoteLength(U256), + #[error("invalid signature")] + InvalidSignature(), + #[error("invalid nonce: expected {0}, provided {1}")] + InvalidNonce(U256, U256), + #[error("unknown revert: {0}")] + Unknown(String), +} + +impl From for FlashtestationRegistryError { + fn from(value: Bytes) -> Self { + // Empty revert + if value.is_empty() { + return FlashtestationRegistryError::Unknown( + "Transaction reverted without reason".to_string(), + ); + } + + // Try to decode each custom error type + if let Ok(InvalidQuote { output }) = InvalidQuote::abi_decode(&value) { + return FlashtestationRegistryError::InvalidQuote(output); + } + + if let Ok(TEEServiceAlreadyRegistered { + teeAddress, + workloadId, + }) = TEEServiceAlreadyRegistered::abi_decode(&value) + { + return FlashtestationRegistryError::TEEServiceAlreadyRegistered( + teeAddress, workloadId, + ); + } + + if let Ok(SenderMustMatchTEEAddress { sender, teeAddress }) = + SenderMustMatchTEEAddress::abi_decode(&value) + { + return FlashtestationRegistryError::SenderMustMatchTEEAddress(sender, teeAddress); + } + + if let Ok(InvalidTEEType { teeType }) = InvalidTEEType::abi_decode(&value) { + return FlashtestationRegistryError::InvalidTEEType(teeType); + } + + if let Ok(InvalidTEEVersion { version }) = InvalidTEEVersion::abi_decode(&value) { + return FlashtestationRegistryError::InvalidTEEVersion(version); + } + + if let Ok(InvalidReportDataLength { length }) = InvalidReportDataLength::abi_decode(&value) + { + return FlashtestationRegistryError::InvalidReportDataLength(length); + } + + if let Ok(InvalidQuoteLength { length }) = InvalidQuoteLength::abi_decode(&value) { + return FlashtestationRegistryError::InvalidQuoteLength(length); + } + + if let Ok(InvalidSignature {}) = InvalidSignature::abi_decode(&value) { + return FlashtestationRegistryError::InvalidSignature(); + } + + if let Ok(InvalidNonce { expected, provided }) = InvalidNonce::abi_decode(&value) { + return FlashtestationRegistryError::InvalidNonce(expected, provided); + } + + FlashtestationRegistryError::Unknown(hex::encode(value)) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum BlockBuilderPolicyError { + #[error("unauthorized block builder: {0}")] + UnauthorizedBlockBuilder(Address), + #[error("unsupported version: {0}")] + UnsupportedVersion(u8), + #[error("invalid signature")] + InvalidSignature(), + #[error("invalid nonce: expected {0}, provided {1}")] + InvalidNonce(U256, U256), + #[error("unknown revert: {0}")] + Unknown(String), +} + +impl From for BlockBuilderPolicyError { + fn from(value: Bytes) -> Self { + // Empty revert + if value.is_empty() { + return BlockBuilderPolicyError::Unknown( + "Transaction reverted without reason".to_string(), + ); + } + + // Try to decode each custom error type + if let Ok(UnauthorizedBlockBuilder { caller }) = + UnauthorizedBlockBuilder::abi_decode(&value) + { + return BlockBuilderPolicyError::UnauthorizedBlockBuilder(caller); + } + + if let Ok(UnsupportedVersion { version }) = UnsupportedVersion::abi_decode(&value) { + return BlockBuilderPolicyError::UnsupportedVersion(version); + } + + if let Ok(InvalidSignature {}) = InvalidSignature::abi_decode(&value) { + return BlockBuilderPolicyError::InvalidSignature(); + } + + if let Ok(InvalidNonce { expected, provided }) = InvalidNonce::abi_decode(&value) { + return BlockBuilderPolicyError::InvalidNonce(expected, provided); + } + + BlockBuilderPolicyError::Unknown(hex::encode(value)) + } +} + pub mod args; pub mod attestation; pub mod builder_tx; diff --git a/crates/op-rbuilder/src/flashtestations/service.rs b/crates/op-rbuilder/src/flashtestations/service.rs index f3885a8..00b2858 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -1,10 +1,11 @@ use reth_node_builder::BuilderContext; +use secp256k1::{PublicKey, Secp256k1, SecretKey}; use tracing::{info, warn}; use crate::{ flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, traits::NodeBounds, - tx_signer::{generate_ethereum_keypair, Signer}, + tx_signer::{generate_ethereum_keypair, public_key_to_address, Signer}, }; use super::{ @@ -13,7 +14,6 @@ use super::{ tx_manager::TxManager, }; -// TODO: Flashtestations error types pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, ctx: &BuilderContext, @@ -24,7 +24,16 @@ where { info!("Flashtestations enabled"); - let (private_key, public_key, address) = generate_ethereum_keypair(); + let (private_key, public_key, address) = if args.debug { + info!("Flashtestations debug mode enabled, generating debug key"); + // Generate deterministic key for debugging purposes + let secp = Secp256k1::new(); + let private_key = SecretKey::from_slice(&[0x42; 32]).unwrap(); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + (private_key, public_key, public_key_to_address(&public_key)) + } else { + generate_ethereum_keypair() + }; let tee_service_signer = Signer { address, pubkey: public_key, @@ -86,6 +95,7 @@ where builder_policy_address, builder_proof_version: args.builder_proof_version, builder_signer, + enable_block_proofs: args.enable_block_proofs, registered, }); diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs index 0fc3571..95db965 100644 --- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,3 +1,4 @@ +use alloy_json_rpc::RpcError; use alloy_network::ReceiptResponse; use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_rpc_types_eth::TransactionRequest; @@ -8,10 +9,30 @@ use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::{sol, SolCall, SolValue}; use op_alloy_network::Optimism; -use tracing::{debug, error, info}; +use tracing::{debug, error, info, warn}; use crate::{flashtestations::IFlashtestationRegistry, tx_signer::Signer}; +#[derive(Debug, thiserror::Error)] +pub enum TxManagerError { + #[error("rpc error: {0}")] + RpcError(TransportError), + #[error("tx reverted: {0}")] + TxReverted(TxHash), + #[error("error checking tx confirmation: {0}")] + TxConfirmationError(PendingTransactionError), + #[error("tx rpc error: {0}")] + TxRpcError(RpcError), + #[error("signer error: {0}")] + SignerError(ecdsa::Error), +} + +impl From for TxManagerError { + fn from(e: TransportError) -> Self { + TxManagerError::RpcError(e) + } +} + #[derive(Debug, Clone)] pub struct TxManager { tee_service_signer: Signer, @@ -35,8 +56,14 @@ impl TxManager { } } - pub async fn fund_address(&self, from: Signer, to: Address, amount: U256) -> eyre::Result<()> { - let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into())?; + pub async fn fund_address( + &self, + from: Signer, + to: Address, + amount: U256, + ) -> Result<(), TxManagerError> { + let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into()) + .map_err(TxManagerError::SignerError)?; let provider = ProviderBuilder::new() .disable_recommended_fillers() .fetch_chain_id() @@ -60,36 +87,40 @@ impl TxManager { match Self::process_pending_tx(provider.send_transaction(funding_tx.into()).await).await { Ok(tx_hash) => { info!(target: "flashtestations", tx_hash = %tx_hash, "funding transaction confirmed successfully"); + Ok(()) } Err(e) => { - error!(target: "flashtestations", error = %e, "funding transaction failed"); - return Err(e); + warn!(target: "flashtestations", error = %e, "funding transaction failed"); + Err(e) } } - - Ok(()) } pub async fn fund_and_register_tee_service( &self, attestation: Vec, funding_amount: U256, - ) -> eyre::Result<()> { + ) -> Result<(), TxManagerError> { info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address); self.fund_address( self.funding_signer, self.tee_service_signer.address, funding_amount, ) - .await?; + .await + .unwrap_or_else(|e| { + warn!(target: "flashtestations", error = %e, "Failed to fund TEE address, attempting to register without funding"); + }); let quote_bytes = Bytes::from(attestation); let wallet = - PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into())?; + PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into()) + .map_err(TxManagerError::SignerError)?; let provider = ProviderBuilder::new() .disable_recommended_fillers() .fetch_chain_id() .with_gas_estimation() + .with_cached_nonce_management() .wallet(wallet) .network::() .connect(self.rpc_url.as_str()) @@ -97,7 +128,6 @@ impl TxManager { info!(target: "flashtestations", "submitting quote to registry at {}", self.registry_address); - // TODO: add retries let calldata = IFlashtestationRegistry::registerTEEServiceCall { rawQuote: quote_bytes, } @@ -105,23 +135,21 @@ impl TxManager { let tx = TransactionRequest { from: Some(self.tee_service_signer.address), to: Some(TxKind::Call(self.registry_address)), - nonce: Some(0), input: calldata.into(), ..Default::default() }; match Self::process_pending_tx(provider.send_transaction(tx.into()).await).await { Ok(tx_hash) => { info!(target: "flashtestations", tx_hash = %tx_hash, "attestation transaction confirmed successfully"); - Ok(()) } Err(e) => { - error!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); - Err(e) + warn!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); } } + Ok(()) } - pub async fn clean_up(&self) -> eyre::Result<()> { + pub async fn clean_up(&self) -> Result<(), TxManagerError> { info!(target: "flashtestations", "sending funds back from TEE generated key to funding address"); let provider = ProviderBuilder::new() .disable_recommended_fillers() @@ -140,7 +168,7 @@ impl TxManager { self.funding_signer.address, balance.saturating_sub(gas_cost), ) - .await? + .await?; } Ok(()) } @@ -148,7 +176,7 @@ impl TxManager { /// Processes a pending transaction and logs whether the transaction succeeded or not async fn process_pending_tx( pending_tx_result: TransportResult>, - ) -> eyre::Result { + ) -> Result { match pending_tx_result { Ok(pending_tx) => { let tx_hash = *pending_tx.tx_hash(); @@ -164,13 +192,13 @@ impl TxManager { if receipt.status() { Ok(receipt.transaction_hash()) } else { - Err(eyre::eyre!("Transaction reverted: {}", tx_hash)) + Err(TxManagerError::TxReverted(tx_hash)) } } - Err(e) => Err(e.into()), + Err(e) => Err(TxManagerError::TxConfirmationError(e)), } } - Err(e) => Err(e.into()), + Err(e) => Err(TxManagerError::TxRpcError(e)), } } } From 590dda5d81e75a1cf3d47aa9d7f493a8cda7feb2 Mon Sep 17 00:00:00 2001 From: avalonche Date: Wed, 2 Jul 2025 06:35:13 +1000 Subject: [PATCH 05/14] better logging and README --- Cargo.toml | 2 +- README.md | 16 +- crates/op-rbuilder/src/builders/builder_tx.rs | 13 +- .../op-rbuilder/src/flashtestations/args.rs | 15 +- .../src/flashtestations/attestation.rs | 23 +- .../src/flashtestations/builder_tx.rs | 416 +++++++++++------- crates/op-rbuilder/src/flashtestations/mod.rs | 2 + .../src/flashtestations/service.rs | 15 +- crates/op-rbuilder/src/tx_signer.rs | 18 + 9 files changed, 336 insertions(+), 184 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c1d32d..059d961 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,7 +106,7 @@ revm = { version = "27.0.3", features = [ "optional_balance_check", ], default-features = false } revm-inspectors = { version = "0.27", default-features = false } -op-revm = { version = "8.0.2", default-features = false } +op-revm = { version = "8.0.2", features = ["serde"], default-features = false } ethereum_ssz_derive = "0.9.0" ethereum_ssz = "0.9.0" diff --git a/README.md b/README.md index df30098..a2211b0 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ cargo run -p op-rbuilder --bin op-rbuilder -- node \ ### Flashtestations +Flashtestations is a feature that enables Trusted Execution Environment (TEE) attestation for block building. It provides cryptographic proof that blocks were built within a secure enclave, ensuring the integrity and confidentiality of the block building process. + +#### Usage + To run op-rbuilder with flashtestations: ```bash @@ -53,14 +57,18 @@ cargo run -p op-rbuilder --bin op-rbuilder --features=flashtestations -- node \ --authrpc.port 9551 \ --authrpc.jwtsecret /path/to/jwt.hex \ --flashtestations.enabled \ - --flashtestations.rpc-url your-rpc-url \ # rpc to submit the attestation transaction to --flashtestations.funding-amount 0.01 \ # amount in ETH to fund the TEE generated key --flashtestations.funding-key secret-key \ # funding key for the TEE key --flashtestations.registry-address 0xFlashtestationsRegistryAddress \ - flashtestations.builder-policy-address 0xBuilderPolicyAddress + --flashtestations.builder-policy-address 0xBuilderPolicyAddress ``` -Note that `--rollup.builder-secret-key` must be set and funded in order for the flashtestations key to be funded and submit the attestation on-chain. +#### Additional CLI Config + +- `--flashtestations.enable-block-proofs`: Enable end-of-block transaction proofs that verify the block was built within a TEE +- `--flashtestations.debug`: Enable debug mode with a deterministic TEE key and debug attestation server for testing and development +- `--flashtestations.quote-provider `: Specify a remote URL to provide an attestation instead of generating a quote in process +- `--flashtestations.rpc-url `: Use a remote provider to submit attestations to ## Observability @@ -189,4 +197,4 @@ More instructions on installing and configuring `act` can be found on [their web ### Known issues - Running actions locally require a Github Token. You can generate one by following instructions on [Github Docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). After generating a token you will need to pass it to `act` either through the command line using `-s GITHUB_TOKEN=` or by adding it to the `~/.config/act/actrc` file. -- You might get an error about missing or incompatible `warp-ubuntu-latest-x64-32x` platform. This can be mitigated by adding `-P warp-ubuntu-latest-x64-32x=ghcr.io/catthehacker/ubuntu:act-latest` on the command line when calling `act` or appending this flag to `~/.config/act/actrc` +- You might get an error about missing or incompatible `warp-ubuntu-latest-x64-32x` platform. This can be mitigated by adding `-P warp-ubuntu-latest-x64-32x=ghcr.io/catthehacker/ubuntu:act-latest` on the command line when calling `act` or appending this flag to `~/.config/act/actrc` \ No newline at end of file diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 71a581c..a499b6d 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -153,11 +153,11 @@ impl StandardBuilderTx { Self { signer } } - pub fn simulate_builder_txs( + pub fn simulate_builder_tx( &self, ctx: &OpPayloadBuilderCtx, db: &mut State>, - ) -> Result, BuilderTransactionError> { + ) -> Result, BuilderTransactionError> { match self.signer { Some(signer) => { let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); @@ -166,13 +166,13 @@ impl StandardBuilderTx { let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( signed_tx.encoded_2718().as_slice(), ); - Ok(vec![BuilderTransactionCtx { + Ok(Some(BuilderTransactionCtx { gas_used, da_size, signed_tx, - }]) + })) } - None => Ok(vec![]), + None => Ok(None), } } @@ -242,6 +242,7 @@ impl BuilderTransactions for StandardBuilderTx { ctx: &OpPayloadBuilderCtx, db: &mut State>, ) -> Result, BuilderTransactionError> { - self.simulate_builder_txs(ctx, db) + let builder_tx = self.simulate_builder_tx(ctx, db)?; + Ok(builder_tx.into_iter().collect()) } } diff --git a/crates/op-rbuilder/src/flashtestations/args.rs b/crates/op-rbuilder/src/flashtestations/args.rs index 3c242c5..86f0b83 100644 --- a/crates/op-rbuilder/src/flashtestations/args.rs +++ b/crates/op-rbuilder/src/flashtestations/args.rs @@ -24,8 +24,19 @@ pub struct FlashtestationsArgs { pub debug: bool, // Debug url for attestations - #[arg(long = "flashtestations.debug-url", env = "FLASHTESTATIONS_DEBUG_URL")] - pub debug_url: Option, + #[arg( + long = "flashtestations.debug-tee-key-seed", + env = "FLASHTESTATIONS_DEBUG_TEE_KEY_SEED", + default_value = "debug" + )] + pub debug_tee_key_seed: String, + + // Remote url for attestations + #[arg( + long = "flashtestations.quote-provider", + env = "FLASHTESTATIONS_QUOTE_PROVIDER" + )] + pub quote_provider: Option, /// The rpc url to post the onchain attestation requests to #[arg(long = "flashtestations.rpc-url", env = "FLASHTESTATIONS_RPC_URL")] diff --git a/crates/op-rbuilder/src/flashtestations/attestation.rs b/crates/op-rbuilder/src/flashtestations/attestation.rs index e52f588..d6ab185 100644 --- a/crates/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/op-rbuilder/src/flashtestations/attestation.rs @@ -9,8 +9,8 @@ const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/ pub struct AttestationConfig { /// If true, uses the debug HTTP service instead of real TDX hardware pub debug: bool, - /// The URL of the debug HTTP service - pub debug_url: Option, + /// The URL of the quote provider + pub quote_provider: Option, } /// Trait for attestation providers @@ -18,18 +18,18 @@ pub trait AttestationProvider { fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result>; } -/// Debug HTTP service attestation provider -pub struct DebugAttestationProvider { +/// Remote attestation provider +pub struct RemoteAttestationProvider { service_url: String, } -impl DebugAttestationProvider { +impl RemoteAttestationProvider { pub fn new(service_url: String) -> Self { Self { service_url } } } -impl AttestationProvider for DebugAttestationProvider { +impl AttestationProvider for RemoteAttestationProvider { fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { let report_data_hex = hex::encode(report_data); let url = format!("{}/{}", self.service_url, report_data_hex); @@ -51,15 +51,16 @@ pub fn get_attestation_provider( config: AttestationConfig, ) -> Box { if config.debug { - Box::new(DebugAttestationProvider::new( + Box::new(RemoteAttestationProvider::new( config - .debug_url + .quote_provider .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), )) } else { - // TODO: replace with real attestation provider - Box::new(DebugAttestationProvider::new( - DEBUG_QUOTE_SERVICE_URL.to_string(), + Box::new(RemoteAttestationProvider::new( + config + .quote_provider + .expect("remote quote provider is required"), )) } } diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index fae9182..aed0efb 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -1,13 +1,13 @@ -use alloy::sol_types::{SolCall, SolValue}; +use alloy::sol_types::{SolCall, SolEvent, SolValue}; use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_op_evm::OpEvm; use alloy_primitives::{keccak256, map::foldhash::HashMap, Address, Bytes, TxKind, B256, U256}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; -use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm}; +use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm, EvmError}; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; +use reth_primitives::{Log, Recovered}; use reth_provider::{ProviderError, StateProvider}; use reth_revm::{database::StateProviderDatabase, State}; use revm::{ @@ -20,7 +20,7 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; -use tracing::{debug, error, info, warn}; +use tracing::{debug, info, warn}; use crate::{ builders::{ @@ -28,8 +28,9 @@ use crate::{ StandardBuilderTx, }, flashtestations::{ - BlockBuilderPolicyError, BlockData, FlashtestationRegistryError, + BlockBuilderPolicyError, BlockBuilderProofVerified, BlockData, FlashtestationRegistryError, FlashtestationRevertReason, IBlockBuilderPolicy, IFlashtestationRegistry, + TEEServiceRegistered, }, primitives::reth::ExecutionInfo, tx_signer::Signer, @@ -72,12 +73,13 @@ pub struct FlashtestationsBuilderTx { fallback_builder_tx: StandardBuilderTx, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct TxSimulateResult { pub gas_used: u64, pub success: bool, pub state_changes: HashMap, pub revert_reason: Option, + pub logs: Vec, } impl FlashtestationsBuilderTx { @@ -106,7 +108,6 @@ impl FlashtestationsBuilderTx { nonce: u64, ) -> Result, secp256k1::Error> { // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { chain_id, nonce, @@ -150,18 +151,11 @@ impl FlashtestationsBuilderTx { fn signed_block_builder_proof_tx( &self, - transactions: Vec, + block_content_hash: B256, ctx: &OpPayloadBuilderCtx, gas_limit: u64, nonce: u64, ) -> Result, secp256k1::Error> { - let block_content_hash = Self::compute_block_content_hash( - transactions, - ctx.parent_hash(), - ctx.block_number(), - ctx.timestamp(), - ); - let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { version: self.builder_proof_version, blockContentHash: block_content_hash, @@ -212,15 +206,17 @@ impl FlashtestationsBuilderTx { keccak256(&encoded) } - fn simulate_register_tee_service_tx( + fn simulate_register_tee_service_tx( &self, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, - nonce: u64, - ) -> Result - where - DB: revm::Database, - { + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + let register_tx = self.signed_register_tee_service_tx( self.attestation.clone(), ctx.block_gas_limit(), @@ -228,20 +224,27 @@ impl FlashtestationsBuilderTx { ctx.chain_id(), nonce, )?; - let ResultAndState { result, state } = evm - .transact(®ister_tx) - .map_err(|e| BuilderTransactionError::EvmExecutionError(Box::new(e)))?; + let ResultAndState { result, state } = match evm.transact(®ister_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + warn!(target: "flashtestations", %err, "register tee service tx failed"); + return Ok(TxSimulateResult::default()); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; match result { - // TODO: check logs - ExecutionResult::Success { gas_used, .. } => Ok(TxSimulateResult { + ExecutionResult::Success { gas_used, logs, .. } => Ok(TxSimulateResult { gas_used, success: true, state_changes: state, revert_reason: None, + logs, }), ExecutionResult::Revert { output, .. } => { let revert_reason = FlashtestationRegistryError::from(output); - warn!(target: "flashtestations", "register tee tx reverted during simulation: {}", revert_reason); Ok(TxSimulateResult { gas_used: 0, success: false, @@ -249,54 +252,73 @@ impl FlashtestationsBuilderTx { revert_reason: Some(FlashtestationRevertReason::FlashtestationRegistry( revert_reason, )), + logs: vec![], }) } - _ => { - error!(target: "flashtestations", "register tee tx halted or reverted during simulation"); - Ok(TxSimulateResult { - gas_used: 0, - success: false, - state_changes: state, - revert_reason: None, - }) - } + ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::Halt( + serde_json::to_string(&reason).unwrap_or_default(), + )), + logs: vec![], + }), } } - fn check_registered(&self, revert_reason: Option) -> bool { - if let Some(FlashtestationRevertReason::FlashtestationRegistry( - FlashtestationRegistryError::TEEServiceAlreadyRegistered(_, _), - )) = revert_reason - { - return true; + fn check_tee_address_registered_log(&self, logs: Vec, address: Address) -> bool { + for log in logs { + if log.topics().len() > 1 && log.topics()[0] == TEEServiceRegistered::SIGNATURE_HASH { + if let Ok(decoded) = TEEServiceRegistered::decode_log(&log) { + if decoded.address == address { + return true; + } + }; + } } false } - fn simulate_verify_block_proof_tx( + fn simulate_verify_block_proof_tx( &self, - transactions: Vec, + block_content_hash: B256, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, - nonce: u64, - ) -> Result - where - DB: revm::Database, - { - let verify_block_proof_tx = - self.signed_block_builder_proof_tx(transactions, ctx, ctx.block_gas_limit(), nonce)?; - let ResultAndState { result, state, .. } = evm.transact(&verify_block_proof_tx)?; + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + + let verify_block_proof_tx = self.signed_block_builder_proof_tx( + block_content_hash, + ctx, + ctx.block_gas_limit(), + nonce, + )?; + let ResultAndState { result, state } = match evm.transact(&verify_block_proof_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + warn!(target: "flashtestations", %err, "verify block proof tx failed"); + return Ok(TxSimulateResult::default()); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; match result { - // TODO: check logs - ExecutionResult::Success { gas_used, .. } => Ok(TxSimulateResult { + ExecutionResult::Success { gas_used, logs, .. } => Ok(TxSimulateResult { gas_used, success: true, state_changes: state, revert_reason: None, + logs, }), ExecutionResult::Revert { output, .. } => { let revert_reason = BlockBuilderPolicyError::from(output); - warn!(target: "flashtestations", "verify block proof tx reverted during simulation: {}", revert_reason); Ok(TxSimulateResult { gas_used: 0, success: false, @@ -304,18 +326,186 @@ impl FlashtestationsBuilderTx { revert_reason: Some(FlashtestationRevertReason::BlockBuilderPolicy( revert_reason, )), + logs: vec![], }) } - ExecutionResult::Halt { reason, .. } => { - warn!(target: "flashtestations", "verify block proof tx halted during simulation: {:?}", reason); - Ok(TxSimulateResult { - gas_used: 0, - success: false, - state_changes: state, - revert_reason: None, - }) + ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::Halt( + serde_json::to_string(&reason).unwrap_or_default(), + )), + logs: vec![], + }), + } + } + + fn check_verify_block_proof_log(&self, logs: Vec) -> bool { + for log in logs { + if log.topics().len() > 1 + && log.topics()[0] == BlockBuilderProofVerified::SIGNATURE_HASH + { + return true; } } + false + } + + fn fund_tee_service_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result, BuilderTransactionError> { + let balance = get_balance(evm.db_mut(), self.tee_service_signer.address)?; + if balance.is_zero() { + let funding_nonce = get_nonce(evm.db_mut(), self.funding_key.address)?; + let funding_tx = self.signed_funding_tx( + self.tee_service_signer.address, + self.funding_key, + self.funding_amount, + ctx.base_fee(), + ctx.chain_id(), + funding_nonce, + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(funding_tx.encoded_2718().as_slice()); + let ResultAndState { state, .. } = match evm.transact(&funding_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + warn!(target: "flashtestations", %err, "funding tx failed"); + return Ok(None); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; + info!(target: "flashtestations", "adding funding tx to builder txs"); + evm.db_mut().commit(state); + Ok(Some(BuilderTransactionCtx { + gas_used: 21000, + da_size, + signed_tx: funding_tx, + })) + } else { + Ok(None) + } + } + + fn register_tee_service_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result<(Option, bool), BuilderTransactionError> { + let TxSimulateResult { + gas_used, + success, + state_changes, + revert_reason, + logs, + } = self.simulate_register_tee_service_tx(ctx, evm)?; + if success { + let has_log = + self.check_tee_address_registered_log(logs, self.tee_service_signer.address); + if !has_log { + warn!(target: "flashtestations", "transaction did not emit TEEServiceRegistered log, FlashtestationRegistry contract address may be incorrect"); + Ok((None, false)) + } else { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + let register_tx = self.signed_register_tee_service_tx( + self.attestation.clone(), + gas_used * 64 / 63, // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer + ctx.base_fee(), + ctx.chain_id(), + nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + register_tx.encoded_2718().as_slice(), + ); + info!(target: "flashtestations", "adding register tee tx to builder txs"); + evm.db_mut().commit(state_changes); + Ok(( + Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: register_tx, + }), + false, + )) + } + } else if let Some(FlashtestationRevertReason::FlashtestationRegistry( + FlashtestationRegistryError::TEEServiceAlreadyRegistered(_, _), + )) = revert_reason + { + Ok((None, true)) + } else { + warn!(target: "flashtestations", reason = ?revert_reason, "register tee service tx failed"); + Ok((None, false)) + } + } + + fn verify_block_proof_tx( + &self, + transactions: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result, BuilderTransactionError> { + let block_content_hash = Self::compute_block_content_hash( + transactions.clone(), + ctx.parent_hash(), + ctx.block_number(), + ctx.timestamp(), + ); + + let TxSimulateResult { + gas_used, + success, + revert_reason, + logs, + .. + } = self.simulate_verify_block_proof_tx(block_content_hash, ctx, evm)?; + if success { + let has_log = self.check_verify_block_proof_log(logs); + if !has_log { + warn!(target: "flashtestations", "transaction did not emit BlockBuilderProofVerified log, BlockBuilderPolicy contract address may be incorrect"); + Ok(None) + } else { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + // Due to EIP-150, only 63/64 of available gas is forwarded to external calls so need to add a buffer + let verify_block_proof_tx = self.signed_block_builder_proof_tx( + block_content_hash, + ctx, + gas_used * 64 / 63, + nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + verify_block_proof_tx.encoded_2718().as_slice(), + ); + debug!(target: "flashtestations", "adding verify block proof tx to builder txs"); + Ok(Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: verify_block_proof_tx, + })) + } + } else { + warn!(target: "flashtestations", reason = ?revert_reason, "verify block proof tx failed, falling back to standard builder tx"); + self.fallback_builder_tx + .simulate_builder_tx(ctx, evm.db_mut()) + } } } @@ -344,103 +534,27 @@ impl BuilderTransactions for FlashtestationsBuilderTx { let mut builder_txs = Vec::::new(); - let mut nonce: u64 = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; if !self.registered.load(Ordering::Relaxed) { - let balance = get_balance(evm.db_mut(), self.tee_service_signer.address)?; - if balance.is_zero() { - // funding transaction - let funding_nonce = get_nonce(evm.db_mut(), self.funding_key.address)?; - let funding_tx = self.signed_funding_tx( - self.tee_service_signer.address, - self.funding_key, - self.funding_amount, - ctx.base_fee(), - ctx.chain_id(), - funding_nonce, - )?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - funding_tx.encoded_2718().as_slice(), - ); - let ResultAndState { state, .. } = evm.transact(&funding_tx)?; - info!(target: "flashtestations", "adding funding tx to builder txs"); - builder_txs.push(BuilderTransactionCtx { - gas_used: 21000, - da_size, - signed_tx: funding_tx, - }); - evm.db_mut().commit(state); - } - let TxSimulateResult { - gas_used, - success, - state_changes, - revert_reason, - } = self.simulate_register_tee_service_tx(ctx, &mut evm, nonce)?; - let registered = self.check_registered(revert_reason); - self.registered.store(registered, Ordering::Relaxed); - if success { - let register_tx = self.signed_register_tee_service_tx( - self.attestation.clone(), - gas_used * 64 / 63, // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - ctx.base_fee(), - ctx.chain_id(), - nonce, - )?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - register_tx.encoded_2718().as_slice(), - ); - info!(target: "flashtestations", "adding register tee tx to builder txs"); - builder_txs.push(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx: register_tx, - }); - evm.db_mut().commit(state_changes); - nonce += 1; + builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); + let (register_tx, registered) = self.register_tee_service_tx(ctx, &mut evm)?; + if registered { + self.registered.store(true, Ordering::Relaxed); } + builder_txs.extend(register_tx); } if self.enable_block_proofs { // add verify block proof tx - let TxSimulateResult { - gas_used, - success: should_include_tx, - .. - } = self.simulate_verify_block_proof_tx( + builder_txs.extend(self.verify_block_proof_tx( info.executed_transactions.clone(), ctx, &mut evm, - nonce, - )?; - if should_include_tx { - // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - let verify_block_proof_tx = self.signed_block_builder_proof_tx( - info.executed_transactions.clone(), - ctx, - gas_used * 64 / 63, - nonce, - )?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - verify_block_proof_tx.encoded_2718().as_slice(), - ); - debug!(target: "flashtestations", "adding verify block proof tx to builder txs"); - builder_txs.push(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx: verify_block_proof_tx, - }); - return Ok(builder_txs); - } else { - warn!(target: "flashtestations", "verify block proof tx reverted, falling back to standard builder tx"); - } + )?); + } else { + // Fallback to standard builder tx (either when block proofs are disabled or when verify block proof tx fails) + builder_txs.extend(self.fallback_builder_tx.simulate_builder_tx(ctx, db)?); } - // Fallback to standard builder tx (either when block proofs are disabled or when verify block proof tx fails) - builder_txs.extend( - self.fallback_builder_tx - .simulate_builder_txs(ctx, evm.db_mut())?, - ); - Ok(builder_txs) } } diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index 617264c..569046c 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -53,6 +53,8 @@ pub enum FlashtestationRevertReason { FlashtestationRegistry(FlashtestationRegistryError), #[error("block builder policy error: {0}")] BlockBuilderPolicy(BlockBuilderPolicyError), + #[error("halt: {0}")] + Halt(String), } #[derive(Debug, thiserror::Error)] diff --git a/crates/op-rbuilder/src/flashtestations/service.rs b/crates/op-rbuilder/src/flashtestations/service.rs index 00b2858..19d716f 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -1,11 +1,10 @@ use reth_node_builder::BuilderContext; -use secp256k1::{PublicKey, Secp256k1, SecretKey}; use tracing::{info, warn}; use crate::{ flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, traits::NodeBounds, - tx_signer::{generate_ethereum_keypair, public_key_to_address, Signer}, + tx_signer::{generate_ethereum_keypair, generate_key_from_seed, Signer}, }; use super::{ @@ -22,18 +21,16 @@ pub async fn bootstrap_flashtestations( where Node: NodeBounds, { - info!("Flashtestations enabled"); - let (private_key, public_key, address) = if args.debug { info!("Flashtestations debug mode enabled, generating debug key"); // Generate deterministic key for debugging purposes - let secp = Secp256k1::new(); - let private_key = SecretKey::from_slice(&[0x42; 32]).unwrap(); - let public_key = PublicKey::from_secret_key(&secp, &private_key); - (private_key, public_key, public_key_to_address(&public_key)) + generate_key_from_seed(&args.debug_tee_key_seed) } else { generate_ethereum_keypair() }; + + info!("Flashtestations key generated: {}", address); + let tee_service_signer = Signer { address, pubkey: public_key, @@ -52,7 +49,7 @@ where let attestation_provider = get_attestation_provider(AttestationConfig { debug: args.debug, - debug_url: args.debug_url, + quote_provider: args.quote_provider, }); // Prepare report data with public key (64 bytes, no 0x04 prefix) diff --git a/crates/op-rbuilder/src/tx_signer.rs b/crates/op-rbuilder/src/tx_signer.rs index f10e5d4..e421514 100644 --- a/crates/op-rbuilder/src/tx_signer.rs +++ b/crates/op-rbuilder/src/tx_signer.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use alloy::signers::k256::sha2::Sha256; use alloy_consensus::SignableTransaction; use alloy_primitives::{Address, Signature, B256, U256}; use op_alloy_consensus::OpTypedTransaction; @@ -100,6 +101,23 @@ pub fn public_key_to_address(public_key: &PublicKey) -> Address { Address::from_slice(&hash[12..32]) } +// Generate a key deterministically from a seed for debug and testing +// Do not use in production +pub fn generate_key_from_seed(seed: &str) -> (SecretKey, PublicKey, Address) { + // Hash the seed + let mut hasher = Sha256::new(); + hasher.update(seed.as_bytes()); + let hash = hasher.finalize(); + + // Create signing key + let secp = Secp256k1::new(); + let private_key = SecretKey::from_slice(&hash).expect("Failed to create private key"); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + let address = public_key_to_address(&public_key); + + (private_key, public_key, address) +} + #[cfg(test)] mod test { use super::*; From ebb91b0883e07ba61a038765cd29dc90d0a6d08d Mon Sep 17 00:00:00 2001 From: avalonche Date: Fri, 11 Jul 2025 16:54:19 +1000 Subject: [PATCH 06/14] fix log validation --- Cargo.lock | 1 + README.md | 2 +- crates/op-rbuilder/Cargo.toml | 1 + crates/op-rbuilder/src/builders/builder_tx.rs | 20 ++++++------- crates/op-rbuilder/src/builders/context.rs | 30 ++++++------------- .../src/builders/flashblocks/payload.rs | 2 +- .../src/builders/standard/payload.rs | 8 ++--- .../src/flashtestations/builder_tx.rs | 16 +++++----- 8 files changed, 34 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 803a89b..a7f46f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6290,6 +6290,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-eips", + "alloy-evm", "alloy-json-rpc", "alloy-network", "alloy-op-evm 0.15.0", diff --git a/README.md b/README.md index a2211b0..8ee8419 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ cargo run -p op-rbuilder --bin op-rbuilder --features=flashtestations -- node \ - `--flashtestations.enable-block-proofs`: Enable end-of-block transaction proofs that verify the block was built within a TEE - `--flashtestations.debug`: Enable debug mode with a deterministic TEE key and debug attestation server for testing and development - `--flashtestations.quote-provider `: Specify a remote URL to provide an attestation instead of generating a quote in process -- `--flashtestations.rpc-url `: Use a remote provider to submit attestations to +- `--flashtestations.rpc-url `: Use a remote rpc provider to submit attestations to ## Observability diff --git a/crates/op-rbuilder/Cargo.toml b/crates/op-rbuilder/Cargo.toml index a2bf891..013a2cd 100644 --- a/crates/op-rbuilder/Cargo.toml +++ b/crates/op-rbuilder/Cargo.toml @@ -59,6 +59,7 @@ alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-contract.workspace = true alloy-eips.workspace = true +alloy-evm.workspace = true alloy-rpc-types-beacon.workspace = true alloy-rpc-types-engine.workspace = true alloy-transport-http.workspace = true diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index a499b6d..4fc76c6 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -1,5 +1,6 @@ use alloy_consensus::TxEip1559; use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718}; +use alloy_evm::Database; use alloy_primitives::{ map::foldhash::{HashSet, HashSetExt}, Address, TxKind, @@ -15,7 +16,7 @@ use reth_provider::{ProviderError, StateProvider}; use reth_revm::State; use revm::{ context::result::{EVMError, ResultAndState}, - Database, DatabaseCommit, + DatabaseCommit, }; use tracing::{debug, warn}; @@ -75,7 +76,7 @@ pub trait BuilderTransactions: Debug { state_provider: impl StateProvider, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State>, + db: &mut State, ) -> Result, BuilderTransactionError>; fn add_builder_txs( @@ -83,7 +84,7 @@ pub trait BuilderTransactions: Debug { state_provider: impl StateProvider, info: &mut ExecutionInfo, builder_ctx: &OpPayloadBuilderCtx, - db: &mut State>, + db: &mut State, ) -> Result<(), BuilderTransactionError> { { let mut evm = builder_ctx @@ -156,7 +157,7 @@ impl StandardBuilderTx { pub fn simulate_builder_tx( &self, ctx: &OpPayloadBuilderCtx, - db: &mut State>, + db: &mut State, ) -> Result, BuilderTransactionError> { match self.signer { Some(signer) => { @@ -197,17 +198,14 @@ impl StandardBuilderTx { 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, + db: &mut State, signer: Signer, gas_used: u64, message: Vec, - ) -> Result, BuilderTransactionError> - where - DB: Database, - { + ) -> Result, BuilderTransactionError> { let nonce = db .load_cache_account(signer.address) .map(|acc| acc.account_info().unwrap_or_default().nonce) @@ -240,7 +238,7 @@ impl BuilderTransactions for StandardBuilderTx { _state_provider: impl StateProvider, _info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State>, + db: &mut State, ) -> Result, BuilderTransactionError> { let builder_tx = self.simulate_builder_tx(ctx, db)?; Ok(builder_tx.into_iter().collect()) diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index 8ca514a..fe8d539 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -1,5 +1,6 @@ use alloy_consensus::{conditional::BlockConditionalAttributes, Eip658Value, Transaction}; use alloy_eips::Typed2718; +use alloy_evm::Database; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{BlockHash, Bytes, U256}; use alloy_rpc_types_eth::Withdrawals; @@ -27,12 +28,9 @@ use reth_optimism_txpool::{ use reth_payload_builder::PayloadId; use reth_primitives::SealedHeader; use reth_primitives_traits::{InMemorySize, SignedTransaction}; -use reth_provider::ProviderError; use reth_revm::{context::Block, State}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; -use revm::{ - context::result::ResultAndState, interpreter::as_u64_saturated, Database, DatabaseCommit, -}; +use revm::{context::result::ResultAndState, interpreter::as_u64_saturated, DatabaseCommit}; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{debug, info, trace}; @@ -239,13 +237,10 @@ impl OpPayloadBuilderCtx { } /// Executes all sequencer transactions that are included in the payload attributes. - pub fn execute_sequencer_transactions( + pub fn execute_sequencer_transactions( &self, - db: &mut State, - ) -> Result, PayloadBuilderError> - where - DB: Database + std::fmt::Debug, - { + db: &mut State, + ) -> Result, PayloadBuilderError> { let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); @@ -326,17 +321,14 @@ impl OpPayloadBuilderCtx { /// Executes the given best transactions and updates the execution info. /// /// Returns `Ok(Some(())` if the job was cancelled. - pub fn execute_best_transactions( + pub fn execute_best_transactions( &self, info: &mut ExecutionInfo, - db: &mut State, + db: &mut State, mut best_txs: impl PayloadTxsBounds, block_gas_limit: u64, block_da_limit: Option, - ) -> Result, PayloadBuilderError> - where - DB: Database + std::fmt::Debug, - { + ) -> Result, PayloadBuilderError> { let execute_txs_start_time = Instant::now(); let mut num_txs_considered = 0; let mut num_txs_simulated = 0; @@ -345,11 +337,7 @@ impl OpPayloadBuilderCtx { let mut num_bundles_reverted = 0; let base_fee = self.base_fee(); let tx_da_limit = self.da_config.max_da_tx_size(); - let mut evm: alloy_op_evm::OpEvm< - &mut State, - revm::inspector::NoOpInspector, - reth_evm::precompiles::PrecompilesMap, - > = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); info!( target: "payload_builder", diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index f924c11..fc2ffe2 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -412,7 +412,7 @@ where flashblock_count = ctx.flashblock_index(), target_gas = total_gas_per_batch, gas_used = info.cumulative_gas_used, - target_da = total_da_per_batch.unwrap_or(0), + target_da = total_da_per_batch, da_used = info.cumulative_da_bytes_used, "Building flashblock", ); diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index 0151909..b384ad5 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -9,6 +9,7 @@ use alloy_consensus::{ constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, }; use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; +use alloy_evm::Database; use alloy_primitives::U256; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; @@ -22,14 +23,13 @@ use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; use reth_primitives::RecoveredBlock; -use reth_provider::{ExecutionOutcome, ProviderError, StateProvider}; +use reth_provider::{ExecutionOutcome, StateProvider}; use reth_revm::{ database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, }; use reth_transaction_pool::{ BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, }; -use revm::Database; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; @@ -305,7 +305,7 @@ impl OpBuilder<'_, Txs> { pub fn execute( self, state_provider: impl StateProvider, - db: &mut State>, + db: &mut State, ctx: &OpPayloadBuilderCtx, builder_tx: BuilderTx, ) -> Result, PayloadBuilderError> @@ -410,7 +410,7 @@ impl OpBuilder<'_, Txs> { /// Builds the payload on top of the state. pub fn build( self, - state: impl Database, + state: impl Database, state_provider: impl StateProvider, ctx: OpPayloadBuilderCtx, builder_tx: BuilderTx, diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index aed0efb..a748a1e 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -1,6 +1,7 @@ use alloy::sol_types::{SolCall, SolEvent, SolValue}; use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; +use alloy_evm::Database; use alloy_op_evm::OpEvm; use alloy_primitives::{keccak256, map::foldhash::HashMap, Address, Bytes, TxKind, B256, U256}; use core::fmt::Debug; @@ -8,13 +9,13 @@ use op_alloy_consensus::OpTypedTransaction; use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm, EvmError}; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::{Log, Recovered}; -use reth_provider::{ProviderError, StateProvider}; +use reth_provider::StateProvider; use reth_revm::{database::StateProviderDatabase, State}; use revm::{ context::result::{ExecutionResult, ResultAndState}, inspector::NoOpInspector, state::Account, - Database, DatabaseCommit, + DatabaseCommit, }; use std::sync::{ atomic::{AtomicBool, Ordering}, @@ -177,6 +178,7 @@ impl FlashtestationsBuilderTx { /// Computes the block content hash according to the formula: /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) + /// https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#block-building-process fn compute_block_content_hash( transactions: Vec, parent_hash: B256, @@ -269,9 +271,9 @@ impl FlashtestationsBuilderTx { fn check_tee_address_registered_log(&self, logs: Vec, address: Address) -> bool { for log in logs { - if log.topics().len() > 1 && log.topics()[0] == TEEServiceRegistered::SIGNATURE_HASH { + if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { if let Ok(decoded) = TEEServiceRegistered::decode_log(&log) { - if decoded.address == address { + if decoded.teeAddress == address { return true; } }; @@ -343,9 +345,7 @@ impl FlashtestationsBuilderTx { fn check_verify_block_proof_log(&self, logs: Vec) -> bool { for log in logs { - if log.topics().len() > 1 - && log.topics()[0] == BlockBuilderProofVerified::SIGNATURE_HASH - { + if log.topics().first() == Some(&BlockBuilderProofVerified::SIGNATURE_HASH) { return true; } } @@ -515,7 +515,7 @@ impl BuilderTransactions for FlashtestationsBuilderTx { state_provider: impl StateProvider, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State>, + db: &mut State, ) -> Result, BuilderTransactionError> { let state = StateProviderDatabase::new(state_provider); let mut simulation_state = State::builder() From fc73a4edab75d688755e7ce2f173e27c19fa75d9 Mon Sep 17 00:00:00 2001 From: avalonche Date: Thu, 17 Jul 2025 06:12:23 +1000 Subject: [PATCH 07/14] fix registration retries --- Cargo.lock | 3 +- crates/op-rbuilder/Cargo.toml | 1 + crates/op-rbuilder/src/builders/builder_tx.rs | 6 +-- .../src/flashtestations/builder_tx.rs | 47 ++++++++++++++----- crates/op-rbuilder/src/flashtestations/mod.rs | 2 +- .../src/flashtestations/tx_manager.rs | 11 +++-- crates/op-rbuilder/src/tx_signer.rs | 2 +- 7 files changed, 51 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7f46f1..57b2ba2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6290,7 +6290,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-eips", - "alloy-evm", + "alloy-evm 0.14.0", "alloy-json-rpc", "alloy-network", "alloy-op-evm 0.15.0", @@ -6321,6 +6321,7 @@ dependencies = [ "jsonrpsee 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpsee-core 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpsee-types 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", + "k256", "macros", "metrics", "moka", diff --git a/crates/op-rbuilder/Cargo.toml b/crates/op-rbuilder/Cargo.toml index 013a2cd..9581962 100644 --- a/crates/op-rbuilder/Cargo.toml +++ b/crates/op-rbuilder/Cargo.toml @@ -122,6 +122,7 @@ http = "1.0" sha3 = "0.10" hex = "0.4" ureq = "2.10" +k256 = "0.13.4" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 4fc76c6..d427768 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -73,7 +73,7 @@ impl From for PayloadBuilderError { pub trait BuilderTransactions: Debug { fn simulate_builder_txs( &self, - state_provider: impl StateProvider, + state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, @@ -81,7 +81,7 @@ pub trait BuilderTransactions: Debug { fn add_builder_txs( &self, - state_provider: impl StateProvider, + state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, builder_ctx: &OpPayloadBuilderCtx, db: &mut State, @@ -235,7 +235,7 @@ impl StandardBuilderTx { impl BuilderTransactions for StandardBuilderTx { fn simulate_builder_txs( &self, - _state_provider: impl StateProvider, + _state_provider: impl StateProvider + Clone, _info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index a748a1e..b48b743 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -1,9 +1,9 @@ -use alloy::sol_types::{SolCall, SolEvent, SolValue}; use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::Database; use alloy_op_evm::OpEvm; use alloy_primitives::{keccak256, map::foldhash::HashMap, Address, Bytes, TxKind, B256, U256}; +use alloy_sol_types::{SolCall, SolEvent, SolValue}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm, EvmError}; @@ -385,7 +385,7 @@ impl FlashtestationsBuilderTx { } } }; - info!(target: "flashtestations", "adding funding tx to builder txs"); + info!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?funding_tx.tx_hash(), "adding funding tx to builder txs"); evm.db_mut().commit(state); Ok(Some(BuilderTransactionCtx { gas_used: 21000, @@ -431,7 +431,7 @@ impl FlashtestationsBuilderTx { let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( register_tx.encoded_2718().as_slice(), ); - info!(target: "flashtestations", "adding register tee tx to builder txs"); + info!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?register_tx.tx_hash(), "adding register tee tx to builder txs"); evm.db_mut().commit(state_changes); Ok(( Some(BuilderTransactionCtx { @@ -494,7 +494,7 @@ impl FlashtestationsBuilderTx { let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( verify_block_proof_tx.encoded_2718().as_slice(), ); - debug!(target: "flashtestations", "adding verify block proof tx to builder txs"); + debug!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?verify_block_proof_tx.tx_hash(), "adding verify block proof tx to builder txs"); Ok(Some(BuilderTransactionCtx { gas_used, da_size, @@ -507,21 +507,47 @@ impl FlashtestationsBuilderTx { .simulate_builder_tx(ctx, evm.db_mut()) } } + + fn set_registered( + &self, + state_provider: impl StateProvider + Clone, + ctx: &OpPayloadBuilderCtx, + ) { + let state = StateProviderDatabase::new(state_provider.clone()); + let mut simulation_state = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + let mut evm = ctx + .evm_config + .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + }); + match self.register_tee_service_tx(ctx, &mut evm) { + Ok((_, registered)) => { + self.registered.store(registered, Ordering::Relaxed); + } + Err(e) => { + debug!(target: "flashtestations", error = ?e, "simulation error when checking if registered"); + } + } + } } impl BuilderTransactions for FlashtestationsBuilderTx { fn simulate_builder_txs( &self, - state_provider: impl StateProvider, + state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, ) -> Result, BuilderTransactionError> { - let state = StateProviderDatabase::new(state_provider); + 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_prestate(db.bundle_state.clone()) .with_bundle_update() .build(); @@ -535,11 +561,10 @@ impl BuilderTransactions for FlashtestationsBuilderTx { let mut builder_txs = Vec::::new(); if !self.registered.load(Ordering::Relaxed) { + info!(target: "flashtestations", "tee service not registered yet, attempting to add registration tx"); + self.set_registered(state_provider, ctx); builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); - let (register_tx, registered) = self.register_tee_service_tx(ctx, &mut evm)?; - if registered { - self.registered.store(true, Ordering::Relaxed); - } + let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?; builder_txs.extend(register_tx); } diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index 569046c..f84ef0d 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -1,5 +1,5 @@ -use alloy::{sol, sol_types::SolError}; use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256}; +use alloy_sol_types::{sol, SolError}; sol!( #[sol(rpc, abi)] diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs index 95db965..ed4aca0 100644 --- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,13 +1,16 @@ use alloy_json_rpc::RpcError; use alloy_network::ReceiptResponse; -use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256}; use alloy_rpc_types_eth::TransactionRequest; -use alloy_transport::TransportResult; +use alloy_sol_types::SolCall; +use alloy_transport::{TransportError, TransportErrorKind, TransportResult}; +use k256::ecdsa; use std::time::Duration; -use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; +use alloy_provider::{ + PendingTransactionBuilder, PendingTransactionError, Provider, ProviderBuilder, +}; use alloy_signer_local::PrivateKeySigner; -use alloy_sol_types::{sol, SolCall, SolValue}; use op_alloy_network::Optimism; use tracing::{debug, error, info, warn}; diff --git a/crates/op-rbuilder/src/tx_signer.rs b/crates/op-rbuilder/src/tx_signer.rs index e421514..6b8b847 100644 --- a/crates/op-rbuilder/src/tx_signer.rs +++ b/crates/op-rbuilder/src/tx_signer.rs @@ -1,8 +1,8 @@ use std::str::FromStr; -use alloy::signers::k256::sha2::Sha256; use alloy_consensus::SignableTransaction; use alloy_primitives::{Address, Signature, B256, U256}; +use k256::sha2::Sha256; use op_alloy_consensus::OpTypedTransaction; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; From 23fce750be294cd2d1dca479423828a9b2004026 Mon Sep 17 00:00:00 2001 From: avalonche Date: Fri, 18 Jul 2025 00:32:45 +1000 Subject: [PATCH 08/14] fix args --- README.md | 2 +- crates/op-rbuilder/src/flashtestations/args.rs | 6 +++++- crates/op-rbuilder/src/flashtestations/builder_tx.rs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ee8419..35b1837 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Flashtestations is a feature that enables Trusted Execution Environment (TEE) at To run op-rbuilder with flashtestations: ```bash -cargo run -p op-rbuilder --bin op-rbuilder --features=flashtestations -- node \ +cargo run -p op-rbuilder --bin op-rbuilder -- node \ --chain /path/to/chain-config.json \ --http \ --authrpc.port 9551 \ diff --git a/crates/op-rbuilder/src/flashtestations/args.rs b/crates/op-rbuilder/src/flashtestations/args.rs index 86f0b83..56036d1 100644 --- a/crates/op-rbuilder/src/flashtestations/args.rs +++ b/crates/op-rbuilder/src/flashtestations/args.rs @@ -34,7 +34,11 @@ pub struct FlashtestationsArgs { // Remote url for attestations #[arg( long = "flashtestations.quote-provider", - env = "FLASHTESTATIONS_QUOTE_PROVIDER" + env = "FLASHTESTATIONS_QUOTE_PROVIDER", + required_if_eq_all([ + ("flashtestations_enabled", "true"), + ("debug", "false") + ]) )] pub quote_provider: Option, diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index b48b743..236960b 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -561,7 +561,7 @@ impl BuilderTransactions for FlashtestationsBuilderTx { let mut builder_txs = Vec::::new(); if !self.registered.load(Ordering::Relaxed) { - info!(target: "flashtestations", "tee service not registered yet, attempting to add registration tx"); + info!(target: "flashtestations", "tee service not registered yet, attempting to register"); self.set_registered(state_provider, ctx); builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?; From df74e2e95a8a99ba4b1fbef8274ad558f8de8045 Mon Sep 17 00:00:00 2001 From: avalonche Date: Fri, 18 Jul 2025 06:47:34 +1000 Subject: [PATCH 09/14] make quote provider default to debug url --- crates/op-rbuilder/src/flashtestations/args.rs | 6 +----- crates/op-rbuilder/src/flashtestations/attestation.rs | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/op-rbuilder/src/flashtestations/args.rs b/crates/op-rbuilder/src/flashtestations/args.rs index 56036d1..86f0b83 100644 --- a/crates/op-rbuilder/src/flashtestations/args.rs +++ b/crates/op-rbuilder/src/flashtestations/args.rs @@ -34,11 +34,7 @@ pub struct FlashtestationsArgs { // Remote url for attestations #[arg( long = "flashtestations.quote-provider", - env = "FLASHTESTATIONS_QUOTE_PROVIDER", - required_if_eq_all([ - ("flashtestations_enabled", "true"), - ("debug", "false") - ]) + env = "FLASHTESTATIONS_QUOTE_PROVIDER" )] pub quote_provider: Option, diff --git a/crates/op-rbuilder/src/flashtestations/attestation.rs b/crates/op-rbuilder/src/flashtestations/attestation.rs index d6ab185..2086343 100644 --- a/crates/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/op-rbuilder/src/flashtestations/attestation.rs @@ -47,6 +47,7 @@ impl AttestationProvider for RemoteAttestationProvider { } } +#[allow(clippy::if_same_then_else)] pub fn get_attestation_provider( config: AttestationConfig, ) -> Box { @@ -60,7 +61,7 @@ pub fn get_attestation_provider( Box::new(RemoteAttestationProvider::new( config .quote_provider - .expect("remote quote provider is required"), + .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), // TODO: remove this once we have a real quote provider )) } } From 501d23e5e6c02a38533a826c66fe1fa52b29ae78 Mon Sep 17 00:00:00 2001 From: avalonche Date: Tue, 22 Jul 2025 10:21:19 +1000 Subject: [PATCH 10/14] fix payload build error on last flashblock --- crates/op-rbuilder/src/flashtestations/builder_tx.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 236960b..0c4e132 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -546,7 +546,6 @@ impl BuilderTransactions for FlashtestationsBuilderTx { let state = StateProviderDatabase::new(state_provider.clone()); let mut simulation_state = State::builder() .with_database(state) - .with_cached_prestate(db.cache.clone()) .with_bundle_prestate(db.bundle_state.clone()) .with_bundle_update() .build(); From 5979450ebfdf44157e76d8bb3b10f97b4b04dc3a Mon Sep 17 00:00:00 2001 From: avalonche Date: Tue, 22 Jul 2025 14:26:50 +1000 Subject: [PATCH 11/14] add flashblocks tx --- crates/op-rbuilder/src/builders/builder_tx.rs | 143 ++++------------- .../src/builders/flashblocks/builder_tx.rs | 149 ++++++++++++++++++ .../src/builders/flashblocks/mod.rs | 2 +- .../src/builders/flashblocks/payload.rs | 19 ++- .../src/builders/flashblocks/service.rs | 39 ++--- crates/op-rbuilder/src/builders/mod.rs | 4 +- .../src/builders/standard/builder_tx.rs | 146 +++++++++++++++++ .../op-rbuilder/src/builders/standard/mod.rs | 1 + .../src/builders/standard/service.rs | 45 +++--- .../src/flashtestations/builder_tx.rs | 44 ++---- .../src/flashtestations/service.rs | 6 +- 11 files changed, 394 insertions(+), 204 deletions(-) create mode 100644 crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs create mode 100644 crates/op-rbuilder/src/builders/standard/builder_tx.rs diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index d427768..ffd654a 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -1,29 +1,27 @@ -use alloy_consensus::TxEip1559; -use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718}; use alloy_evm::Database; use alloy_primitives::{ map::foldhash::{HashSet, HashSetExt}, - Address, TxKind, + Address, }; use core::fmt::Debug; -use op_alloy_consensus::OpTypedTransaction; use op_revm::OpTransactionError; use reth_evm::{eth::receipt_builder::ReceiptBuilderCtx, ConfigureEvm, Evm}; use reth_node_api::PayloadBuilderError; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::{ProviderError, StateProvider}; -use reth_revm::State; +use reth_revm::{ + database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, +}; use revm::{ context::result::{EVMError, ResultAndState}, DatabaseCommit, }; use tracing::{debug, warn}; -use crate::{ - builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer, -}; +use crate::{builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo}; +#[derive(Debug, Clone)] pub struct BuilderTransactionCtx { pub gas_used: u64, pub da_size: u64, @@ -70,12 +68,12 @@ impl From for PayloadBuilderError { } } -pub trait BuilderTransactions: Debug { +pub trait BuilderTransactions: Debug { fn simulate_builder_txs( &self, state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, db: &mut State, ) -> Result, BuilderTransactionError>; @@ -83,7 +81,7 @@ pub trait BuilderTransactions: Debug { &self, state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, - builder_ctx: &OpPayloadBuilderCtx, + builder_ctx: &OpPayloadBuilderCtx, db: &mut State, ) -> Result<(), BuilderTransactionError> { { @@ -92,7 +90,7 @@ pub trait BuilderTransactions: Debug { .evm_with_env(&mut *db, builder_ctx.evm_env.clone()); let mut invalid: HashSet

= HashSet::new(); - // simulate builder txs on the top of block state + let builder_txs = self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; for builder_tx in builder_txs { @@ -139,108 +137,33 @@ pub trait BuilderTransactions: Debug { Ok(()) } } -} - -// Scaffolding for how to construct the end of block builder transaction -// This will be the regular end of block transaction without the TEE key -#[derive(Debug, Clone)] -pub struct StandardBuilderTx { - #[allow(dead_code)] - pub signer: Option, -} - -impl StandardBuilderTx { - pub fn new(signer: Option) -> Self { - Self { signer } - } - pub fn simulate_builder_tx( + fn simulate_builder_txs_state( &self, - ctx: &OpPayloadBuilderCtx, + state_provider: impl StateProvider + Clone, + builder_txs: Vec<&BuilderTransactionCtx>, + ctx: &OpPayloadBuilderCtx, db: &mut State, - ) -> Result, BuilderTransactionError> { - match self.signer { - Some(signer) => { - let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); - let gas_used = self.estimate_builder_tx_gas(&message); - let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - signed_tx.encoded_2718().as_slice(), - ); - Ok(Some(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - })) - } - None => Ok(None), + ) -> Result>, BuilderTransactionError> { + let state = StateProviderDatabase::new(state_provider.clone()); + let mut simulation_state = State::builder() + .with_database(state) + .with_bundle_prestate(db.bundle_state.clone()) + .with_bundle_update() + .build(); + let mut evm = ctx + .evm_config + .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + + for builder_tx in builder_txs { + let ResultAndState { state, .. } = evm + .transact(&builder_tx.signed_tx) + .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; + + evm.db_mut().commit(state); + evm.db_mut().merge_transitions(BundleRetention::Reverts); } - } - fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { - // Count zero and non-zero bytes - let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { - if byte == 0 { - (zeros + 1, nonzeros) - } else { - (zeros, nonzeros + 1) - } - }); - - // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) - let zero_cost = zero_bytes * 4; - let nonzero_cost = nonzero_bytes * 16; - - // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 - let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; - let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; - - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) - } - - fn signed_builder_tx( - &self, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - signer: Signer, - gas_used: u64, - message: Vec, - ) -> Result, BuilderTransactionError> { - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?; - - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: ctx.chain_id(), - nonce, - gas_limit: gas_used, - max_fee_per_gas: ctx.base_fee().into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(Address::ZERO), - // Include the message as part of the transaction data - input: message.into(), - ..Default::default() - }); - // Sign the transaction - let builder_tx = signer - .sign_tx(tx) - .map_err(BuilderTransactionError::SigningError)?; - - Ok(builder_tx) - } -} - -impl BuilderTransactions for StandardBuilderTx { - fn simulate_builder_txs( - &self, - _state_provider: impl StateProvider + Clone, - _info: &mut ExecutionInfo, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result, BuilderTransactionError> { - let builder_tx = self.simulate_builder_tx(ctx, db)?; - Ok(builder_tx.into_iter().collect()) + Ok(simulation_state) } } diff --git a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs new file mode 100644 index 0000000..45b335e --- /dev/null +++ b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -0,0 +1,149 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718}; +use alloy_evm::Database; +use alloy_primitives::{Address, TxKind}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; +use reth_provider::StateProvider; +use reth_revm::State; + +use crate::{ + builders::{ + context::OpPayloadBuilderCtx, flashblocks::payload::FlashblocksExtraCtx, + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, + }, + flashtestations::builder_tx::FlashtestationsBuilderTx, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub struct FlashblocksBuilderTx { + pub signer: Option, + pub flashtestations_builder_tx: Option, +} + +impl FlashblocksBuilderTx { + pub fn new( + signer: Option, + flashtestations_builder_tx: Option, + ) -> Self { + Self { + signer, + flashtestations_builder_tx, + } + } + + pub fn simulate_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + match self.signer { + Some(signer) => { + let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); + let gas_used = self.estimate_builder_tx_gas(&message); + let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + signed_tx.encoded_2718().as_slice(), + ); + Ok(Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + })) + } + None => Ok(None), + } + } + + fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { + // Count zero and non-zero bytes + let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { + if byte == 0 { + (zeros + 1, nonzeros) + } else { + (zeros, nonzeros + 1) + } + }); + + // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) + let zero_cost = zero_bytes * 4; + let nonzero_cost = nonzero_bytes * 16; + + // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 + let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; + let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) + } + + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> { + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?; + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + gas_limit: gas_used, + max_fee_per_gas: ctx.base_fee().into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(Address::ZERO), + // Include the message as part of the transaction data + input: message.into(), + ..Default::default() + }); + // Sign the transaction + let builder_tx = signer + .sign_tx(tx) + .map_err(BuilderTransactionError::SigningError)?; + + Ok(builder_tx) + } +} + +impl BuilderTransactions for FlashblocksBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; + builder_txs.extend(flashblocks_builder_tx.clone()); + if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { + // We only include flashtestations txs in the last flashblock + if ctx.flashblock_index() == ctx.target_flashblock_count() - 1 { + let mut simulation_state = self.simulate_builder_txs_state::( + state_provider.clone(), + flashblocks_builder_tx.iter().collect(), + ctx, + db, + )?; + let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + &mut simulation_state, + )?; + builder_txs.extend(flashtestations_builder_txs); + } + } + Ok(builder_txs) + } +} diff --git a/crates/op-rbuilder/src/builders/flashblocks/mod.rs b/crates/op-rbuilder/src/builders/flashblocks/mod.rs index d85259a..7470be4 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/mod.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/mod.rs @@ -3,8 +3,8 @@ use crate::traits::{NodeBounds, PoolBounds}; use config::FlashblocksConfig; use service::FlashblocksServiceBuilder; +mod builder_tx; mod config; -//mod context; mod payload; mod service; mod wspub; diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index fc2ffe2..5c74fa7 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -60,7 +60,7 @@ struct ExtraExecutionInfo { } #[derive(Debug, Default)] -struct FlashblocksExtraCtx { +pub struct FlashblocksExtraCtx { /// Current flashblock index pub flashblock_index: u64, /// Target flashblock count @@ -113,7 +113,6 @@ pub struct OpPayloadBuilder { /// The metrics for the builder pub metrics: Arc, /// The end of builder transaction type - #[allow(dead_code)] pub builder_tx: BuilderTx, } @@ -172,7 +171,7 @@ impl OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BuilderTx: BuilderTransactions, + BuilderTx: BuilderTransactions, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -270,10 +269,8 @@ where ctx.metrics.sequencer_tx_gauge.set(sequencer_tx_time); // If we have payload with txpool we add first builder tx right after deposits - if !ctx.attributes().no_tx_pool { - self.builder_tx - .add_builder_txs(&state_provider, &mut info, &ctx, &mut db)?; - } + self.builder_tx + .add_builder_txs(&state_provider, &mut info, &ctx, &mut db)?; // We subtract gas limit and da limit for builder transaction from the whole limit let builder_txs = self @@ -412,12 +409,13 @@ where flashblock_count = ctx.flashblock_index(), target_gas = total_gas_per_batch, gas_used = info.cumulative_gas_used, - target_da = total_da_per_batch, + target_da = total_da_per_batch.unwrap_or(0), da_used = info.cumulative_da_bytes_used, "Building flashblock", ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); + total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size if let Some(da_limit) = total_da_per_batch.as_mut() { @@ -475,7 +473,8 @@ where .payload_tx_simulation_gauge .set(payload_tx_simulation_time); - ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); + self.builder_tx + .add_builder_txs(&state_provider, &mut info, &ctx, &mut db)?; let total_block_built_duration = Instant::now(); let build_result = build_block(db, &ctx, &mut info); @@ -682,7 +681,7 @@ impl crate::builders::generator::PayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BuilderTx: BuilderTransactions + Clone + Send + Sync, + BuilderTx: BuilderTransactions + Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs index 0603466..534b40e 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs @@ -1,7 +1,8 @@ use super::{payload::OpPayloadBuilder, FlashblocksConfig}; use crate::{ builders::{ - builder_tx::{BuilderTransactions, StandardBuilderTx}, + builder_tx::BuilderTransactions, + flashblocks::{builder_tx::FlashblocksBuilderTx, payload::FlashblocksExtraCtx}, generator::BlockPayloadJobGenerator, BuilderConfig, }, @@ -27,7 +28,7 @@ impl FlashblocksServiceBuilder { where Node: NodeBounds, Pool: PoolBounds, - BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, + BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, { let payload_builder = OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), @@ -72,27 +73,21 @@ where _: OpEvmConfig, ) -> eyre::Result::Payload>> { let signer = self.0.builder_signer; - if self.0.flashtestations_config.flashtestations_enabled { - let flashtestations_builder_tx = match bootstrap_flashtestations( - self.0.flashtestations_config.clone(), - ctx, - signer, - ) - .await - { - Ok(service) => service, + let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled { + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), ctx).await { + Ok(builder_tx) => Some(builder_tx), Err(e) => { - tracing::warn!(error = %e, "Failed to bootstrap flashtestations, falling back to standard builder tx"); - return self.spawn_payload_builder_service( - ctx, - pool, - StandardBuilderTx { signer }, - ); + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builderb will not include flashtestations txs"); + None } - }; - - return self.spawn_payload_builder_service(ctx, pool, flashtestations_builder_tx); - } - self.spawn_payload_builder_service(ctx, pool, StandardBuilderTx { signer }) + } + } else { + None + }; + self.spawn_payload_builder_service( + ctx, + pool, + FlashblocksBuilderTx::new(signer, flashtestations_builder_tx), + ) } } diff --git a/crates/op-rbuilder/src/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs index 17b78b1..4b7630b 100644 --- a/crates/op-rbuilder/src/builders/mod.rs +++ b/crates/op-rbuilder/src/builders/mod.rs @@ -20,9 +20,7 @@ mod flashblocks; mod generator; mod standard; -pub use builder_tx::{ - BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, StandardBuilderTx, -}; +pub use builder_tx::{BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions}; pub use context::OpPayloadBuilderCtx; pub use flashblocks::FlashblocksBuilder; pub use standard::StandardBuilder; diff --git a/crates/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/op-rbuilder/src/builders/standard/builder_tx.rs new file mode 100644 index 0000000..865e5fc --- /dev/null +++ b/crates/op-rbuilder/src/builders/standard/builder_tx.rs @@ -0,0 +1,146 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718}; +use alloy_evm::Database; +use alloy_primitives::{Address, TxKind}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; +use reth_provider::StateProvider; +use reth_revm::State; + +use crate::{ + builders::{ + context::OpPayloadBuilderCtx, BuilderTransactionCtx, BuilderTransactionError, + BuilderTransactions, + }, + flashtestations::builder_tx::FlashtestationsBuilderTx, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub struct StandardBuilderTx { + pub signer: Option, + pub flashtestations_builder_tx: Option, +} + +impl StandardBuilderTx { + pub fn new( + signer: Option, + flashtestations_builder_tx: Option, + ) -> Self { + Self { + signer, + flashtestations_builder_tx, + } + } + + pub fn simulate_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + match self.signer { + Some(signer) => { + let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); + let gas_used = self.estimate_builder_tx_gas(&message); + let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + signed_tx.encoded_2718().as_slice(), + ); + Ok(Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + })) + } + None => Ok(None), + } + } + + fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { + // Count zero and non-zero bytes + let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { + if byte == 0 { + (zeros + 1, nonzeros) + } else { + (zeros, nonzeros + 1) + } + }); + + // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) + let zero_cost = zero_bytes * 4; + let nonzero_cost = nonzero_bytes * 16; + + // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 + let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; + let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) + } + + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> { + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?; + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + gas_limit: gas_used, + max_fee_per_gas: ctx.base_fee().into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(Address::ZERO), + // Include the message as part of the transaction data + input: message.into(), + ..Default::default() + }); + // Sign the transaction + let builder_tx = signer + .sign_tx(tx) + .map_err(BuilderTransactionError::SigningError)?; + + Ok(builder_tx) + } +} + +impl BuilderTransactions for StandardBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + let standard_builder_tx = self.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::<()>( + state_provider.clone(), + standard_builder_tx.iter().collect(), + ctx, + db, + )?; + let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + &mut simulation_state, + )?; + builder_txs.extend(flashtestations_builder_txs); + } + Ok(builder_txs) + } +} diff --git a/crates/op-rbuilder/src/builders/standard/mod.rs b/crates/op-rbuilder/src/builders/standard/mod.rs index 157428d..e26bbc6 100644 --- a/crates/op-rbuilder/src/builders/standard/mod.rs +++ b/crates/op-rbuilder/src/builders/standard/mod.rs @@ -5,6 +5,7 @@ use crate::{ use super::BuilderConfig; +mod builder_tx; mod payload; mod service; diff --git a/crates/op-rbuilder/src/builders/standard/service.rs b/crates/op-rbuilder/src/builders/standard/service.rs index 6a1601c..6a3c7ce 100644 --- a/crates/op-rbuilder/src/builders/standard/service.rs +++ b/crates/op-rbuilder/src/builders/standard/service.rs @@ -7,8 +7,8 @@ use reth_provider::CanonStateSubscriptions; use crate::{ builders::{ - standard::payload::StandardOpPayloadBuilder, BuilderConfig, BuilderTransactions, - StandardBuilderTx, + standard::{builder_tx::StandardBuilderTx, payload::StandardOpPayloadBuilder}, + BuilderConfig, BuilderTransactions, }, flashtestations::service::bootstrap_flashtestations, traits::{NodeBounds, PoolBounds}, @@ -72,34 +72,25 @@ where evm_config: OpEvmConfig, ) -> eyre::Result::Payload>> { let signer = self.0.builder_signer; - if self.0.flashtestations_config.flashtestations_enabled { - let flashtestation_builder_tx = match bootstrap_flashtestations( - self.0.flashtestations_config.clone(), - ctx, - signer, - ) - .await + let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled { + match bootstrap_flashtestations::(self.0.flashtestations_config.clone(), ctx) + .await { - Ok(flashtestation_builder_tx) => flashtestation_builder_tx, + Ok(builder_tx) => Some(builder_tx), Err(e) => { - tracing::warn!(error = %e, "Failed to bootstrap flashtestations, falling back to standard builder tx"); - return self.spawn_payload_builder_service( - evm_config, - ctx, - pool, - StandardBuilderTx::new(signer), - ); + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builderb will not include flashtestations txs"); + None } - }; + } + } else { + None + }; - return self.spawn_payload_builder_service( - evm_config, - ctx, - pool, - flashtestation_builder_tx, - ); - } - - self.spawn_payload_builder_service(evm_config, ctx, pool, StandardBuilderTx::new(signer)) + self.spawn_payload_builder_service( + evm_config, + ctx, + pool, + StandardBuilderTx::new(signer, flashtestations_builder_tx), + ) } } diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 0c4e132..2527e8f 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -26,7 +26,6 @@ use tracing::{debug, info, warn}; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, - StandardBuilderTx, }, flashtestations::{ BlockBuilderPolicyError, BlockBuilderProofVerified, BlockData, FlashtestationRegistryError, @@ -45,7 +44,6 @@ pub struct FlashtestationsBuilderTxArgs { pub registry_address: Address, pub builder_policy_address: Address, pub builder_proof_version: u8, - pub builder_signer: Option, pub enable_block_proofs: bool, pub registered: bool, } @@ -70,8 +68,6 @@ pub struct FlashtestationsBuilderTx { registered: Arc, // Whether block proofs are enabled enable_block_proofs: bool, - // fallback builder transaction implementation - fallback_builder_tx: StandardBuilderTx, } #[derive(Debug, Default)] @@ -95,7 +91,6 @@ impl FlashtestationsBuilderTx { builder_proof_version: args.builder_proof_version, registered: Arc::new(AtomicBool::new(args.registered)), enable_block_proofs: args.enable_block_proofs, - fallback_builder_tx: StandardBuilderTx::new(args.builder_signer), } } @@ -150,10 +145,10 @@ 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, + ctx: &OpPayloadBuilderCtx, gas_limit: u64, nonce: u64, ) -> Result, secp256k1::Error> { @@ -208,9 +203,9 @@ impl FlashtestationsBuilderTx { keccak256(&encoded) } - fn simulate_register_tee_service_tx( + fn simulate_register_tee_service_tx( &self, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm< &mut State>, NoOpInspector, @@ -282,10 +277,10 @@ impl FlashtestationsBuilderTx { false } - fn simulate_verify_block_proof_tx( + fn simulate_verify_block_proof_tx( &self, block_content_hash: B256, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm< &mut State>, NoOpInspector, @@ -352,9 +347,9 @@ impl FlashtestationsBuilderTx { false } - fn fund_tee_service_tx( + fn fund_tee_service_tx( &self, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm< &mut State>, NoOpInspector, @@ -397,9 +392,9 @@ impl FlashtestationsBuilderTx { } } - fn register_tee_service_tx( + fn register_tee_service_tx( &self, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm< &mut State>, NoOpInspector, @@ -453,10 +448,10 @@ impl FlashtestationsBuilderTx { } } - fn verify_block_proof_tx( + fn verify_block_proof_tx( &self, transactions: Vec, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm< &mut State>, NoOpInspector, @@ -503,15 +498,14 @@ impl FlashtestationsBuilderTx { } } else { warn!(target: "flashtestations", reason = ?revert_reason, "verify block proof tx failed, falling back to standard builder tx"); - self.fallback_builder_tx - .simulate_builder_tx(ctx, evm.db_mut()) + Ok(None) } } - fn set_registered( + fn set_registered( &self, state_provider: impl StateProvider + Clone, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, ) { let state = StateProviderDatabase::new(state_provider.clone()); let mut simulation_state = State::builder() @@ -535,12 +529,12 @@ impl FlashtestationsBuilderTx { } } -impl BuilderTransactions for FlashtestationsBuilderTx { +impl BuilderTransactions for FlashtestationsBuilderTx { fn simulate_builder_txs( &self, state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, db: &mut State, ) -> Result, BuilderTransactionError> { let state = StateProviderDatabase::new(state_provider.clone()); @@ -574,11 +568,7 @@ impl BuilderTransactions for FlashtestationsBuilderTx { ctx, &mut evm, )?); - } else { - // Fallback to standard builder tx (either when block proofs are disabled or when verify block proof tx fails) - builder_txs.extend(self.fallback_builder_tx.simulate_builder_tx(ctx, db)?); } - Ok(builder_txs) } } diff --git a/crates/op-rbuilder/src/flashtestations/service.rs b/crates/op-rbuilder/src/flashtestations/service.rs index 19d716f..50b4efc 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -16,7 +16,6 @@ use super::{ pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, ctx: &BuilderContext, - builder_signer: Option, ) -> eyre::Result where Node: NodeBounds, @@ -83,7 +82,7 @@ where (None, false) }; - let builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { + let flashtestations_builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { attestation, tee_service_signer, funding_key, @@ -91,7 +90,6 @@ where registry_address, builder_policy_address, builder_proof_version: args.builder_proof_version, - builder_signer, enable_block_proofs: args.enable_block_proofs, registered, }); @@ -115,7 +113,7 @@ where }, ); - Ok(builder_tx) + Ok(flashtestations_builder_tx) } #[cfg(test)] From 045ecc44d0e6c6ddc84a571b6c8c5cf5ecb52db1 Mon Sep 17 00:00:00 2001 From: avalonche Date: Wed, 23 Jul 2025 04:01:50 +1000 Subject: [PATCH 12/14] fix index --- .../src/builders/flashblocks/builder_tx.rs | 16 +++++++++++----- .../src/builders/flashblocks/payload.rs | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs index 45b335e..f095390 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -124,11 +124,17 @@ impl BuilderTransactions for FlashblocksBuilderTx { db: &mut State, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); - let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; - builder_txs.extend(flashblocks_builder_tx.clone()); - if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { - // We only include flashtestations txs in the last flashblock - if ctx.flashblock_index() == ctx.target_flashblock_count() - 1 { + if ctx.is_first_fallback_block() { + let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; + builder_txs.extend(flashblocks_builder_tx.clone()); + } + + if ctx.is_last_flashblock() { + let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; + builder_txs.extend(flashblocks_builder_tx.clone()); + 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::( state_provider.clone(), flashblocks_builder_tx.iter().collect(), diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index 5c74fa7..92f0086 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -65,6 +65,8 @@ pub struct FlashblocksExtraCtx { pub flashblock_index: u64, /// Target flashblock count pub target_flashblock_count: u64, + /// Whether the current flashblock is the first fallback block + pub is_first_fallback_block: bool, } impl OpPayloadBuilderCtx { @@ -78,6 +80,11 @@ impl OpPayloadBuilderCtx { self.extra_ctx.target_flashblock_count } + /// Returns if the flashblock is the first fallback block + pub fn is_first_fallback_block(&self) -> bool { + self.extra_ctx.is_first_fallback_block + } + /// Increments the flashblock index pub fn increment_flashblock_index(&mut self) -> u64 { self.extra_ctx.flashblock_index += 1; @@ -90,6 +97,11 @@ impl OpPayloadBuilderCtx { self.extra_ctx.target_flashblock_count } + /// Sets the first fallback block flag + pub fn set_first_fallback_block(&mut self, is_first_fallback_block: bool) { + self.extra_ctx.is_first_fallback_block = is_first_fallback_block; + } + /// Returns if the flashblock is the last one pub fn is_last_flashblock(&self) -> bool { self.flashblock_index() == self.target_flashblock_count() - 1 @@ -250,6 +262,7 @@ where extra_ctx: FlashblocksExtraCtx { flashblock_index: 0, target_flashblock_count: self.config.flashblocks_per_block(), + is_first_fallback_block: true, }, }; @@ -286,6 +299,8 @@ where self.ws_pub .publish(&fb_payload) .map_err(PayloadBuilderError::other)?; + // We set the first fallback block flag to false after building the first fallback block + ctx.set_first_fallback_block(false); info!( target: "payload_builder", From 1042f8a8749cebcbd5a39c3497539a193a9f7535 Mon Sep 17 00:00:00 2001 From: avalonche Date: Wed, 23 Jul 2025 12:23:53 +1000 Subject: [PATCH 13/14] fix tests --- crates/op-rbuilder/src/builders/builder_tx.rs | 8 ++--- .../src/builders/flashblocks/payload.rs | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index ffd654a..a26af66 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -83,7 +83,7 @@ pub trait BuilderTransactions: Debug { info: &mut ExecutionInfo, builder_ctx: &OpPayloadBuilderCtx, db: &mut State, - ) -> Result<(), BuilderTransactionError> { + ) -> Result, BuilderTransactionError> { { let mut evm = builder_ctx .evm_config @@ -93,7 +93,7 @@ pub trait BuilderTransactions: Debug { let builder_txs = self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; - for builder_tx in builder_txs { + for builder_tx in builder_txs.iter() { if invalid.contains(&builder_tx.signed_tx.signer()) { debug!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); continue; @@ -128,13 +128,13 @@ pub trait BuilderTransactions: Debug { // Append sender and transaction to the respective lists info.executed_senders.push(builder_tx.signed_tx.signer()); info.executed_transactions - .push(builder_tx.signed_tx.into_inner()); + .push(builder_tx.signed_tx.clone().into_inner()); } // Release the db reference by dropping evm drop(evm); - Ok(()) + Ok(builder_txs) } } diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index 92f0086..8ba2c20 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -282,14 +282,11 @@ where ctx.metrics.sequencer_tx_gauge.set(sequencer_tx_time); // If we have payload with txpool we add first builder tx right after deposits - self.builder_tx - .add_builder_txs(&state_provider, &mut info, &ctx, &mut db)?; + let builder_txs = + self.builder_tx + .add_builder_txs(&state_provider, &mut info, &ctx, &mut db)?; // We subtract gas limit and da limit for builder transaction from the whole limit - let builder_txs = self - .builder_tx - .simulate_builder_txs(&state_provider, &mut info, &ctx, &mut db) - .map_err(|err| PayloadBuilderError::Other(Box::new(err)))?; let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); @@ -430,6 +427,21 @@ where ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .with_bundle_prestate(bundle_state.clone()) + .build(); + + let builder_txs = self.builder_tx.simulate_builder_txs( + &state_provider, + &mut info, + &ctx, + &mut db, + )?; + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + let builder_tx_da_size: u64 = + builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size @@ -437,12 +449,6 @@ where *da_limit = da_limit.saturating_sub(builder_tx_da_size); } - let mut db = State::builder() - .with_database(state) - .with_bundle_update() - .with_bundle_prestate(bundle_state.clone()) - .build(); - let best_txs_start_time = Instant::now(); let best_txs = BestPayloadTransactions::new( // We are not using without_updates in here, so arriving transaction could target the current block From 415fdf94657ce5207e3c1b25b7066a5453eb46d2 Mon Sep 17 00:00:00 2001 From: avalonche Date: Tue, 29 Jul 2025 02:13:57 +1000 Subject: [PATCH 14/14] update code to latest flashtestations contract changes --- Cargo.lock | 2 +- .../src/flashtestations/builder_tx.rs | 7 +- crates/op-rbuilder/src/flashtestations/mod.rs | 47 ++++++++---- .../src/flashtestations/service.rs | 75 +++++++------------ .../src/flashtestations/tx_manager.rs | 2 + 5 files changed, 67 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57b2ba2..744f1ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6290,7 +6290,7 @@ dependencies = [ "alloy-consensus", "alloy-contract", "alloy-eips", - "alloy-evm 0.14.0", + "alloy-evm 0.15.0", "alloy-json-rpc", "alloy-network", "alloy-op-evm 0.15.0", diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 2527e8f..570c048 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -38,6 +38,7 @@ use crate::{ pub struct FlashtestationsBuilderTxArgs { pub attestation: Vec, + pub extra_registration_data: Bytes, pub tee_service_signer: Signer, pub funding_key: Signer, pub funding_amount: U256, @@ -52,6 +53,8 @@ pub struct FlashtestationsBuilderTxArgs { pub struct FlashtestationsBuilderTx { // Attestation for the builder attestation: Vec, + // Extra registration data for the builder + extra_registration_data: Bytes, // TEE service generated key tee_service_signer: Signer, // Funding key for the TEE signer @@ -83,6 +86,7 @@ impl FlashtestationsBuilderTx { pub fn new(args: FlashtestationsBuilderTxArgs) -> Self { Self { attestation: args.attestation, + extra_registration_data: args.extra_registration_data, tee_service_signer: args.tee_service_signer, funding_key: args.funding_key, funding_amount: args.funding_amount, @@ -128,6 +132,7 @@ impl FlashtestationsBuilderTx { let quote_bytes = Bytes::from(attestation); let calldata = IFlashtestationRegistry::registerTEEServiceCall { rawQuote: quote_bytes, + extendedRegistrationData: self.extra_registration_data.clone(), } .abi_encode(); @@ -438,7 +443,7 @@ impl FlashtestationsBuilderTx { )) } } else if let Some(FlashtestationRevertReason::FlashtestationRegistry( - FlashtestationRegistryError::TEEServiceAlreadyRegistered(_, _), + FlashtestationRegistryError::TEEServiceAlreadyRegistered(_), )) = revert_reason { Ok((None, true)) diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index f84ef0d..aacd5c4 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -4,7 +4,7 @@ use alloy_sol_types::{sol, SolError}; sol!( #[sol(rpc, abi)] interface IFlashtestationRegistry { - function registerTEEService(bytes calldata rawQuote) external; + function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external; } #[sol(rpc, abi)] @@ -21,18 +21,25 @@ sol!( type WorkloadId is bytes32; - event TEEServiceRegistered( - address teeAddress, WorkloadId workloadId, bytes rawQuote, bytes publicKey, bool alreadyExists - ); + event TEEServiceRegistered(address teeAddress, bytes rawQuote, bool alreadyExists); event BlockBuilderProofVerified( - address caller, WorkloadId workloadId, uint256 blockNumber, uint8 version, bytes32 blockContentHash + address caller, + WorkloadId workloadId, + uint256 blockNumber, + uint8 version, + bytes32 blockContentHash, + string commit_hash ); // FlashtestationRegistry errors error InvalidQuote(bytes output); - error TEEServiceAlreadyRegistered(address teeAddress, WorkloadId workloadId); + error TEEServiceAlreadyRegistered(address teeAddress); + error InvalidRegistrationDataHash(bytes32 expected, bytes32 received); error SenderMustMatchTEEAddress(address sender, address teeAddress); + error ByteSizeExceeded(uint256 size); + + // QuoteParser errors error InvalidTEEType(bytes4 teeType); error InvalidTEEVersion(uint16 version); error InvalidReportDataLength(uint256 length); @@ -61,8 +68,12 @@ pub enum FlashtestationRevertReason { pub enum FlashtestationRegistryError { #[error("invalid quote: {0}")] InvalidQuote(Bytes), - #[error("tee address {0} already registered with workload id {1}")] - TEEServiceAlreadyRegistered(Address, B256), + #[error("tee address {0} already registered")] + TEEServiceAlreadyRegistered(Address), + #[error("invalid registration data hash: expected {0}, received {1}")] + InvalidRegistrationDataHash(B256, B256), + #[error("byte size exceeded: {0}")] + ByteSizeExceeded(U256), #[error("sender address {0} must match quote tee address {1}")] SenderMustMatchTEEAddress(Address, Address), #[error("invalid tee type: {0}")] @@ -95,14 +106,20 @@ impl From for FlashtestationRegistryError { return FlashtestationRegistryError::InvalidQuote(output); } - if let Ok(TEEServiceAlreadyRegistered { - teeAddress, - workloadId, - }) = TEEServiceAlreadyRegistered::abi_decode(&value) + if let Ok(TEEServiceAlreadyRegistered { teeAddress }) = + TEEServiceAlreadyRegistered::abi_decode(&value) { - return FlashtestationRegistryError::TEEServiceAlreadyRegistered( - teeAddress, workloadId, - ); + return FlashtestationRegistryError::TEEServiceAlreadyRegistered(teeAddress); + } + + if let Ok(InvalidRegistrationDataHash { expected, received }) = + InvalidRegistrationDataHash::abi_decode(&value) + { + return FlashtestationRegistryError::InvalidRegistrationDataHash(expected, received); + } + + if let Ok(ByteSizeExceeded { size }) = ByteSizeExceeded::abi_decode(&value) { + return FlashtestationRegistryError::ByteSizeExceeded(size); } if let Ok(SenderMustMatchTEEAddress { sender, teeAddress }) = diff --git a/crates/op-rbuilder/src/flashtestations/service.rs b/crates/op-rbuilder/src/flashtestations/service.rs index 50b4efc..1509a1e 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -1,3 +1,4 @@ +use alloy_primitives::{keccak256, Bytes}; use reth_node_builder::BuilderContext; use tracing::{info, warn}; @@ -51,10 +52,26 @@ where quote_provider: args.quote_provider, }); - // Prepare report data with public key (64 bytes, no 0x04 prefix) + // Prepare report data: + // - TEE address (20 bytes) at reportData[0:20] + // - Extended registration data hash (32 bytes) at reportData[20:52] + // - Total: 52 bytes, padded to 64 bytes with zeros + + // Extract TEE address as 20 bytes + let tee_address_bytes: [u8; 20] = tee_service_signer.address.into(); + + // Calculate keccak256 hash of empty bytes (32 bytes) + let ext_data = Bytes::from(b""); + let ext_data_hash = keccak256(&ext_data); + + // Create 64-byte report data array let mut report_data = [0u8; 64]; - let pubkey_uncompressed = tee_service_signer.pubkey.serialize_uncompressed(); - report_data.copy_from_slice(&pubkey_uncompressed[1..65]); // Skip 0x04 prefix + + // Copy TEE address (20 bytes) to positions 0-19 + report_data[0..20].copy_from_slice(&tee_address_bytes); + + // Copy extended registration data hash (32 bytes) to positions 20-51 + report_data[20..52].copy_from_slice(ext_data_hash.as_ref()); // Request TDX attestation info!(target: "flashtestations", "requesting TDX attestation"); @@ -69,7 +86,11 @@ where ); // Submit report onchain by registering the key of the tee service match tx_manager - .fund_and_register_tee_service(attestation.clone(), args.funding_amount) + .fund_and_register_tee_service( + attestation.clone(), + ext_data.clone(), + args.funding_amount, + ) .await { Ok(_) => (Some(tx_manager), true), @@ -84,6 +105,7 @@ where let flashtestations_builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { attestation, + extra_registration_data: ext_data, tee_service_signer, funding_key, funding_amount: args.funding_amount, @@ -115,48 +137,3 @@ where Ok(flashtestations_builder_tx) } - -#[cfg(test)] -mod tests { - use alloy_primitives::Address; - use secp256k1::{PublicKey, Secp256k1, SecretKey}; - use sha3::{Digest, Keccak256}; - - use crate::tx_signer::public_key_to_address; - - /// Derives Ethereum address from report data using the same logic as the Solidity contract - fn derive_ethereum_address_from_report_data(pubkey_64_bytes: &[u8]) -> Address { - // This exactly matches the Solidity implementation: - // address(uint160(uint256(keccak256(reportData)))) - - // Step 1: keccak256(reportData) - let hash = Keccak256::digest(pubkey_64_bytes); - - // Step 2: Take last 20 bytes (same as uint256 -> uint160 conversion) - let mut address_bytes = [0u8; 20]; - address_bytes.copy_from_slice(&hash[12..32]); - - Address::from(address_bytes) - } - - #[test] - fn test_address_derivation_matches() { - // Test that our manual derivation is correct - let secp = Secp256k1::new(); - let private_key = SecretKey::from_slice(&[0x01; 32]).unwrap(); - let public_key = PublicKey::from_secret_key(&secp, &private_key); - - // Get address using our implementation - let our_address = public_key_to_address(&public_key); - - // Get address using our manual derivation (matching Solidity) - let pubkey_bytes = public_key.serialize_uncompressed(); - let report_data = &pubkey_bytes[1..65]; // Skip 0x04 prefix - let manual_address = derive_ethereum_address_from_report_data(report_data); - - assert_eq!( - our_address, manual_address, - "Address derivation should match" - ); - } -} diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs index ed4aca0..1dae98a 100644 --- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -102,6 +102,7 @@ impl TxManager { pub async fn fund_and_register_tee_service( &self, attestation: Vec, + extra_registration_data: Bytes, funding_amount: U256, ) -> Result<(), TxManagerError> { info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address); @@ -133,6 +134,7 @@ impl TxManager { let calldata = IFlashtestationRegistry::registerTEEServiceCall { rawQuote: quote_bytes, + extendedRegistrationData: extra_registration_data, } .abi_encode(); let tx = TransactionRequest {