From 83d23ab147b473936cb7f0638fee7651a3c33b3c Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Fri, 3 May 2024 17:05:10 +0100 Subject: [PATCH] test(sequencer-relayer): reinstate black box tests (#1033) ## Summary The black box tests were removed in a recent PR. This PR reinstates them using the new mock gRPC framework `astria-grpc-mock`. ## Background The tests would have needed heavy refactoring in #963, and we wanted to avoid adding a lot more code to that already-large PR. We decided to temporarily delete them and reinstate them using `astria-grpc-mock` to mock responses from the Celestia app. ## Changes The tests removed in #963 and a single test briefly added in #1001 then removed again have been restored. I also added a new test to check the relayer shuts down in a timely manner. Previously the tests leaned heavily on counts of blobs received by the mock Celestia app. The current tests retain these checks, but also query the `/status` endpoint of the sequencer-relayer to confirm its state. There was also a single test previously which checked the state of the postsubmit.json file. I didn't think this was necessary given that we're querying the state in a more "black box" way now (via the http server), but I didn't remove the function to perform this check (`TestSequencerRelayer::assert_state_files_are_as_expected`) pending a decision on whether to reinstate that check or not inside the `later_height_in_state_leads_to_expected_relay` test. As @SuperFluffy predicted, all the new protobuf packages added in #963 had to be added to the `pbjson_build` builder to generate the serde impls required for using them in `astria-grpc-mock`. This accounts for the vast majority of the new LoC in this PR. I made a few small changes to the mock framework: - added `Mock::up_to_n_times` to avoid failures due to a single-use mock response being sent multiple times - added `DynamicResponse` to support constructing mock responses which can be passed the relevant gRPC request - changed `MockGuard::wait_until_satisfied` to take `self` by ref rather than value, since if it consumes self and `wait_until_satisfied` times out, the `MockGuard` panics in its `Drop` impl, meaning we don't get a good indication from e.g. a `tokio::time::timeout` as to what went wrong Most of the new functionality lives in `MockCelestiaAppServer` and `TestSequencerRelayer`. For the former, all gRPCs except for `BroadcastTx` and `GetTx` are set up to always return valid-enough responses - i.e. these don't need to be mounted individually in every test case. In the case of `MockCelestiaAppServer`, the new functionality is a combination of support for mounting mock responses and functions with timeouts to query the `/status`, `/healthz` and `/readyz` http endpoints of the sequencer-relayer. ## Testing These changes are tests. Closes #1008. --- Cargo.lock | 39 +- Cargo.toml | 4 - crates/astria-celestia-client/Cargo.toml | 42 - crates/astria-celestia-client/src/client.rs | 316 --- crates/astria-celestia-client/src/lib.rs | 69 - .../src/metrics_init.rs | 31 - .../astria-celestia-client/src/submission.rs | 86 - crates/astria-celestia-mock/Cargo.toml | 15 - crates/astria-celestia-mock/README.md | 5 - crates/astria-celestia-mock/src/lib.rs | 6 - .../astria-celestia-mock/src/rpc_impl/blob.rs | 21 - .../src/rpc_impl/header.rs | 14 - .../astria-celestia-mock/src/rpc_impl/mod.rs | 2 - .../src/generated/celestia.blob.v1.serde.rs | 451 +++++ .../generated/cosmos.auth.v1beta1.serde.rs | 673 +++++++ .../cosmos.base.abci.v1beta1.serde.rs | 653 ++++++ .../cosmos.base.node.v1beta1.serde.rs | 163 ++ .../cosmos.base.tendermint.v1beta1.serde.rs | 522 +++++ .../generated/cosmos.base.v1beta1.serde.rs | 108 + .../cosmos.crypto.multisig.v1beta1.serde.rs | 208 ++ .../cosmos.crypto.secp256k1.serde.rs | 94 + .../cosmos.tx.signing.v1beta1.serde.rs | 83 + .../src/generated/cosmos.tx.v1beta1.serde.rs | 1753 +++++++++++++++++ crates/astria-core/src/generated/mod.rs | 99 +- .../src/generated/tendermint.abci.serde.rs | 239 +++ .../src/generated/tendermint.p2p.serde.rs | 460 +++++ .../src/generated/tendermint.types.serde.rs | 284 +++ crates/astria-grpc-mock/src/mock.rs | 9 + crates/astria-grpc-mock/src/mock_server.rs | 2 +- crates/astria-grpc-mock/src/mounted_mock.rs | 15 +- crates/astria-grpc-mock/src/response.rs | 48 + crates/astria-sequencer-relayer/Cargo.toml | 2 +- .../src/relayer/mod.rs | 2 +- .../src/relayer/read.rs | 14 +- .../src/relayer/submission.rs | 6 +- .../tests/blackbox/helper.rs | 624 ------ .../helpers/mock_celestia_app_server.rs | 428 ++++ .../blackbox/helpers/mock_sequencer_server.rs | 173 ++ .../tests/blackbox/helpers/mod.rs | 12 + .../helpers/test_sequencer_relayer.rs | 785 ++++++++ .../tests/blackbox/main.rs | 464 ++++- tools/protobuf-compiler/src/main.rs | 3 + 42 files changed, 7672 insertions(+), 1355 deletions(-) delete mode 100644 crates/astria-celestia-client/Cargo.toml delete mode 100644 crates/astria-celestia-client/src/client.rs delete mode 100644 crates/astria-celestia-client/src/lib.rs delete mode 100644 crates/astria-celestia-client/src/metrics_init.rs delete mode 100644 crates/astria-celestia-client/src/submission.rs delete mode 100644 crates/astria-celestia-mock/Cargo.toml delete mode 100644 crates/astria-celestia-mock/README.md delete mode 100644 crates/astria-celestia-mock/src/lib.rs delete mode 100644 crates/astria-celestia-mock/src/rpc_impl/blob.rs delete mode 100644 crates/astria-celestia-mock/src/rpc_impl/header.rs delete mode 100644 crates/astria-celestia-mock/src/rpc_impl/mod.rs create mode 100644 crates/astria-core/src/generated/celestia.blob.v1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.auth.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.base.abci.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.base.node.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.base.tendermint.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.base.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.crypto.multisig.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.crypto.secp256k1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.tx.signing.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/cosmos.tx.v1beta1.serde.rs create mode 100644 crates/astria-core/src/generated/tendermint.abci.serde.rs create mode 100644 crates/astria-core/src/generated/tendermint.p2p.serde.rs create mode 100644 crates/astria-core/src/generated/tendermint.types.serde.rs delete mode 100644 crates/astria-sequencer-relayer/tests/blackbox/helper.rs create mode 100644 crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_celestia_app_server.rs create mode 100644 crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs create mode 100644 crates/astria-sequencer-relayer/tests/blackbox/helpers/mod.rs create mode 100644 crates/astria-sequencer-relayer/tests/blackbox/helpers/test_sequencer_relayer.rs diff --git a/Cargo.lock b/Cargo.lock index e322cd97f2..d4f36244a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,39 +501,6 @@ dependencies = [ "vergen", ] -[[package]] -name = "astria-celestia-client" -version = "0.1.0" -dependencies = [ - "astria-core", - "astria-merkle", - "astria-telemetry", - "async-trait", - "base64 0.21.7", - "base64-serde", - "celestia-rpc", - "celestia-tendermint", - "celestia-types", - "hex", - "jsonrpsee", - "metrics", - "prost", - "serde", - "serde_json", - "sha2 0.10.8", - "tendermint 0.34.1", - "thiserror", - "tracing", -] - -[[package]] -name = "astria-celestia-mock" -version = "0.1.0" -dependencies = [ - "astria-celestia-client", - "jsonrpsee", -] - [[package]] name = "astria-cli" version = "0.3.1" @@ -829,10 +796,10 @@ version = "0.12.0" dependencies = [ "assert-json-diff", "astria-build-info", - "astria-celestia-mock", "astria-config", "astria-core", "astria-eyre", + "astria-grpc-mock", "astria-merkle", "astria-sequencer-client", "astria-telemetry", @@ -5731,9 +5698,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 38f3fd4ba2..95f248e442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,6 @@ exclude = ["tools/protobuf-compiler"] members = [ "crates/astria-build-info", - "crates/astria-celestia-client", - "crates/astria-celestia-mock", "crates/astria-cli", "crates/astria-composer", "crates/astria-conductor", @@ -27,8 +25,6 @@ members = [ # not act on lints default-members = [ "crates/astria-build-info", - "crates/astria-celestia-client", - "crates/astria-celestia-mock", "crates/astria-cli", "crates/astria-composer", "crates/astria-conductor", diff --git a/crates/astria-celestia-client/Cargo.toml b/crates/astria-celestia-client/Cargo.toml deleted file mode 100644 index 04bfbdf01e..0000000000 --- a/crates/astria-celestia-client/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "astria-celestia-client" -description = "an extension of eigerco's celestia client with astria specific pieces" -version = "0.1.0" -edition = "2021" -rust-version = "1.73" -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/astriaorg/astria" -homepage = "https://astria.org" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -async-trait = { workspace = true } -base64 = { workspace = true } -base64-serde = { workspace = true } -celestia-tendermint = { workspace = true } -hex = { workspace = true } -metrics = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -sha2 = { workspace = true } -tendermint = { workspace = true } -thiserror = { workspace = true } -tracing = { workspace = true } - -astria-core = { path = "../astria-core" } -merkle = { package = "astria-merkle", path = "../astria-merkle" } -telemetry = { package = "astria-telemetry", path = "../astria-telemetry", features = [ - "display", -] } - -### Celestia specific imports -# -# The crates imported here are all specific to eigerco's celestia APIs -# (including jsonrpsee). -# As none of them are reexported it is important to keep these in sync -# when updating. -celestia-rpc = "0.1.1" -celestia-types = "0.1.1" -jsonrpsee = { version = "0.20", features = ["client-core", "macros"] } -prost = { workspace = true } diff --git a/crates/astria-celestia-client/src/client.rs b/crates/astria-celestia-client/src/client.rs deleted file mode 100644 index cf738199cc..0000000000 --- a/crates/astria-celestia-client/src/client.rs +++ /dev/null @@ -1,316 +0,0 @@ -use astria_core::sequencerblock::v1alpha1::{ - celestia::CelestiaSequencerBlobError, - CelestiaRollupBlob, - CelestiaSequencerBlob, - SequencerBlock, -}; -use async_trait::async_trait; -use base64::{ - display::Base64Display, - engine::general_purpose::STANDARD, -}; -use celestia_rpc::BlobClient; -use celestia_types::{ - blob::SubmitOptions, - nmt::Namespace, - Blob, - Commitment, -}; -use prost::{ - DecodeError, - Message as _, -}; -use tracing::{ - debug, - instrument, -}; - -use crate::submission::ToBlobsError; - -impl CelestiaClientExt for jsonrpsee::http_client::HttpClient {} -impl CelestiaClientExt for jsonrpsee::ws_client::WsClient {} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct SubmitSequencerBlocksError(SubmitSequencerBlocksErrorKind); - -impl SubmitSequencerBlocksError { - fn assemble(source: ToBlobsError) -> Self { - Self(SubmitSequencerBlocksErrorKind::AssembleBlobs(source)) - } - - fn jsonrpc(source: jsonrpsee::core::Error) -> Self { - Self(SubmitSequencerBlocksErrorKind::JsonRpc(source)) - } -} - -#[derive(Debug, thiserror::Error)] -enum SubmitSequencerBlocksErrorKind { - #[error("failed assembling blobs from sequencer block")] - AssembleBlobs(#[source] ToBlobsError), - #[error("the JSONRPC call failed")] - JsonRpc(#[source] jsonrpsee::core::Error), -} - -pub struct BadBlob { - pub reason: BadBlobReason, - pub commitment: Commitment, -} - -pub enum BadBlobReason { - Conversion(CelestiaSequencerBlobError), - Deserialization(DecodeError), - WrongNamespace(Namespace), -} - -pub struct GetSequencerBlobsResponse { - pub height: u64, - pub namespace: Namespace, - pub sequencer_blobs: Vec, - pub bad_blobs: Vec, -} - -#[async_trait] -pub trait CelestiaClientExt: BlobClient { - /// Fetch sequencer blobs at the given height and namespace. - /// - /// Returns successfully deserialized blobs in the `.sequencer_blobs` field. The - /// `.bad_blobs` field contains the celestia commitment for each blob - /// that could not be turned into sequencer data and the reason for it. - /// - /// # Errors - /// - /// Fails if the underlying `blob.GetAll` JSONRPC failed. - async fn get_sequencer_blobs( - &self, - height: T, - namespace: Namespace, - ) -> Result - where - T: Into + Send, - { - let height = height.into(); - let blobs = self.blob_get_all(height, &[namespace]).await?; - - let mut sequencer_blobs = Vec::new(); - let mut bad_blobs = Vec::new(); - for blob in blobs { - if blob.namespace != namespace { - bad_blobs.push(BadBlob { - reason: BadBlobReason::WrongNamespace(blob.namespace), - commitment: blob.commitment, - }); - } - 'blob: { - let raw_blob = - match astria_core::generated::sequencerblock::v1alpha1::CelestiaSequencerBlob::decode( - &*blob.data, - ) { - Ok(blob) => blob, - Err(err) => { - bad_blobs.push(BadBlob { - reason: BadBlobReason::Deserialization(err), - commitment: blob.commitment, - }); - break 'blob; - } - }; - match CelestiaSequencerBlob::try_from_raw(raw_blob) { - Ok(blob) => sequencer_blobs.push(blob), - Err(err) => bad_blobs.push(BadBlob { - reason: BadBlobReason::Conversion(err), - commitment: blob.commitment, - }), - } - } - } - - Ok(GetSequencerBlobsResponse { - height, - namespace, - sequencer_blobs, - bad_blobs, - }) - } - - /// Returns the rollup blob for a given rollup namespace at a given height, if it exists. - /// - /// # Errors - /// - /// Returns an error if: - /// + the verification key could not be constructed from the data stored in `namespace_data`; - /// + the RPC to fetch the blobs failed. - #[instrument(skip_all, fields( - height = height.into(), - namespace = %telemetry::display::base64(&namespace.as_bytes()), - block_hash = %telemetry::display::base64(&sequencer_blob.block_hash()), - ))] - async fn get_rollup_blobs_matching_sequencer_blob( - &self, - height: T, - namespace: Namespace, - sequencer_blob: &CelestiaSequencerBlob, - ) -> Result, jsonrpsee::core::Error> - where - T: Into + Copy + Send, - { - #[must_use] - fn is_blob_not_found(err: &jsonrpsee::core::Error) -> bool { - if let jsonrpsee::core::Error::Call(err) = err { - return err.message().contains("blob: not found"); - } - false - } - - let height = height.into(); - - let rsp = self.blob_get_all(height, &[namespace]).await; - let blobs = match rsp { - Ok(blobs) => blobs, - Err(err) if is_blob_not_found(&err) => { - return Ok(vec![]); - } - Err(err) => { - return Err(err); - } - }; - let rollup_datas = convert_and_filter_rollup_blobs(blobs, namespace, sequencer_blob); - Ok(rollup_datas) - } - - /// Submits a sequencer `block` to celestia - /// - /// `Blocks` after converted into celestia blobs and then posted. Rollup - /// data is posted to a namespace derived from the rollup chain id. - /// Sequencer data for each is posted to a namespace derived from the - /// sequencer block's chain ID. - /// - /// This calls the `blob.Submit` celestia-node RPC. - /// - /// Returns Result: - /// - Ok: the celestia block height blobs were included in. - /// - Errors: - /// - SubmitSequencerBlocksError::AssembleBlobs when failed to assemble blob - /// - SubmitSequencerBlocksError::JsonRpc when Celestia `blob.Submit` fails - async fn submit_sequencer_block( - &self, - block: SequencerBlock, - submit_options: SubmitOptions, - ) -> Result { - use crate::submission::ToBlobs as _; - let mut blobs = Vec::new(); - - block - .try_to_blobs(&mut blobs) - .map_err(SubmitSequencerBlocksError::assemble)?; - - let height = self - .blob_submit(&blobs, submit_options) - .await - .map_err(SubmitSequencerBlocksError::jsonrpc)?; - - Ok(height) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum BlobAssemblyError { - #[error("failed constructing celestia blob from rollup data at index `{index}`")] - ConstructBlobFromRollupData { - source: celestia_types::Error, - index: usize, - }, - #[error("failed constructing celestia blob from sequencer data")] - ConstructBlobFromSequencerData(#[source] celestia_types::Error), - #[error("failed signing rollup namespace data at index `{index}`")] - SignRollupData { - source: serde_json::Error, - index: usize, - }, - #[error( - "failed to construct inclusion proof for the transaction at index `{index}` because its \ - index was outside the tree" - )] - ConstructProof { index: usize }, -} - -/// Attempts to convert the bytes stored in the celestia blobs to [`CelestiaRollupBlob`]. -/// -/// Drops a blob under the following conditions: -/// + the blob's namespace does not match the provided [`Namespace`] -/// + cannot be decode/convert to [`CelestiaRollupBlob`] -/// + block hash does not match that of [`CcelestiaSequencerBlob`] -/// + the proof, ID, and transactions recorded in the blob cannot be verified against the seuencer -/// blob's `rollup_transaction_root`. -fn convert_and_filter_rollup_blobs( - blobs: Vec, - namespace: Namespace, - sequencer_blob: &CelestiaSequencerBlob, -) -> Vec { - let mut rollups = Vec::with_capacity(blobs.len()); - for blob in blobs { - if blob.namespace != namespace { - debug!("blob does not belong to expected namespace; skipping"); - continue; - } - let proto_blob = - match astria_core::generated::sequencerblock::v1alpha1::CelestiaRollupBlob::decode( - &*blob.data, - ) { - Err(e) => { - debug!( - error = &e as &dyn std::error::Error, - target = "astria.sequencerblock.v1alpha1.CelestiaRollupBlob", - blob.commitment = %Base64Display::new(&blob.commitment.0, &STANDARD), - "failed decoding blob as protobuf; skipping" - ); - continue; - } - Ok(proto_blob) => proto_blob, - }; - let rollup_blob = match CelestiaRollupBlob::try_from_raw(proto_blob) { - Err(e) => { - debug!( - error = &e as &dyn std::error::Error, - blob.commitment = %Base64Display::new(&blob.commitment.0, &STANDARD), - "failed converting raw protobuf blob to native type; skipping" - ); - continue; - } - Ok(rollup_blob) => rollup_blob, - }; - if rollup_blob.sequencer_block_hash() != sequencer_blob.block_hash() { - debug!( - block_hash.rollup = hex::encode(rollup_blob.sequencer_block_hash()), - block_hash.sequencer = hex::encode(sequencer_blob.block_hash()), - "block hash in rollup blob does not match block hash in sequencer blob; dropping \ - blob" - ); - continue; - } - if !does_rollup_blob_verify_against_sequencer_blob(&rollup_blob, sequencer_blob) { - debug!( - "the rollup blob proof applied to its chain ID and transactions did not match the \ - rollup transactions root in the sequencer blob; dropping the blob" - ); - continue; - } - rollups.push(rollup_blob); - } - rollups -} - -fn does_rollup_blob_verify_against_sequencer_blob( - rollup_blob: &CelestiaRollupBlob, - sequencer_blob: &CelestiaSequencerBlob, -) -> bool { - rollup_blob - .proof() - .audit() - .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()) - .finish_leaf() - .perform() -} diff --git a/crates/astria-celestia-client/src/lib.rs b/crates/astria-celestia-client/src/lib.rs deleted file mode 100644 index 8edd77b6f5..0000000000 --- a/crates/astria-celestia-client/src/lib.rs +++ /dev/null @@ -1,69 +0,0 @@ -pub mod client; -pub mod metrics_init; -pub mod submission; - -pub use astria_core::sequencerblock::v1alpha1::{ - CelestiaRollupBlob, - CelestiaSequencerBlob, -}; -pub use celestia_rpc; -pub use celestia_tendermint; -pub use celestia_types; -use celestia_types::nmt::{ - Namespace, - NS_ID_V0_SIZE, -}; -pub use client::CelestiaClientExt; -pub use jsonrpsee; - -pub fn is_blob_not_found(err: &T) -> bool { - err.is_blob_not_found() -} - -pub trait IsBlobNotFound { - fn is_blob_not_found(&self) -> bool; -} - -impl IsBlobNotFound for jsonrpsee::core::Error { - fn is_blob_not_found(&self) -> bool { - let jsonrpsee::core::Error::Call(error) = self else { - return false; - }; - error.code() == 1 && error.message().contains("blob: not found") - } -} - -#[must_use = "a celestia namespace must be used in order to be useful"] -pub const fn celestia_namespace_v0_from_array(bytes: [u8; N]) -> Namespace { - #[allow(clippy::assertions_on_constants)] - const _: () = assert!( - 10 == NS_ID_V0_SIZE, - "verify that the celestia v0 namespace was changed from 10 bytes" - ); - let first_10_bytes = [ - bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], - bytes[9], - ]; - Namespace::const_v0(first_10_bytes) -} - -#[must_use = "a celestia namespace must be used in order to be useful"] -pub const fn celestia_namespace_v0_from_rollup_id( - rollup_id: astria_core::primitive::v1::RollupId, -) -> Namespace { - celestia_namespace_v0_from_array(rollup_id.get()) -} - -#[must_use = "a celestia namespace must be used in order to be useful"] -pub fn celestia_namespace_v0_from_str(chain_id: &str) -> Namespace { - celestia_namespace_v0_from_bytes(chain_id.as_bytes()) -} - -#[must_use = "a celestia namespace must be used in order to be useful"] -pub fn celestia_namespace_v0_from_bytes(bytes: &[u8]) -> Namespace { - use sha2::{ - Digest as _, - Sha256, - }; - celestia_namespace_v0_from_array(Sha256::digest(bytes).into()) -} diff --git a/crates/astria-celestia-client/src/metrics_init.rs b/crates/astria-celestia-client/src/metrics_init.rs deleted file mode 100644 index 0261cbc9d9..0000000000 --- a/crates/astria-celestia-client/src/metrics_init.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Crate-specific metrics functionality. -//! -//! Registers metrics & lists constants to be used as metric names throughout crate. - -use metrics::{ - describe_gauge, - gauge, - Unit, -}; - -/// Registers all metrics used by this crate. -pub fn register() { - gauge!(ROLLUP_BLOBS_PER_ASTRIA_BLOCK, "lib" => env!("CARGO_CRATE_NAME")); - describe_gauge!( - ROLLUP_BLOBS_PER_ASTRIA_BLOCK, - Unit::Count, - "The number of rollup blobs generated from a single astria sequencer block" - ); - - gauge!(ROLLUP_BLOBS_PER_CELESTIA_TX, "lib" => env!("CARGO_CRATE_NAME")); - describe_gauge!( - ROLLUP_BLOBS_PER_CELESTIA_TX, - Unit::Count, - "The total number of rollup blobs included in the last Celestia submission" - ); -} - -pub const ROLLUP_BLOBS_PER_ASTRIA_BLOCK: &str = - concat!(env!("CARGO_CRATE_NAME"), "_rollups_blobs_per_astria_block"); -pub const ROLLUP_BLOBS_PER_CELESTIA_TX: &str = - concat!(env!("CARGO_CRATE_NAME"), "_rollup_blobs_per_celestia_tx"); diff --git a/crates/astria-celestia-client/src/submission.rs b/crates/astria-celestia-client/src/submission.rs deleted file mode 100644 index b939bb71d7..0000000000 --- a/crates/astria-celestia-client/src/submission.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Logic to convert sequencer blocks to celestia blobs before submission. - -use astria_core::{ - primitive::v1::RollupId, - sequencerblock::v1alpha1::SequencerBlock, -}; -use celestia_types::Blob; -use prost::Message as _; - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct ToBlobsError(ToBlobsErrorKind); - -impl ToBlobsError { - fn rollup(source: celestia_types::Error, rollup_id: RollupId) -> Self { - Self(ToBlobsErrorKind::Rollup { - source, - rollup_id, - }) - } - - fn sequencer(source: celestia_types::Error) -> Self { - Self(ToBlobsErrorKind::Sequencer(source)) - } -} - -#[derive(Debug, thiserror::Error)] -enum ToBlobsErrorKind { - #[error( - "failed converting sequencer block subset for rollup with ID `{rollup_id}` to Celestia \ - blob" - )] - Rollup { - source: celestia_types::Error, - rollup_id: RollupId, - }, - #[error("failed converting sequencer block metadata to Celestia blob")] - Sequencer(#[source] celestia_types::Error), -} - -pub trait ToBlobs: Sized { - /// Convert a sequencer block to a sequence of blobs, writing them to `blobs`. - /// - /// If conversion of the sequencer block fails `blobs` is left unchanged. - /// - /// # Errors - /// - /// Returns an error if conversion to a Celestia blob failed. See `[Blob::new]` - /// for more information. - fn try_to_blobs(self, blobs: &mut Vec) -> Result<(), ToBlobsError>; -} - -impl ToBlobs for SequencerBlock { - fn try_to_blobs(self, blobs: &mut Vec) -> Result<(), ToBlobsError> { - let initial_len = blobs.len(); - if let Err(e) = convert(self, blobs) { - blobs.truncate(initial_len); - return Err(e); - } - Ok(()) - } -} - -fn convert(block: SequencerBlock, blobs: &mut Vec) -> Result<(), ToBlobsError> { - let (sequencer_blob, rollup_blobs) = block.into_celestia_blobs(); - // Allocate extra space: one blob for the sequencer blob "header", - // the rest for the rollup blobs. - blobs.reserve(rollup_blobs.len() + 1); - let sequencer_namespace = - crate::celestia_namespace_v0_from_str(sequencer_blob.header().chain_id().as_str()); - - let header_blob = Blob::new( - sequencer_namespace, - sequencer_blob.into_raw().encode_to_vec(), - ) - .map_err(ToBlobsError::sequencer)?; - blobs.push(header_blob); - for blob in rollup_blobs { - let rollup_id = blob.rollup_id(); - let namespace = crate::celestia_namespace_v0_from_rollup_id(rollup_id); - let blob = Blob::new(namespace, blob.into_raw().encode_to_vec()) - .map_err(move |source| ToBlobsError::rollup(source, rollup_id))?; - blobs.push(blob); - } - Ok(()) -} diff --git a/crates/astria-celestia-mock/Cargo.toml b/crates/astria-celestia-mock/Cargo.toml deleted file mode 100644 index 090f9da3e1..0000000000 --- a/crates/astria-celestia-mock/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "astria-celestia-mock" -version = "0.1.0" -edition = "2021" -rust-version = "1.73" -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/astriaorg/astria" -homepage = "https://astria.org" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -celestia-client = { package = "astria-celestia-client", path = "../astria-celestia-client" } -jsonrpsee = { workspace = true, features = ["macros", "server"] } diff --git a/crates/astria-celestia-mock/README.md b/crates/astria-celestia-mock/README.md deleted file mode 100644 index 7953dbe9bd..0000000000 --- a/crates/astria-celestia-mock/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Celestia-node JSONRPC server traits - -This crate contains a minimal number of traits that are used -for mocking a celestia node jsonrpc server. The main use is -for blackbox tests of sequencer-relayer. diff --git a/crates/astria-celestia-mock/src/lib.rs b/crates/astria-celestia-mock/src/lib.rs deleted file mode 100644 index 14c8f07108..0000000000 --- a/crates/astria-celestia-mock/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod rpc_impl; - -pub use rpc_impl::{ - blob::BlobServer, - header::HeaderServer, -}; diff --git a/crates/astria-celestia-mock/src/rpc_impl/blob.rs b/crates/astria-celestia-mock/src/rpc_impl/blob.rs deleted file mode 100644 index e2438a63cc..0000000000 --- a/crates/astria-celestia-mock/src/rpc_impl/blob.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// The Celestia JSON RPC blob API. -/// -/// This currently only provides a wrapper for the `blob.Submit` RPC method. -use celestia_client::celestia_types::{ - blob::SubmitOptions, - Blob, -}; -use jsonrpsee::proc_macros::rpc; -// This only needs to be explicitly imported when activaing the server feature -// due to a quirk in the jsonrpsee proc macro. -use jsonrpsee::types::ErrorObjectOwned; - -#[rpc(server)] -pub trait Blob { - #[method(name = "blob.Submit")] - async fn blob_submit( - &self, - blobs: Vec, - opts: SubmitOptions, - ) -> Result; -} diff --git a/crates/astria-celestia-mock/src/rpc_impl/header.rs b/crates/astria-celestia-mock/src/rpc_impl/header.rs deleted file mode 100644 index 9da917de09..0000000000 --- a/crates/astria-celestia-mock/src/rpc_impl/header.rs +++ /dev/null @@ -1,14 +0,0 @@ -/// The Celestia JSON RPC header API. -/// -/// This currently only provides a wrapper for the `header.NetworkHead` RPC method. -use celestia_client::celestia_types::ExtendedHeader; -use jsonrpsee::proc_macros::rpc; -// This only needs to be explicitly imported when activaing the server feature -// due to a quirk in the jsonrpsee proc macro. -use jsonrpsee::types::ErrorObjectOwned; - -#[rpc(server)] -pub trait Header { - #[method(name = "header.NetworkHead")] - async fn header_network_head(&self) -> Result; -} diff --git a/crates/astria-celestia-mock/src/rpc_impl/mod.rs b/crates/astria-celestia-mock/src/rpc_impl/mod.rs deleted file mode 100644 index def1a2cfb1..0000000000 --- a/crates/astria-celestia-mock/src/rpc_impl/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod blob; -pub mod header; diff --git a/crates/astria-core/src/generated/celestia.blob.v1.serde.rs b/crates/astria-core/src/generated/celestia.blob.v1.serde.rs new file mode 100644 index 0000000000..954bd364db --- /dev/null +++ b/crates/astria-core/src/generated/celestia.blob.v1.serde.rs @@ -0,0 +1,451 @@ +impl serde::Serialize for MsgPayForBlobs { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.signer.is_empty() { + len += 1; + } + if !self.namespaces.is_empty() { + len += 1; + } + if !self.blob_sizes.is_empty() { + len += 1; + } + if !self.share_commitments.is_empty() { + len += 1; + } + if !self.share_versions.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("celestia.blob.v1.MsgPayForBlobs", len)?; + if !self.signer.is_empty() { + struct_ser.serialize_field("signer", &self.signer)?; + } + if !self.namespaces.is_empty() { + struct_ser.serialize_field("namespaces", &self.namespaces.iter().map(pbjson::private::base64::encode).collect::>())?; + } + if !self.blob_sizes.is_empty() { + struct_ser.serialize_field("blob_sizes", &self.blob_sizes)?; + } + if !self.share_commitments.is_empty() { + struct_ser.serialize_field("share_commitments", &self.share_commitments.iter().map(pbjson::private::base64::encode).collect::>())?; + } + if !self.share_versions.is_empty() { + struct_ser.serialize_field("share_versions", &self.share_versions)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MsgPayForBlobs { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "signer", + "namespaces", + "blob_sizes", + "blobSizes", + "share_commitments", + "shareCommitments", + "share_versions", + "shareVersions", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Signer, + Namespaces, + BlobSizes, + ShareCommitments, + ShareVersions, + } + 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 { + "signer" => Ok(GeneratedField::Signer), + "namespaces" => Ok(GeneratedField::Namespaces), + "blobSizes" | "blob_sizes" => Ok(GeneratedField::BlobSizes), + "shareCommitments" | "share_commitments" => Ok(GeneratedField::ShareCommitments), + "shareVersions" | "share_versions" => Ok(GeneratedField::ShareVersions), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MsgPayForBlobs; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct celestia.blob.v1.MsgPayForBlobs") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut signer__ = None; + let mut namespaces__ = None; + let mut blob_sizes__ = None; + let mut share_commitments__ = None; + let mut share_versions__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Signer => { + if signer__.is_some() { + return Err(serde::de::Error::duplicate_field("signer")); + } + signer__ = Some(map_.next_value()?); + } + GeneratedField::Namespaces => { + if namespaces__.is_some() { + return Err(serde::de::Error::duplicate_field("namespaces")); + } + namespaces__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + GeneratedField::BlobSizes => { + if blob_sizes__.is_some() { + return Err(serde::de::Error::duplicate_field("blobSizes")); + } + blob_sizes__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + GeneratedField::ShareCommitments => { + if share_commitments__.is_some() { + return Err(serde::de::Error::duplicate_field("shareCommitments")); + } + share_commitments__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + GeneratedField::ShareVersions => { + if share_versions__.is_some() { + return Err(serde::de::Error::duplicate_field("shareVersions")); + } + share_versions__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + } + } + Ok(MsgPayForBlobs { + signer: signer__.unwrap_or_default(), + namespaces: namespaces__.unwrap_or_default(), + blob_sizes: blob_sizes__.unwrap_or_default(), + share_commitments: share_commitments__.unwrap_or_default(), + share_versions: share_versions__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("celestia.blob.v1.MsgPayForBlobs", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Params { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.gas_per_blob_byte != 0 { + len += 1; + } + if self.gov_max_square_size != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("celestia.blob.v1.Params", len)?; + if self.gas_per_blob_byte != 0 { + struct_ser.serialize_field("gas_per_blob_byte", &self.gas_per_blob_byte)?; + } + if self.gov_max_square_size != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("gov_max_square_size", ToString::to_string(&self.gov_max_square_size).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Params { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "gas_per_blob_byte", + "gasPerBlobByte", + "gov_max_square_size", + "govMaxSquareSize", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + GasPerBlobByte, + GovMaxSquareSize, + } + 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 { + "gasPerBlobByte" | "gas_per_blob_byte" => Ok(GeneratedField::GasPerBlobByte), + "govMaxSquareSize" | "gov_max_square_size" => Ok(GeneratedField::GovMaxSquareSize), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Params; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct celestia.blob.v1.Params") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut gas_per_blob_byte__ = None; + let mut gov_max_square_size__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::GasPerBlobByte => { + if gas_per_blob_byte__.is_some() { + return Err(serde::de::Error::duplicate_field("gasPerBlobByte")); + } + gas_per_blob_byte__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::GovMaxSquareSize => { + if gov_max_square_size__.is_some() { + return Err(serde::de::Error::duplicate_field("govMaxSquareSize")); + } + gov_max_square_size__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(Params { + gas_per_blob_byte: gas_per_blob_byte__.unwrap_or_default(), + gov_max_square_size: gov_max_square_size__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("celestia.blob.v1.Params", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for QueryParamsRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("celestia.blob.v1.QueryParamsRequest", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for QueryParamsRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + } + 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, + { + Err(serde::de::Error::unknown_field(value, FIELDS)) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = QueryParamsRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct celestia.blob.v1.QueryParamsRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(QueryParamsRequest { + }) + } + } + deserializer.deserialize_struct("celestia.blob.v1.QueryParamsRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for QueryParamsResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.params.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("celestia.blob.v1.QueryParamsResponse", len)?; + if let Some(v) = self.params.as_ref() { + struct_ser.serialize_field("params", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for QueryParamsResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "params", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Params, + } + 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 { + "params" => Ok(GeneratedField::Params), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = QueryParamsResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct celestia.blob.v1.QueryParamsResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut params__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Params => { + if params__.is_some() { + return Err(serde::de::Error::duplicate_field("params")); + } + params__ = map_.next_value()?; + } + } + } + Ok(QueryParamsResponse { + params: params__, + }) + } + } + deserializer.deserialize_struct("celestia.blob.v1.QueryParamsResponse", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.auth.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.auth.v1beta1.serde.rs new file mode 100644 index 0000000000..7d488b65ee --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.auth.v1beta1.serde.rs @@ -0,0 +1,673 @@ +impl serde::Serialize for BaseAccount { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.address.is_empty() { + len += 1; + } + if self.pub_key.is_some() { + len += 1; + } + if self.account_number != 0 { + len += 1; + } + if self.sequence != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.auth.v1beta1.BaseAccount", len)?; + if !self.address.is_empty() { + struct_ser.serialize_field("address", &self.address)?; + } + if let Some(v) = self.pub_key.as_ref() { + struct_ser.serialize_field("pub_key", v)?; + } + if self.account_number != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("account_number", ToString::to_string(&self.account_number).as_str())?; + } + if self.sequence != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("sequence", ToString::to_string(&self.sequence).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for BaseAccount { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "pub_key", + "pubKey", + "account_number", + "accountNumber", + "sequence", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + PubKey, + AccountNumber, + Sequence, + } + 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 { + "address" => Ok(GeneratedField::Address), + "pubKey" | "pub_key" => Ok(GeneratedField::PubKey), + "accountNumber" | "account_number" => Ok(GeneratedField::AccountNumber), + "sequence" => Ok(GeneratedField::Sequence), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = BaseAccount; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.auth.v1beta1.BaseAccount") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut pub_key__ = None; + let mut account_number__ = None; + let mut sequence__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = Some(map_.next_value()?); + } + GeneratedField::PubKey => { + if pub_key__.is_some() { + return Err(serde::de::Error::duplicate_field("pubKey")); + } + pub_key__ = map_.next_value()?; + } + GeneratedField::AccountNumber => { + if account_number__.is_some() { + return Err(serde::de::Error::duplicate_field("accountNumber")); + } + account_number__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Sequence => { + if sequence__.is_some() { + return Err(serde::de::Error::duplicate_field("sequence")); + } + sequence__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(BaseAccount { + address: address__.unwrap_or_default(), + pub_key: pub_key__, + account_number: account_number__.unwrap_or_default(), + sequence: sequence__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.auth.v1beta1.BaseAccount", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Params { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.max_memo_characters != 0 { + len += 1; + } + if self.tx_sig_limit != 0 { + len += 1; + } + if self.tx_size_cost_per_byte != 0 { + len += 1; + } + if self.sig_verify_cost_ed25519 != 0 { + len += 1; + } + if self.sig_verify_cost_secp256k1 != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.auth.v1beta1.Params", len)?; + if self.max_memo_characters != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("max_memo_characters", ToString::to_string(&self.max_memo_characters).as_str())?; + } + if self.tx_sig_limit != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("tx_sig_limit", ToString::to_string(&self.tx_sig_limit).as_str())?; + } + if self.tx_size_cost_per_byte != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("tx_size_cost_per_byte", ToString::to_string(&self.tx_size_cost_per_byte).as_str())?; + } + if self.sig_verify_cost_ed25519 != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("sig_verify_cost_ed25519", ToString::to_string(&self.sig_verify_cost_ed25519).as_str())?; + } + if self.sig_verify_cost_secp256k1 != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("sig_verify_cost_secp256k1", ToString::to_string(&self.sig_verify_cost_secp256k1).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Params { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "max_memo_characters", + "maxMemoCharacters", + "tx_sig_limit", + "txSigLimit", + "tx_size_cost_per_byte", + "txSizeCostPerByte", + "sig_verify_cost_ed25519", + "sigVerifyCostEd25519", + "sig_verify_cost_secp256k1", + "sigVerifyCostSecp256k1", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + MaxMemoCharacters, + TxSigLimit, + TxSizeCostPerByte, + SigVerifyCostEd25519, + SigVerifyCostSecp256k1, + } + 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 { + "maxMemoCharacters" | "max_memo_characters" => Ok(GeneratedField::MaxMemoCharacters), + "txSigLimit" | "tx_sig_limit" => Ok(GeneratedField::TxSigLimit), + "txSizeCostPerByte" | "tx_size_cost_per_byte" => Ok(GeneratedField::TxSizeCostPerByte), + "sigVerifyCostEd25519" | "sig_verify_cost_ed25519" => Ok(GeneratedField::SigVerifyCostEd25519), + "sigVerifyCostSecp256k1" | "sig_verify_cost_secp256k1" => Ok(GeneratedField::SigVerifyCostSecp256k1), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Params; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.auth.v1beta1.Params") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut max_memo_characters__ = None; + let mut tx_sig_limit__ = None; + let mut tx_size_cost_per_byte__ = None; + let mut sig_verify_cost_ed25519__ = None; + let mut sig_verify_cost_secp256k1__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::MaxMemoCharacters => { + if max_memo_characters__.is_some() { + return Err(serde::de::Error::duplicate_field("maxMemoCharacters")); + } + max_memo_characters__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TxSigLimit => { + if tx_sig_limit__.is_some() { + return Err(serde::de::Error::duplicate_field("txSigLimit")); + } + tx_sig_limit__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::TxSizeCostPerByte => { + if tx_size_cost_per_byte__.is_some() { + return Err(serde::de::Error::duplicate_field("txSizeCostPerByte")); + } + tx_size_cost_per_byte__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::SigVerifyCostEd25519 => { + if sig_verify_cost_ed25519__.is_some() { + return Err(serde::de::Error::duplicate_field("sigVerifyCostEd25519")); + } + sig_verify_cost_ed25519__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::SigVerifyCostSecp256k1 => { + if sig_verify_cost_secp256k1__.is_some() { + return Err(serde::de::Error::duplicate_field("sigVerifyCostSecp256k1")); + } + sig_verify_cost_secp256k1__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(Params { + max_memo_characters: max_memo_characters__.unwrap_or_default(), + tx_sig_limit: tx_sig_limit__.unwrap_or_default(), + tx_size_cost_per_byte: tx_size_cost_per_byte__.unwrap_or_default(), + sig_verify_cost_ed25519: sig_verify_cost_ed25519__.unwrap_or_default(), + sig_verify_cost_secp256k1: sig_verify_cost_secp256k1__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.auth.v1beta1.Params", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for QueryAccountRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.address.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.auth.v1beta1.QueryAccountRequest", len)?; + if !self.address.is_empty() { + struct_ser.serialize_field("address", &self.address)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for QueryAccountRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + } + 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 { + "address" => Ok(GeneratedField::Address), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = QueryAccountRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.auth.v1beta1.QueryAccountRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = Some(map_.next_value()?); + } + } + } + Ok(QueryAccountRequest { + address: address__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.auth.v1beta1.QueryAccountRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for QueryAccountResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.account.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.auth.v1beta1.QueryAccountResponse", len)?; + if let Some(v) = self.account.as_ref() { + struct_ser.serialize_field("account", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for QueryAccountResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "account", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Account, + } + 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 { + "account" => Ok(GeneratedField::Account), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = QueryAccountResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.auth.v1beta1.QueryAccountResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut account__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Account => { + if account__.is_some() { + return Err(serde::de::Error::duplicate_field("account")); + } + account__ = map_.next_value()?; + } + } + } + Ok(QueryAccountResponse { + account: account__, + }) + } + } + deserializer.deserialize_struct("cosmos.auth.v1beta1.QueryAccountResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for QueryParamsRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("cosmos.auth.v1beta1.QueryParamsRequest", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for QueryParamsRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + } + 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, + { + Err(serde::de::Error::unknown_field(value, FIELDS)) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = QueryParamsRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.auth.v1beta1.QueryParamsRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(QueryParamsRequest { + }) + } + } + deserializer.deserialize_struct("cosmos.auth.v1beta1.QueryParamsRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for QueryParamsResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.params.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.auth.v1beta1.QueryParamsResponse", len)?; + if let Some(v) = self.params.as_ref() { + struct_ser.serialize_field("params", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for QueryParamsResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "params", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Params, + } + 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 { + "params" => Ok(GeneratedField::Params), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = QueryParamsResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.auth.v1beta1.QueryParamsResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut params__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Params => { + if params__.is_some() { + return Err(serde::de::Error::duplicate_field("params")); + } + params__ = map_.next_value()?; + } + } + } + Ok(QueryParamsResponse { + params: params__, + }) + } + } + deserializer.deserialize_struct("cosmos.auth.v1beta1.QueryParamsResponse", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.base.abci.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.base.abci.v1beta1.serde.rs new file mode 100644 index 0000000000..4bf2cc8df6 --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.base.abci.v1beta1.serde.rs @@ -0,0 +1,653 @@ +impl serde::Serialize for AbciMessageLog { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.msg_index != 0 { + len += 1; + } + if !self.log.is_empty() { + len += 1; + } + if !self.events.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.abci.v1beta1.ABCIMessageLog", len)?; + if self.msg_index != 0 { + struct_ser.serialize_field("msg_index", &self.msg_index)?; + } + if !self.log.is_empty() { + struct_ser.serialize_field("log", &self.log)?; + } + if !self.events.is_empty() { + struct_ser.serialize_field("events", &self.events)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AbciMessageLog { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "msg_index", + "msgIndex", + "log", + "events", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + MsgIndex, + Log, + Events, + } + 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 { + "msgIndex" | "msg_index" => Ok(GeneratedField::MsgIndex), + "log" => Ok(GeneratedField::Log), + "events" => Ok(GeneratedField::Events), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AbciMessageLog; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.abci.v1beta1.ABCIMessageLog") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut msg_index__ = None; + let mut log__ = None; + let mut events__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::MsgIndex => { + if msg_index__.is_some() { + return Err(serde::de::Error::duplicate_field("msgIndex")); + } + msg_index__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Log => { + if log__.is_some() { + return Err(serde::de::Error::duplicate_field("log")); + } + log__ = Some(map_.next_value()?); + } + GeneratedField::Events => { + if events__.is_some() { + return Err(serde::de::Error::duplicate_field("events")); + } + events__ = Some(map_.next_value()?); + } + } + } + Ok(AbciMessageLog { + msg_index: msg_index__.unwrap_or_default(), + log: log__.unwrap_or_default(), + events: events__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.abci.v1beta1.ABCIMessageLog", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Attribute { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.key.is_empty() { + len += 1; + } + if !self.value.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.abci.v1beta1.Attribute", len)?; + if !self.key.is_empty() { + struct_ser.serialize_field("key", &self.key)?; + } + if !self.value.is_empty() { + struct_ser.serialize_field("value", &self.value)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Attribute { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "key", + "value", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Key, + Value, + } + 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 { + "key" => Ok(GeneratedField::Key), + "value" => Ok(GeneratedField::Value), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Attribute; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.abci.v1beta1.Attribute") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut key__ = None; + let mut value__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = Some(map_.next_value()?); + } + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = Some(map_.next_value()?); + } + } + } + Ok(Attribute { + key: key__.unwrap_or_default(), + value: value__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.abci.v1beta1.Attribute", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for StringEvent { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.r#type.is_empty() { + len += 1; + } + if !self.attributes.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.abci.v1beta1.StringEvent", len)?; + if !self.r#type.is_empty() { + struct_ser.serialize_field("type", &self.r#type)?; + } + if !self.attributes.is_empty() { + struct_ser.serialize_field("attributes", &self.attributes)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for StringEvent { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "type", + "attributes", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Type, + Attributes, + } + 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 { + "type" => Ok(GeneratedField::Type), + "attributes" => Ok(GeneratedField::Attributes), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = StringEvent; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.abci.v1beta1.StringEvent") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut r#type__ = None; + let mut attributes__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Type => { + if r#type__.is_some() { + return Err(serde::de::Error::duplicate_field("type")); + } + r#type__ = Some(map_.next_value()?); + } + GeneratedField::Attributes => { + if attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("attributes")); + } + attributes__ = Some(map_.next_value()?); + } + } + } + Ok(StringEvent { + r#type: r#type__.unwrap_or_default(), + attributes: attributes__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.abci.v1beta1.StringEvent", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for TxResponse { + #[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; + } + if !self.txhash.is_empty() { + len += 1; + } + if !self.codespace.is_empty() { + len += 1; + } + if self.code != 0 { + len += 1; + } + if !self.data.is_empty() { + len += 1; + } + if !self.raw_log.is_empty() { + len += 1; + } + if !self.logs.is_empty() { + len += 1; + } + if !self.info.is_empty() { + len += 1; + } + if self.gas_wanted != 0 { + len += 1; + } + if self.gas_used != 0 { + len += 1; + } + if self.tx.is_some() { + len += 1; + } + if !self.timestamp.is_empty() { + len += 1; + } + if !self.events.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.abci.v1beta1.TxResponse", len)?; + if self.height != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; + } + if !self.txhash.is_empty() { + struct_ser.serialize_field("txhash", &self.txhash)?; + } + if !self.codespace.is_empty() { + struct_ser.serialize_field("codespace", &self.codespace)?; + } + if self.code != 0 { + struct_ser.serialize_field("code", &self.code)?; + } + if !self.data.is_empty() { + struct_ser.serialize_field("data", &self.data)?; + } + if !self.raw_log.is_empty() { + struct_ser.serialize_field("raw_log", &self.raw_log)?; + } + if !self.logs.is_empty() { + struct_ser.serialize_field("logs", &self.logs)?; + } + if !self.info.is_empty() { + struct_ser.serialize_field("info", &self.info)?; + } + if self.gas_wanted != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("gas_wanted", ToString::to_string(&self.gas_wanted).as_str())?; + } + if self.gas_used != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("gas_used", ToString::to_string(&self.gas_used).as_str())?; + } + if let Some(v) = self.tx.as_ref() { + struct_ser.serialize_field("tx", v)?; + } + if !self.timestamp.is_empty() { + struct_ser.serialize_field("timestamp", &self.timestamp)?; + } + if !self.events.is_empty() { + struct_ser.serialize_field("events", &self.events)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TxResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "height", + "txhash", + "codespace", + "code", + "data", + "raw_log", + "rawLog", + "logs", + "info", + "gas_wanted", + "gasWanted", + "gas_used", + "gasUsed", + "tx", + "timestamp", + "events", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Height, + Txhash, + Codespace, + Code, + Data, + RawLog, + Logs, + Info, + GasWanted, + GasUsed, + Tx, + Timestamp, + Events, + } + 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), + "txhash" => Ok(GeneratedField::Txhash), + "codespace" => Ok(GeneratedField::Codespace), + "code" => Ok(GeneratedField::Code), + "data" => Ok(GeneratedField::Data), + "rawLog" | "raw_log" => Ok(GeneratedField::RawLog), + "logs" => Ok(GeneratedField::Logs), + "info" => Ok(GeneratedField::Info), + "gasWanted" | "gas_wanted" => Ok(GeneratedField::GasWanted), + "gasUsed" | "gas_used" => Ok(GeneratedField::GasUsed), + "tx" => Ok(GeneratedField::Tx), + "timestamp" => Ok(GeneratedField::Timestamp), + "events" => Ok(GeneratedField::Events), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TxResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.abci.v1beta1.TxResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut height__ = None; + let mut txhash__ = None; + let mut codespace__ = None; + let mut code__ = None; + let mut data__ = None; + let mut raw_log__ = None; + let mut logs__ = None; + let mut info__ = None; + let mut gas_wanted__ = None; + let mut gas_used__ = None; + let mut tx__ = None; + let mut timestamp__ = None; + let mut events__ = 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) + ; + } + GeneratedField::Txhash => { + if txhash__.is_some() { + return Err(serde::de::Error::duplicate_field("txhash")); + } + txhash__ = Some(map_.next_value()?); + } + GeneratedField::Codespace => { + if codespace__.is_some() { + return Err(serde::de::Error::duplicate_field("codespace")); + } + codespace__ = Some(map_.next_value()?); + } + GeneratedField::Code => { + if code__.is_some() { + return Err(serde::de::Error::duplicate_field("code")); + } + code__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Data => { + if data__.is_some() { + return Err(serde::de::Error::duplicate_field("data")); + } + data__ = Some(map_.next_value()?); + } + GeneratedField::RawLog => { + if raw_log__.is_some() { + return Err(serde::de::Error::duplicate_field("rawLog")); + } + raw_log__ = Some(map_.next_value()?); + } + GeneratedField::Logs => { + if logs__.is_some() { + return Err(serde::de::Error::duplicate_field("logs")); + } + logs__ = Some(map_.next_value()?); + } + GeneratedField::Info => { + if info__.is_some() { + return Err(serde::de::Error::duplicate_field("info")); + } + info__ = Some(map_.next_value()?); + } + GeneratedField::GasWanted => { + if gas_wanted__.is_some() { + return Err(serde::de::Error::duplicate_field("gasWanted")); + } + gas_wanted__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::GasUsed => { + if gas_used__.is_some() { + return Err(serde::de::Error::duplicate_field("gasUsed")); + } + gas_used__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Tx => { + if tx__.is_some() { + return Err(serde::de::Error::duplicate_field("tx")); + } + tx__ = map_.next_value()?; + } + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = Some(map_.next_value()?); + } + GeneratedField::Events => { + if events__.is_some() { + return Err(serde::de::Error::duplicate_field("events")); + } + events__ = Some(map_.next_value()?); + } + } + } + Ok(TxResponse { + height: height__.unwrap_or_default(), + txhash: txhash__.unwrap_or_default(), + codespace: codespace__.unwrap_or_default(), + code: code__.unwrap_or_default(), + data: data__.unwrap_or_default(), + raw_log: raw_log__.unwrap_or_default(), + logs: logs__.unwrap_or_default(), + info: info__.unwrap_or_default(), + gas_wanted: gas_wanted__.unwrap_or_default(), + gas_used: gas_used__.unwrap_or_default(), + tx: tx__, + timestamp: timestamp__.unwrap_or_default(), + events: events__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.abci.v1beta1.TxResponse", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.base.node.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.base.node.v1beta1.serde.rs new file mode 100644 index 0000000000..e94f573ea3 --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.base.node.v1beta1.serde.rs @@ -0,0 +1,163 @@ +impl serde::Serialize for ConfigRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("cosmos.base.node.v1beta1.ConfigRequest", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ConfigRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + } + 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, + { + Err(serde::de::Error::unknown_field(value, FIELDS)) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ConfigRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.node.v1beta1.ConfigRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(ConfigRequest { + }) + } + } + deserializer.deserialize_struct("cosmos.base.node.v1beta1.ConfigRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ConfigResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.minimum_gas_price.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.node.v1beta1.ConfigResponse", len)?; + if !self.minimum_gas_price.is_empty() { + struct_ser.serialize_field("minimum_gas_price", &self.minimum_gas_price)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ConfigResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "minimum_gas_price", + "minimumGasPrice", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + MinimumGasPrice, + } + 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 { + "minimumGasPrice" | "minimum_gas_price" => Ok(GeneratedField::MinimumGasPrice), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ConfigResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.node.v1beta1.ConfigResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut minimum_gas_price__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::MinimumGasPrice => { + if minimum_gas_price__.is_some() { + return Err(serde::de::Error::duplicate_field("minimumGasPrice")); + } + minimum_gas_price__ = Some(map_.next_value()?); + } + } + } + Ok(ConfigResponse { + minimum_gas_price: minimum_gas_price__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.node.v1beta1.ConfigResponse", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.base.tendermint.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.base.tendermint.v1beta1.serde.rs new file mode 100644 index 0000000000..1c8572b0c8 --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.base.tendermint.v1beta1.serde.rs @@ -0,0 +1,522 @@ +impl serde::Serialize for GetNodeInfoRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("cosmos.base.tendermint.v1beta1.GetNodeInfoRequest", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetNodeInfoRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + } + 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, + { + Err(serde::de::Error::unknown_field(value, FIELDS)) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetNodeInfoRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.tendermint.v1beta1.GetNodeInfoRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(GetNodeInfoRequest { + }) + } + } + deserializer.deserialize_struct("cosmos.base.tendermint.v1beta1.GetNodeInfoRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetNodeInfoResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.default_node_info.is_some() { + len += 1; + } + if self.application_version.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.tendermint.v1beta1.GetNodeInfoResponse", len)?; + if let Some(v) = self.default_node_info.as_ref() { + struct_ser.serialize_field("default_node_info", v)?; + } + if let Some(v) = self.application_version.as_ref() { + struct_ser.serialize_field("application_version", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetNodeInfoResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "default_node_info", + "defaultNodeInfo", + "application_version", + "applicationVersion", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + DefaultNodeInfo, + ApplicationVersion, + } + 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 { + "defaultNodeInfo" | "default_node_info" => Ok(GeneratedField::DefaultNodeInfo), + "applicationVersion" | "application_version" => Ok(GeneratedField::ApplicationVersion), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetNodeInfoResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.tendermint.v1beta1.GetNodeInfoResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut default_node_info__ = None; + let mut application_version__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::DefaultNodeInfo => { + if default_node_info__.is_some() { + return Err(serde::de::Error::duplicate_field("defaultNodeInfo")); + } + default_node_info__ = map_.next_value()?; + } + GeneratedField::ApplicationVersion => { + if application_version__.is_some() { + return Err(serde::de::Error::duplicate_field("applicationVersion")); + } + application_version__ = map_.next_value()?; + } + } + } + Ok(GetNodeInfoResponse { + default_node_info: default_node_info__, + application_version: application_version__, + }) + } + } + deserializer.deserialize_struct("cosmos.base.tendermint.v1beta1.GetNodeInfoResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Module { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.path.is_empty() { + len += 1; + } + if !self.version.is_empty() { + len += 1; + } + if !self.sum.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.tendermint.v1beta1.Module", len)?; + if !self.path.is_empty() { + struct_ser.serialize_field("path", &self.path)?; + } + if !self.version.is_empty() { + struct_ser.serialize_field("version", &self.version)?; + } + if !self.sum.is_empty() { + struct_ser.serialize_field("sum", &self.sum)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Module { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "path", + "version", + "sum", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Path, + Version, + Sum, + } + 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 { + "path" => Ok(GeneratedField::Path), + "version" => Ok(GeneratedField::Version), + "sum" => Ok(GeneratedField::Sum), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Module; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.tendermint.v1beta1.Module") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut path__ = None; + let mut version__ = None; + let mut sum__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Path => { + if path__.is_some() { + return Err(serde::de::Error::duplicate_field("path")); + } + path__ = Some(map_.next_value()?); + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = Some(map_.next_value()?); + } + GeneratedField::Sum => { + if sum__.is_some() { + return Err(serde::de::Error::duplicate_field("sum")); + } + sum__ = Some(map_.next_value()?); + } + } + } + Ok(Module { + path: path__.unwrap_or_default(), + version: version__.unwrap_or_default(), + sum: sum__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.tendermint.v1beta1.Module", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for VersionInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.name.is_empty() { + len += 1; + } + if !self.app_name.is_empty() { + len += 1; + } + if !self.version.is_empty() { + len += 1; + } + if !self.git_commit.is_empty() { + len += 1; + } + if !self.build_tags.is_empty() { + len += 1; + } + if !self.go_version.is_empty() { + len += 1; + } + if !self.build_deps.is_empty() { + len += 1; + } + if !self.cosmos_sdk_version.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.tendermint.v1beta1.VersionInfo", len)?; + if !self.name.is_empty() { + struct_ser.serialize_field("name", &self.name)?; + } + if !self.app_name.is_empty() { + struct_ser.serialize_field("app_name", &self.app_name)?; + } + if !self.version.is_empty() { + struct_ser.serialize_field("version", &self.version)?; + } + if !self.git_commit.is_empty() { + struct_ser.serialize_field("git_commit", &self.git_commit)?; + } + if !self.build_tags.is_empty() { + struct_ser.serialize_field("build_tags", &self.build_tags)?; + } + if !self.go_version.is_empty() { + struct_ser.serialize_field("go_version", &self.go_version)?; + } + if !self.build_deps.is_empty() { + struct_ser.serialize_field("build_deps", &self.build_deps)?; + } + if !self.cosmos_sdk_version.is_empty() { + struct_ser.serialize_field("cosmos_sdk_version", &self.cosmos_sdk_version)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for VersionInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "name", + "app_name", + "appName", + "version", + "git_commit", + "gitCommit", + "build_tags", + "buildTags", + "go_version", + "goVersion", + "build_deps", + "buildDeps", + "cosmos_sdk_version", + "cosmosSdkVersion", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Name, + AppName, + Version, + GitCommit, + BuildTags, + GoVersion, + BuildDeps, + CosmosSdkVersion, + } + 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 { + "name" => Ok(GeneratedField::Name), + "appName" | "app_name" => Ok(GeneratedField::AppName), + "version" => Ok(GeneratedField::Version), + "gitCommit" | "git_commit" => Ok(GeneratedField::GitCommit), + "buildTags" | "build_tags" => Ok(GeneratedField::BuildTags), + "goVersion" | "go_version" => Ok(GeneratedField::GoVersion), + "buildDeps" | "build_deps" => Ok(GeneratedField::BuildDeps), + "cosmosSdkVersion" | "cosmos_sdk_version" => Ok(GeneratedField::CosmosSdkVersion), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = VersionInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.tendermint.v1beta1.VersionInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut name__ = None; + let mut app_name__ = None; + let mut version__ = None; + let mut git_commit__ = None; + let mut build_tags__ = None; + let mut go_version__ = None; + let mut build_deps__ = None; + let mut cosmos_sdk_version__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Name => { + if name__.is_some() { + return Err(serde::de::Error::duplicate_field("name")); + } + name__ = Some(map_.next_value()?); + } + GeneratedField::AppName => { + if app_name__.is_some() { + return Err(serde::de::Error::duplicate_field("appName")); + } + app_name__ = Some(map_.next_value()?); + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = Some(map_.next_value()?); + } + GeneratedField::GitCommit => { + if git_commit__.is_some() { + return Err(serde::de::Error::duplicate_field("gitCommit")); + } + git_commit__ = Some(map_.next_value()?); + } + GeneratedField::BuildTags => { + if build_tags__.is_some() { + return Err(serde::de::Error::duplicate_field("buildTags")); + } + build_tags__ = Some(map_.next_value()?); + } + GeneratedField::GoVersion => { + if go_version__.is_some() { + return Err(serde::de::Error::duplicate_field("goVersion")); + } + go_version__ = Some(map_.next_value()?); + } + GeneratedField::BuildDeps => { + if build_deps__.is_some() { + return Err(serde::de::Error::duplicate_field("buildDeps")); + } + build_deps__ = Some(map_.next_value()?); + } + GeneratedField::CosmosSdkVersion => { + if cosmos_sdk_version__.is_some() { + return Err(serde::de::Error::duplicate_field("cosmosSdkVersion")); + } + cosmos_sdk_version__ = Some(map_.next_value()?); + } + } + } + Ok(VersionInfo { + name: name__.unwrap_or_default(), + app_name: app_name__.unwrap_or_default(), + version: version__.unwrap_or_default(), + git_commit: git_commit__.unwrap_or_default(), + build_tags: build_tags__.unwrap_or_default(), + go_version: go_version__.unwrap_or_default(), + build_deps: build_deps__.unwrap_or_default(), + cosmos_sdk_version: cosmos_sdk_version__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.tendermint.v1beta1.VersionInfo", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.base.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.base.v1beta1.serde.rs new file mode 100644 index 0000000000..5f2515f20a --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.base.v1beta1.serde.rs @@ -0,0 +1,108 @@ +impl serde::Serialize for Coin { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.denom.is_empty() { + len += 1; + } + if !self.amount.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.base.v1beta1.Coin", len)?; + if !self.denom.is_empty() { + struct_ser.serialize_field("denom", &self.denom)?; + } + if !self.amount.is_empty() { + struct_ser.serialize_field("amount", &self.amount)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Coin { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "denom", + "amount", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Denom, + Amount, + } + 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 { + "denom" => Ok(GeneratedField::Denom), + "amount" => Ok(GeneratedField::Amount), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Coin; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.base.v1beta1.Coin") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut denom__ = None; + let mut amount__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Denom => { + if denom__.is_some() { + return Err(serde::de::Error::duplicate_field("denom")); + } + denom__ = Some(map_.next_value()?); + } + GeneratedField::Amount => { + if amount__.is_some() { + return Err(serde::de::Error::duplicate_field("amount")); + } + amount__ = Some(map_.next_value()?); + } + } + } + Ok(Coin { + denom: denom__.unwrap_or_default(), + amount: amount__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.base.v1beta1.Coin", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.crypto.multisig.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.crypto.multisig.v1beta1.serde.rs new file mode 100644 index 0000000000..e7b08aac86 --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.crypto.multisig.v1beta1.serde.rs @@ -0,0 +1,208 @@ +impl serde::Serialize for CompactBitArray { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.extra_bits_stored != 0 { + len += 1; + } + if !self.elems.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.crypto.multisig.v1beta1.CompactBitArray", len)?; + if self.extra_bits_stored != 0 { + struct_ser.serialize_field("extra_bits_stored", &self.extra_bits_stored)?; + } + if !self.elems.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("elems", pbjson::private::base64::encode(&self.elems).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for CompactBitArray { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "extra_bits_stored", + "extraBitsStored", + "elems", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ExtraBitsStored, + Elems, + } + 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 { + "extraBitsStored" | "extra_bits_stored" => Ok(GeneratedField::ExtraBitsStored), + "elems" => Ok(GeneratedField::Elems), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = CompactBitArray; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.crypto.multisig.v1beta1.CompactBitArray") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut extra_bits_stored__ = None; + let mut elems__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ExtraBitsStored => { + if extra_bits_stored__.is_some() { + return Err(serde::de::Error::duplicate_field("extraBitsStored")); + } + extra_bits_stored__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Elems => { + if elems__.is_some() { + return Err(serde::de::Error::duplicate_field("elems")); + } + elems__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + } + } + Ok(CompactBitArray { + extra_bits_stored: extra_bits_stored__.unwrap_or_default(), + elems: elems__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.crypto.multisig.v1beta1.CompactBitArray", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for MultiSignature { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.signatures.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.crypto.multisig.v1beta1.MultiSignature", len)?; + if !self.signatures.is_empty() { + struct_ser.serialize_field("signatures", &self.signatures.iter().map(pbjson::private::base64::encode).collect::>())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for MultiSignature { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "signatures", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Signatures, + } + 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 { + "signatures" => Ok(GeneratedField::Signatures), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = MultiSignature; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.crypto.multisig.v1beta1.MultiSignature") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut signatures__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Signatures => { + if signatures__.is_some() { + return Err(serde::de::Error::duplicate_field("signatures")); + } + signatures__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + } + } + Ok(MultiSignature { + signatures: signatures__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.crypto.multisig.v1beta1.MultiSignature", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.crypto.secp256k1.serde.rs b/crates/astria-core/src/generated/cosmos.crypto.secp256k1.serde.rs new file mode 100644 index 0000000000..7c0b63fcec --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.crypto.secp256k1.serde.rs @@ -0,0 +1,94 @@ +impl serde::Serialize for PubKey { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.key.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.crypto.secp256k1.PubKey", len)?; + if !self.key.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("key", pbjson::private::base64::encode(&self.key).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for PubKey { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "key", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Key, + } + 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 { + "key" => Ok(GeneratedField::Key), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = PubKey; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.crypto.secp256k1.PubKey") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut key__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + } + } + Ok(PubKey { + key: key__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.crypto.secp256k1.PubKey", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.tx.signing.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.tx.signing.v1beta1.serde.rs new file mode 100644 index 0000000000..12d5413ca2 --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.tx.signing.v1beta1.serde.rs @@ -0,0 +1,83 @@ +impl serde::Serialize for SignMode { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unspecified => "SIGN_MODE_UNSPECIFIED", + Self::Direct => "SIGN_MODE_DIRECT", + Self::Textual => "SIGN_MODE_TEXTUAL", + Self::DirectAux => "SIGN_MODE_DIRECT_AUX", + Self::LegacyAminoJson => "SIGN_MODE_LEGACY_AMINO_JSON", + Self::Eip191 => "SIGN_MODE_EIP_191", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for SignMode { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "SIGN_MODE_UNSPECIFIED", + "SIGN_MODE_DIRECT", + "SIGN_MODE_TEXTUAL", + "SIGN_MODE_DIRECT_AUX", + "SIGN_MODE_LEGACY_AMINO_JSON", + "SIGN_MODE_EIP_191", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SignMode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "SIGN_MODE_UNSPECIFIED" => Ok(SignMode::Unspecified), + "SIGN_MODE_DIRECT" => Ok(SignMode::Direct), + "SIGN_MODE_TEXTUAL" => Ok(SignMode::Textual), + "SIGN_MODE_DIRECT_AUX" => Ok(SignMode::DirectAux), + "SIGN_MODE_LEGACY_AMINO_JSON" => Ok(SignMode::LegacyAminoJson), + "SIGN_MODE_EIP_191" => Ok(SignMode::Eip191), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/cosmos.tx.v1beta1.serde.rs b/crates/astria-core/src/generated/cosmos.tx.v1beta1.serde.rs new file mode 100644 index 0000000000..febf724454 --- /dev/null +++ b/crates/astria-core/src/generated/cosmos.tx.v1beta1.serde.rs @@ -0,0 +1,1753 @@ +impl serde::Serialize for AuthInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.signer_infos.is_empty() { + len += 1; + } + if self.fee.is_some() { + len += 1; + } + if self.tip.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.AuthInfo", len)?; + if !self.signer_infos.is_empty() { + struct_ser.serialize_field("signer_infos", &self.signer_infos)?; + } + if let Some(v) = self.fee.as_ref() { + struct_ser.serialize_field("fee", v)?; + } + if let Some(v) = self.tip.as_ref() { + struct_ser.serialize_field("tip", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AuthInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "signer_infos", + "signerInfos", + "fee", + "tip", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + SignerInfos, + Fee, + Tip, + } + 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 { + "signerInfos" | "signer_infos" => Ok(GeneratedField::SignerInfos), + "fee" => Ok(GeneratedField::Fee), + "tip" => Ok(GeneratedField::Tip), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AuthInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.AuthInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut signer_infos__ = None; + let mut fee__ = None; + let mut tip__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::SignerInfos => { + if signer_infos__.is_some() { + return Err(serde::de::Error::duplicate_field("signerInfos")); + } + signer_infos__ = Some(map_.next_value()?); + } + GeneratedField::Fee => { + if fee__.is_some() { + return Err(serde::de::Error::duplicate_field("fee")); + } + fee__ = map_.next_value()?; + } + GeneratedField::Tip => { + if tip__.is_some() { + return Err(serde::de::Error::duplicate_field("tip")); + } + tip__ = map_.next_value()?; + } + } + } + Ok(AuthInfo { + signer_infos: signer_infos__.unwrap_or_default(), + fee: fee__, + tip: tip__, + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.AuthInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for BroadcastMode { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Unspecified => "BROADCAST_MODE_UNSPECIFIED", + Self::Block => "BROADCAST_MODE_BLOCK", + Self::Sync => "BROADCAST_MODE_SYNC", + Self::Async => "BROADCAST_MODE_ASYNC", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for BroadcastMode { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "BROADCAST_MODE_UNSPECIFIED", + "BROADCAST_MODE_BLOCK", + "BROADCAST_MODE_SYNC", + "BROADCAST_MODE_ASYNC", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = BroadcastMode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "BROADCAST_MODE_UNSPECIFIED" => Ok(BroadcastMode::Unspecified), + "BROADCAST_MODE_BLOCK" => Ok(BroadcastMode::Block), + "BROADCAST_MODE_SYNC" => Ok(BroadcastMode::Sync), + "BROADCAST_MODE_ASYNC" => Ok(BroadcastMode::Async), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} +impl serde::Serialize for BroadcastTxRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.tx_bytes.is_empty() { + len += 1; + } + if self.mode != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.BroadcastTxRequest", len)?; + if !self.tx_bytes.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("tx_bytes", pbjson::private::base64::encode(&self.tx_bytes).as_str())?; + } + if self.mode != 0 { + let v = BroadcastMode::try_from(self.mode) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.mode)))?; + struct_ser.serialize_field("mode", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for BroadcastTxRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "tx_bytes", + "txBytes", + "mode", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TxBytes, + Mode, + } + 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 { + "txBytes" | "tx_bytes" => Ok(GeneratedField::TxBytes), + "mode" => Ok(GeneratedField::Mode), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = BroadcastTxRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.BroadcastTxRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut tx_bytes__ = None; + let mut mode__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TxBytes => { + if tx_bytes__.is_some() { + return Err(serde::de::Error::duplicate_field("txBytes")); + } + tx_bytes__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Mode => { + if mode__.is_some() { + return Err(serde::de::Error::duplicate_field("mode")); + } + mode__ = Some(map_.next_value::()? as i32); + } + } + } + Ok(BroadcastTxRequest { + tx_bytes: tx_bytes__.unwrap_or_default(), + mode: mode__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.BroadcastTxRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for BroadcastTxResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.tx_response.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.BroadcastTxResponse", len)?; + if let Some(v) = self.tx_response.as_ref() { + struct_ser.serialize_field("tx_response", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for BroadcastTxResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "tx_response", + "txResponse", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TxResponse, + } + 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 { + "txResponse" | "tx_response" => Ok(GeneratedField::TxResponse), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = BroadcastTxResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.BroadcastTxResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut tx_response__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TxResponse => { + if tx_response__.is_some() { + return Err(serde::de::Error::duplicate_field("txResponse")); + } + tx_response__ = map_.next_value()?; + } + } + } + Ok(BroadcastTxResponse { + tx_response: tx_response__, + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.BroadcastTxResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Fee { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.amount.is_empty() { + len += 1; + } + if self.gas_limit != 0 { + len += 1; + } + if !self.payer.is_empty() { + len += 1; + } + if !self.granter.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.Fee", len)?; + if !self.amount.is_empty() { + struct_ser.serialize_field("amount", &self.amount)?; + } + if self.gas_limit != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("gas_limit", ToString::to_string(&self.gas_limit).as_str())?; + } + if !self.payer.is_empty() { + struct_ser.serialize_field("payer", &self.payer)?; + } + if !self.granter.is_empty() { + struct_ser.serialize_field("granter", &self.granter)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Fee { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "amount", + "gas_limit", + "gasLimit", + "payer", + "granter", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Amount, + GasLimit, + Payer, + Granter, + } + 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 { + "amount" => Ok(GeneratedField::Amount), + "gasLimit" | "gas_limit" => Ok(GeneratedField::GasLimit), + "payer" => Ok(GeneratedField::Payer), + "granter" => Ok(GeneratedField::Granter), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Fee; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.Fee") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut amount__ = None; + let mut gas_limit__ = None; + let mut payer__ = None; + let mut granter__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Amount => { + if amount__.is_some() { + return Err(serde::de::Error::duplicate_field("amount")); + } + amount__ = Some(map_.next_value()?); + } + GeneratedField::GasLimit => { + if gas_limit__.is_some() { + return Err(serde::de::Error::duplicate_field("gasLimit")); + } + gas_limit__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Payer => { + if payer__.is_some() { + return Err(serde::de::Error::duplicate_field("payer")); + } + payer__ = Some(map_.next_value()?); + } + GeneratedField::Granter => { + if granter__.is_some() { + return Err(serde::de::Error::duplicate_field("granter")); + } + granter__ = Some(map_.next_value()?); + } + } + } + Ok(Fee { + amount: amount__.unwrap_or_default(), + gas_limit: gas_limit__.unwrap_or_default(), + payer: payer__.unwrap_or_default(), + granter: granter__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.Fee", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetTxRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.hash.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.GetTxRequest", len)?; + if !self.hash.is_empty() { + struct_ser.serialize_field("hash", &self.hash)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetTxRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "hash", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Hash, + } + 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 { + "hash" => Ok(GeneratedField::Hash), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetTxRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.GetTxRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut hash__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Hash => { + if hash__.is_some() { + return Err(serde::de::Error::duplicate_field("hash")); + } + hash__ = Some(map_.next_value()?); + } + } + } + Ok(GetTxRequest { + hash: hash__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.GetTxRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetTxResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.tx.is_some() { + len += 1; + } + if self.tx_response.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.GetTxResponse", len)?; + if let Some(v) = self.tx.as_ref() { + struct_ser.serialize_field("tx", v)?; + } + if let Some(v) = self.tx_response.as_ref() { + struct_ser.serialize_field("tx_response", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetTxResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "tx", + "tx_response", + "txResponse", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Tx, + TxResponse, + } + 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 { + "tx" => Ok(GeneratedField::Tx), + "txResponse" | "tx_response" => Ok(GeneratedField::TxResponse), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetTxResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.GetTxResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut tx__ = None; + let mut tx_response__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Tx => { + if tx__.is_some() { + return Err(serde::de::Error::duplicate_field("tx")); + } + tx__ = map_.next_value()?; + } + GeneratedField::TxResponse => { + if tx_response__.is_some() { + return Err(serde::de::Error::duplicate_field("txResponse")); + } + tx_response__ = map_.next_value()?; + } + } + } + Ok(GetTxResponse { + tx: tx__, + tx_response: tx_response__, + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.GetTxResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ModeInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.sum.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.ModeInfo", len)?; + if let Some(v) = self.sum.as_ref() { + match v { + mode_info::Sum::Single(v) => { + struct_ser.serialize_field("single", v)?; + } + mode_info::Sum::Multi(v) => { + struct_ser.serialize_field("multi", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ModeInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "single", + "multi", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Single, + Multi, + } + 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 { + "single" => Ok(GeneratedField::Single), + "multi" => Ok(GeneratedField::Multi), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ModeInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.ModeInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut sum__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Single => { + if sum__.is_some() { + return Err(serde::de::Error::duplicate_field("single")); + } + sum__ = map_.next_value::<::std::option::Option<_>>()?.map(mode_info::Sum::Single) +; + } + GeneratedField::Multi => { + if sum__.is_some() { + return Err(serde::de::Error::duplicate_field("multi")); + } + sum__ = map_.next_value::<::std::option::Option<_>>()?.map(mode_info::Sum::Multi) +; + } + } + } + Ok(ModeInfo { + sum: sum__, + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.ModeInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for mode_info::Multi { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.bitarray.is_some() { + len += 1; + } + if !self.mode_infos.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.ModeInfo.Multi", len)?; + if let Some(v) = self.bitarray.as_ref() { + struct_ser.serialize_field("bitarray", v)?; + } + if !self.mode_infos.is_empty() { + struct_ser.serialize_field("mode_infos", &self.mode_infos)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for mode_info::Multi { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "bitarray", + "mode_infos", + "modeInfos", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Bitarray, + ModeInfos, + } + 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 { + "bitarray" => Ok(GeneratedField::Bitarray), + "modeInfos" | "mode_infos" => Ok(GeneratedField::ModeInfos), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = mode_info::Multi; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.ModeInfo.Multi") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut bitarray__ = None; + let mut mode_infos__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Bitarray => { + if bitarray__.is_some() { + return Err(serde::de::Error::duplicate_field("bitarray")); + } + bitarray__ = map_.next_value()?; + } + GeneratedField::ModeInfos => { + if mode_infos__.is_some() { + return Err(serde::de::Error::duplicate_field("modeInfos")); + } + mode_infos__ = Some(map_.next_value()?); + } + } + } + Ok(mode_info::Multi { + bitarray: bitarray__, + mode_infos: mode_infos__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.ModeInfo.Multi", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for mode_info::Single { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.mode != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.ModeInfo.Single", len)?; + if self.mode != 0 { + let v = super::signing::v1beta1::SignMode::try_from(self.mode) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.mode)))?; + struct_ser.serialize_field("mode", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for mode_info::Single { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "mode", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Mode, + } + 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 { + "mode" => Ok(GeneratedField::Mode), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = mode_info::Single; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.ModeInfo.Single") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut mode__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Mode => { + if mode__.is_some() { + return Err(serde::de::Error::duplicate_field("mode")); + } + mode__ = Some(map_.next_value::()? as i32); + } + } + } + Ok(mode_info::Single { + mode: mode__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.ModeInfo.Single", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SignDoc { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.body_bytes.is_empty() { + len += 1; + } + if !self.auth_info_bytes.is_empty() { + len += 1; + } + if !self.chain_id.is_empty() { + len += 1; + } + if self.account_number != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.SignDoc", len)?; + if !self.body_bytes.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("body_bytes", pbjson::private::base64::encode(&self.body_bytes).as_str())?; + } + if !self.auth_info_bytes.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("auth_info_bytes", pbjson::private::base64::encode(&self.auth_info_bytes).as_str())?; + } + if !self.chain_id.is_empty() { + struct_ser.serialize_field("chain_id", &self.chain_id)?; + } + if self.account_number != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("account_number", ToString::to_string(&self.account_number).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SignDoc { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "body_bytes", + "bodyBytes", + "auth_info_bytes", + "authInfoBytes", + "chain_id", + "chainId", + "account_number", + "accountNumber", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + BodyBytes, + AuthInfoBytes, + ChainId, + AccountNumber, + } + 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 { + "bodyBytes" | "body_bytes" => Ok(GeneratedField::BodyBytes), + "authInfoBytes" | "auth_info_bytes" => Ok(GeneratedField::AuthInfoBytes), + "chainId" | "chain_id" => Ok(GeneratedField::ChainId), + "accountNumber" | "account_number" => Ok(GeneratedField::AccountNumber), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SignDoc; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.SignDoc") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut body_bytes__ = None; + let mut auth_info_bytes__ = None; + let mut chain_id__ = None; + let mut account_number__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::BodyBytes => { + if body_bytes__.is_some() { + return Err(serde::de::Error::duplicate_field("bodyBytes")); + } + body_bytes__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::AuthInfoBytes => { + if auth_info_bytes__.is_some() { + return Err(serde::de::Error::duplicate_field("authInfoBytes")); + } + auth_info_bytes__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::ChainId => { + if chain_id__.is_some() { + return Err(serde::de::Error::duplicate_field("chainId")); + } + chain_id__ = Some(map_.next_value()?); + } + GeneratedField::AccountNumber => { + if account_number__.is_some() { + return Err(serde::de::Error::duplicate_field("accountNumber")); + } + account_number__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(SignDoc { + body_bytes: body_bytes__.unwrap_or_default(), + auth_info_bytes: auth_info_bytes__.unwrap_or_default(), + chain_id: chain_id__.unwrap_or_default(), + account_number: account_number__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.SignDoc", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SignerInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.public_key.is_some() { + len += 1; + } + if self.mode_info.is_some() { + len += 1; + } + if self.sequence != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.SignerInfo", len)?; + if let Some(v) = self.public_key.as_ref() { + struct_ser.serialize_field("public_key", v)?; + } + if let Some(v) = self.mode_info.as_ref() { + struct_ser.serialize_field("mode_info", v)?; + } + if self.sequence != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("sequence", ToString::to_string(&self.sequence).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SignerInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "public_key", + "publicKey", + "mode_info", + "modeInfo", + "sequence", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + PublicKey, + ModeInfo, + Sequence, + } + 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 { + "publicKey" | "public_key" => Ok(GeneratedField::PublicKey), + "modeInfo" | "mode_info" => Ok(GeneratedField::ModeInfo), + "sequence" => Ok(GeneratedField::Sequence), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SignerInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.SignerInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut public_key__ = None; + let mut mode_info__ = None; + let mut sequence__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::PublicKey => { + if public_key__.is_some() { + return Err(serde::de::Error::duplicate_field("publicKey")); + } + public_key__ = map_.next_value()?; + } + GeneratedField::ModeInfo => { + if mode_info__.is_some() { + return Err(serde::de::Error::duplicate_field("modeInfo")); + } + mode_info__ = map_.next_value()?; + } + GeneratedField::Sequence => { + if sequence__.is_some() { + return Err(serde::de::Error::duplicate_field("sequence")); + } + sequence__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(SignerInfo { + public_key: public_key__, + mode_info: mode_info__, + sequence: sequence__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.SignerInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Tip { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.amount.is_empty() { + len += 1; + } + if !self.tipper.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.Tip", len)?; + if !self.amount.is_empty() { + struct_ser.serialize_field("amount", &self.amount)?; + } + if !self.tipper.is_empty() { + struct_ser.serialize_field("tipper", &self.tipper)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Tip { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "amount", + "tipper", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Amount, + Tipper, + } + 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 { + "amount" => Ok(GeneratedField::Amount), + "tipper" => Ok(GeneratedField::Tipper), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Tip; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.Tip") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut amount__ = None; + let mut tipper__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Amount => { + if amount__.is_some() { + return Err(serde::de::Error::duplicate_field("amount")); + } + amount__ = Some(map_.next_value()?); + } + GeneratedField::Tipper => { + if tipper__.is_some() { + return Err(serde::de::Error::duplicate_field("tipper")); + } + tipper__ = Some(map_.next_value()?); + } + } + } + Ok(Tip { + amount: amount__.unwrap_or_default(), + tipper: tipper__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.Tip", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Tx { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.body.is_some() { + len += 1; + } + if self.auth_info.is_some() { + len += 1; + } + if !self.signatures.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.Tx", len)?; + if let Some(v) = self.body.as_ref() { + struct_ser.serialize_field("body", v)?; + } + if let Some(v) = self.auth_info.as_ref() { + struct_ser.serialize_field("auth_info", v)?; + } + if !self.signatures.is_empty() { + struct_ser.serialize_field("signatures", &self.signatures.iter().map(pbjson::private::base64::encode).collect::>())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Tx { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "body", + "auth_info", + "authInfo", + "signatures", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Body, + AuthInfo, + Signatures, + } + 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 { + "body" => Ok(GeneratedField::Body), + "authInfo" | "auth_info" => Ok(GeneratedField::AuthInfo), + "signatures" => Ok(GeneratedField::Signatures), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Tx; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.Tx") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut body__ = None; + let mut auth_info__ = None; + let mut signatures__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Body => { + if body__.is_some() { + return Err(serde::de::Error::duplicate_field("body")); + } + body__ = map_.next_value()?; + } + GeneratedField::AuthInfo => { + if auth_info__.is_some() { + return Err(serde::de::Error::duplicate_field("authInfo")); + } + auth_info__ = map_.next_value()?; + } + GeneratedField::Signatures => { + if signatures__.is_some() { + return Err(serde::de::Error::duplicate_field("signatures")); + } + signatures__ = + Some(map_.next_value::>>()? + .into_iter().map(|x| x.0).collect()) + ; + } + } + } + Ok(Tx { + body: body__, + auth_info: auth_info__, + signatures: signatures__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.Tx", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for TxBody { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.messages.is_empty() { + len += 1; + } + if !self.memo.is_empty() { + len += 1; + } + if self.timeout_height != 0 { + len += 1; + } + if !self.extension_options.is_empty() { + len += 1; + } + if !self.non_critical_extension_options.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("cosmos.tx.v1beta1.TxBody", len)?; + if !self.messages.is_empty() { + struct_ser.serialize_field("messages", &self.messages)?; + } + if !self.memo.is_empty() { + struct_ser.serialize_field("memo", &self.memo)?; + } + if self.timeout_height != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("timeout_height", ToString::to_string(&self.timeout_height).as_str())?; + } + if !self.extension_options.is_empty() { + struct_ser.serialize_field("extension_options", &self.extension_options)?; + } + if !self.non_critical_extension_options.is_empty() { + struct_ser.serialize_field("non_critical_extension_options", &self.non_critical_extension_options)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TxBody { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "messages", + "memo", + "timeout_height", + "timeoutHeight", + "extension_options", + "extensionOptions", + "non_critical_extension_options", + "nonCriticalExtensionOptions", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Messages, + Memo, + TimeoutHeight, + ExtensionOptions, + NonCriticalExtensionOptions, + } + 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 { + "messages" => Ok(GeneratedField::Messages), + "memo" => Ok(GeneratedField::Memo), + "timeoutHeight" | "timeout_height" => Ok(GeneratedField::TimeoutHeight), + "extensionOptions" | "extension_options" => Ok(GeneratedField::ExtensionOptions), + "nonCriticalExtensionOptions" | "non_critical_extension_options" => Ok(GeneratedField::NonCriticalExtensionOptions), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TxBody; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct cosmos.tx.v1beta1.TxBody") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut messages__ = None; + let mut memo__ = None; + let mut timeout_height__ = None; + let mut extension_options__ = None; + let mut non_critical_extension_options__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Messages => { + if messages__.is_some() { + return Err(serde::de::Error::duplicate_field("messages")); + } + messages__ = Some(map_.next_value()?); + } + GeneratedField::Memo => { + if memo__.is_some() { + return Err(serde::de::Error::duplicate_field("memo")); + } + memo__ = Some(map_.next_value()?); + } + GeneratedField::TimeoutHeight => { + if timeout_height__.is_some() { + return Err(serde::de::Error::duplicate_field("timeoutHeight")); + } + timeout_height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ExtensionOptions => { + if extension_options__.is_some() { + return Err(serde::de::Error::duplicate_field("extensionOptions")); + } + extension_options__ = Some(map_.next_value()?); + } + GeneratedField::NonCriticalExtensionOptions => { + if non_critical_extension_options__.is_some() { + return Err(serde::de::Error::duplicate_field("nonCriticalExtensionOptions")); + } + non_critical_extension_options__ = Some(map_.next_value()?); + } + } + } + Ok(TxBody { + messages: messages__.unwrap_or_default(), + memo: memo__.unwrap_or_default(), + timeout_height: timeout_height__.unwrap_or_default(), + extension_options: extension_options__.unwrap_or_default(), + non_critical_extension_options: non_critical_extension_options__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("cosmos.tx.v1beta1.TxBody", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/mod.rs b/crates/astria-core/src/generated/mod.rs index 7634d2e1d4..a11e1e8c79 100644 --- a/crates/astria-core/src/generated/mod.rs +++ b/crates/astria-core/src/generated/mod.rs @@ -75,7 +75,15 @@ pub mod composer { #[path = ""] pub mod celestia { #[path = "celestia.blob.v1.rs"] - pub mod v1; + pub mod v1 { + include!("celestia.blob.v1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("celestia.blob.v1.serde.rs"); + } + } } #[path = ""] @@ -83,6 +91,12 @@ pub mod cosmos { pub mod auth { pub mod v1beta1 { include!("cosmos.auth.v1beta1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.auth.v1beta1.serde.rs"); + } } } @@ -90,23 +104,47 @@ pub mod cosmos { pub mod abci { pub mod v1beta1 { include!("cosmos.base.abci.v1beta1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.base.abci.v1beta1.serde.rs"); + } } } pub mod node { pub mod v1beta1 { include!("cosmos.base.node.v1beta1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.base.node.v1beta1.serde.rs"); + } } } pub mod tendermint { pub mod v1beta1 { include!("cosmos.base.tendermint.v1beta1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.base.tendermint.v1beta1.serde.rs"); + } } } pub mod v1beta1 { include!("cosmos.base.v1beta1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.base.v1beta1.serde.rs"); + } } } @@ -114,11 +152,23 @@ pub mod cosmos { pub mod multisig { pub mod v1beta1 { include!("cosmos.crypto.multisig.v1beta1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.crypto.multisig.v1beta1.serde.rs"); + } } } pub mod secp256k1 { include!("cosmos.crypto.secp256k1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.crypto.secp256k1.serde.rs"); + } } } @@ -126,23 +176,58 @@ pub mod cosmos { pub mod signing { pub mod v1beta1 { include!("cosmos.tx.signing.v1beta1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.tx.signing.v1beta1.serde.rs"); + } } } pub mod v1beta1 { include!("cosmos.tx.v1beta1.rs"); + #[cfg(feature = "serde")] + use super::signing; + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("cosmos.tx.v1beta1.serde.rs"); + } } } } #[path = ""] pub mod tendermint { - #[path = "tendermint.abci.rs"] - pub mod abci; + pub mod abci { + include!("tendermint.abci.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("tendermint.abci.serde.rs"); + } + } - #[path = "tendermint.p2p.rs"] - pub mod p2p; + pub mod p2p { + include!("tendermint.p2p.rs"); - #[path = "tendermint.types.rs"] - pub mod types; + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("tendermint.p2p.serde.rs"); + } + } + + pub mod types { + include!("tendermint.types.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("tendermint.types.serde.rs"); + } + } } diff --git a/crates/astria-core/src/generated/tendermint.abci.serde.rs b/crates/astria-core/src/generated/tendermint.abci.serde.rs new file mode 100644 index 0000000000..981cd716e3 --- /dev/null +++ b/crates/astria-core/src/generated/tendermint.abci.serde.rs @@ -0,0 +1,239 @@ +impl serde::Serialize for Event { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.r#type.is_empty() { + len += 1; + } + if !self.attributes.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("tendermint.abci.Event", len)?; + if !self.r#type.is_empty() { + struct_ser.serialize_field("type", &self.r#type)?; + } + if !self.attributes.is_empty() { + struct_ser.serialize_field("attributes", &self.attributes)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Event { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "type", + "attributes", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Type, + Attributes, + } + 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 { + "type" => Ok(GeneratedField::Type), + "attributes" => Ok(GeneratedField::Attributes), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Event; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct tendermint.abci.Event") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut r#type__ = None; + let mut attributes__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Type => { + if r#type__.is_some() { + return Err(serde::de::Error::duplicate_field("type")); + } + r#type__ = Some(map_.next_value()?); + } + GeneratedField::Attributes => { + if attributes__.is_some() { + return Err(serde::de::Error::duplicate_field("attributes")); + } + attributes__ = Some(map_.next_value()?); + } + } + } + Ok(Event { + r#type: r#type__.unwrap_or_default(), + attributes: attributes__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("tendermint.abci.Event", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for EventAttribute { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.key.is_empty() { + len += 1; + } + if !self.value.is_empty() { + len += 1; + } + if self.index { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("tendermint.abci.EventAttribute", len)?; + if !self.key.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("key", pbjson::private::base64::encode(&self.key).as_str())?; + } + if !self.value.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("value", pbjson::private::base64::encode(&self.value).as_str())?; + } + if self.index { + struct_ser.serialize_field("index", &self.index)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for EventAttribute { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "key", + "value", + "index", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Key, + Value, + Index, + } + 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 { + "key" => Ok(GeneratedField::Key), + "value" => Ok(GeneratedField::Value), + "index" => Ok(GeneratedField::Index), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = EventAttribute; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct tendermint.abci.EventAttribute") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut key__ = None; + let mut value__ = None; + let mut index__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Key => { + if key__.is_some() { + return Err(serde::de::Error::duplicate_field("key")); + } + key__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Index => { + if index__.is_some() { + return Err(serde::de::Error::duplicate_field("index")); + } + index__ = Some(map_.next_value()?); + } + } + } + Ok(EventAttribute { + key: key__.unwrap_or_default(), + value: value__.unwrap_or_default(), + index: index__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("tendermint.abci.EventAttribute", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/tendermint.p2p.serde.rs b/crates/astria-core/src/generated/tendermint.p2p.serde.rs new file mode 100644 index 0000000000..4cabad651f --- /dev/null +++ b/crates/astria-core/src/generated/tendermint.p2p.serde.rs @@ -0,0 +1,460 @@ +impl serde::Serialize for DefaultNodeInfo { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.protocol_version.is_some() { + len += 1; + } + if !self.default_node_id.is_empty() { + len += 1; + } + if !self.listen_addr.is_empty() { + len += 1; + } + if !self.network.is_empty() { + len += 1; + } + if !self.version.is_empty() { + len += 1; + } + if !self.channels.is_empty() { + len += 1; + } + if !self.moniker.is_empty() { + len += 1; + } + if self.other.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("tendermint.p2p.DefaultNodeInfo", len)?; + if let Some(v) = self.protocol_version.as_ref() { + struct_ser.serialize_field("protocol_version", v)?; + } + if !self.default_node_id.is_empty() { + struct_ser.serialize_field("default_node_id", &self.default_node_id)?; + } + if !self.listen_addr.is_empty() { + struct_ser.serialize_field("listen_addr", &self.listen_addr)?; + } + if !self.network.is_empty() { + struct_ser.serialize_field("network", &self.network)?; + } + if !self.version.is_empty() { + struct_ser.serialize_field("version", &self.version)?; + } + if !self.channels.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("channels", pbjson::private::base64::encode(&self.channels).as_str())?; + } + if !self.moniker.is_empty() { + struct_ser.serialize_field("moniker", &self.moniker)?; + } + if let Some(v) = self.other.as_ref() { + struct_ser.serialize_field("other", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DefaultNodeInfo { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "protocol_version", + "protocolVersion", + "default_node_id", + "defaultNodeId", + "listen_addr", + "listenAddr", + "network", + "version", + "channels", + "moniker", + "other", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ProtocolVersion, + DefaultNodeId, + ListenAddr, + Network, + Version, + Channels, + Moniker, + Other, + } + 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 { + "protocolVersion" | "protocol_version" => Ok(GeneratedField::ProtocolVersion), + "defaultNodeId" | "default_node_id" => Ok(GeneratedField::DefaultNodeId), + "listenAddr" | "listen_addr" => Ok(GeneratedField::ListenAddr), + "network" => Ok(GeneratedField::Network), + "version" => Ok(GeneratedField::Version), + "channels" => Ok(GeneratedField::Channels), + "moniker" => Ok(GeneratedField::Moniker), + "other" => Ok(GeneratedField::Other), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DefaultNodeInfo; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct tendermint.p2p.DefaultNodeInfo") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut protocol_version__ = None; + let mut default_node_id__ = None; + let mut listen_addr__ = None; + let mut network__ = None; + let mut version__ = None; + let mut channels__ = None; + let mut moniker__ = None; + let mut other__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ProtocolVersion => { + if protocol_version__.is_some() { + return Err(serde::de::Error::duplicate_field("protocolVersion")); + } + protocol_version__ = map_.next_value()?; + } + GeneratedField::DefaultNodeId => { + if default_node_id__.is_some() { + return Err(serde::de::Error::duplicate_field("defaultNodeId")); + } + default_node_id__ = Some(map_.next_value()?); + } + GeneratedField::ListenAddr => { + if listen_addr__.is_some() { + return Err(serde::de::Error::duplicate_field("listenAddr")); + } + listen_addr__ = Some(map_.next_value()?); + } + GeneratedField::Network => { + if network__.is_some() { + return Err(serde::de::Error::duplicate_field("network")); + } + network__ = Some(map_.next_value()?); + } + GeneratedField::Version => { + if version__.is_some() { + return Err(serde::de::Error::duplicate_field("version")); + } + version__ = Some(map_.next_value()?); + } + GeneratedField::Channels => { + if channels__.is_some() { + return Err(serde::de::Error::duplicate_field("channels")); + } + channels__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Moniker => { + if moniker__.is_some() { + return Err(serde::de::Error::duplicate_field("moniker")); + } + moniker__ = Some(map_.next_value()?); + } + GeneratedField::Other => { + if other__.is_some() { + return Err(serde::de::Error::duplicate_field("other")); + } + other__ = map_.next_value()?; + } + } + } + Ok(DefaultNodeInfo { + protocol_version: protocol_version__, + default_node_id: default_node_id__.unwrap_or_default(), + listen_addr: listen_addr__.unwrap_or_default(), + network: network__.unwrap_or_default(), + version: version__.unwrap_or_default(), + channels: channels__.unwrap_or_default(), + moniker: moniker__.unwrap_or_default(), + other: other__, + }) + } + } + deserializer.deserialize_struct("tendermint.p2p.DefaultNodeInfo", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for DefaultNodeInfoOther { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.tx_index.is_empty() { + len += 1; + } + if !self.rpc_address.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("tendermint.p2p.DefaultNodeInfoOther", len)?; + if !self.tx_index.is_empty() { + struct_ser.serialize_field("tx_index", &self.tx_index)?; + } + if !self.rpc_address.is_empty() { + struct_ser.serialize_field("rpc_address", &self.rpc_address)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for DefaultNodeInfoOther { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "tx_index", + "txIndex", + "rpc_address", + "rpcAddress", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TxIndex, + RpcAddress, + } + 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 { + "txIndex" | "tx_index" => Ok(GeneratedField::TxIndex), + "rpcAddress" | "rpc_address" => Ok(GeneratedField::RpcAddress), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = DefaultNodeInfoOther; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct tendermint.p2p.DefaultNodeInfoOther") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut tx_index__ = None; + let mut rpc_address__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TxIndex => { + if tx_index__.is_some() { + return Err(serde::de::Error::duplicate_field("txIndex")); + } + tx_index__ = Some(map_.next_value()?); + } + GeneratedField::RpcAddress => { + if rpc_address__.is_some() { + return Err(serde::de::Error::duplicate_field("rpcAddress")); + } + rpc_address__ = Some(map_.next_value()?); + } + } + } + Ok(DefaultNodeInfoOther { + tx_index: tx_index__.unwrap_or_default(), + rpc_address: rpc_address__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("tendermint.p2p.DefaultNodeInfoOther", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ProtocolVersion { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.p2p != 0 { + len += 1; + } + if self.block != 0 { + len += 1; + } + if self.app != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("tendermint.p2p.ProtocolVersion", len)?; + if self.p2p != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("p2p", ToString::to_string(&self.p2p).as_str())?; + } + if self.block != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("block", ToString::to_string(&self.block).as_str())?; + } + if self.app != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("app", ToString::to_string(&self.app).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ProtocolVersion { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "p2p", + "block", + "app", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + P2p, + Block, + App, + } + 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 { + "p2p" => Ok(GeneratedField::P2p), + "block" => Ok(GeneratedField::Block), + "app" => Ok(GeneratedField::App), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ProtocolVersion; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct tendermint.p2p.ProtocolVersion") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut p2p__ = None; + let mut block__ = None; + let mut app__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::P2p => { + if p2p__.is_some() { + return Err(serde::de::Error::duplicate_field("p2p")); + } + p2p__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Block => { + if block__.is_some() { + return Err(serde::de::Error::duplicate_field("block")); + } + block__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::App => { + if app__.is_some() { + return Err(serde::de::Error::duplicate_field("app")); + } + app__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(ProtocolVersion { + p2p: p2p__.unwrap_or_default(), + block: block__.unwrap_or_default(), + app: app__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("tendermint.p2p.ProtocolVersion", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/tendermint.types.serde.rs b/crates/astria-core/src/generated/tendermint.types.serde.rs new file mode 100644 index 0000000000..8f122c8e62 --- /dev/null +++ b/crates/astria-core/src/generated/tendermint.types.serde.rs @@ -0,0 +1,284 @@ +impl serde::Serialize for Blob { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.namespace_id.is_empty() { + len += 1; + } + if !self.data.is_empty() { + len += 1; + } + if self.share_version != 0 { + len += 1; + } + if self.namespace_version != 0 { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("tendermint.types.Blob", len)?; + if !self.namespace_id.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("namespace_id", pbjson::private::base64::encode(&self.namespace_id).as_str())?; + } + if !self.data.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("data", pbjson::private::base64::encode(&self.data).as_str())?; + } + if self.share_version != 0 { + struct_ser.serialize_field("share_version", &self.share_version)?; + } + if self.namespace_version != 0 { + struct_ser.serialize_field("namespace_version", &self.namespace_version)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Blob { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "namespace_id", + "namespaceId", + "data", + "share_version", + "shareVersion", + "namespace_version", + "namespaceVersion", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + NamespaceId, + Data, + ShareVersion, + NamespaceVersion, + } + 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 { + "namespaceId" | "namespace_id" => Ok(GeneratedField::NamespaceId), + "data" => Ok(GeneratedField::Data), + "shareVersion" | "share_version" => Ok(GeneratedField::ShareVersion), + "namespaceVersion" | "namespace_version" => Ok(GeneratedField::NamespaceVersion), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Blob; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct tendermint.types.Blob") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut namespace_id__ = None; + let mut data__ = None; + let mut share_version__ = None; + let mut namespace_version__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::NamespaceId => { + if namespace_id__.is_some() { + return Err(serde::de::Error::duplicate_field("namespaceId")); + } + namespace_id__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Data => { + if data__.is_some() { + return Err(serde::de::Error::duplicate_field("data")); + } + data__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::ShareVersion => { + if share_version__.is_some() { + return Err(serde::de::Error::duplicate_field("shareVersion")); + } + share_version__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::NamespaceVersion => { + if namespace_version__.is_some() { + return Err(serde::de::Error::duplicate_field("namespaceVersion")); + } + namespace_version__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + } + } + Ok(Blob { + namespace_id: namespace_id__.unwrap_or_default(), + data: data__.unwrap_or_default(), + share_version: share_version__.unwrap_or_default(), + namespace_version: namespace_version__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("tendermint.types.Blob", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for BlobTx { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.tx.is_empty() { + len += 1; + } + if !self.blobs.is_empty() { + len += 1; + } + if !self.type_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("tendermint.types.BlobTx", len)?; + if !self.tx.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("tx", pbjson::private::base64::encode(&self.tx).as_str())?; + } + if !self.blobs.is_empty() { + struct_ser.serialize_field("blobs", &self.blobs)?; + } + if !self.type_id.is_empty() { + struct_ser.serialize_field("type_id", &self.type_id)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for BlobTx { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "tx", + "blobs", + "type_id", + "typeId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Tx, + Blobs, + TypeId, + } + 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 { + "tx" => Ok(GeneratedField::Tx), + "blobs" => Ok(GeneratedField::Blobs), + "typeId" | "type_id" => Ok(GeneratedField::TypeId), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = BlobTx; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct tendermint.types.BlobTx") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut tx__ = None; + let mut blobs__ = None; + let mut type_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Tx => { + if tx__.is_some() { + return Err(serde::de::Error::duplicate_field("tx")); + } + tx__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::Blobs => { + if blobs__.is_some() { + return Err(serde::de::Error::duplicate_field("blobs")); + } + blobs__ = Some(map_.next_value()?); + } + GeneratedField::TypeId => { + if type_id__.is_some() { + return Err(serde::de::Error::duplicate_field("typeId")); + } + type_id__ = Some(map_.next_value()?); + } + } + } + Ok(BlobTx { + tx: tx__.unwrap_or_default(), + blobs: blobs__.unwrap_or_default(), + type_id: type_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("tendermint.types.BlobTx", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-grpc-mock/src/mock.rs b/crates/astria-grpc-mock/src/mock.rs index 3b9cc0ce38..f0a7ff3e16 100644 --- a/crates/astria-grpc-mock/src/mock.rs +++ b/crates/astria-grpc-mock/src/mock.rs @@ -33,6 +33,7 @@ pub struct Mock { pub(crate) rpc: &'static str, pub(crate) matchers: Vec, pub(crate) response: Box, + pub(crate) max_n_matches: Option, pub(crate) expectation_range: Times, pub(crate) name: Option, } @@ -45,6 +46,13 @@ impl Mock { } } + #[must_use = "a mock must be mounted on a server to be useful"] + pub fn up_to_n_times(mut self, n: u64) -> Self { + assert!(n > 0, "n must be strictly greater than 0!"); + self.max_n_matches = Some(n); + self + } + #[must_use = "a mock must be mounted on a server to be useful"] pub fn expect>(mut self, r: T) -> Self { let range = r.into(); @@ -87,6 +95,7 @@ impl MockBuilder { rpc, matchers, response: Box::new(rsp), + max_n_matches: None, name: None, expectation_range: Times(TimesEnum::Unbounded(RangeFull)), } diff --git a/crates/astria-grpc-mock/src/mock_server.rs b/crates/astria-grpc-mock/src/mock_server.rs index 47b94c7b0c..bd74a5b8bc 100644 --- a/crates/astria-grpc-mock/src/mock_server.rs +++ b/crates/astria-grpc-mock/src/mock_server.rs @@ -110,7 +110,7 @@ pub struct MockGuard { } impl MockGuard { - pub async fn wait_until_satisfied(self) { + pub async fn wait_until_satisfied(&self) { let (notify, flag) = &*self.notify; let mut notification = pin!(notify.notified()); diff --git a/crates/astria-grpc-mock/src/mounted_mock.rs b/crates/astria-grpc-mock/src/mounted_mock.rs index 16e03b9b9f..efd14f52a2 100644 --- a/crates/astria-grpc-mock/src/mounted_mock.rs +++ b/crates/astria-grpc-mock/src/mounted_mock.rs @@ -102,7 +102,7 @@ pub(crate) struct MountedMock { inner: Mock, position_in_set: usize, notify: Arc<(Notify, AtomicBool)>, - successful_response: Vec<(Request, ResponseResult)>, + successful_responses: Vec<(Request, ResponseResult)>, bad_responses: Vec, } @@ -112,7 +112,10 @@ impl MountedMock { rpc: &'static str, request: &Request, ) -> MockResult { - if self.inner.rpc != rpc + let n_matches = + u64::try_from(self.successful_responses.len() + self.bad_responses.len()).ok(); + if self.inner.max_n_matches == n_matches + || self.inner.rpc != rpc || !self .inner .matchers @@ -124,7 +127,7 @@ impl MountedMock { let response = match self.inner.response.respond(request) { Err(status) => { - self.successful_response + self.successful_responses .push((clone_request(request), Err(status.clone()))); Ok(Err(status)) } @@ -133,7 +136,7 @@ impl MountedMock { clone_response(&mock_response.inner).into_parts(); if let Ok(message) = erased_message.clone_box().into_any().downcast::() { let rsp = tonic::Response::from_parts(metadata, *message, extensions); - self.successful_response + self.successful_responses .push((clone_request(request), Ok(mock_response))); Ok(Ok(rsp)) } else { @@ -180,7 +183,7 @@ impl MountedMock { inner, position_in_set, notify: Arc::new((Notify::new(), AtomicBool::new(false))), - successful_response: Vec::new(), + successful_responses: Vec::new(), bad_responses: Vec::new(), } } @@ -193,7 +196,7 @@ impl MountedMock { VerificationReport { mock_name: self.inner.name.clone(), rpc: self.inner.rpc, - n_successful_requests: self.successful_response.len() as u64, + n_successful_requests: self.successful_responses.len() as u64, bad_responses: self.bad_responses.clone(), expectation_range: self.inner.expectation_range.clone(), position_in_set: self.position_in_set, diff --git a/crates/astria-grpc-mock/src/response.rs b/crates/astria-grpc-mock/src/response.rs index 40bd596811..3e05e669d2 100644 --- a/crates/astria-grpc-mock/src/response.rs +++ b/crates/astria-grpc-mock/src/response.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use super::{ clone_response, AnyMessage, @@ -54,6 +56,52 @@ impl Respond for DefaultResponse { } } +pub fn dynamic_response(responder: F) -> DynamicResponse +where + O: erased_serde::Serialize + prost::Name + Clone + 'static, + F: Fn(&I) -> O, +{ + DynamicResponse { + type_name: std::any::type_name::(), + responder: Box::new(responder), + _phantom_data: PhantomData, + } +} + +pub struct DynamicResponse { + type_name: &'static str, + responder: Box, + _phantom_data: PhantomData<(I, O)>, +} + +impl Respond for DynamicResponse +where + I: Send + Sync + 'static, + O: erased_serde::Serialize + prost::Name + Clone + 'static, + F: Send + Sync + Fn(&I) -> O, +{ + fn respond(&self, outer_req: &tonic::Request) -> ResponseResult { + let erased_req = outer_req.get_ref(); + let Some(req) = erased_req.as_any().downcast_ref::() else { + let actual = erased_req.as_name().full_name(); + let expected = std::any::type_name::(); + let req_as_json = serde_json::to_string(erased_req.as_serialize()) + .expect("can map registered protobuf response to json"); + let msg = format!( + "failed downcasting request to concrete type; expected type of request: \ + `{expected}`, actual type of request: `{actual}`, request: {req_as_json}", + ); + return Err(tonic::Status::internal(msg)); + }; + + let resp = (self.responder)(req); + Ok(MockResponse { + type_name: self.type_name, + inner: erase_response(tonic::Response::new(resp)), + }) + } +} + pub struct MockResponse { pub(crate) type_name: &'static str, pub(crate) inner: tonic::Response, diff --git a/crates/astria-sequencer-relayer/Cargo.toml b/crates/astria-sequencer-relayer/Cargo.toml index 8afc719795..9157fe8bec 100644 --- a/crates/astria-sequencer-relayer/Cargo.toml +++ b/crates/astria-sequencer-relayer/Cargo.toml @@ -66,7 +66,7 @@ telemetry = { package = "astria-telemetry", path = "../astria-telemetry", featur tokio-stream = { workspace = true } [dev-dependencies] -celestia-mock = { package = "astria-celestia-mock", path = "../astria-celestia-mock" } +astria-grpc-mock = { path = "../astria-grpc-mock" } config = { package = "astria-config", path = "../astria-config", features = [ "tests", ] } diff --git a/crates/astria-sequencer-relayer/src/relayer/mod.rs b/crates/astria-sequencer-relayer/src/relayer/mod.rs index 9b08515dca..2fb2bea75a 100644 --- a/crates/astria-sequencer-relayer/src/relayer/mod.rs +++ b/crates/astria-sequencer-relayer/src/relayer/mod.rs @@ -205,7 +205,7 @@ impl Relayer { &mut block_stream, submitter.clone(), &mut forward_once_free, - ).wrap_err("submitter exited unexpectly while trying to forward block") { + ).wrap_err("submitter exited unexpectedly while trying to forward block") { // XXX: exiting because there is no logic to restart the blob-submitter task. // With the current implementation of the task it should also never go down // unless it has exhausted all u32::MAX attempts to submit to Celestia and diff --git a/crates/astria-sequencer-relayer/src/relayer/read.rs b/crates/astria-sequencer-relayer/src/relayer/read.rs index ad8e5c6d7d..c73ae3739c 100644 --- a/crates/astria-sequencer-relayer/src/relayer/read.rs +++ b/crates/astria-sequencer-relayer/src/relayer/read.rs @@ -16,6 +16,7 @@ use astria_core::{ }; use astria_eyre::eyre::{ self, + ensure, Report, WrapErr as _, }; @@ -233,11 +234,16 @@ async fn fetch_block( .await .wrap_err("retry attempts exhausted; bailing")?; - state.set_sequencer_connected(true); - - let block = SequencerBlock::try_from_raw(block.into_inner()) - .wrap_err("failed to parse raw proto block from grpc response")?; + let maybe_block = SequencerBlock::try_from_raw(block.into_inner()) + .wrap_err("failed to parse raw proto block from grpc response"); + state.set_sequencer_connected(maybe_block.is_ok()); + let block = maybe_block?; + ensure!( + height == block.height(), + "requested block at height `{height}` but received a block at height `{}`", + block.height() + ); Ok(block) } diff --git a/crates/astria-sequencer-relayer/src/relayer/submission.rs b/crates/astria-sequencer-relayer/src/relayer/submission.rs index 3646c23d65..32e4c6df5b 100644 --- a/crates/astria-sequencer-relayer/src/relayer/submission.rs +++ b/crates/astria-sequencer-relayer/src/relayer/submission.rs @@ -121,8 +121,8 @@ impl Started { } = pre else { panic!( - "once intialized, a submission's `pre` field must always be `Started`. Here it is \ - not. This is a bug" + "once initialized, a submission's `pre` field must always be `Started`. Here it \ + is not. This is a bug" ); }; @@ -142,7 +142,7 @@ impl Started { ); new.post .write_to_path(&new.post_path) - .wrap_err("failed comitting post-submission state to disk")?; + .wrap_err("failed committing post-submission state to disk")?; Ok(new) } } diff --git a/crates/astria-sequencer-relayer/tests/blackbox/helper.rs b/crates/astria-sequencer-relayer/tests/blackbox/helper.rs deleted file mode 100644 index 9699491c27..0000000000 --- a/crates/astria-sequencer-relayer/tests/blackbox/helper.rs +++ /dev/null @@ -1,624 +0,0 @@ -use std::{ - collections::{ - HashSet, - VecDeque, - }, - io::Write, - mem, - net::SocketAddr, - sync::{ - Arc, - Mutex, - }, - time::Duration, -}; - -use assert_json_diff::assert_json_include; -use astria_core::{ - generated::sequencerblock::v1alpha1::{ - sequencer_service_server::{ - SequencerService, - SequencerServiceServer, - }, - FilteredSequencerBlock as RawFilteredSequencerBlock, - GetFilteredSequencerBlockRequest, - GetSequencerBlockRequest, - SequencerBlock as RawSequencerBlock, - }, - primitive::v1::RollupId, - protocol::test_utils::ConfigureSequencerBlock, - sequencerblock::v1alpha1::SequencerBlock, -}; -use astria_sequencer_relayer::{ - config::Config, - SequencerRelayer, - ShutdownHandle, -}; -use celestia_types::{ - blob::SubmitOptions, - Blob, -}; -use ed25519_consensus::SigningKey; -use itertools::Itertools; -use once_cell::sync::Lazy; -use serde_json::json; -use tempfile::NamedTempFile; -use tendermint_config::PrivValidatorKey; -use tendermint_rpc::{ - response::Wrapper, - Id, -}; -use tokio::{ - net::TcpListener, - runtime::{ - self, - RuntimeFlavor, - }, - sync::{ - mpsc, - oneshot, - }, - task::JoinHandle, - time::timeout, -}; -use tonic::{ - Request, - Response, - Status, -}; -use tracing::info; -use wiremock::{ - matchers::body_partial_json, - Mock, - MockGuard, - MockServer, - ResponseTemplate, -}; - -static TELEMETRY: Lazy<()> = Lazy::new(|| { - astria_eyre::install().unwrap(); - if std::env::var_os("TEST_LOG").is_some() { - let filter_directives = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()); - println!("initializing telemetry"); - telemetry::configure() - .no_otel() - .stdout_writer(std::io::stdout) - .force_stdout() - .pretty_print() - .filter_directives(&filter_directives) - .try_init() - .unwrap(); - } else { - telemetry::configure() - .no_otel() - .stdout_writer(std::io::sink) - .try_init() - .unwrap(); - } -}); - -/// Copied verbatim from -/// [tendermint-rs](https://github.com/informalsystems/tendermint-rs/blob/main/config/tests/support/config/priv_validator_key.ed25519.json) -const PRIVATE_VALIDATOR_KEY: &str = r#" -{ - "address": "AD7DAE5FEC609CF02F9BDE7D81D0C3CD66141563", - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "8mv0sqLoTOt6U8PxrndAh3myAGR4L7rb3w42WVnuRTQ=" - }, - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "skHDGUYe2pOhwfSrXZQ6KeKnmKgTOn+f++Vmj4OOqIHya/SyouhM63pTw/Gud0CHebIAZHgvutvfDjZZWe5FNA==" - } -} -"#; - -const CELESTIA_BEARER_TOKEN: &str = "ABCDEFG"; - -pub struct BlockGuard { - inner: oneshot::Receiver<()>, -} - -impl BlockGuard { - // TODO: harmonize this with the ABCI `MockGuard` to have the same return value - #[allow(clippy::missing_errors_doc)] - pub async fn wait_until_satisfied(self) -> Result<(), tokio::sync::oneshot::error::RecvError> { - self.inner.await - } -} - -pub struct MockSequencerServer { - #[allow(clippy::type_complexity)] - blocks: Arc, RawSequencerBlock)>>>, -} - -#[async_trait::async_trait] -impl SequencerService for MockSequencerServer { - async fn get_sequencer_block( - self: Arc, - _request: Request, - ) -> Result, Status> { - let mut blocks = self.blocks.lock().unwrap(); - blocks.pop_front().map_or_else( - || Err(Status::not_found("no more blocks")), - |(tx, block)| { - tx.send(()).unwrap(); - Ok(Response::new(block)) - }, - ) - } - - async fn get_filtered_sequencer_block( - self: Arc, - _request: Request, - ) -> Result, Status> { - return Err(Status::internal("unimplemented")); - } -} - -pub struct TestSequencerRelayer { - /// The socket address that sequencer relayer is serving its API endpoint on - /// - /// This is useful for checking if it's healthy, ready, or how many p2p peers - /// are subscribed to it. - pub api_address: SocketAddr, - - /// The mocked celestia node jsonrpc server - pub celestia: MockCelestia, - - /// The mocked cometbft service - pub cometbft: MockServer, - - /// The block to return in the mock sequencer gRPC server - /// `get_sequencer_block` method. - #[allow(clippy::type_complexity)] - pub sequencer_server_blocks: Arc, RawSequencerBlock)>>>, - - pub sequencer: JoinHandle<()>, - - /// A handle which issues a shutdown to the sequencer relayer on being dropped. - pub relayer_shutdown_handle: Option, - pub sequencer_relayer: JoinHandle<()>, - - pub config: Config, - - pub signing_key: SigningKey, - - pub account: tendermint::account::Id, - - pub validator_keyfile: NamedTempFile, - - pub pre_submit_file: NamedTempFile, - pub post_submit_file: NamedTempFile, -} - -impl Drop for TestSequencerRelayer { - fn drop(&mut self) { - // We drop the shutdown handle here to cause the sequencer relayer to shut down. - let _ = self.relayer_shutdown_handle.take(); - - let sequencer_relayer = mem::replace(&mut self.sequencer_relayer, tokio::spawn(async {})); - let _ = futures::executor::block_on(async move { - timeout(Duration::from_secs(30), sequencer_relayer) - .await - .unwrap_or_else(|_| { - panic!("timed out waiting for sequencer relayer to shut down"); - }) - }); - - self.sequencer.abort(); - self.celestia.server_handle.stop().unwrap(); - } -} - -impl TestSequencerRelayer { - pub async fn mount_abci_response(&self, height: u32) -> MockGuard { - use tendermint::{ - abci, - hash::AppHash, - }; - use tendermint_rpc::endpoint::abci_info; - let abci_response = abci_info::Response { - response: abci::response::Info { - data: "SequencerRelayerTest".into(), - version: "1.0.0".into(), - app_version: 1, - last_block_height: height.into(), - last_block_app_hash: AppHash::try_from([0; 32].to_vec()).unwrap(), - }, - }; - let abci_response = Wrapper::new_with_id(Id::Num(1), Some(abci_response), None); - Mock::given(body_partial_json(json!({"method": "abci_info"}))) - .respond_with(ResponseTemplate::new(200).set_body_json(abci_response)) - .up_to_n_times(1) - .expect(1..) - .mount_as_scoped(&self.cometbft) - .await - } - - pub fn mount_block_response( - &mut self, - block_to_mount: CometBftBlockToMount, - ) -> BlockGuard { - let proposer = if RELAY_SELF { - self.account - } else { - tendermint::account::Id::try_from(vec![0u8; 20]).unwrap() - }; - - let should_corrupt = matches!(block_to_mount, CometBftBlockToMount::BadAtHeight(_)); - - let block = match block_to_mount { - CometBftBlockToMount::GoodAtHeight(height) - | CometBftBlockToMount::BadAtHeight(height) => ConfigureSequencerBlock { - block_hash: Some([99u8; 32]), - height, - proposer_address: Some(proposer), - sequence_data: vec![( - RollupId::from_unhashed_bytes(b"some_rollup_id"), - vec![99u8; 32], - )], - ..Default::default() - } - .make(), - CometBftBlockToMount::Block(block) => block, - }; - - let (tx, rx) = oneshot::channel(); - - let mut block = block.into_raw(); - if should_corrupt { - let header = block.header.as_mut().unwrap(); - header.data_hash = [0; 32].to_vec(); - } - - let mut blocks = self.sequencer_server_blocks.lock().unwrap(); - blocks.push_back((tx, block)); - BlockGuard { - inner: rx, - } - } - - #[track_caller] - pub fn assert_state_files_are_as_expected( - &self, - pre_sequencer_height: u64, - post_sequencer_height: u64, - ) { - let pre_submit_state: serde_json::Value = - serde_json::from_str(&std::fs::read_to_string(&self.config.pre_submit_path).unwrap()) - .unwrap(); - assert_json_include!( - actual: pre_submit_state, - expected: json!({ - "sequencer_height": pre_sequencer_height - }), - ); - - let post_submit_state: serde_json::Value = - serde_json::from_str(&std::fs::read_to_string(&self.config.post_submit_path).unwrap()) - .unwrap(); - assert_json_include!( - actual: post_submit_state, - expected: json!({ - "sequencer_height": post_sequencer_height, - }), - ); - } -} - -// allow: this is not performance-critical, with likely only one instance per test fixture. -#[allow(clippy::large_enum_variant)] -pub enum CometBftBlockToMount { - GoodAtHeight(u32), - BadAtHeight(u32), - Block(SequencerBlock), -} - -pub struct TestSequencerRelayerConfig { - // Sets up the test relayer to ignore all blocks except those proposed by the same address - // stored in its validator key. - pub relay_only_self: bool, - // Sets the start height of relayer and configures the on-disk pre- and post-submit files to - // look accordingly. - pub last_written_sequencer_height: Option, - // The rollup ID filter, to be stringified and provided as `Config::only_include_rollups` - // value. - pub only_include_rollups: HashSet, -} - -impl TestSequencerRelayerConfig { - pub async fn spawn_relayer(self) -> TestSequencerRelayer { - assert_ne!( - runtime::Handle::current().runtime_flavor(), - RuntimeFlavor::CurrentThread, - "the sequencer relayer must be run on a multi-threaded runtime, e.g. the test could \ - be configured using `#[tokio::test(flavor = \"multi_thread\", worker_threads = 1)]`" - ); - Lazy::force(&TELEMETRY); - - let mut celestia = MockCelestia::start().await; - let celestia_addr = (&mut celestia.addr_rx).await.unwrap(); - let celestia_keyfile = write_file( - b"c8076374e2a4a58db1c924e3dafc055e9685481054fe99e58ed67f5c6ed80e62".as_slice(), - ) - .await; - - let validator_keyfile = write_file(PRIVATE_VALIDATOR_KEY.as_bytes()).await; - let PrivValidatorKey { - address, - priv_key, - .. - } = PrivValidatorKey::parse_json(PRIVATE_VALIDATOR_KEY).unwrap(); - let signing_key = priv_key - .ed25519_signing_key() - .cloned() - .unwrap() - .try_into() - .unwrap(); - - let cometbft = MockServer::start().await; - - let grpc_listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let grpc_addr = grpc_listener.local_addr().unwrap(); - let sequencer_server_blocks = Arc::new(Mutex::new(VecDeque::new())); - let sequencer = MockSequencerServer { - blocks: sequencer_server_blocks.clone(), - }; - - let grpc_server = - tonic::transport::Server::builder().add_service(SequencerServiceServer::new(sequencer)); - - let sequencer = { - let serve = grpc_server.serve_with_incoming( - tokio_stream::wrappers::TcpListenerStream::new(grpc_listener), - ); - tokio::task::spawn(async move { serve.await.unwrap() }) - }; - - let (pre_submit_file, post_submit_file) = - if let Some(last_written_sequencer_height) = self.last_written_sequencer_height { - create_files_for_start_at_height(last_written_sequencer_height) - } else { - create_files_for_fresh_start() - }; - - let only_include_rollups = self.only_include_rollups.iter().join(",").to_string(); - - let config = Config { - cometbft_endpoint: cometbft.uri(), - sequencer_grpc_endpoint: format!("http://{grpc_addr}"), - celestia_app_grpc_endpoint: format!("http://{celestia_addr}"), - celestia_app_key_file: celestia_keyfile.path().to_string_lossy().to_string(), - block_time: 1000, - relay_only_validator_key_blocks: self.relay_only_self, - validator_key_file: validator_keyfile.path().to_string_lossy().to_string(), - only_include_rollups, - api_addr: "0.0.0.0:0".into(), - log: String::new(), - force_stdout: false, - no_otel: false, - no_metrics: false, - metrics_http_listener_addr: String::new(), - pretty_print: true, - pre_submit_path: pre_submit_file.path().to_owned(), - post_submit_path: post_submit_file.path().to_owned(), - }; - - info!(config = serde_json::to_string(&config).unwrap()); - let (sequencer_relayer, relayer_shutdown_handle) = - SequencerRelayer::new(config.clone()).unwrap(); - let api_address = sequencer_relayer.local_addr(); - let sequencer_relayer = tokio::task::spawn(sequencer_relayer.run()); - - TestSequencerRelayer { - api_address, - celestia, - config, - sequencer, - sequencer_server_blocks, - cometbft, - relayer_shutdown_handle: Some(relayer_shutdown_handle), - sequencer_relayer, - signing_key, - account: address, - validator_keyfile, - pre_submit_file, - post_submit_file, - } - } -} - -async fn write_file(data: &'static [u8]) -> NamedTempFile { - tokio::task::spawn_blocking(|| { - let keyfile = NamedTempFile::new().unwrap(); - (&keyfile).write_all(data).unwrap(); - keyfile - }) - .await - .unwrap() -} - -fn create_files_for_fresh_start() -> (NamedTempFile, NamedTempFile) { - let pre = NamedTempFile::new() - .expect("must be able to create an empty pre submit state file to run tests"); - let post = NamedTempFile::new() - .expect("must be able to create an empty post submit state file to run tests"); - serde_json::to_writer( - &pre, - &json!({ - "state": "ignore" - }), - ) - .expect("must be able to write pre-submit state to run tests"); - serde_json::to_writer( - &post, - &json!({ - "state": "fresh" - }), - ) - .expect("must be able to write post-submit state to run tests"); - (pre, post) -} - -fn create_files_for_start_at_height(height: u64) -> (NamedTempFile, NamedTempFile) { - let pre = NamedTempFile::new() - .expect("must be able to create an empty pre submit state file to run tests"); - let post = NamedTempFile::new() - .expect("must be able to create an empty post submit state file to run tests"); - - serde_json::to_writer( - &pre, - &json!({ - "state": "ignore", - }), - ) - .expect("must be able to write pre state to file to run tests"); - serde_json::to_writer_pretty( - &post, - &json!({ - "state": "submitted", - "celestia_height": 5, - "sequencer_height": height - }), - ) - .expect("must be able to write post state to file to run tests"); - (pre, post) -} - -use celestia_mock::{ - BlobServer, - HeaderServer, -}; -use jsonrpsee::{ - core::async_trait, - server::ServerHandle, - types::ErrorObjectOwned, -}; - -pub struct MockCelestia { - pub addr_rx: oneshot::Receiver, - pub state_rpc_confirmed_rx: mpsc::UnboundedReceiver>, - pub server_handle: ServerHandle, -} - -impl MockCelestia { - async fn start() -> Self { - use jsonrpsee::server::ServerBuilder; - use tower_http::validate_request::ValidateRequestHeaderLayer; - let (addr_tx, addr_rx) = oneshot::channel(); - let auth = tower::ServiceBuilder::new() - .layer(ValidateRequestHeaderLayer::bearer(CELESTIA_BEARER_TOKEN)); - let server = ServerBuilder::new() - .set_middleware(auth) - .build("127.0.0.1:0") - .await - .unwrap(); - let addr = server.local_addr().unwrap(); - addr_tx.send(addr).unwrap(); - let (state_rpc_confirmed_tx, state_rpc_confirmed_rx) = mpsc::unbounded_channel(); - let state_celestia = BlobServerImpl { - rpc_confirmed_tx: state_rpc_confirmed_tx, - }; - let header_celestia = HeaderServerImpl; - let mut merged_celestia = state_celestia.into_rpc(); - merged_celestia.merge(header_celestia.into_rpc()).unwrap(); - let server_handle = server.start(merged_celestia); - Self { - addr_rx, - state_rpc_confirmed_rx, - server_handle, - } - } -} - -struct HeaderServerImpl; - -#[async_trait] -impl HeaderServer for HeaderServerImpl { - async fn header_network_head( - &self, - ) -> Result { - use celestia_tendermint::{ - block::{ - header::Header, - Commit, - }, - validator, - }; - use celestia_types::{ - DataAvailabilityHeader, - ExtendedHeader, - }; - let header = ExtendedHeader { - header: Header { - height: 42u32.into(), - ..make_celestia_tendermint_header() - }, - commit: Commit { - height: 42u32.into(), - ..Commit::default() - }, - validator_set: validator::Set::without_proposer(vec![]), - dah: DataAvailabilityHeader { - row_roots: vec![], - column_roots: vec![], - }, - }; - Ok(header) - } -} - -struct BlobServerImpl { - rpc_confirmed_tx: mpsc::UnboundedSender>, -} - -#[async_trait] -impl BlobServer for BlobServerImpl { - async fn blob_submit( - &self, - blobs: Vec, - _opts: SubmitOptions, - ) -> Result { - self.rpc_confirmed_tx.send(blobs).unwrap(); - Ok(100) - } -} - -#[allow(clippy::missing_panics_doc)] -#[must_use] -/// Returns a default tendermint block header for test purposes. -pub fn make_celestia_tendermint_header() -> celestia_tendermint::block::Header { - use celestia_tendermint::{ - account, - block::{ - header::Version, - Header, - Height, - }, - chain, - hash::AppHash, - Hash, - Time, - }; - - Header { - version: Version { - block: 0, - app: 0, - }, - chain_id: chain::Id::try_from("test").unwrap(), - height: Height::from(1u32), - time: Time::now(), - last_block_id: None, - last_commit_hash: Hash::None, - data_hash: Hash::None, - validators_hash: Hash::Sha256([0; 32]), - next_validators_hash: Hash::Sha256([0; 32]), - consensus_hash: Hash::Sha256([0; 32]), - app_hash: AppHash::try_from([0; 32].to_vec()).unwrap(), - last_results_hash: Hash::None, - evidence_hash: Hash::None, - proposer_address: account::Id::try_from([0u8; 20].to_vec()).unwrap(), - } -} diff --git a/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_celestia_app_server.rs b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_celestia_app_server.rs new file mode 100644 index 0000000000..0b74ff6269 --- /dev/null +++ b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_celestia_app_server.rs @@ -0,0 +1,428 @@ +use std::{ + net::SocketAddr, + sync::{ + Arc, + Mutex, + }, +}; + +use astria_core::generated::{ + celestia::v1::{ + query_server::{ + Query as BlobQueryService, + QueryServer as BlobQueryServer, + }, + Params as BlobParams, + QueryParamsRequest as QueryBlobParamsRequest, + QueryParamsResponse as QueryBlobParamsResponse, + }, + cosmos::{ + auth::v1beta1::{ + query_server::{ + Query as AuthQueryService, + QueryServer as AuthQueryServer, + }, + BaseAccount, + Params as AuthParams, + QueryAccountRequest, + QueryAccountResponse, + QueryParamsRequest as QueryAuthParamsRequest, + QueryParamsResponse as QueryAuthParamsResponse, + }, + base::{ + abci::v1beta1::TxResponse, + node::v1beta1::{ + service_server::{ + Service as MinGasPriceService, + ServiceServer as MinGasPriceServer, + }, + ConfigRequest as MinGasPriceRequest, + ConfigResponse as MinGasPriceResponse, + }, + tendermint::v1beta1::{ + service_server::{ + Service as NodeInfoService, + ServiceServer as NodeInfoServer, + }, + GetNodeInfoRequest, + GetNodeInfoResponse, + }, + }, + tx::v1beta1::{ + service_server::{ + Service as TxService, + ServiceServer as TxServer, + }, + BroadcastTxRequest, + BroadcastTxResponse, + GetTxRequest, + GetTxResponse, + }, + }, + tendermint::{ + p2p::DefaultNodeInfo, + types::BlobTx, + }, +}; +use astria_eyre::eyre::{ + self, + WrapErr as _, +}; +use astria_grpc_mock::{ + matcher::message_type, + response::{ + constant_response, + dynamic_response, + }, + Mock, + MockGuard, + MockServer, +}; +use celestia_types::nmt::Namespace; +use prost::{ + Message, + Name, +}; +use tokio::task::JoinHandle; +use tonic::{ + transport::Server, + Request, + Response, + Status, +}; + +const CELESTIA_NETWORK_NAME: &str = "test-celestia"; +const GET_NODE_INFO_GRPC_NAME: &str = "get_node_info"; +const QUERY_ACCOUNT_GRPC_NAME: &str = "query_account"; +const QUERY_AUTH_PARAMS_GRPC_NAME: &str = "query_auth_params"; +const QUERY_BLOB_PARAMS_GRPC_NAME: &str = "query_blob_params"; +const MIN_GAS_PRICE_GRPC_NAME: &str = "min_gas_price"; +const GET_TX_GRPC_NAME: &str = "get_tx"; +const BROADCAST_TX_GRPC_NAME: &str = "broadcast_tx"; + +pub struct MockCelestiaAppServer { + pub _server: JoinHandle>, + pub mock_server: MockServer, + pub local_addr: SocketAddr, + pub namespaces: Arc>>, +} + +impl MockCelestiaAppServer { + pub async fn spawn() -> Self { + use tokio_stream::wrappers::TcpListenerStream; + + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let local_addr = listener.local_addr().unwrap(); + + let mock_server = MockServer::new(); + register_get_node_info(&mock_server).await; + register_query_account(&mock_server).await; + register_query_auth_params(&mock_server).await; + register_query_blob_params(&mock_server).await; + register_min_gas_price(&mock_server).await; + + let server = { + let service_impl = CelestiaAppServiceImpl(mock_server.clone()); + tokio::spawn(async move { + Server::builder() + .add_service(NodeInfoServer::new(service_impl.clone())) + .add_service(AuthQueryServer::new(service_impl.clone())) + .add_service(BlobQueryServer::new(service_impl.clone())) + .add_service(MinGasPriceServer::new(service_impl.clone())) + .add_service(TxServer::new(service_impl)) + .serve_with_incoming(TcpListenerStream::new(listener)) + .await + .wrap_err("gRPC sequencer server failed") + }) + }; + Self { + _server: server, + mock_server, + local_addr, + namespaces: Arc::new(Mutex::new(Vec::new())), + } + } + + pub async fn mount_broadcast_tx_response(&self, debug_name: impl Into) { + self.prepare_broadcast_tx_response(debug_name) + .mount(&self.mock_server) + .await; + } + + pub async fn mount_broadcast_tx_response_as_scoped( + &self, + debug_name: impl Into, + ) -> MockGuard { + self.prepare_broadcast_tx_response(debug_name) + .mount_as_scoped(&self.mock_server) + .await + } + + pub async fn mount_get_tx_response(&self, height: i64, debug_name: impl Into) { + Self::prepare_get_tx_response(height, debug_name) + .mount(&self.mock_server) + .await; + } + + pub async fn mount_get_tx_response_as_scoped( + &self, + height: i64, + debug_name: impl Into, + ) -> MockGuard { + Self::prepare_get_tx_response(height, debug_name) + .mount_as_scoped(&self.mock_server) + .await + } + + fn prepare_broadcast_tx_response(&self, debug_name: impl Into) -> Mock { + let debug_name = debug_name.into(); + let txhash = debug_name.clone(); + let namespaces = self.namespaces.clone(); + let responder = move |request: &BroadcastTxRequest| { + namespaces + .lock() + .unwrap() + .extend(extract_blob_namespaces(request)); + // We only use the `code` and `txhash` fields in the success case. The `txhash` would + // be an actual hex-encoded SHA256 in prod, but here we can just use the + // debug name for ease of debugging. + let tx_response = TxResponse { + txhash: txhash.clone(), + code: 0, + ..TxResponse::default() + }; + BroadcastTxResponse { + tx_response: Some(tx_response), + } + }; + + Mock::for_rpc_given(BROADCAST_TX_GRPC_NAME, message_type::()) + .respond_with(dynamic_response(responder)) + .expect(1) + .up_to_n_times(1) + .with_name(debug_name) + } + + fn prepare_get_tx_response(height: i64, debug_name: impl Into) -> Mock { + let debug_name = debug_name.into(); + // We only use the `tx_response.code` and `tx_response.height` fields in the success case. + // The `txhash` would be an actual hex-encoded SHA256 in prod, but here we can just use the + // debug name for ease of debugging. + let tx_response = TxResponse { + height, + txhash: debug_name.clone(), + code: 0, + ..TxResponse::default() + }; + let response = GetTxResponse { + tx: None, + tx_response: Some(tx_response), + }; + Mock::for_rpc_given(GET_TX_GRPC_NAME, message_type::()) + .respond_with(constant_response(response)) + .up_to_n_times(1) + .expect(1) + .with_name(debug_name) + } +} + +/// Registers a handler for all incoming `GetNodeInfoRequest`s which responds with the same +/// `GetNodeInfoResponse` every time. +async fn register_get_node_info(mock_server: &MockServer) { + let default_node_info = Some(DefaultNodeInfo { + network: CELESTIA_NETWORK_NAME.to_string(), + ..Default::default() + }); + let response = GetNodeInfoResponse { + default_node_info, + ..Default::default() + }; + + Mock::for_rpc_given( + GET_NODE_INFO_GRPC_NAME, + message_type::(), + ) + .respond_with(constant_response(response)) + .with_name("global get node info") + .mount(mock_server) + .await; +} + +/// Registers a handler for all incoming `QueryAccountRequest`s which responds with a +/// `QueryAccountResponse` using the received account address, but otherwise the same data every +/// time. +async fn register_query_account(mock_server: &MockServer) { + let responder = |request: &QueryAccountRequest| { + let account = BaseAccount { + address: request.address.clone(), + pub_key: None, // this field is ignored by the relayer + account_number: 10, + sequence: 53, + }; + let account_as_any = pbjson_types::Any { + type_url: BaseAccount::type_url(), + value: account.encode_to_vec().into(), + }; + QueryAccountResponse { + account: Some(account_as_any), + } + }; + Mock::for_rpc_given( + QUERY_ACCOUNT_GRPC_NAME, + message_type::(), + ) + .respond_with(dynamic_response(responder)) + .with_name("global query account") + .mount(mock_server) + .await; +} + +/// Registers a handler for all incoming `QueryAuthParamsRequest`s which responds with the same +/// `QueryAuthParamsResponse` every time. +/// +/// The response is as per current values in Celestia mainnet. +async fn register_query_auth_params(mock_server: &MockServer) { + let params = AuthParams { + max_memo_characters: 256, + tx_sig_limit: 7, + tx_size_cost_per_byte: 10, + sig_verify_cost_ed25519: 590, + sig_verify_cost_secp256k1: 1000, + }; + let response = QueryAuthParamsResponse { + params: Some(params), + }; + Mock::for_rpc_given( + QUERY_AUTH_PARAMS_GRPC_NAME, + message_type::(), + ) + .respond_with(constant_response(response)) + .with_name("global query auth params") + .mount(mock_server) + .await; +} + +/// Registers a handler for all incoming `QueryBlobParamsRequest`s which responds with the same +/// `QueryBlobParamsResponse` every time. +/// +/// The response is as per current values in Celestia mainnet. +async fn register_query_blob_params(mock_server: &MockServer) { + let response = QueryBlobParamsResponse { + params: Some(BlobParams { + gas_per_blob_byte: 8, + gov_max_square_size: 64, + }), + }; + Mock::for_rpc_given( + QUERY_BLOB_PARAMS_GRPC_NAME, + message_type::(), + ) + .respond_with(constant_response(response)) + .with_name("global query blob params") + .mount(mock_server) + .await; +} + +/// Registers a handler for all incoming `MinGasPriceRequest`s which responds with the same +/// `MinGasPriceResponse` every time. +/// +/// The response is as per the current value in Celestia mainnet. +async fn register_min_gas_price(mock_server: &MockServer) { + let response = MinGasPriceResponse { + minimum_gas_price: "0.002000000000000000utia".to_string(), + }; + Mock::for_rpc_given( + MIN_GAS_PRICE_GRPC_NAME, + message_type::(), + ) + .respond_with(constant_response(response)) + .with_name("global min gas price") + .mount(mock_server) + .await; +} + +#[derive(Clone)] +struct CelestiaAppServiceImpl(MockServer); + +#[async_trait::async_trait] +impl NodeInfoService for CelestiaAppServiceImpl { + async fn get_node_info( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0 + .handle_request(GET_NODE_INFO_GRPC_NAME, request) + .await + } +} + +#[async_trait::async_trait] +impl AuthQueryService for CelestiaAppServiceImpl { + async fn account( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0 + .handle_request(QUERY_ACCOUNT_GRPC_NAME, request) + .await + } + + async fn params( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0 + .handle_request(QUERY_AUTH_PARAMS_GRPC_NAME, request) + .await + } +} + +#[async_trait::async_trait] +impl BlobQueryService for CelestiaAppServiceImpl { + async fn params( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0 + .handle_request(QUERY_BLOB_PARAMS_GRPC_NAME, request) + .await + } +} + +#[async_trait::async_trait] +impl MinGasPriceService for CelestiaAppServiceImpl { + async fn config( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0 + .handle_request(MIN_GAS_PRICE_GRPC_NAME, request) + .await + } +} + +#[async_trait::async_trait] +impl TxService for CelestiaAppServiceImpl { + async fn get_tx( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0.handle_request(GET_TX_GRPC_NAME, request).await + } + + async fn broadcast_tx( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0.handle_request(BROADCAST_TX_GRPC_NAME, request).await + } +} + +fn extract_blob_namespaces(request: &BroadcastTxRequest) -> Vec { + let blob_tx = BlobTx::decode(request.tx_bytes.as_ref()).unwrap(); + blob_tx + .blobs + .iter() + .map(|blob| Namespace::new_v0(blob.namespace_id.as_ref()).unwrap()) + .collect() +} diff --git a/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs new file mode 100644 index 0000000000..a5a33585ca --- /dev/null +++ b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs @@ -0,0 +1,173 @@ +use std::{ + net::SocketAddr, + sync::Arc, +}; + +use astria_core::{ + generated::sequencerblock::v1alpha1::{ + sequencer_service_server::{ + SequencerService, + SequencerServiceServer, + }, + FilteredSequencerBlock as RawFilteredSequencerBlock, + GetFilteredSequencerBlockRequest, + GetSequencerBlockRequest, + SequencerBlock as RawSequencerBlock, + }, + primitive::v1::RollupId, + protocol::test_utils::ConfigureSequencerBlock, + sequencerblock::v1alpha1::SequencerBlock, +}; +use astria_eyre::eyre::{ + self, + WrapErr as _, +}; +use astria_grpc_mock::{ + matcher::message_type, + response::constant_response, + Mock, + MockGuard, + MockServer, +}; +use tendermint::account::Id as AccountId; +use tokio::task::JoinHandle; +use tonic::{ + transport::Server, + Request, + Response, + Status, +}; + +const GET_SEQUENCER_BLOCK_GRPC_NAME: &str = "get_sequencer_block"; +const GET_FILTERED_SEQUENCER_BLOCK_GRPC_NAME: &str = "get_filtered_sequencer_block"; + +pub struct MockSequencerServer { + pub _server: JoinHandle>, + pub mock_server: MockServer, + pub local_addr: SocketAddr, +} + +impl MockSequencerServer { + pub async fn spawn() -> Self { + use tokio_stream::wrappers::TcpListenerStream; + + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let local_addr = listener.local_addr().unwrap(); + + let mock_server = MockServer::new(); + + let server = { + let sequencer_service = SequencerServiceImpl(mock_server.clone()); + tokio::spawn(async move { + Server::builder() + .add_service(SequencerServiceServer::new(sequencer_service)) + .serve_with_incoming(TcpListenerStream::new(listener)) + .await + .wrap_err("gRPC sequencer server failed") + }) + }; + Self { + _server: server, + mock_server, + local_addr, + } + } + + pub async fn mount_sequencer_block_response( + &self, + account: AccountId, + block_to_mount: SequencerBlockToMount, + debug_name: impl Into, + ) { + prepare_sequencer_block_response::(account, block_to_mount, debug_name) + .mount(&self.mock_server) + .await; + } + + pub async fn mount_sequencer_block_response_as_scoped( + &self, + account: AccountId, + block_to_mount: SequencerBlockToMount, + debug_name: impl Into, + ) -> MockGuard { + prepare_sequencer_block_response::(account, block_to_mount, debug_name) + .mount_as_scoped(&self.mock_server) + .await + } +} + +// allow: this is not performance-critical, with likely only one instance per test fixture. +#[allow(clippy::large_enum_variant)] +pub enum SequencerBlockToMount { + GoodAtHeight(u32), + BadAtHeight(u32), + Block(SequencerBlock), +} + +struct SequencerServiceImpl(MockServer); + +#[tonic::async_trait] +impl SequencerService for SequencerServiceImpl { + async fn get_sequencer_block( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0 + .handle_request(GET_SEQUENCER_BLOCK_GRPC_NAME, request) + .await + } + + async fn get_filtered_sequencer_block( + self: Arc, + request: Request, + ) -> Result, Status> { + self.0 + .handle_request(GET_FILTERED_SEQUENCER_BLOCK_GRPC_NAME, request) + .await + } +} + +fn prepare_sequencer_block_response( + account: AccountId, + block_to_mount: SequencerBlockToMount, + debug_name: impl Into, +) -> Mock { + let proposer = if RELAY_SELF { + account + } else { + AccountId::try_from(vec![0u8; 20]).unwrap() + }; + + let should_corrupt = matches!(block_to_mount, SequencerBlockToMount::BadAtHeight(_)); + + let block = match block_to_mount { + SequencerBlockToMount::GoodAtHeight(height) + | SequencerBlockToMount::BadAtHeight(height) => ConfigureSequencerBlock { + block_hash: Some([99u8; 32]), + height, + proposer_address: Some(proposer), + sequence_data: vec![( + RollupId::from_unhashed_bytes(b"some_rollup_id"), + vec![99u8; 32], + )], + ..Default::default() + } + .make(), + SequencerBlockToMount::Block(block) => block, + }; + + let mut block = block.into_raw(); + if should_corrupt { + let header = block.header.as_mut().unwrap(); + header.data_hash[0] = header.data_hash[0].wrapping_add(1); + } + + Mock::for_rpc_given( + GET_SEQUENCER_BLOCK_GRPC_NAME, + message_type::(), + ) + .respond_with(constant_response(block)) + .up_to_n_times(1) + .expect(1) + .with_name(debug_name) +} diff --git a/crates/astria-sequencer-relayer/tests/blackbox/helpers/mod.rs b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mod.rs new file mode 100644 index 0000000000..f9ea1ccd4c --- /dev/null +++ b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mod.rs @@ -0,0 +1,12 @@ +mod mock_celestia_app_server; +mod mock_sequencer_server; +mod test_sequencer_relayer; + +pub use self::{ + mock_celestia_app_server::MockCelestiaAppServer, + mock_sequencer_server::{ + MockSequencerServer, + SequencerBlockToMount, + }, + test_sequencer_relayer::TestSequencerRelayerConfig, +}; diff --git a/crates/astria-sequencer-relayer/tests/blackbox/helpers/test_sequencer_relayer.rs b/crates/astria-sequencer-relayer/tests/blackbox/helpers/test_sequencer_relayer.rs new file mode 100644 index 0000000000..a3b4fadd9a --- /dev/null +++ b/crates/astria-sequencer-relayer/tests/blackbox/helpers/test_sequencer_relayer.rs @@ -0,0 +1,785 @@ +use std::{ + collections::HashSet, + fmt::{ + self, + Display, + Formatter, + }, + future::Future, + io::Write, + mem, + net::SocketAddr, + time::Duration, +}; + +use assert_json_diff::assert_json_include; +use astria_core::primitive::v1::RollupId; +use astria_grpc_mock::MockGuard as GrpcMockGuard; +use astria_sequencer_relayer::{ + config::Config, + SequencerRelayer, + ShutdownHandle, +}; +use ed25519_consensus::SigningKey; +use futures::TryFutureExt; +use itertools::Itertools; +use once_cell::sync::Lazy; +use reqwest::{ + Response, + StatusCode, +}; +use serde::Deserialize; +use serde_json::json; +use tempfile::NamedTempFile; +use tendermint_config::PrivValidatorKey; +use tendermint_rpc::{ + response::Wrapper, + Id, +}; +use tokio::{ + runtime::{ + self, + RuntimeFlavor, + }, + task::{ + yield_now, + JoinHandle, + }, +}; +use tracing::{ + error, + info, +}; +use wiremock::{ + matchers::body_partial_json, + MockServer as WireMockServer, + ResponseTemplate, +}; + +use super::{ + MockCelestiaAppServer, + MockSequencerServer, + SequencerBlockToMount, +}; + +/// Copied verbatim from +/// [tendermint-rs](https://github.com/informalsystems/tendermint-rs/blob/main/config/tests/support/config/priv_validator_key.ed25519.json) +const PRIVATE_VALIDATOR_KEY: &str = r#" +{ + "address": "AD7DAE5FEC609CF02F9BDE7D81D0C3CD66141563", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "8mv0sqLoTOt6U8PxrndAh3myAGR4L7rb3w42WVnuRTQ=" + }, + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "skHDGUYe2pOhwfSrXZQ6KeKnmKgTOn+f++Vmj4OOqIHya/SyouhM63pTw/Gud0CHebIAZHgvutvfDjZZWe5FNA==" + } +} +"#; + +static TELEMETRY: Lazy<()> = Lazy::new(|| { + astria_eyre::install().unwrap(); + if std::env::var_os("TEST_LOG").is_some() { + let filter_directives = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()); + println!("initializing telemetry"); + telemetry::configure() + .no_otel() + .stdout_writer(std::io::stdout) + .force_stdout() + .pretty_print() + .filter_directives(&filter_directives) + .try_init() + .unwrap(); + } else { + telemetry::configure() + .no_otel() + .stdout_writer(std::io::sink) + .try_init() + .unwrap(); + } +}); + +pub struct TestSequencerRelayer { + /// The socket address that sequencer relayer is serving its API endpoint on + /// + /// This is useful for checking if it's healthy, ready, or how many p2p peers + /// are subscribed to it. + pub api_address: SocketAddr, + + /// The mocked celestia app server. + pub celestia_app: MockCelestiaAppServer, + + /// The mocked cometbft server. + pub cometbft: wiremock::MockServer, + + /// The mocked sequencer server. + pub sequencer: MockSequencerServer, + + /// A handle which issues a shutdown to the sequencer relayer on being dropped. + pub relayer_shutdown_handle: Option, + pub sequencer_relayer: JoinHandle<()>, + + pub config: Config, + + pub signing_key: SigningKey, + + pub account: tendermint::account::Id, + + pub validator_keyfile: NamedTempFile, + + pub pre_submit_file: NamedTempFile, + pub post_submit_file: NamedTempFile, +} + +impl Drop for TestSequencerRelayer { + fn drop(&mut self) { + // We drop the shutdown handle here to cause the sequencer relayer to shut down. + let _ = self.relayer_shutdown_handle.take(); + + let sequencer_relayer = mem::replace(&mut self.sequencer_relayer, tokio::spawn(async {})); + let _ = futures::executor::block_on(async move { + tokio::time::timeout(Duration::from_secs(2), sequencer_relayer) + .await + .unwrap_or_else(|_| { + error!("timed out waiting for sequencer relayer to shut down"); + Ok(()) + }) + }); + } +} + +impl TestSequencerRelayer { + /// Mounts a `CometBFT` ABCI Info response. + pub async fn mount_abci_response(&self, height: u32) { + use tendermint::{ + abci, + hash::AppHash, + }; + use tendermint_rpc::endpoint::abci_info; + let abci_response = abci_info::Response { + response: abci::response::Info { + data: "SequencerRelayerTest".into(), + version: "1.0.0".into(), + app_version: 1, + last_block_height: height.into(), + last_block_app_hash: AppHash::try_from([0; 32].to_vec()).unwrap(), + }, + }; + let abci_response = Wrapper::new_with_id(Id::Num(1), Some(abci_response), None); + wiremock::Mock::given(body_partial_json(json!({"method": "abci_info"}))) + .respond_with(ResponseTemplate::new(200).set_body_json(abci_response)) + .up_to_n_times(1) + .expect(1..) + .named("CometBFT abci_info") + .mount(&self.cometbft) + .await; + } + + /// Mounts a Sequencer block response. + /// + /// The `debug_name` is assigned to the mock and is output on error to assist with debugging. + pub async fn mount_sequencer_block_response( + &self, + block_to_mount: SequencerBlockToMount, + debug_name: impl Into, + ) { + self.sequencer + .mount_sequencer_block_response::(self.account, block_to_mount, debug_name) + .await; + } + + /// Mounts a Sequencer block response and returns a `GrpcMockGuard` to allow for waiting for + /// the mock to be satisfied. + /// + /// The `debug_name` is assigned to the mock and is output on error to assist with debugging. + pub async fn mount_sequencer_block_response_as_scoped( + &self, + block_to_mount: SequencerBlockToMount, + debug_name: impl Into, + ) -> GrpcMockGuard { + self.sequencer + .mount_sequencer_block_response_as_scoped::( + self.account, + block_to_mount, + debug_name, + ) + .await + } + + /// Mounts a Celestia `BroadcastTx` response. + /// + /// The `debug_name` is assigned to the mock and is output on error to assist with debugging. + /// It is also assigned as the `TxHash` in the response. + pub async fn mount_celestia_app_broadcast_tx_response(&self, debug_name: impl Into) { + self.celestia_app + .mount_broadcast_tx_response(debug_name) + .await; + } + + /// Mounts a Celestia `BroadcastTx` response and returns a `GrpcMockGuard` to allow for waiting + /// for the mock to be satisfied. + /// + /// The `debug_name` is assigned to the mock and is output on error to assist with debugging. + /// It is also assigned as the `TxHash` in the response. + pub async fn mount_celestia_app_broadcast_tx_response_as_scoped( + &self, + debug_name: impl Into, + ) -> GrpcMockGuard { + self.celestia_app + .mount_broadcast_tx_response_as_scoped(debug_name) + .await + } + + /// Mounts a Celestia `GetTx` response. + /// + /// The `debug_name` is assigned to the mock and is output on error to assist with debugging. + /// It is also assigned as the `TxHash` in the request and response. + pub async fn mount_celestia_app_get_tx_response( + &self, + celestia_height: i64, + debug_name: impl Into, + ) { + self.celestia_app + .mount_get_tx_response(celestia_height, debug_name) + .await; + } + + /// Mounts a Celestia `GetTx` response and returns a `GrpcMockGuard` to allow for waiting for + /// the mock to be satisfied. + /// + /// The `debug_name` is assigned to the mock and is output on error to assist with debugging. + /// It is also assigned as the `TxHash` in the request and response. + pub async fn mount_celestia_app_get_tx_response_as_scoped( + &self, + celestia_height: i64, + debug_name: impl Into, + ) -> GrpcMockGuard { + self.celestia_app + .mount_get_tx_response_as_scoped(celestia_height, debug_name) + .await + } + + /// Gets the state reported via the "status" endpoint of the sequencer-relayer. + /// + /// # Panics + /// + /// Panics if the state cannot be retrieved within 100 milliseconds. + pub async fn state(&self, context: &str) -> SequencerRelayerState { + let (value, _status_code) = self.get_from_relayer_api("status", context).await; + serde_json::from_value(value).expect("should parse status") + } + + /// Gets the status reported via the "healthz" endpoint of the sequencer-relayer. + /// + /// # Panics + /// + /// Panics if the status cannot be retrieved within 100 milliseconds. + pub async fn healthz(&self, context: &str) -> (String, StatusCode) { + let (value, status_code) = self.get_from_relayer_api("healthz", context).await; + let zpage: ZPage = serde_json::from_value(value).expect("should parse healthz"); + (zpage.status, status_code) + } + + /// Gets the status reported via the "readyz" endpoint of the sequencer-relayer. + /// + /// # Panics + /// + /// Panics if the status cannot be retrieved within 100 milliseconds. + pub async fn readyz(&self, context: &str) -> (String, StatusCode) { + let (value, status_code) = self.get_from_relayer_api("readyz", context).await; + let zpage: ZPage = serde_json::from_value(value).expect("should parse readyz"); + (zpage.status, status_code) + } + + /// Polls the "status" endpoint of the sequencer-relayer until it reports + /// `latest_confirmed_celestia_height` equal to `height`. + /// + /// # Panics + /// + /// Panics if any individual request exceeds 100 milliseconds, cannot be parsed, or if the + /// endpoint doesn't report the given height within the number of milliseconds specified. + pub async fn wait_for_latest_confirmed_celestia_height(&self, height: u64, within_ms: u64) { + let predicate = |value: serde_json::Value, status_code: StatusCode| -> bool { + if !status_code.is_success() { + return false; + } + let state: SequencerRelayerState = + serde_json::from_value(value).expect("should parse status"); + state.latest_confirmed_celestia_height == Some(height) + }; + + let context = "waiting for latest confirmed celestia height"; + self.wait_until_relayer_api_matches("status", within_ms, context, predicate) + .await; + } + + /// Polls the "status" endpoint of the sequencer-relayer until it reports + /// `latest_fetched_sequencer_height` equal to `height`. + /// + /// # Panics + /// + /// Panics if any individual request exceeds 100 milliseconds, cannot be parsed, or if the + /// endpoint doesn't report the given height within the number of milliseconds specified. + pub async fn wait_for_latest_fetched_sequencer_height(&self, height: u64, within_ms: u64) { + let predicate = |value: serde_json::Value, status_code: StatusCode| -> bool { + if !status_code.is_success() { + return false; + } + let state: SequencerRelayerState = + serde_json::from_value(value).expect("should parse status"); + state.latest_fetched_sequencer_height == Some(height) + }; + + let context = "waiting for latest fetched sequencer height"; + self.wait_until_relayer_api_matches("status", within_ms, context, predicate) + .await; + } + + /// Polls the "status" endpoint of the sequencer-relayer until it reports + /// `latest_observed_sequencer_height` equal to `height`. + /// + /// # Panics + /// + /// Panics if any individual request exceeds 100 milliseconds, cannot be parsed, or if the + /// endpoint doesn't report the given height within the number of milliseconds specified. + pub async fn wait_for_latest_observed_sequencer_height(&self, height: u64, within_ms: u64) { + let predicate = |value: serde_json::Value, status_code: StatusCode| -> bool { + if !status_code.is_success() { + return false; + } + let state: SequencerRelayerState = + serde_json::from_value(value).expect("should parse status"); + state.latest_observed_sequencer_height == Some(height) + }; + + let context = "waiting for latest observed sequencer height"; + self.wait_until_relayer_api_matches("status", within_ms, context, predicate) + .await; + } + + /// Polls the "healthz" endpoint of the sequencer-relayer until it responds with the given + /// status code. Returns the value of the response's `status` field. + /// + /// # Panics + /// + /// Panics if any individual request exceeds 100 milliseconds, cannot be parsed, or if the + /// endpoint doesn't respond with the given code within the number of milliseconds specified. + pub async fn wait_for_healthz( + &self, + code: StatusCode, + within_ms: u64, + context: &str, + ) -> String { + self.wait_for_zpage(ZPageType::Healthz, code, within_ms, context) + .await + } + + /// Polls the "readyz" endpoint of the sequencer-relayer until it responds with the given + /// status code. Returns the value of the response's `status` field. + /// + /// # Panics + /// + /// Panics if any individual request exceeds 100 milliseconds, cannot be parsed, or if the + /// endpoint doesn't respond with the given code within the number of milliseconds specified. + pub async fn wait_for_readyz(&self, code: StatusCode, within_ms: u64, context: &str) -> String { + self.wait_for_zpage(ZPageType::Readyz, code, within_ms, context) + .await + } + + /// Polls `is_finished` on the sequencer-relayer task until it returns `true`. + /// + /// # Panics + /// + /// Panics if the relayer task doesn't finish within the number of milliseconds specified. + pub async fn wait_for_relayer_shutdown(&mut self, within_ms: u64) { + let relayer_join_handle = mem::replace(&mut self.sequencer_relayer, tokio::spawn(async {})); + let check_finished = async { + loop { + if relayer_join_handle.is_finished() { + return; + } + yield_now().await; + } + }; + self.timeout_ms( + within_ms, + "waiting for sequencer-relayer to finish", + check_finished, + ) + .await; + } + + pub fn celestia_app_received_blob_count(&self) -> usize { + self.celestia_app.namespaces.lock().unwrap().len() + } + + pub fn has_celestia_app_received_blob_from_rollup(&self, rollup_id: RollupId) -> bool { + let namespace = astria_core::celestia::namespace_v0_from_rollup_id(rollup_id); + self.celestia_app + .namespaces + .lock() + .unwrap() + .iter() + .contains(&namespace) + } + + /// Polls the given z-page endpoint of the sequencer-relayer until it responds with the given + /// status code. Returns the value of the response's `status` field. + /// + /// # Panics + /// + /// Panics if any individual request exceeds 100 milliseconds, cannot be parsed, or if the + /// endpoint doesn't respond with the given code within the number of milliseconds specified. + async fn wait_for_zpage( + &self, + zpage: ZPageType, + code: StatusCode, + within_ms: u64, + context: &str, + ) -> String { + let predicate = |_, status_code: StatusCode| -> bool { status_code == code }; + let (value, _status_code) = self + .wait_until_relayer_api_matches( + zpage.to_string().as_str(), + within_ms, + context, + predicate, + ) + .await; + let zpage: ZPage = serde_json::from_value(value).expect("should parse {zpage}"); + zpage.status + } + + /// Polls the given endpoint of the sequencer-relayer until it responds with data satisfying + /// `predicate`. + /// + /// The predicate is passed the JSON value and status code from the response, and if it returns + /// `true`, the function returns. + /// + /// # Panics + /// + /// Panics if any individual request exceeds 100 milliseconds, cannot be parsed, or if the + /// predicate isn't satisfied within the number of milliseconds specified. + async fn wait_until_relayer_api_matches

( + &self, + api_endpoint: &str, + within_ms: u64, + context: &str, + mut predicate: P, + ) -> (serde_json::Value, StatusCode) + where + P: FnMut(serde_json::Value, StatusCode) -> bool, + { + let getter = async { + loop { + let (value, status_code) = self.get_from_relayer_api(api_endpoint, context).await; + if predicate(value.clone(), status_code) { + return (value, status_code); + } + yield_now().await; + } + }; + self.timeout_ms(within_ms, context, getter).await + } + + /// Gets the JSON body reported via the given endpoint of the sequencer-relayer. + /// + /// # Panics + /// + /// Panics if the response cannot be retrieved within 100 milliseconds. + async fn get_from_relayer_api( + &self, + api_endpoint: &str, + context: &str, + ) -> (serde_json::Value, StatusCode) { + let url = format!("http://{}/{api_endpoint}", self.api_address); + let getter = async { + reqwest::get(&url) + .await + .unwrap_or_else(|error| panic!("should get response from `{url}`: {error}")) + }; + + let new_context = format!("{context}: get from `{url}`"); + let response = self.timeout_ms(100, &new_context, getter).await; + + let status_code = response.status(); + let value = response + .json::() + .await + .unwrap_or_else(|error| { + panic!("{context}: failed to parse response from `{url}` as JSON: {error}") + }); + (value, status_code) + } + + /// Executes `future` within the specified duration, returning its result. + /// + /// If execution takes more than 80% of the allowed time, an error is logged before returning. + /// + /// # Panics + /// + /// Panics if execution takes longer than the specified duration. + pub async fn timeout_ms( + &self, + num_milliseconds: u64, + context: &str, + future: F, + ) -> F::Output { + let start = std::time::Instant::now(); + let within = Duration::from_millis(num_milliseconds); + if let Ok(value) = tokio::time::timeout(within, future).await { + let elapsed = start.elapsed(); + if elapsed * 5 > within * 4 { + error!(%context, + "elapsed time ({} seconds) was over 80% of the specified timeout ({} \ + seconds) - consider increasing the timeout", + elapsed.as_secs_f32(), + within.as_secs_f32() + ); + } + value + } else { + let state = tokio::time::timeout(Duration::from_millis(100), async { + reqwest::get(format!("http://{}/status", self.api_address)) + .and_then(Response::json) + .await + .ok() + }) + .await + .unwrap_or(None) + .and_then(|value: serde_json::Value| { + serde_json::from_value::(value).ok() + }) + .map_or("unknown".to_string(), |state| format!("{state:?}")); + + let healthz = tokio::time::timeout(Duration::from_millis(100), async { + reqwest::get(format!("http://{}/healthz", self.api_address)) + .and_then(Response::json) + .await + .ok() + }) + .await + .unwrap_or(None) + .and_then(|value: serde_json::Value| serde_json::from_value::(value).ok()) + .map_or("unknown".to_string(), |zpage| zpage.status); + + panic!("timed out; context: `{context}`, state: `{state}`, healthz: `{healthz}`"); + } + } + + #[track_caller] + pub fn assert_state_files_are_as_expected( + &self, + pre_sequencer_height: u32, + post_sequencer_height: u32, + ) { + let pre_submit_state: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&self.config.pre_submit_path).unwrap()) + .unwrap(); + assert_json_include!( + actual: pre_submit_state, + expected: json!({ + "sequencer_height": pre_sequencer_height + }), + ); + + let post_submit_state: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&self.config.post_submit_path).unwrap()) + .unwrap(); + assert_json_include!( + actual: post_submit_state, + expected: json!({ + "sequencer_height": post_sequencer_height, + }), + ); + } +} + +// allow: want the name to reflect this is a test config. +#[allow(clippy::module_name_repetitions)] +pub struct TestSequencerRelayerConfig { + /// Sets up the test relayer to ignore all blocks except those proposed by the same address + /// stored in its validator key. + pub relay_only_self: bool, + /// Sets the start height of relayer and configures the on-disk pre- and post-submit files to + /// look accordingly. + pub last_written_sequencer_height: Option, + /// The rollup ID filter, to be stringified and provided as `Config::only_include_rollups` + /// value. + pub only_include_rollups: HashSet, +} + +impl TestSequencerRelayerConfig { + pub async fn spawn_relayer(self) -> TestSequencerRelayer { + assert_ne!( + runtime::Handle::current().runtime_flavor(), + RuntimeFlavor::CurrentThread, + "the sequencer relayer must be run on a multi-threaded runtime, e.g. the test could \ + be configured using `#[tokio::test(flavor = \"multi_thread\", worker_threads = 1)]`" + ); + Lazy::force(&TELEMETRY); + + let celestia_app = MockCelestiaAppServer::spawn().await; + let celestia_app_grpc_endpoint = format!("http://{}", celestia_app.local_addr); + let celestia_keyfile = write_file( + b"c8076374e2a4a58db1c924e3dafc055e9685481054fe99e58ed67f5c6ed80e62".as_slice(), + ) + .await; + + let validator_keyfile = write_file(PRIVATE_VALIDATOR_KEY.as_bytes()).await; + let PrivValidatorKey { + address, + priv_key, + .. + } = PrivValidatorKey::parse_json(PRIVATE_VALIDATOR_KEY).unwrap(); + let signing_key = priv_key + .ed25519_signing_key() + .cloned() + .unwrap() + .try_into() + .unwrap(); + + let cometbft = WireMockServer::start().await; + + let sequencer = MockSequencerServer::spawn().await; + let sequencer_grpc_endpoint = format!("http://{}", sequencer.local_addr); + + let (pre_submit_file, post_submit_file) = + if let Some(last_written_sequencer_height) = self.last_written_sequencer_height { + create_files_for_start_at_height(last_written_sequencer_height) + } else { + create_files_for_fresh_start() + }; + + let only_include_rollups = self.only_include_rollups.iter().join(",").to_string(); + + let config = Config { + cometbft_endpoint: cometbft.uri(), + sequencer_grpc_endpoint, + celestia_app_grpc_endpoint, + celestia_app_key_file: celestia_keyfile.path().to_string_lossy().to_string(), + block_time: 1000, + relay_only_validator_key_blocks: self.relay_only_self, + validator_key_file: validator_keyfile.path().to_string_lossy().to_string(), + only_include_rollups, + api_addr: "0.0.0.0:0".into(), + log: String::new(), + force_stdout: false, + no_otel: false, + no_metrics: false, + metrics_http_listener_addr: String::new(), + pretty_print: true, + pre_submit_path: pre_submit_file.path().to_owned(), + post_submit_path: post_submit_file.path().to_owned(), + }; + + info!(config = serde_json::to_string(&config).unwrap()); + let (sequencer_relayer, relayer_shutdown_handle) = + SequencerRelayer::new(config.clone()).unwrap(); + let api_address = sequencer_relayer.local_addr(); + let sequencer_relayer = tokio::task::spawn(sequencer_relayer.run()); + + TestSequencerRelayer { + api_address, + celestia_app, + config, + sequencer, + cometbft, + relayer_shutdown_handle: Some(relayer_shutdown_handle), + sequencer_relayer, + signing_key, + account: address, + validator_keyfile, + pre_submit_file, + post_submit_file, + } + } +} + +#[derive(Deserialize, Debug)] +pub struct SequencerRelayerState { + pub ready: bool, + pub celestia_connected: bool, + pub sequencer_connected: bool, + pub latest_confirmed_celestia_height: Option, + pub latest_fetched_sequencer_height: Option, + pub latest_observed_sequencer_height: Option, + pub latest_requested_sequencer_height: Option, +} + +#[derive(Deserialize, Debug)] +struct ZPage { + status: String, +} + +enum ZPageType { + Healthz, + Readyz, +} + +impl Display for ZPageType { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + ZPageType::Healthz => formatter.write_str("healthz"), + ZPageType::Readyz => formatter.write_str("readyz"), + } + } +} + +async fn write_file(data: &'static [u8]) -> NamedTempFile { + tokio::task::spawn_blocking(|| { + let keyfile = NamedTempFile::new().unwrap(); + (&keyfile).write_all(data).unwrap(); + keyfile + }) + .await + .unwrap() +} + +fn create_files_for_fresh_start() -> (NamedTempFile, NamedTempFile) { + let pre = NamedTempFile::new() + .expect("must be able to create an empty pre submit state file to run tests"); + let post = NamedTempFile::new() + .expect("must be able to create an empty post submit state file to run tests"); + serde_json::to_writer( + &pre, + &json!({ + "state": "ignore" + }), + ) + .expect("must be able to write pre-submit state to run tests"); + serde_json::to_writer( + &post, + &json!({ + "state": "fresh" + }), + ) + .expect("must be able to write post-submit state to run tests"); + (pre, post) +} + +fn create_files_for_start_at_height(height: u64) -> (NamedTempFile, NamedTempFile) { + let pre = NamedTempFile::new() + .expect("must be able to create an empty pre submit state file to run tests"); + let post = NamedTempFile::new() + .expect("must be able to create an empty post submit state file to run tests"); + + serde_json::to_writer( + &pre, + &json!({ + "state": "ignore", + }), + ) + .expect("must be able to write pre state to file to run tests"); + serde_json::to_writer_pretty( + &post, + &json!({ + "state": "submitted", + "celestia_height": 5, + "sequencer_height": height + }), + ) + .expect("must be able to write post state to file to run tests"); + (pre, post) +} diff --git a/crates/astria-sequencer-relayer/tests/blackbox/main.rs b/crates/astria-sequencer-relayer/tests/blackbox/main.rs index e38e2e744d..bcd165f4f0 100644 --- a/crates/astria-sequencer-relayer/tests/blackbox/main.rs +++ b/crates/astria-sequencer-relayer/tests/blackbox/main.rs @@ -1,29 +1,75 @@ #![allow(clippy::missing_panics_doc)] -pub mod helper; +pub mod helpers; -use std::{ - collections::HashSet, - time::Duration, -}; +use std::collections::HashSet; -use assert_json_diff::assert_json_include; -use helper::{ - CometBftBlockToMount, +use astria_core::{ + primitive::v1::RollupId, + protocol::test_utils::ConfigureSequencerBlock, +}; +use helpers::{ + SequencerBlockToMount, TestSequencerRelayerConfig, }; use reqwest::StatusCode; -use serde_json::json; -use tokio::time::{ - sleep, - timeout, -}; +use tendermint::account::Id as AccountId; +const RELAY_SELF: bool = true; const RELAY_ALL: bool = false; +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn one_block_is_relayed_to_celestia() { + let sequencer_relayer = TestSequencerRelayerConfig { + relay_only_self: false, + last_written_sequencer_height: None, + only_include_rollups: HashSet::new(), + } + .spawn_relayer() + .await; + + sequencer_relayer.mount_abci_response(1).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(1); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 1") + .await; + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 1") + .await; + let get_tx_guard = sequencer_relayer + .mount_celestia_app_get_tx_response_as_scoped(53, "get tx 1") + .await; + // The `MIN_POLL_INTERVAL_SECS` is 1, meaning the relayer waits for 1 second before attempting + // the first `GetTx`, so we wait for 2 seconds. + sequencer_relayer + .timeout_ms( + 2_000, + "waiting for get tx guard", + get_tx_guard.wait_until_satisfied(), + ) + .await; + + // Assert the relayer reports the correct Celestia and sequencer heights. + sequencer_relayer + .wait_for_latest_confirmed_celestia_height(53, 1_000) + .await; + sequencer_relayer + .wait_for_latest_fetched_sequencer_height(1, 1_000) + .await; + sequencer_relayer + .wait_for_latest_observed_sequencer_height(1, 1_000) + .await; + + assert_eq!( + sequencer_relayer.celestia_app_received_blob_count(), + 2, + "expected 2 blobs in total, 1 header blob and 1 rollup blob" + ); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn report_degraded_if_block_fetch_fails() { - let mut sequencer_relayer = TestSequencerRelayerConfig { + let sequencer_relayer = TestSequencerRelayerConfig { relay_only_self: false, last_written_sequencer_height: None, only_include_rollups: HashSet::new(), @@ -31,60 +77,356 @@ async fn report_degraded_if_block_fetch_fails() { .spawn_relayer() .await; - // Relayer reports 200 on /readyz after start - let wait_for_readyz = async { - loop { - let readyz = reqwest::get(format!("http://{}/readyz", sequencer_relayer.api_address)) - .await - .unwrap(); - if readyz.status().is_success() { - break readyz; - } - sleep(Duration::from_millis(100)).await; - } - }; - let readyz = timeout(Duration::from_secs(1), wait_for_readyz) - .await - .expect("sequencer must report ready for test to work"); + // Relayer reports 200 on /readyz after start. + let readyz_status = sequencer_relayer + .wait_for_readyz(StatusCode::OK, 1_000, "waiting for readyz") + .await; + assert_eq!(readyz_status, "ok"); + + // Mount a good block, so the relayer will report 200 on /healthz. + sequencer_relayer.mount_abci_response(1).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(1); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 1") + .await; + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 1") + .await; + let get_tx_guard = sequencer_relayer + .mount_celestia_app_get_tx_response_as_scoped(53, "get tx 1") + .await; + let healthz_status = sequencer_relayer + .wait_for_healthz(StatusCode::OK, 2_000, "waiting for first healthz") + .await; + assert_eq!(healthz_status, "ok"); + sequencer_relayer + .timeout_ms( + 2_000, + "waiting for get tx guard", + get_tx_guard.wait_until_satisfied(), + ) + .await; + + // Mount a bad block next, so the relayer will fail to fetch the block. + sequencer_relayer.mount_abci_response(2).await; + let block_to_mount = SequencerBlockToMount::BadAtHeight(2); + let block_guard = sequencer_relayer + .mount_sequencer_block_response_as_scoped::(block_to_mount, "bad block 2") + .await; + + // Relayer reports 500 on /healthz after fetching the block failed. + let healthz_status = sequencer_relayer + .wait_for_healthz( + StatusCode::INTERNAL_SERVER_ERROR, + 2_000, + "waiting for second healthz", + ) + .await; + assert_eq!(healthz_status, "degraded"); + + sequencer_relayer + .timeout_ms( + 2_000, + "waiting for sequencer block guard", + block_guard.wait_until_satisfied(), + ) + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn later_height_in_state_leads_to_expected_relay() { + let sequencer_relayer = TestSequencerRelayerConfig { + relay_only_self: false, + last_written_sequencer_height: Some(5), + only_include_rollups: HashSet::new(), + } + .spawn_relayer() + .await; + + sequencer_relayer.mount_abci_response(6).await; + sequencer_relayer.mount_abci_response(7).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(6); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 1") + .await; + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 1") + .await; + let get_tx_guard = sequencer_relayer + .mount_celestia_app_get_tx_response_as_scoped(53, "get tx 1") + .await; + sequencer_relayer + .timeout_ms( + 2_000, + "waiting for get tx guard", + get_tx_guard.wait_until_satisfied(), + ) + .await; + + // Assert the relayer reports the correct Celestia and sequencer heights. + sequencer_relayer + .wait_for_latest_confirmed_celestia_height(53, 1_000) + .await; + sequencer_relayer + .wait_for_latest_fetched_sequencer_height(6, 1_000) + .await; + sequencer_relayer + .wait_for_latest_observed_sequencer_height(7, 1_000) + .await; assert_eq!( - StatusCode::OK, - readyz.status(), - "relayer should report 200 after start" + sequencer_relayer.celestia_app_received_blob_count(), + 2, + "expected 2 blobs in total, 1 header blob and 1 rollup blob" ); - assert_json_include!( - expected: json!({"status": "ok"}), - actual: readyz.json::().await.unwrap(), +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn three_blocks_are_relayed() { + let sequencer_relayer = TestSequencerRelayerConfig { + relay_only_self: false, + last_written_sequencer_height: None, + only_include_rollups: HashSet::new(), + } + .spawn_relayer() + .await; + + sequencer_relayer.mount_abci_response(1).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(1); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 1") + .await; + + sequencer_relayer.mount_abci_response(2).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(2); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 2") + .await; + + sequencer_relayer.mount_abci_response(3).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(3); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 3") + .await; + + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 1") + .await; + sequencer_relayer + .mount_celestia_app_get_tx_response(53, "get tx 1") + .await; + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 2") + .await; + sequencer_relayer + .mount_celestia_app_get_tx_response(53, "get tx 2") + .await; + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 3") + .await; + let get_tx_guard = sequencer_relayer + .mount_celestia_app_get_tx_response_as_scoped(53, "get tx 3") + .await; + // Each block will have taken ~1 second due to the delay before each `GetTx`, so use 4.5 + // seconds. + sequencer_relayer + .timeout_ms( + 4_500, + "waiting for get tx guard", + get_tx_guard.wait_until_satisfied(), + ) + .await; + + // Assert the relayer reports the correct Celestia and sequencer heights. + sequencer_relayer + .wait_for_latest_confirmed_celestia_height(53, 1_000) + .await; + sequencer_relayer + .wait_for_latest_fetched_sequencer_height(3, 1_000) + .await; + sequencer_relayer + .wait_for_latest_observed_sequencer_height(3, 1_000) + .await; + + assert_eq!( + sequencer_relayer.celestia_app_received_blob_count(), + 6, + "expected 6 blobs in total, 1 header blob and 1 rollup blob per block" ); +} - // mount a bad block next, so the relayer will fail to fetch the block - let abci_guard = sequencer_relayer.mount_abci_response(1).await; - let block_guard = - sequencer_relayer.mount_block_response::(CometBftBlockToMount::BadAtHeight(1)); - timeout( - Duration::from_millis(2 * sequencer_relayer.config.block_time), - futures::future::join( - abci_guard.wait_until_satisfied(), - block_guard.wait_until_satisfied(), - ), - ) - .await - .expect("requesting abci info and block must have occurred") - .1 - .unwrap(); - - // Relayer reports 500 on /healthz after fetching the block failed - let readyz = reqwest::get(format!("http://{}/healthz", sequencer_relayer.api_address)) - .await - .unwrap(); +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn block_from_other_proposer_is_skipped() { + let sequencer_relayer = TestSequencerRelayerConfig { + relay_only_self: true, + last_written_sequencer_height: None, + only_include_rollups: HashSet::new(), + } + .spawn_relayer() + .await; + + sequencer_relayer.mount_abci_response(1).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(1); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 1") + .await; + + sequencer_relayer.mount_abci_response(2).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(2); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 2") + .await; + + sequencer_relayer.mount_abci_response(3).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(3); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 3") + .await; + + // We only expect two broadcast/get Tx gRPCs - block 2 should not have been broadcast. + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 1") + .await; + sequencer_relayer + .mount_celestia_app_get_tx_response(53, "get tx 1") + .await; + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 2") + .await; + let get_tx_guard = sequencer_relayer + .mount_celestia_app_get_tx_response_as_scoped(53, "get tx 2") + .await; + // Each block will have taken ~1 second due to the delay before each `GetTx`, so use 4 seconds. + sequencer_relayer + .timeout_ms( + 4_000, + "waiting for get tx guard", + get_tx_guard.wait_until_satisfied(), + ) + .await; + + // Assert the relayer reports the correct Celestia and sequencer heights. + sequencer_relayer + .wait_for_latest_confirmed_celestia_height(53, 1_000) + .await; + sequencer_relayer + .wait_for_latest_fetched_sequencer_height(3, 1_000) + .await; + sequencer_relayer + .wait_for_latest_observed_sequencer_height(3, 1_000) + .await; assert_eq!( - StatusCode::INTERNAL_SERVER_ERROR, - readyz.status(), - "relayer should report 500 when failing to fetch block" + sequencer_relayer.celestia_app_received_blob_count(), + 4, + "expected 4 blobs in total, 1 header blob and 1 rollup blob per block" ); - assert_json_include!( - expected: json!({"status": "degraded"}), - actual: readyz.json::().await.unwrap(), +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn should_filter_rollup() { + let included_rollup_ids: HashSet<_> = (0..5).map(|x| RollupId::new([x; 32])).collect(); + let excluded_rollup_ids: HashSet<_> = (0..5).map(|x| RollupId::new([100 + x; 32])).collect(); + + let sequencer_relayer = TestSequencerRelayerConfig { + relay_only_self: false, + last_written_sequencer_height: None, + only_include_rollups: included_rollup_ids.clone(), + } + .spawn_relayer() + .await; + + // Create one transaction per rollup ID. + let sequence_data = included_rollup_ids + .iter() + .chain(excluded_rollup_ids.iter()) + .map(|id| (*id, vec![1; 1])) + .collect(); + let block = ConfigureSequencerBlock { + block_hash: Some([99u8; 32]), + height: 1, + proposer_address: Some(AccountId::try_from(vec![0u8; 20]).unwrap()), + sequence_data, + ..Default::default() + } + .make(); + let block_to_mount = SequencerBlockToMount::Block(block); + + sequencer_relayer.mount_abci_response(1).await; + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 1") + .await; + sequencer_relayer + .mount_celestia_app_broadcast_tx_response("broadcast tx 1") + .await; + let get_tx_guard = sequencer_relayer + .mount_celestia_app_get_tx_response_as_scoped(53, "get tx 1") + .await; + sequencer_relayer + .timeout_ms( + 2_000, + "waiting for get tx guard", + get_tx_guard.wait_until_satisfied(), + ) + .await; + + // There should be one blob per included rollup ID + one blob for sequencer namespace data. + assert_eq!( + sequencer_relayer.celestia_app_received_blob_count(), + included_rollup_ids.len() + 1 ); + + // Check all included rollups IDs are actually included in the seen blobs. + for included_rollup_id in included_rollup_ids { + assert!(sequencer_relayer.has_celestia_app_received_blob_from_rollup(included_rollup_id)); + } + + // Check all excluded rollups IDs are actually excluded from the seen blobs. + for excluded_rollup_id in excluded_rollup_ids { + assert!(!sequencer_relayer.has_celestia_app_received_blob_from_rollup(excluded_rollup_id)); + } +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn should_shut_down() { + let mut sequencer_relayer = TestSequencerRelayerConfig { + relay_only_self: false, + last_written_sequencer_height: None, + only_include_rollups: HashSet::new(), + } + .spawn_relayer() + .await; + + // Start handling a block. + sequencer_relayer.mount_abci_response(1).await; + let block_to_mount = SequencerBlockToMount::GoodAtHeight(1); + sequencer_relayer + .mount_sequencer_block_response::(block_to_mount, "good block 1") + .await; + let broadcast_guard = sequencer_relayer + .mount_celestia_app_broadcast_tx_response_as_scoped("broadcast tx 1") + .await; + sequencer_relayer + .timeout_ms( + 1_000, + "waiting for broadcast guard", + broadcast_guard.wait_until_satisfied(), + ) + .await; + + // Send the shutdown signal - equivalent to sigkill being issued to sequencer-relayer process. + sequencer_relayer.relayer_shutdown_handle.take(); + + let get_tx_guard = sequencer_relayer + .mount_celestia_app_get_tx_response_as_scoped(53, "get tx 1") + .await; + sequencer_relayer + .timeout_ms( + 2_000, + "waiting for get tx guard", + get_tx_guard.wait_until_satisfied(), + ) + .await; + + sequencer_relayer.wait_for_relayer_shutdown(1_000).await; } diff --git a/tools/protobuf-compiler/src/main.rs b/tools/protobuf-compiler/src/main.rs index 84ccdebdfd..e5970ad900 100644 --- a/tools/protobuf-compiler/src/main.rs +++ b/tools/protobuf-compiler/src/main.rs @@ -112,6 +112,9 @@ fn main() { ".astria.execution.v1alpha2", ".astria.primitive.v1", ".astria.sequencerblock.v1alpha1", + ".celestia", + ".cosmos", + ".tendermint", ]) .unwrap();