diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index 6a2430b82d7d6bc939d155f9fca0381076b68e0a..85aa175a06e2604c07b62e2687d9f147261817e3 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -37,6 +37,8 @@ delayed inbox). It has been demonstrated that the prediction is over-approximated after a Layer 1 downtime, which is the most likely scenario triggering a flush of the delayed inbox. (!16425) +- An optional chain_id field has been added to the blueprints chunks + sent by the sequencer. (!16366) ### Bug fixes diff --git a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs index 35620856bbc5f351e6f1e8ef35ad0003f9a61067..ee55c1a6d3c2d259e68505af19b4b3632c057e65 100644 --- a/etherlink/kernel_evm/kernel/src/blueprint_storage.rs +++ b/etherlink/kernel_evm/kernel/src/blueprint_storage.rs @@ -551,6 +551,7 @@ mod tests { number: U256::from(0), nb_chunks: 1u16, chunk_index: 0u16, + chain_id: None, }; store_last_info_per_level_timestamp(&mut host, Timestamp::from(40)).unwrap(); @@ -612,6 +613,7 @@ mod tests { number: U256::from(0), nb_chunks: 1u16, chunk_index: 0u16, + chain_id: None, }; let mut delayed_inbox = diff --git a/etherlink/kernel_evm/kernel/src/dal.rs b/etherlink/kernel_evm/kernel/src/dal.rs index 47194ee950ad3943792e4580d7c92f0df4a74236..53f3231c67a838ef5e2ddb68d8a7d8a4f4a1bc79 100644 --- a/etherlink/kernel_evm/kernel/src/dal.rs +++ b/etherlink/kernel_evm/kernel/src/dal.rs @@ -280,6 +280,7 @@ pub mod tests { nb_chunks: nb_chunks as u16, number, chunk_index: chunk_index as u16, + chain_id: None, }) .collect() } diff --git a/etherlink/kernel_evm/kernel/src/inbox.rs b/etherlink/kernel_evm/kernel/src/inbox.rs index 8cf2def8fe40811677a5f565fac5895525a86d6a..e176cc528b75820f1369c9350207af53b7705306 100644 --- a/etherlink/kernel_evm/kernel/src/inbox.rs +++ b/etherlink/kernel_evm/kernel/src/inbox.rs @@ -1427,6 +1427,7 @@ mod tests { number: 7.into(), nb_chunks: 3, chunk_index: 0, + chain_id: None, }; let is_valid = insert_blueprint_and_read_inbox(head_level, &valid_sk, &pk, &blueprint); diff --git a/etherlink/kernel_evm/kernel/src/parsing.rs b/etherlink/kernel_evm/kernel/src/parsing.rs index 813bcc848aabc57225157f3b6d35236d7655f79b..95cbcd08f8cda165c5b6a8f719b9ceb020ac019c 100644 --- a/etherlink/kernel_evm/kernel/src/parsing.rs +++ b/etherlink/kernel_evm/kernel/src/parsing.rs @@ -904,6 +904,7 @@ pub mod tests { number: 7.into(), nb_chunks: 3, chunk_index: 0, + chain_id: None, }; // Blueprint chunk for level 7 is ok. diff --git a/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs b/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs index 02baea4aa2bc4c8e6898a322be0e028c57425e8a..62c7789880058d0c73c191855248d225e7f5152b 100644 --- a/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs +++ b/etherlink/kernel_evm/kernel/src/sequencer_blueprint.rs @@ -68,6 +68,7 @@ pub struct UnsignedSequencerBlueprint { pub number: U256, pub nb_chunks: u16, pub chunk_index: u16, + pub chain_id: Option, } #[derive(PartialEq, Debug, Clone)] @@ -84,66 +85,160 @@ impl From<&SequencerBlueprint> for UnsignedSequencerBlueprint { impl Encodable for UnsignedSequencerBlueprint { fn rlp_append(&self, stream: &mut rlp::RlpStream) { - stream.begin_list(4); - stream.append(&self.chunk); - append_u256_le(stream, &self.number); - append_u16_le(stream, &self.nb_chunks); - append_u16_le(stream, &self.chunk_index); + let Self { + chunk, + number, + nb_chunks, + chunk_index, + chain_id, + } = self; + stream.begin_list(4 + if chain_id.is_some() { 1 } else { 0 }); + stream.append(chunk); + append_u256_le(stream, number); + append_u16_le(stream, nb_chunks); + append_u16_le(stream, chunk_index); + if let Some(chain_id) = chain_id { + append_u256_le(stream, chain_id); + } } } impl Encodable for SequencerBlueprint { fn rlp_append(&self, stream: &mut rlp::RlpStream) { - stream.begin_list(5); - stream.append(&self.blueprint.chunk); - append_u256_le(stream, &self.blueprint.number); - append_u16_le(stream, &self.blueprint.nb_chunks); - append_u16_le(stream, &self.blueprint.chunk_index); + let UnsignedSequencerBlueprint { + chunk, + number, + nb_chunks, + chunk_index, + chain_id, + } = &self.blueprint; + stream.begin_list(5 + if chain_id.is_some() { 1 } else { 0 }); + stream.append(chunk); + append_u256_le(stream, number); + append_u16_le(stream, nb_chunks); + append_u16_le(stream, chunk_index); + if let Some(chain_id) = chain_id { + append_u256_le(stream, chain_id); + } stream.append(&self.signature.as_ref()); } } impl Decodable for UnsignedSequencerBlueprint { fn decode(decoder: &rlp::Rlp) -> Result { - rlp_helpers::check_list(decoder, 4)?; - let mut it = decoder.iter(); - let chunk = rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "chunk")?; - let number = decode_field_u256_le(&rlp_helpers::next(&mut it)?, "number")?; - let nb_chunks = decode_field_u16_le(&rlp_helpers::next(&mut it)?, "nb_chunks")?; - let chunk_index = - decode_field_u16_le(&rlp_helpers::next(&mut it)?, "chunk_index")?; - Ok(Self { - chunk, - number, - nb_chunks, - chunk_index, - }) + if !decoder.is_list() { + return Err(DecoderError::RlpExpectedToBeList); + } + match decoder.item_count()? { + 4 => { + // Optional chain_id field is absent + let mut it = decoder.iter(); + let chunk = + rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "chunk")?; + let number = + decode_field_u256_le(&rlp_helpers::next(&mut it)?, "number")?; + let nb_chunks = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "nb_chunks")?; + let chunk_index = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "chunk_index")?; + Ok(Self { + chunk, + number, + nb_chunks, + chunk_index, + chain_id: None, + }) + } + 5 => { + // Optional chain_id field is provided + let mut it = decoder.iter(); + let chunk = + rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "chunk")?; + let number = + decode_field_u256_le(&rlp_helpers::next(&mut it)?, "number")?; + let nb_chunks = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "nb_chunks")?; + let chunk_index = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "chunk_index")?; + let chain_id = + decode_field_u256_le(&rlp_helpers::next(&mut it)?, "chain_id")?; + Ok(Self { + chunk, + number, + nb_chunks, + chunk_index, + chain_id: Some(chain_id), + }) + } + _ => Err(DecoderError::RlpInvalidLength), + } } } impl Decodable for SequencerBlueprint { fn decode(decoder: &rlp::Rlp) -> Result { - rlp_helpers::check_list(decoder, 5)?; - let mut it = decoder.iter(); - let chunk = rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "chunk")?; - let number = decode_field_u256_le(&rlp_helpers::next(&mut it)?, "number")?; - let nb_chunks = decode_field_u16_le(&rlp_helpers::next(&mut it)?, "nb_chunks")?; - let chunk_index = - decode_field_u16_le(&rlp_helpers::next(&mut it)?, "chunk_index")?; - let bytes: Vec = - rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "signature")?; - let signature = UnknownSignature::try_from(bytes.as_slice()) - .map_err(|_| DecoderError::Custom("Invalid signature encoding"))?; - let blueprint = UnsignedSequencerBlueprint { - chunk, - number, - nb_chunks, - chunk_index, - }; - Ok(Self { - blueprint, - signature, - }) + if !decoder.is_list() { + return Err(DecoderError::RlpExpectedToBeList); + } + match decoder.item_count()? { + 5 => { + // Optional chain_id field is absent + let mut it = decoder.iter(); + let chunk = + rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "chunk")?; + let number = + decode_field_u256_le(&rlp_helpers::next(&mut it)?, "number")?; + let nb_chunks = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "nb_chunks")?; + let chunk_index = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "chunk_index")?; + let bytes: Vec = + rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "signature")?; + let signature = UnknownSignature::try_from(bytes.as_slice()) + .map_err(|_| DecoderError::Custom("Invalid signature encoding"))?; + let blueprint = UnsignedSequencerBlueprint { + chunk, + number, + nb_chunks, + chunk_index, + chain_id: None, + }; + Ok(Self { + blueprint, + signature, + }) + } + 6 => { + // Optional chain_id field is provided + let mut it = decoder.iter(); + let chunk = + rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "chunk")?; + let number = + decode_field_u256_le(&rlp_helpers::next(&mut it)?, "number")?; + let nb_chunks = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "nb_chunks")?; + let chunk_index = + decode_field_u16_le(&rlp_helpers::next(&mut it)?, "chunk_index")?; + let chain_id = + decode_field_u256_le(&rlp_helpers::next(&mut it)?, "chain_id")?; + let bytes: Vec = + rlp_helpers::decode_field(&rlp_helpers::next(&mut it)?, "signature")?; + let signature = UnknownSignature::try_from(bytes.as_slice()) + .map_err(|_| DecoderError::Custom("Invalid signature encoding"))?; + let blueprint = UnsignedSequencerBlueprint { + chunk, + number, + nb_chunks, + chunk_index, + chain_id: Some(chain_id), + }; + Ok(Self { + blueprint, + signature, + }) + } + _ => Err(DecoderError::RlpInvalidLength), + } } } @@ -154,7 +249,7 @@ mod tests { use crate::inbox::Transaction; use crate::inbox::TransactionContent::Ethereum; use primitive_types::{H160, U256}; - use rlp::Encodable; + use rlp::{Decodable, Encodable}; use tezos_crypto_rs::hash::UnknownSignature; use tezos_ethereum::rlp_helpers::FromRlpBytes; use tezos_ethereum::{ @@ -162,10 +257,9 @@ mod tests { }; use tezos_smart_rollup_encoding::timestamp::Timestamp; - fn sequencer_blueprint_roundtrip(v: SequencerBlueprint) { + fn rlp_roundtrip(v: S) { let bytes = v.rlp_bytes(); - let v2: SequencerBlueprint = FromRlpBytes::from_rlp_bytes(&bytes) - .expect("Sequencer blueprint should be decodable"); + let v2: S = FromRlpBytes::from_rlp_bytes(&bytes).expect("Should be decodable"); assert_eq!(v, v2, "Roundtrip failed on {:?}", v) } @@ -197,7 +291,7 @@ mod tests { } } - fn dummy_blueprint() -> SequencerBlueprint { + fn dummy_blueprint_unsigned(chain_id: Option) -> UnsignedSequencerBlueprint { let transactions = vec![dummy_transaction(0), dummy_transaction(1)]; let timestamp = Timestamp::from(42); let blueprint = Blueprint { @@ -205,24 +299,49 @@ mod tests { transactions, }; let chunk = rlp::Encodable::rlp_bytes(&blueprint); + UnsignedSequencerBlueprint { + chunk: chunk.into(), + number: U256::from(42), + nb_chunks: 1u16, + chunk_index: 0u16, + chain_id, + } + } + + fn dummy_blueprint(chain_id: Option) -> SequencerBlueprint { let signature = UnknownSignature::from_base58_check( "sigdGBG68q2vskMuac4AzyNb1xCJTfuU8MiMbQtmZLUCYydYrtTd5Lessn1EFLTDJzjXoYxRasZxXbx6tHnirbEJtikcMHt3" ).expect("signature decoding should work"); SequencerBlueprint { - blueprint: UnsignedSequencerBlueprint { - chunk: chunk.into(), - number: U256::from(42), - nb_chunks: 1u16, - chunk_index: 0u16, - }, + blueprint: dummy_blueprint_unsigned(chain_id), signature, } } + #[test] + fn roundtrip_rlp_no_chain_id() { + let chunk = dummy_blueprint(None); + rlp_roundtrip(chunk); + } + #[test] fn roundtrip_rlp() { - let v = dummy_blueprint(); - sequencer_blueprint_roundtrip(v); + let chain_id = U256::one(); + let chunk = dummy_blueprint(Some(chain_id)); + rlp_roundtrip(chunk); + } + + #[test] + fn roundtrip_rlp_no_chain_id_unsigned() { + let chunk = dummy_blueprint_unsigned(None); + rlp_roundtrip(chunk); + } + + #[test] + fn roundtrip_rlp_unsigned() { + let chain_id = U256::one(); + let chunk = dummy_blueprint_unsigned(Some(chain_id)); + rlp_roundtrip(chunk); } }