Skip to content

Commit 0c70cc5

Browse files
committed
Add permit functions for flashblocks number contract
1 parent 89a0889 commit 0c70cc5

File tree

9 files changed

+366
-87
lines changed

9 files changed

+366
-87
lines changed

crates/op-rbuilder/src/args/op.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ pub struct FlashblocksArgs {
167167
)]
168168
pub flashblocks_number_contract_address: Option<Address>,
169169

170+
/// Use permit signatures if flashtestations is enabled with the flashtestation key
171+
/// to increment the flashblocks number
172+
#[arg(
173+
long = "flashblocks.number-contract-use-permit",
174+
env = "FLASHBLOCK_NUMBER_CONTRACT_USE_PERMIT",
175+
default_value = "false"
176+
)]
177+
pub flashblocks_number_contract_use_permit: bool,
178+
170179
/// Flashblocks p2p configuration
171180
#[command(flatten)]
172181
pub p2p: FlashblocksP2pArgs,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl From<BuilderTransactionError> for PayloadBuilderError {
126126
BuilderTransactionError::EvmExecutionError(e) => {
127127
PayloadBuilderError::EvmExecutionError(e)
128128
}
129-
_ => PayloadBuilderError::Other(Box::new(error)),
129+
_ => PayloadBuilderError::other(error),
130130
}
131131
}
132132
}

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

Lines changed: 216 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1-
use alloy_consensus::TxEip1559;
21
use alloy_eips::Encodable2718;
32
use alloy_evm::{Database, Evm};
43
use alloy_op_evm::OpEvm;
5-
use alloy_primitives::{Address, TxKind};
6-
use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, sol};
4+
use alloy_primitives::{Address, B256, Bytes, Signature, U256};
5+
use alloy_sol_types::{SolCall, SolEvent, SolInterface, SolValue, sol};
76
use core::fmt::Debug;
8-
use op_alloy_consensus::OpTypedTransaction;
9-
use op_revm::OpHaltReason;
107
use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap};
11-
use reth_optimism_primitives::OpTransactionSigned;
12-
use reth_primitives::Recovered;
138
use reth_provider::StateProvider;
14-
use reth_revm::State;
15-
use revm::{
16-
DatabaseRef,
17-
context::result::{ExecutionResult, ResultAndState},
18-
inspector::NoOpInspector,
19-
};
9+
use reth_revm::{State, database::StateProviderDatabase};
10+
use revm::inspector::NoOpInspector;
2011
use tracing::warn;
2112

