diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index 1e6d5c400dd118ba28e7c17ed7626914c163d406..30e0e45ba3e65c38fc67a4995d4e9c241409ac1a 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -12,6 +12,8 @@ - Rework block production to simplify data flow and remove unnecessary IO. (!16661 !16684 !16685 !16618) +- Add a storage field containing the number of the next blueprint to + apply at path ``/evm/blueprints/next``. (!16569) ## Next proposal diff --git a/etherlink/kernel_evm/kernel/src/block.rs b/etherlink/kernel_evm/kernel/src/block.rs index acfa7c92d6c8aefb3c43a615a0c75d9be0e4ebca..0ab4e23414e11d2e0e8ad5ddd85dadc1bb98079c 100644 --- a/etherlink/kernel_evm/kernel/src/block.rs +++ b/etherlink/kernel_evm/kernel/src/block.rs @@ -9,7 +9,9 @@ use crate::apply::{ apply_transaction, ExecutionInfo, ExecutionResult, Validity, WITHDRAWAL_OUTBOX_QUEUE, }; use crate::block_storage; -use crate::blueprint_storage::{drop_blueprint, read_blueprint}; +use crate::blueprint_storage::{ + drop_blueprint, read_blueprint, store_next_blueprint_number, +}; use crate::configuration::ConfigurationMode; use crate::configuration::Limits; use crate::delayed_inbox::DelayedInbox; @@ -412,6 +414,10 @@ fn promote_block( safe_host.promote()?; safe_host.promote_trace()?; drop_blueprint(safe_host.host, block.number)?; + store_next_blueprint_number( + safe_host.host, + block.number.saturating_add(U256::one()), + )?; Event::BlueprintApplied { number: block.number, diff --git a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs index 0548f72af084bbbd90e864d145fcf0d9cd1ba87b..9f972833553ba9c2a8ed4ac706a7e5015dfde0f2 100644 --- a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs +++ b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs @@ -26,13 +26,16 @@ use tezos_smart_rollup_core::MAX_INPUT_MESSAGE_SIZE; use tezos_smart_rollup_host::path::*; use tezos_smart_rollup_host::runtime::RuntimeError; use tezos_storage::{ - error::Error as GenStorageError, read_rlp, store_read_slice, store_rlp, + error::Error as GenStorageError, read_rlp, read_u256_le, store_read_slice, store_rlp, + write_u256_le, }; pub const EVM_BLUEPRINTS: RefPath = RefPath::assert_from(b"/evm/blueprints"); const EVM_BLUEPRINT_NB_CHUNKS: RefPath = RefPath::assert_from(b"/nb_chunks"); +const NEXT_BLUEPRINT_NUMBER: RefPath = RefPath::assert_from(b"/evm/blueprints/next"); + /// The store representation of a blueprint. /// It's designed to support storing sequencer blueprints, /// which can be chunked, and blueprints constructed from @@ -164,17 +167,19 @@ pub fn store_inbox_blueprint( #[inline(always)] pub fn read_next_blueprint_number(host: &Host) -> anyhow::Result { - match block_storage::read_current_number(host) { - Err(err) => match err.downcast_ref() { - Some(GenStorageError::Runtime(RuntimeError::PathNotFound)) => { - Ok(U256::zero()) - } - _ => Err(err), - }, - Ok(block_number) => Ok(block_number.saturating_add(U256::one())), + match read_u256_le(host, &NEXT_BLUEPRINT_NUMBER) { + Err(GenStorageError::Runtime(RuntimeError::PathNotFound)) => Ok(U256::zero()), + res => Ok(res?), } } +pub fn store_next_blueprint_number( + host: &mut Host, + number: U256, +) -> anyhow::Result<()> { + Ok(write_u256_le(host, &NEXT_BLUEPRINT_NUMBER, number)?) +} + // Used to store a blueprint made out of forced delayed transactions. pub fn store_forced_blueprint( host: &mut Host, diff --git a/etherlink/kernel_evm/kernel/src/dal.rs b/etherlink/kernel_evm/kernel/src/dal.rs index 53f3231c67a838ef5e2ddb68d8a7d8a4f4a1bc79..cf884029c420a4de6c47641288605a03231b5a39 100644 --- a/etherlink/kernel_evm/kernel/src/dal.rs +++ b/etherlink/kernel_evm/kernel/src/dal.rs @@ -73,10 +73,13 @@ fn rlp_length(data: &[u8]) -> Result { fn parse_unsigned_sequencer_blueprint( host: &mut Host, bytes: &[u8], - head_level: &Option, + next_blueprint_number: &U256, ) -> (Option, usize) { if let Result::Ok(chunk_length) = rlp_length(bytes) { - match parse_unsigned_blueprint_chunk(&bytes[..chunk_length], head_level) { + match parse_unsigned_blueprint_chunk( + &bytes[..chunk_length], + next_blueprint_number, + ) { SequencerBlueprintRes::SequencerBlueprint(unsigned_chunk) => ( Some(ParsedInput::UnsignedSequencerBlueprint(unsigned_chunk)), chunk_length + TAG_SIZE, @@ -97,7 +100,7 @@ fn parse_unsigned_sequencer_blueprint( fn parse_input( host: &mut Host, bytes: &[u8], - head_level: &Option, + next_blueprint_number: &U256, ) -> (Option, usize) { // The expected format is: @@ -107,7 +110,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, head_level) + parse_unsigned_sequencer_blueprint(host, bytes, next_blueprint_number) } invalid_tag => { log!( @@ -125,7 +128,7 @@ fn parse_input( fn parse_slot( host: &mut Host, slot: &[u8], - head_level: &Option, + next_blueprint_number: &U256, ) -> Vec { // The format of a dal slot is // tagged chunk | tagged chunk | .. | padding @@ -145,7 +148,8 @@ fn parse_slot( if offset >= slot.len() { return buffer; }; - let (next_input, length) = parse_input(host, &slot[offset..], head_level); + let (next_input, length) = + parse_input(host, &slot[offset..], next_blueprint_number); match next_input { None => return buffer, // Once an unparsable input has been read, @@ -165,7 +169,7 @@ fn parse_slot( pub fn fetch_and_parse_sequencer_blueprint_from_dal( host: &mut Host, params: &RollupDalParameters, - head_level: &Option, + next_blueprint_number: &U256, slot_index: u8, published_level: u32, ) -> Option> { @@ -182,7 +186,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, head_level); + let chunks = parse_slot(host, &slot, next_blueprint_number); log!( host, Debug, @@ -359,7 +363,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, - &None, + &0.into(), 0, published_level, ); @@ -399,7 +403,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, - &None, + &0.into(), 0, published_level, ); @@ -465,7 +469,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, - &None, + &0.into(), 0, published_level, ); @@ -543,7 +547,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, - &None, + &0.into(), 0, published_level, ); @@ -557,7 +561,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, - &None, + &0.into(), 0, published_level, ); @@ -577,7 +581,7 @@ pub mod tests { fn test_parse_slot_with_blueprints_from_the_past() { let mut host = MockKernelHost::default(); - let head_level = Some(2.into()); + let next_blueprint_number = 3.into(); let chunks = chunk_blueprint_range(0, 5); let expected_chunks = chunk_blueprint_range(3, 5); @@ -591,7 +595,7 @@ pub mod tests { let chunks_from_slot = fetch_and_parse_sequencer_blueprint_from_dal( &mut host, &dal_parameters, - &head_level, + &next_blueprint_number, 0, published_level, ); diff --git a/etherlink/kernel_evm/kernel/src/inbox.rs b/etherlink/kernel_evm/kernel/src/inbox.rs index c47ef7ed9eef6d81220547cf7011f53b70b40839..4b39ffb3a237129f5bed4aad0eeacca3ffbbfc19 100644 --- a/etherlink/kernel_evm/kernel/src/inbox.rs +++ b/etherlink/kernel_evm/kernel/src/inbox.rs @@ -363,8 +363,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(); + let next_blueprint_number: U256 = + crate::blueprint_storage::read_next_blueprint_number(host)?; for signal in signals.0.iter() { let published_level = signal.published_level; let slot_indices = &signal.slot_indices; @@ -380,7 +380,7 @@ impl InputHandler for SequencerInput { fetch_and_parse_sequencer_blueprint_from_dal( host, ¶ms, - &head_level, + &next_blueprint_number, *slot_index, published_level, ) @@ -692,7 +692,8 @@ pub fn read_sequencer_inbox( // during this kernel run. let mut inbox_is_empty = true; let limits = fetch_limits(host); - let head_level: Option = crate::block_storage::read_current_number(host).ok(); + let next_blueprint_number: U256 = + crate::blueprint_storage::read_next_blueprint_number(host)?; let mut parsing_context = SequencerParsingContext { sequencer, delayed_bridge, @@ -701,7 +702,7 @@ pub fn read_sequencer_inbox( .saturating_sub(TICKS_FOR_BLUEPRINT_INTERCEPT), dal_configuration: dal, buffer_transaction_chunks: None, - head_level, + next_blueprint_number, }; loop { // Checks there will be enough ticks to handle at least another chunk of @@ -760,7 +761,7 @@ pub fn read_sequencer_inbox( #[cfg(test)] mod tests { use super::*; - use crate::blueprint_storage::blueprint_path; + use crate::blueprint_storage::{blueprint_path, store_next_blueprint_number}; use crate::configuration::TezosContracts; use crate::dal_slot_import_signal::{ DalSlotIndicesList, DalSlotIndicesOfLevel, UnsignedDalSlotSignals, @@ -1373,10 +1374,7 @@ mod tests { // Prepare the host. let mut host = MockKernelHost::default(); let address = smart_rollup_address(); - crate::block_storage::internal_for_tests::store_current_number( - &mut host, head_level, - ) - .unwrap(); + store_next_blueprint_number(&mut host, head_level + 1).unwrap(); // Prepare the blueprint. let mut blueprint_bytes = diff --git a/etherlink/kernel_evm/kernel/src/migration.rs b/etherlink/kernel_evm/kernel/src/migration.rs index 738493df611ac70ec0a54862895f8ade97b68ca8..40b15c95d8a43cc50fae16cf3a0ebbf32493d903 100644 --- a/etherlink/kernel_evm/kernel/src/migration.rs +++ b/etherlink/kernel_evm/kernel/src/migration.rs @@ -6,7 +6,7 @@ use crate::block_storage; use crate::blueprint_storage::{ - blueprint_path, clear_all_blueprints, read_next_blueprint_number, + blueprint_path, clear_all_blueprints, store_next_blueprint_number, }; use crate::error::Error; use crate::error::StorageError; @@ -69,6 +69,30 @@ pub fn allow_path_not_found(res: Result<(), RuntimeError>) -> Result<(), Runtime const TMP_NEXT_BLUEPRINT_PATH: RefPath = RefPath::assert_from(b"/__tmp_next_blueprint_path"); +mod legacy { + // This module contains copies of old implementations of some + // functions. The legacy semantics of these functions is needed in + // some migration step to access the storage using the fields + // which were present at the time. + + use super::*; + use tezos_storage::error::Error as GenStorageError; + + pub fn read_next_blueprint_number( + host: &Host, + ) -> anyhow::Result { + match block_storage::read_current_number(host) { + Err(err) => match err.downcast_ref() { + Some(GenStorageError::Runtime(RuntimeError::PathNotFound)) => { + Ok(U256::zero()) + } + _ => Err(err), + }, + Ok(block_number) => Ok(block_number.saturating_add(U256::one())), + } + } +} + fn migrate_to( host: &mut Host, version: StorageVersion, @@ -200,7 +224,7 @@ fn migrate_to( // However we need to keep the next blueprint as it // trigerred the upgrade. - let next_blueprint_number = read_next_blueprint_number(host)?; + let next_blueprint_number = legacy::read_next_blueprint_number(host)?; let blueprint_path = blueprint_path(next_blueprint_number)?; allow_path_not_found( host.store_move(&blueprint_path, &TMP_NEXT_BLUEPRINT_PATH), @@ -242,6 +266,11 @@ fn migrate_to( host.store_write_all(&ENABLE_FAST_WITHDRAWAL, &[1_u8])?; Ok(MigrationStatus::Done) } + StorageVersion::V27 => { + // Initialize the next_blueprint field + store_next_blueprint_number(host, legacy::read_next_blueprint_number(host)?)?; + Ok(MigrationStatus::Done) + } } } diff --git a/etherlink/kernel_evm/kernel/src/parsing.rs b/etherlink/kernel_evm/kernel/src/parsing.rs index b32aa685228553a362a9e03be5408f63b2536d5d..9af9d37a56746fa0fefa5d01bf82ce1b7cde3535 100644 --- a/etherlink/kernel_evm/kernel/src/parsing.rs +++ b/etherlink/kernel_evm/kernel/src/parsing.rs @@ -303,24 +303,21 @@ pub struct SequencerParsingContext { // Delayed inbox transactions may come in chunks. If the buffer is // [Some _] a chunked transaction is being parsed, pub buffer_transaction_chunks: Option, - // Head level of the chain, handling blueprints before the head is useless. - // It is optional to handle when the first block has not been produced - // yet. - pub head_level: Option, + // Number of the next expected blueprint, handling blueprints + // before this is useless. + pub next_blueprint_number: U256, } fn check_unsigned_blueprint_chunk( unsigned_seq_blueprint: UnsignedSequencerBlueprint, - head_level: &Option, + next_blueprint_number: &U256, ) -> 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; - } + if unsigned_seq_blueprint.number.lt(next_blueprint_number) { + return SequencerBlueprintRes::InvalidNumber; } SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) @@ -328,13 +325,13 @@ fn check_unsigned_blueprint_chunk( pub fn parse_unsigned_blueprint_chunk( bytes: &[u8], - head_level: &Option, + next_blueprint_number: &U256, ) -> SequencerBlueprintRes { // Parse an unsigned sequencer blueprint match UnsignedSequencerBlueprint::from_rlp_bytes(bytes).ok() { None => SequencerBlueprintRes::Unparsable, Some(unsigned_seq_blueprint) => { - check_unsigned_blueprint_chunk(unsigned_seq_blueprint, head_level) + check_unsigned_blueprint_chunk(unsigned_seq_blueprint, next_blueprint_number) } } } @@ -342,7 +339,7 @@ pub fn parse_unsigned_blueprint_chunk( pub fn parse_blueprint_chunk( bytes: &[u8], sequencer: &PublicKey, - head_level: &Option, + next_blueprint_number: &U256, ) -> SequencerBlueprintRes { // Parse the sequencer blueprint match SequencerBlueprint::from_rlp_bytes(bytes).ok() { @@ -350,7 +347,7 @@ pub fn parse_blueprint_chunk( Some(SequencerBlueprint { blueprint, signature, - }) => match check_unsigned_blueprint_chunk(blueprint, head_level) { + }) => match check_unsigned_blueprint_chunk(blueprint, next_blueprint_number) { SequencerBlueprintRes::SequencerBlueprint(unsigned_seq_blueprint) => { let bytes = unsigned_seq_blueprint.rlp_bytes().to_vec(); @@ -384,7 +381,11 @@ impl SequencerInput { .allocated_ticks .saturating_sub(TICKS_FOR_BLUEPRINT_CHUNK_SIGNATURE); - let res = parse_blueprint_chunk(bytes, &context.sequencer, &context.head_level); + let res = parse_blueprint_chunk( + bytes, + &context.sequencer, + &context.next_blueprint_number, + ); InputResult::Input(Input::ModeSpecific(Self::SequencerBlueprint(res))) } @@ -897,7 +898,7 @@ pub mod tests { "edsk422LGdmDnai4Cya6csM6oFmgHpDQKUhatTURJRAY4h7NHNz9sz", ) .unwrap(); - let head_level: Option = Some(6.into()); + let next_blueprint_number: U256 = 7.into(); let blueprint = UnsignedSequencerBlueprint { chunk: vec![], @@ -911,7 +912,7 @@ pub mod tests { let res = parse_blueprint_chunk( &sequencer_signed_blueprint_chunk_bytes(&blueprint, sk.clone()), &pk, - &head_level, + &next_blueprint_number, ); assert!(matches!(res, SequencerBlueprintRes::SequencerBlueprint(_))); @@ -922,7 +923,7 @@ pub mod tests { let res = parse_blueprint_chunk( &sequencer_signed_blueprint_chunk_bytes(&blueprint, invalid_sk), &pk, - &head_level, + &next_blueprint_number, ); assert!(matches!(res, SequencerBlueprintRes::InvalidSignature)); @@ -935,7 +936,7 @@ pub mod tests { sk.clone(), ), &pk, - &head_level, + &next_blueprint_number, ); assert!(matches!(res, SequencerBlueprintRes::InvalidNumber)); @@ -948,7 +949,7 @@ pub mod tests { sk.clone(), ), &pk, - &head_level, + &next_blueprint_number, ); assert!(matches!(res, SequencerBlueprintRes::InvalidNumber)); } diff --git a/etherlink/kernel_evm/kernel/src/stage_one.rs b/etherlink/kernel_evm/kernel/src/stage_one.rs index 119b5da0498d7b41c80eedd7ab58c4f1fee0f6ca..21c7d86fc8a48d6bc7a2a0ee89c519b64fdc3eee 100644 --- a/etherlink/kernel_evm/kernel/src/stage_one.rs +++ b/etherlink/kernel_evm/kernel/src/stage_one.rs @@ -226,7 +226,9 @@ mod tests { use crate::{ block_storage::internal_for_tests::store_current_number, - blueprint_storage::read_next_blueprint, dal::tests::*, parsing::RollupType, + blueprint_storage::{read_next_blueprint, store_next_blueprint_number}, + dal::tests::*, + parsing::RollupType, }; use super::*; @@ -495,7 +497,7 @@ mod tests { fetch_blueprints(&mut host, DEFAULT_SR_ADDRESS, &mut conf).expect("fetch failed"); // The dummy chunk in the inbox is registered at block 10 - store_current_number(&mut host, U256::from(9)).unwrap(); + store_next_blueprint_number(&mut host, U256::from(10)).unwrap(); if read_next_blueprint(&mut host, &mut conf) .expect("Blueprint reading shouldn't fail") .0 diff --git a/etherlink/kernel_evm/kernel/src/storage.rs b/etherlink/kernel_evm/kernel/src/storage.rs index 7d134e2140a2dd40190c4c19e008b8ac6d8b8cfc..d0fa9b9b54164b05caf4198ebe8b1b94f3c0f662 100644 --- a/etherlink/kernel_evm/kernel/src/storage.rs +++ b/etherlink/kernel_evm/kernel/src/storage.rs @@ -59,6 +59,7 @@ pub enum StorageVersion { V24, V25, V26, + V27, } impl From for u64 { @@ -73,7 +74,7 @@ impl StorageVersion { } } -pub const STORAGE_VERSION: StorageVersion = StorageVersion::V26; +pub const STORAGE_VERSION: StorageVersion = StorageVersion::V27; pub const PRIVATE_FLAG_PATH: RefPath = RefPath::assert_from(b"/evm/remove_whitelist"); diff --git a/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm b/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm index 1f99fe90bf3c8a5cb8daac83d03a82ccfc501355..c4efa61843a4df05175fc16f45a5157593e20af4 100755 Binary files a/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm and b/etherlink/kernel_evm/kernel/tests/resources/failed_migration.wasm differ