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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ This will increment the flashblock number before the start of every flashblock a
To run op-rbuilder with flashtestations:

```bash
cargo run -p op-rbuilder --bin op-rbuilder --features=flashtestations -- node \
cargo run -p op-rbuilder --bin op-rbuilder -- node \
--chain /path/to/chain-config.json \
--http \
--authrpc.port 9551 \
Expand All @@ -73,7 +73,7 @@ cargo run -p op-rbuilder --bin op-rbuilder --features=flashtestations -- node \
--flashtestations.funding-amount 0.01 \ # amount in ETH to fund the TEE generated key
--flashtestations.funding-key secret-key \ # funding key for the TEE key
--flashtestations.registry-address 0xFlashtestationsRegistryAddress \
flashtestations.builder-policy-address 0xBuilderPolicyAddress
--flashtestations.builder-policy-address 0xBuilderPolicyAddress
```

Note that `--rollup.builder-secret-key` must be set and funded in order for the flashtestations key to be funded and submit the attestation on-chain.
Expand Down
3 changes: 3 additions & 0 deletions crates/op-rbuilder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ testing = [
"ctor",
"macros",
"rlimit",
"hyper",
"hyper-util",
"http-body-util",
]

interop = []
Expand Down
168 changes: 146 additions & 22 deletions crates/op-rbuilder/src/builders/builder_tx.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
use alloy_consensus::TxEip1559;
use alloy_consensus::{Transaction, TxEip1559};
use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN};
use alloy_evm::Database;
use alloy_op_evm::OpEvm;
use alloy_primitives::{
Address, B256, Log, TxKind, U256,
map::foldhash::{HashSet, HashSetExt},
Address, B256, Bytes, TxKind, U256,
map::foldhash::{HashMap, 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 op_revm::{OpHaltReason, OpTransactionError};
use reth_evm::{
ConfigureEvm, Evm, EvmError, eth::receipt_builder::ReceiptBuilderCtx,
precompiles::PrecompilesMap,
};
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};
use revm::{
DatabaseCommit,
context::result::{EVMError, ResultAndState},
context::result::{EVMError, ExecutionResult, ResultAndState},
inspector::NoOpInspector,
state::Account,
};
use tracing::warn;

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

#[derive(Debug, Default)]
pub struct SimulationSuccessResult {
pub gas_used: u64,
pub output: Bytes,
pub state_changes: HashMap<Address, Account>,
}

#[derive(Debug, Clone)]
pub struct BuilderTransactionCtx {
pub gas_used: u64,
Expand All @@ -46,6 +59,14 @@ impl BuilderTransactionCtx {
}
}

#[derive(Debug, thiserror::Error)]
pub enum InvalidContractDataError {
#[error("did not find expected log {0:?} in emitted logs")]
InvalidLogs(B256),
#[error("could not decode output from contract call")]
OutputAbiDecodeError,
}

/// Possible error variants during construction of builder txs.
#[derive(Debug, thiserror::Error)]
pub enum BuilderTransactionError {
Expand All @@ -55,6 +76,15 @@ pub enum BuilderTransactionError {
/// Signature signing fails
#[error("failed to sign transaction: {0}")]
SigningError(secp256k1::Error),
/// Invalid contract errors indicating the contract is incorrect
#[error("contract {0} may be incorrect, invalid contract data: {1}")]
InvalidContract(Address, InvalidContractDataError),
/// Transaction halted execution
#[error("transaction halted {0:?}")]
TransactionHalted(OpHaltReason),
/// Transaction reverted
#[error("transaction reverted {0}")]
TransactionReverted(Box<dyn core::error::Error + Send + Sync>),
/// Invalid tx errors during evm execution.
#[error("invalid transaction error {0}")]
InvalidTransactionError(Box<dyn core::error::Error + Send + Sync>),
Expand Down Expand Up @@ -89,16 +119,27 @@ impl From<BuilderTransactionError> for PayloadBuilderError {
}
}

pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
fn simulate_builder_txs<Extra: Debug + Default>(
impl BuilderTransactionError {
pub fn other(error: impl core::error::Error + Send + Sync + 'static) -> Self {
BuilderTransactionError::Other(Box::new(error))
}

pub fn msg(msg: impl core::fmt::Display) -> Self {
Self::Other(msg.to_string().into())
}
}

pub trait BuilderTransactions<ExtraCtx: Debug + Default = (), Extra: Debug + Default = ()> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

it starts to look scary :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we can move Extra fields to ExtraCtx :)

fn simulate_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
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>(
fn add_builder_txs(
&self,
state_provider: impl StateProvider + Clone,
info: &mut ExecutionInfo<Extra>,
Expand All @@ -113,8 +154,13 @@ 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 builder_tx.is_top_of_block != top_of_block {
// don't commit tx if the buidler tx is not being added in the intended
Expand All @@ -131,7 +177,7 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
.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");
warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), result = ?result, "builder tx reverted");
invalid.insert(builder_tx.signed_tx.signer());
continue;
}
Expand Down Expand Up @@ -165,7 +211,7 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
}
}