2213
use crate::{
2314
builders::{
2415
BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions,
25-
InvalidContractDataError,
26-
builder_tx::{BuilderTxBase, get_nonce},
16+
InvalidContractDataError, SimulationSuccessResult,
17+
builder_tx::BuilderTxBase,
2718
context::OpPayloadBuilderCtx,
2819
flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx},
2920
},
@@ -37,8 +28,17 @@ sol!(
3728
#[sol(rpc, abi)]
3829
#[derive(Debug)]
3930
interface IFlashblockNumber {
31+
uint256 public flashblockNumber;
32+
4033
function incrementFlashblockNumber() external;
4134

35+
function permitIncrementFlashblockNumber(uint256 currentFlashblockNumber, bytes memory signature) external;
36+
37+
function computeStructHash(uint256 currentFlashblockNumber) external pure returns (bytes32);
38+
39+
function hashTypedDataV4(bytes32 structHash) external view returns (bytes32);
40+
41+
4242
// @notice Emitted when flashblock index is incremented
4343
// @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block)
4444
event FlashblockIncremented(uint256 newFlashblockIndex);
@@ -55,10 +55,8 @@ sol!(
5555
pub(super) enum FlashblockNumberError {
5656
#[error("flashblocks number contract tx reverted: {0:?}")]
5757
Revert(IFlashblockNumber::IFlashblockNumberErrors),
58-
#[error("unknown revert: {0} err: {1}")]
59-
Unknown(String, Error),
60-
#[error("halt: {0:?}")]
61-
Halt(OpHaltReason),
58+
#[error("unknown revert: {0}")]
59+
Unknown(String),
6260
}
6361

6462
// This will be the end of block transaction of a regular block
@@ -133,25 +131,28 @@ impl BuilderTransactions<FlashblocksExtraCtx, FlashblocksExecutionInfo> for Flas
133131
// This will be the end of block transaction of a regular block
134132
#[derive(Debug, Clone)]
135133
pub(super) struct FlashblocksNumberBuilderTx {
136-
pub signer: Option<Signer>,
134+
pub signer: Signer,
137135
pub flashblock_number_address: Address,
136+
pub use_permit: bool,
138137
pub base_builder_tx: BuilderTxBase<FlashblocksExtraCtx>,
139138
pub flashtestations_builder_tx:
140139
Option<FlashtestationsBuilderTx<FlashblocksExtraCtx, FlashblocksExecutionInfo>>,
141140
}
142141

143142
impl FlashblocksNumberBuilderTx {
144143
pub(super) fn new(
145-
signer: Option<Signer>,
144+
signer: Signer,
146145
flashblock_number_address: Address,
146+
use_permit: bool,
147147
flashtestations_builder_tx: Option<
148148
FlashtestationsBuilderTx<FlashblocksExtraCtx, FlashblocksExecutionInfo>,
149149
>,
150150
) -> Self {
151-
let base_builder_tx = BuilderTxBase::new(signer);
151+
let base_builder_tx = BuilderTxBase::new(Some(signer));
152152
Self {
153153
signer,
154154
flashblock_number_address,
155+
use_permit,
155156
base_builder_tx,
156157
flashtestations_builder_tx,
157158
}
@@ -200,27 +201,173 @@ impl FlashblocksNumberBuilderTx {
200201
)),
201202
}
202203
}
204+
205+
fn current_flashblock_number(
206+
&self,
207+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
208+
evm: &mut OpEvm<
209+
&mut State<StateProviderDatabase<impl StateProvider>>,
210+
NoOpInspector,
211+
PrecompilesMap,
212+
>,
213+
) -> Result<U256, BuilderTransactionError> {
214+
let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {}.abi_encode();
215+
let SimulationSuccessResult { output, .. } =
216+
self.simulate_flashblocks_call(current_flashblock_calldata, None, ctx, evm)?;
217+
IFlashblockNumber::flashblockNumberCall::abi_decode_returns(&output).map_err(|_| {
218+
BuilderTransactionError::InvalidContract(
219+
self.flashblock_number_address,
220+
InvalidContractDataError::OutputAbiDecodeError,
221+
)
222+
})
223+
}
203224

204-
fn signed_flashblock_number_tx(
225+
fn signed_increment_flashblocks_tx(
205226
&self,
206227
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
207-
gas_limit: u64,
208-
nonce: u64,
209-
signer: &Signer,
210-
) -> Result<Recovered<OpTransactionSigned>, secp256k1::Error> {
228+
evm: &mut OpEvm<
229+
&mut State<StateProviderDatabase<impl StateProvider>>,
230+
NoOpInspector,
231+
PrecompilesMap,
232+
>,
233+
) -> Result<BuilderTransactionCtx, BuilderTransactionError> {
211234
let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}.abi_encode();
212-
// Create the EIP-1559 transaction
213-
let tx = OpTypedTransaction::Eip1559(TxEip1559 {
214-
chain_id: ctx.chain_id(),
215-
nonce,
216-
gas_limit,
217-
max_fee_per_gas: ctx.base_fee().into(),
218-
max_priority_fee_per_gas: 0,
219-
to: TxKind::Call(self.flashblock_number_address),
220-
input: calldata.into(),
221-
..Default::default()
222-
});
223-
signer.sign_tx(tx)
235+
let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call(
236+
calldata.clone(),
237+
Some(IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH),
238+
ctx,
239+
evm,
240+
)?;
241+
let signed_tx = self.sign_tx(
242+
self.flashblock_number_address,
243+
self.signer,
244+
Some(gas_used),
245+
calldata.into(),
246+
ctx,
247+
evm.db_mut(),
248+
)?;
249+
let da_size =
250+
op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice());
251+
Ok(BuilderTransactionCtx {
252+
signed_tx,
253+
gas_used,
254+
da_size,
255+
is_top_of_block: true,
256+
})
257+
}
258+
259+
fn increment_flashblocks_permit_signature(
260+
&self,
261+
flashtestations_signer: &Signer,
262+
current_flashblock_number: U256,
263+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
264+
evm: &mut OpEvm<
265+
&mut State<StateProviderDatabase<impl StateProvider>>,
266+
NoOpInspector,
267+
PrecompilesMap,
268+
>,
269+
) -> Result<Signature, BuilderTransactionError> {
270+
let struct_hash_calldata = IFlashblockNumber::computeStructHashCall {
271+
currentFlashblockNumber: current_flashblock_number,
272+
}
273+
.abi_encode();
274+
let SimulationSuccessResult { output, .. } =
275+
self.simulate_flashblocks_call(struct_hash_calldata, None, ctx, evm)?;
276+
let struct_hash = B256::abi_decode(&output).map_err(|_| {
277+
BuilderTransactionError::InvalidContract(
278+
self.flashblock_number_address,
279+
InvalidContractDataError::OutputAbiDecodeError,
280+
)
281+
})?;
282+
let typed_data_hash_calldata = IFlashblockNumber::hashTypedDataV4Call {
283+
structHash: struct_hash,
284+
}
285+
.abi_encode();
286+
let SimulationSuccessResult { output, .. } =
287+
self.simulate_flashblocks_call(typed_data_hash_calldata, None, ctx, evm)?;
288+
let typed_data_hash = B256::abi_decode(&output).map_err(|_| {
289+
BuilderTransactionError::InvalidContract(
290+
self.flashblock_number_address,
291+
InvalidContractDataError::OutputAbiDecodeError,
292+
)
293+
})?;
294+
let signature = flashtestations_signer.sign_message(typed_data_hash)?;
295+
Ok(signature)
296+
}
297+
298+
fn signed_increment_flashblocks_permit_tx(
299+
&self,
300+
flashtestations_signer: &Signer,
301+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
302+
evm: &mut OpEvm<
303+
&mut State<StateProviderDatabase<impl StateProvider>>,
304+
NoOpInspector,
305+
PrecompilesMap,
306+
>,
307+
) -> Result<BuilderTransactionCtx, BuilderTransactionError> {
308+
let current_flashblock_number = self.current_flashblock_number(ctx, evm)?;
309+
let signature = self.increment_flashblocks_permit_signature(
310+
flashtestations_signer,
311+
current_flashblock_number,
312+
ctx,
313+
evm,
314+
)?;
315+
let calldata = IFlashblockNumber::permitIncrementFlashblockNumberCall {
316+
currentFlashblockNumber: current_flashblock_number,
317+
signature: signature.as_bytes().into(),
318+
}
319+
.abi_encode();
320+
let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call(
321+
calldata.clone(),
322+
Some(IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH),
323+
ctx,
324+
evm,
325+
)?;
326+
let signed_tx = self.sign_tx(
327+
self.flashblock_number_address,
328+
self.signer,
329+
Some(gas_used),
330+
calldata.into(),
331+
ctx,
332+
evm.db_mut(),
333+
)?;
334+
let da_size =
335+
op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice());
336+
Ok(BuilderTransactionCtx {
337+
signed_tx,
338+
gas_used,
339+
da_size,
340+
is_top_of_block: true,
341+
})
342+
}
343+
344+
fn simulate_flashblocks_call(
345+
&self,
346+
calldata: Vec<u8>,
347+
expected_topic: Option<B256>,
348+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
349+
evm: &mut OpEvm<
350+
&mut State<StateProviderDatabase<impl StateProvider>>,
351+
NoOpInspector,
352+
PrecompilesMap,
353+
>,
354+
) -> Result<SimulationSuccessResult, BuilderTransactionError> {
355+
let signed_tx = self.sign_tx(
356+
self.flashblock_number_address,
357+
self.signer,
358+
None,
359+
calldata.into(),
360+
ctx,
361+
evm.db_mut(),
362+
)?;
363+
self.simulate_call(signed_tx, expected_topic, Self::handle_revert, evm)
364+
}
365+
366+
fn handle_revert(revert_output: Bytes) -> BuilderTransactionError {
367+
let revert_reason = IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&revert_output)
368+
.map(FlashblockNumberError::Revert)
369+
.unwrap_or_else(|_| FlashblockNumberError::Unknown(hex::encode(revert_output)));
370+
BuilderTransactionError::TransactionReverted(Box::new(revert_reason))
224371
}
225372
}
226373

