From 00f6b13d4b1e657d7da092f8f873d73b05db99b2 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Wed, 8 May 2024 17:37:11 +0200 Subject: [PATCH] feat(conductor, relayer)!: batch multiple Sequencer blocks to save on Celestia fees (#1045) ## Summary Batch mutiple Sequencer blocks into single Celestia blobs to reduce fee payments. ## Background Until now, each Sequencer block was turned into multiple blobs (one for overall sequencer block metadata, and one blob per rollup that had transactiosn in the sequencer block). This wasn't as efficient as it could be because the new compression scheme introduced in https://github.com/astriaorg/astria/pull/1006 can only come to bear with more bytes to compress. Relayer will collect sequencer blocks up to a total (compressed) size of 1MB (1/2 of the current max of 2MB that Celestia blocks can be). ## Changes - Introduce protobuf messages `astria.sequencerblock.v1alpha1.CelestiaHeaderList` and `astria.sequencerblock.v1alpha1.CelestiaRollupDataList` - Rename `astria.sequencerblock.v1alpha1.CelestiaRollupBlob` to `astria.sequencerblock.v1alpha1.CelestiaRollupData` - Rename `astria.sequencerblock.v1alpha1.CelestiaSequencerBlob` to `astria.sequencerblock.v1alpha1.CelestiaHeader` - Collect Sequencer Blocks into the `*List` protobuf messages before posting them to Celestia (instead of splitting up each Sequencer block into mutiple blobs and posting them one by one). ## Testing Add unit tests around the next submission aggregation logic. Update conductor blackbox tests. ## Metrics + `CELESTIA_PAYLOAD_CREATION_LATENCY`: histogram with microsecond units to track the time it takes to create a payload of Celestia blobs (encoding + compressing all protobufs) + metrics for reporting compression ratio and total compressed payload size were moved from the payload/blob construction phase to the submission phase. ## Breaking Changelist - Relayer and Conductor write/read new protobuf messages to/from Celestia. ## Related Issues Closes https://github.com/astriaorg/astria/issues/1042 Closes https://github.com/astriaorg/astria/issues/1049 --- Cargo.lock | 3 + Cargo.toml | 1 - crates/astria-conductor/src/block_cache.rs | 4 +- .../src/celestia/block_verifier.rs | 6 +- .../astria-conductor/src/celestia/convert.rs | 118 +- crates/astria-conductor/src/celestia/mod.rs | 8 +- .../src/celestia/reconstruct.rs | 14 +- .../astria-conductor/src/celestia/verify.rs | 39 +- .../tests/blackbox/helpers/macros.rs | 10 +- .../tests/blackbox/helpers/mod.rs | 50 +- .../astria.sequencerblock.v1alpha1.rs | 76 +- .../astria.sequencerblock.v1alpha1.serde.rs | 1224 ++++++++++------- .../src/sequencerblock/v1alpha1/block.rs | 13 +- .../src/sequencerblock/v1alpha1/celestia.rs | 177 ++- .../src/sequencerblock/v1alpha1/mod.rs | 4 +- crates/astria-sequencer-relayer/Cargo.toml | 3 + crates/astria-sequencer-relayer/src/config.rs | 2 +- .../src/metrics_init.rs | 12 + .../src/relayer/write/conversion.rs | 658 +++++++-- .../src/relayer/write/mod.rs | 287 ++-- .../sequencerblock/v1alpha1/celestia.proto | 52 +- 21 files changed, 1686 insertions(+), 1075 deletions(-) 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; }