Skip to content

Commit 0019a7b

Browse files
authored
Add permit functions for flashblocks number contract (#287)
* Add permit flashtestations tx calls from builder * move simumlation calls to builder tx * Add permit functions for flashblocks number contract * refactor to simulate call * fix tests
1 parent 21046aa commit 0019a7b

File tree

10 files changed

+311
-142
lines changed

10 files changed

+311
-142
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: 146 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,25 @@
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, Signature, U256};
5+
use alloy_rpc_types_eth::TransactionInput;
6+
use alloy_sol_types::{SolCall, SolEvent, sol};
77
use core::fmt::Debug;
8-
use op_alloy_consensus::OpTypedTransaction;
9-
use op_revm::OpHaltReason;
8+
use op_alloy_rpc_types::OpTransactionRequest;
109
use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap};
11-
use reth_optimism_primitives::OpTransactionSigned;
12-
use reth_primitives::Recovered;
1310
use reth_provider::StateProvider;
1411
use reth_revm::State;
15-
use revm::{
16-
DatabaseRef,
17-
context::result::{ExecutionResult, ResultAndState},
18-
inspector::NoOpInspector,
19-
};
12+
use revm::{DatabaseRef, inspector::NoOpInspector};
2013
use tracing::warn;
2114

2215
use crate::{
2316
builders::{
2417
BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions,
25-
InvalidContractDataError,
26-
builder_tx::{BuilderTxBase, get_nonce},
18+
SimulationSuccessResult,
19+
builder_tx::BuilderTxBase,
2720
context::OpPayloadBuilderCtx,
2821
flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx},
22+
get_nonce,
2923
},
3024
flashtestations::builder_tx::FlashtestationsBuilderTx,
3125
primitives::reth::ExecutionInfo,
@@ -37,8 +31,17 @@ sol!(
3731
#[sol(rpc, abi)]
3832
#[derive(Debug)]
3933
interface IFlashblockNumber {
34+
uint256 public flashblockNumber;
35+
4036
function incrementFlashblockNumber() external;
4137

38+
function permitIncrementFlashblockNumber(uint256 currentFlashblockNumber, bytes memory signature) external;
39+
40+
function computeStructHash(uint256 currentFlashblockNumber) external pure returns (bytes32);
41+
42+
function hashTypedDataV4(bytes32 structHash) external view returns (bytes32);
43+
44+
4245
// @notice Emitted when flashblock index is incremented
4346
// @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block)
4447
event FlashblockIncremented(uint256 newFlashblockIndex);
@@ -51,16 +54,6 @@ sol!(
5154
}
5255
);
5356

54-
#[derive(Debug, thiserror::Error)]
55-
pub(super) enum FlashblockNumberError {
56-
#[error("flashblocks number contract tx reverted: {0:?}")]
57-
Revert(IFlashblockNumber::IFlashblockNumberErrors),
58-
#[error("unknown revert: {0} err: {1}")]
59-
Unknown(String, Error),
60-
#[error("halt: {0:?}")]
61-
Halt(OpHaltReason),
62-
}
63-
6457
// This will be the end of block transaction of a regular block
6558
#[derive(Debug, Clone)]
6659
pub(super) struct FlashblocksBuilderTx {
@@ -133,94 +126,138 @@ impl BuilderTransactions<FlashblocksExtraCtx, FlashblocksExecutionInfo> for Flas
133126
// This will be the end of block transaction of a regular block
134127
#[derive(Debug, Clone)]
135128
pub(super) struct FlashblocksNumberBuilderTx {
136-
pub signer: Option<Signer>,
129+
pub signer: Signer,
137130
pub flashblock_number_address: Address,
131+
pub use_permit: bool,
138132
pub base_builder_tx: BuilderTxBase<FlashblocksExtraCtx>,
139133
pub flashtestations_builder_tx:
140134
Option<FlashtestationsBuilderTx<FlashblocksExtraCtx, FlashblocksExecutionInfo>>,
141135
}
142136

143137
impl FlashblocksNumberBuilderTx {
144138
pub(super) fn new(
145-
signer: Option<Signer>,
139+
signer: Signer,
146140
flashblock_number_address: Address,
141+
use_permit: bool,
147142
flashtestations_builder_tx: Option<
148143
FlashtestationsBuilderTx<FlashblocksExtraCtx, FlashblocksExecutionInfo>,
149144
>,
150145
) -> Self {
151-
let base_builder_tx = BuilderTxBase::new(signer);
146+
let base_builder_tx = BuilderTxBase::new(Some(signer));
152147
Self {
153148
signer,
154149
flashblock_number_address,
150+
use_permit,
155151
base_builder_tx,
156152
flashtestations_builder_tx,
157153
}
158154
}
159155

160-
// TODO: remove and clean up in favour of simulate_call()
161-
fn estimate_flashblock_number_tx_gas(
156+
fn signed_increment_flashblocks_tx(
162157
&self,
163158
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
164-
evm: &mut OpEvm<impl Database, NoOpInspector, PrecompilesMap>,
165-
signer: &Signer,
166-
nonce: u64,
167-
) -> Result<u64, BuilderTransactionError> {
168-
let tx = self.signed_flashblock_number_tx(ctx, ctx.block_gas_limit(), nonce, signer)?;
169-
let ResultAndState { result, .. } = match evm.transact(&tx) {
170-
Ok(res) => res,
171-
Err(err) => {
172-
return Err(BuilderTransactionError::EvmExecutionError(Box::new(err)));
173-
}
159+
evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>,
160+
) -> Result<BuilderTransactionCtx, BuilderTransactionError> {
161+
let calldata = IFlashblockNumber::incrementFlashblockNumberCall {};
162+
self.increment_flashblocks_tx(calldata, ctx, evm)
163+
}
164+
165+
fn increment_flashblocks_permit_signature(
166+
&self,
167+
flashtestations_signer: &Signer,
168+
current_flashblock_number: U256,
169+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
170+
evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>,
171+
) -> Result<Signature, BuilderTransactionError> {
172+
let struct_hash_calldata = IFlashblockNumber::computeStructHashCall {
173+
currentFlashblockNumber: current_flashblock_number,
174174
};
175+
let SimulationSuccessResult { output, .. } =
176+
self.simulate_flashblocks_readonly_call(struct_hash_calldata, ctx, evm)?;
177+
let typed_data_hash_calldata =
178+
IFlashblockNumber::hashTypedDataV4Call { structHash: output };
179+
let SimulationSuccessResult { output, .. } =
180+
self.simulate_flashblocks_readonly_call(typed_data_hash_calldata, ctx, evm)?;
181+
let signature = flashtestations_signer.sign_message(output)?;
182+
Ok(signature)
183+
}
175184

176-
match result {
177-
ExecutionResult::Success { gas_used, logs, .. } => {
178-
if logs.iter().any(|log| {
179-
log.topics().first()
180-
== Some(&IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH)
181-
}) {
182-
Ok(gas_used)
183-
} else {
184-
Err(BuilderTransactionError::InvalidContract(
185-
self.flashblock_number_address,
186-
InvalidContractDataError::InvalidLogs(
187-
vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH],
188-
vec![],
189-
),
190-
))
191-
}
192-
}
193-
ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::other(
194-
IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output)
195-
.map(FlashblockNumberError::Revert)
196-
.unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)),
197-
)),
198-
ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other(
199-
FlashblockNumberError::Halt(reason),
200-
)),
201-
}
185+
fn signed_increment_flashblocks_permit_tx(
186+
&self,
187+
flashtestations_signer: &Signer,
188+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
189+
evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>,
190+
) -> Result<BuilderTransactionCtx, BuilderTransactionError> {
191+
let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {};
192+
let SimulationSuccessResult { output, .. } =
193+
self.simulate_flashblocks_readonly_call(current_flashblock_calldata, ctx, evm)?;
194+
let signature =
195+
self.increment_flashblocks_permit_signature(flashtestations_signer, output, ctx, evm)?;
196+
let calldata = IFlashblockNumber::permitIncrementFlashblockNumberCall {
197+
currentFlashblockNumber: output,
198+
signature: signature.as_bytes().into(),
199+
};
200+
self.increment_flashblocks_tx(calldata, ctx, evm)
201+
}
202+
203+
fn increment_flashblocks_tx<T: SolCall + Clone>(
204+
&self,
205+
calldata: T,
206+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
207+
evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>,
208+
) -> Result<BuilderTransactionCtx, BuilderTransactionError> {
209+
let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call(
210+
calldata.clone(),
211+
vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH],
212+
ctx,
213+
evm,
214+
)?;
215+
let signed_tx = self.sign_tx(
216+
self.flashblock_number_address,
217+
self.signer,
218+
gas_used,
219+
calldata.abi_encode().into(),
220+
ctx,
221+
evm.db_mut(),
222+
)?;
223+
let da_size =
224+
op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice());
225+
Ok(BuilderTransactionCtx {
226+
signed_tx,
227+
gas_used,
228+
da_size,
229+
is_top_of_block: true,
230+
})
231+
}
232+
233+
fn simulate_flashblocks_readonly_call<T: SolCall>(
234+
&self,
235+
calldata: T,
236+
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
237+
evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>,
238+
) -> Result<SimulationSuccessResult<T>, BuilderTransactionError> {
239+
self.simulate_flashblocks_call(calldata, vec![], ctx, evm)
202240
}
203241

204-
fn signed_flashblock_number_tx(
242+
fn simulate_flashblocks_call<T: SolCall>(
205243
&self,
244+
calldata: T,
245+
expected_logs: Vec<B256>,
206246
ctx: &OpPayloadBuilderCtx<FlashblocksExtraCtx>,
207-
gas_limit: u64,
208-
nonce: u64,
209-
signer: &Signer,
210-
) -> Result<Recovered<OpTransactionSigned>, secp256k1::Error> {
211-
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)
247+
evm: &mut OpEvm<impl Database + DatabaseRef, NoOpInspector, PrecompilesMap>,
248+
) -> Result<SimulationSuccessResult<T>, BuilderTransactionError> {
249+
let tx_req = OpTransactionRequest::default()
250+
.gas_limit(ctx.block_gas_limit())
251+
.max_fee_per_gas(ctx.base_fee().into())
252+
.to(self.flashblock_number_address)
253+
.from(self.signer.address) // use tee key as signer for simulations
254+
.nonce(get_nonce(evm.db(), self.signer.address)?)
255+
.input(TransactionInput::new(calldata.abi_encode().into()));
256+
self.simulate_call::<T, IFlashblockNumber::IFlashblockNumberErrors>(
257+
tx_req,
258+
expected_logs,
259+
evm,
260+
)
224261
}
225262
}
226263

@@ -242,46 +279,35 @@ impl BuilderTransactions<FlashblocksExtraCtx, FlashblocksExecutionInfo>
242279
builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?);
243280
} else {
244281
// 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)?;
282+
let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone());
283+
evm.modify_cfg(|cfg| {
284+
cfg.disable_balance_check = true;
285+
cfg.disable_block_gas_limit = true;
286+
});
253287

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-
)?;
288+
let flashblocks_num_tx = if let Some(flashtestations) = &self.flashtestations_builder_tx
289+
&& self.use_permit
290+
{
291+
self.signed_increment_flashblocks_permit_tx(
292+
flashtestations.tee_signer(),
293+
ctx,
294+
&mut evm,
295+
)
296+
} else {
297+
self.signed_increment_flashblocks_tx(ctx, &mut evm)
298+
};
264299

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-
};
300+
let tx = match flashblocks_num_tx {
301+
Ok(tx) => Some(tx),
302+
Err(e) => {
303+
warn!(target: "builder_tx", error = ?e, "flashblocks number contract tx simulation failed, defaulting to fallback builder tx");
304+
self.base_builder_tx
305+
.simulate_builder_tx(ctx, &mut *db)?
306+
.map(|tx| tx.set_top_of_block())
307+
}
308+
};
282309

283-
builder_txs.extend(tx);
284-
}
310+
builder_txs.extend(tx);
285311
}
286312

287313
if ctx.is_last_flashblock() {

0 commit comments

Comments
 (0)