Skip to content
Merged
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
151 changes: 150 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion crates/consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ default = []
[dependencies]
alethia-reth-chainspec = { path = "../chainspec", default-features = false }
alethia-reth-evm = { path = "../evm" }
alethia-reth-primitives = { path = "../primitives" }
alloy-consensus = { workspace = true }
alloy-hardforks = { workspace = true }
alloy-primitives = { workspace = true }
Expand Down
39 changes: 2 additions & 37 deletions crates/consensus/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use reth_primitives_traits::{
use crate::eip4396::{SHASTA_INITIAL_BASE_FEE, calculate_next_block_eip4396_base_fee};
use alethia_reth_chainspec::{hardfork::TaikoHardforks, spec::TaikoChainSpec};
use alethia_reth_evm::alloy::TAIKO_GOLDEN_TOUCH_ADDRESS;
use alethia_reth_primitives::SHASTA_EXTRA_DATA_LEN;

sol! {
function anchor(bytes32, bytes32, uint64, uint32) external;
Expand Down Expand Up @@ -145,15 +144,6 @@ where
}

validate_header_extra_data(header, MAXIMUM_EXTRA_DATA_SIZE)?;
if self.chain_spec.is_shasta_active(header.timestamp()) &&
header.extra_data().len() != SHASTA_EXTRA_DATA_LEN
{
return Err(ConsensusError::Other(format!(
"Shasta extra-data length invalid: {} != {}",
header.extra_data().len(),
SHASTA_EXTRA_DATA_LEN
)));
}
validate_header_gas(header)?;
validate_header_base_fee(header, &self.chain_spec)
}
Expand Down Expand Up @@ -355,11 +345,8 @@ fn validate_input_selector(

#[cfg(test)]
mod test {
use alethia_reth_chainspec::{TAIKO_DEVNET, spec::TaikoDevnetConfigExt};
use alethia_reth_primitives::SHASTA_EXTRA_DATA_LEN;
use alloy_consensus::Header;
use alloy_primitives::{B256, Bytes};
use std::sync::Arc;
use alloy_primitives::U64;

use super::validate_input_selector;

Expand All @@ -371,7 +358,7 @@ mod test {

assert!(validate_against_parent_eip4396_base_fee(&header).is_err());

header.base_fee_per_gas = Some(1);
header.base_fee_per_gas = Some(U64::random().to::<u64>());
assert!(validate_against_parent_eip4396_base_fee(&header).is_ok());
}

Expand Down Expand Up @@ -428,26 +415,4 @@ mod test {
let base_fee = calculate_next_block_eip4396_base_fee(&parent, BLOCK_TIME_TARGET);
assert!(base_fee < 1_000_000_000, "Base fee should decrease when below target");
}

#[test]
fn rejects_invalid_shasta_extra_data_len() {
#[derive(Debug)]
struct NoopReader;
impl TaikoBlockReader for NoopReader {
fn block_timestamp_by_hash(&self, _: B256) -> Option<u64> {
None
}
}

let spec = (*TAIKO_DEVNET).clone().as_ref().clone_with_devnet_shasta_timestamp(0).unwrap();
let consensus = TaikoBeaconConsensus::new(Arc::new(spec), Arc::new(NoopReader));

let mut header = Header::default();
header.timestamp = 1;
header.base_fee_per_gas = Some(1);
header.extra_data = Bytes::copy_from_slice(&[0u8; SHASTA_EXTRA_DATA_LEN - 1]);

let sealed = SealedHeader::seal_slow(header);
assert!(consensus.validate_header(&sealed).is_err());
}
}
5 changes: 5 additions & 0 deletions crates/db/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ tables! {
type Key = BlockNumber;
type Value = BlockNumber;
}

table BatchToLastBlock {
type Key = BlockNumber;
type Value = BlockNumber;
}
}

#[cfg(test)]
Expand Down
60 changes: 7 additions & 53 deletions crates/primitives/src/extra_data.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,20 @@
//! Helpers for decoding Taiko-specific block `extraData` fields.

use std::{error::Error, fmt::Display};

/// Index of the end-of-proposal flag in Shasta extra data.
pub const SHASTA_EXTRA_DATA_END_OF_PROPOSAL_INDEX: usize = 7;

/// Exact number of bytes required for Shasta extra data.
pub const SHASTA_EXTRA_DATA_LEN: usize = 8;

/// Error indicating that the Shasta extra data has an invalid length.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShastaExtraDataError {
/// The actual length of the provided extra data.
pub got: usize,
/// The expected length of the Shasta extra data.
pub expected: usize,
}

impl Display for ShastaExtraDataError {
/// Formats the error message indicating the invalid length of Shasta extra data.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid Shasta extra data length: {} != {}", self.got, self.expected)
}
}

/// Implements the standard Error trait for ShastaExtraDataError.
impl Error for ShastaExtraDataError {}
/// Minimum number of bytes required for Shasta extra data.
pub const SHASTA_EXTRA_DATA_LEN: usize = 7;

/// Returns the base fee sharing percentage encoded in Shasta extra data.
pub fn decode_shasta_basefee_sharing_pctg(extra: &[u8]) -> u8 {
extra.first().copied().unwrap_or_default()
}

