From 6665948892b39a583e0cc623d1355954328bebff Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:35:46 -0400 Subject: [PATCH] refactor(proto)!: update `SequencerBlockHeader` and related proto types to not use cometbft header (#830) ## Summary update `SequencerBlockHeader`, `SequencerBlock`, `FilteredSequencerBlock`, and `CelestiaSequencerBlob` to not contain an entire cometbft header, but only to contain relevant fields required. ## Background this is required for the update to ABCI v0.38, as `finalize_block` no longer passes in the entire block header (like `begin_block` did). we keep needed fields like height, chain_id, timestamp, proposer_address, data_hash in the `SequencerBlockHeader`. ## Changes - update `SequencerBlockHeader` to not contain an entire cometbft header but have height, chain_id, timestamp, proposer_address, data_hash - all fields are now explicitly needed by other components of the stack - update `SequencerBlock`, `FilteredSequencerBlock`, and `CelestiaSequencerBlob` to have `block_hash` as this can no longer be calculated from the cometbft header as it doesn't exist anymore - deprecate `rollup_ids_root` in `SequencerBlockHeader` as it can be calculated by the list of rollup IDs in a block ## Testing unit tests ## Breaking Changelist - the `SequencerBlockHeader`, `SequencerBlock`, `FilteredSequencerBlock`, and `CelestiaSequencerBlob` are changed. ## Related Issues related to #679 --- charts/sequencer/Chart.yaml | 2 +- charts/sequencer/templates/ingress.yaml | 2 - crates/astria-celestia-client/src/client.rs | 12 +- crates/astria-celestia-client/src/lib.rs | 6 +- .../astria-celestia-client/src/submission.rs | 8 +- crates/astria-cli/src/commands/rollup.rs | 2 +- crates/astria-cli/src/commands/sequencer.rs | 2 +- crates/astria-conductor/src/block_cache.rs | 2 +- .../src/celestia/block_verifier.rs | 60 +- crates/astria-conductor/src/celestia/mod.rs | 14 +- .../src/celestia/reporting.rs | 2 +- .../astria-conductor/src/executor/client.rs | 2 +- crates/astria-conductor/src/executor/mod.rs | 16 +- crates/astria-conductor/src/executor/state.rs | 2 +- crates/astria-conductor/src/executor/tests.rs | 49 +- crates/astria-conductor/src/lib.rs | 2 +- .../src/sequencer/block_stream.rs | 6 +- .../astria-conductor/src/sequencer/client.rs | 8 +- .../src/sequencer/reporting.rs | 16 +- crates/astria-conductor/src/utils.rs | 19 - .../tests/blackbox/helpers/macros.rs | 4 +- .../tests/blackbox/helpers/mock_grpc.rs | 2 +- .../tests/blackbox/helpers/mod.rs | 2 +- .../astria-core/src/execution/v1alpha2/mod.rs | 2 +- .../src/generated/astria.sequencer.v1.rs | 22 +- .../astria.sequencerblock.v1alpha1.rs | 16 +- .../astria.sequencerblock.v1alpha1.serde.rs | 397 +++++ crates/astria-core/src/generated/mod.rs | 13 + crates/astria-core/src/lib.rs | 39 +- crates/astria-core/src/sequencer/mod.rs | 38 + .../astria-core/src/sequencer/v1/block/mod.rs | 4 +- .../astria-core/src/sequencer/v1/celestia.rs | 32 +- crates/astria-core/src/sequencer/v1/mod.rs | 121 +- .../src/sequencer/v1/transaction/action.rs | 4 +- crates/astria-core/src/sequencerblock/mod.rs | 39 + .../src/sequencerblock/v1alpha1/block.rs | 1528 +++++++++++++++++ .../src/sequencerblock/v1alpha1/celestia.rs | 614 +++++++ .../src/sequencerblock/v1alpha1/mod.rs | 124 ++ .../src/sequencerblock/v1alpha1/tests.rs | 62 + .../src/extension_trait.rs | 26 +- crates/astria-sequencer-client/src/lib.rs | 14 +- .../src/relayer/builder.rs | 2 +- .../src/relayer/mod.rs | 8 +- .../src/relayer/read.rs | 4 +- .../src/relayer/write/conversion.rs | 5 +- .../tests/blackbox/helper.rs | 16 +- .../src/accounts/state_ext.rs | 9 +- crates/astria-sequencer/src/api_state_ext.rs | 23 +- crates/astria-sequencer/src/app.rs | 17 +- .../src/bridge/bridge_lock_action.rs | 14 +- .../astria-sequencer/src/bridge/state_ext.rs | 16 +- crates/astria-sequencer/src/grpc/sequencer.rs | 24 +- .../src/ibc/ics20_transfer.rs | 10 +- .../src/proposal/commitment.rs | 12 +- crates/astria-sequencer/src/sequencer.rs | 2 +- justfile | 18 +- .../astria/sequencer/v1/block.proto | 8 +- .../astria/sequencer/v1/celestia.proto | 14 +- .../sequencerblock/v1alpha1/block.proto | 6 +- .../sequencerblock/v1alpha1/celestia.proto | 10 +- tools/protobuf-compiler/src/main.rs | 4 +- 61 files changed, 3170 insertions(+), 387 deletions(-) create mode 100644 crates/astria-core/src/sequencerblock/mod.rs create mode 100644 crates/astria-core/src/sequencerblock/v1alpha1/block.rs create mode 100644 crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs create mode 100644 crates/astria-core/src/sequencerblock/v1alpha1/mod.rs create mode 100644 crates/astria-core/src/sequencerblock/v1alpha1/tests.rs diff --git a/charts/sequencer/Chart.yaml b/charts/sequencer/Chart.yaml index cf08da5226..25509b0317 100644 --- a/charts/sequencer/Chart.yaml +++ b/charts/sequencer/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.11.4 +version: 0.11.5 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/sequencer/templates/ingress.yaml b/charts/sequencer/templates/ingress.yaml index 7c9f063d9b..371a90d7f5 100644 --- a/charts/sequencer/templates/ingress.yaml +++ b/charts/sequencer/templates/ingress.yaml @@ -26,7 +26,6 @@ metadata: {{- end }} {{- end }} {{- else }} - kubernetes.io/ingress.class: nginx {{- end }} spec: {{- if and $ingressSupportsIngressClassName $ingress.ingressClassName }} @@ -103,7 +102,6 @@ metadata: {{- end }} {{- end }} {{- else }} - kubernetes.io/ingress.class: nginx {{- end }} spec: {{- if and $ingressSupportsIngressClassName $ingress.ingressClassName }} diff --git a/crates/astria-celestia-client/src/client.rs b/crates/astria-celestia-client/src/client.rs index ae072f4427..cf738199cc 100644 --- a/crates/astria-celestia-client/src/client.rs +++ b/crates/astria-celestia-client/src/client.rs @@ -1,4 +1,4 @@ -use astria_core::sequencer::v1::{ +use astria_core::sequencerblock::v1alpha1::{ celestia::CelestiaSequencerBlobError, CelestiaRollupBlob, CelestiaSequencerBlob, @@ -103,7 +103,7 @@ pub trait CelestiaClientExt: BlobClient { } 'blob: { let raw_blob = - match astria_core::generated::sequencer::v1::CelestiaSequencerBlob::decode( + match astria_core::generated::sequencerblock::v1alpha1::CelestiaSequencerBlob::decode( &*blob.data, ) { Ok(blob) => blob, @@ -254,11 +254,13 @@ fn convert_and_filter_rollup_blobs( continue; } let proto_blob = - match astria_core::generated::sequencer::v1::CelestiaRollupBlob::decode(&*blob.data) { + match astria_core::generated::sequencerblock::v1alpha1::CelestiaRollupBlob::decode( + &*blob.data, + ) { Err(e) => { debug!( error = &e as &dyn std::error::Error, - target = "astria.sequencer.v1alpha.CelestiaRollupBlob", + target = "astria.sequencerblock.v1alpha1.CelestiaRollupBlob", blob.commitment = %Base64Display::new(&blob.commitment.0, &STANDARD), "failed decoding blob as protobuf; skipping" ); @@ -305,7 +307,7 @@ fn does_rollup_blob_verify_against_sequencer_blob( rollup_blob .proof() .audit() - .with_root(sequencer_blob.rollup_transactions_root()) + .with_root(sequencer_blob.header().rollup_transactions_root()) .with_leaf_builder() .write(&rollup_blob.rollup_id().get()) .write(&merkle::Tree::from_leaves(rollup_blob.transactions()).root()) diff --git a/crates/astria-celestia-client/src/lib.rs b/crates/astria-celestia-client/src/lib.rs index 4ba212aff2..68d7ab97cb 100644 --- a/crates/astria-celestia-client/src/lib.rs +++ b/crates/astria-celestia-client/src/lib.rs @@ -2,7 +2,7 @@ pub mod client; pub mod metrics_init; pub mod submission; -pub use astria_core::sequencer::v1::{ +pub use astria_core::sequencerblock::v1alpha1::{ CelestiaRollupBlob, CelestiaSequencerBlob, }; @@ -55,10 +55,10 @@ pub const fn celestia_namespace_v0_from_rollup_id( } #[must_use = "a celestia namespace must be used in order to be useful"] -pub fn celestia_namespace_v0_from_cometbft_header(header: &tendermint::block::Header) -> Namespace { +pub fn celestia_namespace_v0_from_str(chain_id: &str) -> Namespace { use sha2::{ Digest as _, Sha256, }; - celestia_namespace_v0_from_array(Sha256::digest(header.chain_id.as_bytes()).into()) + celestia_namespace_v0_from_array(Sha256::digest(chain_id.as_bytes()).into()) } diff --git a/crates/astria-celestia-client/src/submission.rs b/crates/astria-celestia-client/src/submission.rs index 1012d7859e..604cce3074 100644 --- a/crates/astria-celestia-client/src/submission.rs +++ b/crates/astria-celestia-client/src/submission.rs @@ -1,8 +1,8 @@ //! Logic to convert sequencer blocks to celestia blobs before submission. -use astria_core::sequencer::v1::{ - RollupId, - SequencerBlock, +use astria_core::{ + sequencer::v1::RollupId, + sequencerblock::v1alpha1::SequencerBlock, }; use celestia_types::Blob; use prost::Message as _; @@ -67,7 +67,7 @@ fn convert(block: SequencerBlock, blobs: &mut Vec) -> Result<(), ToBlobsEr // the rest for the rollup blobs. blobs.reserve(rollup_blobs.len() + 1); let sequencer_namespace = - crate::celestia_namespace_v0_from_cometbft_header(sequencer_blob.header()); + crate::celestia_namespace_v0_from_str(sequencer_blob.header().chain_id().as_str()); let header_blob = Blob::new( sequencer_namespace, diff --git a/crates/astria-cli/src/commands/rollup.rs b/crates/astria-cli/src/commands/rollup.rs index e11f838af9..4ab2e92c59 100644 --- a/crates/astria-cli/src/commands/rollup.rs +++ b/crates/astria-cli/src/commands/rollup.rs @@ -116,7 +116,7 @@ pub(crate) async fn create_config(args: &ConfigCreateArgs) -> eyre::Result<()> { .await .wrap_err("failed to get sequencer block for initial sequencer height")?; - let new_height: u64 = res.header().cometbft_header().height.into(); + let new_height: u64 = res.height().into(); conf.sequencer_initial_block_height = Some(new_height); } diff --git a/crates/astria-cli/src/commands/sequencer.rs b/crates/astria-cli/src/commands/sequencer.rs index 1d929ff940..822e41578b 100644 --- a/crates/astria-cli/src/commands/sequencer.rs +++ b/crates/astria-cli/src/commands/sequencer.rs @@ -159,7 +159,7 @@ pub(crate) async fn get_block_height(args: &BlockHeightGetArgs) -> eyre::Result< .wrap_err("failed to get sequencer block")?; println!("Block Height:"); - println!(" {}", res.header().cometbft_header().height); + println!(" {}", res.height()); Ok(()) } diff --git a/crates/astria-conductor/src/block_cache.rs b/crates/astria-conductor/src/block_cache.rs index 383b9abfbf..cf2f318e95 100644 --- a/crates/astria-conductor/src/block_cache.rs +++ b/crates/astria-conductor/src/block_cache.rs @@ -4,7 +4,7 @@ use std::{ future::Future, }; -use astria_core::sequencer::v1::{ +use astria_core::sequencerblock::v1alpha1::{ block::FilteredSequencerBlock, CelestiaSequencerBlob, }; diff --git a/crates/astria-conductor/src/celestia/block_verifier.rs b/crates/astria-conductor/src/celestia/block_verifier.rs index 2990ab2396..7ec2773c70 100644 --- a/crates/astria-conductor/src/celestia/block_verifier.rs +++ b/crates/astria-conductor/src/celestia/block_verifier.rs @@ -274,10 +274,13 @@ fn verify_vote_signature( mod test { use std::collections::BTreeMap; - use astria_core::sequencer::v1::{ - celestia::UncheckedCelestiaSequencerBlob, - test_utils::make_cometbft_block, - RollupId, + use astria_core::{ + generated::sequencerblock::v1alpha1::SequencerBlockHeader as RawSequencerBlockHeader, + sequencer::v1::RollupId, + sequencerblock::v1alpha1::{ + block::SequencerBlockHeader, + celestia::UncheckedCelestiaSequencerBlob, + }, }; use prost::Message as _; use sequencer_client::{ @@ -371,24 +374,33 @@ mod test { #[test] fn validate_sequencer_blob_last_commit_none_ok() { let rollup_transactions_root = merkle::Tree::from_leaves([[1, 2, 3], [4, 5, 6]]).root(); - let chain_ids_commitment = merkle::Tree::new().root(); + let rollup_ids_root = merkle::Tree::new().root(); - let tree = merkle_tree_from_transactions([rollup_transactions_root, chain_ids_commitment]); + let tree = merkle_tree_from_transactions([rollup_transactions_root, rollup_ids_root]); let data_hash = tree.root(); let rollup_transactions_proof = tree.construct_proof(0).unwrap(); let rollup_ids_proof = tree.construct_proof(1).unwrap(); - let mut header = make_cometbft_block().header; - let height = header.height.value().try_into().unwrap(); - header.data_hash = Some(Hash::try_from(data_hash.to_vec()).unwrap()); - let (validator_set, proposer_address, commit) = - make_test_validator_set_and_commit(height, header.chain_id.clone()); - header.proposer_address = proposer_address; + make_test_validator_set_and_commit(1, "test-chain".try_into().unwrap()); + + let header = RawSequencerBlockHeader { + chain_id: "test-chain".to_string(), + height: 1, + time: Some(pbjson_types::Timestamp { + seconds: 1, + nanos: 0, + }), + data_hash: data_hash.to_vec(), + rollup_transactions_root: rollup_transactions_root.to_vec(), + proposer_address: proposer_address.as_bytes().to_vec(), + }; + let header = SequencerBlockHeader::try_from_raw(header).unwrap(); + let sequencer_blob = UncheckedCelestiaSequencerBlob { + block_hash: [0u8; 32], header, rollup_ids: vec![], - rollup_transactions_root, rollup_transactions_proof, rollup_ids_proof, } @@ -414,18 +426,26 @@ mod test { let rollup_transactions_proof = tree.construct_proof(0).unwrap(); let rollup_ids_proof = tree.construct_proof(1).unwrap(); - let mut header = make_cometbft_block().header; - let height = header.height.value().try_into().unwrap(); - header.data_hash = Some(Hash::try_from(data_hash.to_vec()).unwrap()); - let (validator_set, proposer_address, commit) = - make_test_validator_set_and_commit(height, header.chain_id.clone()); - header.proposer_address = proposer_address; + make_test_validator_set_and_commit(1, "test-chain".try_into().unwrap()); + + let header = RawSequencerBlockHeader { + chain_id: "test-chain".to_string(), + height: 1, + time: Some(pbjson_types::Timestamp { + seconds: 1, + nanos: 0, + }), + data_hash: data_hash.to_vec(), + rollup_transactions_root: rollup_transactions_root.to_vec(), + proposer_address: proposer_address.as_bytes().to_vec(), + }; + let header = SequencerBlockHeader::try_from_raw(header).unwrap(); let sequencer_blob = UncheckedCelestiaSequencerBlob { + block_hash: [0u8; 32], header, rollup_ids: vec![rollup_id], - rollup_transactions_root, rollup_transactions_proof, rollup_ids_proof, } diff --git a/crates/astria-conductor/src/celestia/mod.rs b/crates/astria-conductor/src/celestia/mod.rs index aac97e6799..7ad65355a8 100644 --- a/crates/astria-conductor/src/celestia/mod.rs +++ b/crates/astria-conductor/src/celestia/mod.rs @@ -8,6 +8,7 @@ use std::{ time::Duration, }; +use astria_core::sequencerblock::v1alpha1::block::SequencerBlockHeader; use astria_eyre::eyre::{ self, bail, @@ -35,10 +36,7 @@ use futures::{ use futures_bounded::FuturesMap; use pin_project_lite::pin_project; use sequencer_client::{ - tendermint::{ - self, - block::Height as SequencerHeight, - }, + tendermint::block::Height as SequencerHeight, HttpClient as SequencerClient, }; use telemetry::display::{ @@ -97,14 +95,14 @@ struct ReconstructedBlocks { #[derive(Clone, Debug)] pub(crate) struct ReconstructedBlock { pub(crate) block_hash: [u8; 32], - pub(crate) header: tendermint::block::Header, + pub(crate) header: SequencerBlockHeader, pub(crate) transactions: Vec>, pub(crate) celestia_height: u64, } impl ReconstructedBlock { pub(crate) fn sequencer_height(&self) -> SequencerHeight { - self.header.height + self.header.height() } } @@ -628,8 +626,8 @@ async fn get_sequencer_namespace(client: SequencerClient) -> eyre::Result Self { let hash = block.block_hash(); let height = block.height(); - let timestamp = convert_tendermint_time_to_protobuf_timestamp(block.cometbft_header().time); + let timestamp = convert_tendermint_time_to_protobuf_timestamp(block.header().time()); let FilteredSequencerBlockParts { mut rollup_transactions, .. diff --git a/crates/astria-conductor/src/executor/state.rs b/crates/astria-conductor/src/executor/state.rs index 5d6548c460..8a577b35d2 100644 --- a/crates/astria-conductor/src/executor/state.rs +++ b/crates/astria-conductor/src/executor/state.rs @@ -206,7 +206,7 @@ fn map_rollup_height_to_sequencer_height( mod tests { use astria_core::{ generated::execution::v1alpha2 as raw, - Protobuf as _, + sequencerblock::Protobuf as _, }; use pbjson_types::Timestamp; diff --git a/crates/astria-conductor/src/executor/tests.rs b/crates/astria-conductor/src/executor/tests.rs index 344f8faf90..c221fa0261 100644 --- a/crates/astria-conductor/src/executor/tests.rs +++ b/crates/astria-conductor/src/executor/tests.rs @@ -36,9 +36,11 @@ use astria_core::{ make_cometbft_block, ConfigureCometBftBlock, }, - SequencerBlock, }, - Protobuf, + sequencerblock::{ + v1alpha1::SequencerBlock, + Protobuf as _, + }, }; use bytes::Bytes; use prost::Message; @@ -214,20 +216,17 @@ fn get_expected_execution_hash( Bytes::copy_from_slice(&hasher.finalize()) } -fn hash(s: &[u8]) -> [u8; 32] { - use sha2::{ - Digest as _, - Sha256, - }; - Sha256::digest(s).into() -} +fn make_reconstructed_block(height: u32) -> ReconstructedBlock { + let block = ConfigureCometBftBlock { + height, + ..Default::default() + } + .make(); + let block = SequencerBlock::try_from_cometbft(block).unwrap(); -fn make_reconstructed_block() -> ReconstructedBlock { - let mut block = make_cometbft_block(); - block.header.height = SequencerHeight::from(100u32); ReconstructedBlock { - block_hash: hash(b"block1"), - header: block.header, + block_hash: block.block_hash(), + header: block.header().clone(), transactions: vec![], celestia_height: 1, } @@ -278,13 +277,15 @@ fn make_rollup_data(data: &str) -> RawRollupData { async fn firm_blocks_at_expected_heights_are_executed() { let mut mock = start_mock().await; - let mut block = make_reconstructed_block(); + let height = 100; + + let mut block = make_reconstructed_block(height); let rollup_data = make_rollup_data("test_transaction"); block.transactions.push(rollup_data.encode_to_vec()); let expected_exection_hash = get_expected_execution_hash( mock.executor.state.borrow().firm().hash(), - vec![rollup_data], + vec![rollup_data.clone()], ); mock.executor @@ -296,9 +297,7 @@ async fn firm_blocks_at_expected_heights_are_executed() { mock.executor.state.borrow().firm().hash(), ); - let mut block = make_reconstructed_block(); - block.header.height = block.header.height.increment(); - let rollup_data = make_rollup_data("test_transaction"); + let mut block = make_reconstructed_block(height + 1); block.transactions.push(rollup_data.encode_to_vec()); let expected_exection_hash = get_expected_execution_hash( mock.executor.state.borrow().firm().hash(), @@ -364,7 +363,7 @@ async fn first_firm_then_soft_leads_to_soft_being_dropped() { let firm_block = ReconstructedBlock { block_hash: soft_block.block_hash(), - header: soft_block.cometbft_header().clone(), + header: soft_block.header().clone(), transactions: soft_block .rollup_transactions() .get(&ROLLUP_ID) @@ -419,7 +418,7 @@ async fn first_soft_then_firm_update_state_correctly() { let firm_block = ReconstructedBlock { block_hash: soft_block.block_hash(), - header: soft_block.cometbft_header().clone(), + header: soft_block.header().clone(), transactions: soft_block .rollup_transactions() .get(&ROLLUP_ID) @@ -527,9 +526,7 @@ async fn non_sequential_future_soft_blocks_give_error() { #[tokio::test] async fn out_of_order_firm_blocks_are_rejected() { let mut mock = start_mock().await; - let mut block = make_reconstructed_block(); - - block.header.height = SequencerHeight::from(99u32); + let block = make_reconstructed_block(99); assert!( mock.executor .execute_firm(mock.client.clone(), block.clone()) @@ -537,7 +534,7 @@ async fn out_of_order_firm_blocks_are_rejected() { .is_err() ); - block.header.height = SequencerHeight::from(101u32); + let block = make_reconstructed_block(101); assert!( mock.executor .execute_firm(mock.client.clone(), block.clone()) @@ -545,7 +542,7 @@ async fn out_of_order_firm_blocks_are_rejected() { .is_err() ); - block.header.height = SequencerHeight::from(100u32); + let block = make_reconstructed_block(100); assert!( mock.executor .execute_firm(mock.client.clone(), block.clone()) diff --git a/crates/astria-conductor/src/lib.rs b/crates/astria-conductor/src/lib.rs index 7dbba794b5..9466ba4107 100644 --- a/crates/astria-conductor/src/lib.rs +++ b/crates/astria-conductor/src/lib.rs @@ -15,7 +15,7 @@ pub mod conductor; pub mod config; pub(crate) mod executor; pub(crate) mod sequencer; -pub(crate) mod utils; +mod utils; pub use build_info::BUILD_INFO; pub use conductor::Conductor; diff --git a/crates/astria-conductor/src/sequencer/block_stream.rs b/crates/astria-conductor/src/sequencer/block_stream.rs index 1ab5e0bc3d..2475561063 100644 --- a/crates/astria-conductor/src/sequencer/block_stream.rs +++ b/crates/astria-conductor/src/sequencer/block_stream.rs @@ -4,9 +4,9 @@ use std::{ task::Poll, }; -use astria_core::sequencer::v1::{ - block::FilteredSequencerBlock, - RollupId, +use astria_core::{ + sequencer::v1::RollupId, + sequencerblock::v1alpha1::block::FilteredSequencerBlock, }; use astria_eyre::eyre::{ self, diff --git a/crates/astria-conductor/src/sequencer/client.rs b/crates/astria-conductor/src/sequencer/client.rs index 2d1a7a1b56..b82afce9b0 100644 --- a/crates/astria-conductor/src/sequencer/client.rs +++ b/crates/astria-conductor/src/sequencer/client.rs @@ -3,14 +3,12 @@ use std::time::Duration; use astria_core::{ - generated::sequencer::v1::{ + generated::sequencerblock::v1alpha1::{ sequencer_service_client::SequencerServiceClient, GetFilteredSequencerBlockRequest, }, - sequencer::v1::{ - block::FilteredSequencerBlock, - RollupId, - }, + sequencer::v1::RollupId, + sequencerblock::v1alpha1::block::FilteredSequencerBlock, }; use astria_eyre::eyre::{ self, diff --git a/crates/astria-conductor/src/sequencer/reporting.rs b/crates/astria-conductor/src/sequencer/reporting.rs index 01fd2243fd..7c350624a4 100644 --- a/crates/astria-conductor/src/sequencer/reporting.rs +++ b/crates/astria-conductor/src/sequencer/reporting.rs @@ -1,9 +1,9 @@ -use astria_core::sequencer::v1::{ - block::{ +use astria_core::{ + sequencer::v1::RollupId, + sequencerblock::v1alpha1::block::{ FilteredSequencerBlock, RollupTransactions, }, - RollupId, }; use indexmap::IndexMap; use serde::ser::{ @@ -42,13 +42,15 @@ impl<'a> Serialize for ReportRollups<'a> { #[cfg(test)] mod tests { - use astria_core::sequencer::v1::{ - block::{ + use astria_core::{ + sequencer::v1::{ + test_utils::ConfigureCometBftBlock, + RollupId, + }, + sequencerblock::v1alpha1::block::{ FilteredSequencerBlock, SequencerBlock, }, - test_utils::ConfigureCometBftBlock, - RollupId, }; use insta::assert_json_snapshot; diff --git a/crates/astria-conductor/src/utils.rs b/crates/astria-conductor/src/utils.rs index a58e39ad31..efd49c60e3 100644 --- a/crates/astria-conductor/src/utils.rs +++ b/crates/astria-conductor/src/utils.rs @@ -2,27 +2,8 @@ use astria_eyre::eyre::{ self, WrapErr as _, }; -use celestia_client::celestia_types::Height as CelestiaHeight; -use sequencer_client::tendermint::block::Height as SequencerHeight; use tokio::task::JoinError; -/// A necessary evil because the celestia client code uses a forked tendermint-rs. -pub(crate) trait IncrementableHeight { - fn increment(self) -> Self; -} - -impl IncrementableHeight for CelestiaHeight { - fn increment(self) -> Self { - self.increment() - } -} - -impl IncrementableHeight for SequencerHeight { - fn increment(self) -> Self { - self.increment() - } -} - pub(crate) fn flatten(res: Result, JoinError>) -> eyre::Result { match res { Ok(Ok(val)) => Ok(val), diff --git a/crates/astria-conductor/tests/blackbox/helpers/macros.rs b/crates/astria-conductor/tests/blackbox/helpers/macros.rs index 83c1f5c6ae..eee23a74d6 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/macros.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/macros.rs @@ -44,7 +44,7 @@ macro_rules! filtered_sequencer_block { ..Default::default() } .make(); - ::astria_core::sequencer::v1::SequencerBlock::try_from_cometbft(block) + ::astria_core::sequencerblock::v1alpha1::SequencerBlock::try_from_cometbft(block) .unwrap() .into_filtered_block([$crate::ROLLUP_ID]) .into_raw() @@ -157,7 +157,7 @@ macro_rules! mount_get_filtered_sequencer_block { ($test_env:ident, sequencer_height: $height:expr $(,)?) => { $test_env .mount_get_filtered_sequencer_block( - ::astria_core::generated::sequencer::v1::GetFilteredSequencerBlockRequest { + ::astria_core::generated::sequencerblock::v1alpha1::GetFilteredSequencerBlockRequest { height: $height, rollup_ids: vec![$crate::ROLLUP_ID.to_vec()], }, diff --git a/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs b/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs index be90576b72..1ef27635d3 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs @@ -20,7 +20,7 @@ use astria_core::generated::{ GetGenesisInfoRequest, UpdateCommitmentStateRequest, }, - sequencer::v1::{ + sequencerblock::v1alpha1::{ sequencer_service_server::{ SequencerService, SequencerServiceServer, diff --git a/crates/astria-conductor/tests/blackbox/helpers/mod.rs b/crates/astria-conductor/tests/blackbox/helpers/mod.rs index cec6416564..648a63fd56 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mod.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mod.rs @@ -10,7 +10,7 @@ use astria_core::{ CommitmentState, GenesisInfo, }, - sequencer::v1::FilteredSequencerBlock, + sequencerblock::v1alpha1::FilteredSequencerBlock, }, sequencer::v1::RollupId, }; diff --git a/crates/astria-core/src/execution/v1alpha2/mod.rs b/crates/astria-core/src/execution/v1alpha2/mod.rs index cf21b147e6..5df818d11a 100644 --- a/crates/astria-core/src/execution/v1alpha2/mod.rs +++ b/crates/astria-core/src/execution/v1alpha2/mod.rs @@ -7,7 +7,7 @@ use crate::{ IncorrectRollupIdLength, RollupId, }, - Protobuf, + sequencerblock::Protobuf, }; // An error when transforming a [`raw::GenesisInfo`] into a [`GenesisInfo`]. diff --git a/crates/astria-core/src/generated/astria.sequencer.v1.rs b/crates/astria-core/src/generated/astria.sequencer.v1.rs index 599c6fcbaa..6e4e978c1b 100644 --- a/crates/astria-core/src/generated/astria.sequencer.v1.rs +++ b/crates/astria-core/src/generated/astria.sequencer.v1.rs @@ -35,7 +35,7 @@ pub struct RollupTransactions { #[prost(bytes = "vec", repeated, tag = "2")] pub transactions: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, /// The proof that these rollup transactions are included in sequencer block. - /// `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + /// `astria.SequencerBlock.rollup_transactions_proof`. #[prost(message, optional, tag = "3")] pub proof: ::core::option::Option, } @@ -91,7 +91,7 @@ pub struct SequencerBlockHeader { #[prost(message, optional, tag = "1")] pub cometbft_header: ::core::option::Option<::tendermint_proto::types::Header>, /// The 32-byte merkle root of all the rollup transactions in the block, - /// Corresponds to `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, + /// Corresponds to `MHT(astria.SequencerBlock.rollup_transactions)`, #[prost(bytes = "vec", tag = "2")] pub rollup_transactions_root: ::prost::alloc::vec::Vec, /// The 32-byte merkle root of all the rollup IDs in the block. @@ -154,7 +154,7 @@ pub struct FilteredSequencerBlock { #[prost(message, repeated, tag = "2")] pub rollup_transactions: ::prost::alloc::vec::Vec, /// The Merkle Tree Hash of all the rollup transactions in the block (not just the - /// subset included). Corresponds to `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, + /// subset included). Corresponds to `MHT(astria.SequencerBlock.rollup_transactions)`, /// the Merkle Tree Hash derived from the rollup transactions. /// Always 32 bytes. #[prost(bytes = "vec", tag = "3")] @@ -168,7 +168,7 @@ pub struct FilteredSequencerBlock { pub rollup_transactions_proof: ::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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. /// Note that these are all the rollup IDs in the sequencer block, not merely those in /// `rollup_transactions` field. This is necessary to prove that no rollup IDs were omitted. #[prost(bytes = "vec", repeated, tag = "5")] @@ -305,7 +305,7 @@ pub struct CelestiaRollupBlob { #[prost(bytes = "vec", repeated, tag = "3")] pub transactions: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, /// The proof that these rollup transactions are included in sequencer block. - /// `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + /// `astria.sequencerblock.v1alpha1.SequencerBlock.rollup_transactions_proof`. #[prost(message, optional, tag = "4")] pub proof: ::core::option::Option, } @@ -318,7 +318,7 @@ impl ::prost::Name for CelestiaRollupBlob { } /// The metadata of a sequencer block that is submitted to celestia. /// -/// It is created by splitting a `astria.sequencer.v1alpha.SequencerBlock` into a +/// 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. /// @@ -328,26 +328,26 @@ impl ::prost::Name for CelestiaRollupBlob { #[derive(Clone, PartialEq, ::prost::Message)] pub struct CelestiaSequencerBlob { /// The original CometBFT header that is the input to this blob's original sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.header`. + /// Corresponds to `astria.SequencerBlock.header`. #[prost(message, optional, tag = "1")] pub header: ::core::option::Option<::tendermint_proto::types::Header>, /// 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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. #[prost(bytes = "vec", repeated, tag = "2")] pub rollup_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, /// The Merkle Tree Hash of the rollup transactions. Corresponds to - /// `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, the Merkle + /// `MHT(astria.SequencerBlock.rollup_transactions)`, the Merkle /// Tree Hash deriveed from the rollup transactions. /// Always 32 bytes. #[prost(bytes = "vec", tag = "3")] pub rollup_transactions_root: ::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, /// 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, } diff --git a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs index 1f133ed39c..2914bd097d 100644 --- a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs @@ -35,7 +35,7 @@ pub struct RollupTransactions { #[prost(bytes = "vec", repeated, tag = "2")] pub transactions: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, /// The proof that these rollup transactions are included in sequencer block. - /// `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + /// `astria.SequencerBlock.rollup_transactions_proof`. #[prost(message, optional, tag = "3")] pub proof: ::core::option::Option, } @@ -105,7 +105,7 @@ pub struct SequencerBlockHeader { #[prost(bytes = "vec", tag = "5")] pub proposer_address: ::prost::alloc::vec::Vec, /// The 32-byte merkle root of all the rollup transactions in the block, - /// Corresponds to `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, + /// Corresponds to `MHT(astria.SequencerBlock.rollup_transactions)`, #[prost(bytes = "vec", tag = "6")] pub rollup_transactions_root: ::prost::alloc::vec::Vec, } @@ -176,7 +176,7 @@ pub struct FilteredSequencerBlock { pub rollup_transactions_proof: ::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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. /// Note that these are all the rollup IDs in the sequencer block, not merely those in /// `rollup_transactions` field. This is necessary to prove that no rollup IDs were omitted. #[prost(bytes = "vec", repeated, tag = "5")] @@ -249,7 +249,7 @@ pub struct CelestiaRollupBlob { #[prost(bytes = "vec", repeated, tag = "3")] pub transactions: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, /// The proof that these rollup transactions are included in sequencer block. - /// `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + /// `astria.SequencerBlock.rollup_transactions_proof`. #[prost(message, optional, tag = "4")] pub proof: ::core::option::Option, } @@ -262,7 +262,7 @@ impl ::prost::Name for CelestiaRollupBlob { } /// The metadata of a sequencer block that is submitted to celestia. /// -/// It is created by splitting a `astria.sequencer.v1alpha.SequencerBlock` into a +/// 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. /// @@ -279,15 +279,15 @@ pub struct CelestiaSequencerBlob { 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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. #[prost(bytes = "vec", repeated, tag = "3")] pub rollup_ids: ::prost::alloc::vec::Vec<::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.rollup_transactions_proof`. #[prost(message, optional, tag = "4")] pub rollup_transactions_proof: ::core::option::Option, /// The proof that the rollup IDs are included in sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.rollup_ids_proof`. + /// Corresponds to `astria.SequencerBlock.rollup_ids_proof`. #[prost(message, optional, tag = "5")] pub rollup_ids_proof: ::core::option::Option, } 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 5673537720..e9424beb24 100644 --- a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs +++ b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs @@ -472,6 +472,237 @@ impl<'de> serde::Deserialize<'de> for GetFilteredSequencerBlockRequest { deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for GetSequencerBlockRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.height != 0 { + 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 Proof { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.audit_path.is_empty() { + len += 1; + } + if self.leaf_index != 0 { + len += 1; + } + if self.tree_size != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.Proof", len)?; + if !self.audit_path.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("audit_path", pbjson::private::base64::encode(&self.audit_path).as_str())?; + } + if self.leaf_index != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("leaf_index", ToString::to_string(&self.leaf_index).as_str())?; + } + if self.tree_size != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("tree_size", ToString::to_string(&self.tree_size).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Proof { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "audit_path", + "auditPath", + "leaf_index", + "leafIndex", + "tree_size", + "treeSize", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AuditPath, + LeafIndex, + TreeSize, + } + 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 { + "auditPath" | "audit_path" => Ok(GeneratedField::AuditPath), + "leafIndex" | "leaf_index" => Ok(GeneratedField::LeafIndex), + "treeSize" | "tree_size" => Ok(GeneratedField::TreeSize), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Proof; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.v1alpha1.Proof") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut audit_path__ = None; + let mut leaf_index__ = None; + let mut tree_size__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AuditPath => { + if audit_path__.is_some() { + return Err(serde::de::Error::duplicate_field("auditPath")); + } + audit_path__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::LeafIndex => { + if leaf_index__.is_some() { + return Err(serde::de::Error::duplicate_field("leafIndex")); + } + leaf_index__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TreeSize => { + if tree_size__.is_some() { + return Err(serde::de::Error::duplicate_field("treeSize")); + } + tree_size__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(Proof { + audit_path: audit_path__.unwrap_or_default(), + leaf_index: leaf_index__.unwrap_or_default(), + tree_size: tree_size__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.Proof", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for RollupData { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -714,6 +945,172 @@ impl<'de> serde::Deserialize<'de> for RollupTransactions { deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.RollupTransactions", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for SequencerBlock { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + 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.rollup_ids_proof.is_some() { + len += 1; + } + if !self.block_hash.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.SequencerBlock", len)?; + 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 let Some(v) = self.rollup_ids_proof.as_ref() { + struct_ser.serialize_field("rollup_ids_proof", v)?; + } + 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())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SequencerBlock { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "header", + "rollup_transactions", + "rollupTransactions", + "rollup_transactions_proof", + "rollupTransactionsProof", + "rollup_ids_proof", + "rollupIdsProof", + "block_hash", + "blockHash", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Header, + RollupTransactions, + RollupTransactionsProof, + RollupIdsProof, + BlockHash, + } + 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 { + "header" => Ok(GeneratedField::Header), + "rollupTransactions" | "rollup_transactions" => Ok(GeneratedField::RollupTransactions), + "rollupTransactionsProof" | "rollup_transactions_proof" => Ok(GeneratedField::RollupTransactionsProof), + "rollupIdsProof" | "rollup_ids_proof" => Ok(GeneratedField::RollupIdsProof), + "blockHash" | "block_hash" => Ok(GeneratedField::BlockHash), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SequencerBlock; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.v1alpha1.SequencerBlock") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut header__ = None; + let mut rollup_transactions__ = None; + let mut rollup_transactions_proof__ = None; + let mut rollup_ids_proof__ = None; + let mut block_hash__ = None; + while let Some(k) = map_.next_key()? { + match k { + 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::RollupIdsProof => { + if rollup_ids_proof__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupIdsProof")); + } + rollup_ids_proof__ = map_.next_value()?; + } + 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) + ; + } + } + } + Ok(SequencerBlock { + header: header__, + rollup_transactions: rollup_transactions__.unwrap_or_default(), + rollup_transactions_proof: rollup_transactions_proof__, + rollup_ids_proof: rollup_ids_proof__, + block_hash: block_hash__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SequencerBlock", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for SequencerBlockHeader { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/astria-core/src/generated/mod.rs b/crates/astria-core/src/generated/mod.rs index 8e53f4ccf3..dea101fdc5 100644 --- a/crates/astria-core/src/generated/mod.rs +++ b/crates/astria-core/src/generated/mod.rs @@ -52,6 +52,19 @@ pub mod sequencer { } } +#[path = ""] +pub mod sequencerblock { + pub mod v1alpha1 { + include!("astria.sequencerblock.v1alpha1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("astria.sequencerblock.v1alpha1.serde.rs"); + } + } +} + #[path = ""] pub mod composer { #[path = "astria.composer.v1alpha1.rs"] diff --git a/crates/astria-core/src/lib.rs b/crates/astria-core/src/lib.rs index 9434983b55..780bf6ded3 100644 --- a/crates/astria-core/src/lib.rs +++ b/crates/astria-core/src/lib.rs @@ -9,44 +9,7 @@ pub mod generated; pub mod execution; pub mod primitive; pub mod sequencer; +pub mod sequencerblock; #[cfg(feature = "serde")] pub(crate) mod serde; - -/// A trait to convert from raw decoded protobuf types to idiomatic astria types. -/// -/// The primary use of this trait is to convert to/from foreign types. -pub trait Protobuf: Sized { - /// Errors that can occur when transforming from a raw type. - type Error; - /// The raw deserialized protobuf type. - type Raw; - - /// Convert from a reference to the raw protobuf type. - /// - /// # Errors - /// Returns [`Self::Error`] as defined by the implementator of this trait. - fn try_from_raw_ref(raw: &Self::Raw) -> Result; - - /// Convert from the raw protobuf type, dropping it. - /// - /// This method provides a default implementation in terms of - /// [`Self::try_from_raw_ref`]. - /// - /// # Errors - /// Returns [`Self::Error`] as defined by the implementator of this trait. - fn try_from_raw(raw: Self::Raw) -> Result { - Self::try_from_raw_ref(&raw) - } - - /// Convert to the raw protobuf type by reference. - fn to_raw(&self) -> Self::Raw; - - /// Convert to the raw protobuf type, dropping `self`. - /// - /// This method provides a default implementation in terms of - /// [`Self::to_raw`]. - fn into_raw(self) -> Self::Raw { - Self::to_raw(&self) - } -} diff --git a/crates/astria-core/src/sequencer/mod.rs b/crates/astria-core/src/sequencer/mod.rs index a3a6d96c3f..f526869585 100644 --- a/crates/astria-core/src/sequencer/mod.rs +++ b/crates/astria-core/src/sequencer/mod.rs @@ -1 +1,39 @@ pub mod v1; + +/// A trait to convert from raw decoded protobuf types to idiomatic astria types. +/// +/// The primary use of this trait is to convert to/from foreign types. +pub trait Protobuf: Sized { + /// Errors that can occur when transforming from a raw type. + type Error; + /// The raw deserialized protobuf type. + type Raw; + + /// Convert from a reference to the raw protobuf type. + /// + /// # Errors + /// Returns [`Self::Error`] as defined by the implementator of this trait. + fn try_from_raw_ref(raw: &Self::Raw) -> Result; + + /// Convert from the raw protobuf type, dropping it. + /// + /// This method provides a default implementation in terms of + /// [`Self::try_from_raw_ref`]. + /// + /// # Errors + /// Returns [`Self::Error`] as defined by the implementator of this trait. + fn try_from_raw(raw: Self::Raw) -> Result { + Self::try_from_raw_ref(&raw) + } + + /// Convert to the raw protobuf type by reference. + fn to_raw(&self) -> Self::Raw; + + /// Convert to the raw protobuf type, dropping `self`. + /// + /// This method provides a default implementation in terms of + /// [`Self::to_raw`]. + fn into_raw(self) -> Self::Raw { + Self::to_raw(&self) + } +} diff --git a/crates/astria-core/src/sequencer/v1/block/mod.rs b/crates/astria-core/src/sequencer/v1/block/mod.rs index 1789f60b4d..bf78d387e8 100644 --- a/crates/astria-core/src/sequencer/v1/block/mod.rs +++ b/crates/astria-core/src/sequencer/v1/block/mod.rs @@ -14,8 +14,8 @@ use super::{ IncorrectRollupIdLength, RollupId, }; -use crate::{ - sequencer::v1::{ +use crate::sequencer::{ + v1::{ are_rollup_ids_included, are_rollup_txs_included, asset, diff --git a/crates/astria-core/src/sequencer/v1/celestia.rs b/crates/astria-core/src/sequencer/v1/celestia.rs index c6d107f509..adda5c641d 100644 --- a/crates/astria-core/src/sequencer/v1/celestia.rs +++ b/crates/astria-core/src/sequencer/v1/celestia.rs @@ -12,7 +12,7 @@ use super::{ IncorrectRollupIdLength, RollupId, }; -use crate::Protobuf; +use crate::sequencer::Protobuf; /// A bundle of blobs constructed from a [`super::SequencerBlock`]. /// @@ -123,7 +123,7 @@ enum CelestiaRollupBlobErrorKind { FieldNotSet { field: &'static str }, #[error("failed converting the provided bytes to Rollup ID")] RollupId { source: IncorrectRollupIdLength }, - #[error("failed constructing a Merkle Hash Tree Proof from the provided raw protobf type")] + #[error("failed constructing a Merkle Hash Tree Proof from the provided raw protobuf type")] Proof { source: ::Error, }, @@ -341,7 +341,7 @@ impl CelestiaSequencerBlobError { fn rollup_transactions_not_in_cometbft_block() -> Self { Self { - kind: CelestiaSequencerBlobErrorKind::RollupTransactiosnNotInCometBftBlock, + kind: CelestiaSequencerBlobErrorKind::RollupTransactionsNotInCometBftBlock, } } @@ -385,7 +385,7 @@ enum CelestiaSequencerBlobErrorKind { "the Merkle Tree Hash of the rollup transactions was not a leaf in the sequencer block \ data" )] - RollupTransactiosnNotInCometBftBlock, + RollupTransactionsNotInCometBftBlock, #[error("the Merkle Tree Hash of the rollup IDs was not a leaf in the sequencer block data")] RollupIdsNotInCometBftBlock, } @@ -397,26 +397,26 @@ enum CelestiaSequencerBlobErrorKind { #[derive(Clone, Debug)] pub struct UncheckedCelestiaSequencerBlob { /// The original `CometBFT` header that is the input to this blob's original sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.header`. + /// Corresponds to `astria.SequencerBlock.header`. pub header: tendermint::block::header::Header, /// The rollup rollup IDs for which `CelestiaRollupBlob`s were submitted to celestia. /// Corresponds to the `astria.sequencer.v1alpha1.RollupTransactions.id` field - /// and is extracted from `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. pub rollup_ids: Vec, /// The Merkle Tree Hash of the rollup transactions. Corresponds to - /// `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, the Merkle + /// `MHT(astria.SequencerBlock.rollup_transactions)`, the Merkle /// Tree Hash deriveed from the rollup transactions. /// Always 32 bytes. pub rollup_transactions_root: [u8; 32], /// The proof that the rollup transactions are included in sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + /// Corresponds to `astria.SequencerBlock.rollup_transactions_proof`. pub rollup_transactions_proof: merkle::Proof, /// The proof that this sequencer blob includes all rollup IDs of the original sequencer /// block it was derived from. This proof together with `Sha256(MHT(rollup_ids))` (Sha256 /// applied to the Merkle Tree Hash of the rollup ID sequence) must be equal to /// `header.data_hash` which itself must match - /// `astria.sequencer.v1alpha.SequencerBlock.header.data_hash`. This field corresponds to - /// `astria.sequencer.v1alpha.SequencerBlock.rollup_ids_proof`. + /// `astria.SequencerBlock.header.data_hash`. This field corresponds to + /// `astria.SequencerBlock.rollup_ids_proof`. pub rollup_ids_proof: merkle::Proof, } @@ -501,26 +501,26 @@ pub struct CelestiaSequencerBlob { /// The block hash obtained from hashing `.header`. block_hash: [u8; 32], /// The original `CometBFT` header that is the input to this blob's original sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.header`. + /// Corresponds to `astria.SequencerBlock.header`. header: tendermint::block::header::Header, /// The rollup IDs for which `CelestiaRollupBlob`s were submitted to celestia. /// Corresponds to the `astria.sequencer.v1alpha1.RollupTransactions.id` field - /// and is extracted from `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. rollup_ids: Vec, /// The Merkle Tree Hash of the rollup transactions. Corresponds to - /// `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, the Merkle + /// `MHT(astria.SequencerBlock.rollup_transactions)`, the Merkle /// Tree Hash deriveed from the rollup transactions. /// Always 32 bytes. rollup_transactions_root: [u8; 32], /// The proof that the rollup transactions are included in sequencer block. - /// Corresponds to `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + /// Corresponds to `astria.SequencerBlock.rollup_transactions_proof`. rollup_transactions_proof: merkle::Proof, /// The proof that this sequencer blob includes all rollup IDs of the original sequencer /// block it was derived from. This proof together with `Sha256(MHT(rollup_ids))` (Sha256 /// applied to the Merkle Tree Hash of the rollup ID sequence) must be equal to /// `header.data_hash` which itself must match - /// `astria.sequencer.v1alpha.SequencerBlock.header.data_hash`. This field corresponds to - /// `astria.sequencer.v1alpha.SequencerBlock.rollup_ids_proof`. + /// `astria.SequencerBlock.header.data_hash`. This field corresponds to + /// `astria.SequencerBlock.rollup_ids_proof`. rollup_ids_proof: merkle::Proof, } diff --git a/crates/astria-core/src/sequencer/v1/mod.rs b/crates/astria-core/src/sequencer/v1/mod.rs index 9de3eb944e..777896bd48 100644 --- a/crates/astria-core/src/sequencer/v1/mod.rs +++ b/crates/astria-core/src/sequencer/v1/mod.rs @@ -8,10 +8,7 @@ use sha2::{ Sha256, }; -use crate::{ - generated::sequencer::v1 as raw, - Protobuf, -}; +use crate::generated::sequencer::v1 as raw; pub mod abci; pub mod account; @@ -37,6 +34,65 @@ pub use transaction::{ UnsignedTransaction, }; +use crate::sequencer::Protobuf; + +impl Protobuf for merkle::Proof { + type Error = merkle::audit::InvalidProof; + type Raw = raw::Proof; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + // XXX: Implementing this by cloning is ok because `audit_path` + // has to be cloned always due to `UncheckedProof`'s constructor. + Self::try_from_raw(raw.clone()) + } + + fn try_from_raw(raw: Self::Raw) -> Result { + let Self::Raw { + audit_path, + leaf_index, + tree_size, + } = raw; + let leaf_index = leaf_index.try_into().expect( + "running on a machine with at least 64 bit pointer width and can convert from u64 to \ + usize", + ); + let tree_size = tree_size.try_into().expect( + "running on a machine with at least 64 bit pointer width and can convert from u64 to \ + usize", + ); + Self::unchecked() + .audit_path(audit_path) + .leaf_index(leaf_index) + .tree_size(tree_size) + .try_into_proof() + } + + fn to_raw(&self) -> Self::Raw { + // XXX: Implementing in terms of clone is ok because the fields would need to be cloned + // anyway. + self.clone().into_raw() + } + + fn into_raw(self) -> Self::Raw { + let merkle::audit::UncheckedProof { + audit_path, + leaf_index, + tree_size, + } = self.into_unchecked(); + Self::Raw { + audit_path, + leaf_index: leaf_index.try_into().expect( + "running on a machine with at most 64 bit pointer width and can convert from \ + usize to u64", + ), + tree_size: tree_size.try_into().expect( + "running on a machine with at most 64 bit pointer width and can convert from \ + usize to u64", + ), + } + } +} + use self::block::RollupTransactions; pub const ADDRESS_LEN: usize = 20; @@ -260,63 +316,6 @@ pub struct IncorrectAddressLength { received: usize, } -impl Protobuf for merkle::Proof { - type Error = merkle::audit::InvalidProof; - type Raw = raw::Proof; - - fn try_from_raw_ref(raw: &Self::Raw) -> Result { - // XXX: Implementing this by cloning is ok because `audit_path` - // has to be cloned always due to `UncheckedProof`'s constructor. - Self::try_from_raw(raw.clone()) - } - - fn try_from_raw(raw: Self::Raw) -> Result { - let Self::Raw { - audit_path, - leaf_index, - tree_size, - } = raw; - let leaf_index = leaf_index.try_into().expect( - "running on a machine with at least 64 bit pointer width and can convert from u64 to \ - usize", - ); - let tree_size = tree_size.try_into().expect( - "running on a machine with at least 64 bit pointer width and can convert from u64 to \ - usize", - ); - Self::unchecked() - .audit_path(audit_path) - .leaf_index(leaf_index) - .tree_size(tree_size) - .try_into_proof() - } - - fn to_raw(&self) -> Self::Raw { - // XXX: Implementing in terms of clone is ok because the fields would need to be cloned - // anyway. - self.clone().into_raw() - } - - fn into_raw(self) -> Self::Raw { - let merkle::audit::UncheckedProof { - audit_path, - leaf_index, - tree_size, - } = self.into_unchecked(); - Self::Raw { - audit_path, - leaf_index: leaf_index.try_into().expect( - "running on a machine with at most 64 bit pointer width and can convert from \ - usize to u64", - ), - tree_size: tree_size.try_into().expect( - "running on a machine with at most 64 bit pointer width and can convert from \ - usize to u64", - ), - } - } -} - fn do_rollup_transaction_match_root( rollup_transactions: &RollupTransactions, root: [u8; 32], diff --git a/crates/astria-core/src/sequencer/v1/transaction/action.rs b/crates/astria-core/src/sequencer/v1/transaction/action.rs index 426d39411a..fa0b87226a 100644 --- a/crates/astria-core/src/sequencer/v1/transaction/action.rs +++ b/crates/astria-core/src/sequencer/v1/transaction/action.rs @@ -9,8 +9,8 @@ use penumbra_ibc::IbcRelay; use penumbra_proto::penumbra::core::component::ibc::v1::FungibleTokenPacketData; use super::raw; -use crate::{ - sequencer::v1::{ +use crate::sequencer::{ + v1::{ asset::{ self, Denom, diff --git a/crates/astria-core/src/sequencerblock/mod.rs b/crates/astria-core/src/sequencerblock/mod.rs new file mode 100644 index 0000000000..1b946e25c1 --- /dev/null +++ b/crates/astria-core/src/sequencerblock/mod.rs @@ -0,0 +1,39 @@ +pub mod v1alpha1; + +/// A trait to convert from raw decoded protobuf types to idiomatic astria types. +/// +/// The primary use of this trait is to convert to/from foreign types. +pub trait Protobuf: Sized { + /// Errors that can occur when transforming from a raw type. + type Error; + /// The raw deserialized protobuf type. + type Raw; + + /// Convert from a reference to the raw protobuf type. + /// + /// # Errors + /// Returns [`Self::Error`] as defined by the implementator of this trait. + fn try_from_raw_ref(raw: &Self::Raw) -> Result; + + /// Convert from the raw protobuf type, dropping it. + /// + /// This method provides a default implementation in terms of + /// [`Self::try_from_raw_ref`]. + /// + /// # Errors + /// Returns [`Self::Error`] as defined by the implementator of this trait. + fn try_from_raw(raw: Self::Raw) -> Result { + Self::try_from_raw_ref(&raw) + } + + /// Convert to the raw protobuf type by reference. + fn to_raw(&self) -> Self::Raw; + + /// Convert to the raw protobuf type, dropping `self`. + /// + /// This method provides a default implementation in terms of + /// [`Self::to_raw`]. + fn into_raw(self) -> Self::Raw { + Self::to_raw(&self) + } +} diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs new file mode 100644 index 0000000000..af4d4b4635 --- /dev/null +++ b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs @@ -0,0 +1,1528 @@ +use std::collections::HashMap; + +use indexmap::IndexMap; +use sha2::Sha256; +use tendermint::{ + account, + Time, +}; +use transaction::SignedTransaction; + +use super::{ + are_rollup_ids_included, + are_rollup_txs_included, + celestia::{ + self, + CelestiaRollupBlob, + CelestiaSequencerBlob, + }, + raw, +}; +use crate::{ + sequencer::v1::{ + asset, + derive_merkle_tree_from_rollup_txs, + transaction, + transaction::action, + Address, + IncorrectAddressLength, + IncorrectRollupIdLength, + RollupId, + }, + sequencerblock::Protobuf as _, +}; + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct RollupTransactionsError(RollupTransactionsErrorKind); + +impl RollupTransactionsError { + fn rollup_id(source: IncorrectRollupIdLength) -> Self { + Self(RollupTransactionsErrorKind::RollupId(source)) + } + + fn field_not_set(field: &'static str) -> Self { + Self(RollupTransactionsErrorKind::FieldNotSet(field)) + } + + fn proof_invalid(source: merkle::audit::InvalidProof) -> Self { + Self(RollupTransactionsErrorKind::ProofInvalid(source)) + } +} + +#[derive(Debug, thiserror::Error)] +enum RollupTransactionsErrorKind { + #[error("`id` field is invalid")] + RollupId(#[source] IncorrectRollupIdLength), + #[error("the expected field in the raw source type was not set: `{0}`")] + FieldNotSet(&'static str), + #[error("failed constructing a proof from the raw protobuf `proof` field")] + ProofInvalid(#[source] merkle::audit::InvalidProof), +} + +/// The individual parts that make up a [`RollupTransactions`] type. +/// +/// Provides convenient access to the fields of [`RollupTransactions`]. +#[derive(Clone, Debug, PartialEq)] +pub struct RollupTransactionsParts { + pub rollup_id: RollupId, + pub transactions: Vec>, + pub proof: merkle::Proof, +} + +/// The opaque transactions belonging to a rollup identified by its rollup ID. +#[derive(Clone, Debug, PartialEq)] +pub struct RollupTransactions { + /// The 32 bytes identifying a rollup. Usually the sha256 hash of a plain rollup name. + rollup_id: RollupId, + /// The block data for this rollup in the form of encoded [`RollupData`]. + transactions: Vec>, + /// Proof that this set of transactions belongs in the rollup datas merkle tree + proof: merkle::Proof, +} + +impl RollupTransactions { + /// Returns the [`RollupId`] identifying the rollup these transactions belong to. + #[must_use] + pub fn rollup_id(&self) -> RollupId { + self.rollup_id + } + + /// Returns the block data for this rollup. + #[must_use] + pub fn transactions(&self) -> &[Vec] { + &self.transactions + } + + /// Returns the merkle proof that these transactions were included + /// in the `action_tree_commitment`. + #[must_use] + pub fn proof(&self) -> &merkle::Proof { + &self.proof + } + + /// Transforms these rollup transactions into their raw representation, which can in turn be + /// encoded as protobuf. + #[must_use] + pub fn into_raw(self) -> raw::RollupTransactions { + let Self { + rollup_id, + transactions, + proof, + } = self; + raw::RollupTransactions { + rollup_id: rollup_id.get().to_vec(), + transactions, + proof: Some(proof.into_raw()), + } + } + + /// Attempts to transform the rollup transactions from their raw representation. + /// + /// # Errors + /// Returns an error if the rollup ID bytes could not be turned into a [`RollupId`]. + pub fn try_from_raw(raw: raw::RollupTransactions) -> Result { + let raw::RollupTransactions { + rollup_id, + transactions, + proof, + } = raw; + let rollup_id = + RollupId::try_from_slice(&rollup_id).map_err(RollupTransactionsError::rollup_id)?; + let proof = 'proof: { + let Some(proof) = proof else { + break 'proof Err(RollupTransactionsError::field_not_set("proof")); + }; + merkle::Proof::try_from_raw(proof).map_err(RollupTransactionsError::proof_invalid) + }?; + Ok(Self { + rollup_id, + transactions, + proof, + }) + } + + /// Convert [`RollupTransactions`] into [`RollupTransactionsParts`]. + #[must_use] + pub fn into_parts(self) -> RollupTransactionsParts { + let Self { + rollup_id, + transactions, + proof, + } = self; + RollupTransactionsParts { + rollup_id, + transactions, + proof, + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct SequencerBlockError(SequencerBlockErrorKind); + +impl SequencerBlockError { + fn invalid_block_hash(length: usize) -> Self { + Self(SequencerBlockErrorKind::InvalidBlockHash(length)) + } + + fn comet_bft_data_hash_does_not_match_reconstructed() -> Self { + Self(SequencerBlockErrorKind::CometBftDataHashDoesNotMatchReconstructed) + } + + fn comet_bft_block_hash_is_none() -> Self { + Self(SequencerBlockErrorKind::CometBftBlockHashIsNone) + } + + fn field_not_set(field: &'static str) -> Self { + Self(SequencerBlockErrorKind::FieldNotSet(field)) + } + + fn header(source: SequencerBlockHeaderError) -> Self { + Self(SequencerBlockErrorKind::Header(source)) + } + + fn parse_rollup_transactions(source: RollupTransactionsError) -> Self { + Self(SequencerBlockErrorKind::ParseRollupTransactions(source)) + } + + fn transaction_proof_invalid(source: merkle::audit::InvalidProof) -> Self { + Self(SequencerBlockErrorKind::TransactionProofInvalid(source)) + } + + fn id_proof_invalid(source: merkle::audit::InvalidProof) -> Self { + Self(SequencerBlockErrorKind::IdProofInvalid(source)) + } + + fn no_rollup_transactions_root() -> Self { + Self(SequencerBlockErrorKind::NoRollupTransactionsRoot) + } + + fn incorrect_rollup_transactions_root_length(len: usize) -> Self { + Self(SequencerBlockErrorKind::IncorrectRollupTransactionsRootLength(len)) + } + + fn no_rollup_ids_root() -> Self { + Self(SequencerBlockErrorKind::NoRollupIdsRoot) + } + + fn incorrect_rollup_ids_root_length(len: usize) -> Self { + Self(SequencerBlockErrorKind::IncorrectRollupIdsRootLength(len)) + } + + fn rollup_transactions_not_in_sequencer_block() -> Self { + Self(SequencerBlockErrorKind::RollupTransactionsNotInSequencerBlock) + } + + fn rollup_ids_not_in_sequencer_block() -> Self { + Self(SequencerBlockErrorKind::RollupIdsNotInSequencerBlock) + } + + fn signed_transaction_protobuf_decode(source: prost::DecodeError) -> Self { + Self(SequencerBlockErrorKind::SignedTransactionProtobufDecode( + source, + )) + } + + fn raw_signed_transaction_conversion(source: transaction::SignedTransactionError) -> Self { + Self(SequencerBlockErrorKind::RawSignedTransactionConversion( + source, + )) + } + + fn rollup_transactions_root_does_not_match_reconstructed() -> Self { + Self(SequencerBlockErrorKind::RollupTransactionsRootDoesNotMatchReconstructed) + } + + fn rollup_ids_root_does_not_match_reconstructed() -> Self { + Self(SequencerBlockErrorKind::RollupIdsRootDoesNotMatchReconstructed) + } + + fn invalid_rollup_transactions_root() -> Self { + Self(SequencerBlockErrorKind::InvalidRollupTransactionsRoot) + } + + fn invalid_rollup_ids_proof() -> Self { + Self(SequencerBlockErrorKind::InvalidRollupIdsProof) + } +} + +#[derive(Debug, thiserror::Error)] +enum SequencerBlockErrorKind { + #[error("the block hash was expected to be 32 bytes long, but was actually `{0}`")] + InvalidBlockHash(usize), + #[error( + "the CometBFT block.header.data_hash does not match the Merkle Tree Hash derived from \ + block.data" + )] + CometBftDataHashDoesNotMatchReconstructed, + #[error("hashing the CometBFT block.header returned an empty hash which is not permitted")] + CometBftBlockHashIsNone, + #[error("the expected field in the raw source type was not set: `{0}`")] + FieldNotSet(&'static str), + #[error("failed constructing a sequencer block header from the raw protobuf header")] + Header(#[source] SequencerBlockHeaderError), + #[error( + "failed parsing a raw protobuf rollup transaction because it contained an invalid rollup \ + ID" + )] + ParseRollupTransactions(#[source] RollupTransactionsError), + #[error("failed constructing a transaction proof from the raw protobuf transaction proof")] + TransactionProofInvalid(#[source] merkle::audit::InvalidProof), + #[error("failed constructing a rollup ID proof from the raw protobuf rollup ID proof")] + IdProofInvalid(#[source] merkle::audit::InvalidProof), + #[error( + "the cometbft block.data field was too short and did not contain the rollup transaction \ + root" + )] + NoRollupTransactionsRoot, + #[error( + "the rollup transaction root in the cometbft block.data field was expected to be 32 bytes \ + long, but was actually `{0}`" + )] + IncorrectRollupTransactionsRootLength(usize), + #[error("the cometbft block.data field was too short and did not contain the rollup ID root")] + NoRollupIdsRoot, + #[error( + "the rollup ID root in the cometbft block.data field was expected to be 32 bytes long, \ + but was actually `{0}`" + )] + IncorrectRollupIdsRootLength(usize), + #[error( + "the Merkle Tree Hash derived from the rollup transactions recorded in the raw protobuf \ + sequencer block could not be verified against their proof and the block's data hash" + )] + RollupTransactionsNotInSequencerBlock, + #[error( + "the Merkle Tree Hash derived from the rollup IDs recorded in the raw protobuf sequencer \ + block could not be verified against their proof and the block's data hash" + )] + RollupIdsNotInSequencerBlock, + #[error( + "failed decoding an entry in the cometbft block.data field as a protobuf signed astria \ + transaction" + )] + SignedTransactionProtobufDecode(#[source] prost::DecodeError), + #[error( + "failed converting a raw protobuf signed transaction decoded from the cometbft block.data + field to a native astria signed transaction" + )] + RawSignedTransactionConversion(#[source] transaction::SignedTransactionError), + #[error( + "the root derived from the rollup transactions in the cometbft block.data field did not \ + match the root stored in the same block.data field" + )] + RollupTransactionsRootDoesNotMatchReconstructed, + #[error( + "the root derived from the rollup IDs in the cometbft block.data field did not match the \ + root stored in the same block.data field" + )] + RollupIdsRootDoesNotMatchReconstructed, + #[error( + "the rollup transactions root in the header did not verify against data_hash given the \ + rollup transactions proof" + )] + InvalidRollupTransactionsRoot, + #[error( + "the rollup IDs root constructed from the block's rollup IDs did not verify against \ + data_hash given the rollup IDs proof" + )] + InvalidRollupIdsProof, +} + +/// The individual parts that make up a [`SequencerBlockHeader`]. +/// +/// This type exists to provide convenient access to the fields of +/// a `[SequencerBlockHeader]`. +#[derive(Debug)] +pub struct SequencerBlockHeaderParts { + pub chain_id: tendermint::chain::Id, + pub height: tendermint::block::Height, + pub time: Time, + pub rollup_transactions_root: [u8; 32], + pub data_hash: [u8; 32], + pub proposer_address: account::Id, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SequencerBlockHeader { + chain_id: tendermint::chain::Id, + height: tendermint::block::Height, + time: Time, + // the 32-byte merkle root of all the rollup transactions in the block + rollup_transactions_root: [u8; 32], + data_hash: [u8; 32], + proposer_address: account::Id, +} + +impl SequencerBlockHeader { + #[must_use] + pub fn chain_id(&self) -> &tendermint::chain::Id { + &self.chain_id + } + + #[must_use] + pub fn height(&self) -> tendermint::block::Height { + self.height + } + + #[must_use] + pub fn time(&self) -> Time { + self.time + } + + #[must_use] + pub fn rollup_transactions_root(&self) -> [u8; 32] { + self.rollup_transactions_root + } + + #[must_use] + pub fn data_hash(&self) -> [u8; 32] { + self.data_hash + } + + #[must_use] + pub fn proposer_address(&self) -> &account::Id { + &self.proposer_address + } + + /// Convert [`SequencerBlockHeader`] into its [`SequencerBlockHeaderParts`]. + #[must_use] + pub fn into_parts(self) -> SequencerBlockHeaderParts { + let Self { + chain_id, + height, + time, + rollup_transactions_root, + data_hash, + proposer_address, + } = self; + SequencerBlockHeaderParts { + chain_id, + height, + time, + rollup_transactions_root, + data_hash, + proposer_address, + } + } + + #[must_use] + pub fn into_raw(self) -> raw::SequencerBlockHeader { + let time: tendermint_proto::google::protobuf::Timestamp = self.time.into(); + raw::SequencerBlockHeader { + chain_id: self.chain_id.to_string(), + height: self.height.value(), + time: Some(pbjson_types::Timestamp { + seconds: time.seconds, + nanos: time.nanos, + }), + rollup_transactions_root: self.rollup_transactions_root.to_vec(), + data_hash: self.data_hash.to_vec(), + proposer_address: self.proposer_address.as_bytes().to_vec(), + } + } + + /// Attempts to transform the sequencer block header from its raw representation. + /// + /// # Errors + /// + /// - If the `cometbft_header` field is not set. + /// - If the `cometbft_header` field cannot be converted. + /// - If the `rollup_transactions_root` field is not 32 bytes long. + pub fn try_from_raw(raw: raw::SequencerBlockHeader) -> Result { + let raw::SequencerBlockHeader { + chain_id, + height, + time, + rollup_transactions_root, + data_hash, + proposer_address, + .. + } = raw; + + let chain_id = tendermint::chain::Id::try_from(chain_id) + .map_err(SequencerBlockHeaderError::invalid_chain_id)?; + + let height = tendermint::block::Height::try_from(height) + .map_err(SequencerBlockHeaderError::invalid_height)?; + + let Some(time) = time else { + return Err(SequencerBlockHeaderError::field_not_set("time")); + }; + let time = Time::try_from(tendermint_proto::google::protobuf::Timestamp { + seconds: time.seconds, + nanos: time.nanos, + }) + .map_err(SequencerBlockHeaderError::time)?; + + let rollup_transactions_root = + rollup_transactions_root.try_into().map_err(|e: Vec<_>| { + SequencerBlockHeaderError::incorrect_rollup_transactions_root_length(e.len()) + })?; + + let data_hash = data_hash.try_into().map_err(|e: Vec<_>| { + SequencerBlockHeaderError::incorrect_rollup_transactions_root_length(e.len()) + })?; + + let proposer_address = account::Id::try_from(proposer_address) + .map_err(SequencerBlockHeaderError::proposer_address)?; + + Ok(Self { + chain_id, + height, + time, + rollup_transactions_root, + data_hash, + proposer_address, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct SequencerBlockHeaderError(SequencerBlockHeaderErrorKind); + +impl SequencerBlockHeaderError { + fn invalid_chain_id(source: tendermint::Error) -> Self { + Self(SequencerBlockHeaderErrorKind::InvalidChainId(source)) + } + + fn invalid_height(source: tendermint::Error) -> Self { + Self(SequencerBlockHeaderErrorKind::InvalidHeight(source)) + } + + fn field_not_set(field: &'static str) -> Self { + Self(SequencerBlockHeaderErrorKind::FieldNotSet(field)) + } + + fn time(source: tendermint::Error) -> Self { + Self(SequencerBlockHeaderErrorKind::Time(source)) + } + + fn incorrect_rollup_transactions_root_length(len: usize) -> Self { + Self(SequencerBlockHeaderErrorKind::IncorrectRollupTransactionsRootLength(len)) + } + + fn proposer_address(source: tendermint::Error) -> Self { + Self(SequencerBlockHeaderErrorKind::ProposerAddress(source)) + } +} + +#[derive(Debug, thiserror::Error)] +enum SequencerBlockHeaderErrorKind { + #[error("the chain ID in the raw protobuf sequencer block header was invalid")] + InvalidChainId(#[source] tendermint::Error), + #[error("the height in the raw protobuf sequencer block header was invalid")] + InvalidHeight(#[source] tendermint::Error), + #[error("the expected field in the raw source type was not set: `{0}`")] + FieldNotSet(&'static str), + #[error("failed to create a tendermint time from the raw protobuf time")] + Time(#[source] tendermint::Error), + #[error( + "the rollup transaction root in the cometbft block.data field was expected to be 32 bytes \ + long, but was actually `{0}`" + )] + IncorrectRollupTransactionsRootLength(usize), + #[error( + "the proposer address in the raw protobuf sequencer block header was not 20 bytes long" + )] + ProposerAddress(#[source] tendermint::Error), +} + +/// The individual parts that make up a [`SequencerBlock`]. +/// +/// Exists to provide convenient access to fields of a [`SequencerBlock`]. +#[derive(Clone, Debug, PartialEq)] +#[allow(clippy::module_name_repetitions)] +pub struct SequencerBlockParts { + pub block_hash: [u8; 32], + pub header: SequencerBlockHeader, + pub rollup_transactions: IndexMap, + pub rollup_transactions_proof: merkle::Proof, + pub rollup_ids_proof: merkle::Proof, +} + +/// `SequencerBlock` is constructed from a tendermint/cometbft block by +/// converting its opaque `data` bytes into sequencer specific types. +#[derive(Clone, Debug, PartialEq)] +#[allow(clippy::module_name_repetitions)] +pub struct SequencerBlock { + /// The result of hashing the cometbft header. Guaranteed to not be `None` as compared to + /// the cometbft/tendermint-rs return type. + block_hash: [u8; 32], + /// the block header, which contains the cometbft header and additional sequencer-specific + /// commitments. + header: SequencerBlockHeader, + /// The collection of rollup transactions that were included in this block. + rollup_transactions: IndexMap, + // The proof that the rollup transactions are included in the `CometBFT` block this + // sequencer block is derived form. This proof together with + // `Sha256(MTH(rollup_transactions))` must match `header.data_hash`. + // `MTH(rollup_transactions)` is the Merkle Tree Hash derived from the + // rollup transactions. + rollup_transactions_proof: merkle::Proof, + // The proof that the rollup IDs listed in `rollup_transactions` are included + // in the `CometBFT` block this sequencer block is derived form. This proof together + // with `Sha256(MTH(rollup_ids))` must match `header.data_hash`. + // `MTH(rollup_ids)` is the Merkle Tree Hash derived from the rollup IDs listed in + // the rollup transactions. + rollup_ids_proof: merkle::Proof, +} + +impl SequencerBlock { + /// Returns the hash of the `CometBFT` block this sequencer block is derived from. + /// + /// This is done by hashing the `CometBFT` header stored in this block. + #[must_use] + pub fn block_hash(&self) -> [u8; 32] { + self.block_hash + } + + #[must_use] + pub fn header(&self) -> &SequencerBlockHeader { + &self.header + } + + /// The height stored in this sequencer block. + #[must_use] + pub fn height(&self) -> tendermint::block::Height { + self.header.height + } + + #[must_use] + pub fn rollup_transactions(&self) -> &IndexMap { + &self.rollup_transactions + } + + /// Converst a [`SequencerBlock`] into its [`SequencerBlockParts`]. + #[must_use] + pub fn into_parts(self) -> SequencerBlockParts { + let Self { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + } = self; + SequencerBlockParts { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + } + } + + /// Returns the map of rollup transactions, consuming `self`. + #[must_use] + pub fn into_rollup_transactions(self) -> IndexMap { + self.rollup_transactions + } + + #[must_use] + pub fn into_raw(self) -> raw::SequencerBlock { + let Self { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + } = self; + raw::SequencerBlock { + block_hash: block_hash.to_vec(), + header: Some(header.into_raw()), + rollup_transactions: rollup_transactions + .into_values() + .map(RollupTransactions::into_raw) + .collect(), + rollup_transactions_proof: Some(rollup_transactions_proof.into_raw()), + rollup_ids_proof: Some(rollup_ids_proof.into_raw()), + } + } + + #[must_use] + pub fn into_filtered_block(mut self, rollup_ids: I) -> FilteredSequencerBlock + where + I: IntoIterator, + RollupId: From, + { + let all_rollup_ids: Vec = self.rollup_transactions.keys().copied().collect(); + + let mut filtered_rollup_transactions = IndexMap::new(); + for id in rollup_ids { + let id = id.into(); + if let Some(rollup_transactions) = self.rollup_transactions.shift_remove(&id) { + filtered_rollup_transactions.insert(id, rollup_transactions); + }; + } + + FilteredSequencerBlock { + block_hash: self.block_hash, + header: self.header, + rollup_transactions: filtered_rollup_transactions, + rollup_transactions_proof: self.rollup_transactions_proof, + all_rollup_ids, + rollup_ids_proof: self.rollup_ids_proof, + } + } + + #[must_use] + pub fn to_filtered_block(&self, rollup_ids: I) -> FilteredSequencerBlock + where + I: IntoIterator, + RollupId: From, + { + let all_rollup_ids: Vec = self.rollup_transactions.keys().copied().collect(); + + let mut filtered_rollup_transactions = IndexMap::new(); + for id in rollup_ids { + let id = id.into(); + if let Some(rollup_transactions) = self.rollup_transactions.get(&id).cloned() { + filtered_rollup_transactions.insert(id, rollup_transactions); + }; + } + + FilteredSequencerBlock { + block_hash: self.block_hash, + header: self.header.clone(), + rollup_transactions: filtered_rollup_transactions, + rollup_transactions_proof: self.rollup_transactions_proof.clone(), + all_rollup_ids, + rollup_ids_proof: self.rollup_ids_proof.clone(), + } + } + + /// Turn the sequencer block into a [`CelestiaSequencerBlob`] and its associated list of + /// [`CelestiaRollupBlob`]s. + #[must_use] + pub fn into_celestia_blobs(self) -> (CelestiaSequencerBlob, Vec) { + celestia::CelestiaBlobBundle::from_sequencer_block(self).into_parts() + } + + /// Converts from a [`tendermint::Block`]. + /// + /// # Errors + /// TODO(https://github.com/astriaorg/astria/issues/612) + #[allow(clippy::missing_panics_doc)] // the panic sources are checked before hand; revisit if refactoring + pub fn try_from_cometbft(block: tendermint::Block) -> Result { + let tendermint::Block { + header, + data, + .. + } = block; + + // TODO: see https://github.com/astriaorg/astria/issues/774#issuecomment-1981584681 + // deposits are not included in a block pulled from cometbft, so they don't match what's + // stored in the sequencer any more. + // this function can be removed after relayer/conductor are updated to use the sequencer + // API. + Self::try_from_cometbft_header_and_data(header, data, HashMap::new()) + } + + /// Converts from a [`tendermint::block::Header`] and the block data. + /// + /// # Errors + /// TODO(https://github.com/astriaorg/astria/issues/612) + /// + /// # Panics + /// + /// - if a rollup data merkle proof cannot be constructed. + pub fn try_from_cometbft_header_and_data( + cometbft_header: tendermint::block::Header, + data: Vec>, + deposits: HashMap>, + ) -> Result { + let Some(tendermint::Hash::Sha256(data_hash)) = cometbft_header.data_hash else { + // header.data_hash is Option and Hash itself has + // variants Sha256([u8; 32]) or None. + return Err(SequencerBlockError::field_not_set("header.data_hash")); + }; + + let tendermint::Hash::Sha256(block_hash) = cometbft_header.hash() else { + return Err(SequencerBlockError::comet_bft_block_hash_is_none()); + }; + + let tree = merkle_tree_from_data(&data); + if tree.root() != data_hash { + return Err(SequencerBlockError::comet_bft_data_hash_does_not_match_reconstructed()); + } + + Self::try_from_block_info_and_data( + block_hash, + cometbft_header.chain_id, + cometbft_header.height, + cometbft_header.time, + cometbft_header.proposer_address, + data, + deposits, + ) + } + + /// Converts from relevant header fields and the block data. + /// + /// # Errors + /// TODO(https://github.com/astriaorg/astria/issues/612) + /// + /// # Panics + /// + /// - if a rollup data merkle proof cannot be constructed. + pub fn try_from_block_info_and_data( + block_hash: [u8; 32], + chain_id: tendermint::chain::Id, + height: tendermint::block::Height, + time: Time, + proposer_address: account::Id, + data: Vec>, + deposits: HashMap>, + ) -> Result { + use prost::Message as _; + + let tree = merkle_tree_from_data(&data); + let data_hash = tree.root(); + + let mut data_list = data.into_iter(); + let rollup_transactions_root: [u8; 32] = data_list + .next() + .ok_or(SequencerBlockError::no_rollup_transactions_root())? + .try_into() + .map_err(|e: Vec<_>| { + SequencerBlockError::incorrect_rollup_transactions_root_length(e.len()) + })?; + + let rollup_ids_root: [u8; 32] = data_list + .next() + .ok_or(SequencerBlockError::no_rollup_ids_root())? + .try_into() + .map_err(|e: Vec<_>| SequencerBlockError::incorrect_rollup_ids_root_length(e.len()))?; + + let mut rollup_datas = IndexMap::new(); + for elem in data_list { + let raw_tx = crate::generated::sequencer::v1::SignedTransaction::decode(&*elem) + .map_err(SequencerBlockError::signed_transaction_protobuf_decode)?; + let signed_tx = SignedTransaction::try_from_raw(raw_tx) + .map_err(SequencerBlockError::raw_signed_transaction_conversion)?; + for action in signed_tx.into_unsigned().actions { + if let action::Action::Sequence(action::SequenceAction { + rollup_id, + data, + fee_asset_id: _, + }) = action + { + let elem = rollup_datas.entry(rollup_id).or_insert(vec![]); + let data = RollupData::SequencedData(data).into_raw().encode_to_vec(); + elem.push(data); + } + } + } + for (id, deposits) in deposits { + rollup_datas.entry(id).or_default().extend( + deposits + .into_iter() + .map(|deposit| RollupData::Deposit(deposit).into_raw().encode_to_vec()), + ); + } + + // XXX: The rollup data must be sorted by its keys before constructing the merkle tree. + // Since it's constructed from non-deterministically ordered sources, there is otherwise no + // guarantee that the same data will give the root. + rollup_datas.sort_unstable_keys(); + + // ensure the rollup IDs commitment matches the one calculated from the rollup data + if rollup_ids_root != merkle::Tree::from_leaves(rollup_datas.keys()).root() { + return Err(SequencerBlockError::rollup_ids_root_does_not_match_reconstructed()); + } + + let rollup_transaction_tree = derive_merkle_tree_from_rollup_txs(&rollup_datas); + if rollup_transactions_root != rollup_transaction_tree.root() { + return Err( + SequencerBlockError::rollup_transactions_root_does_not_match_reconstructed(), + ); + } + + let mut rollup_transactions = IndexMap::new(); + for (i, (rollup_id, data)) in rollup_datas.into_iter().enumerate() { + let proof = rollup_transaction_tree + .construct_proof(i) + .expect("the proof must exist because the tree was derived with the same leaf"); + rollup_transactions.insert( + rollup_id, + RollupTransactions { + rollup_id, + transactions: data, // TODO: rename this field? + proof, + }, + ); + } + rollup_transactions.sort_unstable_keys(); + + // action tree root is always the first tx in a block + let rollup_transactions_proof = tree.construct_proof(0).expect( + "the tree has at least one leaf; if this line is reached and `construct_proof` \ + returns None it means that the short circuiting checks above it have been removed", + ); + + let rollup_ids_proof = tree.construct_proof(1).expect( + "the tree has at least two leaves; if this line is reached and `construct_proof` \ + returns None it means that the short circuiting checks above it have been removed", + ); + + Ok(Self { + block_hash, + header: SequencerBlockHeader { + chain_id, + height, + time, + rollup_transactions_root, + data_hash, + proposer_address, + }, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + }) + } + + /// 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::SequencerBlock) -> Result { + use sha2::Digest as _; + + fn rollup_txs_to_tuple( + raw: raw::RollupTransactions, + ) -> Result<(RollupId, RollupTransactions), RollupTransactionsError> { + let rollup_transactions = RollupTransactions::try_from_raw(raw)?; + Ok((rollup_transactions.rollup_id, rollup_transactions)) + } + + let raw::SequencerBlock { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + } = raw; + + let block_hash = block_hash + .try_into() + .map_err(|e: Vec<_>| SequencerBlockError::invalid_block_hash(e.len()))?; + + let rollup_transactions_proof = 'proof: { + let Some(rollup_transactions_proof) = rollup_transactions_proof else { + break 'proof Err(SequencerBlockError::field_not_set( + "rollup_transactions_proof", + )); + }; + merkle::Proof::try_from_raw(rollup_transactions_proof) + .map_err(SequencerBlockError::transaction_proof_invalid) + }?; + let rollup_ids_proof = 'proof: { + let Some(rollup_ids_proof) = rollup_ids_proof else { + break 'proof Err(SequencerBlockError::field_not_set("rollup_ids_proof")); + }; + merkle::Proof::try_from_raw(rollup_ids_proof) + .map_err(SequencerBlockError::id_proof_invalid) + }?; + let header = 'header: { + let Some(header) = header else { + break 'header Err(SequencerBlockError::field_not_set("header")); + }; + SequencerBlockHeader::try_from_raw(header).map_err(SequencerBlockError::header) + }?; + + let rollup_transactions: IndexMap = rollup_transactions + .into_iter() + .map(rollup_txs_to_tuple) + .collect::>() + .map_err(SequencerBlockError::parse_rollup_transactions)?; + + let data_hash = header.data_hash; + + if !rollup_transactions_proof + .verify(&Sha256::digest(header.rollup_transactions_root), data_hash) + { + return Err(SequencerBlockError::invalid_rollup_transactions_root()); + }; + + let rollup_ids_root = merkle::Tree::from_leaves(rollup_transactions.keys()).root(); + if !rollup_ids_proof.verify(&Sha256::digest(rollup_ids_root), data_hash) { + return Err(SequencerBlockError::invalid_rollup_ids_proof()); + }; + + if !are_rollup_txs_included(&rollup_transactions, &rollup_transactions_proof, data_hash) { + return Err(SequencerBlockError::rollup_transactions_not_in_sequencer_block()); + } + if !are_rollup_ids_included( + rollup_transactions.keys().copied(), + &rollup_ids_proof, + data_hash, + ) { + return Err(SequencerBlockError::rollup_ids_not_in_sequencer_block()); + } + + Ok(Self { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + }) + } +} + +/// Constructs a `[merkle::Tree]` from an iterator yielding byte slices. +/// +/// This hashes each item before pushing it into the Merkle Tree, which +/// effectively causes a double hashing. The leaf hash of an item `d_i` +/// is then `MTH(d_i) = SHA256(0x00 || SHA256(d_i))`. +pub fn merkle_tree_from_data(iter: I) -> merkle::Tree +where + I: IntoIterator, + B: AsRef<[u8]>, +{ + use sha2::Digest as _; + merkle::Tree::from_leaves(iter.into_iter().map(|item| Sha256::digest(&item))) +} + +/// The individual parts that make up a [`FilteredSequencerBlock`]. +/// +/// Exists to provide convenient access to fields of a [`FilteredSequencerBlock`]. +#[derive(Debug, Clone, PartialEq)] +#[allow(clippy::module_name_repetitions)] +pub struct FilteredSequencerBlockParts { + pub block_hash: [u8; 32], + pub header: SequencerBlockHeader, + // filtered set of rollup transactions + pub rollup_transactions: IndexMap, + // proof that `rollup_transactions_root` is included in `data_hash` + pub rollup_transactions_proof: merkle::Proof, + // all rollup ids in the sequencer block + pub all_rollup_ids: Vec, + // proof that `rollup_ids` is included in `data_hash` + pub rollup_ids_proof: merkle::Proof, +} + +#[derive(Debug, Clone, PartialEq)] +#[allow(clippy::module_name_repetitions)] +pub struct FilteredSequencerBlock { + block_hash: [u8; 32], + header: SequencerBlockHeader, + // filtered set of rollup transactions + rollup_transactions: IndexMap, + // proof that `rollup_transactions_root` is included in `data_hash` + rollup_transactions_proof: merkle::Proof, + // all rollup ids in the sequencer block + all_rollup_ids: Vec, + // proof that `rollup_ids` is included in `data_hash` + rollup_ids_proof: merkle::Proof, +} + +impl FilteredSequencerBlock { + #[must_use] + pub fn block_hash(&self) -> [u8; 32] { + self.block_hash + } + + #[must_use] + pub fn header(&self) -> &SequencerBlockHeader { + &self.header + } + + #[must_use] + pub fn height(&self) -> tendermint::block::Height { + self.header.height + } + + #[must_use] + pub fn rollup_transactions(&self) -> &IndexMap { + &self.rollup_transactions + } + + #[must_use] + pub fn rollup_transactions_root(&self) -> [u8; 32] { + self.header.rollup_transactions_root + } + + #[must_use] + pub fn rollup_transactions_proof(&self) -> &merkle::Proof { + &self.rollup_transactions_proof + } + + #[must_use] + pub fn all_rollup_ids(&self) -> &[RollupId] { + &self.all_rollup_ids + } + + #[must_use] + pub fn rollup_ids_proof(&self) -> &merkle::Proof { + &self.rollup_ids_proof + } + + #[must_use] + pub fn into_raw(self) -> raw::FilteredSequencerBlock { + let Self { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + .. + } = self; + raw::FilteredSequencerBlock { + block_hash: block_hash.to_vec(), + header: Some(header.into_raw()), + rollup_transactions: rollup_transactions + .into_values() + .map(RollupTransactions::into_raw) + .collect(), + rollup_transactions_proof: Some(rollup_transactions_proof.into_raw()), + all_rollup_ids: self.all_rollup_ids.iter().map(|id| id.to_vec()).collect(), + rollup_ids_proof: Some(rollup_ids_proof.into_raw()), + } + } + + /// Converts from the raw decoded protobuf representation of this type. + /// + /// # Errors + /// + /// - if the rollup transactions proof is not set + /// - if the rollup IDs proof is not set + /// - if the rollup transactions proof cannot be constructed from the raw protobuf + /// - if the rollup IDs proof cannot be constructed from the raw protobuf + /// - if the cometbft header is not set + /// - if the cometbft header cannot be constructed from the raw protobuf + /// - if the cometbft block hash is None + /// - if the data hash is None + /// - if the rollup transactions cannot be parsed + /// - if the rollup transactions root is not 32 bytes + /// - if the rollup transactions are not included in the sequencer block + /// - if the rollup IDs root is not 32 bytes + /// - if the rollup IDs are not included in the sequencer block + pub fn try_from_raw( + raw: raw::FilteredSequencerBlock, + ) -> Result { + use sha2::Digest as _; + + fn rollup_txs_to_tuple( + raw: raw::RollupTransactions, + ) -> Result<(RollupId, RollupTransactions), RollupTransactionsError> { + let rollup_transactions = RollupTransactions::try_from_raw(raw)?; + Ok((rollup_transactions.rollup_id, rollup_transactions)) + } + + let raw::FilteredSequencerBlock { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + all_rollup_ids, + rollup_ids_proof, + .. + } = raw; + + let block_hash = block_hash + .try_into() + .map_err(|e: Vec<_>| FilteredSequencerBlockError::invalid_block_hash(e.len()))?; + + let rollup_transactions_proof = { + let Some(rollup_transactions_proof) = rollup_transactions_proof else { + return Err(FilteredSequencerBlockError::field_not_set( + "rollup_transactions_proof", + )); + }; + merkle::Proof::try_from_raw(rollup_transactions_proof) + .map_err(FilteredSequencerBlockError::transaction_proof_invalid) + }?; + let rollup_ids_proof = { + let Some(rollup_ids_proof) = rollup_ids_proof else { + return Err(FilteredSequencerBlockError::field_not_set( + "rollup_ids_proof", + )); + }; + merkle::Proof::try_from_raw(rollup_ids_proof) + .map_err(FilteredSequencerBlockError::id_proof_invalid) + }?; + let header = { + let Some(header) = header else { + return Err(FilteredSequencerBlockError::field_not_set("header")); + }; + SequencerBlockHeader::try_from_raw(header) + .map_err(FilteredSequencerBlockError::invalid_header) + }?; + + // XXX: These rollup transactions are not sorted compared to those used for + // deriving the rollup transactions merkle tree in `SequencerBlock`. + let rollup_transactions = rollup_transactions + .into_iter() + .map(rollup_txs_to_tuple) + .collect::, _>>() + .map_err(FilteredSequencerBlockError::parse_rollup_transactions)?; + + let all_rollup_ids: Vec = all_rollup_ids + .into_iter() + .map(RollupId::try_from_vec) + .collect::>() + .map_err(FilteredSequencerBlockError::invalid_rollup_id)?; + + if !rollup_transactions_proof.verify( + &Sha256::digest(header.rollup_transactions_root), + header.data_hash, + ) { + return Err(FilteredSequencerBlockError::rollup_transactions_not_in_sequencer_block()); + } + + for rollup_transactions in rollup_transactions.values() { + if !super::do_rollup_transaction_match_root( + rollup_transactions, + header.rollup_transactions_root, + ) { + return Err( + FilteredSequencerBlockError::rollup_transaction_for_id_not_in_sequencer_block( + rollup_transactions.rollup_id(), + ), + ); + } + } + + if !are_rollup_ids_included( + all_rollup_ids.iter().copied(), + &rollup_ids_proof, + header.data_hash, + ) { + return Err(FilteredSequencerBlockError::rollup_ids_not_in_sequencer_block()); + } + + Ok(Self { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + all_rollup_ids, + rollup_ids_proof, + }) + } + + /// Transforms the filtered blocks into its constitutent parts. + #[must_use] + pub fn into_parts(self) -> FilteredSequencerBlockParts { + let Self { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + all_rollup_ids, + rollup_ids_proof, + } = self; + FilteredSequencerBlockParts { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + all_rollup_ids, + rollup_ids_proof, + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct FilteredSequencerBlockError(FilteredSequencerBlockErrorKind); + +#[derive(Debug, thiserror::Error)] +enum FilteredSequencerBlockErrorKind { + #[error( + "the block hash in the raw protobuf filtered sequencer block was expected to be 32 bytes \ + long, but was actually `{0}`" + )] + InvalidBlockHash(usize), + #[error("failed to create a sequencer block header from the raw protobuf header")] + InvalidHeader(SequencerBlockHeaderError), + #[error("the rollup ID in the raw protobuf rollup transaction was not 32 bytes long")] + InvalidRollupId(IncorrectRollupIdLength), + #[error("the expected field in the raw source type was not set: `{0}`")] + FieldNotSet(&'static str), + #[error("failed parsing a raw protobuf rollup transaction")] + ParseRollupTransactions(RollupTransactionsError), + #[error( + "the rollup transactions in the sequencer block were not included in the block's data hash" + )] + RollupTransactionsNotInSequencerBlock, + #[error( + "the rollup transaction for rollup ID `{id}` contained in the filtered sequencer block \ + could not be verified against the rollup transactions root" + )] + RollupTransactionForIdNotInSequencerBlock { id: RollupId }, + #[error("the rollup IDs in the sequencer block were not included in the block's data hash")] + RollupIdsNotInSequencerBlock, + #[error("failed constructing a transaction proof from the raw protobuf transaction proof")] + TransactionProofInvalid(merkle::audit::InvalidProof), + #[error("failed constructing a rollup ID proof from the raw protobuf rollup ID proof")] + IdProofInvalid(merkle::audit::InvalidProof), +} + +impl FilteredSequencerBlockError { + fn invalid_block_hash(len: usize) -> Self { + Self(FilteredSequencerBlockErrorKind::InvalidBlockHash(len)) + } + + fn invalid_header(source: SequencerBlockHeaderError) -> Self { + Self(FilteredSequencerBlockErrorKind::InvalidHeader(source)) + } + + fn invalid_rollup_id(source: IncorrectRollupIdLength) -> Self { + Self(FilteredSequencerBlockErrorKind::InvalidRollupId(source)) + } + + fn field_not_set(field: &'static str) -> Self { + Self(FilteredSequencerBlockErrorKind::FieldNotSet(field)) + } + + fn parse_rollup_transactions(source: RollupTransactionsError) -> Self { + Self(FilteredSequencerBlockErrorKind::ParseRollupTransactions( + source, + )) + } + + fn rollup_transactions_not_in_sequencer_block() -> Self { + Self(FilteredSequencerBlockErrorKind::RollupTransactionsNotInSequencerBlock) + } + + fn rollup_transaction_for_id_not_in_sequencer_block(id: RollupId) -> Self { + Self( + FilteredSequencerBlockErrorKind::RollupTransactionForIdNotInSequencerBlock { + id, + }, + ) + } + + fn rollup_ids_not_in_sequencer_block() -> Self { + Self(FilteredSequencerBlockErrorKind::RollupIdsNotInSequencerBlock) + } + + fn transaction_proof_invalid(source: merkle::audit::InvalidProof) -> Self { + Self(FilteredSequencerBlockErrorKind::TransactionProofInvalid( + source, + )) + } + + fn id_proof_invalid(source: merkle::audit::InvalidProof) -> Self { + Self(FilteredSequencerBlockErrorKind::IdProofInvalid(source)) + } +} + +/// [`Deposit`] represents a deposit from the sequencer to a rollup. +/// +/// A [`Deposit`] is constructed whenever a [`BridgeLockAction`] is executed +/// and stored as part of the block's events. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Deposit { + // the address on the sequencer to which the funds were sent to. + bridge_address: Address, + // the rollup ID registered to the `bridge_address` + rollup_id: RollupId, + // the amount that was transferred to `bridge_address` + amount: u128, + // the asset ID of the asset that was transferred + asset_id: asset::Id, + // the address on the destination chain (rollup) which to send the bridged funds to + destination_chain_address: String, +} + +impl Deposit { + #[must_use] + pub fn new( + bridge_address: Address, + rollup_id: RollupId, + amount: u128, + asset_id: asset::Id, + destination_chain_address: String, + ) -> Self { + Self { + bridge_address, + rollup_id, + amount, + asset_id, + destination_chain_address, + } + } + + #[must_use] + pub fn bridge_address(&self) -> &Address { + &self.bridge_address + } + + #[must_use] + pub fn rollup_id(&self) -> &RollupId { + &self.rollup_id + } + + #[must_use] + pub fn amount(&self) -> u128 { + self.amount + } + + #[must_use] + pub fn asset_id(&self) -> &asset::Id { + &self.asset_id + } + + #[must_use] + pub fn destination_chain_address(&self) -> &str { + &self.destination_chain_address + } + + #[must_use] + pub fn into_raw(self) -> raw::Deposit { + let Self { + bridge_address, + rollup_id, + amount, + asset_id, + destination_chain_address, + } = self; + raw::Deposit { + bridge_address: bridge_address.to_vec(), + rollup_id: rollup_id.to_vec(), + amount: Some(amount.into()), + asset_id: asset_id.get().to_vec(), + destination_chain_address, + } + } + + /// Attempts to transform the deposit from its raw representation. + /// + /// # Errors + /// + /// - if the bridge address is invalid + /// - if the amount is unset + /// - if the rollup ID is invalid + /// - if the asset ID is invalid + pub fn try_from_raw(raw: raw::Deposit) -> Result { + let raw::Deposit { + bridge_address, + rollup_id, + amount, + asset_id, + destination_chain_address, + } = raw; + let bridge_address = Address::try_from_slice(&bridge_address) + .map_err(DepositError::incorrect_address_length)?; + let amount = amount.ok_or(DepositError::field_not_set("amount"))?.into(); + let rollup_id = RollupId::try_from_slice(&rollup_id) + .map_err(DepositError::incorrect_rollup_id_length)?; + let asset_id = asset::Id::try_from_slice(&asset_id) + .map_err(DepositError::incorrect_asset_id_length)?; + Ok(Self { + bridge_address, + rollup_id, + amount, + asset_id, + destination_chain_address, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct DepositError(DepositErrorKind); + +impl DepositError { + fn incorrect_address_length(source: IncorrectAddressLength) -> Self { + Self(DepositErrorKind::IncorrectAddressLength(source)) + } + + fn field_not_set(field: &'static str) -> Self { + Self(DepositErrorKind::FieldNotSet(field)) + } + + fn incorrect_rollup_id_length(source: IncorrectRollupIdLength) -> Self { + Self(DepositErrorKind::IncorrectRollupIdLength(source)) + } + + fn incorrect_asset_id_length(source: asset::IncorrectAssetIdLength) -> Self { + Self(DepositErrorKind::IncorrectAssetIdLength(source)) + } +} + +#[derive(Debug, thiserror::Error)] +enum DepositErrorKind { + #[error("the address length is not 20 bytes")] + IncorrectAddressLength(#[source] IncorrectAddressLength), + #[error("the expected field in the raw source type was not set: `{0}`")] + FieldNotSet(&'static str), + #[error("the rollup ID length is not 32 bytes")] + IncorrectRollupIdLength(#[source] IncorrectRollupIdLength), + #[error("the asset ID length is not 32 bytes")] + IncorrectAssetIdLength(#[source] asset::IncorrectAssetIdLength), +} + +/// A piece of data that is sent to a rollup execution node. +/// +/// The data can be either sequenced data (originating from a [`SequenceAction`] +/// submitted by a user) or a [`Deposit`] originating from a [`BridgeLockAction`]. +/// +/// The rollup node receives this type as opaque, protobuf-encoded bytes from conductor, +/// and must decode it accordingly. +#[derive(Debug, Clone, PartialEq)] +pub enum RollupData { + SequencedData(Vec), + Deposit(Deposit), +} + +impl RollupData { + #[must_use] + pub fn into_raw(self) -> raw::RollupData { + match self { + Self::SequencedData(data) => raw::RollupData { + value: Some(raw::rollup_data::Value::SequencedData(data)), + }, + Self::Deposit(deposit) => raw::RollupData { + value: Some(raw::rollup_data::Value::Deposit(deposit.into_raw())), + }, + } + } + + /// Attempts to transform the `RollupData` from its raw representation. + /// + /// # Errors + /// + /// - if the `data` field is not set + /// - if the variant is `Deposit` but a `Deposit` cannot be constructed from the raw proto + pub fn try_from_raw(raw: raw::RollupData) -> Result { + let raw::RollupData { + value, + } = raw; + match value { + Some(raw::rollup_data::Value::SequencedData(data)) => Ok(Self::SequencedData(data)), + Some(raw::rollup_data::Value::Deposit(deposit)) => Deposit::try_from_raw(deposit) + .map(Self::Deposit) + .map_err(RollupDataError::deposit), + None => Err(RollupDataError::field_not_set("data")), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct RollupDataError(RollupDataErrorKind); + +impl RollupDataError { + fn field_not_set(field: &'static str) -> Self { + Self(RollupDataErrorKind::FieldNotSet(field)) + } + + fn deposit(source: DepositError) -> Self { + Self(RollupDataErrorKind::Deposit(source)) + } +} + +#[derive(Debug, thiserror::Error)] +enum RollupDataErrorKind { + #[error("the expected field in the raw source type was not set: `{0}`")] + FieldNotSet(&'static str), + #[error("failed to validate `deposit` field")] + Deposit(#[source] DepositError), +} diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs b/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs new file mode 100644 index 0000000000..1b0a540072 --- /dev/null +++ b/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs @@ -0,0 +1,614 @@ +use sha2::{ + Digest as _, + Sha256, +}; + +use super::{ + block::{ + RollupTransactionsParts, + SequencerBlock, + SequencerBlockHeader, + SequencerBlockHeaderError, + }, + raw, + RollupId, +}; +use crate::{ + sequencer::v1::IncorrectRollupIdLength, + sequencerblock::Protobuf, +}; + +/// A bundle of blobs constructed from a [`super::SequencerBlock`]. +/// +/// 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, +} + +impl CelestiaBlobBundle { + /// Construct a bundle of celestia blobs from a [`super::SequencerBlock`]. + #[must_use] + pub(super) fn from_sequencer_block(block: SequencerBlock) -> Self { + let super::block::SequencerBlockParts { + block_hash, + header, + rollup_transactions, + rollup_transactions_proof, + rollup_ids_proof, + } = block.into_parts(); + + let head = CelestiaSequencerBlob { + block_hash, + header, + rollup_ids: rollup_transactions.keys().copied().collect(), + rollup_transactions_proof, + rollup_ids_proof, + }; + + let mut tail = Vec::with_capacity(rollup_transactions.len()); + for (rollup_id, rollup_txs) in rollup_transactions { + let RollupTransactionsParts { + transactions, + proof, + .. + } = rollup_txs.into_parts(); + tail.push(CelestiaRollupBlob { + sequencer_block_hash: block_hash, + rollup_id, + transactions, + proof, + }); + } + Self { + head, + tail, + } + } + + /// Returns the head and the tail of the celestia blob bundle, consuming it. + pub(super) fn into_parts(self) -> (CelestiaSequencerBlob, Vec) { + (self.head, self.tail) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed constructing a celestia rollup blob")] +#[allow(clippy::module_name_repetitions)] +pub struct CelestiaRollupBlobError { + #[source] + kind: CelestiaRollupBlobErrorKind, +} + +impl CelestiaRollupBlobError { + fn field_not_set(field: &'static str) -> Self { + Self { + kind: CelestiaRollupBlobErrorKind::FieldNotSet { + field, + }, + } + } + + fn rollup_id(source: IncorrectRollupIdLength) -> Self { + Self { + kind: CelestiaRollupBlobErrorKind::RollupId { + source, + }, + } + } + + fn proof(source: ::Error) -> Self { + Self { + kind: CelestiaRollupBlobErrorKind::Proof { + source, + }, + } + } + + fn sequencer_block_hash(actual_len: usize) -> Self { + Self { + kind: CelestiaRollupBlobErrorKind::SequencerBlockHash(actual_len), + } + } +} + +#[derive(Debug, thiserror::Error)] +enum CelestiaRollupBlobErrorKind { + #[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")] + RollupId { source: IncorrectRollupIdLength }, + #[error("failed constructing a Merkle Hash Tree Proof from the provided raw protobf type")] + Proof { + source: ::Error, + }, + #[error( + "the provided bytes were too short for a sequencer block hash. Expected: 32 bytes, \ + provided: {0}" + )] + SequencerBlockHash(usize), +} + +/// A shadow of [`CelestiaRollupBlob`] with public access to all its fields. +/// +/// At the moment there are no invariants upheld by [`CelestiaRollupBlob`] so +/// they can be converted directly into one another. This can change in the future. +pub struct UncheckedCelestiaRollupBlob { + /// 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 + /// `astria.sequencerblock.v1alpha1.RollupTransactions.rollup_id` + pub rollup_id: RollupId, + /// A list of opaque bytes that are serialized rollup transactions. + pub transactions: Vec>, + /// The proof that these rollup transactions are included in sequencer block. + pub proof: merkle::Proof, +} + +impl UncheckedCelestiaRollupBlob { + #[must_use] + pub fn into_celestia_rollup_blob(self) -> CelestiaRollupBlob { + CelestiaRollupBlob::from_unchecked(self) + } +} + +#[derive(Clone, Debug)] +#[allow(clippy::module_name_repetitions)] +pub struct CelestiaRollupBlob { + /// 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 + /// `astria.sequencerblock.v1alpha1.RollupTransactions.rollup_id` + rollup_id: RollupId, + /// A list of opaque bytes that are serialized rollup transactions. + transactions: Vec>, + /// The proof that these rollup transactions are included in sequencer block. + proof: merkle::Proof, +} + +impl CelestiaRollupBlob { + #[must_use] + pub fn proof(&self) -> &merkle::Proof { + &self.proof + } + + #[must_use] + pub fn transactions(&self) -> &[Vec] { + &self.transactions + } + + #[must_use] + pub fn rollup_id(&self) -> RollupId { + self.rollup_id + } + + #[must_use] + pub fn sequencer_block_hash(&self) -> [u8; 32] { + self.sequencer_block_hash + } + + /// Converts from the unchecked representation of this type (its shadow). + /// + /// 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 { + sequencer_block_hash, + rollup_id, + transactions, + proof, + } = unchecked; + Self { + sequencer_block_hash, + rollup_id, + transactions, + proof, + } + } + + /// Converts to the unchecked representation of this type (its shadow). + /// + /// Useful to get public access to the type's fields. + #[must_use] + pub fn into_unchecked(self) -> UncheckedCelestiaRollupBlob { + let Self { + sequencer_block_hash, + rollup_id, + transactions, + proof, + } = self; + UncheckedCelestiaRollupBlob { + sequencer_block_hash, + rollup_id, + transactions, + proof, + } + } + + /// Converts to the raw decoded protobuf representation of this type. + /// + /// Useful for then encoding it as protobuf. + #[must_use] + pub fn into_raw(self) -> raw::CelestiaRollupBlob { + let Self { + sequencer_block_hash, + rollup_id, + transactions, + proof, + } = self; + raw::CelestiaRollupBlob { + sequencer_block_hash: sequencer_block_hash.to_vec(), + rollup_id: rollup_id.to_vec(), + transactions, + proof: Some(proof.into_raw()), + } + } + + /// 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::CelestiaRollupBlob) -> Result { + let raw::CelestiaRollupBlob { + sequencer_block_hash, + rollup_id, + transactions, + proof, + } = raw; + let rollup_id = + RollupId::try_from_vec(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()))?; + let proof = 'proof: { + let Some(proof) = proof else { + break 'proof Err(CelestiaRollupBlobError::field_not_set("proof")); + }; + merkle::Proof::try_from_raw(proof).map_err(CelestiaRollupBlobError::proof) + }?; + Ok(Self { + sequencer_block_hash, + rollup_id, + transactions, + proof, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed constructing a celestia sequencer blob")] +#[allow(clippy::module_name_repetitions)] +pub struct CelestiaSequencerBlobError { + #[source] + kind: CelestiaSequencerBlobErrorKind, +} + +impl CelestiaSequencerBlobError { + fn block_hash(actual_len: usize) -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::BlockHash(actual_len), + } + } + + fn header(source: SequencerBlockHeaderError) -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::Header { + source, + }, + } + } + + fn field_not_set(field: &'static str) -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::FieldNotSet(field), + } + } + + fn rollup_ids(source: IncorrectRollupIdLength) -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::RollupIds { + source, + }, + } + } + + fn rollup_transactions_proof(source: ::Error) -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::RollupTransactionsProof { + source, + }, + } + } + + fn rollup_ids_proof(source: ::Error) -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::RollupIdsProof { + source, + }, + } + } + + fn rollup_transactions_not_in_cometbft_block() -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::RollupTransactionsNotInCometBftBlock, + } + } + + fn rollup_ids_not_in_cometbft_block() -> Self { + Self { + kind: CelestiaSequencerBlobErrorKind::RollupIdsNotInCometBftBlock, + } + } +} + +#[derive(Debug, thiserror::Error)] +enum CelestiaSequencerBlobErrorKind { + #[error( + "the provided bytes were too short for a block hash; expected: 32 bytes, actual: {0} bytes" + )] + BlockHash(usize), + #[error("failed constructing the sequencer block header from its raw source value")] + Header { source: SequencerBlockHeaderError }, + #[error("the field of the raw source value was not set: `{0}`")] + FieldNotSet(&'static str), + #[error("one of the rollup IDs in the raw source value was invalid")] + RollupIds { source: IncorrectRollupIdLength }, + #[error( + "failed constructing a Merkle Hash Tree Proof for the rollup transactions from the raw \ + raw source type" + )] + RollupTransactionsProof { + source: ::Error, + }, + #[error( + "failed constructing a Merkle Hash Tree Proof for the rollup IDs from the raw raw source \ + type" + )] + RollupIdsProof { + source: ::Error, + }, + #[error( + "the Merkle Tree Hash of the rollup transactions was not a leaf in the sequencer block \ + data" + )] + RollupTransactionsNotInCometBftBlock, + #[error("the Merkle Tree Hash of the rollup IDs was not a leaf in the sequencer block data")] + RollupIdsNotInCometBftBlock, +} + +/// A shadow of [`CelestiaSequencerBlob`] 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 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. + /// Corresponds to the `astria.sequencer.v1.RollupTransactions.id` field + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. + pub rollup_ids: Vec, + /// The proof that the rollup transactions are included in sequencer block. + /// Corresponds to `astria.SequencerBlock.rollup_transactions_proof`. + pub rollup_transactions_proof: merkle::Proof, + /// The proof that this sequencer blob includes all rollup IDs of the original sequencer + /// block it was derived from. This proof together with `Sha256(MHT(rollup_ids))` (Sha256 + /// applied to the Merkle Tree Hash of the rollup ID sequence) must be equal to + /// `header.data_hash` which itself must match + /// `astria.SequencerBlock.header.data_hash`. This field corresponds to + /// `astria.SequencerBlock.rollup_ids_proof`. + pub rollup_ids_proof: merkle::Proof, +} + +impl UncheckedCelestiaSequencerBlob { + /// Converts this unchecked blob into its checked [`CelestiaSequencerBlob`] representation. + /// + /// # Errors + /// TODO(https://github.com/astriaorg/astria/issues/612) + pub fn try_into_celestia_sequencer_blob( + self, + ) -> Result { + CelestiaSequencerBlob::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 { + block_hash, + header, + rollup_ids, + rollup_transactions_proof, + rollup_ids_proof, + .. + } = raw; + let header = 'header: { + let Some(header) = header else { + break 'header Err(CelestiaSequencerBlobError::field_not_set("header")); + }; + SequencerBlockHeader::try_from_raw(header).map_err(CelestiaSequencerBlobError::header) + }?; + let rollup_ids: Vec<_> = rollup_ids + .into_iter() + .map(RollupId::try_from_vec) + .collect::>() + .map_err(CelestiaSequencerBlobError::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( + "rollup_transactions_root", + )); + }; + merkle::Proof::try_from_raw(rollup_transactions_proof) + .map_err(CelestiaSequencerBlobError::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", + )); + }; + merkle::Proof::try_from_raw(rollup_ids_proof) + .map_err(CelestiaSequencerBlobError::rollup_ids_proof) + }?; + + let block_hash = block_hash + .try_into() + .map_err(|bytes: Vec<_>| CelestiaSequencerBlobError::block_hash(bytes.len()))?; + + Ok(Self { + block_hash, + header, + rollup_ids, + rollup_transactions_proof, + rollup_ids_proof, + }) + } +} + +#[derive(Clone, Debug)] +#[allow(clippy::module_name_repetitions)] +pub struct CelestiaSequencerBlob { + /// 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. + /// Corresponds to the `astria.sequencer.v1.RollupTransactions.id` field + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. + rollup_ids: Vec, + /// The proof that the rollup transactions are included in sequencer block. + /// Corresponds to `astria.SequencerBlock.rollup_transactions_proof`. + rollup_transactions_proof: merkle::Proof, + /// The proof that this sequencer blob includes all rollup IDs of the original sequencer + /// block it was derived from. This proof together with `Sha256(MHT(rollup_ids))` (Sha256 + /// applied to the Merkle Tree Hash of the rollup ID sequence) must be equal to + /// `header.data_hash` which itself must match + /// `astria.SequencerBlock.header.data_hash`. This field corresponds to + /// `astria.SequencerBlock.rollup_ids_proof`. + rollup_ids_proof: merkle::Proof, +} + +impl CelestiaSequencerBlob { + /// Returns the block hash of the tendermint header stored in this blob. + #[must_use] + pub fn block_hash(&self) -> [u8; 32] { + self.block_hash + } + + /// Returns the sequencer's `CometBFT` chain ID. + #[must_use] + pub fn cometbft_chain_id(&self) -> &tendermint::chain::Id { + self.header.chain_id() + } + + /// Returns the `CometBFT` height stored in the header of the [`SequencerBlock`] this blob was + /// derived from. + #[must_use] + pub fn height(&self) -> tendermint::block::Height { + self.header.height() + } + + /// Returns the header of the [`SequencerBlock`] this blob was derived from. + #[must_use] + pub fn header(&self) -> &SequencerBlockHeader { + &self.header + } + + /// Converts into the unchecked representation fo this type. + #[must_use] + pub fn into_unchecked(self) -> UncheckedCelestiaSequencerBlob { + let Self { + block_hash, + header, + rollup_ids, + rollup_transactions_proof, + rollup_ids_proof, + } = self; + UncheckedCelestiaSequencerBlob { + block_hash, + header, + rollup_ids, + rollup_transactions_proof, + rollup_ids_proof, + } + } + + /// Converts from the unchecked representation of this type. + /// + /// # Errors + /// TODO(https://github.com/astriaorg/astria/issues/612) + pub fn try_from_unchecked( + unchecked: UncheckedCelestiaSequencerBlob, + ) -> Result { + let UncheckedCelestiaSequencerBlob { + block_hash, + header, + rollup_ids, + rollup_transactions_proof, + rollup_ids_proof, + } = unchecked; + + if !rollup_transactions_proof.verify( + &Sha256::digest(header.rollup_transactions_root()), + header.data_hash(), + ) { + return Err(CelestiaSequencerBlobError::rollup_transactions_not_in_cometbft_block()); + } + + if !super::are_rollup_ids_included( + rollup_ids.iter().copied(), + &rollup_ids_proof, + header.data_hash(), + ) { + return Err(CelestiaSequencerBlobError::rollup_ids_not_in_cometbft_block()); + } + + Ok(Self { + block_hash, + header, + rollup_ids, + rollup_transactions_proof, + rollup_ids_proof, + }) + } + + /// Converts into the raw decoded protobuf representation of this type. + pub fn into_raw(self) -> raw::CelestiaSequencerBlob { + let Self { + block_hash, + header, + rollup_ids, + rollup_transactions_proof, + rollup_ids_proof, + .. + } = self; + raw::CelestiaSequencerBlob { + block_hash: block_hash.to_vec(), + header: Some(header.into_raw()), + rollup_ids: rollup_ids.into_iter().map(RollupId::to_vec).collect(), + rollup_transactions_proof: Some(rollup_transactions_proof.into_raw()), + rollup_ids_proof: Some(rollup_ids_proof.into_raw()), + } + } + + /// 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 { + UncheckedCelestiaSequencerBlob::try_from_raw(raw) + .and_then(UncheckedCelestiaSequencerBlob::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 new file mode 100644 index 0000000000..8ab9863146 --- /dev/null +++ b/crates/astria-core/src/sequencerblock/v1alpha1/mod.rs @@ -0,0 +1,124 @@ +pub mod block; +pub mod celestia; + +pub use block::{ + RollupTransactions, + SequencerBlock, +}; +pub use celestia::{ + CelestiaRollupBlob, + CelestiaSequencerBlob, +}; +use indexmap::IndexMap; +use sha2::{ + Digest as _, + Sha256, +}; + +use crate::{ + generated::sequencerblock::v1alpha1 as raw, + sequencer::v1::{ + derive_merkle_tree_from_rollup_txs, + RollupId, + }, + sequencerblock::Protobuf, +}; + +pub(crate) fn are_rollup_ids_included<'a, TRollupIds: 'a>( + ids: TRollupIds, + proof: &merkle::Proof, + data_hash: [u8; 32], +) -> bool +where + TRollupIds: IntoIterator, +{ + let tree = merkle::Tree::from_leaves(ids); + let hash_of_root = Sha256::digest(tree.root()); + proof.verify(&hash_of_root, data_hash) +} + +pub(crate) fn are_rollup_txs_included( + rollup_datas: &IndexMap, + rollup_proof: &merkle::Proof, + data_hash: [u8; 32], +) -> bool { + let rollup_datas = rollup_datas + .iter() + .map(|(rollup_id, tx_data)| (rollup_id, tx_data.transactions())); + let rollup_tree = derive_merkle_tree_from_rollup_txs(rollup_datas); + let hash_of_rollup_root = Sha256::digest(rollup_tree.root()); + rollup_proof.verify(&hash_of_rollup_root, data_hash) +} + +fn do_rollup_transaction_match_root( + rollup_transactions: &RollupTransactions, + root: [u8; 32], +) -> bool { + let id = rollup_transactions.rollup_id(); + rollup_transactions + .proof() + .audit() + .with_root(root) + .with_leaf_builder() + .write(id.as_ref()) + .write(&merkle::Tree::from_leaves(rollup_transactions.transactions()).root()) + .finish_leaf() + .perform() +} + +impl Protobuf for merkle::Proof { + type Error = merkle::audit::InvalidProof; + type Raw = raw::Proof; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + // XXX: Implementing this by cloning is ok because `audit_path` + // has to be cloned always due to `UncheckedProof`'s constructor. + Self::try_from_raw(raw.clone()) + } + + fn try_from_raw(raw: Self::Raw) -> Result { + let Self::Raw { + audit_path, + leaf_index, + tree_size, + } = raw; + let leaf_index = leaf_index.try_into().expect( + "running on a machine with at least 64 bit pointer width and can convert from u64 to \ + usize", + ); + let tree_size = tree_size.try_into().expect( + "running on a machine with at least 64 bit pointer width and can convert from u64 to \ + usize", + ); + Self::unchecked() + .audit_path(audit_path) + .leaf_index(leaf_index) + .tree_size(tree_size) + .try_into_proof() + } + + fn to_raw(&self) -> Self::Raw { + // XXX: Implementing in terms of clone is ok because the fields would need to be cloned + // anyway. + self.clone().into_raw() + } + + fn into_raw(self) -> Self::Raw { + let merkle::audit::UncheckedProof { + audit_path, + leaf_index, + tree_size, + } = self.into_unchecked(); + Self::Raw { + audit_path, + leaf_index: leaf_index.try_into().expect( + "running on a machine with at most 64 bit pointer width and can convert from \ + usize to u64", + ), + tree_size: tree_size.try_into().expect( + "running on a machine with at most 64 bit pointer width and can convert from \ + usize to u64", + ), + } + } +} diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/tests.rs b/crates/astria-core/src/sequencerblock/v1alpha1/tests.rs new file mode 100644 index 0000000000..cb95605d27 --- /dev/null +++ b/crates/astria-core/src/sequencerblock/v1alpha1/tests.rs @@ -0,0 +1,62 @@ +use sha2::Digest as _; + +use super::*; +use crate::sequencer::v1::test_utils::make_cometbft_block; + +#[test] +fn sequencer_block_from_cometbft_block_gives_expected_merkle_proofs() { + let block = make_cometbft_block(); + let sequencer_block = SequencerBlock::try_from_cometbft(block).unwrap(); + let rollup_ids_root = + merkle::Tree::from_leaves(sequencer_block.rollup_transactions.keys()).root(); + + let rollup_transaction_tree = derive_merkle_tree_from_rollup_txs( + sequencer_block + .rollup_transactions + .iter() + .map(|(id, txs)| (id, txs.transactions())), + ); + + for rollup_transactions in sequencer_block.rollup_transactions.values() { + assert!( + super::super::do_rollup_transaction_match_root( + rollup_transactions, + rollup_transaction_tree.root() + ), + "audit failed; rollup transaction and its proof does not evaluate to rollup \ + transactions root", + ); + } + + let data_hash: [u8; 32] = sequencer_block + .header + .cometbft_header + .data_hash + .unwrap() + .as_bytes() + .try_into() + .unwrap(); + assert!( + sequencer_block + .rollup_transactions_proof + .verify(&Sha256::digest(rollup_transaction_tree.root()), data_hash) + ); + assert!( + sequencer_block + .rollup_ids_proof + .verify(&Sha256::digest(rollup_ids_root), data_hash) + ); +} + +#[test] +fn block_to_filtered_roundtrip() { + let block = make_cometbft_block(); + let sequencer_block = SequencerBlock::try_from_cometbft(block).unwrap(); + let rollup_ids = sequencer_block.rollup_transactions.keys(); + let filtered_sequencer_block = sequencer_block.to_filtered_block(rollup_ids); + + let raw = filtered_sequencer_block.clone().into_raw(); + let from_raw = FilteredSequencerBlock::try_from_raw(raw).unwrap(); + + assert_eq!(filtered_sequencer_block, from_raw); +} diff --git a/crates/astria-sequencer-client/src/extension_trait.rs b/crates/astria-sequencer-client/src/extension_trait.rs index afdd4d9175..26ad2c9946 100644 --- a/crates/astria-sequencer-client/src/extension_trait.rs +++ b/crates/astria-sequencer-client/src/extension_trait.rs @@ -26,13 +26,17 @@ use std::{ sync::Arc, }; -pub use astria_core::sequencer::v1::{ - block::SequencerBlockError, - Address, - BalanceResponse, - NonceResponse, - SequencerBlock, - SignedTransaction, +pub use astria_core::{ + sequencer::v1::{ + Address, + BalanceResponse, + NonceResponse, + SignedTransaction, + }, + sequencerblock::v1alpha1::{ + block::SequencerBlockError, + SequencerBlock, + }, }; use async_trait::async_trait; use futures::Stream; @@ -430,7 +434,7 @@ pub trait SequencerClientExt: Client { /// /// - If calling tendermint `abci_query` RPC fails. /// - If the bytes contained in the abci query response cannot be read as an - /// `astria.sequencer.v1alpha1.BalanceResponse`. + /// `astria.sequencer.v1.BalanceResponse`. async fn get_balance( &self, address: AddressT, @@ -453,7 +457,7 @@ pub trait SequencerClientExt: Client { astria_core::generated::sequencer::v1::BalanceResponse::decode(&*response.value) .map_err(|e| { Error::abci_query_deserialization( - "astria.sequencer.v1alpha1.BalanceResponse", + "astria.sequencer.v1.BalanceResponse", response, e, ) @@ -481,7 +485,7 @@ pub trait SequencerClientExt: Client { /// /// - If calling tendermint `abci_query` RPC fails. /// - If the bytes contained in the abci query response cannot be read as an - /// `astria.sequencer.v1alpha1.NonceResponse`. + /// `astria.sequencer.v1.NonceResponse`. async fn get_nonce( &self, address: AddressT, @@ -504,7 +508,7 @@ pub trait SequencerClientExt: Client { astria_core::generated::sequencer::v1::NonceResponse::decode(&*response.value) .map_err(|e| { Error::abci_query_deserialization( - "astria.sequencer.v1alpha1.NonceResponse", + "astria.sequencer.v1.NonceResponse", response, e, ) diff --git a/crates/astria-sequencer-client/src/lib.rs b/crates/astria-sequencer-client/src/lib.rs index ed95457525..bb3300cf6e 100644 --- a/crates/astria-sequencer-client/src/lib.rs +++ b/crates/astria-sequencer-client/src/lib.rs @@ -12,12 +12,14 @@ use std::{ #[cfg(any(feature = "http", feature = "websocket"))] pub use __feature_gated_exports::*; -pub use astria_core::sequencer::v1::{ - Address, - BalanceResponse, - NonceResponse, - SequencerBlock, - SignedTransaction, +pub use astria_core::{ + sequencer::v1::{ + Address, + BalanceResponse, + NonceResponse, + SignedTransaction, + }, + sequencerblock::v1alpha1::SequencerBlock, }; use futures_util::{ FutureExt, diff --git a/crates/astria-sequencer-relayer/src/relayer/builder.rs b/crates/astria-sequencer-relayer/src/relayer/builder.rs index b318af3926..c93f48e311 100644 --- a/crates/astria-sequencer-relayer/src/relayer/builder.rs +++ b/crates/astria-sequencer-relayer/src/relayer/builder.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use astria_core::generated::sequencer::v1::sequencer_service_client::SequencerServiceClient; +use astria_core::generated::sequencerblock::v1alpha1::sequencer_service_client::SequencerServiceClient; use astria_eyre::eyre::{ self, WrapErr as _, diff --git a/crates/astria-sequencer-relayer/src/relayer/mod.rs b/crates/astria-sequencer-relayer/src/relayer/mod.rs index cf5522f500..ad26f5fe6f 100644 --- a/crates/astria-sequencer-relayer/src/relayer/mod.rs +++ b/crates/astria-sequencer-relayer/src/relayer/mod.rs @@ -8,8 +8,8 @@ use std::{ }; use astria_core::{ - generated::sequencer::v1::sequencer_service_client::SequencerServiceClient, - sequencer::v1::SequencerBlock, + generated::sequencerblock::v1alpha1::sequencer_service_client::SequencerServiceClient, + sequencerblock::v1alpha1::SequencerBlock, }; use astria_eyre::eyre::{ self, @@ -225,7 +225,7 @@ impl Relayer { fn block_does_not_match_validator(&self, block: &SequencerBlock) -> bool { self.validator .as_ref() - .is_some_and(|val| val.address != block.header().cometbft_header().proposer_address) + .is_some_and(|val| &val.address != block.header().proposer_address()) } #[instrument(skip_all, fields(%height))] @@ -248,7 +248,7 @@ impl Relayer { if self.block_does_not_match_validator(&block) { info!( address.validator = self.report_validator(), - address.block_proposer = %block.header().cometbft_header().proposer_address, + address.block_proposer = %block.header().proposer_address(), "block proposer does not match internal validator; dropping", ); return Ok(()); diff --git a/crates/astria-sequencer-relayer/src/relayer/read.rs b/crates/astria-sequencer-relayer/src/relayer/read.rs index 38c9ac2e6a..ad8e5c6d7d 100644 --- a/crates/astria-sequencer-relayer/src/relayer/read.rs +++ b/crates/astria-sequencer-relayer/src/relayer/read.rs @@ -8,11 +8,11 @@ use std::{ }; use astria_core::{ - generated::sequencer::v1::{ + generated::sequencerblock::v1alpha1::{ sequencer_service_client::SequencerServiceClient, GetSequencerBlockRequest, }, - sequencer::v1::SequencerBlock, + sequencerblock::v1alpha1::SequencerBlock, }; use astria_eyre::eyre::{ self, diff --git a/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs b/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs index e45b11fd7e..4b2ba4b119 100644 --- a/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs +++ b/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs @@ -59,8 +59,9 @@ pub(super) fn convert(block: SequencerBlock) -> eyre::Result { // 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 = - celestia_client::celestia_namespace_v0_from_cometbft_header(sequencer_blob.header()); + let sequencer_namespace = celestia_client::celestia_namespace_v0_from_str( + sequencer_blob.header().chain_id().as_str(), + ); let header_blob = Blob::new( sequencer_namespace, diff --git a/crates/astria-sequencer-relayer/tests/blackbox/helper.rs b/crates/astria-sequencer-relayer/tests/blackbox/helper.rs index 6844a5307e..00e318074d 100644 --- a/crates/astria-sequencer-relayer/tests/blackbox/helper.rs +++ b/crates/astria-sequencer-relayer/tests/blackbox/helper.rs @@ -11,7 +11,7 @@ use std::{ use assert_json_diff::assert_json_include; use astria_core::{ - generated::sequencer::v1::{ + generated::sequencerblock::v1alpha1::{ sequencer_service_server::{ SequencerService, SequencerServiceServer, @@ -21,10 +21,8 @@ use astria_core::{ GetSequencerBlockRequest, SequencerBlock as RawSequencerBlock, }, - sequencer::v1::{ - test_utils::make_cometbft_block, - SequencerBlock, - }, + sequencer::v1::test_utils::make_cometbft_block, + sequencerblock::v1alpha1::SequencerBlock, }; use astria_sequencer_relayer::{ config::Config, @@ -271,13 +269,7 @@ impl TestSequencerRelayer { .into_raw(); // make the block bad!! - let cometbft_header = block - .header - .as_mut() - .unwrap() - .cometbft_header - .as_mut() - .unwrap(); + let cometbft_header = block.header.as_mut().unwrap(); cometbft_header.data_hash = [0; 32].to_vec(); let mut blocks = self.sequencer_server_blocks.lock().unwrap(); diff --git a/crates/astria-sequencer/src/accounts/state_ext.rs b/crates/astria-sequencer/src/accounts/state_ext.rs index 1edab99cef..f77b1f1ae0 100644 --- a/crates/astria-sequencer/src/accounts/state_ext.rs +++ b/crates/astria-sequencer/src/accounts/state_ext.rs @@ -208,6 +208,7 @@ mod test { asset::{ Denom, Id, + DEFAULT_NATIVE_ASSET_DENOM, }, Address, }; @@ -506,9 +507,9 @@ mod test { let mut state = StateDelta::new(snapshot); // need to set native asset in order to use `get_account_balances()` - crate::asset::initialize_native_asset("asset_0"); + crate::asset::initialize_native_asset(DEFAULT_NATIVE_ASSET_DENOM); - let asset_0 = Id::from_denom("asset_0"); + let asset_0 = Id::from_denom(DEFAULT_NATIVE_ASSET_DENOM); let asset_1 = Id::from_denom("asset_1"); let asset_2 = Id::from_denom("asset_2"); @@ -516,7 +517,7 @@ mod test { asset::state_ext::StateWriteExt::put_ibc_asset( &mut state, asset_0, - &Denom::from_base_denom("asset_0"), + &Denom::from_base_denom(DEFAULT_NATIVE_ASSET_DENOM), ) .expect("should be able to call other trait method on state object"); asset::state_ext::StateWriteExt::put_ibc_asset( @@ -558,7 +559,7 @@ mod test { balances, vec![ AssetBalance { - denom: Denom::from_base_denom("asset_0"), + denom: Denom::from_base_denom(DEFAULT_NATIVE_ASSET_DENOM), balance: amount_expected_0, }, AssetBalance { diff --git a/crates/astria-sequencer/src/api_state_ext.rs b/crates/astria-sequencer/src/api_state_ext.rs index a191dbaee9..ec36d08754 100644 --- a/crates/astria-sequencer/src/api_state_ext.rs +++ b/crates/astria-sequencer/src/api_state_ext.rs @@ -5,17 +5,17 @@ use anyhow::{ Result, }; use astria_core::{ - generated::sequencer::v1 as raw, - sequencer::v1::{ - block::{ + generated::sequencerblock::v1alpha1 as raw, + sequencer::v1::RollupId, + sequencerblock::{ + v1alpha1::block::{ RollupTransactions, SequencerBlock, SequencerBlockHeader, SequencerBlockParts, }, - RollupId, + Protobuf as _, }, - Protobuf, }; use async_trait::async_trait; use borsh::{ @@ -240,6 +240,7 @@ pub(crate) trait StateReadExt: StateRead { .context("failed to decode rollup IDs proof from raw bytes")?; let raw = raw::SequencerBlock { + block_hash: hash.to_vec(), header: header_raw.into(), rollup_transactions, rollup_transactions_proof: rollup_transactions_proof.into(), @@ -348,9 +349,9 @@ pub(crate) trait StateWriteExt: StateWrite { .context("failed to serialize rollup IDs list")?, ); - let block_hash = block.block_hash(); let key = sequencer_block_header_by_hash_key(&block.block_hash()); let SequencerBlockParts { + block_hash, header, rollup_transactions, rollup_transactions_proof, @@ -381,10 +382,12 @@ mod test { use std::collections::HashMap; - use astria_core::sequencer::v1::{ - asset::Id, - block::Deposit, - Address, + use astria_core::{ + sequencer::v1::{ + asset::Id, + Address, + }, + sequencerblock::v1alpha1::block::Deposit, }; use cnidarium::StateDelta; use rand::Rng; diff --git a/crates/astria-sequencer/src/app.rs b/crates/astria-sequencer/src/app.rs index 14c122c6f5..f45d52088a 100644 --- a/crates/astria-sequencer/src/app.rs +++ b/crates/astria-sequencer/src/app.rs @@ -15,13 +15,15 @@ use anyhow::{ use astria_core::{ generated::sequencer::v1 as raw, sequencer::v1::{ - block::Deposit, transaction::Action, Address, RollupId, - SequencerBlock, SignedTransaction, }, + sequencerblock::v1alpha1::block::{ + Deposit, + SequencerBlock, + }, }; use cnidarium::{ ArcStateDeltaExt, @@ -895,6 +897,7 @@ mod test { asset, asset::DEFAULT_NATIVE_ASSET_DENOM, transaction::action::{ + BridgeLockAction, IbcRelayerChangeAction, SequenceAction, SudoAddressChangeAction, @@ -1713,11 +1716,6 @@ mod test { #[tokio::test] async fn app_deliver_tx_bridge_lock_action_ok() { - use astria_core::sequencer::v1::{ - block::Deposit, - transaction::action::BridgeLockAction, - }; - let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); let mut app = initialize_app(None, vec![]).await; @@ -2126,11 +2124,6 @@ mod test { #[tokio::test] async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { - use astria_core::sequencer::v1::{ - block::Deposit, - transaction::action::BridgeLockAction, - }; - let (alice_signing_key, _) = get_alice_signing_key_and_address(); let (mut app, storage) = initialize_app_with_storage(None, vec![]).await; diff --git a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs index 6669e9b7e9..242d40908f 100644 --- a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs @@ -3,13 +3,15 @@ use anyhow::{ Context as _, Result, }; -use astria_core::sequencer::v1::{ - block::Deposit, - transaction::action::{ - BridgeLockAction, - TransferAction, +use astria_core::{ + sequencer::v1::{ + transaction::action::{ + BridgeLockAction, + TransferAction, + }, + Address, }, - Address, + sequencerblock::v1alpha1::block::Deposit, }; use tracing::instrument; diff --git a/crates/astria-sequencer/src/bridge/state_ext.rs b/crates/astria-sequencer/src/bridge/state_ext.rs index 75ed61f028..f8bcdb8ca7 100644 --- a/crates/astria-sequencer/src/bridge/state_ext.rs +++ b/crates/astria-sequencer/src/bridge/state_ext.rs @@ -9,13 +9,13 @@ use anyhow::{ Result, }; use astria_core::{ - generated::sequencer::v1::Deposit as RawDeposit, + generated::sequencerblock::v1alpha1::Deposit as RawDeposit, sequencer::v1::{ asset, - block::Deposit, Address, RollupId, }, + sequencerblock::v1alpha1::block::Deposit, }; use async_trait::async_trait; use borsh::{ @@ -251,11 +251,13 @@ impl StateWriteExt for T {} #[cfg(test)] mod test { - use astria_core::sequencer::v1::{ - asset::Id, - block::Deposit, - Address, - RollupId, + use astria_core::{ + sequencer::v1::{ + asset::Id, + Address, + RollupId, + }, + sequencerblock::v1alpha1::block::Deposit, }; use cnidarium::StateDelta; diff --git a/crates/astria-sequencer/src/grpc/sequencer.rs b/crates/astria-sequencer/src/grpc/sequencer.rs index f5aa801025..d0dfd20ab7 100644 --- a/crates/astria-sequencer/src/grpc/sequencer.rs +++ b/crates/astria-sequencer/src/grpc/sequencer.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use astria_core::{ - generated::sequencer::v1::{ + generated::sequencerblock::v1alpha1::{ sequencer_service_server::SequencerService, FilteredSequencerBlock as RawFilteredSequencerBlock, GetFilteredSequencerBlockRequest, @@ -98,15 +98,14 @@ impl SequencerService for SequencerServer { .await .map_err(|e| Status::internal(format!("failed to get block hash from storage: {e}")))?; - let header_parts = snapshot + let header = snapshot .get_sequencer_block_header_by_hash(&block_hash) .await .map_err(|e| { Status::internal(format!( "failed to get sequencer block header from storage: {e}" )) - })? - .into_parts(); + })?; let (rollup_transactions_proof, rollup_ids_proof) = snapshot .get_block_proofs_by_block_hash(&block_hash) @@ -143,9 +142,9 @@ impl SequencerService for SequencerServer { let all_rollup_ids = all_rollup_ids.into_iter().map(RollupId::to_vec).collect(); let block = RawFilteredSequencerBlock { - cometbft_header: Some(header_parts.cometbft_header.into()), + block_hash: block_hash.to_vec(), + header: Some(header.into_raw()), rollup_transactions, - rollup_transactions_root: header_parts.rollup_transactions_root.to_vec(), rollup_transactions_proof: rollup_transactions_proof.into(), rollup_ids_proof: rollup_ids_proof.into(), all_rollup_ids, @@ -159,7 +158,7 @@ impl SequencerService for SequencerServer { mod test { use std::collections::HashMap; - use astria_core::sequencer::v1::SequencerBlock; + use astria_core::sequencerblock::v1alpha1::SequencerBlock; use cnidarium::StateDelta; use sha2::{ Digest as _, @@ -228,15 +227,6 @@ mod test { }; let request = Request::new(request); let response = server.get_sequencer_block(request).await.unwrap(); - assert_eq!( - response - .into_inner() - .header - .unwrap() - .cometbft_header - .unwrap() - .height, - 1 - ); + assert_eq!(response.into_inner().header.unwrap().height, 1); } } diff --git a/crates/astria-sequencer/src/ibc/ics20_transfer.rs b/crates/astria-sequencer/src/ibc/ics20_transfer.rs index abef9be2e7..59be6c5a23 100644 --- a/crates/astria-sequencer/src/ibc/ics20_transfer.rs +++ b/crates/astria-sequencer/src/ibc/ics20_transfer.rs @@ -15,10 +15,12 @@ use anyhow::{ Context as _, Result, }; -use astria_core::sequencer::v1::{ - asset::Denom, - block::Deposit, - Address, +use astria_core::{ + sequencer::v1::{ + asset::Denom, + Address, + }, + sequencerblock::v1alpha1::block::Deposit, }; use cnidarium::{ StateRead, diff --git a/crates/astria-sequencer/src/proposal/commitment.rs b/crates/astria-sequencer/src/proposal/commitment.rs index 7c6aa3fe90..0c29eedeba 100644 --- a/crates/astria-sequencer/src/proposal/commitment.rs +++ b/crates/astria-sequencer/src/proposal/commitment.rs @@ -1,13 +1,15 @@ use std::collections::HashMap; -use astria_core::sequencer::v1::{ - block::{ +use astria_core::{ + sequencer::v1::{ + group_sequence_actions_in_signed_transaction_transactions_by_rollup_id, + RollupId, + SignedTransaction, + }, + sequencerblock::v1alpha1::block::{ Deposit, RollupData, }, - group_sequence_actions_in_signed_transaction_transactions_by_rollup_id, - RollupId, - SignedTransaction, }; use bytes::Bytes; diff --git a/crates/astria-sequencer/src/sequencer.rs b/crates/astria-sequencer/src/sequencer.rs index 223509ed54..3895a3ef89 100644 --- a/crates/astria-sequencer/src/sequencer.rs +++ b/crates/astria-sequencer/src/sequencer.rs @@ -3,7 +3,7 @@ use anyhow::{ Context as _, Result, }; -use astria_core::generated::sequencer::v1::sequencer_service_server::SequencerServiceServer; +use astria_core::generated::sequencerblock::v1alpha1::sequencer_service_server::SequencerServiceServer; use penumbra_tower_trace::{ trace::request_span, v037::RequestExt as _, diff --git a/justfile b/justfile index b786c36cbc..71b1391b0a 100644 --- a/justfile +++ b/justfile @@ -216,7 +216,7 @@ deploy-smoke-test tag=defaultTag: @echo "Setting up single astria sequencer..." && helm install \ -n astria-validator-single single-sequencer-chart ./charts/sequencer \ -f dev/values/validators/single.yml \ - {{ if tag != '' { replace('--set images.sequencer.devTag=# --set sequencer-relayer.images.sequencer-relayer.devTag=#', '#', tag) } else { '' } }} \ + {{ if tag != '' { replace('--set images.sequencer.devTag=# --set sequencer-relayer.images.sequencerRelayer.devTag=#', '#', tag) } else { '' } }} \ --create-namespace > /dev/null @just wait-for-sequencer > /dev/null @echo "Starting EVM rollup..." && helm install -n astria-dev-cluster astria-chain-chart ./charts/evm-rollup -f dev/values/rollup/dev.yaml \ @@ -252,6 +252,22 @@ run-smoke-test: exit 1 fi + echo "Testing soft commits..." + SOFT_RUNS=0 + soft() { + HEX_NUM=$(curl -X POST $ETH_RPC_URL -s -d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["safe", false],"id":1}' -H 'Content-Type: application/json' | jq -r '.result.number') + echo "$(printf "%d" $HEX_NUM)" + } + while [ $SOFT_RUNS -lt $MAX_RUNS ]; do + if [ $(soft) -gt 0 ]; then + echo "Soft commit success" + break + else + sleep 1 + fi + SOFT_RUNS=$((SOFT_RUNS+1)) + done + echo "Testing finalization..." FINALIZED_RUNS=0 finalized() { diff --git a/proto/sequencerapis/astria/sequencer/v1/block.proto b/proto/sequencerapis/astria/sequencer/v1/block.proto index b037527370..29cd7a2806 100644 --- a/proto/sequencerapis/astria/sequencer/v1/block.proto +++ b/proto/sequencerapis/astria/sequencer/v1/block.proto @@ -18,7 +18,7 @@ message RollupTransactions { // Each entry is a protobuf-encoded `RollupData` message. repeated bytes transactions = 2; // The proof that these rollup transactions are included in sequencer block. - // `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + // `astria.SequencerBlock.rollup_transactions_proof`. astria.sequencer.v1.Proof proof = 3; } @@ -52,7 +52,7 @@ message SequencerBlockHeader { // The original CometBFT header that was the input to this sequencer block. astria_vendored.tendermint.types.Header cometbft_header = 1; // The 32-byte merkle root of all the rollup transactions in the block, - // Corresponds to `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, + // Corresponds to `MHT(astria.SequencerBlock.rollup_transactions)`, bytes rollup_transactions_root = 2; // The 32-byte merkle root of all the rollup IDs in the block. bytes rollup_ids_root = 3; @@ -90,7 +90,7 @@ message FilteredSequencerBlock { // A subset of rollup transactions that were included in this block. repeated RollupTransactions rollup_transactions = 2; // The Merkle Tree Hash of all the rollup transactions in the block (not just the - // subset included). Corresponds to `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, + // subset included). Corresponds to `MHT(astria.SequencerBlock.rollup_transactions)`, // the Merkle Tree Hash derived from the rollup transactions. // Always 32 bytes. bytes rollup_transactions_root = 3; @@ -102,7 +102,7 @@ message FilteredSequencerBlock { astria.sequencer.v1.Proof rollup_transactions_proof = 4; // 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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + // and is extracted from `astria.SequencerBlock.rollup_transactions`. // Note that these are all the rollup IDs in the sequencer block, not merely those in // `rollup_transactions` field. This is necessary to prove that no rollup IDs were omitted. repeated bytes all_rollup_ids = 5; diff --git a/proto/sequencerapis/astria/sequencer/v1/celestia.proto b/proto/sequencerapis/astria/sequencer/v1/celestia.proto index 16e564f7c7..23eca55530 100644 --- a/proto/sequencerapis/astria/sequencer/v1/celestia.proto +++ b/proto/sequencerapis/astria/sequencer/v1/celestia.proto @@ -19,13 +19,13 @@ message CelestiaRollupBlob { // A list of opaque bytes that are serialized rollup transactions. repeated bytes transactions = 3; // The proof that these rollup transactions are included in sequencer block. - // `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + // `astria.sequencerblock.v1alpha1.SequencerBlock.rollup_transactions_proof`. astria.sequencer.v1.Proof proof = 4; } // The metadata of a sequencer block that is submitted to celestia. // -// It is created by splitting a `astria.sequencer.v1alpha.SequencerBlock` into a +// 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. // @@ -33,21 +33,21 @@ message CelestiaRollupBlob { // block hash calculated from `header`. message CelestiaSequencerBlob { // The original CometBFT header that is the input to this blob's original sequencer block. - // Corresponds to `astria.sequencer.v1alpha.SequencerBlock.header`. + // Corresponds to `astria.SequencerBlock.header`. astria_vendored.tendermint.types.Header header = 1; // 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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + // and is extracted from `astria.SequencerBlock.rollup_transactions`. repeated bytes rollup_ids = 2; // The Merkle Tree Hash of the rollup transactions. Corresponds to - // `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, the Merkle + // `MHT(astria.SequencerBlock.rollup_transactions)`, the Merkle // Tree Hash deriveed from the rollup transactions. // Always 32 bytes. bytes rollup_transactions_root = 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.sequencer.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.sequencer.v1.Proof rollup_ids_proof = 5; } diff --git a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/block.proto b/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/block.proto index c6dc9e3f07..4b5c7195ac 100644 --- a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/block.proto +++ b/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/block.proto @@ -18,7 +18,7 @@ message RollupTransactions { // Each entry is a protobuf-encoded `RollupData` message. repeated bytes transactions = 2; // The proof that these rollup transactions are included in sequencer block. - // `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + // `astria.SequencerBlock.rollup_transactions_proof`. astria.sequencerblock.v1alpha1.Proof proof = 3; } @@ -61,7 +61,7 @@ message SequencerBlockHeader { // the cometbft proposer address of the sequencer block bytes proposer_address = 5; // The 32-byte merkle root of all the rollup transactions in the block, - // Corresponds to `MHT(astria.sequencer.v1alpha.SequencerBlock.rollup_transactions)`, + // Corresponds to `MHT(astria.SequencerBlock.rollup_transactions)`, bytes rollup_transactions_root = 6; } @@ -106,7 +106,7 @@ message FilteredSequencerBlock { astria.sequencerblock.v1alpha1.Proof rollup_transactions_proof = 4; // 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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + // and is extracted from `astria.SequencerBlock.rollup_transactions`. // Note that these are all the rollup IDs in the sequencer block, not merely those in // `rollup_transactions` field. This is necessary to prove that no rollup IDs were omitted. repeated bytes all_rollup_ids = 5; diff --git a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto b/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto index ed6b1be1b6..a1194c1f34 100644 --- a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto +++ b/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/celestia.proto @@ -19,13 +19,13 @@ message CelestiaRollupBlob { // A list of opaque bytes that are serialized rollup transactions. repeated bytes transactions = 3; // The proof that these rollup transactions are included in sequencer block. - // `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. + // `astria.SequencerBlock.rollup_transactions_proof`. astria.sequencerblock.v1alpha1.Proof proof = 4; } // The metadata of a sequencer block that is submitted to celestia. // -// It is created by splitting a `astria.sequencer.v1alpha.SequencerBlock` into a +// 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. // @@ -38,12 +38,12 @@ message CelestiaSequencerBlob { 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.sequencer.v1alpha.SequencerBlock.rollup_transactions`. + // and is extracted from `astria.SequencerBlock.rollup_transactions`. repeated bytes 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.rollup_transactions_proof`. astria.sequencerblock.v1alpha1.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.rollup_ids_proof`. astria.sequencerblock.v1alpha1.Proof rollup_ids_proof = 5; } diff --git a/tools/protobuf-compiler/src/main.rs b/tools/protobuf-compiler/src/main.rs index 1452f648a4..cc16129397 100644 --- a/tools/protobuf-compiler/src/main.rs +++ b/tools/protobuf-compiler/src/main.rs @@ -102,7 +102,6 @@ fn main() { .unwrap() .preserve_proto_field_names() .out_dir(&out_dir) - // only add JSON to types required for the execution API for now .build(&[ ".astria.execution.v1alpha2", ".astria.sequencer.v1.Deposit", @@ -114,10 +113,13 @@ fn main() { ".astria.primitive.v1.Uint128", ".astria.sequencerblock.v1alpha1.Deposit", ".astria.sequencerblock.v1alpha1.SequencerBlockHeader", + ".astria.sequencerblock.v1alpha1.SequencerBlock", + ".astria.sequencerblock.v1alpha1.GetSequencerBlockRequest", ".astria.sequencerblock.v1alpha1.FilteredSequencerBlock", ".astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest", ".astria.sequencerblock.v1alpha1.RollupData", ".astria.sequencerblock.v1alpha1.RollupTransactions", + ".astria.sequencerblock.v1alpha1.Proof", ]) .unwrap();