Skip to content

Commit 499aea1

Browse files
committed
Add flashtestations builder transaction trait to payload builder
1 parent 31de8f9 commit 499aea1

File tree

13 files changed

+844
-450
lines changed

13 files changed

+844
-450
lines changed

Cargo.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/op-rbuilder/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ hex = "0.4"
121121
ureq = "2.10"
122122

123123
rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" }
124-
tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", optional = true }
125-
dcap-rs = { git = "https://github.com/automata-network/dcap-rs.git", optional = true }
124+
tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", rev = "ce02e0009c20bb4fc37c4c95701925fc3f139b41", optional = true }
126125

127126
dashmap = { version = "6.1", optional = true }
128127
nanoid = { version = "0.4", optional = true }
@@ -188,7 +187,7 @@ testing = [
188187

189188
interop = []
190189

191-
flashtestations = ["dcap-rs", "tdx"]
190+
flashtestations = ["tdx"]
192191

193192
telemetry = ["reth-tracing-otlp", "opentelemetry"]
194193

Lines changed: 211 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,133 @@
1+
use alloy_consensus::TxEip1559;
2+
use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718};
3+
use alloy_primitives::{
4+
map::foldhash::{HashSet, HashSetExt},
5+
Address, TxKind,
6+
};
7+
use core::fmt::Debug;
8+
use op_alloy_consensus::OpTypedTransaction;
9+
use reth_evm::{eth::receipt_builder::ReceiptBuilderCtx, ConfigureEvm, Evm};
10+
use reth_node_api::PayloadBuilderError;
111
use reth_optimism_primitives::OpTransactionSigned;
212
use reth_primitives::Recovered;
13+
use reth_provider::ProviderError;
14+
use reth_revm::State;
15+
use revm::{context::result::ResultAndState, Database, DatabaseCommit};
16+
use tracing::{debug, warn};
317

4-
use crate::tx_signer::Signer;
18+
use crate::{
19+
builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer,
20+
};
521

