Skip to content

Commit ac17740

Browse files
ManuelBilbaotomip01gianbelinchexqftilitteri
authored
feat(l1,l2): support fusaka blobs cell_proofs (#5121)
**Motivation** <!-- Why does this pull request exist? What are its goals? --> Reapply #4814 **Description** <!-- A clear and concise general description of the changes this PR introduces --> <!-- Link to issues: Resolves #111, Resolves #222 --> --------- Co-authored-by: Tomás Paradelo <[email protected]> Co-authored-by: Tomás Paradelo <[email protected]> Co-authored-by: Gianbelinche <[email protected]> Co-authored-by: Estéfano Bargas <[email protected]> Co-authored-by: ilitteri <[email protected]> Co-authored-by: Ivan Litteri <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 02c4468 commit ac17740

File tree

22 files changed

+339
-59
lines changed

22 files changed

+339
-59
lines changed

cmd/ethrex/l2/options.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ impl TryFrom<SequencerOptions> for SequencerConfig {
189189
maximum_allowed_max_fee_per_blob_gas: opts
190190
.eth_opts
191191
.maximum_allowed_max_fee_per_blob_gas,
192+
osaka_activation_time: opts.eth_opts.osaka_activation_time,
192193
},
193194
l1_watcher: L1WatcherConfig {
194195
bridge_address: opts
@@ -339,6 +340,13 @@ pub struct EthOptions {
339340
help_heading = "Eth options"
340341
)]
341342
pub max_retry_delay: u64,
343+
#[clap(
344+
long,
345+
value_name = "UINT64",
346+
env = "ETHREX_OSAKA_ACTIVATION_TIME",
347+
help = "Block timestamp at which the Osaka fork is activated on L1. If not set, it will assume Osaka is already active."
348+
)]
349+
pub osaka_activation_time: Option<u64>,
342350
}
343351

344352
impl Default for EthOptions {
@@ -353,6 +361,7 @@ impl Default for EthOptions {
353361
backoff_factor: BACKOFF_FACTOR,
354362
min_retry_delay: MIN_RETRY_DELAY,
355363
max_retry_delay: MAX_RETRY_DELAY,
364+
osaka_activation_time: None,
356365
}
357366
}
358367
}

crates/common/crypto/kzg.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ pub fn verify_cell_kzg_proof_batch(
6969
let c_kzg_settings = c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE);
7070
let mut cells = Vec::new();
7171
for blob in blobs {
72-
cells.extend(c_kzg_settings.compute_cells(&(*blob).into())?.into_iter());
72+
let blob: c_kzg::Blob = (*blob).into();
73+
let cells_blob = c_kzg_settings
74+
.compute_cells(&blob)
75+
.map_err(KzgError::CKzg)?;
76+
cells.extend(*cells_blob);
7377
}
7478
c_kzg::KzgSettings::verify_cell_kzg_proof_batch(
7579
c_kzg_settings,
@@ -180,15 +184,35 @@ pub fn verify_kzg_proof(
180184
pub fn blob_to_kzg_commitment_and_proof(blob: &Blob) -> Result<(Commitment, Proof), KzgError> {
181185
let blob: c_kzg::Blob = (*blob).into();
182186

183-
let commitment = c_kzg::KzgSettings::blob_to_kzg_commitment(
184-
c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE),
185-
&blob,
186-
)?;
187-
let commitment_bytes = commitment.to_bytes();
188187
let c_kzg_settings = c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE);
188+
189+
let commitment = c_kzg::KzgSettings::blob_to_kzg_commitment(c_kzg_settings, &blob)?;
190+
191+
let commitment_bytes = commitment.to_bytes();
189192
let proof = c_kzg_settings.compute_blob_kzg_proof(&blob, &commitment_bytes)?;
190193

191194
let proof_bytes = proof.to_bytes();
192195

193196
Ok((commitment_bytes.into_inner(), proof_bytes.into_inner()))
194197
}
198+
199+
#[cfg(feature = "c-kzg")]
200+
pub fn blob_to_commitment_and_cell_proofs(
201+
blob: &Blob,
202+
) -> Result<(Commitment, Vec<Proof>), KzgError> {
203+
let c_kzg_settings = c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE);
204+
205+
let blob: c_kzg::Blob = (*blob).into();
206+
207+
let commitment = c_kzg::KzgSettings::blob_to_kzg_commitment(c_kzg_settings, &blob)?;
208+
209+
let commitment_bytes = commitment.to_bytes();
210+
211+
let (_cells, cell_proofs) = c_kzg_settings
212+
.compute_cells_and_kzg_proofs(&blob)
213+
.map_err(KzgError::CKzg)?;
214+
215+
let cell_proofs = cell_proofs.map(|p| p.to_bytes().into_inner());
216+
217+
Ok((commitment_bytes.into_inner(), cell_proofs.to_vec()))
218+
}

