Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions crates/protocol/src/batch/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Span Batch Errors

/// Span Batch Errors
#[derive(Debug, derive_more::Display, Clone, PartialEq, Eq)]
pub enum SpanBatchError {
/// The span batch is too big
#[display("The span batch is too big.")]
TooBigSpanBatchSize,
/// The bit field is too long
#[display("The bit field is too long")]
BitfieldTooLong,
/// Empty Span Batch
#[display("Empty span batch")]
EmptySpanBatch,
/// Missing L1 origin
#[display("Missing L1 origin")]
MissingL1Origin,
/// Decoding errors
#[display("Span batch decoding error: {_0}")]
Decoding(SpanDecodingError),
}

impl From<SpanDecodingError> for SpanBatchError {
fn from(err: SpanDecodingError) -> Self {
Self::Decoding(err)
}
}

impl core::error::Error for SpanBatchError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Decoding(err) => Some(err),
_ => None,
}
}
}

/// Decoding Error
#[derive(Debug, derive_more::Display, Clone, PartialEq, Eq)]
pub enum SpanDecodingError {
/// Failed to decode relative timestamp
#[display("Failed to decode relative timestamp")]
RelativeTimestamp,
/// Failed to decode L1 origin number
#[display("Failed to decode L1 origin number")]
L1OriginNumber,
/// Failed to decode parent check
#[display("Failed to decode parent check")]
ParentCheck,
/// Failed to decode L1 origin check
#[display("Failed to decode L1 origin check")]
L1OriginCheck,
/// Failed to decode block count
#[display("Failed to decode block count")]
BlockCount,
/// Failed to decode block tx counts
#[display("Failed to decode block tx counts")]
BlockTxCounts,
/// Failed to decode transaction nonces
#[display("Failed to decode transaction nonces")]
TxNonces,
/// Mismatch in length between the transaction type and signature arrays in a span batch
/// transaction payload.
#[display("Mismatch in length between the transaction type and signature arrays")]
TypeSignatureLenMismatch,
/// Invalid transaction type
#[display("Invalid transaction type")]
InvalidTransactionType,
/// Invalid transaction data
#[display("Invalid transaction data")]
InvalidTransactionData,
/// Invalid transaction signature
#[display("Invalid transaction signature")]
InvalidTransactionSignature,
}

impl core::error::Error for SpanDecodingError {}
6 changes: 6 additions & 0 deletions crates/protocol/src/batch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
mod r#type;
pub use r#type::*;

mod errors;
pub use errors::{SpanBatchError, SpanDecodingError};

mod signature;
pub use signature::SpanBatchSignature;

mod validity;
pub use validity::BatchValidity;

Expand Down
42 changes: 42 additions & 0 deletions crates/protocol/src/batch/signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! This module contains the [SpanBatchSignature] type, which represents the ECDSA signature of a
//! transaction within a span batch.

use crate::{SpanBatchError, SpanDecodingError};
use alloy_primitives::{Signature, U256};

/// The ECDSA signature of a transaction within a span batch.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpanBatchSignature {
pub(crate) v: u64,
pub(crate) r: U256,
pub(crate) s: U256,
}

impl From<Signature> for SpanBatchSignature {
fn from(value: Signature) -> Self {
Self { v: value.v().to_u64(), r: value.r(), s: value.s() }
}
}

impl TryFrom<SpanBatchSignature> for Signature {
type Error = SpanBatchError;

fn try_from(value: SpanBatchSignature) -> Result<Self, Self::Error> {
Self::from_rs_and_parity(value.r, value.s, value.v)
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionSignature))
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::Signature;

#[test]
fn test_span_batch_signature_conversion() {
let signature = Signature::from_rs_and_parity(U256::from(1), U256::from(2), 27).unwrap();
let span_batch_signature = SpanBatchSignature::from(signature);
let converted_signature = Signature::try_from(span_batch_signature).unwrap();
assert_eq!(signature, converted_signature);
}
}
9 changes: 6 additions & 3 deletions crates/protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ extern crate alloc;

mod batch;
pub use batch::{
BatchType, BatchValidationProvider, BatchValidity, SingleBatch, SINGLE_BATCH_TYPE,
SPAN_BATCH_TYPE,
BatchType, BatchValidationProvider, BatchValidity, SingleBatch, SpanBatchError,
SpanBatchSignature, SpanDecodingError, SINGLE_BATCH_TYPE, SPAN_BATCH_TYPE,
};

mod block;
Expand All @@ -30,7 +30,10 @@ mod iter;
pub use iter::FrameIter;

mod utils;
pub use utils::{starts_with_2718_deposit, to_system_config, OpBlockConversionError};
pub use utils::{
convert_v_to_y_parity, is_protected_v, read_tx_data, starts_with_2718_deposit,
to_system_config, OpBlockConversionError,
};