/// Returns the proposal ID and end-of-proposal flag encoded in Shasta extra data.
pub fn decode_shasta_proposal_id(extra: &[u8]) -> Result<(u64, bool), ShastaExtraDataError> {
if extra.len() != SHASTA_EXTRA_DATA_LEN {
return Err(ShastaExtraDataError { got: extra.len(), expected: SHASTA_EXTRA_DATA_LEN });
/// Returns the proposal ID encoded in Shasta extra data (bytes 1..6, big-endian).
pub fn decode_shasta_proposal_id(extra: &[u8]) -> Option<u64> {
if extra.len() < SHASTA_EXTRA_DATA_LEN {
return None;
}

let mut buf = [0u8; 8];
buf[2..].copy_from_slice(&extra[1..7]);
let proposal_id = u64::from_be_bytes(buf);
let end_of_proposal = extra[SHASTA_EXTRA_DATA_END_OF_PROPOSAL_INDEX] != 0;
Ok((proposal_id, end_of_proposal))
}

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

#[test]
fn decodes_shasta_proposal_id_and_end_of_proposal() {
let extra = [0x2a, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x01];
let (proposal_id, end_of_proposal) = decode_shasta_proposal_id(&extra).unwrap();
assert_eq!(proposal_id, 0x010203040506);
assert!(end_of_proposal);
}

#[test]
fn rejects_invalid_shasta_extra_data_len() {
let err = decode_shasta_proposal_id(&[0x01, 0x02, 0x03]).unwrap_err();
assert_eq!(err.expected, SHASTA_EXTRA_DATA_LEN);
assert_eq!(err.got, 3);
}
Some(u64::from_be_bytes(buf))
}
3 changes: 1 addition & 2 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ pub mod extra_data;
pub mod payload;

pub use extra_data::{
SHASTA_EXTRA_DATA_END_OF_PROPOSAL_INDEX, SHASTA_EXTRA_DATA_LEN, ShastaExtraDataError,
decode_shasta_basefee_sharing_pctg, decode_shasta_proposal_id,
SHASTA_EXTRA_DATA_LEN, decode_shasta_basefee_sharing_pctg, decode_shasta_proposal_id,
};
3 changes: 3 additions & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
reth-provider = { workspace = true, features = ["test-utils"] }
17 changes: 16 additions & 1 deletion crates/rpc/src/eth/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use alethia_reth_block::{
};
use alethia_reth_chainspec::spec::TaikoChainSpec;
use alethia_reth_db::model::{
STORED_L1_HEAD_ORIGIN_KEY, StoredL1HeadOriginTable, StoredL1OriginTable,
BatchToLastBlock, STORED_L1_HEAD_ORIGIN_KEY, StoredL1HeadOriginTable, StoredL1OriginTable,
};
use alethia_reth_evm::factory::TaikoEvmFactory;
use alethia_reth_primitives::{
Expand Down Expand Up @@ -67,6 +67,8 @@ pub trait TaikoAuthExtApi<T: RpcObject> {
async fn update_l1_origin(&self, l1_origin: RpcL1Origin) -> RpcResult<Option<RpcL1Origin>>;
#[method(name = "setL1OriginSignature")]
async fn set_l1_origin_signature(&self, id: U256, signature: Bytes) -> RpcResult<RpcL1Origin>;
#[method(name = "setBatchToLastBlock")]
async fn set_batch_to_last_block(&self, batch_id: U256, block_number: U256) -> RpcResult<u64>;
#[method(name = "txPoolContentWithMinTip")]
async fn tx_pool_content_with_min_tip(
&self,
Expand Down Expand Up @@ -171,6 +173,19 @@ where
Ok(l1_origin.into_rpc())
}

/// Sets the mapping from batch ID to its last block number in the database.
async fn set_batch_to_last_block(&self, batch_id: U256, block_number: U256) -> RpcResult<u64> {
let tx = self
.provider
.database_provider_rw()
.map_err(|_| EthApiError::InternalEthError)?
.into_tx();
tx.put::<BatchToLastBlock>(batch_id.to(), block_number.to())
.map_err(|_| EthApiError::InternalEthError)?;
tx.commit().map_err(|_| EthApiError::InternalEthError)?;
Ok(batch_id.to())
}

/// Updates the L1 origin in the database.
async fn update_l1_origin(&self, l1_origin: RpcL1Origin) -> RpcResult<Option<RpcL1Origin>> {
let tx = self
Expand Down
9 changes: 9 additions & 0 deletions crates/rpc/src/eth/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use jsonrpsee_types::error::{ErrorCode, ErrorObjectOwned};
pub enum TaikoApiError {
#[error("not found")]
GethNotFound,
#[error(
"proposal last block uncertain: BatchToLastBlockID missing and no newer proposal observed"
)]
ProposalLastBlockUncertain,
}

impl From<TaikoApiError> for ErrorObjectOwned {
Expand All @@ -16,6 +20,11 @@ impl From<TaikoApiError> for ErrorObjectOwned {
"not found",
None::<()>,
),
TaikoApiError::ProposalLastBlockUncertain => ErrorObjectOwned::owned(
ErrorCode::ServerError(-32005).code(),
"proposal last block uncertain: BatchToLastBlockID missing and no newer proposal observed",
None::<()>,
),
}
}
}
Loading