crates/common/types/blobs_bundle.rs

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::ops::AddAssign;
22

33
use crate::serde_utils;
4+
#[cfg(feature = "c-kzg")]
5+
use crate::types::Fork;
46
use crate::types::constants::VERSIONED_HASH_VERSION_KZG;
57
use crate::{Bytes, H256};
68

@@ -80,23 +82,34 @@ impl BlobsBundle {
8082

8183
// In the future we might want to provide a new method that calculates the commitments and proofs using the following.
8284
#[cfg(feature = "c-kzg")]
83-
pub fn create_from_blobs(blobs: &Vec<Blob>) -> Result<Self, BlobsBundleError> {
84-
use ethrex_crypto::kzg::blob_to_kzg_commitment_and_proof;
85+
pub fn create_from_blobs(
86+
blobs: &Vec<Blob>,
87+
wrapper_version: Option<u8>,
88+
) -> Result<Self, BlobsBundleError> {
89+
use ethrex_crypto::kzg::{
90+
blob_to_commitment_and_cell_proofs, blob_to_kzg_commitment_and_proof,
91+
};
8592
let mut commitments = Vec::new();
8693
let mut proofs = Vec::new();
8794

8895
// Populate the commitments and proofs
8996
for blob in blobs {
90-
let (commitment, proof) = blob_to_kzg_commitment_and_proof(blob)?;
91-
commitments.push(commitment);
92-
proofs.push(proof);
97+
if wrapper_version.unwrap_or(0) == 0 {
98+
let (commitment, proof) = blob_to_kzg_commitment_and_proof(blob)?;
99+
commitments.push(commitment);
100+
proofs.push(proof);
101+
} else {
102+
let (commitment, cell_proofs) = blob_to_commitment_and_cell_proofs(blob)?;
103+
commitments.push(commitment);
104+
proofs.extend(cell_proofs);
105+
}
93106
}
94107

95108
Ok(Self {
96109
blobs: blobs.clone(),
97110
commitments,
98111
proofs,
99-
version: 0,
112+
version: wrapper_version.unwrap_or(0),
100113
})
101114
}
102115

@@ -127,6 +140,10 @@ impl BlobsBundle {
127140
return Err(BlobsBundleError::BlobBundleEmptyError);
128141
}
129142

143+
if self.version == 0 && fork >= Fork::Osaka || self.version != 0 && fork < Fork::Osaka {
144+
return Err(BlobsBundleError::InvalidBlobVersionForFork);
145+
}
146+
130147
// Check if the blob versioned hashes and blobs bundle content length mismatch
131148
if blob_count != self.commitments.len()
132149
|| (self.version == 0 && blob_count != self.proofs.len())
@@ -231,6 +248,8 @@ pub enum BlobsBundleError {
231248
BlobToCommitmentAndProofError,
232249
#[error("Max blobs per block exceeded")]
233250
MaxBlobsExceeded,
251+
#[error("Invalid blob version for the current fork")]
252+
InvalidBlobVersionForFork,
234253
#[cfg(feature = "c-kzg")]
235254
#[error("KZG related error: {0}")]
236255
Kzg(#[from] ethrex_crypto::kzg::KzgError),
@@ -259,7 +278,7 @@ mod tests {
259278
})
260279
.collect();
261280

262-
let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs)
281+
let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, None)
263282
.expect("Failed to create blobs bundle");
264283

265284
let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes();
@@ -284,6 +303,78 @@ mod tests {
284303
));
285304
}
286305

