Skip to content

Commit

Permalink
chore(protocol): Refactor Block Info Txs (#303)
Browse files Browse the repository at this point in the history
### Description

Refactors the `block_info.rs` module into a new `info/` module that
makes the `L1BlockInfoTx` more extensible.

Closes #302
  • Loading branch information
refcell authored Nov 22, 2024
1 parent 052a8a3 commit be93ab5
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 282 deletions.
2 changes: 1 addition & 1 deletion crates/protocol/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Block Types for Optimism.
use crate::block_info::{DecodeError, L1BlockInfoTx};
use crate::{DecodeError, L1BlockInfoTx};
use alloy_eips::{eip2718::Eip2718Error, BlockNumHash};
use alloy_primitives::B256;
use op_alloy_consensus::{OpBlock, OpTxEnvelope, OpTxType};
Expand Down
111 changes: 111 additions & 0 deletions crates/protocol/src/info/bedrock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Contains bedrock-specific L1 block info types.
use alloc::{format, string::ToString, vec::Vec};
use alloy_primitives::{Address, Bytes, B256, U256};

use crate::DecodeError;

/// Represents the fields within a Bedrock L1 block info transaction.
///
/// Bedrock Binary Format
// +---------+--------------------------+
// | Bytes | Field |
// +---------+--------------------------+
// | 4 | Function signature |
// | 32 | Number |
// | 32 | Time |
// | 32 | BaseFee |
// | 32 | BlockHash |
// | 32 | SequenceNumber |
// | 32 | BatcherHash |
// | 32 | L1FeeOverhead |
// | 32 | L1FeeScalar |
// +---------+--------------------------+
#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct L1BlockInfoBedrock {
/// The current L1 origin block number
pub number: u64,
/// The current L1 origin block's timestamp
pub time: u64,
/// The current L1 origin block's basefee
pub base_fee: u64,
/// The current L1 origin block's hash
pub block_hash: B256,
/// The current sequence number
pub sequence_number: u64,
/// The address of the batch submitter
pub batcher_address: Address,
/// The fee overhead for L1 data
pub l1_fee_overhead: U256,
/// The fee scalar for L1 data
pub l1_fee_scalar: U256,
}

impl L1BlockInfoBedrock {
/// The length of an L1 info transaction in Bedrock.
pub const L1_INFO_TX_LEN: usize = 4 + 32 * 8;

/// The 4 byte selector of the
/// "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64,bytes32,uint256,uint256)" function
pub const L1_INFO_TX_SELECTOR: [u8; 4] = [0x01, 0x5d, 0x8e, 0xb9];

/// Encodes the [L1BlockInfoBedrock] object into Ethereum transaction calldata.
pub fn encode_calldata(&self) -> Bytes {
let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN);
buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref());
buf.extend_from_slice(U256::from(self.number).to_be_bytes::<32>().as_slice());
buf.extend_from_slice(U256::from(self.time).to_be_bytes::<32>().as_slice());
buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_slice());
buf.extend_from_slice(self.block_hash.as_slice());
buf.extend_from_slice(U256::from(self.sequence_number).to_be_bytes::<32>().as_slice());
buf.extend_from_slice(self.batcher_address.into_word().as_slice());
buf.extend_from_slice(self.l1_fee_overhead.to_be_bytes::<32>().as_slice());
buf.extend_from_slice(self.l1_fee_scalar.to_be_bytes::<32>().as_slice());
buf.into()
}

/// Decodes the [L1BlockInfoBedrock] object from ethereum transaction calldata.
pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
if r.len() != Self::L1_INFO_TX_LEN {
return Err(DecodeError::InvalidLength(format!(
"Invalid calldata length for Bedrock L1 info transaction, expected {}, got {}",
Self::L1_INFO_TX_LEN,
r.len()
)));
}

let number = u64::from_be_bytes(
r[28..36]
.try_into()
.map_err(|_| DecodeError::ParseError("Conversion error for number".to_string()))?,
);
let time = u64::from_be_bytes(
r[60..68]
.try_into()
.map_err(|_| DecodeError::ParseError("Conversion error for time".to_string()))?,
);
let base_fee =
u64::from_be_bytes(r[92..100].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for base fee".to_string())
})?);
let block_hash = B256::from_slice(r[100..132].as_ref());
let sequence_number = u64::from_be_bytes(r[156..164].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for sequence number".to_string())
})?);
let batcher_address = Address::from_slice(r[176..196].as_ref());
let l1_fee_overhead = U256::from_be_slice(r[196..228].as_ref());
let l1_fee_scalar = U256::from_be_slice(r[228..260].as_ref());

Ok(Self {
number,
time,
base_fee,
block_hash,
sequence_number,
batcher_address,
l1_fee_overhead,
l1_fee_scalar,
})
}
}
121 changes: 121 additions & 0 deletions crates/protocol/src/info/ecotone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! Contains ecotone-specific L1 block info types.
use alloc::{format, string::ToString, vec::Vec};
use alloy_primitives::{Address, Bytes, B256, U256};

use crate::DecodeError;

