From 812fa553f2f52a7d1bb346563f7af46d3aacf728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Mon, 9 Dec 2024 10:53:10 +0100 Subject: [PATCH 1/5] Etherlink/Kernel: reject blueprints from the past, DAL case This commit extends the check of !15636 to the case of blueprints imported from the DAL. --- etherlink/kernel_evm/kernel/src/dal.rs | 18 ++++++++++++++---- etherlink/kernel_evm/kernel/src/inbox.rs | 3 +++ etherlink/kernel_evm/kernel/src/parsing.rs | 11 ++++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/etherlink/kernel_evm/kernel/src/dal.rs b/etherlink/kernel_evm/kernel/src/dal.rs index 7c5fb2d7389c..db5a2a43420c 100644 --- a/etherlink/kernel_evm/kernel/src/dal.rs +++ b/etherlink/kernel_evm/kernel/src/dal.rs @@ -4,6 +4,7 @@ use crate::parsing::{parse_unsigned_blueprint_chunk, SequencerBlueprintRes}; use crate::sequencer_blueprint::UnsignedSequencerBlueprint; +use primitive_types::U256; use rlp::{DecoderError, PayloadInfo}; use tezos_evm_logging::{log, Level::*}; use tezos_smart_rollup_host::dal_parameters::RollupDalParameters; @@ -72,9 +73,10 @@ fn rlp_length(data: &[u8]) -> Result { fn parse_unsigned_sequencer_blueprint( host: &mut Host, bytes: &[u8], + head_level: &Option, ) -> (Option, usize) { if let Result::Ok(chunk_length) = rlp_length(bytes) { - match parse_unsigned_blueprint_chunk(&bytes[..chunk_length]) { + match parse_unsigned_blueprint_chunk(&bytes[..chunk_length], head_level) { SequencerBlueprintRes::SequencerBlueprint(unsigned_chunk) => ( Some(ParsedInput::UnsignedSequencerBlueprint(unsigned_chunk)), chunk_length + TAG_SIZE, @@ -95,6 +97,7 @@ fn parse_unsigned_sequencer_blueprint( fn parse_input( host: &mut Host, bytes: &[u8], + head_level: &Option, ) -> (Option, usize) { // The expected format is: @@ -104,7 +107,7 @@ fn parse_input( DAL_PADDING_TAG => (Some(ParsedInput::Padding), TAG_SIZE), DAL_BLUEPRINT_INPUT_TAG => { let bytes = &bytes[TAG_SIZE..]; - parse_unsigned_sequencer_blueprint(host, bytes) + parse_unsigned_sequencer_blueprint(host, bytes, head_level) } invalid_tag => { log!( @@ -122,6 +125,7 @@ fn parse_input( fn parse_slot( host: &mut Host, slot: &[u8], + head_level: &Option, ) -> Vec { // The format of a dal slot is // tagged chunk | tagged chunk | .. | padding @@ -141,7 +145,7 @@ fn parse_slot( if offset >= slot.len() { return buffer; }; - let (next_input, length) = parse_input(host, &slot[offset..]); + let (next_input, length) = parse_input(host, &slot[offset..], head_level); match next_input { None => return buffer, // Once an unparsable input has been read, @@ -161,6 +165,7 @@ fn parse_slot( pub fn fetch_and_parse_sequencer_blueprint_from_dal( host: &mut Host, params: &RollupDalParameters, + head_level: &Option, slot_index: u8, published_level: u32, ) -> Option> { @@ -177,7 +182,7 @@ pub fn fetch_and_parse_sequencer_blueprint_from_dal( // size, we need to remove this padding before parsing the // slot as a blueprint chunk. - let chunks = parse_slot(host, &slot); + let chunks = parse_slot(host, &slot, head_level); log!( host, Debug, @@ -353,6 +358,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, + &None, 0, published_level, ); @@ -392,6 +398,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, + &None, 0, published_level, ); @@ -457,6 +464,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, + &None, 0, published_level, ); @@ -534,6 +542,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, + &None, 0, published_level, ); @@ -547,6 +556,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, + &None, 0, published_level, ); diff --git a/etherlink/kernel_evm/kernel/src/inbox.rs b/etherlink/kernel_evm/kernel/src/inbox.rs index ddf7b9a50b71..8cf2def8fe40 100644 --- a/etherlink/kernel_evm/kernel/src/inbox.rs +++ b/etherlink/kernel_evm/kernel/src/inbox.rs @@ -359,6 +359,8 @@ impl InputHandler for SequencerInput { }) => { log!(host, Debug, "Importing {} DAL signals", &signals.0.len()); let params = host.reveal_dal_parameters(); + let head_level: Option = + crate::block_storage::read_current_number(host).ok(); for signal in signals.0.iter() { let published_level = signal.published_level; let slot_indices = &signal.slot_indices; @@ -374,6 +376,7 @@ impl InputHandler for SequencerInput { fetch_and_parse_sequencer_blueprint_from_dal( host, ¶ms, + &head_level, *slot_index, published_level, ) diff --git a/etherlink/kernel_evm/kernel/src/parsing.rs b/etherlink/kernel_evm/kernel/src/parsing.rs index 30a0d1d3dc38..d25876ff8b15 100644 --- a/etherlink/kernel_evm/kernel/src/parsing.rs +++ b/etherlink/kernel_evm/kernel/src/parsing.rs @@ -309,7 +309,10 @@ pub struct SequencerParsingContext { pub head_level: Option, } -pub fn parse_unsigned_blueprint_chunk(bytes: &[u8]) -> SequencerBlueprintRes { +pub fn parse_unsigned_blueprint_chunk( + bytes: &[u8], + head_level: &Option, +) -> SequencerBlueprintRes { // Parse an unsigned sequencer blueprint match UnsignedSequencerBlueprint::from_rlp_bytes(bytes).ok() { None => SequencerBlueprintRes::Unparsable, @@ -318,6 +321,12 @@ pub fn parse_unsigned_blueprint_chunk(bytes: &[u8]) -> SequencerBlueprintRes { return SequencerBlueprintRes::InvalidNumberOfChunks; } + if let Some(head_level) = head_level { + if unsigned_seq_blueprint.number.le(head_level) { + return SequencerBlueprintRes::InvalidNumber; + } + } + SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) } } -- GitLab From 11039105b8ae291e1afa03ada585899f1d3512eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Mon, 9 Dec 2024 10:54:25 +0100 Subject: [PATCH 2/5] Etherlink/Kernel: factorize blueprint checks This commit factorizes the two checks performed on blueprints, signed or not, at parsing time: - the number of chunks is within the limit, and - the blueprint is not too old. --- etherlink/kernel_evm/kernel/src/parsing.rs | 73 +++++++++++----------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/etherlink/kernel_evm/kernel/src/parsing.rs b/etherlink/kernel_evm/kernel/src/parsing.rs index d25876ff8b15..813bcc848aab 100644 --- a/etherlink/kernel_evm/kernel/src/parsing.rs +++ b/etherlink/kernel_evm/kernel/src/parsing.rs @@ -309,6 +309,23 @@ pub struct SequencerParsingContext { pub head_level: Option, } +fn check_unsigned_blueprint_chunk( + unsigned_seq_blueprint: UnsignedSequencerBlueprint, + head_level: &Option, +) -> SequencerBlueprintRes { + if MAXIMUM_NUMBER_OF_CHUNKS < unsigned_seq_blueprint.nb_chunks { + return SequencerBlueprintRes::InvalidNumberOfChunks; + } + + if let Some(head_level) = head_level { + if unsigned_seq_blueprint.number.le(head_level) { + return SequencerBlueprintRes::InvalidNumber; + } + } + + SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) +} + pub fn parse_unsigned_blueprint_chunk( bytes: &[u8], head_level: &Option, @@ -317,17 +334,7 @@ pub fn parse_unsigned_blueprint_chunk( match UnsignedSequencerBlueprint::from_rlp_bytes(bytes).ok() { None => SequencerBlueprintRes::Unparsable, Some(unsigned_seq_blueprint) => { - if MAXIMUM_NUMBER_OF_CHUNKS < unsigned_seq_blueprint.nb_chunks { - return SequencerBlueprintRes::InvalidNumberOfChunks; - } - - if let Some(head_level) = head_level { - if unsigned_seq_blueprint.number.le(head_level) { - return SequencerBlueprintRes::InvalidNumber; - } - } - - SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) + check_unsigned_blueprint_chunk(unsigned_seq_blueprint, head_level) } } } @@ -340,32 +347,28 @@ pub fn parse_blueprint_chunk( // Parse the sequencer blueprint match SequencerBlueprint::from_rlp_bytes(bytes).ok() { None => SequencerBlueprintRes::Unparsable, - Some(seq_blueprint) => { - // Creates and encodes the unsigned blueprint: - let unsigned_seq_blueprint: UnsignedSequencerBlueprint = - (&seq_blueprint).into(); - if MAXIMUM_NUMBER_OF_CHUNKS < unsigned_seq_blueprint.nb_chunks { - return SequencerBlueprintRes::InvalidNumberOfChunks; - } - - if let Some(head_level) = head_level { - if unsigned_seq_blueprint.number.le(head_level) { - return SequencerBlueprintRes::InvalidNumber; + Some(SequencerBlueprint { + blueprint, + signature, + }) => match check_unsigned_blueprint_chunk(blueprint, head_level) { + SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) => { + let bytes = unsigned_seq_blueprint.rlp_bytes().to_vec(); + + let correctly_signed = sequencer + .verify_signature(&signature.clone().into(), &bytes) + .unwrap_or(false); + + if correctly_signed { + SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) + } else { + SequencerBlueprintRes::InvalidSignature } } - - let bytes = unsigned_seq_blueprint.rlp_bytes().to_vec(); - - let correctly_signed = sequencer - .verify_signature(&seq_blueprint.signature.clone().into(), &bytes) - .unwrap_or(false); - - if correctly_signed { - SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) - } else { - SequencerBlueprintRes::InvalidSignature - } - } + res @ (SequencerBlueprintRes::InvalidNumberOfChunks + | SequencerBlueprintRes::InvalidSignature + | SequencerBlueprintRes::InvalidNumber + | SequencerBlueprintRes::Unparsable) => res, + }, } } -- GitLab From 42cae90b79860af64ee56f0d877a1c278163955f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Mon, 9 Dec 2024 11:10:29 +0100 Subject: [PATCH 3/5] Etherlink/Kernel/Tests: let caller specify BP number --- etherlink/kernel_evm/kernel/src/dal.rs | 14 +++++++------- etherlink/kernel_evm/kernel/src/stage_one.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/etherlink/kernel_evm/kernel/src/dal.rs b/etherlink/kernel_evm/kernel/src/dal.rs index db5a2a43420c..4e3e9ebae47e 100644 --- a/etherlink/kernel_evm/kernel/src/dal.rs +++ b/etherlink/kernel_evm/kernel/src/dal.rs @@ -268,11 +268,11 @@ pub mod tests { pub fn chunk_blueprint( blueprint: BlueprintWithDelayedHashes, + number: U256, ) -> Vec { let bytes = blueprint.rlp_bytes(); let chunks = bytes.chunks(MAX_CHUNK_SIZE); let nb_chunks = chunks.len(); - let number = U256::zero(); chunks .enumerate() .map(|(chunk_index, chunk)| UnsignedSequencerBlueprint { @@ -344,7 +344,7 @@ pub mod tests { let mut host = MockKernelHost::default(); let blueprint = dummy_big_blueprint(100); - let chunks = chunk_blueprint(blueprint); + let chunks = chunk_blueprint(blueprint, 0.into()); assert!( chunks.len() >= 2, "Blueprint is composed of {} chunks, but at least two are required for the test to make sense", @@ -376,7 +376,7 @@ pub mod tests { let mut host = MockKernelHost::default(); let blueprint = dummy_big_blueprint(100); - let chunks = chunk_blueprint(blueprint); + let chunks = chunk_blueprint(blueprint, 0.into()); assert!( chunks.len() >= 2, @@ -436,17 +436,17 @@ pub mod tests { fn test_parse_slot_resume_after_invalid_chunk() { let mut host = MockKernelHost::default(); - let valid_blueprint_chunks_1 = chunk_blueprint(dummy_big_blueprint(1)); + let valid_blueprint_chunks_1 = chunk_blueprint(dummy_big_blueprint(1), 0.into()); let invalid_blueprint_chunks = { - let mut chunks = chunk_blueprint(dummy_big_blueprint(1)); + let mut chunks = chunk_blueprint(dummy_big_blueprint(1), 0.into()); for chunk in chunks.iter_mut() { chunk.nb_chunks = crate::blueprint_storage::MAXIMUM_NUMBER_OF_CHUNKS + 1 } chunks }; - let valid_blueprint_chunks_2 = chunk_blueprint(dummy_big_blueprint(1)); + let valid_blueprint_chunks_2 = chunk_blueprint(dummy_big_blueprint(1), 0.into()); let mut chunks = vec![]; chunks.extend(valid_blueprint_chunks_1.clone()); @@ -531,7 +531,7 @@ pub mod tests { let mut host = MockKernelHost::default(); let blueprint = dummy_big_blueprint(1); - let chunks = chunk_blueprint(blueprint); + let chunks = chunk_blueprint(blueprint, 0.into()); let dal_parameters = host.reveal_dal_parameters(); let published_level = host.host.level(); diff --git a/etherlink/kernel_evm/kernel/src/stage_one.rs b/etherlink/kernel_evm/kernel/src/stage_one.rs index 5529beff0a78..44c5e790f7de 100644 --- a/etherlink/kernel_evm/kernel/src/stage_one.rs +++ b/etherlink/kernel_evm/kernel/src/stage_one.rs @@ -758,7 +758,7 @@ mod tests { fn fill_slots(host: &mut MockKernelHost, slots: Vec) { let blueprint = dummy_big_blueprint(100); - let chunks = chunk_blueprint(blueprint); + let chunks = chunk_blueprint(blueprint, 0.into()); let dal_parameters = host.reveal_dal_parameters(); let published_level = host.host.level() - (dal_parameters.attestation_lag as u32); -- GitLab From 7127162c0592c8a9b177aef1ecd76214c8ada2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Mon, 9 Dec 2024 11:36:59 +0100 Subject: [PATCH 4/5] Etherlink/Kernel/DAL/Tests: blueprints from the past are ignored --- etherlink/kernel_evm/kernel/src/dal.rs | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/etherlink/kernel_evm/kernel/src/dal.rs b/etherlink/kernel_evm/kernel/src/dal.rs index 4e3e9ebae47e..47194ee950ad 100644 --- a/etherlink/kernel_evm/kernel/src/dal.rs +++ b/etherlink/kernel_evm/kernel/src/dal.rs @@ -563,4 +563,38 @@ pub mod tests { assert_eq!(None, chunks_from_slot) } + + fn chunk_blueprint_range(min: usize, max: usize) -> Vec { + let mut chunks = vec![]; + for n in min..max { + chunks.extend(chunk_blueprint(dummy_big_blueprint(1), n.into())); + } + chunks + } + + #[test] + fn test_parse_slot_with_blueprints_from_the_past() { + let mut host = MockKernelHost::default(); + + let head_level = Some(2.into()); + let chunks = chunk_blueprint_range(0, 5); + let expected_chunks = chunk_blueprint_range(3, 5); + + assert_eq!(5, chunks.len()); + assert_eq!(2, expected_chunks.len()); + + let dal_parameters = host.reveal_dal_parameters(); + let published_level = host.host.level() - (dal_parameters.attestation_lag as u32); + prepare_dal_slot(&mut host, &chunks, published_level as i32, 0); + + let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( + &mut host, + &dal_parameters, + &head_level, + 0, + published_level, + ); + + assert_eq!(chunks_from_slot, Some(expected_chunks)); + } } -- GitLab From 0c773d8ca1d2bd309707bd13a73b27c44362d37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Mon, 9 Dec 2024 13:34:30 +0100 Subject: [PATCH 5/5] Etherlink/Kernel/Changelog: mention !15925 --- etherlink/CHANGES_KERNEL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index f2920361b3df..67eb5d5986a6 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -13,7 +13,7 @@ ### Internal -- Maximum gas per transaction is stored in the durable storage. (!15468) +- Maximum gas per transaction is stored in the durable storage. (!15468 !15925) - Minimum base fee per gas is stored in the durable storage. (!15475) - Blueprints from the past are refused on parsing. (!15636) - Clear blueprints on migration (!15637) -- GitLab