6-
pub trait BuilderTx {
7-
fn estimated_builder_tx_gas(&self) -> u64;
8-
fn estimated_builder_tx_da_size(&self) -> Option<u64>;
9-
fn signed_builder_tx(&self) -> Result<Recovered<OpTransactionSigned>, secp256k1::Error>;
22+
pub struct BuilderTransactionCtx {
23+
pub gas_used: u64,
24+
pub da_size: u64,
25+
pub signed_tx: Recovered<OpTransactionSigned>,
26+
}
27+
28+
/// Possible error variants during construction of builder txs.
29+
#[derive(Debug, thiserror::Error)]
30+
pub enum BuilderTransactionError {
31+
/// Thrown when builder account load fails to get builder nonce
32+
#[error("failed to load account {0}")]
33+
AccountLoadFailed(Address),
34+
/// Thrown when signature signing fails
35+
#[error("failed to sign transaction: {0}")]
36+
SigningError(secp256k1::Error),
37+
/// Unrecoverable error during evm execution.
38+
#[error("evm execution error {0}")]
39+
EvmExecutionError(Box<dyn core::error::Error + Send + Sync>),
40+
/// Any other builder transaction errors.
41+
#[error(transparent)]
42+
Other(Box<dyn core::error::Error + Send + Sync>),
43+
}
44+
45+
impl From<secp256k1::Error> for BuilderTransactionError {
46+
fn from(error: secp256k1::Error) -> Self {
47+
BuilderTransactionError::SigningError(error)
48+
}
49+
}
50+
51+
impl From<BuilderTransactionError> for PayloadBuilderError {
52+
fn from(error: BuilderTransactionError) -> Self {
53+
match error {
54+
BuilderTransactionError::EvmExecutionError(e) => {
55+
PayloadBuilderError::EvmExecutionError(e)
56+
}
57+
_ => PayloadBuilderError::Other(Box::new(error)),
58+
}
59+
}
60+
}
61+
62+
pub trait BuilderTransactions {
63+
fn simulate_builder_txs<DB, Extra: Debug + Default>(
64+
&self,
65+
info: &mut ExecutionInfo<Extra>,
66+
ctx: &OpPayloadBuilderCtx,
67+
db: &mut State<DB>,
68+
) -> Result<Vec<BuilderTransactionCtx>, BuilderTransactionError>
69+
where
70+
DB: Database<Error = ProviderError>;
71+
72+
fn add_builder_txs<DB, Extra: Debug + Default>(
73+
&self,
74+
info: &mut ExecutionInfo<Extra>,
75+
builder_ctx: &OpPayloadBuilderCtx,
76+
db: &mut State<DB>,
77+
) -> Result<(), BuilderTransactionError>
78+
where
79+
DB: Database<Error = ProviderError>,
80+
{
81+
{
82+
let mut evm = builder_ctx
83+
.evm_config
84+
.evm_with_env(&mut *db, builder_ctx.evm_env.clone());
85+
let mut invalid: HashSet<Address> = HashSet::new();
86+
let builder_txs = self.simulate_builder_txs(info, builder_ctx, evm.db_mut())?;
87+
for builder_tx in builder_txs {
88+
if invalid.contains(&builder_tx.signed_tx.signer()) {
89+
debug!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted");
90+
continue;
91+
}
92+
93+
let ResultAndState { result, state } = evm
94+
.transact(&builder_tx.signed_tx)
95+
.map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?;
96+
97+
if !result.is_success() {
98+
warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted");
99+
invalid.insert(builder_tx.signed_tx.signer());
100+
continue;
101+
}
102+
103+
// Add gas used by the transaction to cumulative gas used, before creating the receipt
104+
let gas_used = result.gas_used();
105+
info.cumulative_gas_used += gas_used;
106+
107+
let ctx = ReceiptBuilderCtx {
108+
tx: builder_tx.signed_tx.inner(),
109+
evm: &evm,
110+
result,
111+
state: &state,
112+
cumulative_gas_used: info.cumulative_gas_used,
113+
};
114+
info.receipts.push(builder_ctx.build_receipt(ctx, None));
115+
116+
// Commit changes
117+
evm.db_mut().commit(state);
118+
119+
// Append sender and transaction to the respective lists
120+
info.executed_senders.push(builder_tx.signed_tx.signer());
121+
info.executed_transactions
122+
.push(builder_tx.signed_tx.into_inner());
123+
}
124+
125+
// Release the db reference by dropping evm
126+
drop(evm);
127+
128+
Ok(())
129+
}
130+
}
10131
}
11132

12133
// Scaffolding for how to construct the end of block builder transaction
@@ -17,16 +138,94 @@ pub struct StandardBuilderTx {
17138
pub signer: Option<Signer>,
18139
}
19140

20-
impl BuilderTx for StandardBuilderTx {
21-
fn estimated_builder_tx_gas(&self) -> u64 {
22-
todo!()
141+
impl StandardBuilderTx {
142+
pub fn new(signer: Option<Signer>) -> Self {
143+
Self { signer }
144+
}
145+
146+
fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 {
147+
// Count zero and non-zero bytes
148+
let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| {
149+
if byte == 0 {
150+
(zeros + 1, nonzeros)
151+
} else {
152+
(zeros, nonzeros + 1)
153+
}
154+
});
155+
156+
// Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte)
157+
let zero_cost = zero_bytes * 4;
158+
let nonzero_cost = nonzero_bytes * 16;
159+
160+
// Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623
161+
let tokens_in_calldata = zero_bytes + nonzero_bytes * 4;
162+
let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN;
163+
164+
std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas)
23165
}
24166

25-
fn estimated_builder_tx_da_size(&self) -> Option<u64> {
26-
todo!()
167+
fn signed_builder_tx<DB>(
168+
&self,
169+
ctx: &OpPayloadBuilderCtx,
170+
db: &mut State<DB>,
171+
signer: Signer,
172+
gas_used: u64,
173+
message: Vec<u8>,
174+
) -> Result<Recovered<OpTransactionSigned>, BuilderTransactionError>
175+
where
176+
DB: Database<Error = ProviderError>,
177+
{
178+
let nonce = db
179+
.load_cache_account(signer.address)
180+
.map(|acc| acc.account_info().unwrap_or_default().nonce)
181+
.map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?;
182+
183+
// Create the EIP-1559 transaction
184+
let tx = OpTypedTransaction::Eip1559(TxEip1559 {
185+
chain_id: ctx.chain_id(),
186+
nonce,
187+
gas_limit: gas_used,
188+
max_fee_per_gas: ctx.base_fee().into(),
189+
max_priority_fee_per_gas: 0,
190+
to: TxKind::Call(Address::ZERO),
191+
// Include the message as part of the transaction data
192+
input: message.into(),
193+
..Default::default()
194+
});
195+
// Sign the transaction
196+
let builder_tx = signer
197+
.sign_tx(tx)
198+
.map_err(BuilderTransactionError::SigningError)?;
199+
200+
Ok(builder_tx)
27201
}
202+
}
28203

29-
fn signed_builder_tx(&self) -> Result<Recovered<OpTransactionSigned>, secp256k1::Error> {
30-
todo!()
204+
impl BuilderTransactions for StandardBuilderTx {
205+
fn simulate_builder_txs<DB, Extra: Debug + Default>(
206+
&self,
207+
_info: &mut ExecutionInfo<Extra>,
208+
ctx: &OpPayloadBuilderCtx,
209+
db: &mut State<DB>,
210+
) -> Result<Vec<BuilderTransactionCtx>, BuilderTransactionError>
211+
where
212+
DB: Database<Error = ProviderError>,
213+
{
214+
match self.signer {
215+
Some(signer) => {
216+
let message: Vec<u8> = format!("Block Number: {}", ctx.block_number()).into_bytes();
217+
let gas_used = self.estimate_builder_tx_gas(&message);
218+
let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?;
219+
let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes(
220+
signed_tx.encoded_2718().as_slice(),
221+
);
222+
Ok(vec![BuilderTransactionCtx {
223+
gas_used,
224+
da_size,
225+
signed_tx,
226+
}])
227+
}
228+
None => Ok(vec![]),
229+
}
31230
}
32231
}

0 commit comments

Comments
 (0)