-
Notifications
You must be signed in to change notification settings - Fork 67
refactor: builder transactions overhaul #549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
akundaz
wants to merge
1
commit into
main
Choose a base branch
from
ash-lznkouryvmww
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| use alloy_primitives::BlockHash; | ||
|
|
||
| use crate::{evm::OpBlockEvmFactory, hardforks::ActiveHardforks}; | ||
|
|
||
| /// Minimal environment needed by [`super::producer::BuilderTxProducer`] so that | ||
| /// builder-transaction code does not depend on the full payload-job context. | ||
| pub struct BuilderTxEnv<'a> { | ||
| pub evm_factory: &'a OpBlockEvmFactory, | ||
| pub hardforks: &'a ActiveHardforks, | ||
| pub base_fee: u64, | ||
| pub block_number: u64, | ||
| pub block_gas_limit: u64, | ||
| pub parent_hash: BlockHash, | ||
| pub max_uncompressed_block_size: Option<u64>, | ||
| } | ||
|
|
||
| impl BuilderTxEnv<'_> { | ||
| pub fn chain_id(&self) -> u64 { | ||
| self.hardforks.chain_id() | ||
| } | ||
| } |
109 changes: 109 additions & 0 deletions
109
crates/op-rbuilder/src/builder/builder_tx/impls/claim.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| use alloy_consensus::TxEip1559; | ||
| use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; | ||
| use alloy_primitives::{Address, TxKind}; | ||
| use op_alloy_consensus::OpTypedTransaction; | ||
| use reth_optimism_primitives::OpTransactionSigned; | ||
| use reth_primitives_traits::Recovered; | ||
| use reth_provider::StateProvider; | ||
| use revm::DatabaseRef; | ||
| use std::sync::Arc; | ||
|
|
||
| use crate::{ | ||
| builder::builder_tx::{ | ||
| BuilderTxEnv, BuilderTxError, BuilderTxProducer, SimulatedBuilderTx, SimulationState, | ||
| get_nonce, | ||
| }, | ||
| primitives::reth::ExecutionInfo, | ||
| tx_signer::Signer, | ||
| }; | ||
|
|
||
| /// Builder transaction producer for the block claim: a tx from the builder's | ||
| /// address carrying a "Block Number: {}" message, publicly indicating we built | ||
| /// the block. | ||
| #[derive(Debug, Clone, derive_more::Constructor)] | ||
| pub(crate) struct ClaimBuilderTx { | ||
| pub signer: Option<Signer>, | ||
| } | ||
|
|
||
| impl BuilderTxProducer for ClaimBuilderTx { | ||
| fn simulate_builder_txs( | ||
| &self, | ||
| _state_provider: Arc<dyn StateProvider + Send>, | ||
| _info: &ExecutionInfo, | ||
| env: &BuilderTxEnv<'_>, | ||
| sim_state: &mut SimulationState, | ||
| ) -> Result<Vec<SimulatedBuilderTx>, BuilderTxError> { | ||
| Ok(self | ||
| .simulate_builder_tx(env, &mut *sim_state)? | ||
| .into_iter() | ||
| .collect()) | ||
| } | ||
| } | ||
|
|
||
| impl ClaimBuilderTx { | ||
| fn simulate_builder_tx( | ||
| &self, | ||
| env: &BuilderTxEnv<'_>, | ||
| db: impl DatabaseRef, | ||
| ) -> Result<Option<SimulatedBuilderTx>, BuilderTxError> { | ||
| match self.signer { | ||
| Some(signer) => { | ||
| let message: Vec<u8> = format!("Block Number: {}", env.block_number).into_bytes(); | ||
| let gas_used = self.estimate_builder_tx_gas(&message); | ||
| let signed_tx = self.signed_builder_tx(env, db, signer, gas_used, message)?; | ||
| let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( | ||
| signed_tx.encoded_2718().as_slice(), | ||
| ); | ||
| Ok(Some(SimulatedBuilderTx { | ||
| gas_used, | ||
| da_size, | ||
| signed_tx, | ||
| })) | ||
| } | ||
| None => Ok(None), | ||
| } | ||
| } | ||
|
|
||
| fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { | ||
| let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { | ||
| if byte == 0 { | ||
| (zeros + 1, nonzeros) | ||
| } else { | ||
| (zeros, nonzeros + 1) | ||
| } | ||
| }); | ||
|
|
||
| let zero_cost = zero_bytes * 4; | ||
| let nonzero_cost = nonzero_bytes * 16; | ||
|
|
||
| let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; | ||
| let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; | ||
|
|
||
| std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) | ||
| } | ||
|
|
||
| fn signed_builder_tx( | ||
| &self, | ||
| env: &BuilderTxEnv<'_>, | ||
| db: impl DatabaseRef, | ||
| signer: Signer, | ||
| gas_used: u64, | ||
| message: Vec<u8>, | ||
| ) -> Result<Recovered<OpTransactionSigned>, BuilderTxError> { | ||
| let nonce = get_nonce(db, signer.address)?; | ||
|
|
||
| let tx = OpTypedTransaction::Eip1559(TxEip1559 { | ||
| chain_id: env.chain_id(), | ||
| nonce, | ||
| gas_limit: gas_used, | ||
| max_fee_per_gas: env.base_fee.into(), | ||
| max_priority_fee_per_gas: 0, | ||
| to: TxKind::Call(Address::ZERO), | ||
| input: message.into(), | ||
| ..Default::default() | ||
| }); | ||
| let builder_tx = signer.sign_tx(tx).map_err(BuilderTxError::SigningError)?; | ||
|
|
||
| Ok(builder_tx) | ||
| } | ||
| } | ||
216 changes: 216 additions & 0 deletions
216
crates/op-rbuilder/src/builder/builder_tx/impls/flashblock_number.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| use alloy_eips::Encodable2718; | ||
| use alloy_evm::{Database, Evm}; | ||
| use alloy_op_evm::OpEvm; | ||
| 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::precompiles::PrecompilesMap; | ||
| use reth_provider::StateProvider; | ||
| use revm::{DatabaseRef, context_interface::Cfg as _, inspector::NoOpInspector}; | ||
| use std::sync::Arc; | ||
| use tracing::warn; | ||
|
|
||
| use crate::{ | ||
| builder::builder_tx::{ | ||
| BuilderTxEnv, BuilderTxError, BuilderTxProducer, SimulatedBuilderTx, SimulationState, | ||
| SimulationSuccessResult, get_nonce, sign_tx, simulate_call, | ||
| }, | ||
| primitives::reth::ExecutionInfo, | ||
| tx_signer::Signer, | ||
| }; | ||
|
|
||
| sol!( | ||
| // From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.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); | ||
|
|
||
| /// ----------------------------------------------------------------------- | ||
| /// Errors | ||
| /// ----------------------------------------------------------------------- | ||
| error NonBuilderAddress(address addr); | ||
| error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber); | ||
| } | ||
| ); | ||
|
|
||
| /// Builder transaction that increments the on-chain flashblock number contract. | ||
| /// Scheduled for non-first flashblocks via | ||
| /// [`crate::builder::builder_tx::ScheduledBuilderTx`]; the block claim tx is a | ||
| /// separate schedule entry for the first flashblock. | ||
| #[derive(Debug, Clone)] | ||
| pub(crate) struct FlashblockNumberBuilderTx { | ||
| pub signer: Signer, | ||
| pub flashblock_number_address: Address, | ||
| pub use_permit: bool, | ||
| /// TEE signer used only when `use_permit` is true to co-sign the permit. | ||
| pub tee_signer: Option<Signer>, | ||
| } | ||
|
|
||
| impl FlashblockNumberBuilderTx { | ||
| pub(crate) fn new( | ||
| signer: Signer, | ||
| flashblock_number_address: Address, | ||
| use_permit: bool, | ||
| tee_signer: Option<Signer>, | ||
| ) -> Self { | ||
| Self { | ||
| signer, | ||
| flashblock_number_address, | ||
| use_permit, | ||
| tee_signer, | ||
| } | ||
| } | ||
|
|
||
| fn signed_increment_flashblocks_tx( | ||
| &self, | ||
| env: &BuilderTxEnv<'_>, | ||
| evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>, | ||
| ) -> Result<SimulatedBuilderTx, BuilderTxError> { | ||
| let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}; | ||
| self.increment_flashblocks_tx(calldata, env, evm) | ||
| } | ||
|
|
||
| fn increment_flashblocks_permit_signature( | ||
| &self, | ||
| flashtestations_signer: &Signer, | ||
| current_flashblock_number: U256, | ||
| env: &BuilderTxEnv<'_>, | ||
| evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>, | ||
| ) -> Result<Signature, BuilderTxError> { | ||
| let struct_hash_calldata = IFlashblockNumber::computeStructHashCall { | ||
| currentFlashblockNumber: current_flashblock_number, | ||
| }; | ||
| let SimulationSuccessResult { output, .. } = | ||
| self.simulate_flashblocks_readonly_call(struct_hash_calldata, env, evm)?; | ||
| let typed_data_hash_calldata = | ||
| IFlashblockNumber::hashTypedDataV4Call { structHash: output }; | ||
| let SimulationSuccessResult { output, .. } = | ||
| self.simulate_flashblocks_readonly_call(typed_data_hash_calldata, env, evm)?; | ||
| let signature = flashtestations_signer.sign_message(output)?; | ||
| Ok(signature) | ||
| } | ||
|
|
||
| fn signed_increment_flashblocks_permit_tx( | ||
| &self, | ||
| flashtestations_signer: &Signer, | ||
| env: &BuilderTxEnv<'_>, | ||
| evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>, | ||
| ) -> Result<SimulatedBuilderTx, BuilderTxError> { | ||
| let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {}; | ||
| let SimulationSuccessResult { output, .. } = | ||
| self.simulate_flashblocks_readonly_call(current_flashblock_calldata, env, evm)?; | ||
| let signature = | ||
| self.increment_flashblocks_permit_signature(flashtestations_signer, output, env, evm)?; | ||
| let calldata = IFlashblockNumber::permitIncrementFlashblockNumberCall { | ||
| currentFlashblockNumber: output, | ||
| signature: signature.as_bytes().into(), | ||
| }; | ||
| self.increment_flashblocks_tx(calldata, env, evm) | ||
| } | ||
|
|
||
| fn increment_flashblocks_tx<T: SolCall + Clone>( | ||
| &self, | ||
| calldata: T, | ||
| env: &BuilderTxEnv<'_>, | ||
| evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>, | ||
| ) -> Result<SimulatedBuilderTx, BuilderTxError> { | ||
| let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( | ||
| calldata.clone(), | ||
| vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], | ||
| env, | ||
| evm, | ||
| )?; | ||
| let signed_tx = sign_tx( | ||
| self.flashblock_number_address, | ||
| self.signer, | ||
| gas_used, | ||
| calldata.abi_encode().into(), | ||
| env, | ||
| evm.db_mut(), | ||
| )?; | ||
| let da_size = | ||
| op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); | ||
| Ok(SimulatedBuilderTx { | ||
| signed_tx, | ||
| gas_used, | ||
| da_size, | ||
| }) | ||
| } | ||
|
|
||
| fn simulate_flashblocks_readonly_call<T: SolCall>( | ||
| &self, | ||
| calldata: T, | ||
| env: &BuilderTxEnv<'_>, | ||
| evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>, | ||
| ) -> Result<SimulationSuccessResult<T>, BuilderTxError> { | ||
| self.simulate_flashblocks_call(calldata, vec![], env, evm) | ||
| } | ||
|
|
||
| fn simulate_flashblocks_call<T: SolCall>( | ||
| &self, | ||
| calldata: T, | ||
| expected_logs: Vec<B256>, | ||
| env: &BuilderTxEnv<'_>, | ||
| evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>, | ||
| ) -> Result<SimulationSuccessResult<T>, BuilderTxError> { | ||
| let simulation_gas_limit = env.block_gas_limit.min(evm.cfg_env().tx_gas_limit_cap()); | ||
| let tx_req = OpTransactionRequest::default() | ||
| .gas_limit(simulation_gas_limit) | ||
| .max_fee_per_gas(env.base_fee.into()) | ||
| .to(self.flashblock_number_address) | ||
| .from(self.signer.address) | ||
| .nonce(get_nonce(evm.db(), self.signer.address)?) | ||
| .input(TransactionInput::new(calldata.abi_encode().into())); | ||
| simulate_call::<T, IFlashblockNumber::IFlashblockNumberErrors>(tx_req, expected_logs, evm) | ||
| } | ||
| } | ||
|
|
||
| impl BuilderTxProducer for FlashblockNumberBuilderTx { | ||
| fn simulate_builder_txs( | ||
| &self, | ||
| _state_provider: Arc<dyn StateProvider + Send>, | ||
| _info: &ExecutionInfo, | ||
| env: &BuilderTxEnv<'_>, | ||
| sim_state: &mut SimulationState, | ||
| ) -> Result<Vec<SimulatedBuilderTx>, BuilderTxError> { | ||
| let mut evm = env.evm_factory.evm(&mut *sim_state); | ||
| evm.modify_cfg(|cfg| { | ||
| cfg.disable_balance_check = true; | ||
| cfg.disable_block_gas_limit = true; | ||
| }); | ||
|
|
||
| let num_tx = if let Some(tee_signer) = &self.tee_signer | ||
| && self.use_permit | ||
| { | ||
| self.signed_increment_flashblocks_permit_tx(tee_signer, env, &mut evm) | ||
| } else { | ||
| self.signed_increment_flashblocks_tx(env, &mut evm) | ||
| }; | ||
|
|
||
| let tx = num_tx.inspect_err(|e| { | ||
| warn!( | ||
| target: "builder_tx", | ||
| error = %e, | ||
| "flashblocks number contract tx simulation failed" | ||
| ) | ||
| })?; | ||
|
Comment on lines
+206
to
+212
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider a |
||
|
|
||
| Ok(vec![tx]) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| mod claim; | ||
| mod flashblock_number; | ||
|
|
||
| pub(crate) use claim::ClaimBuilderTx; | ||
| pub(crate) use flashblock_number::FlashblockNumberBuilderTx; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| mod env; | ||
| mod impls; | ||
| mod producer; | ||
| mod schedule; | ||
| mod sim; | ||
|
|
||
| pub use env::BuilderTxEnv; | ||
| pub use producer::{BuilderTxError, BuilderTxProducer, SimulatedBuilderTx}; | ||
| pub use schedule::{BuilderTxPosition, BuilderTxSchedule, ScheduledBuilderTx}; | ||
| pub use sim::{SimulationState, SimulationSuccessResult, get_nonce, sign_tx, simulate_call}; | ||
|
|
||
| pub(crate) use schedule::reserve_builder_tx_budget; | ||
|
|
||
| pub(super) use impls::{ClaimBuilderTx, FlashblockNumberBuilderTx}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems more like min gas limit than estimate tx gas.
Also consider: