Skip to content

Commit 1da7c00

Browse files
authored
Add flashblocks number integration tests (#277)
* Add flashblocks number integration tests * comments * comments
1 parent 445c108 commit 1da7c00

File tree

20 files changed

+2383
-213
lines changed

20 files changed

+2383
-213
lines changed

crates/op-rbuilder/src/builders/builder_tx.rs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use alloy_consensus::TxEip1559;
22
use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN};
33
use alloy_evm::Database;
44
use alloy_primitives::{
5-
Address, TxKind,
5+
Address, B256, Log, TxKind,
66
map::foldhash::{HashSet, HashSetExt},
77
};
88
use core::fmt::Debug;
@@ -30,7 +30,22 @@ use crate::{
3030
pub struct BuilderTransactionCtx {
3131
pub gas_used: u64,
3232
pub da_size: u64,
33-
pub signed_tx: Option<Recovered<OpTransactionSigned>>,
33+
pub signed_tx: Recovered<OpTransactionSigned>,
34+
// whether the transaction should be a top of block or
35+
// bottom of block transaction
36+
pub is_top_of_block: bool,
37+
}
38+
39+
impl BuilderTransactionCtx {
40+
pub fn set_top_of_block(mut self) -> Self {
41+
self.is_top_of_block = true;
42+
self
43+
}
44+
45+
pub fn set_bottom_of_block(mut self) -> Self {
46+
self.is_top_of_block = false;
47+
self
48+
}
3449
}
3550

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

86100
fn add_builder_txs<Extra: Debug + Default>(
@@ -98,30 +112,26 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
98112

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

101-
let builder_txs = self.simulate_builder_txs(
102-
state_provider,
103-
info,
104-
builder_ctx,
105-
evm.db_mut(),
106-
top_of_block,
107-
)?;
115+
let builder_txs =
116+
self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?;
108117
for builder_tx in builder_txs.iter() {
109-
let signed_tx = match builder_tx.signed_tx.clone() {
110-
Some(tx) => tx,
111-
None => continue,
112-
};
113-
if invalid.contains(&signed_tx.signer()) {
114-
warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted");
118+
if builder_tx.is_top_of_block != top_of_block {
119+
// don't commit tx if the buidler tx is not being added in the intended
120+
// position in the block
121+
continue;
122+
}
123+
if invalid.contains(&builder_tx.signed_tx.signer()) {
124+
warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted");
115125
continue;
116126
}
117127

118128
let ResultAndState { result, state } = evm
119-
.transact(&signed_tx)
129+
.transact(&builder_tx.signed_tx)
120130
.map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?;
121131

122132
if !result.is_success() {
123-
warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder tx reverted");
124-
invalid.insert(signed_tx.signer());
133+
warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted");
134+
invalid.insert(builder_tx.signed_tx.signer());
125135
continue;
126136
}
127137

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

132142
let ctx = ReceiptBuilderCtx {
133-
tx: signed_tx.inner(),
143+
tx: builder_tx.signed_tx.inner(),
134144
evm: &evm,
135145
result,
136146
state: &state,
@@ -142,9 +152,9 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
142152
evm.db_mut().commit(state);
143153

144154
// Append sender and transaction to the respective lists
145-
info.executed_senders.push(signed_tx.signer());
155+
info.executed_senders.push(builder_tx.signed_tx.signer());
146156
info.executed_transactions
147-
.push(signed_tx.clone().into_inner());
157+
.push(builder_tx.signed_tx.clone().into_inner());
148158
}
149159

150160
// Release the db reference by dropping evm
@@ -172,12 +182,8 @@ pub trait BuilderTransactions<ExtraCtx: Debug + Default = ()>: Debug {
172182
.evm_with_env(&mut simulation_state, ctx.evm_env.clone());
173183

174184
for builder_tx in builder_txs {
175-
let signed_tx = match builder_tx.signed_tx.clone() {
176-
Some(tx) => tx,
177-
None => continue,
178-
};
179185
let ResultAndState { state, .. } = evm
180-
.transact(&signed_tx)
186+
.transact(&builder_tx.signed_tx)
181187
.map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?;
182188

183189
evm.db_mut().commit(state);
@@ -214,7 +220,8 @@ impl BuilderTxBase {
214220
Ok(Some(BuilderTransactionCtx {
215221
gas_used,
216222
da_size,
217-
signed_tx: Some(signed_tx),
223+
signed_tx,
224+
is_top_of_block: false,
218225
}))
219226
}
220227
None => Ok(None),
@@ -276,11 +283,15 @@ impl BuilderTxBase {
276283
}
277284
}
278285

279-
pub(super) fn get_nonce(
286+
pub(crate) fn get_nonce(
280287
db: &mut State<impl Database>,
281288
address: Address,
282289
) -> Result<u64, BuilderTransactionError> {
283290
db.load_cache_account(address)
284291
.map(|acc| acc.account_info().unwrap_or_default().nonce)
285292
.map_err(|_| BuilderTransactionError::AccountLoadFailed(address))
286293
}
294+
295+
pub(crate) fn log_exists(logs: &[Log], topic: &B256) -> bool {
296+
logs.iter().any(|log| log.topics().first() == Some(topic))
297+
}

crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs

Lines changed: 53 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use alloy_consensus::TxEip1559;
22
use alloy_eips::Encodable2718;
33
use alloy_evm::{Database, Evm};
44
use alloy_op_evm::OpEvm;
5-
use alloy_primitives::{Address, Bytes, TxKind, U256};
6-
use alloy_sol_types::{SolCall, SolError, sol};
5+
use alloy_primitives::{Address, B256, TxKind};
6+
use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, sol};
77
use core::fmt::Debug;
88
use op_alloy_consensus::OpTypedTransaction;
99
use op_revm::OpHaltReason;
@@ -21,7 +21,7 @@ use tracing::warn;
2121
use crate::{
2222
builders::{
2323
BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions,
24-
builder_tx::{BuilderTxBase, get_nonce},
24+
builder_tx::{BuilderTxBase, get_nonce, log_exists},
2525
context::OpPayloadBuilderCtx,
2626
flashblocks::payload::FlashblocksExtraCtx,
2727
},
@@ -33,62 +33,34 @@ use crate::{
3333
sol!(
3434
// From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.sol
3535
#[sol(rpc, abi)]
36+
#[derive(Debug)]
3637
interface IFlashblockNumber {
3738
function incrementFlashblockNumber() external;
38-
}
3939

40-
// @notice Emitted when flashblock index is incremented
41-
// @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block)
42-
event FlashblockIncremented(uint256 newFlashblockIndex);
40+
// @notice Emitted when flashblock index is incremented
41+
// @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block)
42+
event FlashblockIncremented(uint256 newFlashblockIndex);
4343

44-
/// -----------------------------------------------------------------------
45-
/// Errors
46-
/// -----------------------------------------------------------------------
47-
error NonBuilderAddress(address addr);
48-
error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber);
44+
/// -----------------------------------------------------------------------
45+
/// Errors
46+
/// -----------------------------------------------------------------------
47+
error NonBuilderAddress(address addr);
48+
error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber);
49+
}
4950
);
5051

5152
#[derive(Debug, thiserror::Error)]
5253
pub(super) enum FlashblockNumberError {
53-
#[error("non builder address: {0}")]
54-
NonBuilderAddress(Address),
55-
#[error("mismatched flashblock number: expected {0}, actual {1}")]
56-
MismatchedFlashblockNumber(U256, U256),
57-
#[error("unknown revert: {0}")]
58-
Unknown(String),
54+
#[error("flashblocks number contract tx reverted: {0:?}")]
55+
Revert(IFlashblockNumber::IFlashblockNumberErrors),
56+
#[error("contract may be invalid, mismatch in log emitted: expected {0:?}")]
57+
LogMismatch(B256),
58+
#[error("unknown revert: {0} err: {1}")]
59+
Unknown(String, Error),
5960
#[error("halt: {0:?}")]
6061
Halt(OpHaltReason),
6162
}
6263

63-
impl From<Bytes> for FlashblockNumberError {
64-
fn from(value: Bytes) -> Self {
65-
// Empty revert
66-
if value.is_empty() {
67-
return FlashblockNumberError::Unknown(
68-
"Transaction reverted without reason".to_string(),
69-
);
70-
}
71-
72-
// Try to decode each custom error type
73-
if let Ok(NonBuilderAddress { addr }) = NonBuilderAddress::abi_decode(&value) {
74-
return FlashblockNumberError::NonBuilderAddress(addr);
75-
}
76-
77-
if let Ok(MismatchedFlashblockNumber {
78-
expectedFlashblockNumber,
79-
actualFlashblockNumber,
80-
}) = MismatchedFlashblockNumber::abi_decode(&value)
81-
{
82-
return FlashblockNumberError::MismatchedFlashblockNumber(
83-
expectedFlashblockNumber,
84-
actualFlashblockNumber,
85-
);
86-
}
87-
88-
FlashblockNumberError::Unknown(hex::encode(value))
89-
}
90-
}
91-
9264
// This will be the end of block transaction of a regular block
9365
#[derive(Debug, Clone)]
9466
pub(super) struct FlashblocksBuilderTx {
@@ -116,7 +88,6 @@ impl BuilderTransactions<FlashblocksExtraCtx> for FlashblocksBuilderTx {
11688
info: &mut ExecutionInfo<Extra>,
11789
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
11890
db: &mut State<impl Database>,
119-
top_of_block: bool,
12091
) -> Result<Vec<BuilderTransactionCtx>, BuilderTransactionError> {
12192
let mut builder_txs = Vec::<BuilderTransactionCtx>::new();
12293

@@ -126,24 +97,14 @@ impl BuilderTransactions<FlashblocksExtraCtx> for FlashblocksBuilderTx {
12697
}
12798

12899
if ctx.is_last_flashblock() {
129-
let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?;
130-
if let Some(tx) = flashblocks_builder_tx.clone() {
131-
if top_of_block {
132-
// don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx
133-
builder_txs.push(BuilderTransactionCtx {
134-
gas_used: tx.gas_used,
135-
da_size: tx.da_size,
136-
signed_tx: None,
137-
});
138-
} else {
139-
builder_txs.push(tx);
140-
}
141-
}
100+
let base_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?;
101+
builder_txs.extend(base_tx.clone());
102+
142103
if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx {
143104
// We only include flashtestations txs in the last flashblock
144105
let mut simulation_state = self.simulate_builder_txs_state::<FlashblocksExtraCtx>(
145106
state_provider.clone(),
146-
flashblocks_builder_tx.iter().collect(),
107+
base_tx.iter().collect(),
147108
ctx,
148109
db,
149110
)?;
@@ -152,7 +113,6 @@ impl BuilderTransactions<FlashblocksExtraCtx> for FlashblocksBuilderTx {
152113
info,
153114
ctx,
154115
&mut simulation_state,
155-
top_of_block,
156116
)?;
157117
builder_txs.extend(flashtestations_builder_txs);
158118
}
@@ -205,10 +165,27 @@ impl FlashblocksNumberBuilderTx {
205165
};
206166

207167
match result {
208-
ExecutionResult::Success { gas_used, .. } => Ok(gas_used),
209-
ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::Other(
210-
Box::new(FlashblockNumberError::from(output)),
211-
)),
168+
ExecutionResult::Success { gas_used, logs, .. } => {
169+
if log_exists(
170+
&logs,
171+
&IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH,
172+
) {
173+
Ok(gas_used)
174+
} else {
175+
Err(BuilderTransactionError::Other(Box::new(
176+
FlashblockNumberError::LogMismatch(
177+
IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH,
178+
),
179+
)))
180+
}
181+
}
182+
ExecutionResult::Revert { output, .. } => {
183+
Err(BuilderTransactionError::Other(Box::new(
184+
IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output)
185+
.map(FlashblockNumberError::Revert)
186+
.unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)),
187+
)))
188+
}
212189
ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::Other(Box::new(
213190
FlashblockNumberError::Halt(reason),
214191
))),
@@ -245,7 +222,6 @@ impl BuilderTransactions<FlashblocksExtraCtx> for FlashblocksNumberBuilderTx {
245222
info: &mut ExecutionInfo<Extra>,
246223
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
247224
db: &mut State<impl Database>,
248-
top_of_block: bool,
249225
) -> Result<Vec<BuilderTransactionCtx>, BuilderTransactionError> {
250226
let mut builder_txs = Vec::<BuilderTransactionCtx>::new();
251227
let state = StateProviderDatabase::new(state_provider.clone());
@@ -256,8 +232,8 @@ impl BuilderTransactions<FlashblocksExtraCtx> for FlashblocksNumberBuilderTx {
256232
.build();
257233

258234
if ctx.is_first_flashblock() {
259-
let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?;
260-
builder_txs.extend(flashblocks_builder_tx.clone());
235+
// fallback block builder tx
236+
builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, db)?);
261237
} else {
262238
// we increment the flashblock number for the next flashblock so we don't increment in the last flashblock
263239
if let Some(signer) = &self.signer {
@@ -274,41 +250,28 @@ impl BuilderTransactions<FlashblocksExtraCtx> for FlashblocksNumberBuilderTx {
274250
{
275251
Ok(gas_used) => {
276252
// Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer
277-
let flashblocks_tx = self.signed_flashblock_number_tx(
253+
let signed_tx = self.signed_flashblock_number_tx(
278254
ctx,
279255
gas_used * 64 / 63,
280256
nonce,
281257
signer,
282258
)?;
283259

284260
let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes(
285-
flashblocks_tx.encoded_2718().as_slice(),
261+
signed_tx.encoded_2718().as_slice(),
286262
);
287263
Some(BuilderTransactionCtx {
288264
gas_used,
289265
da_size,
290-
signed_tx: if top_of_block {
291-
Some(flashblocks_tx)
292-
} else {
293-
None
294-
}, // number tx at top of flashblock
266+
signed_tx,
267+
is_top_of_block: true, // number tx at top of flashblock
295268
})
296269
}
297270
Err(e) => {
298271
warn!(target: "builder_tx", error = ?e, "Flashblocks number contract tx simulation failed, defaulting to fallback builder tx");
299-
let builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?;
300-
if let Some(tx) = &builder_tx
301-
&& top_of_block
302-
{
303-
// don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx
304-
Some(BuilderTransactionCtx {
305-
gas_used: tx.gas_used,
306-
da_size: tx.da_size,
307-
signed_tx: None,
308-
})
309-
} else {
310-
builder_tx
311-
}
272+
self.base_builder_tx
273+
.simulate_builder_tx(ctx, db)?
274+
.map(|tx| tx.set_top_of_block())
312275
}
313276
};
314277

@@ -331,7 +294,6 @@ impl BuilderTransactions<FlashblocksExtraCtx> for FlashblocksNumberBuilderTx {
331294
info,
332295
ctx,
333296
&mut simulation_state,
334-
top_of_block,
335297
)?;
336298
builder_txs.extend(flashtestations_builder_txs);
337299
}

0 commit comments

Comments
 (0)