Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
695 changes: 0 additions & 695 deletions crates/op-rbuilder/src/builder/builder_tx.rs

This file was deleted.

21 changes: 21 additions & 0 deletions crates/op-rbuilder/src/builder/builder_tx/env.rs
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 crates/op-rbuilder/src/builder/builder_tx/impls/claim.rs
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)
}
Comment on lines +67 to +83

Copy link
Copy Markdown
Member

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:

let intrinsic_gas = 21_000
    + input
        .iter()
        .map(|byte| if *byte == 0 { 4 } else { 16 })
        .sum::<u64>();

let floor_gas = eip7623::transaction_floor_cost(eip7623::tokens_in_calldata(input));

intrinsic_gas.max(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 crates/op-rbuilder/src/builder/builder_tx/impls/flashblock_number.rs
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider a flashblock_number_tx_failure_total metric


Ok(vec![tx])
}
}
5 changes: 5 additions & 0 deletions crates/op-rbuilder/src/builder/builder_tx/impls/mod.rs
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;
14 changes: 14 additions & 0 deletions crates/op-rbuilder/src/builder/builder_tx/mod.rs
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};
Loading
Loading