fn simulate_builder_txs_state<Extra: Debug + Default>(
fn simulate_builder_txs_state(
&self,
state_provider: impl StateProvider + Clone,
builder_txs: Vec<&BuilderTransactionCtx>,
Expand All @@ -192,19 +238,101 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {

Ok(simulation_state)
}

fn sign_tx(
&self,
to: Address,
from: Signer,
gas_used: Option<u64>,
calldata: Bytes,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
db: &mut State<impl Database>,
) -> Result<Recovered<OpTransactionSigned>, BuilderTransactionError> {
let nonce = get_nonce(db, from.address)?;
// Create the EIP-1559 transaction
let tx = OpTypedTransaction::Eip1559(TxEip1559 {
chain_id: ctx.chain_id(),
nonce,
// Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer
gas_limit: gas_used
.map(|gas| gas * 64 / 63)
.unwrap_or(ctx.block_gas_limit()),
max_fee_per_gas: ctx.base_fee().into(),
to: TxKind::Call(to),
input: calldata,
..Default::default()
});
Ok(from.sign_tx(tx)?)
}

fn simulate_call(
&self,
signed_tx: Recovered<OpTransactionSigned>,
expected_topic: Option<B256>,
revert_handler: impl FnOnce(Bytes) -> BuilderTransactionError,
evm: &mut OpEvm<
&mut State<StateProviderDatabase<impl StateProvider>>,
NoOpInspector,
PrecompilesMap,
>,
) -> Result<SimulationSuccessResult, BuilderTransactionError> {
let ResultAndState { result, state } = match evm.transact(&signed_tx) {
Ok(res) => res,
Err(err) => {
if err.is_invalid_tx_err() {
return Err(BuilderTransactionError::InvalidTransactionError(Box::new(
err,
)));
} else {
return Err(BuilderTransactionError::EvmExecutionError(Box::new(err)));
}
}
};

match result {
ExecutionResult::Success {
logs,
gas_used,
output,
..
} => {
if let Some(topic) = expected_topic
&& !logs.iter().any(|log| log.topics().first() == Some(&topic))
{
return Err(BuilderTransactionError::InvalidContract(
signed_tx.to().unwrap_or_default(),
InvalidContractDataError::InvalidLogs(topic),
));
}
Ok(SimulationSuccessResult {
gas_used,
output: output.into_data(),
state_changes: state,
})
}
ExecutionResult::Revert { output, .. } => Err(revert_handler(output)),
ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other(
BuilderTransactionError::TransactionHalted(reason),
)),
}
}
}

#[derive(Debug, Clone)]
pub(super) struct BuilderTxBase {
pub(super) struct BuilderTxBase<ExtraCtx = ()> {
pub signer: Option<Signer>,
_marker: std::marker::PhantomData<ExtraCtx>,
}

impl BuilderTxBase {
impl<ExtraCtx: Debug + Default> BuilderTxBase<ExtraCtx> {
pub(super) fn new(signer: Option<Signer>) -> Self {
Self { signer }
Self {
signer,
_marker: std::marker::PhantomData,
}
}

pub(super) fn simulate_builder_tx<ExtraCtx: Debug + Default>(
pub(super) fn simulate_builder_tx(
&self,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
db: &mut State<impl Database>,
Expand Down Expand Up @@ -249,7 +377,7 @@ impl BuilderTxBase {
std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas)
}

fn signed_builder_tx<ExtraCtx: Debug + Default>(
fn signed_builder_tx(
&self,
ctx: &OpPayloadBuilderCtx<ExtraCtx>,
db: &mut State<impl Database>,
Expand Down Expand Up @@ -300,7 +428,3 @@ pub fn get_balance(
.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))
}
Loading
Loading