@@ -242,46 +389,37 @@ impl BuilderTransactions<FlashblocksExtraCtx, FlashblocksExecutionInfo>
242389
builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?);
243390
} else {
244391
// we increment the flashblock number for the next flashblock so we don't increment in the last flashblock
245-
if let Some(signer) = &self.signer {
246-
let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone());
247-
evm.modify_cfg(|cfg| {
248-
cfg.disable_balance_check = true;
249-
cfg.disable_block_gas_limit = true;
250-
});
251-
252-
let nonce = get_nonce(evm.db_mut(), signer.address)?;
253-
254-
let tx = match self.estimate_flashblock_number_tx_gas(ctx, &mut evm, signer, nonce)
255-
{
256-
Ok(gas_used) => {
257-
// Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer
258-
let signed_tx = self.signed_flashblock_number_tx(
259-
ctx,
260-
gas_used * 64 / 63,
261-
nonce,
262-
signer,
263-
)?;
264-
265-
let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes(
266-
signed_tx.encoded_2718().as_slice(),
267-
);
268-
Some(BuilderTransactionCtx {
269-
gas_used,
270-
da_size,
271-
signed_tx,
272-
is_top_of_block: true, // number tx at top of flashblock
273-
})
274-
}
275-
Err(e) => {
276-
warn!(target: "builder_tx", error = ?e, "Flashblocks number contract tx simulation failed, defaulting to fallback builder tx");
277-
self.base_builder_tx
278-
.simulate_builder_tx(ctx, &mut *db)?
279-
.map(|tx| tx.set_top_of_block())
280-
}
281-
};
392+
let mut evm = ctx
393+
.evm_config
394+
.evm_with_env(&mut simulation_state, ctx.evm_env.clone());
395+
evm.modify_cfg(|cfg| {
396+
cfg.disable_balance_check = true;
397+
cfg.disable_block_gas_limit = true;
398+
});
282399

283-
builder_txs.extend(tx);
284-
}
400+
let flashblocks_num_tx = if let Some(flashtestations) = &self.flashtestations_builder_tx
401+
&& self.use_permit
402+
{
403+
self.signed_increment_flashblocks_permit_tx(
404+
flashtestations.tee_signer(),
405+
ctx,
406+
&mut evm,
407+
)
408+
} else {
409+
self.signed_increment_flashblocks_tx(ctx, &mut evm)
410+
};
411+
412+
let tx = match flashblocks_num_tx {
413+
Ok(tx) => Some(tx),
414+
Err(e) => {
415+
warn!(target: "builder_tx", error = ?e, "flashblocks number contract tx simulation failed, defaulting to fallback builder tx");
416+
self.base_builder_tx
417+
.simulate_builder_tx(ctx, db)?
418+
.map(|tx| tx.set_top_of_block())
419+
}
420+
};
421+
422+
builder_txs.extend(tx);
285423
}
286424

287425
if ctx.is_last_flashblock() {

0 commit comments

Comments
 (0)