From 5acd8473174e51b3c7e7a56a77ec287fbb7b2bf2 Mon Sep 17 00:00:00 2001 From: avalonche Date: Fri, 19 Sep 2025 04:14:01 +1000 Subject: [PATCH 1/8] Add flashtestations builder transaction --- Cargo.lock | 6 +- crates/op-rbuilder/src/builders/context.rs | 46 +- .../src/builders/flashblocks/builder_tx.rs | 2 +- .../src/builders/standard/builder_tx.rs | 2 +- .../src/flashtestations/attestation.rs | 1 + .../src/flashtestations/builder_tx.rs | 593 ++++++++++++++++++ crates/op-rbuilder/src/flashtestations/mod.rs | 207 ++++++ .../src/flashtestations/service.rs | 263 ++++---- .../src/flashtestations/tx_manager.rs | 174 ++--- 9 files changed, 999 insertions(+), 295 deletions(-) create mode 100644 crates/op-rbuilder/src/flashtestations/builder_tx.rs diff --git a/Cargo.lock b/Cargo.lock index 686a988d6..dce94b2b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6308,9 +6308,9 @@ dependencies = [ "futures-util", "hex", "http", - "jsonrpsee 0.26.0", - "jsonrpsee-core 0.26.0", - "jsonrpsee-types 0.26.0", + "jsonrpsee", + "jsonrpsee-core", + "jsonrpsee-types", "k256", "macros", "metrics", diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index 9604ced71..63f1256d1 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -2,7 +2,7 @@ use alloy_consensus::{Eip658Value, Transaction, conditional::BlockConditionalAtt use alloy_eips::Typed2718; use alloy_evm::Database; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{Bytes, U256}; +use alloy_primitives::{BlockHash, Bytes, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; use op_alloy_consensus::OpDepositReceipt; @@ -75,41 +75,51 @@ pub struct OpPayloadBuilderCtx { impl OpPayloadBuilderCtx { /// Returns the parent block the payload will be build on. - pub(super) fn parent(&self) -> &SealedHeader { + pub(crate) fn parent(&self) -> &SealedHeader { &self.config.parent_header } + /// Returns the parent hash + pub(crate) fn parent_hash(&self) -> BlockHash { + self.parent().hash() + } + + /// Returns the timestamp + pub(crate) fn timestamp(&self) -> u64 { + self.attributes().timestamp() + } + /// Returns the builder attributes. - pub(super) const fn attributes(&self) -> &OpPayloadBuilderAttributes { + pub(crate) const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } /// Returns the withdrawals if shanghai is active. - pub(super) fn withdrawals(&self) -> Option<&Withdrawals> { + pub(crate) fn withdrawals(&self) -> Option<&Withdrawals> { self.chain_spec .is_shanghai_active_at_timestamp(self.attributes().timestamp()) .then(|| &self.attributes().payload_attributes.withdrawals) } /// Returns the block gas limit to target. - pub(super) fn block_gas_limit(&self) -> u64 { + pub(crate) fn block_gas_limit(&self) -> u64 { self.attributes() .gas_limit .unwrap_or(self.evm_env.block_env.gas_limit) } /// Returns the block number for the block. - pub(super) fn block_number(&self) -> u64 { + pub(crate) fn block_number(&self) -> u64 { as_u64_saturated!(self.evm_env.block_env.number) } /// Returns the current base fee - pub(super) fn base_fee(&self) -> u64 { + pub(crate) fn base_fee(&self) -> u64 { self.evm_env.block_env.basefee } /// Returns the current blob gas price. - pub(super) fn get_blob_gasprice(&self) -> Option { + pub(crate) fn get_blob_gasprice(&self) -> Option { self.evm_env .block_env .blob_gasprice() @@ -119,7 +129,7 @@ impl OpPayloadBuilderCtx { /// Returns the blob fields for the header. /// /// This will always return `Some(0)` after ecotone. - pub(super) fn blob_fields(&self) -> (Option, Option) { + pub(crate) fn blob_fields(&self) -> (Option, Option) { // OP doesn't support blobs/EIP-4844. // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions // Need [Some] or [None] based on hardfork to match block hash. @@ -133,7 +143,7 @@ impl OpPayloadBuilderCtx { /// Returns the extra data for the block. /// /// After holocene this extracts the extradata from the paylpad - pub(super) fn extra_data(&self) -> Result { + pub(crate) fn extra_data(&self) -> Result { if self.is_holocene_active() { self.attributes() .get_holocene_extra_data( @@ -148,47 +158,47 @@ impl OpPayloadBuilderCtx { } /// Returns the current fee settings for transactions from the mempool - pub(super) fn best_transaction_attributes(&self) -> BestTransactionsAttributes { + pub(crate) fn best_transaction_attributes(&self) -> BestTransactionsAttributes { BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) } /// Returns the unique id for this payload job. - pub(super) fn payload_id(&self) -> PayloadId { + pub(crate) fn payload_id(&self) -> PayloadId { self.attributes().payload_id() } /// Returns true if regolith is active for the payload. - pub(super) fn is_regolith_active(&self) -> bool { + pub(crate) fn is_regolith_active(&self) -> bool { self.chain_spec .is_regolith_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if ecotone is active for the payload. - pub(super) fn is_ecotone_active(&self) -> bool { + pub(crate) fn is_ecotone_active(&self) -> bool { self.chain_spec .is_ecotone_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if canyon is active for the payload. - pub(super) fn is_canyon_active(&self) -> bool { + pub(crate) fn is_canyon_active(&self) -> bool { self.chain_spec .is_canyon_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if holocene is active for the payload. - pub(super) fn is_holocene_active(&self) -> bool { + pub(crate) fn is_holocene_active(&self) -> bool { self.chain_spec .is_holocene_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if isthmus is active for the payload. - pub(super) fn is_isthmus_active(&self) -> bool { + pub(crate) fn is_isthmus_active(&self) -> bool { self.chain_spec .is_isthmus_active_at_timestamp(self.attributes().timestamp()) } /// Returns the chain id - pub(super) fn chain_id(&self) -> u64 { + pub(crate) fn chain_id(&self) -> u64 { self.chain_spec.chain_id() } } diff --git a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs index 3445bb2e5..b7e68c6e7 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -25,7 +25,7 @@ use crate::{ context::OpPayloadBuilderCtx, flashblocks::payload::FlashblocksExtraCtx, }, - flashtestations::service::FlashtestationsBuilderTx, + flashtestations::builder_tx::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, tx_signer::Signer, }; diff --git a/crates/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/op-rbuilder/src/builders/standard/builder_tx.rs index c4d154f63..08488dd52 100644 --- a/crates/op-rbuilder/src/builders/standard/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/standard/builder_tx.rs @@ -8,7 +8,7 @@ use crate::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, }, - flashtestations::service::FlashtestationsBuilderTx, + flashtestations::builder_tx::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, tx_signer::Signer, }; diff --git a/crates/op-rbuilder/src/flashtestations/attestation.rs b/crates/op-rbuilder/src/flashtestations/attestation.rs index 534dfcb15..4ad73cca1 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 { 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 000000000..9d43c67f0 --- /dev/null +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -0,0 +1,593 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::Encodable2718; +use alloy_evm::Database; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256, map::foldhash::HashMap}; +use alloy_sol_types::{SolCall, SolEvent, SolValue}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_evm::{ConfigureEvm, Evm, EvmError, precompiles::PrecompilesMap}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::{Log, Recovered}; +use reth_provider::StateProvider; +use reth_revm::{State, database::StateProviderDatabase}; +use revm::{ + DatabaseCommit, + context::result::{ExecutionResult, ResultAndState}, + inspector::NoOpInspector, + state::Account, +}; +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; +use tracing::{debug, info, warn}; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, + }, + flashtestations::{ + BlockBuilderPolicyError, BlockBuilderProofVerified, BlockData, FlashtestationRegistryError, + FlashtestationRevertReason, IBlockBuilderPolicy, IFlashtestationRegistry, + TEEServiceRegistered, + }, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +pub struct FlashtestationsBuilderTxArgs { + pub attestation: Vec, + pub extra_registration_data: Bytes, + 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 enable_block_proofs: bool, + pub registered: bool, +} + +#[derive(Debug, Clone)] +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 + 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, + // Whether block proofs are enabled + enable_block_proofs: bool, +} + +#[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 { + 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, + 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)), + enable_block_proofs: args.enable_block_proofs, + } + } + + fn signed_funding_tx( + &self, + to: Address, + from: Signer, + amount: U256, + base_fee: u64, + chain_id: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + // 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> { + let quote_bytes = Bytes::from(attestation); + let calldata = IFlashtestationRegistry::registerTEEServiceCall { + rawQuote: quote_bytes, + extendedRegistrationData: self.extra_registration_data.clone(), + } + .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, + block_content_hash: B256, + ctx: &OpPayloadBuilderCtx, + gas_limit: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + 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, + 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)) + /// https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#block-building-process + 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, + >, + ) -> 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(), + ctx.base_fee(), + ctx.chain_id(), + nonce, + )?; + 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 { + 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); + Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::FlashtestationRegistry( + revert_reason, + )), + logs: vec![], + }) + } + ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::Halt(reason)), + logs: vec![], + }), + } + } + + fn check_tee_address_registered_log(&self, logs: Vec, address: Address) -> bool { + for log in logs { + if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { + if let Ok(decoded) = TEEServiceRegistered::decode_log(&log) { + if decoded.teeAddress == address { + return true; + } + }; + } + } + false + } + + fn simulate_verify_block_proof_tx( + &self, + block_content_hash: B256, + ctx: &OpPayloadBuilderCtx, + 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 { + 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); + Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::BlockBuilderPolicy( + revert_reason, + )), + logs: vec![], + }) + } + ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::Halt(reason)), + logs: vec![], + }), + } + } + + fn check_verify_block_proof_log(&self, logs: Vec) -> bool { + for log in logs { + if log.topics().first() == Some(&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", 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, + 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", 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 { + 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", 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, + signed_tx: verify_block_proof_tx, + })) + } + } else { + warn!(target: "flashtestations", reason = ?revert_reason, "verify block proof tx failed, falling back to standard builder tx"); + Ok(None) + } + } + + 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 + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> 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()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + }); + + let mut builder_txs = Vec::::new(); + + if !self.registered.load(Ordering::Relaxed) { + 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)?; + builder_txs.extend(register_tx); + } + + if self.enable_block_proofs { + // add verify block proof tx + builder_txs.extend(self.verify_block_proof_tx( + info.executed_transactions.clone(), + ctx, + &mut evm, + )?); + } + 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 a5b16e734..2a2b76d87 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -1,4 +1,211 @@ +use alloy_primitives::{Address, B256, Bytes, FixedBytes, U256}; +use alloy_sol_types::{SolError, sol}; +use op_revm::OpHaltReason; + +sol!( + #[sol(rpc, abi)] + interface IFlashtestationRegistry { + function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external; + } + + #[sol(rpc, abi)] + interface IBlockBuilderPolicy { + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + } + + struct BlockData { + bytes32 parentHash; + uint256 blockNumber; + uint256 timestamp; + bytes32[] transactionHashes; + } + + type WorkloadId is bytes32; + + event TEEServiceRegistered(address teeAddress, bytes rawQuote, bool alreadyExists); + + event BlockBuilderProofVerified( + address caller, + WorkloadId workloadId, + uint256 blockNumber, + uint8 version, + bytes32 blockContentHash, + string commit_hash + ); + + // FlashtestationRegistry errors + error InvalidQuote(bytes output); + 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); + 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), + #[error("halt: {0:?}")] + Halt(OpHaltReason), +} + +#[derive(Debug, thiserror::Error)] +pub enum FlashtestationRegistryError { + #[error("invalid quote: {0}")] + InvalidQuote(Bytes), + #[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}")] + 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 }) = + TEEServiceAlreadyRegistered::abi_decode(&value) + { + 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 }) = + 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; 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 40f126521..ac948921c 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -1,113 +1,19 @@ -use std::sync::Arc; - -use alloy_primitives::U256; +use alloy_primitives::{Bytes, keccak256}; use reth_node_builder::BuilderContext; -use reth_provider::StateProvider; -use reth_revm::State; -use revm::Database; -use std::fmt::Debug; use tracing::{info, warn}; use crate::{ - builders::{ - BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, - }, - primitives::reth::ExecutionInfo, + flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, traits::NodeBounds, - tx_signer::{Signer, generate_ethereum_keypair}, + tx_signer::{Signer, generate_ethereum_keypair, generate_key_from_seed}, }; use super::{ args::FlashtestationsArgs, - attestation::{AttestationConfig, AttestationProvider, get_attestation_provider}, + attestation::{AttestationConfig, get_attestation_provider}, 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, - quote_provider: args.quote_provider, - })); - - let tx_manager = TxManager::new( - tee_service_signer, - args.funding_key - .expect("funding key required when flashtestations enabled"), - args.rpc_url - .expect("external rpc url required when flashtestations enabled"), - 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 - } -} - -#[derive(Debug, Clone)] -pub struct FlashtestationsBuilderTx {} - -impl BuilderTransactions for FlashtestationsBuilderTx { - fn simulate_builder_txs( - &self, - _state_provider: impl StateProvider + Clone, - _info: &mut ExecutionInfo, - _ctx: &OpPayloadBuilderCtx, - _db: &mut State, - _top_of_block: bool, - ) -> Result, BuilderTransactionError> { - Ok(vec![]) - } -} - pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, ctx: &BuilderContext, @@ -115,74 +21,119 @@ pub async fn bootstrap_flashtestations( 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) = if args.debug { + info!("Flashtestations debug mode enabled, generating debug key"); + // Generate deterministic key for debugging purposes + 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, + 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, + quote_provider: args.quote_provider, + }); + + // 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]; + + // 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"); + 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(), + ext_data.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 flashtestations_builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { + attestation, + extra_registration_data: ext_data, + tee_service_signer, + funding_key, + funding_amount: args.funding_amount, + registry_address, + builder_policy_address, + builder_proof_version: args.builder_proof_version, + enable_block_proofs: args.enable_block_proofs, + 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(FlashtestationsBuilderTx {}) -} - -#[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" - ); - } + Ok(flashtestations_builder_tx) } diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs index 9eabdd28a..1dae98a34 100644 --- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,41 +1,40 @@ -use alloy_consensus::TxEip1559; -use alloy_eips::Encodable2718; +use alloy_json_rpc::RpcError; use alloy_network::ReceiptResponse; -use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, keccak256}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, 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 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::{SolCall, SolValue, sol}; 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; - } +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), +} - struct BlockData { - bytes32 parentHash; - uint256 blockNumber; - uint256 timestamp; - bytes32[] transactionHashes; +impl From for TxManagerError { + fn from(e: TransportError) -> Self { + TxManagerError::RpcError(e) } -); +} #[derive(Debug, Clone)] pub struct TxManager { @@ -43,8 +42,6 @@ pub struct TxManager { funding_signer: Signer, rpc_url: String, registry_address: Address, - builder_policy_address: Address, - builder_proof_version: u8, } impl TxManager { @@ -53,21 +50,23 @@ 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, } } - 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() @@ -91,36 +90,41 @@ 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, + extra_registration_data: Bytes, 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()) @@ -128,62 +132,29 @@ impl TxManager { info!(target: "flashtestations", "submitting quote to registry at {}", self.registry_address); - // TODO: add retries let calldata = IFlashtestationRegistry::registerTEEServiceCall { rawQuote: quote_bytes, + extendedRegistrationData: extra_registration_data, } .abi_encode(); 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() }; 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 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<()> { + 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() @@ -202,7 +173,7 @@ impl TxManager { self.funding_signer.address, balance.saturating_sub(gas_cost), ) - .await? + .await?; } Ok(()) } @@ -210,7 +181,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(); @@ -226,42 +197,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)), } } - - /// 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 f865586fbee38822ff568cb2511be3ee2ce10dd2 Mon Sep 17 00:00:00 2001 From: avalonche Date: Fri, 19 Sep 2025 10:53:10 +1000 Subject: [PATCH 2/8] comments --- crates/op-rbuilder/src/flashtestations/builder_tx.rs | 8 ++++---- crates/op-rbuilder/src/flashtestations/tx_manager.rs | 8 +------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 9d43c67f0..c6ad3924d 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -245,10 +245,10 @@ impl FlashtestationsBuilderTx { revert_reason: None, logs, }), - ExecutionResult::Revert { output, .. } => { + ExecutionResult::Revert { output, gas_used} => { let revert_reason = FlashtestationRegistryError::from(output); Ok(TxSimulateResult { - gas_used: 0, + gas_used, success: false, state_changes: state, revert_reason: Some(FlashtestationRevertReason::FlashtestationRegistry( @@ -317,10 +317,10 @@ impl FlashtestationsBuilderTx { revert_reason: None, logs, }), - ExecutionResult::Revert { output, .. } => { + ExecutionResult::Revert { output, gas_used} => { let revert_reason = BlockBuilderPolicyError::from(output); Ok(TxSimulateResult { - gas_used: 0, + gas_used, success: false, state_changes: state, revert_reason: Some(FlashtestationRevertReason::BlockBuilderPolicy( diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs index 1dae98a34..d2c0cc968 100644 --- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -19,7 +19,7 @@ use crate::{flashtestations::IFlashtestationRegistry, tx_signer::Signer}; #[derive(Debug, thiserror::Error)] pub enum TxManagerError { #[error("rpc error: {0}")] - RpcError(TransportError), + RpcError(#[from] TransportError), #[error("tx reverted: {0}")] TxReverted(TxHash), #[error("error checking tx confirmation: {0}")] @@ -30,12 +30,6 @@ pub enum TxManagerError { 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, From 54f01cb93f7206e04ad943f27f606f8836447892 Mon Sep 17 00:00:00 2001 From: avalonche Date: Sat, 20 Sep 2025 07:52:56 +1000 Subject: [PATCH 3/8] clean up errors --- crates/op-rbuilder/src/builders/builder_tx.rs | 49 ++-- .../src/builders/flashblocks/builder_tx.rs | 119 +++------ .../src/builders/standard/builder_tx.rs | 2 - .../src/builders/standard/payload.rs | 3 +- .../src/flashtestations/builder_tx.rs | 39 +-- crates/op-rbuilder/src/flashtestations/mod.rs | 242 +++++------------- 6 files changed, 144 insertions(+), 310 deletions(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index b79200d4e..51bcf83d6 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -30,7 +30,10 @@ use crate::{ pub struct BuilderTransactionCtx { pub gas_used: u64, pub da_size: u64, - pub signed_tx: Option>, + pub signed_tx: Recovered, + // whether the transaction should be a top of block or + // bottom of block transaction + pub is_top_of_block: bool, } /// Possible error variants during construction of builder txs. @@ -80,7 +83,6 @@ pub trait BuilderTransactions: Debug { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError>; fn add_builder_txs( @@ -98,30 +100,26 @@ pub trait BuilderTransactions: Debug { let mut invalid: HashSet
= HashSet::new(); - let builder_txs = self.simulate_builder_txs( - state_provider, - info, - builder_ctx, - evm.db_mut(), - top_of_block, - )?; + let builder_txs = + self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; for builder_tx in builder_txs.iter() { - let signed_tx = match builder_tx.signed_tx.clone() { - Some(tx) => tx, - None => continue, - }; - if invalid.contains(&signed_tx.signer()) { - warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); + if builder_tx.is_top_of_block != top_of_block { + // don't commit tx if the buidler tx is not being added in the intended + // position in the block + continue; + } + if invalid.contains(&builder_tx.signed_tx.signer()) { + warn!(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(&signed_tx) + .transact(&builder_tx.signed_tx) .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; if !result.is_success() { - warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder tx reverted"); - invalid.insert(signed_tx.signer()); + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted"); + invalid.insert(builder_tx.signed_tx.signer()); continue; } @@ -130,7 +128,7 @@ pub trait BuilderTransactions: Debug { info.cumulative_gas_used += gas_used; let ctx = ReceiptBuilderCtx { - tx: signed_tx.inner(), + tx: builder_tx.signed_tx.inner(), evm: &evm, result, state: &state, @@ -142,9 +140,9 @@ pub trait BuilderTransactions: Debug { evm.db_mut().commit(state); // Append sender and transaction to the respective lists - info.executed_senders.push(signed_tx.signer()); + info.executed_senders.push(builder_tx.signed_tx.signer()); info.executed_transactions - .push(signed_tx.clone().into_inner()); + .push(builder_tx.signed_tx.clone().into_inner()); } // Release the db reference by dropping evm @@ -172,12 +170,8 @@ pub trait BuilderTransactions: Debug { .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); for builder_tx in builder_txs { - let signed_tx = match builder_tx.signed_tx.clone() { - Some(tx) => tx, - None => continue, - }; let ResultAndState { state, .. } = evm - .transact(&signed_tx) + .transact(&builder_tx.signed_tx) .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; evm.db_mut().commit(state); @@ -214,7 +208,8 @@ impl BuilderTxBase { Ok(Some(BuilderTransactionCtx { gas_used, da_size, - signed_tx: Some(signed_tx), + signed_tx, + is_top_of_block: false, })) } None => Ok(None), diff --git a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs index b7e68c6e7..2db279c8c 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -2,8 +2,8 @@ use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::{Database, Evm}; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, Bytes, TxKind, U256}; -use alloy_sol_types::{SolCall, SolError, sol}; +use alloy_primitives::{Address, TxKind}; +use alloy_sol_types::{Error, SolCall, SolInterface, sol}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; use op_revm::OpHaltReason; @@ -33,62 +33,32 @@ use crate::{ sol!( // From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.sol #[sol(rpc, abi)] + #[derive(Debug)] interface IFlashblockNumber { function incrementFlashblockNumber() external; - } - // @notice Emitted when flashblock index is incremented - // @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block) - event FlashblockIncremented(uint256 newFlashblockIndex); + // @notice Emitted when flashblock index is incremented + // @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block) + event FlashblockIncremented(uint256 newFlashblockIndex); - /// ----------------------------------------------------------------------- - /// Errors - /// ----------------------------------------------------------------------- - error NonBuilderAddress(address addr); - error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber); + /// ----------------------------------------------------------------------- + /// Errors + /// ----------------------------------------------------------------------- + error NonBuilderAddress(address addr); + error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber); + } ); #[derive(Debug, thiserror::Error)] pub(super) enum FlashblockNumberError { - #[error("non builder address: {0}")] - NonBuilderAddress(Address), - #[error("mismatched flashblock number: expected {0}, actual {1}")] - MismatchedFlashblockNumber(U256, U256), - #[error("unknown revert: {0}")] - Unknown(String), + #[error("flashblocks number contract error: {0:?}")] + Revert(IFlashblockNumber::IFlashblockNumberErrors), + #[error("unknown revert: {0} err: {1}")] + Unknown(String, Error), #[error("halt: {0:?}")] Halt(OpHaltReason), } -impl From for FlashblockNumberError { - fn from(value: Bytes) -> Self { - // Empty revert - if value.is_empty() { - return FlashblockNumberError::Unknown( - "Transaction reverted without reason".to_string(), - ); - } - - // Try to decode each custom error type - if let Ok(NonBuilderAddress { addr }) = NonBuilderAddress::abi_decode(&value) { - return FlashblockNumberError::NonBuilderAddress(addr); - } - - if let Ok(MismatchedFlashblockNumber { - expectedFlashblockNumber, - actualFlashblockNumber, - }) = MismatchedFlashblockNumber::abi_decode(&value) - { - return FlashblockNumberError::MismatchedFlashblockNumber( - expectedFlashblockNumber, - actualFlashblockNumber, - ); - } - - FlashblockNumberError::Unknown(hex::encode(value)) - } -} - // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksBuilderTx { @@ -116,7 +86,6 @@ impl BuilderTransactions for FlashblocksBuilderTx { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); @@ -126,24 +95,14 @@ impl BuilderTransactions for FlashblocksBuilderTx { } if ctx.is_last_flashblock() { - let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; - if let Some(tx) = flashblocks_builder_tx.clone() { - if top_of_block { - // don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx - builder_txs.push(BuilderTransactionCtx { - gas_used: tx.gas_used, - da_size: tx.da_size, - signed_tx: None, - }); - } else { - builder_txs.push(tx); - } - } + let base_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; + builder_txs.extend(base_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(), + base_tx.iter().collect(), ctx, db, )?; @@ -152,7 +111,6 @@ impl BuilderTransactions for FlashblocksBuilderTx { info, ctx, &mut simulation_state, - top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } @@ -206,9 +164,13 @@ impl FlashblocksNumberBuilderTx { match result { ExecutionResult::Success { gas_used, .. } => Ok(gas_used), - ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::Other( - Box::new(FlashblockNumberError::from(output)), - )), + ExecutionResult::Revert { output, .. } => { + Err(BuilderTransactionError::Other(Box::new( + IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output) + .map(FlashblockNumberError::Revert) + .unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)), + ))) + } ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::Other(Box::new( FlashblockNumberError::Halt(reason), ))), @@ -245,7 +207,6 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); let state = StateProviderDatabase::new(state_provider.clone()); @@ -274,7 +235,7 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { { Ok(gas_used) => { // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - let flashblocks_tx = self.signed_flashblock_number_tx( + let signed_tx = self.signed_flashblock_number_tx( ctx, gas_used * 64 / 63, nonce, @@ -282,33 +243,18 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { )?; let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - flashblocks_tx.encoded_2718().as_slice(), + signed_tx.encoded_2718().as_slice(), ); Some(BuilderTransactionCtx { gas_used, da_size, - signed_tx: if top_of_block { - Some(flashblocks_tx) - } else { - None - }, // number tx at top of flashblock + signed_tx, + is_top_of_block: true, // number tx at top of flashblock }) } Err(e) => { warn!(target: "builder_tx", error = ?e, "Flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); - let builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; - if let Some(tx) = &builder_tx - && top_of_block - { - // don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx - Some(BuilderTransactionCtx { - gas_used: tx.gas_used, - da_size: tx.da_size, - signed_tx: None, - }) - } else { - builder_tx - } + self.base_builder_tx.simulate_builder_tx(ctx, db)? } }; @@ -331,7 +277,6 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { info, ctx, &mut simulation_state, - top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } diff --git a/crates/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/op-rbuilder/src/builders/standard/builder_tx.rs index 08488dd52..75a159ad7 100644 --- a/crates/op-rbuilder/src/builders/standard/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/standard/builder_tx.rs @@ -40,7 +40,6 @@ impl BuilderTransactions for StandardBuilderTx { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; @@ -57,7 +56,6 @@ impl BuilderTransactions for StandardBuilderTx { info, ctx, &mut simulation_state, - top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index c510a8ba9..0c0e5de07 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -347,8 +347,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(&state_provider, &mut info, ctx, db, true)?; + let builder_txs = builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, true)?; 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 { diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index c6ad3924d..a4aeed549 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -3,7 +3,7 @@ use alloy_eips::Encodable2718; use alloy_evm::Database; use alloy_op_evm::OpEvm; use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256, map::foldhash::HashMap}; -use alloy_sol_types::{SolCall, SolEvent, SolValue}; +use alloy_sol_types::{SolCall, SolEvent, SolInterface, SolValue}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; use reth_evm::{ConfigureEvm, Evm, EvmError, precompiles::PrecompilesMap}; @@ -28,9 +28,9 @@ use crate::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, }, flashtestations::{ - BlockBuilderPolicyError, BlockBuilderProofVerified, BlockData, FlashtestationRegistryError, - FlashtestationRevertReason, IBlockBuilderPolicy, IFlashtestationRegistry, - TEEServiceRegistered, + BlockData, FlashtestationRevertReason, + IBlockBuilderPolicy::{self, BlockBuilderProofVerified}, + IFlashtestationRegistry::{self, TEEServiceRegistered}, }, primitives::reth::ExecutionInfo, tx_signer::Signer, @@ -245,15 +245,18 @@ impl FlashtestationsBuilderTx { revert_reason: None, logs, }), - ExecutionResult::Revert { output, gas_used} => { - let revert_reason = FlashtestationRegistryError::from(output); + ExecutionResult::Revert { output, gas_used } => { + let revert_reason = + IFlashtestationRegistry::IFlashtestationRegistryErrors::abi_decode(&output) + .map(FlashtestationRevertReason::FlashtestationRegistry) + .unwrap_or_else(|e| { + FlashtestationRevertReason::Unknown(hex::encode(output), e) + }); Ok(TxSimulateResult { gas_used, success: false, state_changes: state, - revert_reason: Some(FlashtestationRevertReason::FlashtestationRegistry( - revert_reason, - )), + revert_reason: Some(revert_reason), logs: vec![], }) } @@ -317,15 +320,18 @@ impl FlashtestationsBuilderTx { revert_reason: None, logs, }), - ExecutionResult::Revert { output, gas_used} => { - let revert_reason = BlockBuilderPolicyError::from(output); + ExecutionResult::Revert { output, gas_used } => { + let revert_reason = + IBlockBuilderPolicy::IBlockBuilderPolicyErrors::abi_decode(&output) + .map(FlashtestationRevertReason::BlockBuilderPolicy) + .unwrap_or_else(|e| { + FlashtestationRevertReason::Unknown(hex::encode(output), e) + }); Ok(TxSimulateResult { gas_used, success: false, state_changes: state, - revert_reason: Some(FlashtestationRevertReason::BlockBuilderPolicy( - revert_reason, - )), + revert_reason: Some(revert_reason), logs: vec![], }) } @@ -387,6 +393,7 @@ impl FlashtestationsBuilderTx { gas_used: 21000, da_size, signed_tx: funding_tx, + is_top_of_block: true, })) } else { Ok(None) @@ -434,12 +441,13 @@ impl FlashtestationsBuilderTx { gas_used, da_size, signed_tx: register_tx, + is_top_of_block: true, }), false, )) } } else if let Some(FlashtestationRevertReason::FlashtestationRegistry( - FlashtestationRegistryError::TEEServiceAlreadyRegistered(_), + IFlashtestationRegistry::IFlashtestationRegistryErrors::TEEServiceAlreadyRegistered(_), )) = revert_reason { Ok((None, true)) @@ -495,6 +503,7 @@ impl FlashtestationsBuilderTx { gas_used, da_size, signed_tx: verify_block_proof_tx, + is_top_of_block: false, })) } } else { diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index 2a2b76d87..a5070be09 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -1,16 +1,73 @@ -use alloy_primitives::{Address, B256, Bytes, FixedBytes, U256}; -use alloy_sol_types::{SolError, sol}; +use alloy_sol_types::{Error, sol}; use op_revm::OpHaltReason; sol!( #[sol(rpc, abi)] + #[derive(Debug)] interface IFlashtestationRegistry { function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external; + + /// @notice Emitted when a TEE service is registered + /// @param teeAddress The address of the TEE service + /// @param rawQuote The raw quote from the TEE device + /// @param alreadyExists Whether the TEE service is already registered + event TEEServiceRegistered(address indexed teeAddress, bytes rawQuote, bool alreadyExists); + + /// @notice Emitted when the attestation contract is the 0x0 address + error InvalidAttestationContract(); + /// @notice Emitted when the signature is expired because the deadline has passed + error ExpiredSignature(uint256 deadline); + /// @notice Emitted when the quote is invalid according to the Automata DCAP Attestation contract + error InvalidQuote(bytes output); + /// @notice Emitted when the report data length is too short + error InvalidReportDataLength(uint256 length); + /// @notice Emitted when the registration data hash does not match the expected hash + error InvalidRegistrationDataHash(bytes32 expected, bytes32 received); + /// @notice Emitted when the byte size is exceeded + error ByteSizeExceeded(uint256 size); + /// @notice Emitted when the TEE service is already registered when registering + error TEEServiceAlreadyRegistered(address teeAddress); + /// @notice Emitted when the signer doesn't match the TEE address + error SignerMustMatchTEEAddress(address signer, address teeAddress); + /// @notice Emitted when the TEE service is not registered + error TEEServiceNotRegistered(address teeAddress); + /// @notice Emitted when the TEE service is already invalid when trying to invalidate a TEE registration + error TEEServiceAlreadyInvalid(address teeAddress); + /// @notice Emitted when the TEE service is still valid when trying to invalidate a TEE registration + error TEEIsStillValid(address teeAddress); + /// @notice Emitted when the nonce is invalid when verifying a signature + error InvalidNonce(uint256 expected, uint256 provided); } #[sol(rpc, abi)] + #[derive(Debug)] interface IBlockBuilderPolicy { function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + + /// @notice Emitted when a block builder proof is successfully verified + /// @param caller The address that called the verification function (TEE address) + /// @param workloadId The workload identifier of the TEE + /// @param version The flashtestation protocol version used + /// @param blockContentHash The hash of the block content + /// @param commitHash The git commit hash associated with the workload + event BlockBuilderProofVerified( + address caller, bytes32 workloadId, uint8 version, bytes32 blockContentHash, string commitHash + ); + + /// @notice Emitted when the registry is the 0x0 address + error InvalidRegistry(); + /// @notice Emitted when a workload to be added is already in the policy + error WorkloadAlreadyInPolicy(); + /// @notice Emitted when a workload to be removed is not in the policy + error WorkloadNotInPolicy(); + /// @notice Emitted when the address is not in the approvedWorkloads mapping + error UnauthorizedBlockBuilder(address caller); + /// @notice Emitted when the nonce is invalid + error InvalidNonce(uint256 expected, uint256 provided); + /// @notice Emitted when the commit hash is empty + error EmptyCommitHash(); + /// @notice Emitted when the source locators array is empty + error EmptySourceLocators(); } struct BlockData { @@ -21,189 +78,20 @@ sol!( } type WorkloadId is bytes32; - - event TEEServiceRegistered(address teeAddress, bytes rawQuote, bool alreadyExists); - - event BlockBuilderProofVerified( - address caller, - WorkloadId workloadId, - uint256 blockNumber, - uint8 version, - bytes32 blockContentHash, - string commit_hash - ); - - // FlashtestationRegistry errors - error InvalidQuote(bytes output); - 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); - 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), + #[error("flashtestation registry error: {0:?}")] + FlashtestationRegistry(IFlashtestationRegistry::IFlashtestationRegistryErrors), + #[error("block builder policy error: {0:?}")] + BlockBuilderPolicy(IBlockBuilderPolicy::IBlockBuilderPolicyErrors), + #[error("unknown revert: {0} err: {1}")] + Unknown(String, Error), #[error("halt: {0:?}")] Halt(OpHaltReason), } -#[derive(Debug, thiserror::Error)] -pub enum FlashtestationRegistryError { - #[error("invalid quote: {0}")] - InvalidQuote(Bytes), - #[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}")] - 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 }) = - TEEServiceAlreadyRegistered::abi_decode(&value) - { - 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 }) = - 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; From e8bbe3e267e9499cca57654845214b79dee4e245 Mon Sep 17 00:00:00 2001 From: avalonche Date: Sat, 20 Sep 2025 16:14:58 +1000 Subject: [PATCH 4/8] clean up cancellation logic --- .../src/builders/flashblocks/payload.rs | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index ae11a6da6..44fa2bfb4 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -52,7 +52,7 @@ use std::{ }; use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info, metadata::Level, span, warn}; +use tracing::{Span, debug, error, info, metadata::Level, span, warn}; type NextBestFlashblocksTxs = BestFlashblocksTxs< ::Transaction, @@ -89,6 +89,8 @@ pub struct FlashblocksExtraCtx { da_per_batch: Option, /// Whether to calculate the state root for each flashblock calculate_state_root: bool, + /// Cancel token for the flashblock + flashblock_cancel: CancellationToken, } impl OpPayloadBuilderCtx { @@ -277,6 +279,7 @@ where .next_evm_env(&config.parent_header, &block_env_attributes) .map_err(PayloadBuilderError::other)?; + let mut fb_cancel = block_cancel.child_token(); let mut ctx = OpPayloadBuilderCtx:: { evm_config: self.evm_config.clone(), chain_spec: self.client.chain_spec(), @@ -296,6 +299,7 @@ where gas_per_batch: 0, da_per_batch: None, calculate_state_root, + flashblock_cancel: fb_cancel.clone(), }, max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), @@ -327,7 +331,7 @@ where &mut info, &ctx, &mut state, - true, + false, ) { Ok(builder_txs) => builder_txs, Err(e) => { @@ -419,7 +423,7 @@ 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!( + warn!( "Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included." ); } @@ -442,11 +446,9 @@ where )); let interval = self.config.specific.interval; let (tx, mut rx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); - let mut fb_cancel = block_cancel.child_token(); - ctx.cancel = fb_cancel.clone(); tokio::spawn({ - let block_cancel = block_cancel.clone(); + let block_cancel = ctx.cancel.clone(); async move { let mut timer = tokio::time::interval_at( @@ -483,7 +485,7 @@ where // Process flashblocks in a blocking loop loop { let fb_span = if span.is_none() { - tracing::Span::none() + Span::none() } else { span!( parent: &span, @@ -500,9 +502,7 @@ where &mut state, &state_provider, &mut best_txs, - &block_cancel, &best_payload, - &fb_span, ) { Ok(()) => {} Err(err) => { @@ -519,7 +519,7 @@ where tokio::select! { Some(fb_cancel) = rx.recv() => { - ctx.cancel = fb_cancel; + ctx.extra_ctx.flashblock_cancel = fb_cancel; }, _ = block_cancel.cancelled() => { self.record_flashblocks_metrics( @@ -535,7 +535,6 @@ where } } - #[allow(clippy::too_many_arguments)] fn build_next_flashblock< DB: Database + std::fmt::Debug + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, @@ -546,9 +545,7 @@ where state: &mut State, state_provider: impl reth::providers::StateProvider + Clone, best_txs: &mut NextBestFlashblocksTxs, - block_cancel: &CancellationToken, best_payload: &BlockCell, - span: &tracing::Span, ) -> Result<(), PayloadBuilderError> { // fallback block is index 0, so we need to increment here ctx.increment_flashblock_index(); @@ -636,14 +633,7 @@ where // We got block cancelled, we won't need anything from the block at this point // Caution: this assume that block cancel token only cancelled when new FCU is received - if block_cancel.is_cancelled() { - self.record_flashblocks_metrics( - ctx, - info, - ctx.target_flashblock_count(), - span, - "Payload building complete, channel closed or job cancelled", - ); + if ctx.cancel.is_cancelled() { return Ok(()); } @@ -696,14 +686,7 @@ where // If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload // To ensure that we will return same blocks as rollup-boost (to leverage caches) - if block_cancel.is_cancelled() { - self.record_flashblocks_metrics( - ctx, - info, - ctx.target_flashblock_count(), - span, - "Payload building complete, channel closed or job cancelled", - ); + if ctx.cancel.is_cancelled() { return Ok(()); } let flashblock_byte_size = self From cd2f145e0b765182090cd6d040e45e5352593c82 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 23 Sep 2025 15:06:57 +1000 Subject: [PATCH 5/8] Update crates/op-rbuilder/src/flashtestations/builder_tx.rs Co-authored-by: Solar Mithril --- crates/op-rbuilder/src/flashtestations/builder_tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index a4aeed549..c10fd994d 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -483,7 +483,7 @@ impl FlashtestationsBuilderTx { } = 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 { + if !self.check_verify_block_proof_log(logs) { warn!(target: "flashtestations", "transaction did not emit BlockBuilderProofVerified log, BlockBuilderPolicy contract address may be incorrect"); Ok(None) } else { From 563f00555529c9f97c6a1d7f45f30a30c34bf33c Mon Sep 17 00:00:00 2001 From: avalonche Date: Wed, 24 Sep 2025 05:12:42 +1000 Subject: [PATCH 6/8] comments --- crates/op-rbuilder/src/builders/builder_tx.rs | 13 ++++- crates/op-rbuilder/src/builders/mod.rs | 4 +- .../src/flashtestations/builder_tx.rs | 51 ++++++------------- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 51bcf83d6..00a98be92 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -2,7 +2,7 @@ use alloy_consensus::TxEip1559; use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; use alloy_primitives::{ - Address, TxKind, + Address, TxKind, U256, map::foldhash::{HashSet, HashSetExt}, }; use core::fmt::Debug; @@ -271,7 +271,7 @@ impl BuilderTxBase { } } -pub(super) fn get_nonce( +pub fn get_nonce( db: &mut State, address: Address, ) -> Result { @@ -279,3 +279,12 @@ pub(super) fn get_nonce( .map(|acc| acc.account_info().unwrap_or_default().nonce) .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) } + +pub fn get_balance( + db: &mut State, + address: Address, +) -> Result { + 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/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs index 8dcde8eb5..5bb05b45e 100644 --- a/crates/op-rbuilder/src/builders/mod.rs +++ b/crates/op-rbuilder/src/builders/mod.rs @@ -21,7 +21,9 @@ mod flashblocks; mod generator; mod standard; -pub use builder_tx::{BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions}; +pub use builder_tx::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, get_balance, get_nonce, +}; pub use context::OpPayloadBuilderCtx; pub use flashblocks::FlashblocksBuilder; pub use standard::StandardBuilder; diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index c10fd994d..eeb48dda2 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -17,15 +17,13 @@ use revm::{ inspector::NoOpInspector, state::Account, }; -use std::sync::{ - Arc, - atomic::{AtomicBool, Ordering}, -}; +use std::sync::OnceLock; use tracing::{debug, info, warn}; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, + get_balance, get_nonce, }, flashtestations::{ BlockData, FlashtestationRevertReason, @@ -68,7 +66,7 @@ pub struct FlashtestationsBuilderTx { // Builder proof version builder_proof_version: u8, // Whether the workload and address has been registered - registered: Arc, + registered: OnceLock, // Whether block proofs are enabled enable_block_proofs: bool, } @@ -93,7 +91,7 @@ impl FlashtestationsBuilderTx { 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)), + registered: OnceLock::new(), enable_block_proofs: args.enable_block_proofs, } } @@ -270,10 +268,10 @@ impl FlashtestationsBuilderTx { } } - fn check_tee_address_registered_log(&self, logs: Vec, address: Address) -> bool { + fn check_tee_address_registered_log(&self, logs: &[Log], address: Address) -> bool { for log in logs { if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { - if let Ok(decoded) = TEEServiceRegistered::decode_log(&log) { + if let Ok(decoded) = TEEServiceRegistered::decode_log(log) { if decoded.teeAddress == address { return true; } @@ -345,7 +343,7 @@ impl FlashtestationsBuilderTx { } } - fn check_verify_block_proof_log(&self, logs: Vec) -> bool { + fn check_verify_block_proof_log(&self, logs: &[Log]) -> bool { for log in logs { if log.topics().first() == Some(&BlockBuilderProofVerified::SIGNATURE_HASH) { return true; @@ -417,9 +415,7 @@ impl FlashtestationsBuilderTx { 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 { + if !self.check_tee_address_registered_log(&logs, self.tee_service_signer.address) { warn!(target: "flashtestations", "transaction did not emit TEEServiceRegistered log, FlashtestationRegistry contract address may be incorrect"); Ok((None, false)) } else { @@ -482,8 +478,7 @@ impl FlashtestationsBuilderTx { .. } = self.simulate_verify_block_proof_tx(block_content_hash, ctx, evm)?; if success { - let has_log = self.check_verify_block_proof_log(logs); - if !self.check_verify_block_proof_log(logs) { + if !self.check_verify_block_proof_log(&logs) { warn!(target: "flashtestations", "transaction did not emit BlockBuilderProofVerified log, BlockBuilderPolicy contract address may be incorrect"); Ok(None) } else { @@ -530,10 +525,12 @@ impl FlashtestationsBuilderTx { }); match self.register_tee_service_tx(ctx, &mut evm) { Ok((_, registered)) => { - self.registered.store(registered, Ordering::Relaxed); + if let Err(e) = self.registered.set(registered) { + warn!(target: "flashtestations", error = ?e, "error setting builder registered"); + } } Err(e) => { - debug!(target: "flashtestations", error = ?e, "simulation error when checking if registered"); + warn!(target: "flashtestations", error = ?e, "simulation error when checking if registered"); } } } @@ -563,12 +560,12 @@ impl BuilderTransactions for Flashtestation let mut builder_txs = Vec::::new(); - if !self.registered.load(Ordering::Relaxed) { + if !self.registered.get().unwrap_or(&false) { 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)?; builder_txs.extend(register_tx); + self.set_registered(state_provider, ctx); } if self.enable_block_proofs { @@ -582,21 +579,3 @@ impl BuilderTransactions for Flashtestation 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)) -} From 9a621f67b42c08fcc7ccf21ffb57ee25d56bd0b5 Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 26 Sep 2025 01:50:14 +1000 Subject: [PATCH 7/8] Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: noot <36753753+noot@users.noreply.github.com> --- crates/op-rbuilder/src/builders/builder_tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 00a98be92..556428110 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -271,7 +271,7 @@ impl BuilderTxBase { } } -pub fn get_nonce( +pub(crate) fn get_nonce( db: &mut State, address: Address, ) -> Result { From bf944d7e721c8aeeaa67f050e5c6d255421283ab Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 26 Sep 2025 01:50:24 +1000 Subject: [PATCH 8/8] Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: noot <36753753+noot@users.noreply.github.com> --- crates/op-rbuilder/src/builders/builder_tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 556428110..f3f3241f8 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -280,7 +280,7 @@ pub(crate) fn get_nonce( .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) } -pub fn get_balance( +pub(crate) fn get_balance( db: &mut State, address: Address, ) -> Result {