/// Represents the fields within an Ecotone L1 block info transaction.
///
/// Ecotone Binary Format
/// +---------+--------------------------+
/// | Bytes | Field |
/// +---------+--------------------------+
/// | 4 | Function signature |
/// | 4 | BaseFeeScalar |
/// | 4 | BlobBaseFeeScalar |
/// | 8 | SequenceNumber |
/// | 8 | Timestamp |
/// | 8 | L1BlockNumber |
/// | 32 | BaseFee |
/// | 32 | BlobBaseFee |
/// | 32 | BlockHash |
/// | 32 | BatcherHash |
/// +---------+--------------------------+
#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct L1BlockInfoEcotone {
/// The current L1 origin block number
pub number: u64,
/// The current L1 origin block's timestamp
pub time: u64,
/// The current L1 origin block's basefee
pub base_fee: u64,
/// The current L1 origin block's hash
pub block_hash: B256,
/// The current sequence number
pub sequence_number: u64,
/// The address of the batch submitter
pub batcher_address: Address,
/// The current blob base fee on L1
pub blob_base_fee: u128,
/// The fee scalar for L1 blobspace data
pub blob_base_fee_scalar: u32,
/// The fee scalar for L1 data
pub base_fee_scalar: u32,
}

impl L1BlockInfoEcotone {
/// The type byte identifier for the L1 scalar format in Ecotone.
pub const L1_SCALAR: u8 = 1;

/// The length of an L1 info transaction in Ecotone.
pub const L1_INFO_TX_LEN: usize = 4 + 32 * 5;

/// The 4 byte selector of "setL1BlockValuesEcotone()"
pub const L1_INFO_TX_SELECTOR: [u8; 4] = [0x44, 0x0a, 0x5e, 0x20];

/// Encodes the [L1BlockInfoEcotone] object into Ethereum transaction calldata.
pub fn encode_calldata(&self) -> Bytes {
let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN);
buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref());
buf.extend_from_slice(self.base_fee_scalar.to_be_bytes().as_ref());
buf.extend_from_slice(self.blob_base_fee_scalar.to_be_bytes().as_ref());
buf.extend_from_slice(self.sequence_number.to_be_bytes().as_ref());
buf.extend_from_slice(self.time.to_be_bytes().as_ref());
buf.extend_from_slice(self.number.to_be_bytes().as_ref());
buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_ref());
buf.extend_from_slice(U256::from(self.blob_base_fee).to_be_bytes::<32>().as_ref());
buf.extend_from_slice(self.block_hash.as_ref());
buf.extend_from_slice(self.batcher_address.into_word().as_ref());
buf.into()
}

/// Decodes the [L1BlockInfoEcotone] object from ethereum transaction calldata.
pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
if r.len() != Self::L1_INFO_TX_LEN {
return Err(DecodeError::InvalidLength(format!(
"Invalid calldata length for Ecotone L1 info transaction, expected {}, got {}",
Self::L1_INFO_TX_LEN,
r.len()
)));
}
let base_fee_scalar = u32::from_be_bytes(r[4..8].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for base fee scalar".to_string())
})?);
let blob_base_fee_scalar = u32::from_be_bytes(r[8..12].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for blob base fee scalar".to_string())
})?);
let sequence_number = u64::from_be_bytes(r[12..20].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for sequence number".to_string())
})?);
let timestamp =
u64::from_be_bytes(r[20..28].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for timestamp".to_string())
})?);
let l1_block_number = u64::from_be_bytes(r[28..36].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for L1 block number".to_string())
})?);
let base_fee =
u64::from_be_bytes(r[60..68].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for base fee".to_string())
})?);
let blob_base_fee = u128::from_be_bytes(r[84..100].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for blob base fee".to_string())
})?);
let block_hash = B256::from_slice(r[100..132].as_ref());
let batcher_address = Address::from_slice(r[144..164].as_ref());

Ok(Self {
number: l1_block_number,
time: timestamp,
base_fee,
block_hash,
sequence_number,
batcher_address,
blob_base_fee,
blob_base_fee_scalar,
base_fee_scalar,
})
}
}
54 changes: 54 additions & 0 deletions crates/protocol/src/info/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Contains error types specific to the L1 block info transaction.
use alloc::string::String;

/// An error type for parsing L1 block info transactions.
#[derive(Debug, thiserror::Error, Copy, Clone)]
pub enum BlockInfoError {
/// Failed to parse the L1 blob base fee scalar.
L1BlobBaseFeeScalar,
/// Failed to parse the base fee scalar.
BaseFeeScalar,
/// Failed to parse the EIP-1559 denominator.
Eip1559Denominator,
/// Failed to parse the EIP-1559 elasticity parameter.
Eip1559Elasticity,
}

impl core::fmt::Display for BlockInfoError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::L1BlobBaseFeeScalar => {
write!(f, "Failed to parse the L1 blob base fee scalar")
}
Self::BaseFeeScalar => write!(f, "Failed to parse the base fee scalar"),
Self::Eip1559Denominator => {
write!(f, "Failed to parse the EIP-1559 denominator")
}
Self::Eip1559Elasticity => {
write!(f, "Failed to parse the EIP-1559 elasticity parameter")
}
}
}
}

/// An error decoding an L1 block info transaction.
#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
/// Invalid selector for the L1 info transaction
InvalidSelector,
/// Parse error for the L1 info transaction
ParseError(String),
/// Invalid length for the L1 info transaction
InvalidLength(String),
}

impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidSelector => write!(f, "Invalid L1 info transaction selector"),
Self::ParseError(msg) => write!(f, "Parse error: {}", msg),
Self::InvalidLength(msg) => write!(f, "Invalid data length: {}", msg), /* Handle display for length errors */
}
}
}
13 changes: 13 additions & 0 deletions crates/protocol/src/info/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Module containing L1 Attributes types (aka the L1 block info transaction).
mod variant;
pub use variant::L1BlockInfoTx;

mod bedrock;
pub use bedrock::L1BlockInfoBedrock;

mod ecotone;
pub use ecotone::L1BlockInfoEcotone;

mod errors;
pub use errors::{BlockInfoError, DecodeError};
Loading

0 comments on commit be93ab5

Please sign in to comment.