Skip to content
Merged
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 @@ -155,6 +156,16 @@ pub struct FlashblocksArgs {
env = "FLASHBLOCKS_CALCULATE_STATE_ROOT"
)]
pub flashblocks_calculate_state_root: bool,

/// 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
147 changes: 132 additions & 15 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,13 +22,15 @@ 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 {
pub gas_used: u64,
pub da_size: u64,
pub signed_tx: Recovered<OpTransactionSigned>,
pub signed_tx: Option<Recovered<OpTransactionSigned>>,
}

/// Possible error variants during construction of builder txs.
Expand Down Expand Up @@ -75,6 +80,7 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
info: &mut ExecutionInfo<Extra>,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
db: &mut State<impl Database>,
top_of_block: bool,
) -> Result<Vec<BuilderTransactionCtx>, BuilderTransactionError>;

fn add_builder_txs<Extra: Debug + Default>(
Expand All @@ -83,6 +89,7 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
info: &mut ExecutionInfo<Extra>,
builder_ctx: &OpPayloadBuilderCtx<ExtraCtx>,
db: &mut State<impl Database>,
top_of_block: bool,
) -> Result<Vec<BuilderTransactionCtx>, BuilderTransactionError> {
{
let mut evm = builder_ctx
Expand All @@ -91,21 +98,30 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {

let mut invalid: HashSet<Address> = HashSet::new();

let builder_txs =
self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?;
let builder_txs = self.simulate_builder_txs(
state_provider,
info,
builder_ctx,
evm.db_mut(),
top_of_block,
)?;
for builder_tx in builder_txs.iter() {
if invalid.contains(&builder_tx.signed_tx.signer()) {
warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted");
let signed_tx = match builder_tx.signed_tx.clone() {
Some(tx) => tx,
None => continue,
};
if invalid.contains(&signed_tx.signer()) {
warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted");
continue;
}

let ResultAndState { result, state } = evm
.transact(&builder_tx.signed_tx)
.transact(&signed_tx)
.map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?;

if !result.is_success() {
warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted");
invalid.insert(builder_tx.signed_tx.signer());
warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder tx reverted");
invalid.insert(signed_tx.signer());
continue;
}

Expand All @@ -114,7 +130,7 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
info.cumulative_gas_used += gas_used;

let ctx = ReceiptBuilderCtx {
tx: builder_tx.signed_tx.inner(),
tx: signed_tx.inner(),
evm: &evm,
result,
state: &state,
Expand All @@ -126,9 +142,9 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
evm.db_mut().commit(state);

// Append sender and transaction to the respective lists
info.executed_senders.push(builder_tx.signed_tx.signer());
info.executed_senders.push(signed_tx.signer());
info.executed_transactions
.push(builder_tx.signed_tx.clone().into_inner());
.push(signed_tx.clone().into_inner());
}

// Release the db reference by dropping evm
Expand All @@ -148,16 +164,20 @@ 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
.evm_config
.evm_with_env(&mut simulation_state, ctx.evm_env.clone());

for builder_tx in builder_txs {
let signed_tx = match builder_tx.signed_tx.clone() {
Some(tx) => tx,
None => continue,
};
let ResultAndState { state, .. } = evm
.transact(&builder_tx.signed_tx)
.transact(&signed_tx)
.map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?;

evm.db_mut().commit(state);
Expand All @@ -167,3 +187,100 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
Ok(simulation_state)
}
}

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

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

pub(super) 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: Some(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(super) 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