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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ cargo run -p op-rbuilder --bin op-rbuilder -- node \
--flashblocks.addr 127.0.0.1 # address to bind the ws that provides flashblocks
```

#### Flashblocks Number Contract

To enable builder tranctions to the [flashblocks number contract](https://github.com/Uniswap/flashblocks_number_contract) for contracts to integrate with flashblocks onchain, specify the address in the CLI args:

```bash
cargo run -p op-rbuilder --bin op-rbuilder -- node \
--chain /path/to/chain-config.json \
--http \
--authrpc.port 9551 \
--authrpc.jwtsecret /path/to/jwt.hex \
--flashblocks.enabled \
--flashblocks.number-contract-address 0xFlashblocksNumberAddress
```

This will increment the flashblock number before the start of every flashblock and replace the builder tx at the end of the block.

### Flashtestations

To run op-rbuilder with flashtestations:
Expand Down
11 changes: 11 additions & 0 deletions crates/op-rbuilder/src/args/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
flashtestations::args::FlashtestationsArgs, gas_limiter::args::GasLimiterArgs,
tx_signer::Signer,
};
use alloy_primitives::Address;
use anyhow::{Result, anyhow};
use clap::Parser;
use reth_optimism_cli::commands::Commands;
Expand Down Expand Up @@ -147,6 +148,16 @@ pub struct FlashblocksArgs {
env = "FLASHBLOCK_LEEWAY_TIME"
)]
pub flashblocks_leeway_time: u64,

/// Flashblocks number contract address
///
/// This is the address of the contract that will be used to increment the flashblock number.
/// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx.
#[arg(
long = "flashblocks.number-contract-address",
env = "FLASHBLOCK_NUMBER_CONTRACT_ADDRESS"
)]
pub flashblocks_number_contract_address: Option<Address>,
}

impl Default for FlashblocksArgs {
Expand Down
108 changes: 105 additions & 3 deletions crates/op-rbuilder/src/builders/builder_tx.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use alloy_consensus::TxEip1559;
use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN};
use alloy_evm::Database;
use alloy_primitives::{
Address,
Address, TxKind,
map::foldhash::{HashSet, HashSetExt},
};
use core::fmt::Debug;
use op_alloy_consensus::OpTypedTransaction;
use op_revm::OpTransactionError;
use reth_evm::{ConfigureEvm, Evm, eth::receipt_builder::ReceiptBuilderCtx};
use reth_node_api::PayloadBuilderError;
Expand All @@ -19,7 +22,9 @@ use revm::{
};
use tracing::warn;

use crate::{builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo};
use crate::{
builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer,
};

#[derive(Debug, Clone)]
pub struct BuilderTransactionCtx {
Expand Down Expand Up @@ -148,7 +153,7 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
let state = StateProviderDatabase::new(state_provider.clone());
let mut simulation_state = State::builder()
.with_database(state)
.with_bundle_prestate(db.bundle_state.clone())
.with_cached_prestate(db.cache.clone())
.with_bundle_update()
.build();
let mut evm = ctx
Expand All @@ -167,3 +172,100 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
Ok(simulation_state)
}
}

#[derive(Debug, Clone)]
pub struct BuilderTxBase {
pub signer: Option<Signer>,
}

impl BuilderTxBase {
pub fn new(signer: Option<Signer>) -> Self {
Self { signer }
}

pub fn simulate_builder_tx<ExtraCtx: Debug + Default>(
&self,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
db: &mut State<impl Database>,
) -> Result<Option<BuilderTransactionCtx>, BuilderTransactionError> {
match self.signer {
Some(signer) => {
let message: Vec<u8> = format!("Block Number: {}", ctx.block_number()).into_bytes();
let gas_used = self.estimate_builder_tx_gas(&message);
let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?;
let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes(
signed_tx.encoded_2718().as_slice(),
);
Ok(Some(BuilderTransactionCtx {
gas_used,
da_size,
signed_tx,
}))
}
None => Ok(None),
}
}

fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 {
// Count zero and non-zero bytes
let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| {
if byte == 0 {
(zeros + 1, nonzeros)
} else {
(zeros, nonzeros + 1)
}
});

// Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte)
let zero_cost = zero_bytes * 4;
let nonzero_cost = nonzero_bytes * 16;

// Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623
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<ExtraCtx: Debug + Default>(
&self,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
db: &mut State<impl Database>,
signer: Signer,
gas_used: u64,
message: Vec<u8>,
) -> Result<Recovered<OpTransactionSigned>, BuilderTransactionError> {
let nonce = db
.load_cache_account(signer.address)
.map(|acc| acc.account_info().unwrap_or_default().nonce)
.map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?;

// Create the EIP-1559 transaction
let tx = OpTypedTransaction::Eip1559(TxEip1559 {
chain_id: ctx.chain_id(),
nonce,
gas_limit: gas_used,
max_fee_per_gas: ctx.base_fee().into(),
max_priority_fee_per_gas: 0,
to: TxKind::Call(Address::ZERO),
// Include the message as part of the transaction data
input: message.into(),
..Default::default()
});
// Sign the transaction
let builder_tx = signer
.sign_tx(tx)
.map_err(BuilderTransactionError::SigningError)?;

Ok(builder_tx)
}
}

pub fn get_nonce(
db: &mut State<impl Database>,
address: Address,
) -> Result<u64, BuilderTransactionError> {
db.load_cache_account(address)
.map(|acc| acc.account_info().unwrap_or_default().nonce)
.map_err(|_| BuilderTransactionError::AccountLoadFailed(address))
}
Loading
Loading