From 8768d278e00b05134f0ee5d45134ba7dbad22408 Mon Sep 17 00:00:00 2001 From: avalonche Date: Wed, 1 Oct 2025 07:59:00 +1000 Subject: [PATCH] Update flashtestation service with latest contracts --- Cargo.lock | 5 +- crates/op-rbuilder/Cargo.toml | 8 +- crates/op-rbuilder/src/builders/builder_tx.rs | 23 +- crates/op-rbuilder/src/builders/context.rs | 2 +- crates/op-rbuilder/src/builders/mod.rs | 5 +- .../src/builders/standard/payload.rs | 14 +- .../op-rbuilder/src/flashtestations/args.rs | 16 +- .../src/flashtestations/attestation.rs | 45 ++-- crates/op-rbuilder/src/flashtestations/mod.rs | 95 +++++++ .../src/flashtestations/service.rs | 248 ++++++++---------- .../src/flashtestations/tx_manager.rs | 169 ++++-------- 11 files changed, 334 insertions(+), 296 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6b1489ad..cff01a33a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6308,6 +6308,9 @@ dependencies = [ "futures-util", "hex", "http", + "http-body-util", + "hyper", + "hyper-util", "jsonrpsee 0.26.0", "jsonrpsee-core 0.26.0", "jsonrpsee-types 0.26.0", @@ -6325,6 +6328,7 @@ dependencies = [ "opentelemetry 0.29.1", "parking_lot", "rand 0.9.2", + "reqwest", "reth", "reth-basic-payload-builder", "reth-chain-state", @@ -6393,7 +6397,6 @@ dependencies = [ "tower 0.5.2", "tracing", "tracing-subscriber 0.3.20", - "ureq", "url", "uuid", "vergen", diff --git a/crates/op-rbuilder/Cargo.toml b/crates/op-rbuilder/Cargo.toml index f1fe8d1db..c674691c4 100644 --- a/crates/op-rbuilder/Cargo.toml +++ b/crates/op-rbuilder/Cargo.toml @@ -125,7 +125,7 @@ moka = "0.12" http = "1.0" sha3 = "0.10" hex = "0.4" -ureq = "2.10" +reqwest = "0.12.23" k256 = "0.13.4" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "b86af43969557bee18f17ec1d6bcd3e984f910b2" } @@ -136,6 +136,9 @@ tar = { version = "0.4", optional = true } ctor = { version = "0.4.2", optional = true } rlimit = { version = "0.10", optional = true } macros = { path = "src/tests/framework/macros", optional = true } +hyper = { version = "1.7.0", features = ["http1"], optional = true } +hyper-util = { version = "0.1.11", optional = true } +http-body-util = { version = "0.1.3", optional = true } testcontainers = "0.24.0" dirs-next = "2.0.0" @@ -157,6 +160,9 @@ reth-ipc = { workspace = true } reth-node-builder = { workspace = true, features = ["test-utils"] } ctor = "0.4.2" rlimit = { version = "0.10" } +hyper = { version = "1.7.0", features = ["http1"] } +hyper-util = { version = "0.1.11" } +http-body-util = { version = "0.1.3" } [features] default = ["jemalloc"] diff --git a/crates/op-rbuilder/src/builders/builder_tx.rs b/crates/op-rbuilder/src/builders/builder_tx.rs index a8ba3bd80..0cd8d5e8e 100644 --- a/crates/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/op-rbuilder/src/builders/builder_tx.rs @@ -2,7 +2,7 @@ use alloy_consensus::TxEip1559; use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; use alloy_primitives::{ - Address, B256, Log, TxKind, + Address, B256, Log, TxKind, U256, map::foldhash::{HashSet, HashSetExt}, }; use core::fmt::Debug; @@ -13,9 +13,7 @@ use reth_node_api::PayloadBuilderError; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::{ProviderError, StateProvider}; -use reth_revm::{ - State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, -}; +use reth_revm::{State, database::StateProviderDatabase}; use revm::{ DatabaseCommit, context::result::{EVMError, ResultAndState}, @@ -57,6 +55,9 @@ pub enum BuilderTransactionError { /// Signature signing fails #[error("failed to sign transaction: {0}")] SigningError(secp256k1::Error), + /// Invalid tx errors during evm execution. + #[error("invalid transaction error {0}")] + InvalidTransactionError(Box), /// Unrecoverable error during evm execution. #[error("evm execution error {0}")] EvmExecutionError(Box), @@ -187,7 +188,6 @@ pub trait BuilderTransactions: Debug { .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; evm.db_mut().commit(state); - evm.db_mut().merge_transitions(BundleRetention::Reverts); } Ok(simulation_state) @@ -283,7 +283,7 @@ impl BuilderTxBase { } } -pub(crate) fn get_nonce( +pub fn get_nonce( db: &mut State, address: Address, ) -> Result { @@ -292,6 +292,15 @@ pub(crate) fn get_nonce( .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) } -pub(crate) fn log_exists(logs: &[Log], topic: &B256) -> bool { +pub fn get_balance( + db: &mut State, + address: Address, +) -> Result { + db.load_cache_account(address) + .map(|acc| acc.account_info().unwrap_or_default().balance) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) +} + +pub fn log_exists(logs: &[Log], topic: &B256) -> bool { logs.iter().any(|log| log.topics().first() == Some(topic)) } diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index b49c697f9..71f10c833 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -480,7 +480,7 @@ impl OpPayloadBuilderCtx { } if exclude_reverting_txs { log_txn(TxnExecutionResult::RevertedAndExcluded); - info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); + info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), result = ?result, "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } else { diff --git a/crates/op-rbuilder/src/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs index 8dcde8eb5..9dbd949ce 100644 --- a/crates/op-rbuilder/src/builders/mod.rs +++ b/crates/op-rbuilder/src/builders/mod.rs @@ -21,7 +21,10 @@ mod flashblocks; mod generator; mod standard; -pub use builder_tx::{BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions}; +pub use builder_tx::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, get_balance, get_nonce, + log_exists, +}; pub use context::OpPayloadBuilderCtx; pub use flashblocks::FlashblocksBuilder; pub use standard::StandardBuilder; diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index 0c0e5de07..66e0adda4 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -347,7 +347,15 @@ impl OpBuilder<'_, Txs> { // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx - let builder_txs = builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, true)?; + let builder_txs = + match builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, true) { + Ok(builder_txs) => builder_txs, + Err(e) => { + error!(target: "payload_builder", "Error adding builder txs to block: {}", e); + vec![] + } + }; + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let block_gas_limit = ctx.block_gas_limit().saturating_sub(builder_tx_gas); if block_gas_limit == 0 { @@ -394,7 +402,9 @@ impl OpBuilder<'_, Txs> { } // Add builder tx to the block - builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, false)?; + if let Err(e) = builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, false) { + error!(target: "payload_builder", "Error adding builder txs to fallback block: {}", e); + }; let state_merge_start_time = Instant::now(); diff --git a/crates/op-rbuilder/src/flashtestations/args.rs b/crates/op-rbuilder/src/flashtestations/args.rs index 3e8266399..54d5d837f 100644 --- a/crates/op-rbuilder/src/flashtestations/args.rs +++ b/crates/op-rbuilder/src/flashtestations/args.rs @@ -1,10 +1,12 @@ use alloy_primitives::{Address, U256, utils::parse_ether}; +use clap::Parser; +use reth_optimism_cli::commands::Commands; -use crate::tx_signer::Signer; +use crate::{args::Cli, tx_signer::Signer}; /// Parameters for Flashtestations configuration /// The names in the struct are prefixed with `flashtestations` -#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] pub struct FlashtestationsArgs { /// When set to true, the builder will initiate the flashtestations /// workflow within the bootstrapping and block building process. @@ -91,3 +93,13 @@ pub struct FlashtestationsArgs { )] pub builder_proof_version: u8, } + +impl Default for FlashtestationsArgs { + fn default() -> Self { + let args = Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashtestations + } +} diff --git a/crates/op-rbuilder/src/flashtestations/attestation.rs b/crates/op-rbuilder/src/flashtestations/attestation.rs index 534dfcb15..39c99539d 100644 --- a/crates/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/op-rbuilder/src/flashtestations/attestation.rs @@ -1,6 +1,5 @@ -use std::io::Read; +use reqwest::Client; use tracing::info; -use ureq; const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/attest"; @@ -12,55 +11,55 @@ pub struct AttestationConfig { /// The URL of the quote provider pub quote_provider: Option, } - -/// Trait for attestation providers -pub trait AttestationProvider { - fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result>; -} - /// Remote attestation provider +#[derive(Debug, Clone)] pub struct RemoteAttestationProvider { + client: Client, service_url: String, } impl RemoteAttestationProvider { pub fn new(service_url: String) -> Self { - Self { service_url } + let client = Client::new(); + Self { + client, + service_url, + } } } -impl AttestationProvider for RemoteAttestationProvider { - fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { +impl RemoteAttestationProvider { + pub async fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { let report_data_hex = hex::encode(report_data); let url = format!("{}/{}", self.service_url, report_data_hex); info!(target: "flashtestations", url = url, "fetching quote in debug mode"); - let response = ureq::get(&url) + let response = self + .client + .get(&url) .timeout(std::time::Duration::from_secs(10)) - .call()?; - - let mut body = Vec::new(); - response.into_reader().read_to_end(&mut body)?; + .send() + .await? + .error_for_status()?; + let body = response.bytes().await?.to_vec(); Ok(body) } } -pub fn get_attestation_provider( - config: AttestationConfig, -) -> Box { +pub fn get_attestation_provider(config: AttestationConfig) -> RemoteAttestationProvider { if config.debug { - Box::new(RemoteAttestationProvider::new( + RemoteAttestationProvider::new( config .quote_provider .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), - )) + ) } else { - Box::new(RemoteAttestationProvider::new( + RemoteAttestationProvider::new( config .quote_provider .expect("remote quote provider must be specified when not in debug mode"), - )) + ) } } diff --git a/crates/op-rbuilder/src/flashtestations/mod.rs b/crates/op-rbuilder/src/flashtestations/mod.rs index a5b16e734..6f9c2743f 100644 --- a/crates/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/op-rbuilder/src/flashtestations/mod.rs @@ -1,3 +1,98 @@ +use alloy_sol_types::{Error, sol}; +use op_revm::OpHaltReason; + +// https://github.com/flashbots/flashtestations/commit/7cc7f68492fe672a823dd2dead649793aac1f216 +sol!( + #[sol(rpc, abi)] + #[derive(Debug)] + interface IFlashtestationRegistry { + function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external; + + /// @notice Emitted when a TEE service is registered + /// @param teeAddress The address of the TEE service + /// @param rawQuote The raw quote from the TEE device + /// @param alreadyExists Whether the TEE service is already registered + event TEEServiceRegistered(address indexed teeAddress, bytes rawQuote, bool alreadyExists); + + /// @notice Emitted when the attestation contract is the 0x0 address + error InvalidAttestationContract(); + /// @notice Emitted when the signature is expired because the deadline has passed + error ExpiredSignature(uint256 deadline); + /// @notice Emitted when the quote is invalid according to the Automata DCAP Attestation contract + error InvalidQuote(bytes output); + /// @notice Emitted when the report data length is too short + error InvalidReportDataLength(uint256 length); + /// @notice Emitted when the registration data hash does not match the expected hash + error InvalidRegistrationDataHash(bytes32 expected, bytes32 received); + /// @notice Emitted when the byte size is exceeded + error ByteSizeExceeded(uint256 size); + /// @notice Emitted when the TEE service is already registered when registering + error TEEServiceAlreadyRegistered(address teeAddress); + /// @notice Emitted when the signer doesn't match the TEE address + error SignerMustMatchTEEAddress(address signer, address teeAddress); + /// @notice Emitted when the TEE service is not registered + error TEEServiceNotRegistered(address teeAddress); + /// @notice Emitted when the TEE service is already invalid when trying to invalidate a TEE registration + error TEEServiceAlreadyInvalid(address teeAddress); + /// @notice Emitted when the TEE service is still valid when trying to invalidate a TEE registration + error TEEIsStillValid(address teeAddress); + /// @notice Emitted when the nonce is invalid when verifying a signature + error InvalidNonce(uint256 expected, uint256 provided); + } + + #[sol(rpc, abi)] + #[derive(Debug)] + interface IBlockBuilderPolicy { + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + + /// @notice Emitted when a block builder proof is successfully verified + /// @param caller The address that called the verification function (TEE address) + /// @param workloadId The workload identifier of the TEE + /// @param version The flashtestation protocol version used + /// @param blockContentHash The hash of the block content + /// @param commitHash The git commit hash associated with the workload + event BlockBuilderProofVerified( + address caller, bytes32 workloadId, uint8 version, bytes32 blockContentHash, string commitHash + ); + + /// @notice Emitted when the registry is the 0x0 address + error InvalidRegistry(); + /// @notice Emitted when a workload to be added is already in the policy + error WorkloadAlreadyInPolicy(); + /// @notice Emitted when a workload to be removed is not in the policy + error WorkloadNotInPolicy(); + /// @notice Emitted when the address is not in the approvedWorkloads mapping + error UnauthorizedBlockBuilder(address caller); + /// @notice Emitted when the nonce is invalid + error InvalidNonce(uint256 expected, uint256 provided); + /// @notice Emitted when the commit hash is empty + error EmptyCommitHash(); + /// @notice Emitted when the source locators array is empty + error EmptySourceLocators(); + } + + struct BlockData { + bytes32 parentHash; + uint256 blockNumber; + uint256 timestamp; + bytes32[] transactionHashes; + } + + type WorkloadId is bytes32; +); + +#[derive(Debug, thiserror::Error)] +pub enum FlashtestationRevertReason { + #[error("flashtestation registry error: {0:?}")] + FlashtestationRegistry(IFlashtestationRegistry::IFlashtestationRegistryErrors), + #[error("block builder policy error: {0:?}")] + BlockBuilderPolicy(IBlockBuilderPolicy::IBlockBuilderPolicyErrors), + #[error("unknown revert: {0} err: {1}")] + Unknown(String, Error), + #[error("halt: {0:?}")] + Halt(OpHaltReason), +} + pub mod args; pub mod attestation; pub mod service; diff --git a/crates/op-rbuilder/src/flashtestations/service.rs b/crates/op-rbuilder/src/flashtestations/service.rs index bd93a777c..83dd613f7 100644 --- a/crates/op-rbuilder/src/flashtestations/service.rs +++ b/crates/op-rbuilder/src/flashtestations/service.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; - -use alloy_primitives::U256; +use alloy_primitives::{Bytes, keccak256}; use reth_node_builder::BuilderContext; use reth_provider::StateProvider; use reth_revm::State; @@ -14,99 +12,15 @@ use crate::{ }, primitives::reth::ExecutionInfo, traits::NodeBounds, - tx_signer::{Signer, generate_ethereum_keypair}, + tx_signer::{Signer, generate_ethereum_keypair, generate_key_from_seed}, }; use super::{ args::FlashtestationsArgs, - attestation::{AttestationConfig, AttestationProvider, get_attestation_provider}, + attestation::{AttestationConfig, get_attestation_provider}, tx_manager::TxManager, }; -#[derive(Clone)] -pub struct FlashtestationsService { - // Attestation provider generating attestations - attestation_provider: Arc>, - // Handles the onchain attestation and TEE block building proofs - tx_manager: TxManager, - // TEE service generated key - tee_service_signer: Signer, - // Funding amount for the TEE signer - funding_amount: U256, -} - -// TODO: FlashtestationsService error types -impl FlashtestationsService { - pub fn new(args: FlashtestationsArgs) -> Self { - let (private_key, public_key, address) = generate_ethereum_keypair(); - let tee_service_signer = Signer { - address, - pubkey: public_key, - secret: private_key, - }; - - let attestation_provider = Arc::new(get_attestation_provider(AttestationConfig { - debug: args.debug, - quote_provider: args.quote_provider, - })); - - let tx_manager = TxManager::new( - tee_service_signer, - args.funding_key - .expect("funding key required when flashtestations enabled"), - args.rpc_url - .expect("external rpc url required when flashtestations enabled"), - args.registry_address - .expect("registry address required when flashtestations enabled"), - args.builder_policy_address - .expect("builder policy address required when flashtestations enabled"), - args.builder_proof_version, - ); - - Self { - attestation_provider, - tx_manager, - tee_service_signer, - funding_amount: args.funding_amount, - } - } - - pub async fn bootstrap(&self) -> eyre::Result<()> { - // Prepare report data with public key (64 bytes, no 0x04 prefix) - let mut report_data = [0u8; 64]; - let pubkey_uncompressed = self.tee_service_signer.pubkey.serialize_uncompressed(); - report_data.copy_from_slice(&pubkey_uncompressed[1..65]); // Skip 0x04 prefix - - // Request TDX attestation - info!(target: "flashtestations", "requesting TDX attestation"); - let attestation = self.attestation_provider.get_attestation(report_data)?; - - // Submit report onchain by registering the key of the tee service - self.tx_manager - .fund_and_register_tee_service(attestation, self.funding_amount) - .await - } - - pub async fn clean_up(&self) -> eyre::Result<()> { - self.tx_manager.clean_up().await - } -} - -#[derive(Debug, Clone)] -pub struct FlashtestationsBuilderTx {} - -impl BuilderTransactions for FlashtestationsBuilderTx { - fn simulate_builder_txs( - &self, - _state_provider: impl StateProvider + Clone, - _info: &mut ExecutionInfo, - _ctx: &OpPayloadBuilderCtx, - _db: &mut State, - ) -> Result, BuilderTransactionError> { - Ok(vec![]) - } -} - pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, ctx: &BuilderContext, @@ -114,74 +28,124 @@ pub async fn bootstrap_flashtestations( where Node: NodeBounds, { - info!("Flashtestations enabled"); + let (private_key, public_key, address) = if args.debug { + info!("Flashtestations debug mode enabled, generating debug key"); + // Generate deterministic key for debugging purposes + generate_key_from_seed(&args.debug_tee_key_seed) + } else { + generate_ethereum_keypair() + }; + + info!("Flashtestations key generated: {}", address); + + let tee_service_signer = Signer { + address, + pubkey: public_key, + secret: private_key, + }; + + let funding_key = args + .funding_key + .expect("funding key required when flashtestations enabled"); + let registry_address = args + .registry_address + .expect("registry address required when flashtestations enabled"); + let _builder_policy_address = args + .builder_policy_address + .expect("builder policy address required when flashtestations enabled"); + + let attestation_provider = get_attestation_provider(AttestationConfig { + debug: args.debug, + quote_provider: args.quote_provider, + }); + + // Prepare report data: + // - TEE address (20 bytes) at reportData[0:20] + // - Extended registration data hash (32 bytes) at reportData[20:52] + // - Total: 52 bytes, padded to 64 bytes with zeros + + // Extract TEE address as 20 bytes + let tee_address_bytes: [u8; 20] = tee_service_signer.address.into(); + + // Calculate keccak256 hash of empty bytes (32 bytes) + let ext_data = Bytes::from(b""); + let ext_data_hash = keccak256(&ext_data); + + // Create 64-byte report data array + let mut report_data = [0u8; 64]; + + // Copy TEE address (20 bytes) to positions 0-19 + report_data[0..20].copy_from_slice(&tee_address_bytes); + + // Copy extended registration data hash (32 bytes) to positions 20-51 + report_data[20..52].copy_from_slice(ext_data_hash.as_ref()); + + // Request TDX attestation + info!(target: "flashtestations", "requesting TDX attestation"); + let attestation = attestation_provider.get_attestation(report_data).await?; + + #[allow(dead_code)] + let (tx_manager, _registered) = if let Some(rpc_url) = args.rpc_url { + let tx_manager = TxManager::new( + tee_service_signer, + funding_key, + rpc_url.clone(), + registry_address, + ); + // Submit report onchain by registering the key of the tee service + match tx_manager + .fund_and_register_tee_service( + attestation.clone(), + ext_data.clone(), + args.funding_amount, + ) + .await + { + Ok(_) => (Some(tx_manager), true), + Err(e) => { + warn!(error = %e, "Failed to register tee service via rpc"); + (Some(tx_manager), false) + } + } + } else { + (None, false) + }; - let flashtestations_service = FlashtestationsService::new(args.clone()); - // Generates new key and registers the attestation onchain - flashtestations_service.bootstrap().await?; + let flashtestations_builder_tx = FlashtestationsBuilderTx {}; - let flashtestations_clone = flashtestations_service.clone(); ctx.task_executor() .spawn_critical_with_graceful_shutdown_signal( "flashtestations clean up task", |shutdown| { Box::pin(async move { let graceful_guard = shutdown.await; - if let Err(e) = flashtestations_clone.clean_up().await { - warn!( - error = %e, - "Failed to complete clean up for flashtestations service", - ) - }; + if let Some(tx_manager) = tx_manager { + if let Err(e) = tx_manager.clean_up().await { + warn!( + error = %e, + "Failed to complete clean up for flashtestations service", + ); + } + } drop(graceful_guard) }) }, ); - Ok(FlashtestationsBuilderTx {}) + Ok(flashtestations_builder_tx) } -#[cfg(test)] -mod tests { - use alloy_primitives::Address; - use secp256k1::{PublicKey, Secp256k1, SecretKey}; - use sha3::{Digest, Keccak256}; - - use crate::tx_signer::public_key_to_address; - - /// Derives Ethereum address from report data using the same logic as the Solidity contract - fn derive_ethereum_address_from_report_data(pubkey_64_bytes: &[u8]) -> Address { - // This exactly matches the Solidity implementation: - // address(uint160(uint256(keccak256(reportData)))) - - // Step 1: keccak256(reportData) - let hash = Keccak256::digest(pubkey_64_bytes); - - // Step 2: Take last 20 bytes (same as uint256 -> uint160 conversion) - let mut address_bytes = [0u8; 20]; - address_bytes.copy_from_slice(&hash[12..32]); - - Address::from(address_bytes) - } - - #[test] - fn test_address_derivation_matches() { - // Test that our manual derivation is correct - let secp = Secp256k1::new(); - let private_key = SecretKey::from_slice(&[0x01; 32]).unwrap(); - let public_key = PublicKey::from_secret_key(&secp, &private_key); - - // Get address using our implementation - let our_address = public_key_to_address(&public_key); - - // Get address using our manual derivation (matching Solidity) - let pubkey_bytes = public_key.serialize_uncompressed(); - let report_data = &pubkey_bytes[1..65]; // Skip 0x04 prefix - let manual_address = derive_ethereum_address_from_report_data(report_data); +#[derive(Debug, Clone)] +pub struct FlashtestationsBuilderTx {} - assert_eq!( - our_address, manual_address, - "Address derivation should match" - ); +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![]) } } diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs index 9eabdd28a..a49f50a36 100644 --- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,41 +1,34 @@ -use alloy_consensus::TxEip1559; -use alloy_eips::Encodable2718; +use alloy_json_rpc::RpcError; use alloy_network::ReceiptResponse; -use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, keccak256}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256}; use alloy_rpc_types_eth::TransactionRequest; -use alloy_transport::TransportResult; -use op_alloy_consensus::OpTypedTransaction; -use reth_optimism_node::OpBuiltPayload; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; +use alloy_sol_types::SolCall; +use alloy_transport::{TransportError, TransportErrorKind, TransportResult}; +use k256::ecdsa; use std::time::Duration; -use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; +use alloy_provider::{ + PendingTransactionBuilder, PendingTransactionError, Provider, ProviderBuilder, +}; use alloy_signer_local::PrivateKeySigner; -use alloy_sol_types::{SolCall, SolValue, sol}; use op_alloy_network::Optimism; -use tracing::{debug, error, info}; - -use crate::tx_signer::Signer; - -sol!( - #[sol(rpc, abi)] - interface IFlashtestationRegistry { - function registerTEEService(bytes calldata rawQuote) external; - } - - #[sol(rpc, abi)] - interface IBlockBuilderPolicy { - function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; - } - - struct BlockData { - bytes32 parentHash; - uint256 blockNumber; - uint256 timestamp; - bytes32[] transactionHashes; - } -); +use tracing::{debug, info, warn}; + +use crate::{flashtestations::IFlashtestationRegistry, tx_signer::Signer}; + +#[derive(Debug, thiserror::Error)] +pub enum TxManagerError { + #[error("rpc error: {0}")] + RpcError(#[from] TransportError), + #[error("tx reverted: {0}")] + TxReverted(TxHash), + #[error("error checking tx confirmation: {0}")] + TxConfirmationError(PendingTransactionError), + #[error("tx rpc error: {0}")] + TxRpcError(RpcError), + #[error("signer error: {0}")] + SignerError(ecdsa::Error), +} #[derive(Debug, Clone)] pub struct TxManager { @@ -43,8 +36,6 @@ pub struct TxManager { funding_signer: Signer, rpc_url: String, registry_address: Address, - builder_policy_address: Address, - builder_proof_version: u8, } impl TxManager { @@ -53,21 +44,23 @@ impl TxManager { funding_signer: Signer, rpc_url: String, registry_address: Address, - builder_policy_address: Address, - builder_proof_version: u8, ) -> Self { Self { tee_service_signer, funding_signer, rpc_url, registry_address, - builder_policy_address, - builder_proof_version, } } - pub async fn fund_address(&self, from: Signer, to: Address, amount: U256) -> eyre::Result<()> { - let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into())?; + pub async fn fund_address( + &self, + from: Signer, + to: Address, + amount: U256, + ) -> Result<(), TxManagerError> { + let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into()) + .map_err(TxManagerError::SignerError)?; let provider = ProviderBuilder::new() .disable_recommended_fillers() .fetch_chain_id() @@ -91,36 +84,41 @@ impl TxManager { match Self::process_pending_tx(provider.send_transaction(funding_tx.into()).await).await { Ok(tx_hash) => { info!(target: "flashtestations", tx_hash = %tx_hash, "funding transaction confirmed successfully"); + Ok(()) } Err(e) => { - error!(target: "flashtestations", error = %e, "funding transaction failed"); - return Err(e); + warn!(target: "flashtestations", error = %e, "funding transaction failed"); + Err(e) } } - - Ok(()) } pub async fn fund_and_register_tee_service( &self, attestation: Vec, + extra_registration_data: Bytes, funding_amount: U256, - ) -> eyre::Result<()> { + ) -> Result<(), TxManagerError> { info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address); self.fund_address( self.funding_signer, self.tee_service_signer.address, funding_amount, ) - .await?; + .await + .unwrap_or_else(|e| { + warn!(target: "flashtestations", error = %e, "Failed to fund TEE address, attempting to register without funding"); + }); let quote_bytes = Bytes::from(attestation); let wallet = - PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into())?; + PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into()) + .map_err(TxManagerError::SignerError)?; let provider = ProviderBuilder::new() .disable_recommended_fillers() .fetch_chain_id() .with_gas_estimation() + .with_cached_nonce_management() .wallet(wallet) .network::() .connect(self.rpc_url.as_str()) @@ -128,16 +126,14 @@ impl TxManager { info!(target: "flashtestations", "submitting quote to registry at {}", self.registry_address); - // TODO: add retries let calldata = IFlashtestationRegistry::registerTEEServiceCall { rawQuote: quote_bytes, + extendedRegistrationData: extra_registration_data, } .abi_encode(); let tx = TransactionRequest { from: Some(self.tee_service_signer.address), to: Some(TxKind::Call(self.registry_address)), - // gas: Some(10_000_000), // Set gas limit manually as the contract is gas heavy - nonce: Some(0), input: calldata.into(), ..Default::default() }; @@ -147,43 +143,13 @@ impl TxManager { Ok(()) } Err(e) => { - error!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); + warn!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); Err(e) } } } - pub fn signed_block_builder_proof( - &self, - payload: OpBuiltPayload, - gas_limit: u64, - base_fee: u64, - chain_id: u64, - nonce: u64, - ) -> Result, secp256k1::Error> { - let block_content_hash = Self::compute_block_content_hash(payload); - - info!(target: "flashtestations", block_content_hash = ?block_content_hash, "submitting block builder proof transaction"); - let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { - version: self.builder_proof_version, - blockContentHash: block_content_hash, - } - .abi_encode(); - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas: base_fee.into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(self.builder_policy_address), - input: calldata.into(), - ..Default::default() - }); - self.tee_service_signer.sign_tx(tx) - } - - pub async fn clean_up(&self) -> eyre::Result<()> { + pub async fn clean_up(&self) -> Result<(), TxManagerError> { info!(target: "flashtestations", "sending funds back from TEE generated key to funding address"); let provider = ProviderBuilder::new() .disable_recommended_fillers() @@ -202,7 +168,7 @@ impl TxManager { self.funding_signer.address, balance.saturating_sub(gas_cost), ) - .await? + .await?; } Ok(()) } @@ -210,7 +176,7 @@ impl TxManager { /// Processes a pending transaction and logs whether the transaction succeeded or not async fn process_pending_tx( pending_tx_result: TransportResult>, - ) -> eyre::Result { + ) -> Result { match pending_tx_result { Ok(pending_tx) => { let tx_hash = *pending_tx.tx_hash(); @@ -226,42 +192,13 @@ impl TxManager { if receipt.status() { Ok(receipt.transaction_hash()) } else { - Err(eyre::eyre!("Transaction reverted: {}", tx_hash)) + Err(TxManagerError::TxReverted(tx_hash)) } } - Err(e) => Err(e.into()), + Err(e) => Err(TxManagerError::TxConfirmationError(e)), } } - Err(e) => Err(e.into()), + Err(e) => Err(TxManagerError::TxRpcError(e)), } } - - /// Computes the block content hash according to the formula: - /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) - fn compute_block_content_hash(payload: OpBuiltPayload) -> B256 { - let block = payload.block(); - let body = block.clone().into_body(); - let transactions = body.transactions(); - - // Create ordered list of transaction hashes - let transaction_hashes: Vec = transactions - .map(|tx| { - // RLP encode the transaction and hash it - let mut encoded = Vec::new(); - tx.encode_2718(&mut encoded); - keccak256(&encoded) - }) - .collect(); - - // Create struct and ABI encode - let block_data = BlockData { - parentHash: block.parent_hash, - blockNumber: U256::from(block.number), - timestamp: U256::from(block.timestamp), - transactionHashes: transaction_hashes, - }; - - let encoded = block_data.abi_encode(); - keccak256(&encoded) - } }