From c6bee147571e17bae0ccbdd2c8a668ca88ef3903 Mon Sep 17 00:00:00 2001 From: avalonche Date: Sat, 4 Oct 2025 07:25:23 +1000 Subject: [PATCH 1/4] Add permit flashtestations tx calls from builder --- .../op-rbuilder/src/flashtestations/args.rs | 8 + .../src/flashtestations/builder_tx.rs | 202 ++++++++++++++++++ crates/op-rbuilder/src/flashtestations/mod.rs | 5 + .../op-rbuilder/src/tests/flashtestations.rs | 111 +++++++++- 4 files changed, 325 insertions(+), 1 deletion(-) diff --git a/crates/op-rbuilder/src/flashtestations/args.rs b/crates/op-rbuilder/src/flashtestations/args.rs index 7856bb01..44d1b126 100644 --- a/crates/op-rbuilder/src/flashtestations/args.rs +++ b/crates/op-rbuilder/src/flashtestations/args.rs @@ -83,6 +83,14 @@ pub struct FlashtestationsArgs { default_value = "1" )] pub builder_proof_version: u8, + + /// Use permit for the flashtestation builder tx + #[arg( + long = "flashtestations.use-permit", + env = "FLASHTESTATIONS_USE_PERMIT", + default_value = "false" + )] + pub flashtestations_use_permit: bool, } impl Default for FlashtestationsArgs { diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 005dcab5..1ddde05e 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -136,6 +136,7 @@ where .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); evm.modify_cfg(|cfg| { cfg.disable_balance_check = true; + cfg.disable_nonce_check = true; }); let calldata = IFlashtestationRegistry::getRegistrationStatusCall { teeAddress: self.tee_service_signer.address, @@ -357,6 +358,207 @@ where )) } } + + fn get_permit_nonce( + &self, + contract_address: Address, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let calldata = IERC20Permit::noncesCall { + owner: self.tee_service_signer.address, + } + .abi_encode(); + let SimulationSuccessResult { output, .. } = + self.simulate_call(contract_address, calldata.into(), None, ctx, evm)?; + U256::abi_decode(&output) + .map_err(|_| BuilderTransactionError::InvalidContract(contract_address)) + } + + fn registration_permit_signature( + &self, + permit_nonce: U256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let struct_hash_calldata = IFlashtestationRegistry::computeStructHashCall { + rawQuote: self.attestation.clone().into(), + extendedRegistrationData: self.extra_registration_data.clone(), + nonce: permit_nonce, + deadline: U256::from(ctx.timestamp()), + } + .abi_encode(); + let SimulationSuccessResult { output, .. } = self.simulate_call( + self.registry_address, + struct_hash_calldata.into(), + None, + ctx, + evm, + )?; + let struct_hash = B256::abi_decode(&output) + .map_err(|_| BuilderTransactionError::InvalidContract(self.registry_address))?; + let typed_data_hash_calldata = IFlashtestationRegistry::hashTypedDataV4Call { + structHash: struct_hash, + } + .abi_encode(); + let SimulationSuccessResult { output, .. } = self.simulate_call( + self.registry_address, + typed_data_hash_calldata.into(), + None, + ctx, + evm, + )?; + let typed_data_hash = B256::abi_decode(&output) + .map_err(|_| BuilderTransactionError::InvalidContract(self.registry_address))?; + let signature = self.tee_service_signer.sign_message(typed_data_hash)?; + Ok(signature) + } + + fn signed_registration_permit_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let permit_nonce = self.get_permit_nonce(self.registry_address, ctx, evm)?; + let signature = self.registration_permit_signature(permit_nonce, ctx, evm)?; + let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall { + rawQuote: self.attestation.clone().into(), + extendedRegistrationData: self.extra_registration_data.clone(), + nonce: permit_nonce, + deadline: U256::from(ctx.timestamp()), + signature: signature.as_bytes().into(), + } + .abi_encode(); + let SimulationSuccessResult { gas_used, .. } = self.simulate_call( + self.registry_address, + calldata.clone().into(), + Some(TEEServiceRegistered::SIGNATURE_HASH), + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.registry_address, + self.builder_key, + gas_used, + calldata.into(), + ctx, + evm.db_mut(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) + } + + fn block_proof_permit_signature( + &self, + permit_nonce: U256, + block_content_hash: B256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let struct_hash_calldata = IBlockBuilderPolicy::computeStructHashCall { + version: self.builder_proof_version, + blockContentHash: block_content_hash, + nonce: permit_nonce, + } + .abi_encode(); + let SimulationSuccessResult { output, .. } = self.simulate_call( + self.builder_policy_address, + struct_hash_calldata.into(), + None, + ctx, + evm, + )?; + let struct_hash = B256::abi_decode(&output) + .map_err(|_| BuilderTransactionError::InvalidContract(self.builder_policy_address))?; + let typed_data_hash_calldata = IBlockBuilderPolicy::getHashedTypeDataV4Call { + structHash: struct_hash, + } + .abi_encode(); + let SimulationSuccessResult { output, .. } = self.simulate_call( + self.builder_policy_address, + typed_data_hash_calldata.into(), + None, + ctx, + evm, + )?; + let typed_data_hash = B256::abi_decode(&output) + .map_err(|_| BuilderTransactionError::InvalidContract(self.builder_policy_address))?; + let signature = self.tee_service_signer.sign_message(typed_data_hash)?; + Ok(signature) + } + + fn signed_block_proof_permit_tx( + &self, + transactions: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let permit_nonce = self.get_permit_nonce(self.builder_policy_address, ctx, evm)?; + let block_content_hash = Self::compute_block_content_hash( + transactions.clone(), + ctx.parent_hash(), + ctx.block_number(), + ctx.timestamp(), + ); + let signature = + self.block_proof_permit_signature(permit_nonce, block_content_hash, ctx, evm)?; + let calldata = IBlockBuilderPolicy::permitVerifyBlockBuilderProofCall { + blockContentHash: block_content_hash, + nonce: U256::from(permit_nonce), + version: self.builder_proof_version, + eip712Sig: signature.as_bytes().into(), + } + .abi_encode(); + let SimulationSuccessResult { gas_used, .. } = self.simulate_call( + self.builder_policy_address, + calldata.clone().into(), + Some(BlockBuilderProofVerified::SIGNATURE_HASH), + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.builder_policy_address, + self.builder_key, + gas_used, + calldata.into(), + ctx, + evm.db_mut(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) + } } impl BuilderTransactions diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index f80a2e0c..65c5b1c0 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -115,6 +115,11 @@ sol!( } type WorkloadId is bytes32; + + + interface IERC20Permit { + function nonces(address owner) external view returns (uint256); + } ); pub mod args; diff --git a/crates/op-rbuilder/src/tests/flashtestations.rs b/crates/op-rbuilder/src/tests/flashtestations.rs index c0cc0250..fb424772 100644 --- a/crates/op-rbuilder/src/tests/flashtestations.rs +++ b/crates/op-rbuilder/src/tests/flashtestations.rs @@ -97,7 +97,6 @@ async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> // check that only the regular builder tx is in the block let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; let txs = block.transactions.into_transactions_vec(); - if_flashblocks!( assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx // Check builder tx @@ -126,6 +125,116 @@ async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> Ok(()) } +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_invalid_quote(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, false, true).await?; + // verify not registered + let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let result = contract + .getRegistrationStatus(TEE_DEBUG_ADDRESS) + .call() + .await?; + assert!( + !result.isValid, + "The tee key is registered for invalid quote" + ); + // check that only regular builder tx is in the block + let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; + let txs = block.transactions.into_transactions_vec(); + + if_flashblocks!( + assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + ); + if_standard!( + assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof + ); + let last_txs = &txs[txs.len() - 2..]; + // Check user transaction + assert_eq!( + last_txs[0].inner.tx_hash(), + tx_hash, + "tx hash for user transaction should match" + ); + // Check builder tx + assert_eq!( + last_txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, false).await?; + // check that only the regular builder tx is in the block + let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; + let txs = block.transactions.into_transactions_vec(); + + if_flashblocks!( + assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + ); + if_standard!( + assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof + ); + let last_txs = &txs[txs.len() - 2..]; + // Check user transaction + assert_eq!( + last_txs[0].inner.tx_hash(), + tx_hash, + "tx hash for user transaction should match" + ); + // Check builder tx + assert_eq!( + last_txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + Ok(()) +} + #[rb_test(flashblocks, args = OpRbuilderArgs { chain_block_time: 1000, enable_revert_protection: true, From 89a08894711b6b6946401df3f0517092b6d1b89a Mon Sep 17 00:00:00 2001 From: avalonche Date: Sat, 4 Oct 2025 17:04:13 +1000 Subject: [PATCH 2/4] move simumlation calls to builder tx --- crates/op-rbuilder/src/builders/builder_tx.rs | 80 +++++++++++- .../src/flashtestations/builder_tx.rs | 122 ++++++++++++++---- crates/op-rbuilder/src/flashtestations/mod.rs | 5 - 3 files changed, 174 insertions(+), 33 deletions(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 11194a38..698634a9 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -1,4 +1,4 @@ -use alloy_consensus::TxEip1559; +use alloy_consensus::{Transaction, TxEip1559}; use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; use alloy_op_evm::OpEvm; @@ -387,6 +387,84 @@ pub trait BuilderTransactions, + calldata: Bytes, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + let nonce = get_nonce(db, from.address)?; + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer + gas_limit: gas_used + .map(|gas| gas * 64 / 63) + .unwrap_or(ctx.block_gas_limit()), + max_fee_per_gas: ctx.base_fee().into(), + to: TxKind::Call(to), + input: calldata, + ..Default::default() + }); + Ok(from.sign_tx(tx)?) + } + + fn simulate_call( + &self, + signed_tx: Recovered, + expected_topic: Option, + revert_handler: impl FnOnce(Bytes) -> BuilderTransactionError, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let ResultAndState { result, state } = match evm.transact(&signed_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; + + match result { + ExecutionResult::Success { + logs, + gas_used, + output, + .. + } => { + if let Some(topic) = expected_topic + && !logs.iter().any(|log| log.topics().first() == Some(&topic)) + { + return Err(BuilderTransactionError::InvalidContract( + signed_tx.to().unwrap_or_default(), + InvalidContractDataError::InvalidLogs(topic), + )); + } + Ok(SimulationSuccessResult { + gas_used, + output: output.into_data(), + state_changes: state, + }) + } + ExecutionResult::Revert { output, .. } => Err(revert_handler(output)), + ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other( + BuilderTransactionError::TransactionHalted(reason), + )), + } + } } #[derive(Debug, Clone)] diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 1ddde05e..62a10184 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -374,9 +374,13 @@ where } .abi_encode(); let SimulationSuccessResult { output, .. } = - self.simulate_call(contract_address, calldata.into(), None, ctx, evm)?; - U256::abi_decode(&output) - .map_err(|_| BuilderTransactionError::InvalidContract(contract_address)) + self.simulate_flashtestation_call(contract_address, calldata, None, ctx, evm)?; + U256::abi_decode(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + contract_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + }) } fn registration_permit_signature( @@ -396,28 +400,36 @@ where deadline: U256::from(ctx.timestamp()), } .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_call( + let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( self.registry_address, - struct_hash_calldata.into(), + struct_hash_calldata, None, ctx, evm, )?; - let struct_hash = B256::abi_decode(&output) - .map_err(|_| BuilderTransactionError::InvalidContract(self.registry_address))?; + let struct_hash = B256::abi_decode(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + self.registry_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; let typed_data_hash_calldata = IFlashtestationRegistry::hashTypedDataV4Call { structHash: struct_hash, } .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_call( + let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( self.registry_address, - typed_data_hash_calldata.into(), + typed_data_hash_calldata, None, ctx, evm, )?; - let typed_data_hash = B256::abi_decode(&output) - .map_err(|_| BuilderTransactionError::InvalidContract(self.registry_address))?; + let typed_data_hash = B256::abi_decode(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + self.registry_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; let signature = self.tee_service_signer.sign_message(typed_data_hash)?; Ok(signature) } @@ -441,17 +453,17 @@ where signature: signature.as_bytes().into(), } .abi_encode(); - let SimulationSuccessResult { gas_used, .. } = self.simulate_call( + let SimulationSuccessResult { gas_used, .. } = self.simulate_flashtestation_call( self.registry_address, - calldata.clone().into(), + calldata.clone(), Some(TEEServiceRegistered::SIGNATURE_HASH), ctx, evm, )?; let signed_tx = self.sign_tx( self.registry_address, - self.builder_key, - gas_used, + self.builder_signer, + Some(gas_used), calldata.into(), ctx, evm.db_mut(), @@ -483,28 +495,36 @@ where nonce: permit_nonce, } .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_call( + let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( self.builder_policy_address, - struct_hash_calldata.into(), + struct_hash_calldata, None, ctx, evm, )?; - let struct_hash = B256::abi_decode(&output) - .map_err(|_| BuilderTransactionError::InvalidContract(self.builder_policy_address))?; + let struct_hash = B256::abi_decode(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + self.builder_policy_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; let typed_data_hash_calldata = IBlockBuilderPolicy::getHashedTypeDataV4Call { structHash: struct_hash, } .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_call( + let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( self.builder_policy_address, - typed_data_hash_calldata.into(), + typed_data_hash_calldata, None, ctx, evm, )?; - let typed_data_hash = B256::abi_decode(&output) - .map_err(|_| BuilderTransactionError::InvalidContract(self.builder_policy_address))?; + let typed_data_hash = B256::abi_decode(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + self.builder_policy_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; let signature = self.tee_service_signer.sign_message(typed_data_hash)?; Ok(signature) } @@ -535,17 +555,17 @@ where eip712Sig: signature.as_bytes().into(), } .abi_encode(); - let SimulationSuccessResult { gas_used, .. } = self.simulate_call( + let SimulationSuccessResult { gas_used, .. } = self.simulate_flashtestation_call( self.builder_policy_address, - calldata.clone().into(), + calldata.clone(), Some(BlockBuilderProofVerified::SIGNATURE_HASH), ctx, evm, )?; let signed_tx = self.sign_tx( self.builder_policy_address, - self.builder_key, - gas_used, + self.builder_signer, + Some(gas_used), calldata.into(), ctx, evm.db_mut(), @@ -559,6 +579,54 @@ where is_top_of_block: false, }) } + + fn simulate_flashtestation_call( + &self, + contract_address: Address, + calldata: Vec, + expected_topic: Option, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let signed_tx = self.sign_tx( + contract_address, + self.builder_signer, + None, + calldata.into(), + ctx, + evm.db_mut(), + )?; + let revert_handler = if contract_address == self.registry_address { + Self::handle_registry_reverts + } else { + Self::handle_block_builder_policy_reverts + }; + self.simulate_call(signed_tx, expected_topic, revert_handler, evm) + } + + fn handle_registry_reverts(revert_output: Bytes) -> BuilderTransactionError { + let revert_reason = + IFlashtestationRegistry::IFlashtestationRegistryErrors::abi_decode(&revert_output) + .map(FlashtestationRevertReason::FlashtestationRegistry) + .unwrap_or_else(|_| { + FlashtestationRevertReason::Unknown(hex::encode(revert_output)) + }); + BuilderTransactionError::TransactionReverted(Box::new(revert_reason)) + } + + fn handle_block_builder_policy_reverts(revert_output: Bytes) -> BuilderTransactionError { + let revert_reason = + IBlockBuilderPolicy::IBlockBuilderPolicyErrors::abi_decode(&revert_output) + .map(FlashtestationRevertReason::BlockBuilderPolicy) + .unwrap_or_else(|_| { + FlashtestationRevertReason::Unknown(hex::encode(revert_output)) + }); + BuilderTransactionError::TransactionReverted(Box::new(revert_reason)) + } } impl BuilderTransactions diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index 65c5b1c0..f80a2e0c 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -115,11 +115,6 @@ sol!( } type WorkloadId is bytes32; - - - interface IERC20Permit { - function nonces(address owner) external view returns (uint256); - } ); pub mod args; From 0c70cc53490e64eca67a3e8385d83e4651cf12e3 Mon Sep 17 00:00:00 2001 From: avalonche Date: Sat, 4 Oct 2025 19:38:07 +1000 Subject: [PATCH 3/4] Add permit functions for flashblocks number contract --- crates/op-rbuilder/src/args/op.rs | 9 + crates/op-rbuilder/src/builders/builder_tx.rs | 2 +- .../src/builders/flashblocks/builder_tx.rs | 294 +++++++++++++----- .../src/builders/flashblocks/config.rs | 8 + .../src/builders/flashblocks/service.rs | 11 +- crates/op-rbuilder/src/tests/flashblocks.rs | 2 +- .../op-rbuilder/src/tests/flashtestations.rs | 117 ++++++- .../src/tests/framework/instance.rs | 2 - .../op-rbuilder/src/tests/framework/utils.rs | 8 + 9 files changed, 366 insertions(+), 87 deletions(-) diff --git a/crates/op-rbuilder/src/args/op.rs b/crates/op-rbuilder/src/args/op.rs index b0b90f84..1d81c382 100644 --- a/crates/op-rbuilder/src/args/op.rs +++ b/crates/op-rbuilder/src/args/op.rs @@ -167,6 +167,15 @@ pub struct FlashblocksArgs { )] pub flashblocks_number_contract_address: Option
, + /// Use permit signatures if flashtestations is enabled with the flashtestation key + /// to increment the flashblocks number + #[arg( + long = "flashblocks.number-contract-use-permit", + env = "FLASHBLOCK_NUMBER_CONTRACT_USE_PERMIT", + default_value = "false" + )] + pub flashblocks_number_contract_use_permit: bool, + /// Flashblocks p2p configuration #[command(flatten)] pub p2p: FlashblocksP2pArgs, diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index 698634a9..be6c042a 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -126,7 +126,7 @@ impl From for PayloadBuilderError { BuilderTransactionError::EvmExecutionError(e) => { PayloadBuilderError::EvmExecutionError(e) } - _ => PayloadBuilderError::Other(Box::new(error)), + _ => PayloadBuilderError::other(error), } } } diff --git a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs index 04afc64d..ac24e7f4 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -1,29 +1,20 @@ -use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::{Database, Evm}; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, TxKind}; -use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, sol}; +use alloy_primitives::{Address, B256, Bytes, Signature, U256}; +use alloy_sol_types::{SolCall, SolEvent, SolInterface, SolValue, sol}; use core::fmt::Debug; -use op_alloy_consensus::OpTypedTransaction; -use op_revm::OpHaltReason; use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap}; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; use reth_provider::StateProvider; -use reth_revm::State; -use revm::{ - DatabaseRef, - context::result::{ExecutionResult, ResultAndState}, - inspector::NoOpInspector, -}; +use reth_revm::{State, database::StateProviderDatabase}; +use revm::inspector::NoOpInspector; use tracing::warn; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - InvalidContractDataError, - builder_tx::{BuilderTxBase, get_nonce}, + InvalidContractDataError, SimulationSuccessResult, + builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, }, @@ -37,8 +28,17 @@ sol!( #[sol(rpc, abi)] #[derive(Debug)] interface IFlashblockNumber { + uint256 public flashblockNumber; + function incrementFlashblockNumber() external; + function permitIncrementFlashblockNumber(uint256 currentFlashblockNumber, bytes memory signature) external; + + function computeStructHash(uint256 currentFlashblockNumber) external pure returns (bytes32); + + function hashTypedDataV4(bytes32 structHash) external view returns (bytes32); + + // @notice Emitted when flashblock index is incremented // @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block) event FlashblockIncremented(uint256 newFlashblockIndex); @@ -55,10 +55,8 @@ sol!( pub(super) enum FlashblockNumberError { #[error("flashblocks number contract tx reverted: {0:?}")] Revert(IFlashblockNumber::IFlashblockNumberErrors), - #[error("unknown revert: {0} err: {1}")] - Unknown(String, Error), - #[error("halt: {0:?}")] - Halt(OpHaltReason), + #[error("unknown revert: {0}")] + Unknown(String), } // This will be the end of block transaction of a regular block @@ -133,8 +131,9 @@ impl BuilderTransactions for Flas // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksNumberBuilderTx { - pub signer: Option, + pub signer: Signer, pub flashblock_number_address: Address, + pub use_permit: bool, pub base_builder_tx: BuilderTxBase, pub flashtestations_builder_tx: Option>, @@ -142,16 +141,18 @@ pub(super) struct FlashblocksNumberBuilderTx { impl FlashblocksNumberBuilderTx { pub(super) fn new( - signer: Option, + signer: Signer, flashblock_number_address: Address, + use_permit: bool, flashtestations_builder_tx: Option< FlashtestationsBuilderTx, >, ) -> Self { - let base_builder_tx = BuilderTxBase::new(signer); + let base_builder_tx = BuilderTxBase::new(Some(signer)); Self { signer, flashblock_number_address, + use_permit, base_builder_tx, flashtestations_builder_tx, } @@ -200,27 +201,173 @@ impl FlashblocksNumberBuilderTx { )), } } + + fn current_flashblock_number( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {}.abi_encode(); + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_call(current_flashblock_calldata, None, ctx, evm)?; + IFlashblockNumber::flashblockNumberCall::abi_decode_returns(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + self.flashblock_number_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + }) + } - fn signed_flashblock_number_tx( + fn signed_increment_flashblocks_tx( &self, ctx: &OpPayloadBuilderCtx, - gas_limit: u64, - nonce: u64, - signer: &Signer, - ) -> Result, secp256k1::Error> { + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}.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.flashblock_number_address), - input: calldata.into(), - ..Default::default() - }); - signer.sign_tx(tx) + let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( + calldata.clone(), + Some(IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH), + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.flashblock_number_address, + self.signer, + Some(gas_used), + calldata.into(), + ctx, + evm.db_mut(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + signed_tx, + gas_used, + da_size, + is_top_of_block: true, + }) + } + + fn increment_flashblocks_permit_signature( + &self, + flashtestations_signer: &Signer, + current_flashblock_number: U256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let struct_hash_calldata = IFlashblockNumber::computeStructHashCall { + currentFlashblockNumber: current_flashblock_number, + } + .abi_encode(); + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_call(struct_hash_calldata, None, ctx, evm)?; + let struct_hash = B256::abi_decode(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + self.flashblock_number_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; + let typed_data_hash_calldata = IFlashblockNumber::hashTypedDataV4Call { + structHash: struct_hash, + } + .abi_encode(); + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_call(typed_data_hash_calldata, None, ctx, evm)?; + let typed_data_hash = B256::abi_decode(&output).map_err(|_| { + BuilderTransactionError::InvalidContract( + self.flashblock_number_address, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; + let signature = flashtestations_signer.sign_message(typed_data_hash)?; + Ok(signature) + } + + fn signed_increment_flashblocks_permit_tx( + &self, + flashtestations_signer: &Signer, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let current_flashblock_number = self.current_flashblock_number(ctx, evm)?; + let signature = self.increment_flashblocks_permit_signature( + flashtestations_signer, + current_flashblock_number, + ctx, + evm, + )?; + let calldata = IFlashblockNumber::permitIncrementFlashblockNumberCall { + currentFlashblockNumber: current_flashblock_number, + signature: signature.as_bytes().into(), + } + .abi_encode(); + let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( + calldata.clone(), + Some(IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH), + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.flashblock_number_address, + self.signer, + Some(gas_used), + calldata.into(), + ctx, + evm.db_mut(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + signed_tx, + gas_used, + da_size, + is_top_of_block: true, + }) + } + + fn simulate_flashblocks_call( + &self, + calldata: Vec, + expected_topic: Option, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let signed_tx = self.sign_tx( + self.flashblock_number_address, + self.signer, + None, + calldata.into(), + ctx, + evm.db_mut(), + )?; + self.simulate_call(signed_tx, expected_topic, Self::handle_revert, evm) + } + + fn handle_revert(revert_output: Bytes) -> BuilderTransactionError { + let revert_reason = IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&revert_output) + .map(FlashblockNumberError::Revert) + .unwrap_or_else(|_| FlashblockNumberError::Unknown(hex::encode(revert_output))); + BuilderTransactionError::TransactionReverted(Box::new(revert_reason)) } } @@ -242,46 +389,37 @@ impl BuilderTransactions builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?); } else { // we increment the flashblock number for the next flashblock so we don't increment in the last flashblock - if let Some(signer) = &self.signer { - let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); - evm.modify_cfg(|cfg| { - cfg.disable_balance_check = true; - cfg.disable_block_gas_limit = true; - }); - - let nonce = get_nonce(evm.db_mut(), signer.address)?; - - let tx = match self.estimate_flashblock_number_tx_gas(ctx, &mut evm, signer, nonce) - { - Ok(gas_used) => { - // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - let signed_tx = self.signed_flashblock_number_tx( - ctx, - gas_used * 64 / 63, - nonce, - signer, - )?; - - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - signed_tx.encoded_2718().as_slice(), - ); - Some(BuilderTransactionCtx { - gas_used, - da_size, - 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"); - self.base_builder_tx - .simulate_builder_tx(ctx, &mut *db)? - .map(|tx| tx.set_top_of_block()) - } - }; + 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; + cfg.disable_block_gas_limit = true; + }); - builder_txs.extend(tx); - } + let flashblocks_num_tx = if let Some(flashtestations) = &self.flashtestations_builder_tx + && self.use_permit + { + self.signed_increment_flashblocks_permit_tx( + flashtestations.tee_signer(), + ctx, + &mut evm, + ) + } else { + self.signed_increment_flashblocks_tx(ctx, &mut evm) + }; + + let tx = match flashblocks_num_tx { + Ok(tx) => Some(tx), + Err(e) => { + warn!(target: "builder_tx", error = ?e, "flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); + self.base_builder_tx + .simulate_builder_tx(ctx, db)? + .map(|tx| tx.set_top_of_block()) + } + }; + + builder_txs.extend(tx); } if ctx.is_last_flashblock() { diff --git a/crates/op-rbuilder/src/builders/flashblocks/config.rs b/crates/op-rbuilder/src/builders/flashblocks/config.rs index a3345edb..a47cc046 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/config.rs @@ -39,6 +39,9 @@ pub struct FlashblocksConfig { /// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx. pub flashblocks_number_contract_address: Option
, + /// whether to use permit signatures for the contract calls + pub flashblocks_number_contract_use_permit: bool, + /// Whether to enable the p2p node for flashblocks pub p2p_enabled: bool, @@ -64,6 +67,7 @@ impl Default for FlashblocksConfig { fixed: false, calculate_state_root: true, flashblocks_number_contract_address: None, + flashblocks_number_contract_use_permit: false, p2p_enabled: false, p2p_port: 9009, p2p_private_key_file: None, @@ -93,6 +97,9 @@ impl TryFrom for FlashblocksConfig { let flashblocks_number_contract_address = args.flashblocks.flashblocks_number_contract_address; + let flashblocks_number_contract_use_permit = + args.flashblocks.flashblocks_number_contract_use_permit; + Ok(Self { ws_addr, interval, @@ -100,6 +107,7 @@ impl TryFrom for FlashblocksConfig { fixed, calculate_state_root, flashblocks_number_contract_address, + flashblocks_number_contract_use_permit, p2p_enabled: args.flashblocks.p2p.p2p_enabled, p2p_port: args.flashblocks.p2p.p2p_port, p2p_private_key_file: args.flashblocks.p2p.p2p_private_key_file, diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs index e11fa2f2..01bdcfac 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs @@ -172,7 +172,7 @@ where _: OpEvmConfig, ) -> eyre::Result::Payload>> { let signer = self.0.builder_signer; - let flashtestations_builder_tx = if let Some(builder_key) = signer + let flashtestations_builder_tx = if let Some(builder_signer) = signer && self.0.flashtestations_config.flashtestations_enabled { match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key) @@ -188,15 +188,18 @@ where None }; - if let Some(flashblocks_number_contract_address) = - self.0.specific.flashblocks_number_contract_address + if let Some(builder_signer) = signer + && let Some(flashblocks_number_contract_address) = + self.0.specific.flashblocks_number_contract_address { + let use_permit = self.0.specific.flashblocks_number_contract_use_permit; self.spawn_payload_builder_service( ctx, pool, FlashblocksNumberBuilderTx::new( - signer, + builder_signer, flashblocks_number_contract_address, + use_permit, flashtestations_builder_tx, ), ) diff --git a/crates/op-rbuilder/src/tests/flashblocks.rs b/crates/op-rbuilder/src/tests/flashblocks.rs index a623aaff..cd92da9f 100644 --- a/crates/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/op-rbuilder/src/tests/flashblocks.rs @@ -526,7 +526,7 @@ async fn test_flashblocks_number_contract_builder_tx(rbuilder: LocalInstance) -> let init_tx = driver .create_transaction() .init_flashblock_number_contract(true) - .with_to(contract_address) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) .with_bundle(BundleOpts::default()) .send() .await?; diff --git a/crates/op-rbuilder/src/tests/flashtestations.rs b/crates/op-rbuilder/src/tests/flashtestations.rs index fb424772..04bbd4cd 100644 --- a/crates/op-rbuilder/src/tests/flashtestations.rs +++ b/crates/op-rbuilder/src/tests/flashtestations.rs @@ -421,7 +421,6 @@ async fn test_flashtestations_permit_with_flashblocks_number_contract( .send() .await?; let block = driver.build_new_block_with_current_timestamp(None).await?; - // check the builder tx, funding tx and registration tx is in the block let num_txs = block.transactions.len(); let txs = block.transactions.into_transactions_vec(); // // 1 deposit tx, 1 regular builder tx, 4 flashblocks number tx, 1 user tx, 1 block proof tx @@ -467,6 +466,122 @@ async fn test_flashtestations_permit_with_flashblocks_number_contract( Ok(()) } +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + flashblocks_number_contract_use_permit: true, + ..Default::default() + }, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + flashtestations_use_permit: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_with_flashblocks_number_permit( + rbuilder: LocalInstance, +) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashblock_number_contract(&driver, &provider, false).await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + // Verify flashblock number is not incremented and builder address is not authorized + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert!( + current_number.is_zero(), // contract deployments incremented the number but we built at least 1 full block + "Flashblock number should not be incremented" + ); + let is_authorized = contract.isBuilder(builder_signer().address).call().await?; + assert!(!is_authorized, "builder should not be authorized"); + + // add tee signer address to authorized builders + let add_builder_tx = driver + .create_transaction() + .add_authorized_builder(TEE_DEBUG_ADDRESS) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*add_builder_tx.tx_hash()) + .await? + .expect("add builder tx not mined"); + let num_txs = block.transactions.len(); + let txs = block.transactions.into_transactions_vec(); + // 1 deposit tx, 5 regular builder tx, 1 add builder tx, 1 block proof tx + assert_eq!(num_txs, 8, "Expected 8 transactions in block"); + // Check no transactions to the flashblocks number contract as tee signer is not authorized + for i in 1..6 { + assert_eq!( + txs[i].to(), + Some(Address::ZERO), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // add builder tx + assert_eq!( + txs[6].tx_hash(), + *add_builder_tx.tx_hash(), + "add builder tx should be in correct position in block" + ); + assert_eq!( + txs[7].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "builder tx should send verify block builder proof" + ); + + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + let txs = block.transactions.into_transactions_vec(); + // 1 deposit tx, 1 regular builder tx, 4 flashblocks builder tx, 1 user tx, 1 block proof tx + assert_eq!(txs.len(), 8, "Expected 8 transactions in block"); + // flashblocks number contract + for i in 2..6 { + assert_eq!( + txs[i].to(), + Some(FLASHBLOCKS_NUMBER_ADDRESS), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // user tx + assert_eq!( + txs[6].tx_hash(), + *tx.tx_hash(), + "user tx should be in correct position in block" + ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert_eq!( + current_number, + U256::from(4), + "Flashblock number not incremented correctly" + ); + Ok(()) +} + async fn setup_flashtestation_contracts( driver: &ChainDriver, provider: &RootProvider, diff --git a/crates/op-rbuilder/src/tests/framework/instance.rs b/crates/op-rbuilder/src/tests/framework/instance.rs index ccfadaf0..d698bf45 100644 --- a/crates/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/op-rbuilder/src/tests/framework/instance.rs @@ -52,7 +52,6 @@ use std::{ use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; use tokio_tungstenite::{connect_async, tungstenite::Message}; use tokio_util::sync::CancellationToken; -use tracing::warn; /// Represents a type that emulates a local in-process instance of the OP builder node. /// This node uses IPC as the communication channel for the RPC server Engine API. @@ -410,7 +409,6 @@ impl FlashblocksListener { } Some(Ok(Message::Text(text))) = read.next() => { let fb = serde_json::from_str(&text).unwrap(); - warn!("GOT FB: {fb:#?}"); flashblocks_clone.lock().push(fb); } } diff --git a/crates/op-rbuilder/src/tests/framework/utils.rs b/crates/op-rbuilder/src/tests/framework/utils.rs index 35a5f2a5..99772de1 100644 --- a/crates/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/op-rbuilder/src/tests/framework/utils.rs @@ -33,6 +33,7 @@ pub trait TransactionBuilderExt { // flashblocks number methods fn deploy_flashblock_number_contract(self) -> Self; fn init_flashblock_number_contract(self, register_builder: bool) -> Self; + fn add_authorized_builder(self, builder: Address) -> Self; // flashtestations methods fn deploy_flashtestation_registry_contract(self) -> Self; fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self; @@ -85,6 +86,13 @@ impl TransactionBuilderExt for TransactionBuilder { .with_signer(flashblocks_number_signer()) } + fn add_authorized_builder(self, builder: Address) -> Self { + let calldata = FlashblocksNumber::addBuilderCall { builder }.abi_encode(); + + self.with_input(calldata.into()) + .with_signer(flashblocks_number_signer()) + } + fn deploy_flashtestation_registry_contract(self) -> Self { self.with_create() .with_input(FlashtestationRegistry::BYTECODE.clone()) From 8b7e42ac8047654771eab37b3cdf543ad2ea3c45 Mon Sep 17 00:00:00 2001 From: avalonche Date: Fri, 24 Oct 2025 05:50:31 +1100 Subject: [PATCH 4/4] refactor to simulate call --- crates/op-rbuilder/src/builders/builder_tx.rs | 80 +---- .../src/builders/flashblocks/builder_tx.rs | 243 +++++----------- .../src/builders/flashblocks/service.rs | 2 +- .../src/flashtestations/builder_tx.rs | 275 +----------------- .../op-rbuilder/src/tests/flashtestations.rs | 111 ------- 5 files changed, 73 insertions(+), 638 deletions(-) diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index be6c042a..7cc56b07 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -1,4 +1,4 @@ -use alloy_consensus::{Transaction, TxEip1559}; +use alloy_consensus::TxEip1559; use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; use alloy_op_evm::OpEvm; @@ -387,84 +387,6 @@ pub trait BuilderTransactions, - calldata: Bytes, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result, BuilderTransactionError> { - let nonce = get_nonce(db, from.address)?; - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: ctx.chain_id(), - nonce, - // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - gas_limit: gas_used - .map(|gas| gas * 64 / 63) - .unwrap_or(ctx.block_gas_limit()), - max_fee_per_gas: ctx.base_fee().into(), - to: TxKind::Call(to), - input: calldata, - ..Default::default() - }); - Ok(from.sign_tx(tx)?) - } - - fn simulate_call( - &self, - signed_tx: Recovered, - expected_topic: Option, - revert_handler: impl FnOnce(Bytes) -> BuilderTransactionError, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let ResultAndState { result, state } = match evm.transact(&signed_tx) { - Ok(res) => res, - Err(err) => { - if err.is_invalid_tx_err() { - return Err(BuilderTransactionError::InvalidTransactionError(Box::new( - err, - ))); - } else { - return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); - } - } - }; - - match result { - ExecutionResult::Success { - logs, - gas_used, - output, - .. - } => { - if let Some(topic) = expected_topic - && !logs.iter().any(|log| log.topics().first() == Some(&topic)) - { - return Err(BuilderTransactionError::InvalidContract( - signed_tx.to().unwrap_or_default(), - InvalidContractDataError::InvalidLogs(topic), - )); - } - Ok(SimulationSuccessResult { - gas_used, - output: output.into_data(), - state_changes: state, - }) - } - ExecutionResult::Revert { output, .. } => Err(revert_handler(output)), - ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other( - BuilderTransactionError::TransactionHalted(reason), - )), - } - } } #[derive(Debug, Clone)] diff --git a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs index ac24e7f4..da32fbcb 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -1,22 +1,25 @@ use alloy_eips::Encodable2718; use alloy_evm::{Database, Evm}; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, B256, Bytes, Signature, U256}; -use alloy_sol_types::{SolCall, SolEvent, SolInterface, SolValue, sol}; +use alloy_primitives::{Address, B256, Signature, U256}; +use alloy_rpc_types_eth::TransactionInput; +use alloy_sol_types::{SolCall, SolEvent, sol}; use core::fmt::Debug; +use op_alloy_rpc_types::OpTransactionRequest; use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap}; use reth_provider::StateProvider; -use reth_revm::{State, database::StateProviderDatabase}; -use revm::inspector::NoOpInspector; +use reth_revm::State; +use revm::{DatabaseRef, inspector::NoOpInspector}; use tracing::warn; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - InvalidContractDataError, SimulationSuccessResult, + SimulationSuccessResult, builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, + get_nonce, }, flashtestations::builder_tx::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, @@ -51,14 +54,6 @@ sol!( } ); -#[derive(Debug, thiserror::Error)] -pub(super) enum FlashblockNumberError { - #[error("flashblocks number contract tx reverted: {0:?}")] - Revert(IFlashblockNumber::IFlashblockNumberErrors), - #[error("unknown revert: {0}")] - Unknown(String), -} - // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksBuilderTx { @@ -158,102 +153,13 @@ impl FlashblocksNumberBuilderTx { } } - // TODO: remove and clean up in favour of simulate_call() - fn estimate_flashblock_number_tx_gas( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm, - signer: &Signer, - nonce: u64, - ) -> Result { - let tx = self.signed_flashblock_number_tx(ctx, ctx.block_gas_limit(), nonce, signer)?; - let ResultAndState { result, .. } = match evm.transact(&tx) { - Ok(res) => res, - Err(err) => { - return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); - } - }; - - match result { - ExecutionResult::Success { gas_used, logs, .. } => { - if logs.iter().any(|log| { - log.topics().first() - == Some(&IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH) - }) { - Ok(gas_used) - } else { - Err(BuilderTransactionError::InvalidContract( - self.flashblock_number_address, - InvalidContractDataError::InvalidLogs( - vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], - vec![], - ), - )) - } - } - 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), - )), - } - } - - fn current_flashblock_number( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {}.abi_encode(); - let SimulationSuccessResult { output, .. } = - self.simulate_flashblocks_call(current_flashblock_calldata, None, ctx, evm)?; - IFlashblockNumber::flashblockNumberCall::abi_decode_returns(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - self.flashblock_number_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - }) - } - fn signed_increment_flashblocks_tx( &self, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, + evm: &mut OpEvm, ) -> Result { - let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}.abi_encode(); - let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( - calldata.clone(), - Some(IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH), - ctx, - evm, - )?; - let signed_tx = self.sign_tx( - self.flashblock_number_address, - self.signer, - Some(gas_used), - calldata.into(), - ctx, - evm.db_mut(), - )?; - let da_size = - op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); - Ok(BuilderTransactionCtx { - signed_tx, - gas_used, - da_size, - is_top_of_block: true, - }) + let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}; + self.increment_flashblocks_tx(calldata, &self.signer, ctx, evm) } fn increment_flashblocks_permit_signature( @@ -261,37 +167,18 @@ impl FlashblocksNumberBuilderTx { flashtestations_signer: &Signer, current_flashblock_number: U256, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, + evm: &mut OpEvm, ) -> Result { let struct_hash_calldata = IFlashblockNumber::computeStructHashCall { currentFlashblockNumber: current_flashblock_number, - } - .abi_encode(); + }; let SimulationSuccessResult { output, .. } = - self.simulate_flashblocks_call(struct_hash_calldata, None, ctx, evm)?; - let struct_hash = B256::abi_decode(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - self.flashblock_number_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - })?; - let typed_data_hash_calldata = IFlashblockNumber::hashTypedDataV4Call { - structHash: struct_hash, - } - .abi_encode(); + self.simulate_flashblocks_readonly_call(struct_hash_calldata, ctx, evm)?; + let typed_data_hash_calldata = + IFlashblockNumber::hashTypedDataV4Call { structHash: output }; let SimulationSuccessResult { output, .. } = - self.simulate_flashblocks_call(typed_data_hash_calldata, None, ctx, evm)?; - let typed_data_hash = B256::abi_decode(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - self.flashblock_number_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - })?; - let signature = flashtestations_signer.sign_message(typed_data_hash)?; + self.simulate_flashblocks_readonly_call(typed_data_hash_calldata, ctx, evm)?; + let signature = flashtestations_signer.sign_message(output)?; Ok(signature) } @@ -299,35 +186,38 @@ impl FlashblocksNumberBuilderTx { &self, flashtestations_signer: &Signer, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, + evm: &mut OpEvm, ) -> Result { - let current_flashblock_number = self.current_flashblock_number(ctx, evm)?; - let signature = self.increment_flashblocks_permit_signature( - flashtestations_signer, - current_flashblock_number, - ctx, - evm, - )?; + let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {}; + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_readonly_call(current_flashblock_calldata, ctx, evm)?; + let signature = + self.increment_flashblocks_permit_signature(flashtestations_signer, output, ctx, evm)?; let calldata = IFlashblockNumber::permitIncrementFlashblockNumberCall { - currentFlashblockNumber: current_flashblock_number, + currentFlashblockNumber: output, signature: signature.as_bytes().into(), - } - .abi_encode(); + }; + self.increment_flashblocks_tx(calldata, flashtestations_signer, ctx, evm) + } + + fn increment_flashblocks_tx( + &self, + calldata: T, + signer: &Signer, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( calldata.clone(), - Some(IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH), + vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], ctx, evm, )?; let signed_tx = self.sign_tx( self.flashblock_number_address, - self.signer, - Some(gas_used), - calldata.into(), + *signer, + gas_used, + calldata.abi_encode().into(), ctx, evm.db_mut(), )?; @@ -341,33 +231,34 @@ impl FlashblocksNumberBuilderTx { }) } - fn simulate_flashblocks_call( + fn simulate_flashblocks_readonly_call( &self, - calldata: Vec, - expected_topic: Option, + calldata: T, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let signed_tx = self.sign_tx( - self.flashblock_number_address, - self.signer, - None, - calldata.into(), - ctx, - evm.db_mut(), - )?; - self.simulate_call(signed_tx, expected_topic, Self::handle_revert, evm) + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + self.simulate_flashblocks_call(calldata, vec![], ctx, evm) } - fn handle_revert(revert_output: Bytes) -> BuilderTransactionError { - let revert_reason = IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&revert_output) - .map(FlashblockNumberError::Revert) - .unwrap_or_else(|_| FlashblockNumberError::Unknown(hex::encode(revert_output))); - BuilderTransactionError::TransactionReverted(Box::new(revert_reason)) + fn simulate_flashblocks_call( + &self, + calldata: T, + expected_logs: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + let tx_req = OpTransactionRequest::default() + .gas_limit(ctx.block_gas_limit()) + .max_fee_per_gas(ctx.base_fee().into()) + .to(self.flashblock_number_address) + .from(self.signer.address) // use tee key as signer for simulations + .nonce(get_nonce(evm.db(), self.signer.address)?) + .input(TransactionInput::new(calldata.abi_encode().into())); + self.simulate_call::( + tx_req, + expected_logs, + evm, + ) } } @@ -389,9 +280,7 @@ impl BuilderTransactions builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?); } else { // we increment the flashblock number for the next flashblock so we don't increment in the last flashblock - let mut evm = ctx - .evm_config - .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); evm.modify_cfg(|cfg| { cfg.disable_balance_check = true; cfg.disable_block_gas_limit = true; @@ -414,7 +303,7 @@ impl BuilderTransactions Err(e) => { warn!(target: "builder_tx", error = ?e, "flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); self.base_builder_tx - .simulate_builder_tx(ctx, db)? + .simulate_builder_tx(ctx, &mut *db)? .map(|tx| tx.set_top_of_block()) } }; diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs index 01bdcfac..bf3e1c2f 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs @@ -172,7 +172,7 @@ where _: OpEvmConfig, ) -> eyre::Result::Payload>> { let signer = self.0.builder_signer; - let flashtestations_builder_tx = if let Some(builder_signer) = signer + let flashtestations_builder_tx = if let Some(builder_key) = signer && self.0.flashtestations_config.flashtestations_enabled { match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key) diff --git a/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/op-rbuilder/src/flashtestations/builder_tx.rs index 62a10184..a031f57b 100644 --- a/crates/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -89,6 +89,10 @@ where } } + pub fn tee_signer(&self) -> &Signer { + &self.tee_service_signer + } + /// 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 @@ -337,7 +341,7 @@ where .gas_limit(ctx.block_gas_limit()) .max_fee_per_gas(ctx.base_fee().into()) .to(contract_address) - .from(self.tee_service_signer.address) // use tee key as signer for simulations + .from(self.builder_signer.address) .nonce(get_nonce(evm.db(), self.tee_service_signer.address)?) .input(TransactionInput::new(calldata.abi_encode().into())); if contract_address == self.registry_address { @@ -358,275 +362,6 @@ where )) } } - - fn get_permit_nonce( - &self, - contract_address: Address, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let calldata = IERC20Permit::noncesCall { - owner: self.tee_service_signer.address, - } - .abi_encode(); - let SimulationSuccessResult { output, .. } = - self.simulate_flashtestation_call(contract_address, calldata, None, ctx, evm)?; - U256::abi_decode(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - contract_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - }) - } - - fn registration_permit_signature( - &self, - permit_nonce: U256, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let struct_hash_calldata = IFlashtestationRegistry::computeStructHashCall { - rawQuote: self.attestation.clone().into(), - extendedRegistrationData: self.extra_registration_data.clone(), - nonce: permit_nonce, - deadline: U256::from(ctx.timestamp()), - } - .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( - self.registry_address, - struct_hash_calldata, - None, - ctx, - evm, - )?; - let struct_hash = B256::abi_decode(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - self.registry_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - })?; - let typed_data_hash_calldata = IFlashtestationRegistry::hashTypedDataV4Call { - structHash: struct_hash, - } - .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( - self.registry_address, - typed_data_hash_calldata, - None, - ctx, - evm, - )?; - let typed_data_hash = B256::abi_decode(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - self.registry_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - })?; - let signature = self.tee_service_signer.sign_message(typed_data_hash)?; - Ok(signature) - } - - fn signed_registration_permit_tx( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let permit_nonce = self.get_permit_nonce(self.registry_address, ctx, evm)?; - let signature = self.registration_permit_signature(permit_nonce, ctx, evm)?; - let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall { - rawQuote: self.attestation.clone().into(), - extendedRegistrationData: self.extra_registration_data.clone(), - nonce: permit_nonce, - deadline: U256::from(ctx.timestamp()), - signature: signature.as_bytes().into(), - } - .abi_encode(); - let SimulationSuccessResult { gas_used, .. } = self.simulate_flashtestation_call( - self.registry_address, - calldata.clone(), - Some(TEEServiceRegistered::SIGNATURE_HASH), - ctx, - evm, - )?; - let signed_tx = self.sign_tx( - self.registry_address, - self.builder_signer, - Some(gas_used), - calldata.into(), - ctx, - evm.db_mut(), - )?; - let da_size = - op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); - Ok(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - is_top_of_block: false, - }) - } - - fn block_proof_permit_signature( - &self, - permit_nonce: U256, - block_content_hash: B256, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let struct_hash_calldata = IBlockBuilderPolicy::computeStructHashCall { - version: self.builder_proof_version, - blockContentHash: block_content_hash, - nonce: permit_nonce, - } - .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( - self.builder_policy_address, - struct_hash_calldata, - None, - ctx, - evm, - )?; - let struct_hash = B256::abi_decode(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - self.builder_policy_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - })?; - let typed_data_hash_calldata = IBlockBuilderPolicy::getHashedTypeDataV4Call { - structHash: struct_hash, - } - .abi_encode(); - let SimulationSuccessResult { output, .. } = self.simulate_flashtestation_call( - self.builder_policy_address, - typed_data_hash_calldata, - None, - ctx, - evm, - )?; - let typed_data_hash = B256::abi_decode(&output).map_err(|_| { - BuilderTransactionError::InvalidContract( - self.builder_policy_address, - InvalidContractDataError::OutputAbiDecodeError, - ) - })?; - let signature = self.tee_service_signer.sign_message(typed_data_hash)?; - Ok(signature) - } - - fn signed_block_proof_permit_tx( - &self, - transactions: Vec, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let permit_nonce = self.get_permit_nonce(self.builder_policy_address, ctx, evm)?; - let block_content_hash = Self::compute_block_content_hash( - transactions.clone(), - ctx.parent_hash(), - ctx.block_number(), - ctx.timestamp(), - ); - let signature = - self.block_proof_permit_signature(permit_nonce, block_content_hash, ctx, evm)?; - let calldata = IBlockBuilderPolicy::permitVerifyBlockBuilderProofCall { - blockContentHash: block_content_hash, - nonce: U256::from(permit_nonce), - version: self.builder_proof_version, - eip712Sig: signature.as_bytes().into(), - } - .abi_encode(); - let SimulationSuccessResult { gas_used, .. } = self.simulate_flashtestation_call( - self.builder_policy_address, - calldata.clone(), - Some(BlockBuilderProofVerified::SIGNATURE_HASH), - ctx, - evm, - )?; - let signed_tx = self.sign_tx( - self.builder_policy_address, - self.builder_signer, - Some(gas_used), - calldata.into(), - ctx, - evm.db_mut(), - )?; - let da_size = - op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); - Ok(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - is_top_of_block: false, - }) - } - - fn simulate_flashtestation_call( - &self, - contract_address: Address, - calldata: Vec, - expected_topic: Option, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let signed_tx = self.sign_tx( - contract_address, - self.builder_signer, - None, - calldata.into(), - ctx, - evm.db_mut(), - )?; - let revert_handler = if contract_address == self.registry_address { - Self::handle_registry_reverts - } else { - Self::handle_block_builder_policy_reverts - }; - self.simulate_call(signed_tx, expected_topic, revert_handler, evm) - } - - fn handle_registry_reverts(revert_output: Bytes) -> BuilderTransactionError { - let revert_reason = - IFlashtestationRegistry::IFlashtestationRegistryErrors::abi_decode(&revert_output) - .map(FlashtestationRevertReason::FlashtestationRegistry) - .unwrap_or_else(|_| { - FlashtestationRevertReason::Unknown(hex::encode(revert_output)) - }); - BuilderTransactionError::TransactionReverted(Box::new(revert_reason)) - } - - fn handle_block_builder_policy_reverts(revert_output: Bytes) -> BuilderTransactionError { - let revert_reason = - IBlockBuilderPolicy::IBlockBuilderPolicyErrors::abi_decode(&revert_output) - .map(FlashtestationRevertReason::BlockBuilderPolicy) - .unwrap_or_else(|_| { - FlashtestationRevertReason::Unknown(hex::encode(revert_output)) - }); - BuilderTransactionError::TransactionReverted(Box::new(revert_reason)) - } } impl BuilderTransactions diff --git a/crates/op-rbuilder/src/tests/flashtestations.rs b/crates/op-rbuilder/src/tests/flashtestations.rs index 04bbd4cd..8ca8795f 100644 --- a/crates/op-rbuilder/src/tests/flashtestations.rs +++ b/crates/op-rbuilder/src/tests/flashtestations.rs @@ -125,116 +125,6 @@ async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> Ok(()) } -#[rb_test(args = OpRbuilderArgs { - chain_block_time: 1000, - enable_revert_protection: true, - flashtestations: FlashtestationsArgs { - flashtestations_enabled: true, - registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), - builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), - debug: true, - enable_block_proofs: true, - ..Default::default() - }, - ..Default::default() -})] -async fn test_flashtestations_invalid_quote(rbuilder: LocalInstance) -> eyre::Result<()> { - let driver = rbuilder.driver().await?; - let provider = rbuilder.provider().await?; - setup_flashtestation_contracts(&driver, &provider, false, true).await?; - // verify not registered - let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); - let result = contract - .getRegistrationStatus(TEE_DEBUG_ADDRESS) - .call() - .await?; - assert!( - !result.isValid, - "The tee key is registered for invalid quote" - ); - // check that only regular builder tx is in the block - let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; - let txs = block.transactions.into_transactions_vec(); - - if_flashblocks!( - assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof - // Check builder tx - assert_eq!( - txs[1].to(), - Some(Address::ZERO), - "builder tx should send to zero address" - ); - ); - if_standard!( - assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof - ); - let last_txs = &txs[txs.len() - 2..]; - // Check user transaction - assert_eq!( - last_txs[0].inner.tx_hash(), - tx_hash, - "tx hash for user transaction should match" - ); - // Check builder tx - assert_eq!( - last_txs[1].to(), - Some(Address::ZERO), - "builder tx should send to zero address" - ); - Ok(()) -} - -#[rb_test(args = OpRbuilderArgs { - chain_block_time: 1000, - enable_revert_protection: true, - flashtestations: FlashtestationsArgs { - flashtestations_enabled: true, - registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), - builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), - debug: true, - enable_block_proofs: true, - ..Default::default() - }, - ..Default::default() -})] -async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> eyre::Result<()> { - let driver = rbuilder.driver().await?; - let provider = rbuilder.provider().await?; - setup_flashtestation_contracts(&driver, &provider, true, false).await?; - // check that only the regular builder tx is in the block - let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; - let txs = block.transactions.into_transactions_vec(); - - if_flashblocks!( - assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof - // Check builder tx - assert_eq!( - txs[1].to(), - Some(Address::ZERO), - "builder tx should send to zero address" - ); - ); - if_standard!( - assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + end of block proof - ); - let last_txs = &txs[txs.len() - 2..]; - // Check user transaction - assert_eq!( - last_txs[0].inner.tx_hash(), - tx_hash, - "tx hash for user transaction should match" - ); - // Check builder tx - assert_eq!( - last_txs[1].to(), - Some(Address::ZERO), - "builder tx should send to zero address" - ); - Ok(()) -} - #[rb_test(flashblocks, args = OpRbuilderArgs { chain_block_time: 1000, enable_revert_protection: true, @@ -478,7 +368,6 @@ async fn test_flashtestations_permit_with_flashblocks_number_contract( flashtestations_enabled: true, registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), debug: true, flashtestations_use_permit: true, enable_block_proofs: true,