diff --git a/Cargo.lock b/Cargo.lock index bbe4ecfe58..8050b95d32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -811,6 +811,7 @@ dependencies = [ "bech32 0.11.0", "celestia-tendermint", "celestia-types", + "const_format", "cosmrs", "dirs", "ed25519-consensus", @@ -829,6 +830,7 @@ dependencies = [ "pbjson-types", "pin-project-lite", "prost", + "rand_chacha 0.3.1", "rand_core 0.6.4", "reqwest", "serde", @@ -842,6 +844,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", + "tokio-test", "tokio-util 0.7.10", "tonic 0.10.2", "tower", diff --git a/Cargo.toml b/Cargo.toml index 95f248e442..0590a5f592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,6 @@ base64-serde = "0.7.0" bytes = "1" celestia-tendermint = "0.32.1" celestia-types = "0.1.1" -celestia-rpc = "0.1.1" clap = "4" ed25519-consensus = "2.1.0" ethers = "2.0.11" diff --git a/crates/astria-conductor/src/block_cache.rs b/crates/astria-conductor/src/block_cache.rs index e6c7ce73b0..ead4d7fc0a 100644 --- a/crates/astria-conductor/src/block_cache.rs +++ b/crates/astria-conductor/src/block_cache.rs @@ -6,7 +6,7 @@ use std::{ use astria_core::sequencerblock::v1alpha1::{ block::FilteredSequencerBlock, - CelestiaSequencerBlob, + SubmittedMetadata, }; use pin_project_lite::pin_project; use sequencer_client::tendermint::block::Height; @@ -21,7 +21,7 @@ impl GetSequencerHeight for FilteredSequencerBlock { } } -impl GetSequencerHeight for CelestiaSequencerBlob { +impl GetSequencerHeight for SubmittedMetadata { fn get_height(&self) -> Height { self.height() } diff --git a/crates/astria-conductor/src/celestia/block_verifier.rs b/crates/astria-conductor/src/celestia/block_verifier.rs index d4711b5a38..aebf592b2d 100644 --- a/crates/astria-conductor/src/celestia/block_verifier.rs +++ b/crates/astria-conductor/src/celestia/block_verifier.rs @@ -225,7 +225,7 @@ mod test { primitive::v1::RollupId, sequencerblock::v1alpha1::{ block::SequencerBlockHeader, - celestia::UncheckedCelestiaSequencerBlob, + celestia::UncheckedSubmittedMetadata, }, }; use prost::Message as _; @@ -343,7 +343,7 @@ mod test { }; let header = SequencerBlockHeader::try_from_raw(header).unwrap(); - let sequencer_blob = UncheckedCelestiaSequencerBlob { + let sequencer_blob = UncheckedSubmittedMetadata { block_hash: [0u8; 32], header, rollup_ids: vec![], @@ -388,7 +388,7 @@ mod test { }; let header = SequencerBlockHeader::try_from_raw(header).unwrap(); - let sequencer_blob = UncheckedCelestiaSequencerBlob { + let sequencer_blob = UncheckedSubmittedMetadata { block_hash: [0u8; 32], header, rollup_ids: vec![rollup_id], diff --git a/crates/astria-conductor/src/celestia/convert.rs b/crates/astria-conductor/src/celestia/convert.rs index 83a3315fdb..6ffe387e38 100644 --- a/crates/astria-conductor/src/celestia/convert.rs +++ b/crates/astria-conductor/src/celestia/convert.rs @@ -1,8 +1,16 @@ use astria_core::{ brotli::decompress_bytes, + generated::sequencerblock::v1alpha1::{ + SubmittedMetadataList, + SubmittedRollupDataList, + }, sequencerblock::v1alpha1::{ - CelestiaRollupBlob, - CelestiaSequencerBlob, + celestia::{ + SubmittedMetadataError, + SubmittedRollupDataError, + }, + SubmittedMetadata, + SubmittedRollupData, }, }; use celestia_types::{ @@ -33,8 +41,8 @@ pub(super) fn decode_raw_blobs( let mut converted_blobs = ConvertedBlobs::new(raw_blobs.celestia_height); for blob in raw_blobs.header_blobs { if blob.namespace == sequencer_namespace { - if let Some(header) = convert_header(&blob) { - converted_blobs.push_header(header); + if let Some(header_list) = convert_blob_to_header_list(&blob) { + converted_blobs.extend_from_header_list_if_well_formed(header_list); } } else { warn!( @@ -47,8 +55,8 @@ pub(super) fn decode_raw_blobs( for blob in raw_blobs.rollup_blobs { if blob.namespace == rollup_namespace { - if let Some(rollup) = convert_rollup(&blob) { - converted_blobs.push_rollup(rollup); + if let Some(rollup_list) = convert_blob_to_rollup_data_list(&blob) { + converted_blobs.extend_from_rollup_data_list_if_well_formed(rollup_list); } } else { warn!( @@ -61,45 +69,76 @@ pub(super) fn decode_raw_blobs( converted_blobs } -/// An unsorted [`CelestiaSequencerBlob`] and [`CelestiaRollupBlob`]. +/// An unsorted [`SubmittedMetadata`] and [`SubmittedRollupData`]. pub(super) struct ConvertedBlobs { celestia_height: u64, - header_blobs: Vec, - rollup_blobs: Vec, + metadata: Vec, + rollup_data: Vec, } impl ConvertedBlobs { - pub(super) fn len_header_blobs(&self) -> usize { - self.header_blobs.len() + pub(super) fn len_headers(&self) -> usize { + self.metadata.len() } - pub(super) fn len_rollup_blobs(&self) -> usize { - self.rollup_blobs.len() + pub(super) fn len_rollup_data_entries(&self) -> usize { + self.rollup_data.len() } - pub(super) fn into_parts(self) -> (u64, Vec, Vec) { - (self.celestia_height, self.header_blobs, self.rollup_blobs) + pub(super) fn into_parts(self) -> (u64, Vec, Vec) { + (self.celestia_height, self.metadata, self.rollup_data) } fn new(celestia_height: u64) -> Self { Self { celestia_height, - header_blobs: Vec::new(), - rollup_blobs: Vec::new(), + metadata: Vec::new(), + rollup_data: Vec::new(), } } - fn push_header(&mut self, header: CelestiaSequencerBlob) { - self.header_blobs.push(header); + fn push_header(&mut self, header: SubmittedMetadata) { + self.metadata.push(header); + } + + fn push_rollup_data(&mut self, rollup: SubmittedRollupData) { + self.rollup_data.push(rollup); } - fn push_rollup(&mut self, rollup: CelestiaRollupBlob) { - self.rollup_blobs.push(rollup); + fn extend_from_header_list_if_well_formed(&mut self, list: SubmittedMetadataList) { + let initial_len = self.metadata.len(); + if let Err(err) = list.entries.into_iter().try_for_each(|raw| { + let header = SubmittedMetadata::try_from_raw(raw)?; + self.push_header(header); + Ok::<(), SubmittedMetadataError>(()) + }) { + info!( + error = &err as &StdError, + "one header in {} was not well-formed; dropping all", + SubmittedMetadataList::full_name(), + ); + self.metadata.truncate(initial_len); + } + } + + fn extend_from_rollup_data_list_if_well_formed(&mut self, list: SubmittedRollupDataList) { + let initial_len = self.rollup_data.len(); + if let Err(err) = list.entries.into_iter().try_for_each(|raw| { + let entry = SubmittedRollupData::try_from_raw(raw)?; + self.push_rollup_data(entry); + Ok::<(), SubmittedRollupDataError>(()) + }) { + info!( + error = &err as &StdError, + "one entry in {} was not well-formed; dropping all", + SubmittedRollupDataList::full_name(), + ); + self.rollup_data.truncate(initial_len); + } } } -fn convert_header(blob: &Blob) -> Option { - use astria_core::generated::sequencerblock::v1alpha1::CelestiaSequencerBlob as ProtoType; +fn convert_blob_to_header_list(blob: &Blob) -> Option { let data = decompress_bytes(&blob.data) .inspect_err(|err| { info!( @@ -108,27 +147,19 @@ fn convert_header(blob: &Blob) -> Option { ); }) .ok()?; - let raw = ProtoType::decode(&*data) + let raw = SubmittedMetadataList::decode(&*data) .inspect_err(|err| { info!( error = err as &StdError, - target = ProtoType::full_name(), - "failed decoding blob bytes as sequencer header; dropping the blob", + target = SubmittedMetadataList::full_name(), + "failed decoding blob bytes; dropping the blob", ); }) .ok()?; - CelestiaSequencerBlob::try_from_raw(raw) - .inspect_err(|err| { - info!( - error = err as &StdError, - "failed verifying decoded sequencer header; dropping it" - ); - }) - .ok() + Some(raw) } -fn convert_rollup(blob: &Blob) -> Option { - use astria_core::generated::sequencerblock::v1alpha1::CelestiaRollupBlob as ProtoType; +fn convert_blob_to_rollup_data_list(blob: &Blob) -> Option { let data = decompress_bytes(&blob.data) .inspect_err(|err| { info!( @@ -137,21 +168,14 @@ fn convert_rollup(blob: &Blob) -> Option { ); }) .ok()?; - let raw_blob = ProtoType::decode(&*data) + let raw = SubmittedRollupDataList::decode(&*data) .inspect_err(|err| { info!( error = err as &StdError, - target = ProtoType::full_name(), - "failed decoding blob bytes as rollup element; dropping the blob", + target = SubmittedRollupDataList::full_name(), + "failed decoding blob bytes; dropping the blob", ); }) .ok()?; - CelestiaRollupBlob::try_from_raw(raw_blob) - .inspect_err(|err| { - info!( - error = err as &StdError, - "failed verifying decoded rollup element; dropping it" - ); - }) - .ok() + Some(raw) } diff --git a/crates/astria-conductor/src/celestia/mod.rs b/crates/astria-conductor/src/celestia/mod.rs index 94fadf1026..caaad4ce5a 100644 --- a/crates/astria-conductor/src/celestia/mod.rs +++ b/crates/astria-conductor/src/celestia/mod.rs @@ -83,7 +83,7 @@ use self::{ latest_height_stream::stream_latest_heights, reconstruct::reconstruct_blocks_from_verified_blobs, verify::{ - verify_header_blobs, + verify_headers, BlobVerifier, }, }; @@ -516,12 +516,12 @@ impl FetchConvertVerifyAndReconstruct { .wrap_err("encountered panic while decoding raw Celestia blobs")?; info!( - number_of_header_blobs = decoded_blobs.len_header_blobs(), - number_of_rollup_blobs = decoded_blobs.len_rollup_blobs(), + number_of_header_blobs = decoded_blobs.len_headers(), + number_of_rollup_blobs = decoded_blobs.len_rollup_data_entries(), "decoded Sequencer header and rollup info from raw Celestia blobs", ); - let verified_blobs = verify_header_blobs(blob_verifier, decoded_blobs).await; + let verified_blobs = verify_headers(blob_verifier, decoded_blobs).await; info!( number_of_verified_header_blobs = verified_blobs.len_header_blobs(), diff --git a/crates/astria-conductor/src/celestia/reconstruct.rs b/crates/astria-conductor/src/celestia/reconstruct.rs index 6fe4e05e88..9deae218e1 100644 --- a/crates/astria-conductor/src/celestia/reconstruct.rs +++ b/crates/astria-conductor/src/celestia/reconstruct.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use astria_core::{ primitive::v1::RollupId, sequencerblock::v1alpha1::{ - CelestiaRollupBlob, - CelestiaSequencerBlob, + SubmittedMetadata, + SubmittedRollupData, }, }; use telemetry::display::base64; @@ -93,9 +93,9 @@ pub(super) fn reconstruct_blocks_from_verified_blobs( } fn remove_header_blob_matching_rollup_blob( - headers: &mut HashMap<[u8; 32], CelestiaSequencerBlob>, - rollup: &CelestiaRollupBlob, -) -> Option { + headers: &mut HashMap<[u8; 32], SubmittedMetadata>, + rollup: &SubmittedRollupData, +) -> Option { // chaining methods and returning () to use the ? operator and to not bind the value headers .get(&rollup.sequencer_block_hash()) @@ -106,8 +106,8 @@ fn remove_header_blob_matching_rollup_blob( } fn verify_rollup_blob_against_sequencer_blob( - rollup_blob: &CelestiaRollupBlob, - sequencer_blob: &CelestiaSequencerBlob, + rollup_blob: &SubmittedRollupData, + sequencer_blob: &SubmittedMetadata, ) -> bool { rollup_blob .proof() diff --git a/crates/astria-conductor/src/celestia/verify.rs b/crates/astria-conductor/src/celestia/verify.rs index 80b03e8f9c..48d658a46e 100644 --- a/crates/astria-conductor/src/celestia/verify.rs +++ b/crates/astria-conductor/src/celestia/verify.rs @@ -5,8 +5,8 @@ use std::{ }; use astria_core::sequencerblock::v1alpha1::{ - CelestiaRollupBlob, - CelestiaSequencerBlob, + SubmittedMetadata, + SubmittedRollupData, }; use astria_eyre::{ eyre, @@ -48,8 +48,8 @@ use crate::utils::flatten; pub(super) struct VerifiedBlobs { celestia_height: u64, - header_blobs: HashMap<[u8; 32], CelestiaSequencerBlob>, - rollup_blobs: Vec, + header_blobs: HashMap<[u8; 32], SubmittedMetadata>, + rollup_blobs: Vec, } impl VerifiedBlobs { @@ -65,14 +65,14 @@ impl VerifiedBlobs { self, ) -> ( u64, - HashMap<[u8; 32], CelestiaSequencerBlob>, - Vec, + HashMap<[u8; 32], SubmittedMetadata>, + Vec, ) { (self.celestia_height, self.header_blobs, self.rollup_blobs) } } -/// Task key to track verification of multiple [`CelestiaSequencerBlob`]s. +/// Task key to track verification of multiple [`SubmittedMetadata`] objects. /// /// The index is necessary because two keys might have clashing hashes and heights. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -86,7 +86,7 @@ struct VerificationTaskKey { /// /// Drops blobs that could not be verified. #[instrument(skip_all)] -pub(super) async fn verify_header_blobs( +pub(super) async fn verify_headers( blob_verifier: Arc, converted_blobs: ConvertedBlobs, ) -> VerifiedBlobs { @@ -102,10 +102,7 @@ pub(super) async fn verify_header_blobs( block_hash: blob.block_hash(), sequencer_height: blob.height(), }, - blob_verifier - .clone() - .verify_header_blob(blob) - .in_current_span(), + blob_verifier.clone().verify_header(blob).in_current_span(), ); } @@ -232,12 +229,12 @@ impl BlobVerifier { } } - async fn verify_header_blob( + async fn verify_header( self: Arc, - blob: CelestiaSequencerBlob, - ) -> eyre::Result { + header: SubmittedMetadata, + ) -> eyre::Result { use base64::prelude::*; - let height = blob.height(); + let height = header.height(); let meta = self .cache .try_get_with( @@ -247,18 +244,18 @@ impl BlobVerifier { .await .wrap_err("failed getting data necessary to verify the sequencer header blob")?; ensure!( - &meta.commit_header.header.chain_id == blob.cometbft_chain_id(), + &meta.commit_header.header.chain_id == header.cometbft_chain_id(), "expected cometbft chain ID `{}`, got `{}`", meta.commit_header.header.chain_id, - blob.cometbft_chain_id(), + header.cometbft_chain_id(), ); ensure!( - meta.commit_header.commit.block_id.hash.as_bytes() == blob.block_hash(), + meta.commit_header.commit.block_id.hash.as_bytes() == header.block_hash(), "block hash `{}` stored in blob does not match block hash `{}` of sequencer block", - BASE64_STANDARD.encode(blob.block_hash()), + BASE64_STANDARD.encode(header.block_hash()), BASE64_STANDARD.encode(meta.commit_header.commit.block_id.hash.as_bytes()), ); - Ok(blob) + Ok(header) } } diff --git a/crates/astria-conductor/tests/blackbox/helpers/macros.rs b/crates/astria-conductor/tests/blackbox/helpers/macros.rs index 6d57982c04..fb4fcf8058 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/macros.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/macros.rs @@ -116,16 +116,20 @@ macro_rules! mount_celestia_blobs { $celestia_height:expr,sequencer_height: $sequencer_height:expr $(,)? ) => {{ - let blobs = $crate::helpers::make_blobs($sequencer_height); + let blobs = $crate::helpers::make_blobs(&[$sequencer_height]); $test_env .mount_celestia_blob_get_all( $celestia_height, $crate::sequencer_namespace(), - blobs.header, + vec![blobs.header], ) .await; $test_env - .mount_celestia_blob_get_all($celestia_height, $crate::rollup_namespace(), blobs.rollup) + .mount_celestia_blob_get_all( + $celestia_height, + $crate::rollup_namespace(), + vec![blobs.rollup], + ) .await }}; } diff --git a/crates/astria-conductor/tests/blackbox/helpers/mod.rs b/crates/astria-conductor/tests/blackbox/helpers/mod.rs index b9a105675d..515b091a34 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mod.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mod.rs @@ -470,27 +470,45 @@ pub fn make_sequencer_block(height: u32) -> astria_core::sequencerblock::v1alpha } pub struct Blobs { - pub header: Vec, - pub rollup: Vec, + pub header: Blob, + pub rollup: Blob, } #[must_use] -pub fn make_blobs(height: u32) -> Blobs { - let (head, tail) = make_sequencer_block(height).into_celestia_blobs(); - - let raw_header = ::prost::Message::encode_to_vec(&head.into_raw()); - let head_compressed = compress_bytes(&raw_header).unwrap(); - let header = Blob::new(sequencer_namespace(), head_compressed).unwrap(); - - let mut rollup = Vec::new(); - for elem in tail { - let raw_rollup = ::prost::Message::encode_to_vec(&elem.into_raw()); - let rollup_compressed = compress_bytes(&raw_rollup).unwrap(); - let blob = Blob::new(rollup_namespace(), rollup_compressed).unwrap(); - rollup.push(blob); +pub fn make_blobs(heights: &[u32]) -> Blobs { + use astria_core::generated::sequencerblock::v1alpha1::{ + SubmittedMetadataList, + SubmittedRollupDataList, + }; + let mut metadata = Vec::new(); + let mut rollup_data = Vec::new(); + for &height in heights { + let (head, mut tail) = make_sequencer_block(height).split_for_celestia(); + metadata.push(head.into_raw()); + assert_eq!( + 1, + tail.len(), + "this test logic assumes that there is only one rollup in the mocked block" + ); + rollup_data.push(tail.swap_remove(0).into_raw()); } + let header_list = SubmittedMetadataList { + entries: metadata, + }; + let rollup_data_list = SubmittedRollupDataList { + entries: rollup_data, + }; + + let raw_header_list = ::prost::Message::encode_to_vec(&header_list); + let head_list_compressed = compress_bytes(&raw_header_list).unwrap(); + let header = Blob::new(sequencer_namespace(), head_list_compressed).unwrap(); + + let raw_rollup_data_list = ::prost::Message::encode_to_vec(&rollup_data_list); + let rollup_data_list_compressed = compress_bytes(&raw_rollup_data_list).unwrap(); + let rollup = Blob::new(rollup_namespace(), rollup_data_list_compressed).unwrap(); + Blobs { - header: vec![header], + header, rollup, } } diff --git a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs index f7485b3444..07bd00e7f7 100644 --- a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs @@ -213,14 +213,33 @@ impl ::prost::Name for RollupData { ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) } } -/// A collection of transactions belonging to a specific rollup that are submitted to celestia. +/// A sequence of `astria.sequencerblock.v1alpha1.SubmittedRollupData` submitted to Celestia. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SubmittedRollupDataList { + #[prost(message, repeated, tag = "1")] + pub entries: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for SubmittedRollupDataList { + const NAME: &'static str = "SubmittedRollupDataList"; + const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) + } +} +/// A collection of transactions belonging to a specific Rollup that is submitted to a Data +/// Availability provider like Celestia. +/// +/// It is created by splitting an `astria.sequencerblock.v1alpha1.SequencerBlock` into a +/// `astria.sequencerblock.v1alpha1.SubmittedMetadata`, and a sequence of +/// `astria.sequencerblock.v1alpha.SubmittedRollupData` (this object; one object per rollup that had +/// data included in the sequencer block). /// -/// The transactions contained in the item belong to a rollup identified -/// by `rollup_id`, and were included in the sequencer block identified -/// by `sequencer_block_hash`. +/// The original sequencer block (and in turn CometBFT block) can be identified by the +/// `sequencer_block_hash` field. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CelestiaRollupBlob { +pub struct SubmittedRollupData { /// The hash of the sequencer block. Must be 32 bytes. #[prost(bytes = "vec", tag = "1")] pub sequencer_block_hash: ::prost::alloc::vec::Vec, @@ -236,48 +255,65 @@ pub struct CelestiaRollupBlob { #[prost(message, optional, tag = "4")] pub proof: ::core::option::Option, } -impl ::prost::Name for CelestiaRollupBlob { - const NAME: &'static str = "CelestiaRollupBlob"; +impl ::prost::Name for SubmittedRollupData { + const NAME: &'static str = "SubmittedRollupData"; const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; fn full_name() -> ::prost::alloc::string::String { ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) } } -/// The metadata of a sequencer block that is submitted to celestia. +/// A sequence of `astria.sequencerblock.v1alpha1.SubmittedMetadata` submitted to Celestia. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SubmittedMetadataList { + #[prost(message, repeated, tag = "1")] + pub entries: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for SubmittedMetadataList { + const NAME: &'static str = "SubmittedMetadataList"; + const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) + } +} +/// The metadata of a sequencer block that is submitted to a Data Availability provider like +/// Celestia /// -/// It is created by splitting a `astria.SequencerBlock` into a -/// `CelestiaSequencerBlob` (which can be thought of as a header), and a sequence ofj -/// `CelestiaRollupBlob`s. +/// It is created by splitting an `astria.sequencerblock.v1alpha1.SequencerBlock` into a +/// `astria.sequencerblock.v1alpha1.SubmittedMetadata` (this object), and a sequence of +/// `astria.sequencerblock.v1alpha.SubmittedRollupData` (one object per rollup that had data +/// included in the sequencer block). /// /// The original sequencer block (and in turn CometBFT block) can be identified by the -/// block hash calculated from `header`. +/// `block_hash` field. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct CelestiaSequencerBlob { +pub struct SubmittedMetadata { /// the 32-byte block hash of the sequencer block. #[prost(bytes = "vec", tag = "1")] pub block_hash: ::prost::alloc::vec::Vec, /// the block header, which contains sequencer-specific commitments. #[prost(message, optional, tag = "2")] pub header: ::core::option::Option, - /// The rollup IDs for which `CelestiaRollupBlob`s were submitted to celestia. - /// Corresponds to the `astria.sequencer.v1.RollupTransactions.rollup_id` field - /// and is extracted from `astria.SequencerBlock.rollup_transactions`. + /// The rollup IDs that had transactions included in the `astria.sequencerblock.v1alpha1.SequencerBlock` + /// that this object is derived from. + /// Corresponds to `astria.sequencerblock.v1alpha1.RollupTransactions.rollup_id` + /// extracted from `astria.sequencerblock.v1alpha1.SsequencerBlock.rollup_transactions`. #[prost(message, repeated, tag = "3")] pub rollup_ids: ::prost::alloc::vec::Vec, /// The proof that the rollup transactions are included in sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + /// Corresponds to `astria.sequencerblock.v1alpha1.SequencerBlock.rollup_transactions_proof`. #[prost(message, optional, tag = "4")] pub rollup_transactions_proof: ::core::option::Option< super::super::primitive::v1::Proof, >, /// The proof that the rollup IDs are included in sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.rollup_ids_proof`. + /// Corresponds to `astria.sequencerblock.v1alpha1.SequencerBlock.rollup_ids_proof`. #[prost(message, optional, tag = "5")] pub rollup_ids_proof: ::core::option::Option, } -impl ::prost::Name for CelestiaSequencerBlob { - const NAME: &'static str = "CelestiaSequencerBlob"; +impl ::prost::Name for SubmittedMetadata { + const NAME: &'static str = "SubmittedMetadata"; const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; fn full_name() -> ::prost::alloc::string::String { ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) diff --git a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs index 5333481f08..2a774726d3 100644 --- a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs +++ b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs @@ -1,4 +1,4 @@ -impl serde::Serialize for CelestiaRollupBlob { +impl serde::Serialize for Deposit { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -6,56 +6,66 @@ impl serde::Serialize for CelestiaRollupBlob { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.sequencer_block_hash.is_empty() { + if self.bridge_address.is_some() { len += 1; } if self.rollup_id.is_some() { len += 1; } - if !self.transactions.is_empty() { + if self.amount.is_some() { len += 1; } - if self.proof.is_some() { + if !self.asset_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.CelestiaRollupBlob", len)?; - if !self.sequencer_block_hash.is_empty() { - #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("sequencer_block_hash", pbjson::private::base64::encode(&self.sequencer_block_hash).as_str())?; + if !self.destination_chain_address.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.Deposit", len)?; + if let Some(v) = self.bridge_address.as_ref() { + struct_ser.serialize_field("bridge_address", v)?; } if let Some(v) = self.rollup_id.as_ref() { struct_ser.serialize_field("rollup_id", v)?; } - if !self.transactions.is_empty() { - struct_ser.serialize_field("transactions", &self.transactions.iter().map(pbjson::private::base64::encode).collect::>())?; + if let Some(v) = self.amount.as_ref() { + struct_ser.serialize_field("amount", v)?; } - if let Some(v) = self.proof.as_ref() { - struct_ser.serialize_field("proof", v)?; + if !self.asset_id.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("asset_id", pbjson::private::base64::encode(&self.asset_id).as_str())?; + } + if !self.destination_chain_address.is_empty() { + struct_ser.serialize_field("destination_chain_address", &self.destination_chain_address)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for CelestiaRollupBlob { +impl<'de> serde::Deserialize<'de> for Deposit { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "sequencer_block_hash", - "sequencerBlockHash", + "bridge_address", + "bridgeAddress", "rollup_id", "rollupId", - "transactions", - "proof", + "amount", + "asset_id", + "assetId", + "destination_chain_address", + "destinationChainAddress", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - SequencerBlockHash, + BridgeAddress, RollupId, - Transactions, - Proof, + Amount, + AssetId, + DestinationChainAddress, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -77,10 +87,11 @@ impl<'de> serde::Deserialize<'de> for CelestiaRollupBlob { E: serde::de::Error, { match value { - "sequencerBlockHash" | "sequencer_block_hash" => Ok(GeneratedField::SequencerBlockHash), + "bridgeAddress" | "bridge_address" => Ok(GeneratedField::BridgeAddress), "rollupId" | "rollup_id" => Ok(GeneratedField::RollupId), - "transactions" => Ok(GeneratedField::Transactions), - "proof" => Ok(GeneratedField::Proof), + "amount" => Ok(GeneratedField::Amount), + "assetId" | "asset_id" => Ok(GeneratedField::AssetId), + "destinationChainAddress" | "destination_chain_address" => Ok(GeneratedField::DestinationChainAddress), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -90,29 +101,28 @@ impl<'de> serde::Deserialize<'de> for CelestiaRollupBlob { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = CelestiaRollupBlob; + type Value = Deposit; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.CelestiaRollupBlob") + formatter.write_str("struct astria.sequencerblock.v1alpha1.Deposit") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut sequencer_block_hash__ = None; + let mut bridge_address__ = None; let mut rollup_id__ = None; - let mut transactions__ = None; - let mut proof__ = None; + let mut amount__ = None; + let mut asset_id__ = None; + let mut destination_chain_address__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::SequencerBlockHash => { - if sequencer_block_hash__.is_some() { - return Err(serde::de::Error::duplicate_field("sequencerBlockHash")); + GeneratedField::BridgeAddress => { + if bridge_address__.is_some() { + return Err(serde::de::Error::duplicate_field("bridgeAddress")); } - sequencer_block_hash__ = - Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) - ; + bridge_address__ = map_.next_value()?; } GeneratedField::RollupId => { if rollup_id__.is_some() { @@ -120,35 +130,41 @@ impl<'de> serde::Deserialize<'de> for CelestiaRollupBlob { } rollup_id__ = map_.next_value()?; } - GeneratedField::Transactions => { - if transactions__.is_some() { - return Err(serde::de::Error::duplicate_field("transactions")); + GeneratedField::Amount => { + if amount__.is_some() { + return Err(serde::de::Error::duplicate_field("amount")); } - transactions__ = - Some(map_.next_value::>>()? - .into_iter().map(|x| x.0).collect()) + amount__ = map_.next_value()?; + } + GeneratedField::AssetId => { + if asset_id__.is_some() { + return Err(serde::de::Error::duplicate_field("assetId")); + } + asset_id__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) ; } - GeneratedField::Proof => { - if proof__.is_some() { - return Err(serde::de::Error::duplicate_field("proof")); + GeneratedField::DestinationChainAddress => { + if destination_chain_address__.is_some() { + return Err(serde::de::Error::duplicate_field("destinationChainAddress")); } - proof__ = map_.next_value()?; + destination_chain_address__ = Some(map_.next_value()?); } } } - Ok(CelestiaRollupBlob { - sequencer_block_hash: sequencer_block_hash__.unwrap_or_default(), + Ok(Deposit { + bridge_address: bridge_address__, rollup_id: rollup_id__, - transactions: transactions__.unwrap_or_default(), - proof: proof__, + amount: amount__, + asset_id: asset_id__.unwrap_or_default(), + destination_chain_address: destination_chain_address__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.CelestiaRollupBlob", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.Deposit", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for CelestiaSequencerBlob { +impl serde::Serialize for FilteredSequencerBlock { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -162,16 +178,19 @@ impl serde::Serialize for CelestiaSequencerBlob { if self.header.is_some() { len += 1; } - if !self.rollup_ids.is_empty() { + if !self.rollup_transactions.is_empty() { len += 1; } if self.rollup_transactions_proof.is_some() { len += 1; } + if !self.all_rollup_ids.is_empty() { + len += 1; + } if self.rollup_ids_proof.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.CelestiaSequencerBlob", len)?; + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.FilteredSequencerBlock", len)?; if !self.block_hash.is_empty() { #[allow(clippy::needless_borrow)] struct_ser.serialize_field("block_hash", pbjson::private::base64::encode(&self.block_hash).as_str())?; @@ -179,19 +198,22 @@ impl serde::Serialize for CelestiaSequencerBlob { if let Some(v) = self.header.as_ref() { struct_ser.serialize_field("header", v)?; } - if !self.rollup_ids.is_empty() { - struct_ser.serialize_field("rollup_ids", &self.rollup_ids)?; + if !self.rollup_transactions.is_empty() { + struct_ser.serialize_field("rollup_transactions", &self.rollup_transactions)?; } if let Some(v) = self.rollup_transactions_proof.as_ref() { struct_ser.serialize_field("rollup_transactions_proof", v)?; } + if !self.all_rollup_ids.is_empty() { + struct_ser.serialize_field("all_rollup_ids", &self.all_rollup_ids.iter().map(pbjson::private::base64::encode).collect::>())?; + } if let Some(v) = self.rollup_ids_proof.as_ref() { struct_ser.serialize_field("rollup_ids_proof", v)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { +impl<'de> serde::Deserialize<'de> for FilteredSequencerBlock { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where @@ -201,10 +223,12 @@ impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { "block_hash", "blockHash", "header", - "rollup_ids", - "rollupIds", + "rollup_transactions", + "rollupTransactions", "rollup_transactions_proof", "rollupTransactionsProof", + "all_rollup_ids", + "allRollupIds", "rollup_ids_proof", "rollupIdsProof", ]; @@ -213,8 +237,9 @@ impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { enum GeneratedField { BlockHash, Header, - RollupIds, + RollupTransactions, RollupTransactionsProof, + AllRollupIds, RollupIdsProof, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -239,8 +264,9 @@ impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { match value { "blockHash" | "block_hash" => Ok(GeneratedField::BlockHash), "header" => Ok(GeneratedField::Header), - "rollupIds" | "rollup_ids" => Ok(GeneratedField::RollupIds), + "rollupTransactions" | "rollup_transactions" => Ok(GeneratedField::RollupTransactions), "rollupTransactionsProof" | "rollup_transactions_proof" => Ok(GeneratedField::RollupTransactionsProof), + "allRollupIds" | "all_rollup_ids" => Ok(GeneratedField::AllRollupIds), "rollupIdsProof" | "rollup_ids_proof" => Ok(GeneratedField::RollupIdsProof), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } @@ -251,20 +277,21 @@ impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = CelestiaSequencerBlob; + type Value = FilteredSequencerBlock; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.CelestiaSequencerBlob") + formatter.write_str("struct astria.sequencerblock.v1alpha1.FilteredSequencerBlock") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { let mut block_hash__ = None; let mut header__ = None; - let mut rollup_ids__ = None; + let mut rollup_transactions__ = None; let mut rollup_transactions_proof__ = None; + let mut all_rollup_ids__ = None; let mut rollup_ids_proof__ = None; while let Some(k) = map_.next_key()? { match k { @@ -282,11 +309,11 @@ impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { } header__ = map_.next_value()?; } - GeneratedField::RollupIds => { - if rollup_ids__.is_some() { - return Err(serde::de::Error::duplicate_field("rollupIds")); + GeneratedField::RollupTransactions => { + if rollup_transactions__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupTransactions")); } - rollup_ids__ = Some(map_.next_value()?); + rollup_transactions__ = Some(map_.next_value()?); } GeneratedField::RollupTransactionsProof => { if rollup_transactions_proof__.is_some() { @@ -294,6 +321,15 @@ impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { } rollup_transactions_proof__ = map_.next_value()?; } + GeneratedField::AllRollupIds => { + if all_rollup_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("allRollupIds")); + } + all_rollup_ids__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } GeneratedField::RollupIdsProof => { if rollup_ids_proof__.is_some() { return Err(serde::de::Error::duplicate_field("rollupIdsProof")); @@ -302,19 +338,20 @@ impl<'de> serde::Deserialize<'de> for CelestiaSequencerBlob { } } } - Ok(CelestiaSequencerBlob { + Ok(FilteredSequencerBlock { block_hash: block_hash__.unwrap_or_default(), header: header__, - rollup_ids: rollup_ids__.unwrap_or_default(), + rollup_transactions: rollup_transactions__.unwrap_or_default(), rollup_transactions_proof: rollup_transactions_proof__, + all_rollup_ids: all_rollup_ids__.unwrap_or_default(), rollup_ids_proof: rollup_ids_proof__, }) } } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.CelestiaSequencerBlob", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.FilteredSequencerBlock", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for Deposit { +impl serde::Serialize for GetFilteredSequencerBlockRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -322,66 +359,39 @@ impl serde::Serialize for Deposit { { use serde::ser::SerializeStruct; let mut len = 0; - if self.bridge_address.is_some() { - len += 1; - } - if self.rollup_id.is_some() { - len += 1; - } - if self.amount.is_some() { - len += 1; - } - if !self.asset_id.is_empty() { + if self.height != 0 { len += 1; } - if !self.destination_chain_address.is_empty() { + if !self.rollup_ids.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.Deposit", len)?; - if let Some(v) = self.bridge_address.as_ref() { - struct_ser.serialize_field("bridge_address", v)?; - } - if let Some(v) = self.rollup_id.as_ref() { - struct_ser.serialize_field("rollup_id", v)?; - } - if let Some(v) = self.amount.as_ref() { - struct_ser.serialize_field("amount", v)?; - } - if !self.asset_id.is_empty() { + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest", len)?; + if self.height != 0 { #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("asset_id", pbjson::private::base64::encode(&self.asset_id).as_str())?; + struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; } - if !self.destination_chain_address.is_empty() { - struct_ser.serialize_field("destination_chain_address", &self.destination_chain_address)?; + if !self.rollup_ids.is_empty() { + struct_ser.serialize_field("rollup_ids", &self.rollup_ids)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for Deposit { +impl<'de> serde::Deserialize<'de> for GetFilteredSequencerBlockRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "bridge_address", - "bridgeAddress", - "rollup_id", - "rollupId", - "amount", - "asset_id", - "assetId", - "destination_chain_address", - "destinationChainAddress", + "height", + "rollup_ids", + "rollupIds", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - BridgeAddress, - RollupId, - Amount, - AssetId, - DestinationChainAddress, + Height, + RollupIds, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -403,11 +413,8 @@ impl<'de> serde::Deserialize<'de> for Deposit { E: serde::de::Error, { match value { - "bridgeAddress" | "bridge_address" => Ok(GeneratedField::BridgeAddress), - "rollupId" | "rollup_id" => Ok(GeneratedField::RollupId), - "amount" => Ok(GeneratedField::Amount), - "assetId" | "asset_id" => Ok(GeneratedField::AssetId), - "destinationChainAddress" | "destination_chain_address" => Ok(GeneratedField::DestinationChainAddress), + "height" => Ok(GeneratedField::Height), + "rollupIds" | "rollup_ids" => Ok(GeneratedField::RollupIds), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -417,70 +424,46 @@ impl<'de> serde::Deserialize<'de> for Deposit { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = Deposit; + type Value = GetFilteredSequencerBlockRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.Deposit") + formatter.write_str("struct astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut bridge_address__ = None; - let mut rollup_id__ = None; - let mut amount__ = None; - let mut asset_id__ = None; - let mut destination_chain_address__ = None; + let mut height__ = None; + let mut rollup_ids__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::BridgeAddress => { - if bridge_address__.is_some() { - return Err(serde::de::Error::duplicate_field("bridgeAddress")); + GeneratedField::Height => { + if height__.is_some() { + return Err(serde::de::Error::duplicate_field("height")); } - bridge_address__ = map_.next_value()?; + height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; } - GeneratedField::RollupId => { - if rollup_id__.is_some() { - return Err(serde::de::Error::duplicate_field("rollupId")); + GeneratedField::RollupIds => { + if rollup_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupIds")); } - rollup_id__ = map_.next_value()?; - } - GeneratedField::Amount => { - if amount__.is_some() { - return Err(serde::de::Error::duplicate_field("amount")); - } - amount__ = map_.next_value()?; - } - GeneratedField::AssetId => { - if asset_id__.is_some() { - return Err(serde::de::Error::duplicate_field("assetId")); - } - asset_id__ = - Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) - ; - } - GeneratedField::DestinationChainAddress => { - if destination_chain_address__.is_some() { - return Err(serde::de::Error::duplicate_field("destinationChainAddress")); - } - destination_chain_address__ = Some(map_.next_value()?); + rollup_ids__ = Some(map_.next_value()?); } } } - Ok(Deposit { - bridge_address: bridge_address__, - rollup_id: rollup_id__, - amount: amount__, - asset_id: asset_id__.unwrap_or_default(), - destination_chain_address: destination_chain_address__.unwrap_or_default(), + Ok(GetFilteredSequencerBlockRequest { + height: height__.unwrap_or_default(), + rollup_ids: rollup_ids__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.Deposit", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for FilteredSequencerBlock { +impl serde::Serialize for GetSequencerBlockRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -488,75 +471,30 @@ impl serde::Serialize for FilteredSequencerBlock { { use serde::ser::SerializeStruct; let mut len = 0; - if !self.block_hash.is_empty() { - len += 1; - } - if self.header.is_some() { - len += 1; - } - if !self.rollup_transactions.is_empty() { - len += 1; - } - if self.rollup_transactions_proof.is_some() { - len += 1; - } - if !self.all_rollup_ids.is_empty() { - len += 1; - } - if self.rollup_ids_proof.is_some() { + if self.height != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.FilteredSequencerBlock", len)?; - if !self.block_hash.is_empty() { + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetSequencerBlockRequest", len)?; + if self.height != 0 { #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("block_hash", pbjson::private::base64::encode(&self.block_hash).as_str())?; - } - if let Some(v) = self.header.as_ref() { - struct_ser.serialize_field("header", v)?; - } - if !self.rollup_transactions.is_empty() { - struct_ser.serialize_field("rollup_transactions", &self.rollup_transactions)?; - } - if let Some(v) = self.rollup_transactions_proof.as_ref() { - struct_ser.serialize_field("rollup_transactions_proof", v)?; - } - if !self.all_rollup_ids.is_empty() { - struct_ser.serialize_field("all_rollup_ids", &self.all_rollup_ids.iter().map(pbjson::private::base64::encode).collect::>())?; - } - if let Some(v) = self.rollup_ids_proof.as_ref() { - struct_ser.serialize_field("rollup_ids_proof", v)?; + struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for FilteredSequencerBlock { +impl<'de> serde::Deserialize<'de> for GetSequencerBlockRequest { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "block_hash", - "blockHash", - "header", - "rollup_transactions", - "rollupTransactions", - "rollup_transactions_proof", - "rollupTransactionsProof", - "all_rollup_ids", - "allRollupIds", - "rollup_ids_proof", - "rollupIdsProof", + "height", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - BlockHash, - Header, - RollupTransactions, - RollupTransactionsProof, - AllRollupIds, - RollupIdsProof, + Height, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -578,12 +516,7 @@ impl<'de> serde::Deserialize<'de> for FilteredSequencerBlock { E: serde::de::Error, { match value { - "blockHash" | "block_hash" => Ok(GeneratedField::BlockHash), - "header" => Ok(GeneratedField::Header), - "rollupTransactions" | "rollup_transactions" => Ok(GeneratedField::RollupTransactions), - "rollupTransactionsProof" | "rollup_transactions_proof" => Ok(GeneratedField::RollupTransactionsProof), - "allRollupIds" | "all_rollup_ids" => Ok(GeneratedField::AllRollupIds), - "rollupIdsProof" | "rollup_ids_proof" => Ok(GeneratedField::RollupIdsProof), + "height" => Ok(GeneratedField::Height), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -593,81 +526,38 @@ impl<'de> serde::Deserialize<'de> for FilteredSequencerBlock { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = FilteredSequencerBlock; + type Value = GetSequencerBlockRequest; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.FilteredSequencerBlock") + formatter.write_str("struct astria.sequencerblock.v1alpha1.GetSequencerBlockRequest") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut block_hash__ = None; - let mut header__ = None; - let mut rollup_transactions__ = None; - let mut rollup_transactions_proof__ = None; - let mut all_rollup_ids__ = None; - let mut rollup_ids_proof__ = None; + let mut height__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::BlockHash => { - if block_hash__.is_some() { - return Err(serde::de::Error::duplicate_field("blockHash")); - } - block_hash__ = - Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) - ; - } - GeneratedField::Header => { - if header__.is_some() { - return Err(serde::de::Error::duplicate_field("header")); - } - header__ = map_.next_value()?; - } - GeneratedField::RollupTransactions => { - if rollup_transactions__.is_some() { - return Err(serde::de::Error::duplicate_field("rollupTransactions")); - } - rollup_transactions__ = Some(map_.next_value()?); - } - GeneratedField::RollupTransactionsProof => { - if rollup_transactions_proof__.is_some() { - return Err(serde::de::Error::duplicate_field("rollupTransactionsProof")); - } - rollup_transactions_proof__ = map_.next_value()?; - } - GeneratedField::AllRollupIds => { - if all_rollup_ids__.is_some() { - return Err(serde::de::Error::duplicate_field("allRollupIds")); + GeneratedField::Height => { + if height__.is_some() { + return Err(serde::de::Error::duplicate_field("height")); } - all_rollup_ids__ = - Some(map_.next_value::>>()? - .into_iter().map(|x| x.0).collect()) + height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } - GeneratedField::RollupIdsProof => { - if rollup_ids_proof__.is_some() { - return Err(serde::de::Error::duplicate_field("rollupIdsProof")); - } - rollup_ids_proof__ = map_.next_value()?; - } } } - Ok(FilteredSequencerBlock { - block_hash: block_hash__.unwrap_or_default(), - header: header__, - rollup_transactions: rollup_transactions__.unwrap_or_default(), - rollup_transactions_proof: rollup_transactions_proof__, - all_rollup_ids: all_rollup_ids__.unwrap_or_default(), - rollup_ids_proof: rollup_ids_proof__, + Ok(GetSequencerBlockRequest { + height: height__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.FilteredSequencerBlock", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetSequencerBlockRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for GetFilteredSequencerBlockRequest { +impl serde::Serialize for RollupData { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -675,39 +565,40 @@ impl serde::Serialize for GetFilteredSequencerBlockRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if self.height != 0 { - len += 1; - } - if !self.rollup_ids.is_empty() { + if self.value.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest", len)?; - if self.height != 0 { - #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; - } - if !self.rollup_ids.is_empty() { - struct_ser.serialize_field("rollup_ids", &self.rollup_ids)?; + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.RollupData", len)?; + if let Some(v) = self.value.as_ref() { + match v { + rollup_data::Value::SequencedData(v) => { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("sequenced_data", pbjson::private::base64::encode(&v).as_str())?; + } + rollup_data::Value::Deposit(v) => { + struct_ser.serialize_field("deposit", v)?; + } + } } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for GetFilteredSequencerBlockRequest { +impl<'de> serde::Deserialize<'de> for RollupData { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "height", - "rollup_ids", - "rollupIds", + "sequenced_data", + "sequencedData", + "deposit", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { - Height, - RollupIds, + SequencedData, + Deposit, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -729,8 +620,8 @@ impl<'de> serde::Deserialize<'de> for GetFilteredSequencerBlockRequest { E: serde::de::Error, { match value { - "height" => Ok(GeneratedField::Height), - "rollupIds" | "rollup_ids" => Ok(GeneratedField::RollupIds), + "sequencedData" | "sequenced_data" => Ok(GeneratedField::SequencedData), + "deposit" => Ok(GeneratedField::Deposit), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -740,46 +631,43 @@ impl<'de> serde::Deserialize<'de> for GetFilteredSequencerBlockRequest { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GetFilteredSequencerBlockRequest; + type Value = RollupData; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest") + formatter.write_str("struct astria.sequencerblock.v1alpha1.RollupData") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { - let mut height__ = None; - let mut rollup_ids__ = None; + let mut value__ = None; while let Some(k) = map_.next_key()? { match k { - GeneratedField::Height => { - if height__.is_some() { - return Err(serde::de::Error::duplicate_field("height")); + GeneratedField::SequencedData => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("sequencedData")); } - height__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + value__ = map_.next_value::<::std::option::Option<::pbjson::private::BytesDeserialize<_>>>()?.map(|x| rollup_data::Value::SequencedData(x.0)); } - GeneratedField::RollupIds => { - if rollup_ids__.is_some() { - return Err(serde::de::Error::duplicate_field("rollupIds")); + GeneratedField::Deposit => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("deposit")); } - rollup_ids__ = Some(map_.next_value()?); + value__ = map_.next_value::<::std::option::Option<_>>()?.map(rollup_data::Value::Deposit) +; } } } - Ok(GetFilteredSequencerBlockRequest { - height: height__.unwrap_or_default(), - rollup_ids: rollup_ids__.unwrap_or_default(), + Ok(RollupData { + value: value__, }) } } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.RollupData", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for GetSequencerBlockRequest { +impl serde::Serialize for RollupTransactions { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result where @@ -787,215 +675,11 @@ impl serde::Serialize for GetSequencerBlockRequest { { use serde::ser::SerializeStruct; let mut len = 0; - if self.height != 0 { + if self.rollup_id.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetSequencerBlockRequest", len)?; - if self.height != 0 { - #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for GetSequencerBlockRequest { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "height", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Height, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "height" => Ok(GeneratedField::Height), - _ => Err(serde::de::Error::unknown_field(value, FIELDS)), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GetSequencerBlockRequest; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.GetSequencerBlockRequest") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut height__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Height => { - if height__.is_some() { - return Err(serde::de::Error::duplicate_field("height")); - } - height__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - } - } - Ok(GetSequencerBlockRequest { - height: height__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetSequencerBlockRequest", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for RollupData { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.value.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.RollupData", len)?; - if let Some(v) = self.value.as_ref() { - match v { - rollup_data::Value::SequencedData(v) => { - #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("sequenced_data", pbjson::private::base64::encode(&v).as_str())?; - } - rollup_data::Value::Deposit(v) => { - struct_ser.serialize_field("deposit", v)?; - } - } - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for RollupData { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "sequenced_data", - "sequencedData", - "deposit", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - SequencedData, - Deposit, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "sequencedData" | "sequenced_data" => Ok(GeneratedField::SequencedData), - "deposit" => Ok(GeneratedField::Deposit), - _ => Err(serde::de::Error::unknown_field(value, FIELDS)), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = RollupData; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.RollupData") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut value__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::SequencedData => { - if value__.is_some() { - return Err(serde::de::Error::duplicate_field("sequencedData")); - } - value__ = map_.next_value::<::std::option::Option<::pbjson::private::BytesDeserialize<_>>>()?.map(|x| rollup_data::Value::SequencedData(x.0)); - } - GeneratedField::Deposit => { - if value__.is_some() { - return Err(serde::de::Error::duplicate_field("deposit")); - } - value__ = map_.next_value::<::std::option::Option<_>>()?.map(rollup_data::Value::Deposit) -; - } - } - } - Ok(RollupData { - value: value__, - }) - } - } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.RollupData", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for RollupTransactions { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.rollup_id.is_some() { - len += 1; - } - if !self.transactions.is_empty() { - len += 1; + if !self.transactions.is_empty() { + len += 1; } if self.proof.is_some() { len += 1; @@ -1470,3 +1154,501 @@ impl<'de> serde::Deserialize<'de> for SequencerBlockHeader { deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SequencerBlockHeader", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SubmittedMetadata { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.block_hash.is_empty() { + len += 1; + } + if self.header.is_some() { + len += 1; + } + if !self.rollup_ids.is_empty() { + len += 1; + } + if self.rollup_transactions_proof.is_some() { + len += 1; + } + if self.rollup_ids_proof.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.SubmittedMetadata", len)?; + if !self.block_hash.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("block_hash", pbjson::private::base64::encode(&self.block_hash).as_str())?; + } + if let Some(v) = self.header.as_ref() { + struct_ser.serialize_field("header", v)?; + } + if !self.rollup_ids.is_empty() { + struct_ser.serialize_field("rollup_ids", &self.rollup_ids)?; + } + if let Some(v) = self.rollup_transactions_proof.as_ref() { + struct_ser.serialize_field("rollup_transactions_proof", v)?; + } + if let Some(v) = self.rollup_ids_proof.as_ref() { + struct_ser.serialize_field("rollup_ids_proof", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SubmittedMetadata { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "block_hash", + "blockHash", + "header", + "rollup_ids", + "rollupIds", + "rollup_transactions_proof", + "rollupTransactionsProof", + "rollup_ids_proof", + "rollupIdsProof", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + BlockHash, + Header, + RollupIds, + RollupTransactionsProof, + RollupIdsProof, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "blockHash" | "block_hash" => Ok(GeneratedField::BlockHash), + "header" => Ok(GeneratedField::Header), + "rollupIds" | "rollup_ids" => Ok(GeneratedField::RollupIds), + "rollupTransactionsProof" | "rollup_transactions_proof" => Ok(GeneratedField::RollupTransactionsProof), + "rollupIdsProof" | "rollup_ids_proof" => Ok(GeneratedField::RollupIdsProof), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SubmittedMetadata; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.v1alpha1.SubmittedMetadata") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut block_hash__ = None; + let mut header__ = None; + let mut rollup_ids__ = None; + let mut rollup_transactions_proof__ = None; + let mut rollup_ids_proof__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::BlockHash => { + if block_hash__.is_some() { + return Err(serde::de::Error::duplicate_field("blockHash")); + } + block_hash__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Header => { + if header__.is_some() { + return Err(serde::de::Error::duplicate_field("header")); + } + header__ = map_.next_value()?; + } + GeneratedField::RollupIds => { + if rollup_ids__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupIds")); + } + rollup_ids__ = Some(map_.next_value()?); + } + GeneratedField::RollupTransactionsProof => { + if rollup_transactions_proof__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupTransactionsProof")); + } + rollup_transactions_proof__ = map_.next_value()?; + } + GeneratedField::RollupIdsProof => { + if rollup_ids_proof__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupIdsProof")); + } + rollup_ids_proof__ = map_.next_value()?; + } + } + } + Ok(SubmittedMetadata { + block_hash: block_hash__.unwrap_or_default(), + header: header__, + rollup_ids: rollup_ids__.unwrap_or_default(), + rollup_transactions_proof: rollup_transactions_proof__, + rollup_ids_proof: rollup_ids_proof__, + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SubmittedMetadata", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SubmittedMetadataList { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.entries.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.SubmittedMetadataList", len)?; + if !self.entries.is_empty() { + struct_ser.serialize_field("entries", &self.entries)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SubmittedMetadataList { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "entries", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Entries, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "entries" => Ok(GeneratedField::Entries), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SubmittedMetadataList; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.v1alpha1.SubmittedMetadataList") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut entries__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Entries => { + if entries__.is_some() { + return Err(serde::de::Error::duplicate_field("entries")); + } + entries__ = Some(map_.next_value()?); + } + } + } + Ok(SubmittedMetadataList { + entries: entries__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SubmittedMetadataList", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SubmittedRollupData { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.sequencer_block_hash.is_empty() { + len += 1; + } + if self.rollup_id.is_some() { + len += 1; + } + if !self.transactions.is_empty() { + len += 1; + } + if self.proof.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.SubmittedRollupData", len)?; + if !self.sequencer_block_hash.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("sequencer_block_hash", pbjson::private::base64::encode(&self.sequencer_block_hash).as_str())?; + } + if let Some(v) = self.rollup_id.as_ref() { + struct_ser.serialize_field("rollup_id", v)?; + } + if !self.transactions.is_empty() { + struct_ser.serialize_field("transactions", &self.transactions.iter().map(pbjson::private::base64::encode).collect::>())?; + } + if let Some(v) = self.proof.as_ref() { + struct_ser.serialize_field("proof", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SubmittedRollupData { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "sequencer_block_hash", + "sequencerBlockHash", + "rollup_id", + "rollupId", + "transactions", + "proof", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SequencerBlockHash, + RollupId, + Transactions, + Proof, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "sequencerBlockHash" | "sequencer_block_hash" => Ok(GeneratedField::SequencerBlockHash), + "rollupId" | "rollup_id" => Ok(GeneratedField::RollupId), + "transactions" => Ok(GeneratedField::Transactions), + "proof" => Ok(GeneratedField::Proof), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SubmittedRollupData; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.v1alpha1.SubmittedRollupData") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut sequencer_block_hash__ = None; + let mut rollup_id__ = None; + let mut transactions__ = None; + let mut proof__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SequencerBlockHash => { + if sequencer_block_hash__.is_some() { + return Err(serde::de::Error::duplicate_field("sequencerBlockHash")); + } + sequencer_block_hash__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::RollupId => { + if rollup_id__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupId")); + } + rollup_id__ = map_.next_value()?; + } + GeneratedField::Transactions => { + if transactions__.is_some() { + return Err(serde::de::Error::duplicate_field("transactions")); + } + transactions__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + GeneratedField::Proof => { + if proof__.is_some() { + return Err(serde::de::Error::duplicate_field("proof")); + } + proof__ = map_.next_value()?; + } + } + } + Ok(SubmittedRollupData { + sequencer_block_hash: sequencer_block_hash__.unwrap_or_default(), + rollup_id: rollup_id__, + transactions: transactions__.unwrap_or_default(), + proof: proof__, + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SubmittedRollupData", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SubmittedRollupDataList { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.entries.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.SubmittedRollupDataList", len)?; + if !self.entries.is_empty() { + struct_ser.serialize_field("entries", &self.entries)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SubmittedRollupDataList { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "entries", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Entries, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "entries" => Ok(GeneratedField::Entries), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SubmittedRollupDataList; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.v1alpha1.SubmittedRollupDataList") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut entries__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Entries => { + if entries__.is_some() { + return Err(serde::de::Error::duplicate_field("entries")); + } + entries__ = Some(map_.next_value()?); + } + } + } + Ok(SubmittedRollupDataList { + entries: entries__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SubmittedRollupDataList", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs index cb142f313a..90ef2f9bfc 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs @@ -12,8 +12,8 @@ use super::{ are_rollup_txs_included, celestia::{ self, - CelestiaRollupBlob, - CelestiaSequencerBlob, + SubmittedMetadata, + SubmittedRollupData, }, raw, }; @@ -325,7 +325,7 @@ enum SequencerBlockErrorKind { /// /// This type exists to provide convenient access to the fields of /// a `[SequencerBlockHeader]`. -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct SequencerBlockHeaderParts { pub chain_id: tendermint::chain::Id, pub height: tendermint::block::Height, @@ -684,11 +684,10 @@ impl SequencerBlock { } } - /// Turn the sequencer block into a [`CelestiaSequencerBlob`] and its associated list of - /// [`CelestiaRollupBlob`]s. + /// Turn the sequencer block into a [`SubmittedMetadata`] and list of [`SubmittedRollupData`]. #[must_use] - pub fn into_celestia_blobs(self) -> (CelestiaSequencerBlob, Vec) { - celestia::CelestiaBlobBundle::from_sequencer_block(self).into_parts() + pub fn split_for_celestia(self) -> (SubmittedMetadata, Vec) { + celestia::PreparedBlock::from_sequencer_block(self).into_parts() } /// Converts from relevant header fields and the block data. diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs b/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs index d9b09e6a2b..14aecfd4ad 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs @@ -16,16 +16,15 @@ use super::{ }; use crate::Protobuf; -/// A bundle of blobs constructed from a [`super::SequencerBlock`]. +/// A [`super::SequencerBlock`] split and prepared for submission to a data availability provider. /// -/// Consists of a head [`CelestiaSequencerBlob`] and a tail of [`CelestiaRollupBlob`]s. -/// Used as a pass-through data structure to -pub(super) struct CelestiaBlobBundle { - head: CelestiaSequencerBlob, - tail: Vec, +/// Consists of a head [`SubmittedMetadata`] and a tail of [`SubmittedRollupData`]s. +pub(super) struct PreparedBlock { + head: SubmittedMetadata, + tail: Vec, } -impl CelestiaBlobBundle { +impl PreparedBlock { /// Construct a bundle of celestia blobs from a [`super::SequencerBlock`]. #[must_use] pub(super) fn from_sequencer_block(block: SequencerBlock) -> Self { @@ -37,7 +36,7 @@ impl CelestiaBlobBundle { rollup_ids_proof, } = block.into_parts(); - let head = CelestiaSequencerBlob { + let head = SubmittedMetadata { block_hash, header, rollup_ids: rollup_transactions.keys().copied().collect(), @@ -52,7 +51,7 @@ impl CelestiaBlobBundle { proof, .. } = rollup_txs.into_parts(); - tail.push(CelestiaRollupBlob { + tail.push(SubmittedRollupData { sequencer_block_hash: block_hash, rollup_id, transactions, @@ -65,8 +64,8 @@ impl CelestiaBlobBundle { } } - /// Returns the head and the tail of the celestia blob bundle, consuming it. - pub(super) fn into_parts(self) -> (CelestiaSequencerBlob, Vec) { + /// Returns the head and the tail of the split block, consuming it. + pub(super) fn into_parts(self) -> (SubmittedMetadata, Vec) { (self.head, self.tail) } } @@ -74,15 +73,15 @@ impl CelestiaBlobBundle { #[derive(Debug, thiserror::Error)] #[error("failed constructing a celestia rollup blob")] #[allow(clippy::module_name_repetitions)] -pub struct CelestiaRollupBlobError { +pub struct SubmittedRollupDataError { #[source] - kind: CelestiaRollupBlobErrorKind, + kind: SubmittedRollupDataErrorKind, } -impl CelestiaRollupBlobError { +impl SubmittedRollupDataError { fn field_not_set(field: &'static str) -> Self { Self { - kind: CelestiaRollupBlobErrorKind::FieldNotSet { + kind: SubmittedRollupDataErrorKind::FieldNotSet { field, }, } @@ -90,7 +89,7 @@ impl CelestiaRollupBlobError { fn rollup_id(source: IncorrectRollupIdLength) -> Self { Self { - kind: CelestiaRollupBlobErrorKind::RollupId { + kind: SubmittedRollupDataErrorKind::RollupId { source, }, } @@ -98,7 +97,7 @@ impl CelestiaRollupBlobError { fn proof(source: ::Error) -> Self { Self { - kind: CelestiaRollupBlobErrorKind::Proof { + kind: SubmittedRollupDataErrorKind::Proof { source, }, } @@ -106,13 +105,13 @@ impl CelestiaRollupBlobError { fn sequencer_block_hash(actual_len: usize) -> Self { Self { - kind: CelestiaRollupBlobErrorKind::SequencerBlockHash(actual_len), + kind: SubmittedRollupDataErrorKind::SequencerBlockHash(actual_len), } } } #[derive(Debug, thiserror::Error)] -enum CelestiaRollupBlobErrorKind { +enum SubmittedRollupDataErrorKind { #[error("the expected field in the raw source type was not set: `{field}`")] FieldNotSet { field: &'static str }, #[error("failed converting the provided bytes to Rollup ID")] @@ -128,11 +127,11 @@ enum CelestiaRollupBlobErrorKind { SequencerBlockHash(usize), } -/// A shadow of [`CelestiaRollupBlob`] with public access to all its fields. +/// A shadow of [`SubmittedRollupData`] with public access to all its fields. /// -/// At the moment there are no invariants upheld by [`CelestiaRollupBlob`] so +/// At the moment there are no invariants upheld by [`SubmittedRollupData`] so /// they can be converted directly into one another. This can change in the future. -pub struct UncheckedCelestiaRollupBlob { +pub struct UncheckedSubmittedRollupData { /// The hash of the sequencer block. Must be 32 bytes. pub sequencer_block_hash: [u8; 32], /// The 32 bytes identifying the rollup this blob belongs to. Matches @@ -144,16 +143,16 @@ pub struct UncheckedCelestiaRollupBlob { pub proof: merkle::Proof, } -impl UncheckedCelestiaRollupBlob { +impl UncheckedSubmittedRollupData { #[must_use] - pub fn into_celestia_rollup_blob(self) -> CelestiaRollupBlob { - CelestiaRollupBlob::from_unchecked(self) + pub fn into_celestia_rollup_blob(self) -> SubmittedRollupData { + SubmittedRollupData::from_unchecked(self) } } #[derive(Clone, Debug)] #[allow(clippy::module_name_repetitions)] -pub struct CelestiaRollupBlob { +pub struct SubmittedRollupData { /// The hash of the sequencer block. Must be 32 bytes. sequencer_block_hash: [u8; 32], /// The 32 bytes identifying the rollup this blob belongs to. Matches @@ -165,7 +164,7 @@ pub struct CelestiaRollupBlob { proof: merkle::Proof, } -impl CelestiaRollupBlob { +impl SubmittedRollupData { #[must_use] pub fn proof(&self) -> &merkle::Proof { &self.proof @@ -190,8 +189,8 @@ impl CelestiaRollupBlob { /// /// This type does not uphold any extra invariants so there are no extra checks necessary. #[must_use] - pub fn from_unchecked(unchecked: UncheckedCelestiaRollupBlob) -> Self { - let UncheckedCelestiaRollupBlob { + pub fn from_unchecked(unchecked: UncheckedSubmittedRollupData) -> Self { + let UncheckedSubmittedRollupData { sequencer_block_hash, rollup_id, transactions, @@ -209,14 +208,14 @@ impl CelestiaRollupBlob { /// /// Useful to get public access to the type's fields. #[must_use] - pub fn into_unchecked(self) -> UncheckedCelestiaRollupBlob { + pub fn into_unchecked(self) -> UncheckedSubmittedRollupData { let Self { sequencer_block_hash, rollup_id, transactions, proof, } = self; - UncheckedCelestiaRollupBlob { + UncheckedSubmittedRollupData { sequencer_block_hash, rollup_id, transactions, @@ -228,14 +227,14 @@ impl CelestiaRollupBlob { /// /// Useful for then encoding it as protobuf. #[must_use] - pub fn into_raw(self) -> raw::CelestiaRollupBlob { + pub fn into_raw(self) -> raw::SubmittedRollupData { let Self { sequencer_block_hash, rollup_id, transactions, proof, } = self; - raw::CelestiaRollupBlob { + raw::SubmittedRollupData { sequencer_block_hash: sequencer_block_hash.to_vec(), rollup_id: Some(rollup_id.to_raw()), transactions, @@ -247,26 +246,26 @@ impl CelestiaRollupBlob { /// /// # Errors /// TODO(https://github.com/astriaorg/astria/issues/612) - pub fn try_from_raw(raw: raw::CelestiaRollupBlob) -> Result { - let raw::CelestiaRollupBlob { + pub fn try_from_raw(raw: raw::SubmittedRollupData) -> Result { + let raw::SubmittedRollupData { sequencer_block_hash, rollup_id, transactions, proof, } = raw; let Some(rollup_id) = rollup_id else { - return Err(CelestiaRollupBlobError::field_not_set("rollup_id")); + return Err(SubmittedRollupDataError::field_not_set("rollup_id")); }; let rollup_id = - RollupId::try_from_raw(&rollup_id).map_err(CelestiaRollupBlobError::rollup_id)?; - let sequencer_block_hash = sequencer_block_hash - .try_into() - .map_err(|bytes: Vec| CelestiaRollupBlobError::sequencer_block_hash(bytes.len()))?; + RollupId::try_from_raw(&rollup_id).map_err(SubmittedRollupDataError::rollup_id)?; + let sequencer_block_hash = sequencer_block_hash.try_into().map_err(|bytes: Vec| { + SubmittedRollupDataError::sequencer_block_hash(bytes.len()) + })?; let proof = 'proof: { let Some(proof) = proof else { - break 'proof Err(CelestiaRollupBlobError::field_not_set("proof")); + break 'proof Err(SubmittedRollupDataError::field_not_set("proof")); }; - merkle::Proof::try_from_raw(proof).map_err(CelestiaRollupBlobError::proof) + merkle::Proof::try_from_raw(proof).map_err(SubmittedRollupDataError::proof) }?; Ok(Self { sequencer_block_hash, @@ -280,21 +279,21 @@ impl CelestiaRollupBlob { #[derive(Debug, thiserror::Error)] #[error("failed constructing a celestia sequencer blob")] #[allow(clippy::module_name_repetitions)] -pub struct CelestiaSequencerBlobError { +pub struct SubmittedMetadataError { #[source] - kind: CelestiaSequencerBlobErrorKind, + kind: SubmittedMetadataErrorKind, } -impl CelestiaSequencerBlobError { +impl SubmittedMetadataError { fn block_hash(actual_len: usize) -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::BlockHash(actual_len), + kind: SubmittedMetadataErrorKind::BlockHash(actual_len), } } fn header(source: SequencerBlockHeaderError) -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::Header { + kind: SubmittedMetadataErrorKind::Header { source, }, } @@ -302,13 +301,13 @@ impl CelestiaSequencerBlobError { fn field_not_set(field: &'static str) -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::FieldNotSet(field), + kind: SubmittedMetadataErrorKind::FieldNotSet(field), } } fn rollup_ids(source: IncorrectRollupIdLength) -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::RollupIds { + kind: SubmittedMetadataErrorKind::RollupIds { source, }, } @@ -316,7 +315,7 @@ impl CelestiaSequencerBlobError { fn rollup_transactions_proof(source: ::Error) -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::RollupTransactionsProof { + kind: SubmittedMetadataErrorKind::RollupTransactionsProof { source, }, } @@ -324,7 +323,7 @@ impl CelestiaSequencerBlobError { fn rollup_ids_proof(source: ::Error) -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::RollupIdsProof { + kind: SubmittedMetadataErrorKind::RollupIdsProof { source, }, } @@ -332,19 +331,19 @@ impl CelestiaSequencerBlobError { fn rollup_transactions_not_in_cometbft_block() -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::RollupTransactionsNotInCometBftBlock, + kind: SubmittedMetadataErrorKind::RollupTransactionsNotInCometBftBlock, } } fn rollup_ids_not_in_cometbft_block() -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::RollupIdsNotInCometBftBlock, + kind: SubmittedMetadataErrorKind::RollupIdsNotInCometBftBlock, } } } #[derive(Debug, thiserror::Error)] -enum CelestiaSequencerBlobErrorKind { +enum SubmittedMetadataErrorKind { #[error( "the provided bytes were too short for a block hash; expected: 32 bytes, actual: {0} bytes" )] @@ -378,17 +377,17 @@ enum CelestiaSequencerBlobErrorKind { RollupIdsNotInCometBftBlock, } -/// A shadow of [`CelestiaSequencerBlob`] with public access to its fields. +/// A shadow of [`SubmittedMetadata`] with public access to its fields. /// /// This type does not guarantee any invariants and is mainly useful to get /// access the sequencer block's internal types. #[derive(Clone, Debug)] -pub struct UncheckedCelestiaSequencerBlob { +pub struct UncheckedSubmittedMetadata { pub block_hash: [u8; 32], /// The original `CometBFT` header that is the input to this blob's original sequencer block. /// Corresponds to `astria.SequencerBlock.header`. pub header: SequencerBlockHeader, - /// The rollup rollup IDs for which `CelestiaRollupBlob`s were submitted to celestia. + /// The rollup rollup IDs for which `SubmittedRollupData`s were submitted to celestia. /// Corresponds to the `astria.sequencer.v1.RollupTransactions.id` field /// and is extracted from `astria.SequencerBlock.rollup_transactions`. pub rollup_ids: Vec, @@ -404,25 +403,23 @@ pub struct UncheckedCelestiaSequencerBlob { pub rollup_ids_proof: merkle::Proof, } -impl UncheckedCelestiaSequencerBlob { - /// Converts this unchecked blob into its checked [`CelestiaSequencerBlob`] representation. +impl UncheckedSubmittedMetadata { + /// Converts this unchecked blob into its checked [`SubmittedMetadata`] representation. /// /// # Errors /// TODO(https://github.com/astriaorg/astria/issues/612) pub fn try_into_celestia_sequencer_blob( self, - ) -> Result { - CelestiaSequencerBlob::try_from_unchecked(self) + ) -> Result { + SubmittedMetadata::try_from_unchecked(self) } /// Converts from the raw decoded protobuf representation of this type. /// /// # Errors /// TODO(https://github.com/astriaorg/astria/issues/612) - pub fn try_from_raw( - raw: raw::CelestiaSequencerBlob, - ) -> Result { - let raw::CelestiaSequencerBlob { + pub fn try_from_raw(raw: raw::SubmittedMetadata) -> Result { + let raw::SubmittedMetadata { block_hash, header, rollup_ids, @@ -432,39 +429,37 @@ impl UncheckedCelestiaSequencerBlob { } = raw; let header = 'header: { let Some(header) = header else { - break 'header Err(CelestiaSequencerBlobError::field_not_set("header")); + break 'header Err(SubmittedMetadataError::field_not_set("header")); }; - SequencerBlockHeader::try_from_raw(header).map_err(CelestiaSequencerBlobError::header) + SequencerBlockHeader::try_from_raw(header).map_err(SubmittedMetadataError::header) }?; let rollup_ids: Vec<_> = rollup_ids .iter() .map(RollupId::try_from_raw) .collect::>() - .map_err(CelestiaSequencerBlobError::rollup_ids)?; + .map_err(SubmittedMetadataError::rollup_ids)?; let rollup_transactions_proof = 'transactions_proof: { let Some(rollup_transactions_proof) = rollup_transactions_proof else { - break 'transactions_proof Err(CelestiaSequencerBlobError::field_not_set( + break 'transactions_proof Err(SubmittedMetadataError::field_not_set( "rollup_transactions_root", )); }; merkle::Proof::try_from_raw(rollup_transactions_proof) - .map_err(CelestiaSequencerBlobError::rollup_transactions_proof) + .map_err(SubmittedMetadataError::rollup_transactions_proof) }?; let rollup_ids_proof = 'ids_proof: { let Some(rollup_ids_proof) = rollup_ids_proof else { - break 'ids_proof Err(CelestiaSequencerBlobError::field_not_set( - "rollup_ids_proof", - )); + break 'ids_proof Err(SubmittedMetadataError::field_not_set("rollup_ids_proof")); }; merkle::Proof::try_from_raw(rollup_ids_proof) - .map_err(CelestiaSequencerBlobError::rollup_ids_proof) + .map_err(SubmittedMetadataError::rollup_ids_proof) }?; let block_hash = block_hash .try_into() - .map_err(|bytes: Vec<_>| CelestiaSequencerBlobError::block_hash(bytes.len()))?; + .map_err(|bytes: Vec<_>| SubmittedMetadataError::block_hash(bytes.len()))?; Ok(Self { block_hash, @@ -478,12 +473,12 @@ impl UncheckedCelestiaSequencerBlob { #[derive(Clone, Debug)] #[allow(clippy::module_name_repetitions)] -pub struct CelestiaSequencerBlob { +pub struct SubmittedMetadata { /// The block hash obtained from hashing `.header`. block_hash: [u8; 32], /// The sequencer block header. header: SequencerBlockHeader, - /// The rollup IDs for which `CelestiaRollupBlob`s were submitted to celestia. + /// The rollup IDs for which `SubmittedRollupData`s were submitted to celestia. /// Corresponds to the `astria.sequencer.v1.RollupTransactions.id` field /// and is extracted from `astria.SequencerBlock.rollup_transactions`. rollup_ids: Vec, @@ -499,7 +494,7 @@ pub struct CelestiaSequencerBlob { rollup_ids_proof: merkle::Proof, } -impl CelestiaSequencerBlob { +impl SubmittedMetadata { /// Returns the block hash of the tendermint header stored in this blob. #[must_use] pub fn block_hash(&self) -> [u8; 32] { @@ -539,7 +534,7 @@ impl CelestiaSequencerBlob { /// Converts into the unchecked representation fo this type. #[must_use] - pub fn into_unchecked(self) -> UncheckedCelestiaSequencerBlob { + pub fn into_unchecked(self) -> UncheckedSubmittedMetadata { let Self { block_hash, header, @@ -547,7 +542,7 @@ impl CelestiaSequencerBlob { rollup_transactions_proof, rollup_ids_proof, } = self; - UncheckedCelestiaSequencerBlob { + UncheckedSubmittedMetadata { block_hash, header, rollup_ids, @@ -561,9 +556,9 @@ impl CelestiaSequencerBlob { /// # Errors /// TODO(https://github.com/astriaorg/astria/issues/612) pub fn try_from_unchecked( - unchecked: UncheckedCelestiaSequencerBlob, - ) -> Result { - let UncheckedCelestiaSequencerBlob { + unchecked: UncheckedSubmittedMetadata, + ) -> Result { + let UncheckedSubmittedMetadata { block_hash, header, rollup_ids, @@ -575,7 +570,7 @@ impl CelestiaSequencerBlob { &Sha256::digest(header.rollup_transactions_root()), header.data_hash(), ) { - return Err(CelestiaSequencerBlobError::rollup_transactions_not_in_cometbft_block()); + return Err(SubmittedMetadataError::rollup_transactions_not_in_cometbft_block()); } if !super::are_rollup_ids_included( @@ -583,7 +578,7 @@ impl CelestiaSequencerBlob { &rollup_ids_proof, header.data_hash(), ) { - return Err(CelestiaSequencerBlobError::rollup_ids_not_in_cometbft_block()); + return Err(SubmittedMetadataError::rollup_ids_not_in_cometbft_block()); } Ok(Self { @@ -596,7 +591,7 @@ impl CelestiaSequencerBlob { } /// Converts into the raw decoded protobuf representation of this type. - pub fn into_raw(self) -> raw::CelestiaSequencerBlob { + pub fn into_raw(self) -> raw::SubmittedMetadata { let Self { block_hash, header, @@ -605,7 +600,7 @@ impl CelestiaSequencerBlob { rollup_ids_proof, .. } = self; - raw::CelestiaSequencerBlob { + raw::SubmittedMetadata { block_hash: block_hash.to_vec(), header: Some(header.into_raw()), rollup_ids: rollup_ids.into_iter().map(RollupId::into_raw).collect(), @@ -618,10 +613,8 @@ impl CelestiaSequencerBlob { /// /// # Errors /// TODO(https://github.com/astriaorg/astria/issues/612) - pub fn try_from_raw( - raw: raw::CelestiaSequencerBlob, - ) -> Result { - UncheckedCelestiaSequencerBlob::try_from_raw(raw) - .and_then(UncheckedCelestiaSequencerBlob::try_into_celestia_sequencer_blob) + pub fn try_from_raw(raw: raw::SubmittedMetadata) -> Result { + UncheckedSubmittedMetadata::try_from_raw(raw) + .and_then(UncheckedSubmittedMetadata::try_into_celestia_sequencer_blob) } } diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/mod.rs b/crates/astria-core/src/sequencerblock/v1alpha1/mod.rs index 17aa88bfaf..62487151cd 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/mod.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/mod.rs @@ -6,8 +6,8 @@ pub use block::{ SequencerBlock, }; pub use celestia::{ - CelestiaRollupBlob, - CelestiaSequencerBlob, + SubmittedMetadata, + SubmittedRollupData, }; use indexmap::IndexMap; use sha2::{ diff --git a/crates/astria-sequencer-relayer/Cargo.toml b/crates/astria-sequencer-relayer/Cargo.toml index 9157fe8bec..b11c911bf7 100644 --- a/crates/astria-sequencer-relayer/Cargo.toml +++ b/crates/astria-sequencer-relayer/Cargo.toml @@ -64,6 +64,7 @@ telemetry = { package = "astria-telemetry", path = "../astria-telemetry", featur "display", ] } tokio-stream = { workspace = true } +const_format = "0.2.32" [dev-dependencies] astria-grpc-mock = { path = "../astria-grpc-mock" } @@ -90,6 +91,8 @@ wiremock = { workspace = true } assert-json-diff = "2.0.2" tower-http = { version = "0.4", features = ["auth"] } tower = { version = "0.4.13" } +tokio-test.workspace = true +rand_chacha = "0.3.1" [build-dependencies] astria-build-info = { path = "../astria-build-info", features = ["build"] } diff --git a/crates/astria-sequencer-relayer/src/config.rs b/crates/astria-sequencer-relayer/src/config.rs index e7f9fc82be..a14d4f0ab4 100644 --- a/crates/astria-sequencer-relayer/src/config.rs +++ b/crates/astria-sequencer-relayer/src/config.rs @@ -86,7 +86,7 @@ impl IncludeRollup { self.0.is_empty() || self.0.contains(rollup_id) } - fn parse(input: &str) -> eyre::Result { + pub(crate) fn parse(input: &str) -> eyre::Result { let rollup_ids = input .split(',') .filter(|base64_encoded_id| !base64_encoded_id.is_empty()) diff --git a/crates/astria-sequencer-relayer/src/metrics_init.rs b/crates/astria-sequencer-relayer/src/metrics_init.rs index 342be0307b..995ca2b9c0 100644 --- a/crates/astria-sequencer-relayer/src/metrics_init.rs +++ b/crates/astria-sequencer-relayer/src/metrics_init.rs @@ -66,6 +66,13 @@ pub fn register() { "The time it takes to submit a blob to Celestia" ); + describe_histogram!( + CELESTIA_PAYLOAD_CREATION_LATENCY, + Unit::Microseconds, + "The time it takes to create a new payload for submitting to Celestia (encoding to \ + protobuf, compression, creating blobs)" + ); + describe_gauge!( TOTAL_BLOB_DATA_SIZE_FOR_ASTRIA_BLOCK, Unit::Bytes, @@ -100,6 +107,11 @@ pub const BLOCKS_PER_CELESTIA_TX: &str = pub const BLOBS_PER_CELESTIA_TX: &str = concat!(env!("CARGO_CRATE_NAME"), "_blobs_per_celestia_tx"); +pub const CELESTIA_PAYLOAD_CREATION_LATENCY: &str = concat!( + env!("CARGO_CRATE_NAME"), + "_celestia_payload_creation_latency" +); + pub const CELESTIA_SUBMISSION_LATENCY: &str = concat!(env!("CARGO_CRATE_NAME"), "_celestia_submission_latency"); diff --git a/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs b/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs index f37e89aff8..093bd423cb 100644 --- a/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs +++ b/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs @@ -1,142 +1,578 @@ +use std::{ + collections::{ + BTreeSet, + HashMap, + HashSet, + }, + pin::Pin, + task::Poll, +}; + use astria_core::{ brotli::compress_bytes, + generated::sequencerblock::v1alpha1::{ + SubmittedMetadata, + SubmittedMetadataList, + SubmittedRollupData, + SubmittedRollupDataList, + }, primitive::v1::RollupId, }; -use astria_eyre::eyre::{ - self, - WrapErr as _, -}; use celestia_types::{ nmt::Namespace, Blob, }; -use prost::Message as _; +use futures::Future; +use pin_project_lite::pin_project; use sequencer_client::SequencerBlock; use tendermint::block::Height as SequencerHeight; -use tracing::debug; - -use crate::{ - metrics_init, - IncludeRollup, +use tracing::{ + trace, + warn, }; -// allow: the signature is dictated by the `serde(serialize_with = ...)` attribute. -#[allow(clippy::trivially_copy_pass_by_ref)] -fn serialize_height(height: &SequencerHeight, serializer: S) -> Result +use crate::IncludeRollup; + +/// The maximum permitted payload size in bytes that relayer will send to Celestia. +/// +/// Taken as half the maximum block size that Celestia currently allows (2 MB at the moment). +const MAX_PAYLOAD_SIZE_BYTES: usize = 1_000_000; + +pub(super) struct Submission { + input: Input, + payload: Payload, +} + +impl Submission { + pub(super) fn into_blobs(self) -> Vec { + self.payload.blobs + } + + pub(super) fn input_metadata(&self) -> &InputMeta { + self.input.meta() + } + + pub(super) fn num_blobs(&self) -> usize { + self.payload.num_blobs() + } + + pub(super) fn num_blocks(&self) -> usize { + self.input.num_blocks() + } + + pub(super) fn greatest_sequencer_height(&self) -> SequencerHeight { + self.input.greatest_sequencer_height().expect( + "`Submission` should not be constructed if no blocks are present in the input. This \ + is a bug", + ) + } + + // allow: used for metric gauges, which require f64. Precision loss is ok and of no + // significance. + #[allow(clippy::cast_precision_loss)] + pub(super) fn compression_ratio(&self) -> f64 { + self.compressed_size() as f64 / self.uncompressed_size() as f64 + } + + pub(super) fn compressed_size(&self) -> usize { + self.payload.compressed_size() + } + + pub(super) fn uncompressed_size(&self) -> usize { + self.payload.uncompressed_size() + } +} + +#[derive(Debug, thiserror::Error)] +pub(super) enum PayloadError { + #[error("failed to compress protobuf encoded bytes")] + Compress(#[from] std::io::Error), + #[error("failed to create Celestia blob from compressed bytes")] + Blob(#[from] celestia_types::Error), +} + +#[derive(Debug, Default)] +struct Payload { + compressed_size: usize, + uncompressed_size: usize, + blobs: Vec, +} + +impl Payload { + fn new() -> Self { + Self::default() + } + + fn with_capacity(cap: usize) -> Self { + Self { + uncompressed_size: 0, + compressed_size: 0, + blobs: Vec::with_capacity(cap), + } + } + + fn is_empty(&self) -> bool { + self.blobs.is_empty() + } + + fn num_blobs(&self) -> usize { + self.blobs.len() + } + + /// Adds `value` to the payload. + /// + /// Encodes `value` as protobuf, compresses it, and creates a Celestia [`Blob`] under + /// `namespace`. + fn try_add( + &mut self, + namespace: Namespace, + value: &T, + ) -> Result<(), PayloadError> { + let encoded = value.encode_to_vec(); + let compressed = compress_bytes(&encoded)?; + let blob = Blob::new(namespace, compressed)?; + self.uncompressed_size += encoded.len(); + self.compressed_size += blob.data.len(); + self.blobs.push(blob); + Ok(()) + } + + fn compressed_size(&self) -> usize { + self.compressed_size + } + + fn uncompressed_size(&self) -> usize { + self.uncompressed_size + } +} + +#[derive(Debug, thiserror::Error)] +pub(super) enum TryIntoPayloadError { + #[error("failed adding protobuf `{type_url}` to Celestia blob payload")] + AddToPayload { + source: PayloadError, + type_url: String, + }, + #[error( + "there was no sequencer namespace present in the input so a payload of Celestia blobs \ + could not be constructed" + )] + NoSequencerNamespacePresent, +} + +#[derive(Clone, Debug, Default, serde::Serialize)] +pub(super) struct InputMeta { + #[serde(serialize_with = "serialize_sequencer_heights")] + sequencer_heights: BTreeSet, + #[serde(serialize_with = "serialize_opt_namespace")] + sequencer_namespace: Option, + #[serde(serialize_with = "serialize_included_rollups")] + rollups_included: HashMap, + rollups_excluded: HashSet, +} + +#[derive(Clone, Debug, Default)] +struct Input { + metadata: Vec, + rollup_data_for_namespace: HashMap>, + meta: InputMeta, +} + +impl Input { + fn new() -> Self { + Self::default() + } + + fn meta(&self) -> &InputMeta { + &self.meta + } + + fn num_blocks(&self) -> usize { + self.metadata.len() + } + + fn extend_from_sequencer_block( + &mut self, + block: SequencerBlock, + rollup_filter: &IncludeRollup, + ) { + if !self.meta.sequencer_heights.insert(block.height()) { + warn!( + sequencer_height = block.height().value(), + "a Sequencer Block was added to the next submission input but its height was \ + already present; carrying on, but this shouldn't happen", + ); + } + let (metadata, rollup_data) = block.split_for_celestia(); + let metadata = metadata.into_raw(); + + // XXX: This should really be set at the beginning of the sequencer-relayer and reused + // everywhere. + self.meta + .sequencer_namespace + .get_or_insert_with(|| sequencer_namespace(&metadata)); + self.metadata.push(metadata); + for elem in rollup_data { + if rollup_filter.should_include(&elem.rollup_id()) { + let namespace = + astria_core::celestia::namespace_v0_from_rollup_id(elem.rollup_id()); + self.meta + .rollups_included + .insert(elem.rollup_id(), namespace); + let list = self.rollup_data_for_namespace.entry(namespace).or_default(); + list.push(elem.into_raw()); + } else { + self.meta.rollups_excluded.insert(elem.rollup_id()); + } + } + } + + fn greatest_sequencer_height(&self) -> Option { + self.meta.sequencer_heights.last().copied() + } + + /// Attempts to convert the input into a payload of Celestia blobs. + fn try_into_payload(self) -> Result { + use prost::Name as _; + + let mut payload = + Payload::with_capacity(self.metadata.len() + self.rollup_data_for_namespace.len()); + + let sequencer_namespace = self + .meta + .sequencer_namespace + .ok_or(TryIntoPayloadError::NoSequencerNamespacePresent)?; + payload + .try_add( + sequencer_namespace, + &SubmittedMetadataList { + entries: self.metadata, + }, + ) + .map_err(|source| TryIntoPayloadError::AddToPayload { + source, + type_url: SubmittedMetadataList::type_url(), + })?; + + for (namespace, entries) in self.rollup_data_for_namespace { + payload + .try_add( + namespace, + &SubmittedRollupDataList { + entries, + }, + ) + .map_err(|source| TryIntoPayloadError::AddToPayload { + source, + type_url: SubmittedRollupDataList::full_name(), + })?; + } + Ok(payload) + } +} + +#[derive(Debug)] +pub(super) struct NextSubmission { + rollup_filter: IncludeRollup, + input: Input, + payload: Payload, +} + +#[derive(Debug, thiserror::Error)] +pub(super) enum TryAddError { + #[error("next submission is full")] + Full(Box), + #[error("failed converting input into payload of Celestia blobs")] + IntoPayload(#[from] TryIntoPayloadError), + #[error( + "sequencer block at height `{sequencer_height}` is too large; its compressed single-block + payload has size `{compressed_size}` bytes, which is larger than the maximum exception + threshold of `{MAX_PAYLOAD_SIZE_BYTES}` bytes" + )] + OversizedBlock { + sequencer_height: SequencerHeight, + compressed_size: usize, + }, +} + +impl NextSubmission { + pub(super) fn new(rollup_filter: IncludeRollup) -> Self { + Self { + rollup_filter, + input: Input::new(), + payload: Payload::new(), + } + } + + /// Adds a [`SequencerBlock`] to the next submission. + /// + /// This function works by cloning the current payload input, adding `block` to it, + /// and generating a new payload. If the new payload is sufficiently small, `block` + /// will be included in the next submission. If it would exceed the maximum payload + /// size it is returned as an error. + pub(super) fn try_add(&mut self, block: SequencerBlock) -> Result<(), TryAddError> { + let mut input_candidate = self.input.clone(); + input_candidate.extend_from_sequencer_block(block.clone(), &self.rollup_filter); + + let payload_creation_start = std::time::Instant::now(); + let payload_candidate = input_candidate.clone().try_into_payload()?; + metrics::histogram!(crate::metrics_init::CELESTIA_PAYLOAD_CREATION_LATENCY) + .record(payload_creation_start.elapsed()); + + if payload_candidate.compressed_size <= MAX_PAYLOAD_SIZE_BYTES { + self.input = input_candidate; + self.payload = payload_candidate; + Ok(()) + } else if input_candidate.num_blocks() == 1 + && payload_candidate.compressed_size > MAX_PAYLOAD_SIZE_BYTES + { + Err(TryAddError::OversizedBlock { + sequencer_height: block.height(), + compressed_size: payload_candidate.compressed_size, + }) + } else { + Err(TryAddError::Full(block.into())) + } + } + + /// Lazily move the currently items out of the next submission. + /// + /// The main reason for this method to exist is to work around async-cancellation. + /// Only when the returned [`TakeNextSubmission`] future is polled is the data moved + /// out, leaving behind an empty [`NextSubmission`] that can be used to accumulate more blocks. + pub(super) fn take(&mut self) -> TakeSubmission<'_> { + TakeSubmission { + inner: Some(self), + } + } +} + +pin_project! { + pub(super) struct TakeSubmission<'a> { + inner: Option<&'a mut NextSubmission>, + } +} + +impl<'a> Future for TakeSubmission<'a> { + type Output = Option; + + fn poll(self: Pin<&mut Self>, _: &mut std::task::Context<'_>) -> Poll { + let next = self + .project() + .inner + .take() + .expect("future must not be polled twice"); + let input = std::mem::take(&mut next.input); + let payload = std::mem::take(&mut next.payload); + if payload.is_empty() { + trace!("payload is empty"); + Poll::Ready(None) + } else { + trace!( + number_of_blobs = payload.num_blobs(), + number_of_blocks = input.num_blocks(), + "returning payload" + ); + Poll::Ready(Some(Submission { + input, + payload, + })) + } + } +} + +/// Constructs a Celestia [`Namespace`] from a [`SubmittedMetadata`]. +/// +/// # Note +/// This should be constructed once at the beginning of sequencer-relayer and then +/// injected everywhere. +/// +/// # Panics +/// Panics if the `header.header` field is unset. This is OK because the argument to this +/// function should only come from a [`SubmittedMetadata`] that was created from its verified +/// counterpart [`astria_core::sequencerblock::v1alpha1::SubmittedMetadata::into_raw`]. +fn sequencer_namespace(metadata: &SubmittedMetadata) -> Namespace { + use const_format::concatcp; + use prost::Name; + const HEADER_EXPECT_MSG: &str = + concatcp!(SubmittedMetadata::PACKAGE, ".", SubmittedMetadata::NAME,); + + astria_core::celestia::namespace_v0_from_sha256_of_bytes( + metadata + .header + .as_ref() + .expect(HEADER_EXPECT_MSG) + .chain_id + .as_bytes(), + ) +} + +fn serialize_opt_namespace( + namespace: &Option, + serializer: S, +) -> Result where S: serde::ser::Serializer, { - serializer.serialize_u64(height.value()) + use serde::ser::Serialize as _; + namespace + .as_ref() + .map(|ns| telemetry::display::base64(ns.as_bytes())) + .serialize(serializer) } -fn serialize_namespace(namespace: &Namespace, serializer: S) -> Result +fn serialize_sequencer_heights<'a, I: 'a, S>(heights: I, serializer: S) -> Result where + I: IntoIterator, S: serde::ser::Serializer, { - use serde::ser::Serialize as _; - telemetry::display::base64(namespace.as_bytes()).serialize(serializer) + serializer.collect_seq(heights.into_iter().map(tendermint::block::Height::value)) } -#[derive(Debug, serde::Serialize)] -pub(super) struct RollupInfo { - number_of_transactions: usize, - #[serde(serialize_with = "serialize_namespace")] - celestia_namespace: Namespace, - sequencer_rollup_id: RollupId, +fn serialize_included_rollups<'a, I: 'a, S>(rollups: I, serializer: S) -> Result +where + I: IntoIterator, + S: serde::ser::Serializer, +{ + serializer.collect_map( + rollups + .into_iter() + .map(|(id, ns)| (id, telemetry::display::base64(ns.as_bytes()))), + ) } -/// Information about a sequencer block that was converted to blobs. -#[derive(Debug, serde::Serialize)] -pub(super) struct ConversionInfo { - #[serde(serialize_with = "serialize_height")] - pub(super) sequencer_height: SequencerHeight, - #[serde(serialize_with = "serialize_namespace")] - pub(super) sequencer_namespace: Namespace, - pub(super) rollups_included: Vec, - pub(super) rollups_excluded: Vec, -} +#[cfg(test)] +mod tests { + use astria_core::{ + primitive::v1::RollupId, + protocol::test_utils::ConfigureSequencerBlock, + }; + use rand_chacha::{ + rand_core::{ + RngCore as _, + SeedableRng as _, + }, + ChaChaRng, + }; + use sequencer_client::SequencerBlock; -/// The result of a sequencer block that was converted to blobs. -pub(super) struct Converted { - pub(super) blobs: Vec, - pub(super) info: ConversionInfo, -} + use super::{ + Input, + NextSubmission, + }; + use crate::{ + relayer::write::conversion::{ + TryAddError, + MAX_PAYLOAD_SIZE_BYTES, + }, + IncludeRollup, + }; -/// Convert the given sequencer block into a collection of blobs and related metadata. -/// -/// Only blobs from the rollups specified in `rollup_filter` will be included. -// allow: we'd need static lifetime on a ref to avoid pass-by-value here. -#[allow(clippy::needless_pass_by_value)] -pub(super) fn convert( - block: SequencerBlock, - rollup_filter: IncludeRollup, -) -> eyre::Result { - let sequencer_height = block.height(); - let mut total_data_uncompressed_size = 0; - let mut total_data_compressed_size = 0; - - let (sequencer_blob, rollup_blobs) = block.into_celestia_blobs(); - // Allocate extra space: one blob for the sequencer blob "header", - // the rest for the rollup blobs. - let mut blobs = Vec::with_capacity(rollup_blobs.len() + 1); - let sequencer_namespace = astria_core::celestia::namespace_v0_from_sha256_of_bytes( - sequencer_blob.header().chain_id().as_str(), - ); - let sequencer_blob_raw = sequencer_blob.into_raw().encode_to_vec(); - total_data_uncompressed_size += sequencer_blob_raw.len(); - let compressed_sequencer_blob_raw = - compress_bytes(&sequencer_blob_raw).wrap_err("failed compressing sequencer blob")?; - total_data_compressed_size += compressed_sequencer_blob_raw.len(); - - let header_blob = Blob::new(sequencer_namespace, compressed_sequencer_blob_raw) - .wrap_err("failed creating head Celestia blob")?; - blobs.push(header_blob); - let mut rollups_included = Vec::new(); - let mut rollups_excluded = Vec::new(); - for blob in rollup_blobs { - let rollup_id = blob.rollup_id(); - let namespace = astria_core::celestia::namespace_v0_from_rollup_id(rollup_id); - let info = RollupInfo { - number_of_transactions: blob.transactions().len(), - celestia_namespace: namespace, - sequencer_rollup_id: rollup_id, + fn include_all_rollups() -> IncludeRollup { + IncludeRollup::parse("").unwrap() + } + + fn block(height: u32) -> SequencerBlock { + ConfigureSequencerBlock { + chain_id: Some("sequencer-0".to_string()), + height, + sequence_data: vec![( + RollupId::from_unhashed_bytes(b"rollup-0"), + b"hello world!".to_vec(), + )], + ..ConfigureSequencerBlock::default() + } + .make() + } + + fn block_with_random_data( + height: u32, + num_bytes: usize, + rng: &mut ChaChaRng, + ) -> SequencerBlock { + let mut random_bytes = vec![0; num_bytes]; + rng.fill_bytes(&mut random_bytes); + ConfigureSequencerBlock { + chain_id: Some("sequencer-0".to_string()), + height, + sequence_data: vec![(RollupId::from_unhashed_bytes(b"rollup-0"), random_bytes)], + ..ConfigureSequencerBlock::default() + } + .make() + } + + #[tokio::test] + async fn add_sequencer_block_to_empty_next_submission() { + let mut next_submission = NextSubmission::new(include_all_rollups()); + next_submission.try_add(block(1)).unwrap(); + let submission = next_submission.take().await.unwrap(); + assert_eq!(1, submission.num_blocks()); + assert_eq!(2, submission.num_blobs()); + } + + #[test] + fn adding_three_sequencer_blocks_with_same_ids_doesnt_change_number_of_blobs() { + let mut next_submission = NextSubmission::new(include_all_rollups()); + next_submission.try_add(block(1)).unwrap(); + next_submission.try_add(block(2)).unwrap(); + next_submission.try_add(block(3)).unwrap(); + let submission = tokio_test::block_on(next_submission.take()).unwrap(); + assert_eq!(3, submission.num_blocks()); + assert_eq!(2, submission.num_blobs()); + } + + #[test] + fn adding_block_to_full_submission_gets_rejected() { + // this test makes use of the fact that random data is essentially incompressible so + // that size(uncompressed_payload) ~= size(compressed_payload). + let mut rng = ChaChaRng::seed_from_u64(0); + let mut next_submission = NextSubmission::new(include_all_rollups()); + // adding 9 blocks with 100KB random data each, which gives a (compressed) payload slightly + // above 900KB. + let num_bytes = 100_000usize; + for height in 1..=9 { + next_submission + .try_add(block_with_random_data(height, num_bytes, &mut rng)) + .unwrap(); + } + let overflowing_block = block_with_random_data(10, num_bytes, &mut rng); + let rejected_block = match next_submission.try_add(overflowing_block.clone()) { + Err(TryAddError::Full(block)) => *block, + other => panic!("expected a `Err(TryAddError::Full)`, but got `{other:?}`"), }; - if rollup_filter.should_include(&rollup_id) { - let raw_blob = blob.into_raw().encode_to_vec(); - total_data_uncompressed_size += raw_blob.len(); - let compressed_blob = compress_bytes(&raw_blob) - .wrap_err_with(|| format!("failed compressing rollup `{rollup_id}`"))?; - total_data_compressed_size += compressed_blob.len(); - let blob = Blob::new(namespace, compressed_blob) - .wrap_err_with(|| format!("failed creating blob for rollup `{rollup_id}`"))?; - blobs.push(blob); - rollups_included.push(info); - } else { - rollups_excluded.push(info); + assert_eq!(overflowing_block, rejected_block); + } + + #[test] + fn oversized_block_is_rejected() { + // this test makes use of the fact that random data is essentially incompressible so + // that size(uncompressed_payload) ~= size(compressed_payload). + let mut rng = ChaChaRng::seed_from_u64(0); + let mut next_submission = NextSubmission::new(include_all_rollups()); + + // using the upper limit defined in the constant and add 1KB of extra bytes to ensure + // the block is too large + let oversized_block = block_with_random_data(10, MAX_PAYLOAD_SIZE_BYTES + 1_000, &mut rng); + match next_submission.try_add(oversized_block) { + Err(TryAddError::OversizedBlock { + sequencer_height, .. + }) => assert_eq!(sequencer_height.value(), 10), + other => panic!("expected a `Err(TryAddError::OversizedBlock)`, but got `{other:?}`"), } } - // gauges require f64, it's okay if the metrics get messed up by overflow or precision loss - #[allow(clippy::cast_precision_loss)] - let compression_ratio = total_data_uncompressed_size as f64 / total_data_compressed_size as f64; - debug!( - sequencer_height = %sequencer_height, - total_data_compressed_size = total_data_compressed_size, - compression_ratio = compression_ratio, - "converted blocks into blobs with compressed data", - ); - #[allow(clippy::cast_precision_loss)] - metrics::gauge!(metrics_init::TOTAL_BLOB_DATA_SIZE_FOR_ASTRIA_BLOCK) - .set(total_data_compressed_size as f64); - metrics::gauge!(metrics_init::COMPRESSION_RATIO_FOR_ASTRIA_BLOCK).set(compression_ratio); - - Ok(Converted { - blobs, - info: ConversionInfo { - sequencer_height, - sequencer_namespace, - rollups_included, - rollups_excluded, - }, - }) + #[test] + fn extend_empty_input_from_sequencer_block() { + let mut input = Input::new(); + input.extend_from_sequencer_block(block(1), &include_all_rollups()); + assert_eq!(1, input.num_blocks()); + } + + #[test] + fn convert_input_to_payload() { + let mut input = Input::new(); + input.extend_from_sequencer_block(block(1), &include_all_rollups()); + let payload = input.try_into_payload().unwrap(); + assert_eq!(2, payload.num_blobs()); + } } diff --git a/crates/astria-sequencer-relayer/src/relayer/write/mod.rs b/crates/astria-sequencer-relayer/src/relayer/write/mod.rs index e97639c969..bf3a3b304d 100644 --- a/crates/astria-sequencer-relayer/src/relayer/write/mod.rs +++ b/crates/astria-sequencer-relayer/src/relayer/write/mod.rs @@ -9,10 +9,7 @@ //! another task sends sequencer blocks ordered by their heights, then //! they will be written in that order. use std::{ - future::Future, - mem, sync::Arc, - task::Poll, time::Duration, }; @@ -23,18 +20,12 @@ use astria_eyre::eyre::{ use celestia_types::Blob; use futures::{ future::{ - BoxFuture, Fuse, FusedFuture as _, }, - stream::FuturesOrdered, FutureExt as _, }; -use pin_project_lite::pin_project; -use sequencer_client::{ - tendermint::block::Height as SequencerHeight, - SequencerBlock, -}; +use sequencer_client::SequencerBlock; use tokio::{ select, sync::{ @@ -66,96 +57,13 @@ use super::{ SubmissionState, TrySubmitError, }; -use crate::IncludeRollup; - -mod conversion; - -use conversion::{ - convert, - ConversionInfo, - Converted, +use crate::{ + metrics_init, + IncludeRollup, }; -struct QueuedConvertedBlocks { - // The maximum number of blobs permitted to sit in the blob queue. - max_blobs: usize, - blobs: Vec, - infos: Vec, - greatest_sequencer_height: Option, -} - -impl QueuedConvertedBlocks { - fn is_empty(&self) -> bool { - self.blobs.is_empty() - } - - fn num_blobs(&self) -> usize { - self.blobs.len() - } - - fn num_converted(&self) -> usize { - self.infos.len() - } - - fn with_max_blobs(max_blobs: usize) -> Self { - Self { - max_blobs, - blobs: Vec::new(), - infos: Vec::new(), - greatest_sequencer_height: None, - } - } - - fn has_capacity(&self) -> bool { - self.blobs.len() < self.max_blobs - } - - fn push(&mut self, mut converted: Converted) { - self.blobs.append(&mut converted.blobs); - let info = converted.info; - let greatest_height = self - .greatest_sequencer_height - .get_or_insert(info.sequencer_height); - *greatest_height = std::cmp::max(*greatest_height, info.sequencer_height); - self.infos.push(info); - } - - /// Lazily move the currently queued blobs out of the queue. - /// - /// The main reason for this method to exist is to work around async-cancellation. - /// Only when the returned [`TakeQueued`] future is polled are the blobs moved - /// out of the queue. - fn take(&mut self) -> TakeQueued<'_> { - TakeQueued { - inner: Some(self), - } - } -} - -pin_project! { - struct TakeQueued<'a> { - inner: Option<&'a mut QueuedConvertedBlocks>, - } -} - -impl<'a> Future for TakeQueued<'a> { - type Output = Option; - - fn poll(self: std::pin::Pin<&mut Self>, _: &mut std::task::Context<'_>) -> Poll { - let queued = self - .project() - .inner - .take() - .expect("this future must not be polled twice"); - let empty = QueuedConvertedBlocks::with_max_blobs(queued.max_blobs); - let queued = mem::replace(queued, empty); - if queued.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(queued)) - } - } -} +mod conversion; +use conversion::NextSubmission; #[derive(Clone)] pub(super) struct BlobSubmitterHandle { @@ -192,18 +100,11 @@ pub(super) struct BlobSubmitter { /// The builder for a client to submit blobs to Celestia. client_builder: CelestiaClientBuilder, - /// The rollups whose data should be included in submissions. - rollup_filter: IncludeRollup, - /// The channel over which sequencer blocks are received. blocks: mpsc::Receiver, - /// The collection of tasks converting from sequencer blocks to celestia blobs, - /// with the sequencer blocks' heights used as keys. - conversions: Conversions, - - /// Celestia blobs waiting to be submitted after conversion from sequencer blocks. - blobs: QueuedConvertedBlocks, + /// The accumulator of all data that will be submitted to Celestia on the next submission. + next_submission: NextSubmission, /// The state of the relayer. state: Arc, @@ -215,6 +116,10 @@ pub(super) struct BlobSubmitter { /// The shutdown token to signal that blob submitter should finish its current submission and /// exit. shutdown_token: CancellationToken, + + /// A block that could not be added to `next_submission` because it would overflow its + /// hardcoded limit. + pending_block: Option, } impl BlobSubmitter { @@ -230,13 +135,12 @@ impl BlobSubmitter { let (tx, rx) = mpsc::channel(128); let submitter = Self { client_builder, - rollup_filter, blocks: rx, - conversions: Conversions::new(8), - blobs: QueuedConvertedBlocks::with_max_blobs(128), + next_submission: NextSubmission::new(rollup_filter), state, submission_state, shutdown_token, + pending_block: None, }; let handle = BlobSubmitterHandle { tx, @@ -255,7 +159,8 @@ impl BlobSubmitter { error.wrap_err(message) })?; - let mut submission = Fuse::terminated(); + // A submission to Celestia that is currently in-flight. + let mut ongoing_submission = Fuse::terminated(); let reason = loop { select!( @@ -267,7 +172,10 @@ impl BlobSubmitter { } // handle result of submitting blocks to Celestia, if in flight - submission_result = &mut submission, if !submission.is_terminated() => { + submission_result = &mut ongoing_submission, + if !ongoing_submission.is_terminated() + => + { // XXX: Breaks the select-loop and returns. With the current retry-logic in // `submit_blobs` this happens after u32::MAX retries which is effectively never. // self.submission_state = match submission_result.wrap_err("failed submitting blocks to Celestia") @@ -282,41 +190,31 @@ impl BlobSubmitter { } // submit blocks to Celestia, if no submission in flight - Some(blobs) = self.blobs.take(), if submission.is_terminated() => { - submission = submit_blobs( + Some(submission) = self.next_submission.take(), + if ongoing_submission.is_terminated() + => { + ongoing_submission = submit_blobs( client.clone(), - blobs, + submission, self.state.clone(), self.submission_state.clone(), ).boxed().fuse(); + if let Some(block) = self.pending_block.take() { + if let Err(error) = self.add_sequencer_block_to_next_submission(block) { + break Err(error).wrap_err( + "critically failed adding Sequencer block to next submission" + ); + } + } } - // handle result of converting blocks to blobs - Some((sequencer_height, conversion_result)) = self.conversions.next() => { - match conversion_result { - // XXX: Emitting at ERROR level because failing to convert constitutes - // a fundamental problem for the relayer, even though it can happily - // continue chugging along. - // XXX: Should there instead be a mechanism to bubble up the error and - // have sequencer-relayer return with an error code (so that k8s can halt - // the chain)? This should probably be part of the protocol/sequencer - // proper. - Err(error) => error!( - %sequencer_height, - %error, - "failed converting sequencer blocks to celestia blobs", - ), - Ok(converted) => self.blobs.push(converted), - }; - } - - // enqueue new blocks for conversion to blobs if there is capacity + // add new blocks to the next submission if there is space. Some(block) = self.blocks.recv(), if self.has_capacity() => { - debug!( - height = %block.height(), - "received sequencer block for submission", - ); - self.conversions.push(block, self.rollup_filter.clone()); + if let Err(error) = self.add_sequencer_block_to_next_submission(block) { + break Err(error).wrap_err( + "critically failed adding Sequencer block to next submission" + ); + } } ); @@ -327,60 +225,87 @@ impl BlobSubmitter { Err(reason) => error!(%reason, "starting shutdown"), } - if submission.is_terminated() { + if ongoing_submission.is_terminated() { info!("no submissions to Celestia were in flight, exiting now"); } else { info!("a submission to Celestia is in flight; waiting for it to finish"); - if let Err(error) = submission.await { + if let Err(error) = ongoing_submission.await { error!(%error, "last submission to Celestia failed before exiting"); } } reason.map(|_| ()) } - /// Returns if the submitter has capacity for more blocks. + #[instrument(skip_all, fields(sequencer_height = block.height().value()), err)] + fn add_sequencer_block_to_next_submission( + &mut self, + block: SequencerBlock, + ) -> eyre::Result<()> { + match self.next_submission.try_add(block) { + Ok(()) => debug!("block was scheduled for next submission"), + Err(conversion::TryAddError::Full(block)) => { + debug!( + "block was rejected from next submission because it would overflow the \ + maximum payload size; pushing back until the next submission is done" + ); + self.pending_block = Some(*block); + } + Err(err) => { + return Err(err).wrap_err("failed adding sequencer block to next submission"); + } + } + Ok(()) + } + + /// Returns if the next submission still has capacity. fn has_capacity(&self) -> bool { - self.conversions.has_capacity() && self.blobs.has_capacity() + // The next submission has capacity if no block was rejected. + self.pending_block.is_none() } } /// Submits new blobs Celestia. -/// -/// # Panics -/// Panics if `blocks` is empty. This function should only be called if there is something to -/// submit. #[instrument(skip_all)] async fn submit_blobs( client: CelestiaClient, - blocks: QueuedConvertedBlocks, + data: conversion::Submission, state: Arc, submission_state: SubmissionState, ) -> eyre::Result { info!( - blocks = %telemetry::display::json(&blocks.infos), + blocks = %telemetry::display::json(&data.input_metadata()), + total_data_uncompressed_size = data.uncompressed_size(), + total_data_compressed_size = data.compressed_size(), + compression_ratio = data.compression_ratio(), "initiated submission of sequencer blocks converted to Celestia blobs", ); let start = std::time::Instant::now(); + // allow: gauges require f64, it's okay if the metrics get messed up by overflow or precision + // loss + #[allow(clippy::cast_precision_loss)] + let compressed_size = data.compressed_size() as f64; + metrics::gauge!(metrics_init::TOTAL_BLOB_DATA_SIZE_FOR_ASTRIA_BLOCK).set(compressed_size); + + metrics::gauge!(metrics_init::COMPRESSION_RATIO_FOR_ASTRIA_BLOCK).set(data.compression_ratio()); + metrics::counter!(crate::metrics_init::CELESTIA_SUBMISSION_COUNT).increment(1); // XXX: The number of sequencer blocks per celestia tx is equal to the number of heights passed // into this function. This comes from the way that `QueuedBlocks::take` is implemented. // // allow: the number of blocks should always be low enough to not cause precision loss #[allow(clippy::cast_precision_loss)] - let blocks_per_celestia_tx = blocks.num_converted() as f64; + let blocks_per_celestia_tx = data.num_blocks() as f64; metrics::gauge!(crate::metrics_init::BLOCKS_PER_CELESTIA_TX).set(blocks_per_celestia_tx); // allow: the number of blobs should always be low enough to not cause precision loss #[allow(clippy::cast_precision_loss)] - let blobs_per_celestia_tx = blocks.num_blobs() as f64; + let blobs_per_celestia_tx = data.num_blobs() as f64; metrics::gauge!(crate::metrics_init::BLOBS_PER_CELESTIA_TX).set(blobs_per_celestia_tx); - let largest_sequencer_height = blocks.greatest_sequencer_height.expect( - "there should always be blobs and accompanying sequencer heights when this function is \ - called", - ); + let largest_sequencer_height = data.greatest_sequencer_height(); + let blobs = data.into_blobs(); let submission_started = match crate::utils::flatten( tokio::task::spawn_blocking(move || submission_state.initialize(largest_sequencer_height)) @@ -394,7 +319,7 @@ async fn submit_blobs( Ok(state) => state, }; - let celestia_height = match submit_with_retry(client, blocks.blobs, state.clone()).await { + let celestia_height = match submit_with_retry(client, blobs, state.clone()).await { Err(error) => { let message = "failed submitting blobs to Celestia"; error!(%error, message); @@ -512,51 +437,3 @@ async fn submit_with_retry( .wrap_err("retry attempts exhausted; bailing")?; Ok(height) } - -/// Currently running conversions of Sequencer blocks to Celestia blobs. -/// -/// The conversion result will be returned in the order they are pushed -/// into this queue. -/// -/// Note on the implementation: the conversions are done in a blocking tokio -/// task so that conversions are started immediately without needing extra -/// polling. This means that the only contribution that `FuturesOrdered` -/// provides is ordering the conversion result by the order the blocks are -/// received. This however is desirable because we want to submit sequencer -/// blocks in the order of their heights to Celestia. -struct Conversions { - // The currently active conversions. - active: FuturesOrdered)>>, - - // The maximum number of conversions that can be active at the same time. - max_conversions: usize, -} - -impl Conversions { - fn new(max_conversions: usize) -> Self { - Self { - active: FuturesOrdered::new(), - max_conversions, - } - } - - fn has_capacity(&self) -> bool { - self.active.len() < self.max_conversions - } - - fn push(&mut self, block: SequencerBlock, rollup_filter: IncludeRollup) { - let height = block.height(); - let conversion = tokio::task::spawn_blocking(move || convert(block, rollup_filter)); - let fut = async move { - let res = crate::utils::flatten(conversion.await); - (height, res) - } - .boxed(); - self.active.push_back(fut); - } - - async fn next(&mut self) -> Option<(SequencerHeight, eyre::Result)> { - use tokio_stream::StreamExt as _; - self.active.next().await - } -} diff --git a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto b/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto index 54ed8b57df..e60fca12d8 100644 --- a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto +++ b/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto @@ -5,12 +5,32 @@ package astria.sequencerblock.v1alpha1; import "astria/primitive/v1/types.proto"; import "astria/sequencerblock/v1alpha1/block.proto"; +// A sequence of `astria.sequencerblock.v1alpha1.SubmittedRollupData` submitted to Celestia. +message SubmittedRollupDataList { + repeated SubmittedRollupData entries = 1; +} + // A collection of transactions belonging to a specific rollup that are submitted to celestia. // +// It is created by splitting an `astria.sequencerblock.v1alpha1.SequencerBlock` into a +// `astria.sequencerblock.v1alpha1.SequencerBlockMetadata`, and a sequence of +// `astria.sequencerblock.v1alpha.RollupData` (this object). +// // The transactions contained in the item belong to a rollup identified // by `rollup_id`, and were included in the sequencer block identified // by `sequencer_block_hash`. -message CelestiaRollupBlob { + +// A collection of transactions belonging to a specific Rollup that is submitted to a Data +// Availability provider like Celestia. +// +// It is created by splitting an `astria.sequencerblock.v1alpha1.SequencerBlock` into a +// `astria.sequencerblock.v1alpha1.SubmittedMetadata`, and a sequence of +// `astria.sequencerblock.v1alpha.SubmittedRollupData` (this object; one object per rollup that had +// data included in the sequencer block). +// +// The original sequencer block (and in turn CometBFT block) can be identified by the +// `sequencer_block_hash` field. +message SubmittedRollupData { // The hash of the sequencer block. Must be 32 bytes. bytes sequencer_block_hash = 1; // The 32 bytes identifying the rollup this blob belongs to. Matches @@ -23,27 +43,35 @@ message CelestiaRollupBlob { astria.primitive.v1.Proof proof = 4; } -// The metadata of a sequencer block that is submitted to celestia. +// A sequence of `astria.sequencerblock.v1alpha1.SubmittedMetadata` submitted to Celestia. +message SubmittedMetadataList { + repeated SubmittedMetadata entries = 1; +} + +// The metadata of a sequencer block that is submitted to a Data Availability provider like +// Celestia // -// It is created by splitting a `astria.SequencerBlock` into a -// `CelestiaSequencerBlob` (which can be thought of as a header), and a sequence ofj -// `CelestiaRollupBlob`s. +// It is created by splitting an `astria.sequencerblock.v1alpha1.SequencerBlock` into a +// `astria.sequencerblock.v1alpha1.SubmittedMetadata` (this object), and a sequence of +// `astria.sequencerblock.v1alpha.SubmittedRollupData` (one object per rollup that had data +// included in the sequencer block). // // The original sequencer block (and in turn CometBFT block) can be identified by the -// block hash calculated from `header`. -message CelestiaSequencerBlob { +// `block_hash` field. +message SubmittedMetadata { // the 32-byte block hash of the sequencer block. bytes block_hash = 1; // the block header, which contains sequencer-specific commitments. astria.sequencerblock.v1alpha1.SequencerBlockHeader header = 2; - // The rollup IDs for which `CelestiaRollupBlob`s were submitted to celestia. - // Corresponds to the `astria.sequencer.v1.RollupTransactions.rollup_id` field - // and is extracted from `astria.SequencerBlock.rollup_transactions`. + // The rollup IDs that had transactions included in the `astria.sequencerblock.v1alpha1.SequencerBlock` + // that this object is derived from. + // Corresponds to `astria.sequencerblock.v1alpha1.RollupTransactions.rollup_id` + // extracted from `astria.sequencerblock.v1alpha1.SsequencerBlock.rollup_transactions`. repeated astria.primitive.v1.RollupId rollup_ids = 3; // The proof that the rollup transactions are included in sequencer block. - // Corresponds to `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + // Corresponds to `astria.sequencerblock.v1alpha1.SequencerBlock.rollup_transactions_proof`. astria.primitive.v1.Proof rollup_transactions_proof = 4; // The proof that the rollup IDs are included in sequencer block. - // Corresponds to `astria.sequencer.v1alpha.SequencerBlock.rollup_ids_proof`. + // Corresponds to `astria.sequencerblock.v1alpha1.SequencerBlock.rollup_ids_proof`. astria.primitive.v1.Proof rollup_ids_proof = 5; }