306+
#[test]
307+
#[cfg(feature = "c-kzg")]
308+
fn transaction_with_valid_blobs_should_pass_on_osaka() {
309+
let blobs = vec!["Hello, world!".as_bytes(), "Goodbye, world!".as_bytes()]
310+
.into_iter()
311+
.map(|data| {
312+
crate::types::blobs_bundle::blob_from_bytes(data.into())
313+
.expect("Failed to create blob")
314+
})
315+
.collect();
316+
317+
let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, Some(1))
318+
.expect("Failed to create blobs bundle");
319+
320+
let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes();
321+
322+
let tx = crate::types::transaction::EIP4844Transaction {
323+
nonce: 3,
324+
max_priority_fee_per_gas: 0,
325+
max_fee_per_gas: 0,
326+
max_fee_per_blob_gas: 0.into(),
327+
gas: 15_000_000,
328+
to: crate::Address::from_low_u64_be(1), // Normal tx
329+
value: crate::U256::zero(), // Value zero
330+
data: crate::Bytes::default(), // No data
331+
access_list: Default::default(), // No access list
332+
blob_versioned_hashes,
333+
..Default::default()
334+
};
335+
336+
assert!(matches!(
337+
blobs_bundle.validate(&tx, crate::types::Fork::Osaka),
338+
Ok(())
339+
));
340+
}
341+
342+
#[test]
343+
#[cfg(feature = "c-kzg")]
344+
fn transaction_with_invalid_fork_should_fail() {
345+
let blobs = vec!["Hello, world!".as_bytes(), "Goodbye, world!".as_bytes()]
346+
.into_iter()
347+
.map(|data| {
348+
crate::types::blobs_bundle::blob_from_bytes(data.into())
349+
.expect("Failed to create blob")
350+
})
351+
.collect();
352+
353+
let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, Some(1))
354+
.expect("Failed to create blobs bundle");
355+
356+
let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes();
357+
358+
let tx = crate::types::transaction::EIP4844Transaction {
359+
nonce: 3,
360+
max_priority_fee_per_gas: 0,
361+
max_fee_per_gas: 0,
362+
max_fee_per_blob_gas: 0.into(),
363+
gas: 15_000_000,
364+
to: crate::Address::from_low_u64_be(1), // Normal tx
365+
value: crate::U256::zero(), // Value zero
366+
data: crate::Bytes::default(), // No data
367+
access_list: Default::default(), // No access list
368+
blob_versioned_hashes,
369+
..Default::default()
370+
};
371+
372+
assert!(!matches!(
373+
blobs_bundle.validate(&tx, crate::types::Fork::Prague),
374+
Ok(())
375+
));
376+
}
377+
287378
#[test]
288379
#[cfg(feature = "c-kzg")]
289380
fn transaction_with_invalid_proofs_should_fail() {
@@ -396,7 +487,7 @@ mod tests {
396487
let blobs =
397488
std::iter::repeat_n(blob, super::MAX_BLOB_COUNT_ELECTRA + 1).collect::<Vec<_>>();
398489

399-
let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs)
490+
let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, None)
400491
.expect("Failed to create blobs bundle");
401492

402493
let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes();

crates/common/types/transaction.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,7 @@ mod serde_impl {
21672167
pub authorization_list: Option<Vec<AuthorizationTupleEntry>>,
21682168
#[serde(default)]
21692169
pub blob_versioned_hashes: Vec<H256>,
2170+
pub wrapper_version: Option<u8>,
21702171
#[serde(default, with = "crate::serde_utils::bytes::vec")]
21712172
pub blobs: Vec<Bytes>,
21722173
#[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
@@ -2232,6 +2233,7 @@ mod serde_impl {
22322233
authorization_list: None,
22332234
blob_versioned_hashes: vec![],
22342235
blobs: vec![],
2236+
wrapper_version: None,
22352237
chain_id: Some(value.chain_id),
22362238
from: Address::default(),
22372239
}
@@ -2286,6 +2288,7 @@ mod serde_impl {
22862288
authorization_list: None,
22872289
blob_versioned_hashes: value.blob_versioned_hashes,
22882290
blobs: vec![],
2291+
wrapper_version: None,
22892292
chain_id: Some(value.chain_id),
22902293
from: Address::default(),
22912294
}
@@ -2308,10 +2311,11 @@ mod serde_impl {
23082311
})
23092312
.collect();
23102313

