Skip to content

Commit

Permalink
feat: Remove L2 Execution Payload (#542)
Browse files Browse the repository at this point in the history
* feat: remove l2 execution payload

* fix: tests

* fix: cargo hack

* fix: remove alloy-primitives/std req
  • Loading branch information
refcell authored Sep 23, 2024
1 parent 7f744d1 commit 3050746
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 488 deletions.
5 changes: 0 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 5 additions & 6 deletions bin/client/src/l2/chain_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ use alloy_primitives::{Address, Bytes, B256};
use alloy_rlp::Decodable;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use kona_derive::traits::L2ChainProvider;
use kona_derive::{block::OpBlock, traits::L2ChainProvider};
use kona_mpt::{OrderedListWalker, TrieHinter, TrieProvider};
use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType};
use kona_primitives::{L2ExecutionPayloadEnvelope, OpBlock};
use op_alloy_consensus::OpTxEnvelope;
use op_alloy_genesis::{RollupConfig, SystemConfig};
use op_alloy_protocol::L2BlockInfo;
Expand Down Expand Up @@ -73,13 +72,13 @@ impl<T: CommsClient + Send + Sync> L2ChainProvider for OracleL2ChainProvider<T>

async fn l2_block_info_by_number(&mut self, number: u64) -> Result<L2BlockInfo> {
// Get the payload at the given block number.
let payload = self.payload_by_number(number).await?;
let payload = self.block_by_number(number).await?;

// Construct the system config from the payload.
payload.to_l2_block_ref(&self.boot_info.rollup_config).map_err(Into::into)
}

async fn payload_by_number(&mut self, number: u64) -> Result<L2ExecutionPayloadEnvelope> {
async fn block_by_number(&mut self, number: u64) -> Result<OpBlock> {
// Fetch the header for the given block number.
let header @ Header { transactions_root, timestamp, .. } =
self.header_by_number(number).await?;
Expand All @@ -104,7 +103,7 @@ impl<T: CommsClient + Send + Sync> L2ChainProvider for OracleL2ChainProvider<T>
withdrawals: self.boot_info.rollup_config.is_canyon_active(timestamp).then(Vec::new),
..Default::default()
};
Ok(optimism_block.into())
Ok(optimism_block)
}

async fn system_config_by_number(
Expand All @@ -113,7 +112,7 @@ impl<T: CommsClient + Send + Sync> L2ChainProvider for OracleL2ChainProvider<T>
rollup_config: Arc<RollupConfig>,
) -> Result<SystemConfig> {
// Get the payload at the given block number.
let payload = self.payload_by_number(number).await?;
let payload = self.block_by_number(number).await?;

// Construct the system config from the payload.
payload.to_system_config(rollup_config.as_ref()).map_err(Into::into)
Expand Down
66 changes: 27 additions & 39 deletions crates/derive/src/batch/span_batch/batch.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! The Span Batch Type
use alloc::vec::Vec;
use alloy_eips::eip2718::Encodable2718;
use alloy_primitives::FixedBytes;
use op_alloy_consensus::OpTxType;
use op_alloy_genesis::RollupConfig;
Expand Down Expand Up @@ -277,20 +278,18 @@ impl SpanBatch {
if self.timestamp() < next_timestamp {
for i in 0..(l2_safe_head.block_info.number - parent_num) {
let safe_block_num = parent_num + i + 1;
let safe_block_payload = match fetcher.payload_by_number(safe_block_num).await {
let safe_block_payload = match fetcher.block_by_number(safe_block_num).await {
Ok(p) => p,
Err(e) => {
warn!("failed to fetch payload for block number {safe_block_num}: {e}");
warn!("failed to fetch block number {safe_block_num}: {e}");
return BatchValidity::Undecided;
}
};
let safe_block_txs = &safe_block_payload.execution_payload.transactions;
let safe_block_txs = &safe_block_payload.body;
let batch_txs = &self.batches[i as usize].transactions;
// Execution payload has deposit txs but batch does not.
let deposit_count: usize = safe_block_txs
.iter()
.map(|tx| if tx.0[0] == OpTxType::Deposit as u8 { 1 } else { 0 })
.sum();
let deposit_count: usize =
safe_block_txs.iter().map(|tx| if tx.is_deposit() { 1 } else { 0 }).sum();
if safe_block_txs.len() - deposit_count != batch_txs.len() {
warn!(
"overlapped block's tx count does not match, safe_block_txs: {}, batch_txs: {}",
Expand All @@ -300,15 +299,17 @@ impl SpanBatch {
return BatchValidity::Drop;
}
for j in 0..batch_txs.len() {
if safe_block_txs[j + deposit_count] != batch_txs[j].0 {
let mut buf = Vec::new();
safe_block_txs[j + deposit_count].encode_2718(&mut buf);
if buf != batch_txs[j].0 {
warn!("overlapped block's transaction does not match");
return BatchValidity::Drop;
}
}
let safe_block_ref = match safe_block_payload.to_l2_block_ref(cfg) {
Ok(r) => r,
Err(e) => {
warn!("failed to extract L2BlockInfo from execution payload, hash: {}, err: {e}", safe_block_payload.execution_payload.block_hash);
warn!("failed to extract L2BlockInfo from execution payload, hash: {}, err: {e}", safe_block_payload.header.hash_slow());
return BatchValidity::Drop;
}
};
Expand Down Expand Up @@ -417,13 +418,14 @@ impl SpanBatch {
mod tests {
use super::*;
use crate::{
block::OpBlock,
stages::test_utils::{CollectingLayer, TraceStorage},
traits::test_utils::TestL2ChainProvider,
};
use alloc::vec;
use alloy_consensus::Header;
use alloy_eips::BlockNumHash;
use alloy_primitives::{b256, Bytes, B256};
use kona_primitives::{L2ExecutionPayload, L2ExecutionPayloadEnvelope};
use alloy_primitives::{b256, Bytes};
use op_alloy_consensus::OpTxType;
use op_alloy_genesis::ChainGenesis;
use tracing::Level;
Expand Down Expand Up @@ -1366,7 +1368,7 @@ mod tests {
);
let logs = trace_store.get_by_level(Level::WARN);
assert_eq!(logs.len(), 1);
assert!(logs[0].contains("failed to fetch payload for block number 41: Payload not found"));
assert!(logs[0].contains("failed to fetch block number 41: L2 Block not found"));
}

// TODO: Test overlap block tx count mismatch
Expand Down Expand Up @@ -1407,13 +1409,11 @@ mod tests {
l1_origin: BlockNumHash { number: 9, ..Default::default() },
..Default::default()
};
let payload = L2ExecutionPayloadEnvelope {
parent_beacon_block_root: None,
execution_payload: L2ExecutionPayload { block_number: 41, ..Default::default() },
};
let block =
OpBlock { header: Header { number: 41, ..Default::default() }, ..Default::default() };
let mut fetcher = TestL2ChainProvider {
blocks: vec![l2_block],
payloads: vec![payload],
op_blocks: vec![block],
..Default::default()
};
let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
Expand All @@ -1431,8 +1431,8 @@ mod tests {
let logs = trace_store.get_by_level(Level::WARN);
assert_eq!(logs.len(), 1);
let str = alloc::format!(
"failed to extract L2BlockInfo from execution payload, hash: {}",
B256::default(),
"failed to extract L2BlockInfo from execution payload, hash: {:?}",
b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd"),
);
assert!(logs[0].contains(&str));
}
Expand All @@ -1444,7 +1444,7 @@ mod tests {
tracing_subscriber::Registry::default().with(layer).init();

let payload_block_hash =
b256!("4444444444444444444444444444444444444444444444444444444444444444");
b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd");
let cfg = RollupConfig {
seq_window_size: 100,
delta_time: Some(0),
Expand Down Expand Up @@ -1477,17 +1477,11 @@ mod tests {
l1_origin: BlockNumHash { number: 9, ..Default::default() },
..Default::default()
};
let payload = L2ExecutionPayloadEnvelope {
parent_beacon_block_root: None,
execution_payload: L2ExecutionPayload {
block_number: 41,
block_hash: payload_block_hash,
..Default::default()
},
};
let block =
OpBlock { header: Header { number: 41, ..Default::default() }, ..Default::default() };
let mut fetcher = TestL2ChainProvider {
blocks: vec![l2_block],
payloads: vec![payload],
op_blocks: vec![block],
..Default::default()
};
let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
Expand All @@ -1514,7 +1508,7 @@ mod tests {
tracing_subscriber::Registry::default().with(layer).init();

let payload_block_hash =
b256!("4444444444444444444444444444444444444444444444444444444444444444");
b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd");
let cfg = RollupConfig {
seq_window_size: 100,
delta_time: Some(0),
Expand Down Expand Up @@ -1553,17 +1547,11 @@ mod tests {
l1_origin: BlockNumHash { number: 9, ..Default::default() },
..Default::default()
};
let payload = L2ExecutionPayloadEnvelope {
parent_beacon_block_root: None,
execution_payload: L2ExecutionPayload {
block_number: 41,
block_hash: payload_block_hash,
..Default::default()
},
};
let block =
OpBlock { header: Header { number: 41, ..Default::default() }, ..Default::default() };
let mut fetcher = TestL2ChainProvider {
blocks: vec![l2_block],
payloads: vec![payload],
op_blocks: vec![block],
..Default::default()
};
let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
Expand Down
166 changes: 166 additions & 0 deletions crates/derive/src/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//! This module contains the various Block types.
use alloc::vec::Vec;
use alloy_consensus::{Header, TxEnvelope};
use alloy_eips::eip4895::Withdrawal;
use alloy_primitives::B256;
use alloy_rlp::{RlpDecodable, RlpEncodable};
use op_alloy_consensus::OpTxEnvelope;
use op_alloy_genesis::{RollupConfig, SystemConfig};
use op_alloy_protocol::{
block_info::DecodeError, BlockInfo, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoTx,
L2BlockInfo,
};
use thiserror::Error;

/// Ethereum full block.
///
/// Withdrawals can be optionally included at the end of the RLP encoded message.
///
/// Taken from [reth-primitives](https://github.com/paradigmxyz/reth)
#[derive(Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
#[rlp(trailing)]
pub struct Block {
/// Block header.
pub header: Header,
/// Transactions in this block.
pub body: Vec<TxEnvelope>,
/// Ommers/uncles header.
pub ommers: Vec<Header>,
/// Block withdrawals.
pub withdrawals: Option<Vec<Withdrawal>>,
}

/// An error encountered during [OpBlock] conversion.
#[derive(Error, Debug)]
pub enum OpBlockConversionError {
/// Invalid genesis hash.
#[error("Invalid genesis hash. Expected {0}, got {1}")]
InvalidGenesisHash(B256, B256),
/// Invalid transaction type.
#[error("First payload transaction has unexpected type: {0}")]
InvalidTxType(u8),
/// L1 Info error
#[error(transparent)]
L1InfoError(#[from] DecodeError),
/// Missing system config in genesis block.
#[error("Missing system config in genesis block")]
MissingSystemConfigGenesis,
/// Empty transactions.
#[error("Empty transactions in payload. Block hash: {0}")]
EmptyTransactions(B256),
}

/// OP Stack full block.
///
/// Withdrawals can be optionally included at the end of the RLP encoded message.
///
/// Taken from [reth-primitives](https://github.com/paradigmxyz/reth)
#[derive(Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
#[rlp(trailing)]
pub struct OpBlock {
/// Block header.
pub header: Header,
/// Transactions in this block.
pub body: Vec<OpTxEnvelope>,
/// Ommers/uncles header.
pub ommers: Vec<Header>,
/// Block withdrawals.
pub withdrawals: Option<Vec<Withdrawal>>,
}

impl OpBlock {
/// Converts the [OpBlock] to an [L2BlockInfo], by checking against the L1
/// information transaction or the genesis block.
pub fn to_l2_block_ref(
&self,
rollup_config: &RollupConfig,
) -> Result<L2BlockInfo, OpBlockConversionError> {
let (l1_origin, sequence_number) = if self.header.number == rollup_config.genesis.l2.number
{
if self.header.hash_slow() != rollup_config.genesis.l2.hash {
return Err(OpBlockConversionError::InvalidGenesisHash(
rollup_config.genesis.l2.hash,
self.header.hash_slow(),
));
}
(rollup_config.genesis.l1, 0)
} else {
if self.body.is_empty() {
return Err(OpBlockConversionError::EmptyTransactions(self.header.hash_slow()));
}

let OpTxEnvelope::Deposit(ref tx) = self.body[0] else {
return Err(OpBlockConversionError::InvalidTxType(self.body[0].tx_type() as u8));
};

let l1_info = L1BlockInfoTx::decode_calldata(tx.input.as_ref())?;
(l1_info.id(), l1_info.sequence_number())
};

Ok(L2BlockInfo {
block_info: BlockInfo {
hash: self.header.hash_slow(),
number: self.header.number,
parent_hash: self.header.parent_hash,
timestamp: self.header.timestamp,
},
l1_origin,
seq_num: sequence_number,
})
}

/// Converts the [OpBlock] to a partial [SystemConfig].
pub fn to_system_config(
&self,
rollup_config: &RollupConfig,
) -> Result<SystemConfig, OpBlockConversionError> {
if self.header.number == rollup_config.genesis.l2.number {
if self.header.hash_slow() != rollup_config.genesis.l2.hash {
return Err(OpBlockConversionError::InvalidGenesisHash(
rollup_config.genesis.l2.hash,
self.header.hash_slow(),
));
}
return rollup_config
.genesis
.system_config
.ok_or(OpBlockConversionError::MissingSystemConfigGenesis);
}

if self.body.is_empty() {
return Err(OpBlockConversionError::EmptyTransactions(self.header.hash_slow()));
}
let OpTxEnvelope::Deposit(ref tx) = self.body[0] else {
return Err(OpBlockConversionError::InvalidTxType(self.body[0].tx_type() as u8));
};

let l1_info = L1BlockInfoTx::decode_calldata(tx.input.as_ref())?;
let l1_fee_scalar = match l1_info {
L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => l1_fee_scalar,
L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
base_fee_scalar,
blob_base_fee_scalar,
..
}) => {
// Translate Ecotone values back into encoded scalar if needed.
// We do not know if it was derived from a v0 or v1 scalar,
// but v1 is fine, a 0 blob base fee has the same effect.
let mut buf = B256::ZERO;
buf[0] = 0x01;
buf[24..28].copy_from_slice(blob_base_fee_scalar.to_be_bytes().as_ref());
buf[28..32].copy_from_slice(base_fee_scalar.to_be_bytes().as_ref());
buf.into()
}
};

Ok(SystemConfig {
batcher_address: l1_info.batcher_address(),
overhead: l1_info.l1_fee_overhead(),
scalar: l1_fee_scalar,
gas_limit: self.header.gas_limit as u64,
base_fee_scalar: None,
blob_base_fee_scalar: None,
})
}
}
Loading

0 comments on commit 3050746

Please sign in to comment.