mod channel;
pub use channel::{
Expand Down
142 changes: 141 additions & 1 deletion crates/protocol/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
//! Utility methods used by protocol types.

use crate::{block_info::DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoTx};
use alloc::vec::Vec;
use alloy_consensus::{TxEnvelope, TxType};
use alloy_primitives::B256;
use alloy_rlp::{Buf, Header};
use op_alloy_consensus::{OpBlock, OpTxEnvelope};
use op_alloy_genesis::{RollupConfig, SystemConfig};

use crate::{
block_info::DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoTx, SpanBatchError,
SpanDecodingError,
};

/// Returns if the given `value` is a deposit transaction.
pub fn starts_with_2718_deposit<B>(value: &B) -> bool
where
Expand Down Expand Up @@ -224,9 +231,85 @@ fn u24(input: &[u8], idx: u32) -> u32 {
+ (u32::from(input[(idx + 2) as usize]) << 16)
}

/// Reads transaction data from a reader.
#[allow(unused)]
pub fn read_tx_data(r: &mut &[u8]) -> Result<(Vec<u8>, TxType), SpanBatchError> {
let mut tx_data = Vec::new();
let first_byte =
*r.first().ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?;
let mut tx_type = 0;
if first_byte <= 0x7F {
// EIP-2718: Non-legacy tx, so write tx type
tx_type = first_byte;
tx_data.push(tx_type);
r.advance(1);
}

// Read the RLP header with a different reader pointer. This prevents the initial pointer from
// being advanced in the case that what we read is invalid.
let rlp_header = Header::decode(&mut (**r).as_ref())
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?;

let tx_payload = if rlp_header.list {
// Grab the raw RLP for the transaction data from `r`. It was unaffected since we copied it.
let payload_length_with_header = rlp_header.payload_length + rlp_header.length();
let payload = r[0..payload_length_with_header].to_vec();
r.advance(payload_length_with_header);
Ok(payload)
} else {
Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))
}?;
tx_data.extend_from_slice(&tx_payload);

Ok((
tx_data,
tx_type
.try_into()
.map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType))?,
))
}

/// Converts a `v` value to a y parity bit, from the transaaction type.
#[allow(unused)]
pub const fn convert_v_to_y_parity(v: u64, tx_type: TxType) -> Result<bool, SpanBatchError> {
match tx_type {
TxType::Legacy => {
if v != 27 && v != 28 {
// EIP-155: v = 2 * chain_id + 35 + yParity
Ok((v - 35) & 1 == 1)
} else {
// Unprotected legacy txs must have v = 27 or 28
Ok(v - 27 == 1)
}
}
TxType::Eip2930 | TxType::Eip1559 => Ok(v == 1),
_ => Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType)),
}
}

/// Checks if the signature of the passed [TxEnvelope] is protected.
#[allow(unused)]
pub const fn is_protected_v(tx: &TxEnvelope) -> bool {
match tx {
TxEnvelope::Legacy(tx) => {
let v = tx.signature().v().to_u64();
if 64 - v.leading_zeros() <= 8 {
return v != 27 && v != 28 && v != 1 && v != 0;
}
// anything not 27 or 28 is considered protected
true
}
_ => true,
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_consensus::{
Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy,
};
use alloy_primitives::{b256, Signature};
use alloy_sol_types::{sol, SolCall};
use revm::{
db::BenchmarkDB,
Expand All @@ -237,6 +320,63 @@ mod tests {

use rstest::rstest;

#[test]
fn test_convert_v_to_y_parity() {
assert_eq!(convert_v_to_y_parity(27, TxType::Legacy), Ok(false));
assert_eq!(convert_v_to_y_parity(28, TxType::Legacy), Ok(true));
assert_eq!(convert_v_to_y_parity(36, TxType::Legacy), Ok(true));
assert_eq!(convert_v_to_y_parity(37, TxType::Legacy), Ok(false));
assert_eq!(convert_v_to_y_parity(1, TxType::Eip2930), Ok(true));
assert_eq!(convert_v_to_y_parity(1, TxType::Eip1559), Ok(true));
assert_eq!(
convert_v_to_y_parity(1, TxType::Eip4844),
Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType))
);
assert_eq!(
convert_v_to_y_parity(0, TxType::Eip7702),
Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType))
);
}

#[test]
fn test_is_protected_v() {
let sig = Signature::test_signature();
assert!(!is_protected_v(&TxEnvelope::Legacy(Signed::new_unchecked(
TxLegacy::default(),
sig,
Default::default(),
))));
let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
let v = 27;
let valid_sig = Signature::from_scalars_and_parity(r, s, v).unwrap();
assert!(!is_protected_v(&TxEnvelope::Legacy(Signed::new_unchecked(
TxLegacy::default(),
valid_sig,
Default::default(),
))));
assert!(is_protected_v(&TxEnvelope::Eip2930(Signed::new_unchecked(
TxEip2930::default(),
sig,
Default::default(),
))));
assert!(is_protected_v(&TxEnvelope::Eip1559(Signed::new_unchecked(
TxEip1559::default(),
sig,
Default::default(),
))));
assert!(is_protected_v(&TxEnvelope::Eip4844(Signed::new_unchecked(
TxEip4844Variant::TxEip4844(TxEip4844::default()),
sig,
Default::default(),
))));
assert!(is_protected_v(&TxEnvelope::Eip7702(Signed::new_unchecked(
TxEip7702::default(),
sig,
Default::default(),
))));
}

#[rstest]
#[case::empty(&[], 0)]
#[case::thousand_zeros(&[0; 1000], 21)]
Expand Down