2314+
let wrapper_version = value.wrapper_version;
23112315
Ok(Self {
23122316
tx: value.try_into()?,
2313-
wrapper_version: None,
2314-
blobs_bundle: BlobsBundle::create_from_blobs(&blobs)?,
2317+
wrapper_version,
2318+
blobs_bundle: BlobsBundle::create_from_blobs(&blobs, wrapper_version)?,
23152319
})
23162320
}
23172321
}
@@ -2374,6 +2378,7 @@ mod serde_impl {
23742378
),
23752379
blob_versioned_hashes: vec![],
23762380
blobs: vec![],
2381+
wrapper_version: None,
23772382
chain_id: Some(value.chain_id),
23782383
from: Address::default(),
23792384
}
@@ -2401,6 +2406,7 @@ mod serde_impl {
24012406
authorization_list: None,
24022407
blob_versioned_hashes: vec![],
24032408
blobs: vec![],
2409+
wrapper_version: None,
24042410
chain_id: Some(value.chain_id),
24052411
from: value.from,
24062412
}
@@ -2451,6 +2457,7 @@ mod serde_impl {
24512457
authorization_list: None,
24522458
blob_versioned_hashes: vec![],
24532459
blobs: vec![],
2460+
wrapper_version: None,
24542461
chain_id: None,
24552462
input: value.data,
24562463
}
@@ -2481,6 +2488,7 @@ mod serde_impl {
24812488
authorization_list: None,
24822489
blob_versioned_hashes: vec![],
24832490
blobs: vec![],
2491+
wrapper_version: None,
24842492
chain_id: Some(value.chain_id),
24852493
input: value.data,
24862494
}
@@ -2831,6 +2839,7 @@ mod tests {
28312839
}],
28322840
blob_versioned_hashes: Default::default(),
28332841
blobs: Default::default(),
2842+
wrapper_version: None,
28342843
chain_id: Default::default(),
28352844
authorization_list: None,
28362845
};
@@ -2884,6 +2893,7 @@ mod tests {
28842893
}],
28852894
blob_versioned_hashes: Default::default(),
28862895
blobs: Default::default(),
2896+
wrapper_version: None,
28872897
chain_id: Default::default(),
28882898
authorization_list: None,
28892899
};

crates/l2/Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ L1_AUTH_PORT=8551
4545
L1_RPC_ADDRESS=0.0.0.0
4646
L2_RPC_ADDRESS=0.0.0.0
4747
PROOF_COORDINATOR_ADDRESS?=127.0.0.1
48-
ETHREX_BLOCK_PRODUCER_OPERATOR_FEE_PER_GAS?=1000000000 \
48+
ETHREX_BLOCK_PRODUCER_OPERATOR_FEE_PER_GAS?=1000000000
49+
# Timestamp for Osaka activation in the hoodi. Sufficient to start network with Prague for tests.
50+
# Future: initialize network with Osaka activated once supported.
51+
ETHREX_OSAKA_ACTIVATION_TIME?=1761677592
4952

5053
# Matches the ports used by the blockchain/metrics dir
5154
L2_PROMETHEUS_METRICS_PORT = 3702
@@ -140,6 +143,7 @@ init-l2: ## 🚀 Initializes an L2 Lambda ethrex Client
140143
--l1.bridge-address ${DEFAULT_BRIDGE_ADDRESS} \
141144
--l1.on-chain-proposer-address ${DEFAULT_ON_CHAIN_PROPOSER_ADDRESS} \
142145
--eth.rpc-url ${L1_RPC_URL} \
146+
--osaka-activation-time ${ETHREX_OSAKA_ACTIVATION_TIME} \
143147
--block-producer.coinbase-address 0x0007a881CD95B1484fca47615B64803dad620C8d \
144148
--block-producer.base-fee-vault-address 0x000c0d6b7c4516a5b274c51ea331a9410fe69127 \
145149
--block-producer.operator-fee-vault-address 0xd5d2a85751b6F158e5b9B8cD509206A865672362 \
@@ -151,7 +155,7 @@ init-l2: ## 🚀 Initializes an L2 Lambda ethrex Client
151155
init-l2-dev: ## 🚀 Initializes an L1 and L2 Lambda ethrex Client
152156
COMPILE_CONTRACTS=true \
153157
cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- \
154-
l2 --dev
158+
l2 --dev --osaka-activation-time ${ETHREX_OSAKA_ACTIVATION_TIME}
155159

156160
init-metrics: ## 🚀 Initializes Grafana and Prometheus with containers
157161
L1_RPC_URL=${L1_RPC_URL} \

0 commit comments

Comments
 (0)