diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 0cd8d5e8..15ecc0d8 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -89,6 +89,15 @@ impl From for PayloadBuilderError { } } +impl BuilderTransactionError { + pub fn other(error: E) -> Self + where + E: core::error::Error + Send + Sync + 'static, + { + BuilderTransactionError::Other(Box::new(error)) + } +} + pub trait BuilderTransactions: Debug { fn simulate_builder_txs( &self, diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index 71f10c83..a18facf5 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 fn parent(&self) -> &SealedHeader { &self.config.parent_header } + /// 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 attributes. pub(super) const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } /// Returns the withdrawals if shanghai is active. - pub(super) fn withdrawals(&self) -> Option<&Withdrawals> { + pub 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 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 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 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 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 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. @@ -132,8 +142,8 @@ 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 { + /// After holocene this extracts the extradata from the payload + pub 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 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 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 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 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 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 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 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 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 7588dd4e..b1b16906 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, }; @@ -172,23 +172,21 @@ impl FlashblocksNumberBuilderTx { ) { Ok(gas_used) } else { - Err(BuilderTransactionError::Other(Box::new( + Err(BuilderTransactionError::other( FlashblockNumberError::LogMismatch( IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH, ), - ))) + )) } } - 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( + ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::other( + IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output) + .map(FlashblockNumberError::Revert) + .unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)), + )), + ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other( FlashblockNumberError::Halt(reason), - ))), + )), } } diff --git a/crates/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/op-rbuilder/src/builders/standard/builder_tx.rs index c19f627d..75a159ad 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/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs new file mode 100644 index 00000000..ee5e793c --- /dev/null +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -0,0 +1,603 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::Encodable2718; +use alloy_evm::Database; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256, map::foldhash::HashMap}; +use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, SolValue}; +use 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::OnceLock; +use tracing::{debug, info}; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, + get_balance, get_nonce, + }, + flashtestations::{ + BlockData, FlashtestationRevertReason, + IBlockBuilderPolicy::{self, BlockBuilderProofVerified}, + IFlashtestationRegistry::{self, 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: OnceLock, + // 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: OnceLock::new(), + 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() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } 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, 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(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: &[Log], address: Address) -> bool { + for log in logs { + if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { + if let Ok(decoded) = TEEServiceRegistered::decode_log(log) { + if decoded.teeAddress == address { + return true; + } + }; + } + } + false + } + + fn simulate_verify_block_proof_tx( + &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() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } 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, 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(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: &[Log]) -> 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() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } 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, + is_top_of_block: false, + })) + } 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 { + if !self.check_tee_address_registered_log(&logs, self.tee_service_signer.address) { + Err(BuilderTransactionError::other( + FlashtestationRevertReason::LogMismatch( + self.registry_address, + TEEServiceRegistered::SIGNATURE_HASH, + ), + )) + } 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, + is_top_of_block: false, + }), + false, + )) + } + } else if let Some(FlashtestationRevertReason::FlashtestationRegistry( + IFlashtestationRegistry::IFlashtestationRegistryErrors::TEEServiceAlreadyRegistered(_), + )) = revert_reason + { + Ok((None, true)) + } else { + Err(BuilderTransactionError::other(revert_reason.unwrap_or( + FlashtestationRevertReason::Unknown( + "unknown revert".into(), + Error::Other("unknown revert".into()), + ), + ))) + } + } + + 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 { + if !self.check_verify_block_proof_log(&logs) { + Err(BuilderTransactionError::other( + FlashtestationRevertReason::LogMismatch( + self.builder_policy_address, + BlockBuilderProofVerified::SIGNATURE_HASH, + ), + )) + } 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, + is_top_of_block: false, + })) + } + } else { + Err(BuilderTransactionError::other(revert_reason.unwrap_or( + FlashtestationRevertReason::Unknown( + "unknown revert".into(), + Error::Other("unknown revert".into()), + ), + ))) + } + } + + fn set_registered( + &self, + state_provider: impl StateProvider + Clone, + ctx: &OpPayloadBuilderCtx, + ) -> Result<(), BuilderTransactionError> { + 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)) => { + if registered { + let _ = self.registered.set(registered); + } + Ok(()) + } + Err(e) => Err(BuilderTransactionError::other(e)), + } + } +} + +impl BuilderTransactions for FlashtestationsBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + // set registered simulating against the committed state + if !self.registered.get().unwrap_or(&false) { + self.set_registered(state_provider.clone(), ctx)?; + } + + 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.get().unwrap_or(&false) { + info!(target: "flashtestations", "tee service not registered yet, attempting to register"); + builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); + let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?; + builder_txs.extend(register_tx); + } + + if self.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) + } +} diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index 6f9c2743..a2a50611 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -1,3 +1,4 @@ +use alloy_primitives::{Address, B256}; use alloy_sol_types::{Error, sol}; use op_revm::OpHaltReason; @@ -87,6 +88,8 @@ pub enum FlashtestationRevertReason { FlashtestationRegistry(IFlashtestationRegistry::IFlashtestationRegistryErrors), #[error("block builder policy error: {0:?}")] BlockBuilderPolicy(IBlockBuilderPolicy::IBlockBuilderPolicyErrors), + #[error("contract {0:?} may be invalid, mismatch in log emitted: expected {1:?}")] + LogMismatch(Address, B256), #[error("unknown revert: {0} err: {1}")] Unknown(String, Error), #[error("halt: {0:?}")] @@ -95,5 +98,6 @@ pub enum FlashtestationRevertReason { 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 237aef7d..5b9162cd 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -1,10 +1,6 @@ use alloy_primitives::{B256, Bytes, keccak256}; use reth_node_builder::BuilderContext; -use reth_provider::StateProvider; -use reth_revm::State; -use revm::Database; use std::{ - fmt::Debug, fs::{self, OpenOptions}, io::Write, os::unix::fs::OpenOptionsExt, @@ -13,10 +9,7 @@ use std::{ 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_key_from_seed, generate_signer}, }; @@ -51,7 +44,7 @@ where let registry_address = args .registry_address .expect("registry address required when flashtestations enabled"); - let _builder_policy_address = args + let builder_policy_address = args .builder_policy_address .expect("builder policy address required when flashtestations enabled"); @@ -85,8 +78,7 @@ where info!(target: "flashtestations", "requesting TDX attestation"); let attestation = attestation_provider.get_attestation(report_data).await?; - #[allow(dead_code)] - let (tx_manager, _registered) = if let Some(rpc_url) = args.rpc_url { + let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url { let tx_manager = TxManager::new( tee_service_signer, funding_key, @@ -112,7 +104,18 @@ where (None, false) }; - let flashtestations_builder_tx = FlashtestationsBuilderTx {}; + 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, + }); ctx.task_executor() .spawn_critical_with_graceful_shutdown_signal( @@ -193,18 +196,3 @@ fn load_tee_key(path: &Path) -> Option { .inspect_err(|e| warn!("failed to create signer from key: {:?}", e)) .ok() } - -#[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, - ) -> Result, BuilderTransactionError> { - Ok(vec![]) - } -}