From f3ea62eaf47035c5936039abf170522092ff2b36 Mon Sep 17 00:00:00 2001 From: Ethan Oroshiba Date: Mon, 12 Aug 2024 12:45:56 -0500 Subject: [PATCH 01/18] chore(core, proto): migrate byte slices from Vec to Bytes (#1319) ## Summary Changed protobuf, astria-core, and dependent code to utilize generic `Bytes` instead of the default `Vec` to utilize zero-copy deserialization from Protobuf and make type conversion cheaper. ## Background Previously, some components were utilizing `Bytes` and some were utilizing `Vec` as the generated Rust types when converting from Protobuf `bytes`. ## Changes - Adjusted Protobuf compiler to use `Bytes` for all Astria paths. - Edited conversions to/from `Raw` in `astria-core` to utilize `prost::bytes::Bytes`. - Adjusted code which relied on `Vec`. ## Testing Passing all tests. ## Related Issues closes #674 --- crates/astria-composer/src/collectors/geth.rs | 2 +- crates/astria-composer/src/executor/tests.rs | 13 ++-- crates/astria-composer/src/test_utils.rs | 2 +- .../tests/blackbox/grpc_collector.rs | 13 ++-- .../src/celestia/block_verifier.rs | 15 ++--- crates/astria-conductor/src/celestia/mod.rs | 3 +- .../astria-conductor/src/executor/client.rs | 4 +- crates/astria-conductor/src/executor/mod.rs | 2 +- .../src/generated/astria.composer.v1alpha1.rs | 8 +-- .../generated/astria.execution.v1alpha1.rs | 20 +++--- .../astria.protocol.bridge.v1alpha1.rs | 4 +- .../astria.protocol.transactions.v1alpha1.rs | 12 ++-- .../astria.sequencerblock.v1alpha1.rs | 42 ++++++------- crates/astria-core/src/primitive/v1/mod.rs | 16 +---- .../src/protocol/bridge/v1alpha1/mod.rs | 15 +++-- crates/astria-core/src/protocol/mod.rs | 7 ++- crates/astria-core/src/protocol/test_utils.rs | 6 +- .../protocol/transaction/v1alpha1/action.rs | 3 +- .../src/protocol/transaction/v1alpha1/mod.rs | 9 +-- .../src/sequencerblock/v1alpha1/block.rs | 62 ++++++++++++------- .../src/sequencerblock/v1alpha1/celestia.rs | 19 +++--- .../astria-sequencer-client/src/tests/http.rs | 3 +- .../blackbox/helpers/mock_sequencer_server.rs | 4 +- .../astria-sequencer-utils/src/blob_parser.rs | 2 +- crates/astria-sequencer/src/api_state_ext.rs | 3 +- crates/astria-sequencer/src/app/test_utils.rs | 3 +- crates/astria-sequencer/src/app/tests_app.rs | 19 +++--- .../src/app/tests_breaking_changes.rs | 9 ++- .../src/app/tests_execute_transaction.rs | 11 ++-- crates/astria-sequencer/src/grpc/sequencer.rs | 8 ++- .../src/mempool/benchmarks.rs | 3 +- .../src/proposal/commitment.rs | 5 +- .../astria-sequencer/src/service/consensus.rs | 2 +- .../src/transaction/checks.rs | 5 +- tools/protobuf-compiler/src/main.rs | 4 +- 35 files changed, 199 insertions(+), 159 deletions(-) diff --git a/crates/astria-composer/src/collectors/geth.rs b/crates/astria-composer/src/collectors/geth.rs index ae9759baa2..5b819de468 100644 --- a/crates/astria-composer/src/collectors/geth.rs +++ b/crates/astria-composer/src/collectors/geth.rs @@ -236,7 +236,7 @@ impl Geth { let data = tx.rlp().to_vec(); let seq_action = SequenceAction { rollup_id, - data, + data: data.into(), fee_asset: fee_asset.clone(), }; diff --git a/crates/astria-composer/src/executor/tests.rs b/crates/astria-composer/src/executor/tests.rs index 463cddfd14..0529fd449f 100644 --- a/crates/astria-composer/src/executor/tests.rs +++ b/crates/astria-composer/src/executor/tests.rs @@ -13,7 +13,10 @@ use astria_core::{ }; use astria_eyre::eyre; use once_cell::sync::Lazy; -use prost::Message; +use prost::{ + bytes::Bytes, + Message as _, +}; use sequencer_client::SignedTransaction; use serde_json::json; use tempfile::NamedTempFile; @@ -81,7 +84,7 @@ static TELEMETRY: Lazy<()> = Lazy::new(|| { fn sequence_action() -> SequenceAction { SequenceAction { rollup_id: RollupId::new([0; ROLLUP_ID_LEN]), - data: vec![], + data: Bytes::new(), fee_asset: "nria".parse().unwrap(), } } @@ -411,7 +414,7 @@ async fn bundle_triggered_by_block_timer() { // send two sequence actions to the executor, both small enough to fit in a single bundle // without filling it let seq0 = SequenceAction { - data: vec![0u8; cfg.max_bytes_per_bundle / 4], + data: vec![0u8; cfg.max_bytes_per_bundle / 4].into(), ..sequence_action() }; @@ -498,13 +501,13 @@ async fn two_seq_actions_single_bundle() { // send two sequence actions to the executor, both small enough to fit in a single bundle // without filling it let seq0 = SequenceAction { - data: vec![0u8; cfg.max_bytes_per_bundle / 4], + data: vec![0u8; cfg.max_bytes_per_bundle / 4].into(), ..sequence_action() }; let seq1 = SequenceAction { rollup_id: RollupId::new([1; ROLLUP_ID_LEN]), - data: vec![1u8; cfg.max_bytes_per_bundle / 4], + data: vec![1u8; cfg.max_bytes_per_bundle / 4].into(), ..sequence_action() }; diff --git a/crates/astria-composer/src/test_utils.rs b/crates/astria-composer/src/test_utils.rs index 830e61b348..0759b81666 100644 --- a/crates/astria-composer/src/test_utils.rs +++ b/crates/astria-composer/src/test_utils.rs @@ -16,7 +16,7 @@ fn encoded_len(action: &SequenceAction) -> usize { pub(crate) fn sequence_action_with_n_bytes(n: usize) -> SequenceAction { SequenceAction { rollup_id: RollupId::new([0; ROLLUP_ID_LEN]), - data: vec![0; n], + data: vec![0; n].into(), fee_asset: "nria" .parse::() .unwrap() diff --git a/crates/astria-composer/tests/blackbox/grpc_collector.rs b/crates/astria-composer/tests/blackbox/grpc_collector.rs index 2080c44635..e1ebfe70b3 100644 --- a/crates/astria-composer/tests/blackbox/grpc_collector.rs +++ b/crates/astria-composer/tests/blackbox/grpc_collector.rs @@ -11,6 +11,7 @@ use astria_core::{ primitive::v1::RollupId, }; use ethers::prelude::Transaction; +use prost::bytes::Bytes; use crate::helper::{ mount_broadcast_tx_sync_invalid_nonce_mock, @@ -45,8 +46,8 @@ async fn tx_from_one_rollup_is_received_by_sequencer() { .unwrap(); composer_client .submit_rollup_transaction(SubmitRollupTransactionRequest { - rollup_id: rollup_id.as_ref().to_vec(), - data: tx.rlp().to_vec(), + rollup_id: Bytes::copy_from_slice(rollup_id.as_ref()), + data: Bytes::copy_from_slice(&tx.rlp()), }) .await .expect("rollup transactions should have been submitted successfully to grpc collector"); @@ -107,8 +108,8 @@ async fn invalid_nonce_causes_resubmission_under_different_nonce() { .unwrap(); composer_client .submit_rollup_transaction(SubmitRollupTransactionRequest { - rollup_id: rollup_id.as_ref().to_vec(), - data: tx.rlp().to_vec(), + rollup_id: Bytes::copy_from_slice(rollup_id.as_ref()), + data: Bytes::copy_from_slice(&tx.rlp()), }) .await .expect("rollup transactions should have been submitted successfully to grpc collector"); @@ -162,8 +163,8 @@ async fn single_rollup_tx_payload_integrity() { .unwrap(); composer_client .submit_rollup_transaction(SubmitRollupTransactionRequest { - rollup_id: rollup_id.as_ref().to_vec(), - data: tx.rlp().to_vec(), + rollup_id: Bytes::copy_from_slice(rollup_id.as_ref()), + data: Bytes::copy_from_slice(&tx.rlp()), }) .await .expect("rollup transactions should have been submitted successfully to grpc collector"); diff --git a/crates/astria-conductor/src/celestia/block_verifier.rs b/crates/astria-conductor/src/celestia/block_verifier.rs index b68178846a..565f284320 100644 --- a/crates/astria-conductor/src/celestia/block_verifier.rs +++ b/crates/astria-conductor/src/celestia/block_verifier.rs @@ -228,6 +228,7 @@ mod test { celestia::UncheckedSubmittedMetadata, }, }; + use bytes::Bytes; use prost::Message as _; use sequencer_client::{ tendermint::{ @@ -337,9 +338,9 @@ mod test { seconds: 1, nanos: 0, }), - data_hash: data_hash.to_vec(), - rollup_transactions_root: rollup_transactions_root.to_vec(), - proposer_address: proposer_address.as_bytes().to_vec(), + data_hash: Bytes::copy_from_slice(&data_hash), + rollup_transactions_root: Bytes::copy_from_slice(&rollup_transactions_root), + proposer_address: Bytes::copy_from_slice(proposer_address.as_bytes()), }; let header = SequencerBlockHeader::try_from_raw(header).unwrap(); @@ -359,7 +360,7 @@ mod test { #[tokio::test] async fn validate_sequencer_blob_with_chain_ids() { - let test_tx = b"test-tx".to_vec(); + let test_tx = Bytes::from_static(b"test-tx"); let rollup_id = RollupId::from_unhashed_bytes(b"test-chain"); let grouped_txs = BTreeMap::from([(rollup_id, vec![test_tx.clone()])]); let rollup_transactions_tree = @@ -382,9 +383,9 @@ mod test { seconds: 1, nanos: 0, }), - data_hash: data_hash.to_vec(), - rollup_transactions_root: rollup_transactions_root.to_vec(), - proposer_address: proposer_address.as_bytes().to_vec(), + data_hash: Bytes::copy_from_slice(&data_hash), + rollup_transactions_root: Bytes::copy_from_slice(&rollup_transactions_root), + proposer_address: Bytes::copy_from_slice(proposer_address.as_bytes()), }; let header = SequencerBlockHeader::try_from_raw(header).unwrap(); diff --git a/crates/astria-conductor/src/celestia/mod.rs b/crates/astria-conductor/src/celestia/mod.rs index 96e57eea20..7c209802e6 100644 --- a/crates/astria-conductor/src/celestia/mod.rs +++ b/crates/astria-conductor/src/celestia/mod.rs @@ -13,6 +13,7 @@ use astria_eyre::eyre::{ bail, WrapErr as _, }; +use bytes::Bytes; use celestia_types::nmt::Namespace; use futures::{ future::{ @@ -101,7 +102,7 @@ pub(crate) struct ReconstructedBlock { pub(crate) celestia_height: u64, pub(crate) block_hash: [u8; 32], pub(crate) header: SequencerBlockHeader, - pub(crate) transactions: Vec>, + pub(crate) transactions: Vec, } impl ReconstructedBlock { diff --git a/crates/astria-conductor/src/executor/client.rs b/crates/astria-conductor/src/executor/client.rs index 8704ee0767..46d784b4a2 100644 --- a/crates/astria-conductor/src/executor/client.rs +++ b/crates/astria-conductor/src/executor/client.rs @@ -117,14 +117,14 @@ impl Client { pub(super) async fn execute_block_with_retry( &mut self, prev_block_hash: Bytes, - transactions: Vec>, + transactions: Vec, timestamp: Timestamp, ) -> eyre::Result { use prost::Message; let transactions = transactions .into_iter() - .map(|tx| RollupData::decode(tx.as_slice())) + .map(RollupData::decode) .collect::>() .wrap_err("failed to decode tx bytes as RollupData")?; diff --git a/crates/astria-conductor/src/executor/mod.rs b/crates/astria-conductor/src/executor/mod.rs index 2d55ce1fd7..2ef56213d9 100644 --- a/crates/astria-conductor/src/executor/mod.rs +++ b/crates/astria-conductor/src/executor/mod.rs @@ -686,7 +686,7 @@ struct ExecutableBlock { hash: [u8; 32], height: SequencerHeight, timestamp: pbjson_types::Timestamp, - transactions: Vec>, + transactions: Vec, } impl ExecutableBlock { diff --git a/crates/astria-core/src/generated/astria.composer.v1alpha1.rs b/crates/astria-core/src/generated/astria.composer.v1alpha1.rs index 7092b8fa51..3a50686cc8 100644 --- a/crates/astria-core/src/generated/astria.composer.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.composer.v1alpha1.rs @@ -4,11 +4,11 @@ #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubmitRollupTransactionRequest { /// the unhashed rollup id - #[prost(bytes = "vec", tag = "1")] - pub rollup_id: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub rollup_id: ::prost::bytes::Bytes, /// the raw data bytes of the rollup transaction - #[prost(bytes = "vec", tag = "2")] - pub data: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "2")] + pub data: ::prost::bytes::Bytes, } impl ::prost::Name for SubmitRollupTransactionRequest { const NAME: &'static str = "SubmitRollupTransactionRequest"; diff --git a/crates/astria-core/src/generated/astria.execution.v1alpha1.rs b/crates/astria-core/src/generated/astria.execution.v1alpha1.rs index bc1a406985..d9cd0169ad 100644 --- a/crates/astria-core/src/generated/astria.execution.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.execution.v1alpha1.rs @@ -1,10 +1,10 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DoBlockRequest { - #[prost(bytes = "vec", tag = "1")] - pub prev_block_hash: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", repeated, tag = "2")] - pub transactions: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes = "bytes", tag = "1")] + pub prev_block_hash: ::prost::bytes::Bytes, + #[prost(bytes = "bytes", repeated, tag = "2")] + pub transactions: ::prost::alloc::vec::Vec<::prost::bytes::Bytes>, #[prost(message, optional, tag = "3")] pub timestamp: ::core::option::Option<::pbjson_types::Timestamp>, } @@ -18,8 +18,8 @@ impl ::prost::Name for DoBlockRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DoBlockResponse { - #[prost(bytes = "vec", tag = "1")] - pub block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub block_hash: ::prost::bytes::Bytes, } impl ::prost::Name for DoBlockResponse { const NAME: &'static str = "DoBlockResponse"; @@ -31,8 +31,8 @@ impl ::prost::Name for DoBlockResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct FinalizeBlockRequest { - #[prost(bytes = "vec", tag = "1")] - pub block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub block_hash: ::prost::bytes::Bytes, } impl ::prost::Name for FinalizeBlockRequest { const NAME: &'static str = "FinalizeBlockRequest"; @@ -64,8 +64,8 @@ impl ::prost::Name for InitStateRequest { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct InitStateResponse { - #[prost(bytes = "vec", tag = "1")] - pub block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub block_hash: ::prost::bytes::Bytes, } impl ::prost::Name for InitStateResponse { const NAME: &'static str = "InitStateResponse"; diff --git a/crates/astria-core/src/generated/astria.protocol.bridge.v1alpha1.rs b/crates/astria-core/src/generated/astria.protocol.bridge.v1alpha1.rs index 642d4485eb..64f7511f83 100644 --- a/crates/astria-core/src/generated/astria.protocol.bridge.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.protocol.bridge.v1alpha1.rs @@ -6,8 +6,8 @@ pub struct BridgeAccountLastTxHashResponse { #[prost(uint64, tag = "2")] pub height: u64, - #[prost(bytes = "vec", optional, tag = "3")] - pub tx_hash: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes = "bytes", optional, tag = "3")] + pub tx_hash: ::core::option::Option<::prost::bytes::Bytes>, } impl ::prost::Name for BridgeAccountLastTxHashResponse { const NAME: &'static str = "BridgeAccountLastTxHashResponse"; diff --git a/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs b/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs index f9ad97a170..56ecdd511b 100644 --- a/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs @@ -5,10 +5,10 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignedTransaction { - #[prost(bytes = "vec", tag = "1")] - pub signature: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "2")] - pub public_key: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub signature: ::prost::bytes::Bytes, + #[prost(bytes = "bytes", tag = "2")] + pub public_key: ::prost::bytes::Bytes, #[prost(message, optional, tag = "3")] pub transaction: ::core::option::Option<::pbjson_types::Any>, } @@ -144,8 +144,8 @@ impl ::prost::Name for TransferAction { pub struct SequenceAction { #[prost(message, optional, tag = "1")] pub rollup_id: ::core::option::Option, - #[prost(bytes = "vec", tag = "2")] - pub data: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "2")] + pub data: ::prost::bytes::Bytes, /// the asset used to pay the transaction fee #[prost(string, tag = "3")] pub fee_asset: ::prost::alloc::string::String, diff --git a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs index eac5b68d69..725abeac05 100644 --- a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs @@ -11,8 +11,8 @@ pub struct RollupTransactions { pub rollup_id: ::core::option::Option, /// The serialized bytes of the rollup data. /// Each entry is a protobuf-encoded `RollupData` message. - #[prost(bytes = "vec", repeated, tag = "2")] - pub transactions: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes = "bytes", repeated, tag = "2")] + pub transactions: ::prost::alloc::vec::Vec<::prost::bytes::Bytes>, /// The proof that these rollup transactions are included in sequencer block. /// `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. #[prost(message, optional, tag = "3")] @@ -57,8 +57,8 @@ pub struct SequencerBlock { #[prost(message, optional, tag = "4")] pub rollup_ids_proof: ::core::option::Option, /// / The block hash of the cometbft block that corresponds to this sequencer block. - #[prost(bytes = "vec", tag = "5")] - pub block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "5")] + pub block_hash: ::prost::bytes::Bytes, } impl ::prost::Name for SequencerBlock { const NAME: &'static str = "SequencerBlock"; @@ -80,15 +80,15 @@ pub struct SequencerBlockHeader { #[prost(message, optional, tag = "3")] pub time: ::core::option::Option<::pbjson_types::Timestamp>, /// the data_hash of the sequencer block (merkle root of all transaction hashes) - #[prost(bytes = "vec", tag = "4")] - pub data_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "4")] + pub data_hash: ::prost::bytes::Bytes, /// the cometbft proposer address of the sequencer block - #[prost(bytes = "vec", tag = "5")] - pub proposer_address: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "5")] + pub proposer_address: ::prost::bytes::Bytes, /// The 32-byte merkle root of all the rollup transactions in the block, /// Corresponds to `MHT(astria.SequencerBlock.rollup_transactions)`, - #[prost(bytes = "vec", tag = "6")] - pub rollup_transactions_root: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "6")] + pub rollup_transactions_root: ::prost::bytes::Bytes, } impl ::prost::Name for SequencerBlockHeader { const NAME: &'static str = "SequencerBlockHeader"; @@ -140,8 +140,8 @@ impl ::prost::Name for Deposit { #[derive(Clone, PartialEq, ::prost::Message)] pub struct FilteredSequencerBlock { /// / The block hash of the cometbft block that corresponds to this sequencer block. - #[prost(bytes = "vec", tag = "1")] - pub block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub block_hash: ::prost::bytes::Bytes, /// the block header, which contains sequencer-specific commitments. #[prost(message, optional, tag = "2")] pub header: ::core::option::Option, @@ -162,8 +162,8 @@ pub struct FilteredSequencerBlock { /// and is extracted from `astria.SequencerBlock.rollup_transactions`. /// Note that these are all the rollup IDs in the sequencer block, not merely those in /// `rollup_transactions` field. This is necessary to prove that no rollup IDs were omitted. - #[prost(bytes = "vec", repeated, tag = "5")] - pub all_rollup_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes = "bytes", repeated, tag = "5")] + pub all_rollup_ids: ::prost::alloc::vec::Vec<::prost::bytes::Bytes>, /// The proof that the `rollup_ids` are included /// in the CometBFT block this sequencer block is derived form. /// @@ -201,7 +201,7 @@ pub mod rollup_data { #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Value { #[prost(bytes, tag = "1")] - SequencedData(::prost::alloc::vec::Vec), + SequencedData(::prost::bytes::Bytes), #[prost(message, tag = "2")] Deposit(super::Deposit), } @@ -241,15 +241,15 @@ impl ::prost::Name for SubmittedRollupDataList { #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubmittedRollupData { /// The hash of the sequencer block. Must be 32 bytes. - #[prost(bytes = "vec", tag = "1")] - pub sequencer_block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub sequencer_block_hash: ::prost::bytes::Bytes, /// The 32 bytes identifying the rollup this blob belongs to. Matches /// `astria.sequencer.v1.RollupTransactions.rollup_id` #[prost(message, optional, tag = "2")] pub rollup_id: ::core::option::Option, /// A list of opaque bytes that are serialized rollup transactions. - #[prost(bytes = "vec", repeated, tag = "3")] - pub transactions: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes = "bytes", repeated, tag = "3")] + pub transactions: ::prost::alloc::vec::Vec<::prost::bytes::Bytes>, /// The proof that these rollup transactions are included in sequencer block. /// `astria.sequencer.v1alpha.SequencerBlock.rollup_transactions_proof`. #[prost(message, optional, tag = "4")] @@ -290,8 +290,8 @@ impl ::prost::Name for SubmittedMetadataList { #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubmittedMetadata { /// the 32-byte block hash of the sequencer block. - #[prost(bytes = "vec", tag = "1")] - pub block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "bytes", tag = "1")] + pub block_hash: ::prost::bytes::Bytes, /// the block header, which contains sequencer-specific commitments. #[prost(message, optional, tag = "2")] pub header: ::core::option::Option, diff --git a/crates/astria-core/src/primitive/v1/mod.rs b/crates/astria-core/src/primitive/v1/mod.rs index ba7b83762a..bf88007ec9 100644 --- a/crates/astria-core/src/primitive/v1/mod.rs +++ b/crates/astria-core/src/primitive/v1/mod.rs @@ -7,6 +7,7 @@ use base64::{ display::Base64Display, prelude::BASE64_STANDARD, }; +use bytes::Bytes; use sha2::{ Digest as _, Sha256, @@ -171,19 +172,6 @@ impl RollupId { Ok(Self::new(inner)) } - /// Converts a byte vector to a rollup ID. - /// - /// # Errors - /// - /// Returns an error if the byte slice was not 32 bytes long. - pub fn try_from_vec(bytes: Vec) -> Result { - let inner = - <[u8; ROLLUP_ID_LEN]>::try_from(bytes).map_err(|bytes| IncorrectRollupIdLength { - received: bytes.len(), - })?; - Ok(Self::new(inner)) - } - #[must_use] pub fn to_raw(&self) -> raw::RollupId { raw::RollupId { @@ -490,7 +478,7 @@ impl std::fmt::Display for Address { pub fn derive_merkle_tree_from_rollup_txs<'a, T, U>(rollup_ids_to_txs: T) -> merkle::Tree where T: IntoIterator, - U: AsRef<[Vec]> + 'a + ?Sized, + U: AsRef<[Bytes]> + 'a + ?Sized, { let mut tree = merkle::Tree::new(); for (rollup_id, txs) in rollup_ids_to_txs { diff --git a/crates/astria-core/src/protocol/bridge/v1alpha1/mod.rs b/crates/astria-core/src/protocol/bridge/v1alpha1/mod.rs index 8fbcd562c0..651a8f3a8c 100644 --- a/crates/astria-core/src/protocol/bridge/v1alpha1/mod.rs +++ b/crates/astria-core/src/protocol/bridge/v1alpha1/mod.rs @@ -1,3 +1,5 @@ +use bytes::Bytes; + use super::raw; use crate::primitive::v1::{ asset, @@ -28,11 +30,12 @@ impl BridgeAccountLastTxHashResponse { height: raw.height, tx_hash: raw .tx_hash - .map(TryInto::<[u8; 32]>::try_into) - .transpose() - .map_err(|bytes: Vec| { - BridgeAccountLastTxHashResponseError::invalid_tx_hash(bytes.len()) - })?, + .map(|bytes| { + <[u8; 32]>::try_from(bytes.as_ref()).map_err(|_| { + BridgeAccountLastTxHashResponseError::invalid_tx_hash(bytes.len()) + }) + }) + .transpose()?, }) } @@ -40,7 +43,7 @@ impl BridgeAccountLastTxHashResponse { pub fn into_raw(self) -> raw::BridgeAccountLastTxHashResponse { raw::BridgeAccountLastTxHashResponse { height: self.height, - tx_hash: self.tx_hash.map(Into::into), + tx_hash: self.tx_hash.map(|tx_hash| Bytes::copy_from_slice(&tx_hash)), } } } diff --git a/crates/astria-core/src/protocol/mod.rs b/crates/astria-core/src/protocol/mod.rs index e7fea6cbbd..2d30299b22 100644 --- a/crates/astria-core/src/protocol/mod.rs +++ b/crates/astria-core/src/protocol/mod.rs @@ -1,3 +1,4 @@ +use bytes::Bytes; use indexmap::IndexMap; use transaction::v1alpha1::SignedTransaction; @@ -19,7 +20,7 @@ pub mod test_utils; /// TODO: This can all be done in-place once is stabilized. pub fn group_sequence_actions_in_signed_transaction_transactions_by_rollup_id( signed_transactions: &[SignedTransaction], -) -> IndexMap>> { +) -> IndexMap> { use prost::Message as _; use crate::sequencerblock::v1alpha1::block::RollupData; @@ -30,9 +31,9 @@ pub fn group_sequence_actions_in_signed_transaction_transactions_by_rollup_id( .flat_map(SignedTransaction::actions) { if let Some(action) = action.as_sequence() { - let txs_for_rollup: &mut Vec> = map.entry(action.rollup_id).or_insert(vec![]); + let txs_for_rollup: &mut Vec = map.entry(action.rollup_id).or_insert(vec![]); let rollup_data = RollupData::SequencedData(action.data.clone()); - txs_for_rollup.push(rollup_data.into_raw().encode_to_vec()); + txs_for_rollup.push(rollup_data.into_raw().encode_to_vec().into()); } } map.sort_unstable_keys(); diff --git a/crates/astria-core/src/protocol/test_utils.rs b/crates/astria-core/src/protocol/test_utils.rs index ff7f325fd1..1f399a4227 100644 --- a/crates/astria-core/src/protocol/test_utils.rs +++ b/crates/astria-core/src/protocol/test_utils.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; +use bytes::Bytes; use prost::Message as _; use super::{ @@ -94,7 +95,7 @@ impl ConfigureSequencerBlock { .map(|(rollup_id, data)| { SequenceAction { rollup_id, - data, + data: data.into(), fee_asset: "nria".parse().unwrap(), } .into() @@ -132,6 +133,7 @@ impl ConfigureSequencerBlock { RollupData::Deposit(Box::new(deposit)) .into_raw() .encode_to_vec() + .into() })); } rollup_transactions.sort_unstable_keys(); @@ -148,7 +150,7 @@ impl ConfigureSequencerBlock { rollup_ids_root.to_vec(), ]; data.extend(txs.into_iter().map(|tx| tx.into_raw().encode_to_vec())); - + let data = data.into_iter().map(Bytes::from).collect(); SequencerBlock::try_from_block_info_and_data( block_hash, chain_id.try_into().unwrap(), diff --git a/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs b/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs index da22b1039e..3ba5c03050 100644 --- a/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs +++ b/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs @@ -1,3 +1,4 @@ +use bytes::Bytes; use ibc_types::{ core::{ channel::ChannelId, @@ -379,7 +380,7 @@ enum SequenceActionErrorKind { #[allow(clippy::module_name_repetitions)] pub struct SequenceAction { pub rollup_id: RollupId, - pub data: Vec, + pub data: Bytes, /// asset to use for fee payment. pub fee_asset: asset::Denom, } diff --git a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs index d5e8543001..c7569452f8 100644 --- a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs +++ b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs @@ -1,3 +1,4 @@ +use bytes::Bytes; use prost::{ Message as _, Name as _, @@ -110,8 +111,8 @@ impl SignedTransaction { .. } = self; raw::SignedTransaction { - signature: signature.to_bytes().to_vec(), - public_key: verification_key.to_bytes().to_vec(), + signature: Bytes::copy_from_slice(&signature.to_bytes()), + public_key: Bytes::copy_from_slice(&verification_key.to_bytes()), transaction: Some(pbjson_types::Any { type_url: raw::UnsignedTransaction::type_url(), value: transaction_bytes, @@ -128,8 +129,8 @@ impl SignedTransaction { .. } = self; raw::SignedTransaction { - signature: signature.to_bytes().to_vec(), - public_key: verification_key.to_bytes().to_vec(), + signature: Bytes::copy_from_slice(&signature.to_bytes()), + public_key: Bytes::copy_from_slice(&verification_key.to_bytes()), transaction: Some(pbjson_types::Any { type_url: raw::UnsignedTransaction::type_url(), value: transaction_bytes.clone(), diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs index 8653961f16..5777814336 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use bytes::Bytes; use indexmap::IndexMap; use sha2::Sha256; use tendermint::{ @@ -68,7 +69,7 @@ enum RollupTransactionsErrorKind { #[derive(Clone, Debug, PartialEq)] pub struct RollupTransactionsParts { pub rollup_id: RollupId, - pub transactions: Vec>, + pub transactions: Vec, pub proof: merkle::Proof, } @@ -78,7 +79,7 @@ pub struct RollupTransactions { /// The 32 bytes identifying a rollup. Usually the sha256 hash of a plain rollup name. rollup_id: RollupId, /// The block data for this rollup in the form of encoded [`RollupData`]. - transactions: Vec>, + transactions: Vec, /// Proof that this set of transactions belongs in the rollup datas merkle tree proof: merkle::Proof, } @@ -92,7 +93,7 @@ impl RollupTransactions { /// Returns the block data for this rollup. #[must_use] - pub fn transactions(&self) -> &[Vec] { + pub fn transactions(&self) -> &[Bytes] { &self.transactions } @@ -112,6 +113,7 @@ impl RollupTransactions { transactions, proof, } = self; + let transactions = transactions.into_iter().map(Into::into).collect(); raw::RollupTransactions { rollup_id: Some(rollup_id.into_raw()), transactions, @@ -140,6 +142,7 @@ impl RollupTransactions { }; merkle::Proof::try_from_raw(proof).map_err(RollupTransactionsError::proof_invalid) }?; + let transactions = transactions.into_iter().map(Into::into).collect(); Ok(Self { rollup_id, transactions, @@ -408,9 +411,9 @@ impl SequencerBlockHeader { seconds: time.seconds, nanos: time.nanos, }), - rollup_transactions_root: self.rollup_transactions_root.to_vec(), - data_hash: self.data_hash.to_vec(), - proposer_address: self.proposer_address.as_bytes().to_vec(), + rollup_transactions_root: Bytes::copy_from_slice(&self.rollup_transactions_root), + data_hash: Bytes::copy_from_slice(&self.data_hash), + proposer_address: Bytes::copy_from_slice(self.proposer_address.as_bytes()), } } @@ -448,12 +451,14 @@ impl SequencerBlockHeader { .map_err(SequencerBlockHeaderError::time)?; let rollup_transactions_root = - rollup_transactions_root.try_into().map_err(|e: Vec<_>| { - SequencerBlockHeaderError::incorrect_rollup_transactions_root_length(e.len()) + rollup_transactions_root.as_ref().try_into().map_err(|_| { + SequencerBlockHeaderError::incorrect_rollup_transactions_root_length( + rollup_transactions_root.len(), + ) })?; - let data_hash = data_hash.try_into().map_err(|e: Vec<_>| { - SequencerBlockHeaderError::incorrect_rollup_transactions_root_length(e.len()) + let data_hash = data_hash.as_ref().try_into().map_err(|_| { + SequencerBlockHeaderError::incorrect_rollup_transactions_root_length(data_hash.len()) })?; let proposer_address = account::Id::try_from(proposer_address) @@ -621,7 +626,7 @@ impl SequencerBlock { rollup_ids_proof, } = self; raw::SequencerBlock { - block_hash: block_hash.to_vec(), + block_hash: Bytes::copy_from_slice(&block_hash), header: Some(header.into_raw()), rollup_transactions: rollup_transactions .into_values() @@ -698,13 +703,14 @@ impl SequencerBlock { /// # Panics /// /// - if a rollup data merkle proof cannot be constructed. + #[allow(clippy::too_many_lines)] // Temporary fix, should refactor: TODO(https://github.com/astriaorg/astria/issues/1357) pub fn try_from_block_info_and_data( block_hash: [u8; 32], chain_id: tendermint::chain::Id, height: tendermint::block::Height, time: Time, proposer_address: account::Id, - data: Vec>, + data: Vec, deposits: HashMap>, ) -> Result { use prost::Message as _; @@ -716,16 +722,18 @@ impl SequencerBlock { let rollup_transactions_root: [u8; 32] = data_list .next() .ok_or(SequencerBlockError::no_rollup_transactions_root())? + .as_ref() .try_into() - .map_err(|e: Vec<_>| { - SequencerBlockError::incorrect_rollup_transactions_root_length(e.len()) + .map_err(|_| { + SequencerBlockError::incorrect_rollup_transactions_root_length(data_list.len()) })?; let rollup_ids_root: [u8; 32] = data_list .next() .ok_or(SequencerBlockError::no_rollup_ids_root())? + .as_ref() .try_into() - .map_err(|e: Vec<_>| SequencerBlockError::incorrect_rollup_ids_root_length(e.len()))?; + .map_err(|_| SequencerBlockError::incorrect_rollup_ids_root_length(data_list.len()))?; let mut rollup_datas = IndexMap::new(); for elem in data_list { @@ -745,7 +753,10 @@ impl SequencerBlock { }) = action { let elem = rollup_datas.entry(rollup_id).or_insert(vec![]); - let data = RollupData::SequencedData(data).into_raw().encode_to_vec(); + let data = RollupData::SequencedData(data) + .into_raw() + .encode_to_vec() + .into(); elem.push(data); } } @@ -758,6 +769,7 @@ impl SequencerBlock { RollupData::Deposit(Box::new(deposit)) .into_raw() .encode_to_vec() + .into() })); } @@ -844,8 +856,9 @@ impl SequencerBlock { } = raw; let block_hash = block_hash + .as_ref() .try_into() - .map_err(|e: Vec<_>| SequencerBlockError::invalid_block_hash(e.len()))?; + .map_err(|_| SequencerBlockError::invalid_block_hash(block_hash.len()))?; let rollup_transactions_proof = 'proof: { let Some(rollup_transactions_proof) = rollup_transactions_proof else { @@ -1009,14 +1022,18 @@ impl FilteredSequencerBlock { .. } = self; raw::FilteredSequencerBlock { - block_hash: block_hash.to_vec(), + block_hash: Bytes::copy_from_slice(&block_hash), header: Some(header.into_raw()), rollup_transactions: rollup_transactions .into_values() .map(RollupTransactions::into_raw) .collect(), rollup_transactions_proof: Some(rollup_transactions_proof.into_raw()), - all_rollup_ids: self.all_rollup_ids.iter().map(|id| id.to_vec()).collect(), + all_rollup_ids: self + .all_rollup_ids + .iter() + .map(|id| Bytes::copy_from_slice(id.as_ref())) + .collect(), rollup_ids_proof: Some(rollup_ids_proof.into_raw()), } } @@ -1061,8 +1078,9 @@ impl FilteredSequencerBlock { } = raw; let block_hash = block_hash + .as_ref() .try_into() - .map_err(|e: Vec<_>| FilteredSequencerBlockError::invalid_block_hash(e.len()))?; + .map_err(|_| FilteredSequencerBlockError::invalid_block_hash(block_hash.len()))?; let rollup_transactions_proof = { let Some(rollup_transactions_proof) = rollup_transactions_proof else { @@ -1100,7 +1118,7 @@ impl FilteredSequencerBlock { let all_rollup_ids: Vec = all_rollup_ids .into_iter() - .map(RollupId::try_from_vec) + .map(|bytes| RollupId::try_from_slice(&bytes)) .collect::>() .map_err(FilteredSequencerBlockError::invalid_rollup_id)?; @@ -1423,7 +1441,7 @@ enum DepositErrorKind { /// and must decode it accordingly. #[derive(Debug, Clone, PartialEq)] pub enum RollupData { - SequencedData(Vec), + SequencedData(Bytes), Deposit(Box), } diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs b/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs index a1dfcf8d88..1646e9af3a 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/celestia.rs @@ -1,3 +1,4 @@ +use bytes::Bytes; use sha2::{ Digest as _, Sha256, @@ -51,6 +52,7 @@ impl PreparedBlock { proof, .. } = rollup_txs.into_parts(); + let transactions = transactions.into_iter().map(Bytes::into).collect(); tail.push(SubmittedRollupData { sequencer_block_hash: block_hash, rollup_id, @@ -138,7 +140,7 @@ pub struct UncheckedSubmittedRollupData { /// `astria.sequencerblock.v1alpha1.RollupTransactions.rollup_id` pub rollup_id: RollupId, /// A list of opaque bytes that are serialized rollup transactions. - pub transactions: Vec>, + pub transactions: Vec, /// The proof that these rollup transactions are included in sequencer block. pub proof: merkle::Proof, } @@ -159,7 +161,7 @@ pub struct SubmittedRollupData { /// `astria.sequencerblock.v1alpha1.RollupTransactions.rollup_id` rollup_id: RollupId, /// A list of opaque bytes that are serialized rollup transactions. - transactions: Vec>, + transactions: Vec, /// The proof that these rollup transactions are included in sequencer block. proof: merkle::Proof, } @@ -171,7 +173,7 @@ impl SubmittedRollupData { } #[must_use] - pub fn transactions(&self) -> &[Vec] { + pub fn transactions(&self) -> &[Bytes] { &self.transactions } @@ -235,7 +237,7 @@ impl SubmittedRollupData { proof, } = self; raw::SubmittedRollupData { - sequencer_block_hash: sequencer_block_hash.to_vec(), + sequencer_block_hash: Bytes::copy_from_slice(&sequencer_block_hash), rollup_id: Some(rollup_id.to_raw()), transactions, proof: Some(proof.into_raw()), @@ -258,8 +260,8 @@ impl SubmittedRollupData { }; let rollup_id = RollupId::try_from_raw(&rollup_id).map_err(SubmittedRollupDataError::rollup_id)?; - let sequencer_block_hash = sequencer_block_hash.try_into().map_err(|bytes: Vec| { - SubmittedRollupDataError::sequencer_block_hash(bytes.len()) + let sequencer_block_hash = sequencer_block_hash.as_ref().try_into().map_err(|_| { + SubmittedRollupDataError::sequencer_block_hash(sequencer_block_hash.len()) })?; let proof = 'proof: { let Some(proof) = proof else { @@ -458,8 +460,9 @@ impl UncheckedSubmittedMetadata { }?; let block_hash = block_hash + .as_ref() .try_into() - .map_err(|bytes: Vec<_>| SubmittedMetadataError::block_hash(bytes.len()))?; + .map_err(|_| SubmittedMetadataError::block_hash(block_hash.len()))?; Ok(Self { block_hash, @@ -618,7 +621,7 @@ impl SubmittedMetadata { .. } = self; raw::SubmittedMetadata { - block_hash: block_hash.to_vec(), + block_hash: Bytes::copy_from_slice(&block_hash), header: Some(header.into_raw()), rollup_ids: rollup_ids.into_iter().map(RollupId::into_raw).collect(), rollup_transactions_proof: Some(rollup_transactions_proof.into_raw()), diff --git a/crates/astria-sequencer-client/src/tests/http.rs b/crates/astria-sequencer-client/src/tests/http.rs index f3036c87cb..a7f40c365f 100644 --- a/crates/astria-sequencer-client/src/tests/http.rs +++ b/crates/astria-sequencer-client/src/tests/http.rs @@ -10,6 +10,7 @@ use astria_core::{ }, }; use hex_literal::hex; +use prost::bytes::Bytes; use serde_json::json; use tendermint::{ block::Height, @@ -295,7 +296,7 @@ async fn get_bridge_account_last_transaction_hash() { let expected_response = BridgeAccountLastTxHashResponse { height: 10, - tx_hash: Some([0; 32].to_vec()), + tx_hash: Some(Bytes::from_static(&[0; 32])), }; let _guard = register_abci_query_response( 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 index a1cca888f5..3b7d7c25a3 100644 --- a/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs +++ b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs @@ -160,7 +160,9 @@ fn prepare_sequencer_block_response( 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); + let mut data_hash = header.data_hash.to_vec(); + data_hash[0] = data_hash[0].wrapping_add(1); + header.data_hash = data_hash.into(); } Mock::for_rpc_given( diff --git a/crates/astria-sequencer-utils/src/blob_parser.rs b/crates/astria-sequencer-utils/src/blob_parser.rs index a5fdb45118..828a9cefca 100644 --- a/crates/astria-sequencer-utils/src/blob_parser.rs +++ b/crates/astria-sequencer-utils/src/blob_parser.rs @@ -648,7 +648,7 @@ impl VerboseRollupData { let transactions_and_deposits: Vec<_> = rollup_data .transactions .iter() - .map(RollupDataDetails::from) + .map(|bytes| RollupDataDetails::from(&bytes.to_vec())) .collect(); let item_count = transactions_and_deposits.len(); VerboseRollupData { diff --git a/crates/astria-sequencer/src/api_state_ext.rs b/crates/astria-sequencer/src/api_state_ext.rs index ec3fe43a40..19bf277150 100644 --- a/crates/astria-sequencer/src/api_state_ext.rs +++ b/crates/astria-sequencer/src/api_state_ext.rs @@ -23,6 +23,7 @@ use borsh::{ BorshDeserialize, BorshSerialize, }; +use bytes::Bytes; use cnidarium::{ StateRead, StateWrite, @@ -242,7 +243,7 @@ pub(crate) trait StateReadExt: StateRead { .context("failed to decode rollup IDs proof from raw bytes")?; let raw = raw::SequencerBlock { - block_hash: hash.to_vec(), + block_hash: Bytes::copy_from_slice(hash), header: header_raw.into(), rollup_transactions, rollup_transactions_proof: rollup_transactions_proof.into(), diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index 6ef60fac9c..09b3571a0a 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -18,6 +18,7 @@ use astria_core::{ UncheckedGenesisState, }, }; +use bytes::Bytes; use cnidarium::Storage; use penumbra_ibc::params::IBCParameters; @@ -146,7 +147,7 @@ pub(crate) fn get_mock_tx(nonce: u32) -> SignedTransaction { actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes([0; 32]), - data: vec![0x99], + data: Bytes::from_static(&[0x99]), fee_asset: "astria".parse().unwrap(), } .into(), diff --git a/crates/astria-sequencer/src/app/tests_app.rs b/crates/astria-sequencer/src/app/tests_app.rs index 762a27ba1c..195b810999 100644 --- a/crates/astria-sequencer/src/app/tests_app.rs +++ b/crates/astria-sequencer/src/app/tests_app.rs @@ -18,7 +18,10 @@ use astria_core::{ sequencerblock::v1alpha1::block::Deposit, }; use cnidarium::StateDelta; -use prost::Message as _; +use prost::{ + bytes::Bytes, + Message as _, +}; use tendermint::{ abci::{ self, @@ -310,7 +313,7 @@ async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { }; let sequence_action = SequenceAction { rollup_id, - data: b"hello world".to_vec(), + data: Bytes::from_static(b"hello world"), fee_asset: nria().into(), }; let tx = UnsignedTransaction { @@ -360,7 +363,7 @@ async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { for (_, rollup_data) in block.rollup_transactions() { for tx in rollup_data.transactions() { let rollup_data = - RollupData::try_from_raw(RawRollupData::decode(tx.as_slice()).unwrap()).unwrap(); + RollupData::try_from_raw(RawRollupData::decode(tx.as_ref()).unwrap()).unwrap(); if let RollupData::Deposit(deposit) = rollup_data { deposits.push(deposit); } @@ -400,7 +403,7 @@ async fn app_execution_results_match_proposal_vs_after_proposal() { }; let sequence_action = SequenceAction { rollup_id, - data: b"hello world".to_vec(), + data: Bytes::from_static(b"hello world"), fee_asset: nria().into(), }; let tx = UnsignedTransaction { @@ -535,7 +538,7 @@ async fn app_prepare_proposal_cometbft_max_bytes_overflow_ok() { actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), - data: vec![1u8; 100_000], + data: Bytes::copy_from_slice(&[1u8; 100_000]), fee_asset: nria().into(), } .into(), @@ -550,7 +553,7 @@ async fn app_prepare_proposal_cometbft_max_bytes_overflow_ok() { actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), - data: vec![1u8; 100_000], + data: Bytes::copy_from_slice(&[1u8; 100_000]), fee_asset: nria().into(), } .into(), @@ -608,7 +611,7 @@ async fn app_prepare_proposal_sequencer_max_bytes_overflow_ok() { actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), - data: vec![1u8; 200_000], + data: Bytes::copy_from_slice(&[1u8; 200_000]), fee_asset: nria().into(), } .into(), @@ -623,7 +626,7 @@ async fn app_prepare_proposal_sequencer_max_bytes_overflow_ok() { actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), - data: vec![1u8; 100_000], + data: Bytes::copy_from_slice(&[1u8; 100_000]), fee_asset: nria().into(), } .into(), diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index b7c11a8bf1..8c1bb2d097 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -39,7 +39,10 @@ use astria_core::{ }; use cnidarium::StateDelta; use penumbra_ibc::params::IBCParameters; -use prost::Message as _; +use prost::{ + bytes::Bytes, + Message as _, +}; use tendermint::{ abci, abci::types::CommitInfo, @@ -125,7 +128,7 @@ async fn app_finalize_block_snapshot() { }; let sequence_action = SequenceAction { rollup_id, - data: b"hello world".to_vec(), + data: Bytes::from_static(b"hello world"), fee_asset: nria().into(), }; let tx = UnsignedTransaction { @@ -226,7 +229,7 @@ async fn app_execute_transaction_with_every_action_snapshot() { .into(), SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), - data: b"hello world".to_vec(), + data: Bytes::from_static(b"hello world"), fee_asset: nria().into(), } .into(), diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index f0c83ddfa6..e2d3449c78 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -27,6 +27,7 @@ use astria_core::{ }, sequencerblock::v1alpha1::block::Deposit, }; +use bytes::Bytes; use cnidarium::StateDelta; use penumbra_ibc::params::IBCParameters; use tendermint::abci::EventAttributeIndexExt as _; @@ -249,7 +250,7 @@ async fn app_execute_transaction_sequence() { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let data = b"hello world".to_vec(); + let data = Bytes::from_static(b"hello world"); let fee = calculate_fee_from_state(&data, &app.state).await.unwrap(); let tx = UnsignedTransaction { @@ -285,7 +286,7 @@ async fn app_execute_transaction_invalid_fee_asset() { let mut app = initialize_app(None, vec![]).await; let alice = get_alice_signing_key(); - let data = b"hello world".to_vec(); + let data = Bytes::from_static(b"hello world"); let tx = UnsignedTransaction { params: TransactionParams::builder() @@ -788,7 +789,7 @@ async fn app_execute_transaction_invalid_nonce() { let alice_address = astria_address(&alice.address_bytes()); // create tx with invalid nonce 1 - let data = b"hello world".to_vec(); + let data = Bytes::from_static(b"hello world"); let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(1) @@ -835,7 +836,7 @@ async fn app_execute_transaction_invalid_chain_id() { let alice_address = astria_address(&alice.address_bytes()); // create tx with invalid nonce 1 - let data = b"hello world".to_vec(); + let data = Bytes::from_static(b"hello world"); let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(0) @@ -889,7 +890,7 @@ async fn app_stateful_check_fails_insufficient_total_balance() { let keypair_address = astria_address(&keypair.verification_key().address_bytes()); // figure out needed fee for a single transfer - let data = b"hello world".to_vec(); + let data = Bytes::from_static(b"hello world"); let fee = calculate_fee_from_state(&data, &app.state.clone()) .await .unwrap(); diff --git a/crates/astria-sequencer/src/grpc/sequencer.rs b/crates/astria-sequencer/src/grpc/sequencer.rs index c17f899587..44c941f172 100644 --- a/crates/astria-sequencer/src/grpc/sequencer.rs +++ b/crates/astria-sequencer/src/grpc/sequencer.rs @@ -12,6 +12,7 @@ use astria_core::{ }, primitive::v1::RollupId, }; +use bytes::Bytes; use cnidarium::Storage; use tonic::{ Request, @@ -148,10 +149,13 @@ impl SequencerService for SequencerServer { rollup_transactions.push(rollup_data.into_raw()); } - let all_rollup_ids = all_rollup_ids.into_iter().map(RollupId::to_vec).collect(); + let all_rollup_ids = all_rollup_ids + .into_iter() + .map(|rollup_id| Bytes::copy_from_slice(rollup_id.as_ref())) + .collect(); let block = RawFilteredSequencerBlock { - block_hash: block_hash.to_vec(), + block_hash: Bytes::copy_from_slice(&block_hash), header: Some(header.into_raw()), rollup_transactions, rollup_transactions_proof: rollup_transactions_proof.into(), diff --git a/crates/astria-sequencer/src/mempool/benchmarks.rs b/crates/astria-sequencer/src/mempool/benchmarks.rs index 7c97d278c1..dacf377f00 100644 --- a/crates/astria-sequencer/src/mempool/benchmarks.rs +++ b/crates/astria-sequencer/src/mempool/benchmarks.rs @@ -25,6 +25,7 @@ use astria_core::{ UnsignedTransaction, }, }; +use bytes::Bytes; use sha2::{ Digest as _, Sha256, @@ -75,7 +76,7 @@ fn transactions() -> &'static Vec { .build(); let sequence_action = SequenceAction { rollup_id: RollupId::new([1; 32]), - data: vec![2; 1000], + data: Bytes::from_static(&[2; 1000]), fee_asset: Denom::IbcPrefixed(IbcPrefixed::new([3; 32])), }; UnsignedTransaction { diff --git a/crates/astria-sequencer/src/proposal/commitment.rs b/crates/astria-sequencer/src/proposal/commitment.rs index ed41a2aa9c..1578b47208 100644 --- a/crates/astria-sequencer/src/proposal/commitment.rs +++ b/crates/astria-sequencer/src/proposal/commitment.rs @@ -71,6 +71,7 @@ pub(crate) fn generate_rollup_datas_commitment( RollupData::Deposit(Box::new(deposit)) .into_raw() .encode_to_vec() + .into() })); } @@ -109,7 +110,7 @@ mod test { fn generate_rollup_datas_commitment_should_ignore_transfers() { let sequence_action = SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), - data: b"helloworld".to_vec(), + data: Bytes::from_static(b"hello world"), fee_asset: crate::test_utils::nria().into(), }; let transfer_action = TransferAction { @@ -163,7 +164,7 @@ mod test { let sequence_action = SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), - data: b"helloworld".to_vec(), + data: b"helloworld".to_vec().into(), fee_asset: crate::test_utils::nria().into(), }; let transfer_action = TransferAction { diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index b9da05020e..a57706023b 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -247,7 +247,7 @@ mod test { actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), - data: b"helloworld".to_vec(), + data: Bytes::from_static(b"hello world"), fee_asset: crate::test_utils::nria().into(), } .into(), diff --git a/crates/astria-sequencer/src/transaction/checks.rs b/crates/astria-sequencer/src/transaction/checks.rs index f96f26ebd3..e73e18acf7 100644 --- a/crates/astria-sequencer/src/transaction/checks.rs +++ b/crates/astria-sequencer/src/transaction/checks.rs @@ -333,6 +333,7 @@ mod tests { TransactionParams, }, }; + use bytes::Bytes; use cnidarium::StateDelta; use super::*; @@ -368,7 +369,7 @@ mod tests { let alice = get_alice_signing_key(); let amount = 100; - let data = [0; 32].to_vec(); + let data = Bytes::from_static(&[0; 32]); let transfer_fee = state_tx.get_transfer_base_fee().await.unwrap(); state_tx .increase_balance( @@ -445,7 +446,7 @@ mod tests { let alice = get_alice_signing_key(); let amount = 100; - let data = [0; 32].to_vec(); + let data = Bytes::from_static(&[0; 32]); let transfer_fee = state_tx.get_transfer_base_fee().await.unwrap(); state_tx .increase_balance( diff --git a/tools/protobuf-compiler/src/main.rs b/tools/protobuf-compiler/src/main.rs index 1342abb6fe..20700a0d40 100644 --- a/tools/protobuf-compiler/src/main.rs +++ b/tools/protobuf-compiler/src/main.rs @@ -64,12 +64,10 @@ fn main() { .build_server(true) .emit_rerun_if_changed(false) .bytes([ - ".astria.execution.v1alpha2", - ".astria.primitive.v1", + ".astria", ".celestia", ".cosmos", ".tendermint", - ".astria.protocol.asset.v1alpha1.AllowedFeeAssetIdsResponse", ]) .client_mod_attribute(".", "#[cfg(feature=\"client\")]") .server_mod_attribute(".", "#[cfg(feature=\"server\")]") From 43098d4311d444700e18dffdc5e3376f630777c6 Mon Sep 17 00:00:00 2001 From: quasystaty Date: Mon, 12 Aug 2024 21:55:04 +0300 Subject: [PATCH 02/18] chore(charts): fix charts production templates (#1359) ## Summary Changes to production templates of the charts, namely secrets, volumes, configmap and deployment ## Background Production deployments through GCP provider has small template errors ## Changes - composer secret private key naming - evm-bridge-withdrawer condition - sequencer-faucet now uses fixed image - evm-faucet condition and secret naming - evm-rollup otel values templating ## Testing - deployed a local kubernetes cluster with all services - this changes were originally made while deploying dusk-9 production network ## Related Issues closes #1358 --- charts/composer/Chart.yaml | 2 +- charts/composer/templates/configmap.yaml | 2 +- charts/composer/templates/deployment.yaml | 8 ++++---- charts/composer/templates/secretproviderclass.yaml | 2 +- charts/evm-bridge-withdrawer/Chart.yaml | 2 +- .../templates/servicemonitor.yaml | 2 +- charts/evm-faucet/Chart.yaml | 2 +- charts/evm-faucet/templates/deployment.yaml | 4 ++-- charts/evm-rollup/Chart.yaml | 2 +- charts/evm-rollup/templates/configmap.yaml | 12 ++++++------ charts/evm-rollup/values.yaml | 12 ++++++------ charts/evm-stack/Chart.lock | 12 ++++++------ charts/evm-stack/Chart.yaml | 10 +++++----- charts/sequencer-faucet/Chart.yaml | 2 +- charts/sequencer-faucet/values.yaml | 2 +- 15 files changed, 38 insertions(+), 38 deletions(-) diff --git a/charts/composer/Chart.yaml b/charts/composer/Chart.yaml index 1f90dd11b9..14aa3c035e 100644 --- a/charts/composer/Chart.yaml +++ b/charts/composer/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.1.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/composer/templates/configmap.yaml b/charts/composer/templates/configmap.yaml index c22e97a639..3d9b13a739 100644 --- a/charts/composer/templates/configmap.yaml +++ b/charts/composer/templates/configmap.yaml @@ -38,7 +38,7 @@ apiVersion: v1 kind: ConfigMap metadata: namespace: {{ include "composer.namespace" . }} - name: sequencer-private-key + name: composer-private-key data: {{ .Values.config.privateKey.secret.filename }}: | {{ .Values.config.privateKey.devContent }} diff --git a/charts/composer/templates/deployment.yaml b/charts/composer/templates/deployment.yaml index 84dd92ec7b..b5c817b1bd 100644 --- a/charts/composer/templates/deployment.yaml +++ b/charts/composer/templates/deployment.yaml @@ -26,7 +26,7 @@ spec: name: composer-env volumeMounts: - mountPath: "/var/secrets" - name: sequencer-private-key + name: composer-private-key startupProbe: httpGet: path: /readyz @@ -47,14 +47,14 @@ spec: resources: {{- toYaml .Values.resources | trim | nindent 12 }} volumes: - - name: sequencer-private-key + - name: composer-private-key {{- if .Values.secretProvider.enabled }} csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: - secretProviderClass: sequencer-private-key + secretProviderClass: composer-private-key {{- else }} configMap: - name: sequencer-private-key + name: composer-private-key {{- end }} diff --git a/charts/composer/templates/secretproviderclass.yaml b/charts/composer/templates/secretproviderclass.yaml index a74347981b..2746c558f5 100644 --- a/charts/composer/templates/secretproviderclass.yaml +++ b/charts/composer/templates/secretproviderclass.yaml @@ -3,7 +3,7 @@ apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: - name: sequencer-private-key + name: composer-private-key spec: provider: {{ .Values.secretProvider.provider }} parameters: diff --git a/charts/evm-bridge-withdrawer/Chart.yaml b/charts/evm-bridge-withdrawer/Chart.yaml index d3038358e3..a40785041d 100644 --- a/charts/evm-bridge-withdrawer/Chart.yaml +++ b/charts/evm-bridge-withdrawer/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.1 +version: 0.1.2 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/evm-bridge-withdrawer/templates/servicemonitor.yaml b/charts/evm-bridge-withdrawer/templates/servicemonitor.yaml index 33f4a257f9..dace167a38 100644 --- a/charts/evm-bridge-withdrawer/templates/servicemonitor.yaml +++ b/charts/evm-bridge-withdrawer/templates/servicemonitor.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.serviceMonitor.enabled .Values.config.relayer.metrics.enabled }} +{{- if and .Values.serviceMonitor.enabled .Values.metrics.enabled }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: diff --git a/charts/evm-faucet/Chart.yaml b/charts/evm-faucet/Chart.yaml index efb161a537..c6a067f3e5 100644 --- a/charts/evm-faucet/Chart.yaml +++ b/charts/evm-faucet/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.1.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/evm-faucet/templates/deployment.yaml b/charts/evm-faucet/templates/deployment.yaml index f5f3790288..713dce5de7 100644 --- a/charts/evm-faucet/templates/deployment.yaml +++ b/charts/evm-faucet/templates/deployment.yaml @@ -35,7 +35,7 @@ spec: valueFrom: secretKeyRef: name: evm-faucet-private-key - key: {{ .Values.privateKey.secret.key }} + key: {{ .Values.config.privateKey.secret.key }} {{- end }} volumeMounts: - mountPath: /home/faucet @@ -43,7 +43,7 @@ spec: subPath: {{tpl .Values.config.rollupName . }}/faucet {{- if .Values.secretProvider.enabled }} - mountPath: /var/secrets - name: faucet-private-key + name: evm-faucet-private-key {{- end }} ports: - containerPort: {{ .Values.ports.faucet }} diff --git a/charts/evm-rollup/Chart.yaml b/charts/evm-rollup/Chart.yaml index 6dec52ace2..e9ed2af13b 100644 --- a/charts/evm-rollup/Chart.yaml +++ b/charts/evm-rollup/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.25.1 +version: 0.25.2 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/evm-rollup/templates/configmap.yaml b/charts/evm-rollup/templates/configmap.yaml index 6a08e4d78f..10fc39d5fa 100644 --- a/charts/evm-rollup/templates/configmap.yaml +++ b/charts/evm-rollup/templates/configmap.yaml @@ -26,12 +26,12 @@ data: NO_COLOR: "{{ .Values.global.useTTY }}" ASTRIA_CONDUCTOR_NO_OTEL: "{{ not .Values.otel.enabled }}" ASTRIA_CONDUCTOR_CELESTIA_BEARER_TOKEN: "{{ .Values.config.celestia.token }}" - OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.otel.endpoint }}" - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "{{ .Values.otel.tracesEndpoint }}" - OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: "{{ .Values.otel.tracesTimeout }}" - OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: "{{ .Values.otel.tracesCompression }}" - OTEL_EXPORTER_OTLP_HEADERS: "{{ .Values.otel.otlpHeaders }}" - OTEL_EXPORTER_OTLP_TRACE_HEADERS: "{{ .Values.otel.traceHeaders }}" + OTEL_EXPORTER_OTLP_ENDPOINT: "{{ tpl .Values.otel.endpoint . }}" + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "{{ tpl .Values.otel.tracesEndpoint . }}" + OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: "{{ tpl .Values.otel.tracesTimeout . }}" + OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: "{{ tpl .Values.otel.tracesCompression . }}" + OTEL_EXPORTER_OTLP_HEADERS: "{{ tpl .Values.otel.otlpHeaders . }}" + OTEL_EXPORTER_OTLP_TRACE_HEADERS: "{{ tpl .Values.otel.traceHeaders .}}" OTEL_SERVICE_NAME: "{{ tpl .Values.otel.serviceNamePrefix . }}-conductor" {{- if not .Values.global.dev }} {{- else }} diff --git a/charts/evm-rollup/values.yaml b/charts/evm-rollup/values.yaml index 0269bd09d0..0d329ac01a 100644 --- a/charts/evm-rollup/values.yaml +++ b/charts/evm-rollup/values.yaml @@ -211,12 +211,12 @@ metrics: otel: enabled: false serviceNamePrefix: '{{ include "rollup.name" . }}' - endpoint: - tracesEndpoint: - tracesCompression: gzip - tracesTimeout: 10 - otlpHeaders: - traceHeaders: + endpoint: "" + tracesEndpoint: "" + tracesCompression: "gzip" + tracesTimeout: "10" + otlpHeaders: "" + traceHeaders: "" serviceMonitor: # set to enable port svc and service monitor diff --git a/charts/evm-stack/Chart.lock b/charts/evm-stack/Chart.lock index c23c20611d..d7c3bb90d7 100644 --- a/charts/evm-stack/Chart.lock +++ b/charts/evm-stack/Chart.lock @@ -1,21 +1,21 @@ dependencies: - name: evm-rollup repository: file://../evm-rollup - version: 0.25.1 + version: 0.25.2 - name: composer repository: file://../composer - version: 0.1.0 + version: 0.1.1 - name: evm-faucet repository: file://../evm-faucet - version: 0.1.0 + version: 0.1.1 - name: evm-bridge-withdrawer repository: file://../evm-bridge-withdrawer - version: 0.1.1 + version: 0.1.2 - name: postgresql repository: https://charts.bitnami.com/bitnami version: 15.2.4 - name: blockscout-stack repository: https://blockscout.github.io/helm-charts version: 1.6.2 -digest: sha256:d337fa8fa4f7c8986527bf03bc7a1511bd1ac196214b35b8c2cf8d99d8e0358b -generated: "2024-07-30T09:09:11.244761-07:00" +digest: sha256:2ceebf8976422c02bc200ad1ad636e1bd83a7b94931a024b5034212ef3fde8b3 +generated: "2024-08-11T18:04:44.817194+03:00" diff --git a/charts/evm-stack/Chart.yaml b/charts/evm-stack/Chart.yaml index 219daad3d3..5232b11c51 100644 --- a/charts/evm-stack/Chart.yaml +++ b/charts/evm-stack/Chart.yaml @@ -15,22 +15,22 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.0 +version: 0.3.1 dependencies: - name: evm-rollup - version: 0.25.1 + version: 0.25.2 repository: "file://../evm-rollup" - name: composer - version: 0.1.0 + version: 0.1.1 repository: "file://../composer" condition: composer.enabled - name: evm-faucet - version: 0.1.0 + version: 0.1.1 repository: "file://../evm-faucet" condition: evm-faucet.enabled - name: evm-bridge-withdrawer - version: 0.1.1 + version: 0.1.2 repository: "file://../evm-bridge-withdrawer" condition: evm-bridge-withdrawer.enabled - name: postgresql diff --git a/charts/sequencer-faucet/Chart.yaml b/charts/sequencer-faucet/Chart.yaml index 31fab5d626..2260d480c0 100644 --- a/charts/sequencer-faucet/Chart.yaml +++ b/charts/sequencer-faucet/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.7.0 +version: 0.7.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/sequencer-faucet/values.yaml b/charts/sequencer-faucet/values.yaml index 8c32c4be66..f0be600ef9 100644 --- a/charts/sequencer-faucet/values.yaml +++ b/charts/sequencer-faucet/values.yaml @@ -21,7 +21,7 @@ config: amount: 1800 images: - sequencerFaucet: "ghcr.io/astriaorg/seq-faucet:0.6.0" + sequencerFaucet: "ghcr.io/astriaorg/seq-faucet:sha-5c9d64c" # When deploying in a production environment should use a secret provider # This is configured for use with GCP, need to set own resource names From c5364e801a193b799e71390f51c54b76a934c999 Mon Sep 17 00:00:00 2001 From: quasystaty Date: Wed, 14 Aug 2024 19:18:28 +0300 Subject: [PATCH 03/18] Chore: Upgrade celestia-node to v0.14.1 (#1360) ## Summary celestia-node image update to v0.14.1 which fixes memory leaks ## Background Image used per celestia-node is outdated ## Changes - celestia-node image `v0.13.4 -> v0.14.1` ## Testing locall kubernetes cluster with all services deployed and firm blocks confirmed --- charts/celestia-local/Chart.yaml | 4 ++-- charts/celestia-local/values.yaml | 2 +- charts/celestia-node/Chart.yaml | 4 ++-- charts/celestia-node/values.yaml | 2 +- charts/evm-rollup/Chart.lock | 6 +++--- charts/evm-rollup/Chart.yaml | 4 ++-- charts/evm-stack/Chart.lock | 6 +++--- charts/evm-stack/Chart.yaml | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/charts/celestia-local/Chart.yaml b/charts/celestia-local/Chart.yaml index 7e955e9424..8303f7153e 100644 --- a/charts/celestia-local/Chart.yaml +++ b/charts/celestia-local/Chart.yaml @@ -15,13 +15,13 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.6.0 +version: 0.6.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.8.0" +appVersion: "1.9.0" maintainers: - name: wafflesvonmaple diff --git a/charts/celestia-local/values.yaml b/charts/celestia-local/values.yaml index c98d2840e8..7f8f429bcb 100644 --- a/charts/celestia-local/values.yaml +++ b/charts/celestia-local/values.yaml @@ -16,7 +16,7 @@ storage: path: "/data/celestia-data" celestiaAppImage: "ghcr.io/celestiaorg/celestia-app:v1.9.0" -celestiaNodeImage: "ghcr.io/celestiaorg/celestia-node:v0.13.4" +celestiaNodeImage: "ghcr.io/celestiaorg/celestia-node:v0.14.1" podSecurityContext: runAsUser: 10001 diff --git a/charts/celestia-node/Chart.yaml b/charts/celestia-node/Chart.yaml index ab3a653a9a..6b7946aa1e 100644 --- a/charts/celestia-node/Chart.yaml +++ b/charts/celestia-node/Chart.yaml @@ -15,13 +15,13 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.2 +version: 0.3.4 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.13.6" +appVersion: "0.14.1" maintainers: - name: wafflesvonmaple diff --git a/charts/celestia-node/values.yaml b/charts/celestia-node/values.yaml index f115e7bae8..6d8093374e 100644 --- a/charts/celestia-node/values.yaml +++ b/charts/celestia-node/values.yaml @@ -19,7 +19,7 @@ config: images: pullPolicy: IfNotPresent - node: ghcr.io/celestiaorg/celestia-node:v0.13.6 + node: ghcr.io/celestiaorg/celestia-node:v0.14.1 ports: celestia: diff --git a/charts/evm-rollup/Chart.lock b/charts/evm-rollup/Chart.lock index 37e9f194df..1b10a291a2 100644 --- a/charts/evm-rollup/Chart.lock +++ b/charts/evm-rollup/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: celestia-node repository: file://../celestia-node - version: 0.3.2 -digest: sha256:dbd9dbd2cc6b97947ab7612bcb69a092d324d53a637c04cdf7b727db5a1a04fb -generated: "2024-05-20T17:12:32.402994-07:00" + version: 0.3.4 +digest: sha256:8389e87d7ce8a92d2af7a5c8177dbbf34d6471933b5c857bae88a44b860382b3 +generated: "2024-08-12T22:11:51.89263+03:00" diff --git a/charts/evm-rollup/Chart.yaml b/charts/evm-rollup/Chart.yaml index e9ed2af13b..096c152ff7 100644 --- a/charts/evm-rollup/Chart.yaml +++ b/charts/evm-rollup/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.25.2 +version: 0.25.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -25,7 +25,7 @@ appVersion: "0.14.0" dependencies: - name: celestia-node - version: "0.3.2" + version: "0.3.4" repository: "file://../celestia-node" condition: celestia-node.enabled diff --git a/charts/evm-stack/Chart.lock b/charts/evm-stack/Chart.lock index d7c3bb90d7..0721955d81 100644 --- a/charts/evm-stack/Chart.lock +++ b/charts/evm-stack/Chart.lock @@ -1,7 +1,7 @@ dependencies: - name: evm-rollup repository: file://../evm-rollup - version: 0.25.2 + version: 0.25.3 - name: composer repository: file://../composer version: 0.1.1 @@ -17,5 +17,5 @@ dependencies: - name: blockscout-stack repository: https://blockscout.github.io/helm-charts version: 1.6.2 -digest: sha256:2ceebf8976422c02bc200ad1ad636e1bd83a7b94931a024b5034212ef3fde8b3 -generated: "2024-08-11T18:04:44.817194+03:00" +digest: sha256:75189d68ee2ddbb135ec487b4aee663fd2d096ae19608efc2d6ebfdec9d8c4a0 +generated: "2024-08-12T22:12:07.880246+03:00" diff --git a/charts/evm-stack/Chart.yaml b/charts/evm-stack/Chart.yaml index 5232b11c51..e1cc2a2465 100644 --- a/charts/evm-stack/Chart.yaml +++ b/charts/evm-stack/Chart.yaml @@ -15,11 +15,11 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.1 +version: 0.3.2 dependencies: - name: evm-rollup - version: 0.25.2 + version: 0.25.3 repository: "file://../evm-rollup" - name: composer version: 0.1.1 From 90d1cb3aa79ac95430312a0a6e9d5a2084a93e13 Mon Sep 17 00:00:00 2001 From: Ethan Oroshiba Date: Thu, 15 Aug 2024 14:12:52 -0500 Subject: [PATCH 04/18] fix(conductor): update for celestia-node v0.15.0 (#1367) ## Summary Updated `fetch_blobs_with_retry()` to accept [Celestia Node v0.15.0](https://github.com/celestiaorg/celestia-node/releases/tag/v0.15.0)'s new `GetAll()` return format. This will still work with Node v0.14.1 as well. ## Background celestia-node v0.15.0 now returns an empty list of blobs for `GetAll` if blobs are not found, which broke our current `jsonrpsee` error handling in conductor. ## Changes - Updated `fetch_blobs_with_retry()` to accept the `ParseError` that Json RPC now throws when there are no blobs fetched. ## Testing E2E tests working --- crates/astria-conductor/src/celestia/fetch.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/astria-conductor/src/celestia/fetch.rs b/crates/astria-conductor/src/celestia/fetch.rs index 4787cf1e43..cf45cf2024 100644 --- a/crates/astria-conductor/src/celestia/fetch.rs +++ b/crates/astria-conductor/src/celestia/fetch.rs @@ -120,6 +120,7 @@ async fn fetch_blobs_with_retry( match client.blob_get_all(height, &[namespace]).await { Ok(blobs) => Ok(blobs), Err(err) if is_blob_not_found(&err) => Ok(vec![]), + Err(err) if is_null_blobs(&err) => Ok(vec![]), Err(err) => Err(err), } } @@ -162,9 +163,18 @@ fn should_retry(error: &jsonrpsee::core::Error) -> bool { ) } +// For celestia-node v0.14.1 and below fn is_blob_not_found(error: &jsonrpsee::core::Error) -> bool { let jsonrpsee::core::Error::Call(error) = error else { return false; }; error.code() == 1 && error.message().contains("blob: not found") } + +// For celestia-node v0.15.0 and newer +fn is_null_blobs(error: &jsonrpsee::core::Error) -> bool { + let jsonrpsee::core::Error::ParseError(error) = error else { + return false; + }; + error.to_string().contains("invalid type: null") +} From 49452f41dd81ff10b97aeb3149e943d07e355d6b Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Mon, 19 Aug 2024 12:43:32 +0100 Subject: [PATCH 05/18] fix(relayer): change `reqwest` for `isahc` in relayer blackbox tests (ENG-699) (#1366) ## Summary This is a straight swap of [`isahc`](https://github.com/sagebind/isahc) in place of `reqwest` in the relayer's blackbox tests. Other than a single timeout being increased, no logic was changed. ## Background Blackbox tests were failing on macOS due to the poor performance of the `reqwest` messages. The timeout for a `GET` was set to 100ms, and using `reqwest` I was seeing times around 120ms. By swapping to `isahc`, times dropped to 2ms. ## Changes - Swapped `isahc` in place of `reqwest` in the realyer's blackbox tests. - Increased the timeout on a flaky test from 2s to 10s (the test occasionally takes just over 2s, less than 3s from several attempts locally). - Added `[lints.rust]` to the relayer's manifest to squash some warnings. ## Testing No new tests required, existing ones now pass on macOS. ## Related Issues Closes #1354. --- Cargo.lock | 109 +++++++++++++++++- crates/astria-sequencer-relayer/Cargo.toml | 8 +- .../helpers/test_sequencer_relayer.rs | 23 ++-- .../tests/blackbox/main.rs | 4 +- 4 files changed, 127 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc7062b82b..74f5d556b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,6 +874,7 @@ dependencies = [ "humantime", "humantime-serde", "hyper", + "isahc", "itertools 0.12.1", "itoa", "k256", @@ -884,7 +885,6 @@ dependencies = [ "prost", "rand_chacha 0.3.1", "rand_core 0.6.4", - "reqwest", "serde", "serde_json", "serde_path_to_error", @@ -1516,6 +1516,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + [[package]] name = "cc" version = "1.0.92" @@ -2144,6 +2150,37 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "curl" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "curl-sys" +version = "0.4.74+curl-8.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys 0.52.0", +] + [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -4192,6 +4229,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "log", + "mime", + "once_cell", + "polling", + "serde", + "serde_json", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + [[package]] name = "itertools" version = "0.10.5" @@ -4494,6 +4560,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libnghttp2-sys" +version = "0.1.10+1.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959c25552127d2e1fa72f0e52548ec04fc386e827ba71a7bd01db46a447dc135" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libp2p-identity" version = "0.2.8" @@ -5708,6 +5784,22 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "poly1305" version = "0.7.2" @@ -5983,7 +6075,7 @@ checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.10.5", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -6003,7 +6095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2 1.0.79", "quote", "syn 2.0.58", @@ -7055,6 +7147,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + [[package]] name = "smallvec" version = "1.13.2" diff --git a/crates/astria-sequencer-relayer/Cargo.toml b/crates/astria-sequencer-relayer/Cargo.toml index 0ec28c9c63..ec5c8d3953 100644 --- a/crates/astria-sequencer-relayer/Cargo.toml +++ b/crates/astria-sequencer-relayer/Cargo.toml @@ -80,7 +80,6 @@ hyper = { workspace = true } itertools = { workspace = true } once_cell = { workspace = true } rand_core = { version = "0.6", features = ["getrandom"] } -reqwest = { workspace = true, features = ["json"] } tempfile = { workspace = true } tendermint-rpc = { workspace = true, features = ["http-client"] } tokio = { workspace = true, features = ["test-util"] } @@ -88,9 +87,16 @@ tokio-stream = { workspace = true, features = ["net"] } wiremock = { workspace = true } assert-json-diff = "2.0.2" +http = "0.2.7" +isahc = { version = "1.7.2", features = ["json"] } tower = { version = "0.4.13" } tokio-test.workspace = true rand_chacha = "0.3.1" [build-dependencies] astria-build-info = { path = "../astria-build-info", features = ["build"] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(dylint_lib, values("tracing_debug_field"))', +] } 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 index 75308751f2..15aa52fa90 100644 --- a/crates/astria-sequencer-relayer/tests/blackbox/helpers/test_sequencer_relayer.rs +++ b/crates/astria-sequencer-relayer/tests/blackbox/helpers/test_sequencer_relayer.rs @@ -24,13 +24,10 @@ use astria_sequencer_relayer::{ SequencerRelayer, ShutdownHandle, }; -use futures::TryFutureExt; +use http::StatusCode; +use isahc::AsyncReadResponseExt; use itertools::Itertools; use once_cell::sync::Lazy; -use reqwest::{ - Response, - StatusCode, -}; use serde::Deserialize; use serde_json::json; use tempfile::NamedTempFile; @@ -539,13 +536,13 @@ impl TestSequencerRelayer { ) -> (serde_json::Value, StatusCode) { let url = format!("http://{}/{api_endpoint}", self.api_address); let getter = async { - reqwest::get(&url) + isahc::get_async(&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 mut response = self.timeout_ms(100, &new_context, getter).await; let status_code = response.status(); let value = response @@ -585,8 +582,10 @@ impl TestSequencerRelayer { value } else { let state = tokio::time::timeout(Duration::from_millis(100), async { - reqwest::get(format!("http://{}/status", self.api_address)) - .and_then(Response::json) + isahc::get_async(format!("http://{}/status", self.api_address)) + .await + .ok()? + .json() .await .ok() }) @@ -598,8 +597,10 @@ impl TestSequencerRelayer { .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) + isahc::get_async(format!("http://{}/healthz", self.api_address)) + .await + .ok()? + .json() .await .ok() }) diff --git a/crates/astria-sequencer-relayer/tests/blackbox/main.rs b/crates/astria-sequencer-relayer/tests/blackbox/main.rs index e97888d546..1cb3716b70 100644 --- a/crates/astria-sequencer-relayer/tests/blackbox/main.rs +++ b/crates/astria-sequencer-relayer/tests/blackbox/main.rs @@ -12,7 +12,7 @@ use helpers::{ SequencerBlockToMount, TestSequencerRelayerConfig, }; -use reqwest::StatusCode; +use http::StatusCode; use tendermint::account::Id as AccountId; #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -273,7 +273,7 @@ async fn should_filter_rollup() { .await; sequencer_relayer .timeout_ms( - 2_000, + 10_000, "waiting for get tx guard", get_tx_guard.wait_until_satisfied(), ) From a1432ba5462b12d189cab0a8eda869e30683a259 Mon Sep 17 00:00:00 2001 From: Ethan Oroshiba Date: Mon, 19 Aug 2024 08:41:00 -0500 Subject: [PATCH 06/18] refactor(core): shorten `try_from_block_info_and_data()` (#1371) ## Summary Shortened `try_from_block_info_and_data()` to remove clippy exception. ## Background Migration from `Vec` to `Bytes` in #1319 introduced some new code which put `SequencerBlock::try_from_block_info_and_data` over the line limit. ## Changes - Created helper function to return `rollup_transactions_root` and `rollup_ids_root` from a iterator on `data`. ## Testing Passing all tests ## Related Issues closes #1357 --- .../src/sequencerblock/v1alpha1/block.rs | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs index 5777814336..140f8c3706 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs @@ -1,4 +1,7 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + vec::IntoIter, +}; use bytes::Bytes; use indexmap::IndexMap; @@ -703,7 +706,6 @@ impl SequencerBlock { /// # Panics /// /// - if a rollup data merkle proof cannot be constructed. - #[allow(clippy::too_many_lines)] // Temporary fix, should refactor: TODO(https://github.com/astriaorg/astria/issues/1357) pub fn try_from_block_info_and_data( block_hash: [u8; 32], chain_id: tendermint::chain::Id, @@ -719,21 +721,8 @@ impl SequencerBlock { let data_hash = tree.root(); let mut data_list = data.into_iter(); - let rollup_transactions_root: [u8; 32] = data_list - .next() - .ok_or(SequencerBlockError::no_rollup_transactions_root())? - .as_ref() - .try_into() - .map_err(|_| { - SequencerBlockError::incorrect_rollup_transactions_root_length(data_list.len()) - })?; - - let rollup_ids_root: [u8; 32] = data_list - .next() - .ok_or(SequencerBlockError::no_rollup_ids_root())? - .as_ref() - .try_into() - .map_err(|_| SequencerBlockError::incorrect_rollup_ids_root_length(data_list.len()))?; + let (rollup_transactions_root, rollup_ids_root) = + rollup_transactions_and_ids_root_from_data(&mut data_list)?; let mut rollup_datas = IndexMap::new(); for elem in data_list { @@ -923,6 +912,26 @@ impl SequencerBlock { } } +fn rollup_transactions_and_ids_root_from_data( + data_list: &mut IntoIter, +) -> Result<([u8; 32], [u8; 32]), SequencerBlockError> { + let rollup_transactions_root: [u8; 32] = data_list + .next() + .ok_or(SequencerBlockError::no_rollup_transactions_root())? + .as_ref() + .try_into() + .map_err(|_| { + SequencerBlockError::incorrect_rollup_transactions_root_length(data_list.len()) + })?; + let rollup_ids_root: [u8; 32] = data_list + .next() + .ok_or(SequencerBlockError::no_rollup_ids_root())? + .as_ref() + .try_into() + .map_err(|_| SequencerBlockError::incorrect_rollup_ids_root_length(data_list.len()))?; + Ok((rollup_transactions_root, rollup_ids_root)) +} + /// Constructs a `[merkle::Tree]` from an iterator yielding byte slices. /// /// This hashes each item before pushing it into the Merkle Tree, which From 7b36af7fc3b0920a13a1210c7806a9407f91850c Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Tue, 20 Aug 2024 10:55:46 +0100 Subject: [PATCH 07/18] fix: abci error code (#1280) ## Summary This changes `AbciErrorCode` to no longer be able to represent tendermint Code 0. ## Background Previously `0` was defined as `UNSPECIFIED` (in line with common practice for protobuf enum variant assignations). However, in terms of ABCI codes, `0` should always represent success rather than an error case. We also had an `impl From for AbciErrorCode` which allowed for constructing arbitrary `AbciErrorCode`s with meaningless values in the context of the Astria codebase. This impl was unused however. ## Changes * Changed `AbciErrorCode` to wrap a `NonZeroU32` rather than a `u32` so that const tendermint `Code`s matching the `AbciErrorCode` variants can be created without using `unsafe` blocks. All such unsafe code is restricted to the `AbciErrorCode` impl and is trivially checkable to be safe. * Removed the `UNSPECIFIED` variant, making the `AbciErrorCode` name more meaningful, as it now only represents error cases. * Removed the `From` impl, meaning only the set of defined const values can be used. * Replaced `impl From for Code` with `pub const fn value(self) -> NonZeroU32` so that const `Code`s can be used (useful when matching on `Code` values). ## Testing N/A (all new code is trivial IMO). ## Breaking Changelist * Technically removing the pub const `AbciErrorCode::UNSPECIFIED` breaks the astria-core crate's API, but as it's unpublished yet, and since that const was never actually used afaik, this should not be an issue. ## Related Issues Closes #1259. --- crates/astria-composer/src/executor/mod.rs | 58 +++++------ .../tests/blackbox/helper/mod.rs | 2 +- crates/astria-core/src/protocol/abci.rs | 97 ++++++++----------- crates/astria-sequencer/src/accounts/query.rs | 23 ++--- crates/astria-sequencer/src/app/mod.rs | 7 +- crates/astria-sequencer/src/assets/query.rs | 29 +++--- crates/astria-sequencer/src/bridge/query.rs | 11 ++- .../astria-sequencer/src/service/info/mod.rs | 6 +- .../astria-sequencer/src/service/mempool.rs | 37 +++---- .../astria-sequencer/src/transaction/query.rs | 25 ++--- 10 files changed, 143 insertions(+), 152 deletions(-) diff --git a/crates/astria-composer/src/executor/mod.rs b/crates/astria-composer/src/executor/mod.rs index c0e60240fe..4fada3b1af 100644 --- a/crates/astria-composer/src/executor/mod.rs +++ b/crates/astria-composer/src/executor/mod.rs @@ -47,7 +47,10 @@ use sequencer_client::{ Address, SequencerClientExt as _, }; -use tendermint::crypto::Sha256; +use tendermint::{ + abci::Code, + crypto::Sha256, +}; use tokio::{ select, sync::{ @@ -643,6 +646,7 @@ impl Future for SubmitFut { #[allow(clippy::too_many_lines)] fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + const INVALID_NONCE: Code = Code::Err(AbciErrorCode::INVALID_NONCE.value()); loop { let this = self.as_mut().project(); @@ -671,8 +675,8 @@ impl Future for SubmitFut { SubmitStateProj::WaitingForSend { fut, } => match ready!(fut.poll(cx)) { - Ok(rsp) => { - let tendermint::abci::Code::Err(code) = rsp.code else { + Ok(rsp) => match rsp.code { + tendermint::abci::Code::Ok => { info!("sequencer responded with ok; submission successful"); this.metrics @@ -685,35 +689,33 @@ impl Future for SubmitFut { .nonce .checked_add(1) .expect("nonce should not overflow"))); - }; - match AbciErrorCode::from(code) { - AbciErrorCode::INVALID_NONCE => { - info!( - "sequencer rejected transaction due to invalid nonce; \ - fetching new nonce" - ); - SubmitState::WaitingForNonce { - fut: get_latest_nonce( - this.client.clone(), - *this.address, - self.metrics, - ) - .boxed(), - } + } + INVALID_NONCE => { + info!( + "sequencer rejected transaction due to invalid nonce; fetching \ + new nonce" + ); + SubmitState::WaitingForNonce { + fut: get_latest_nonce( + this.client.clone(), + *this.address, + self.metrics, + ) + .boxed(), } - _other => { - warn!( - abci.code = rsp.code.value(), - abci.log = rsp.log, - "sequencer rejected the transaction; the bundle is likely lost", - ); + } + tendermint::abci::Code::Err(_) => { + warn!( + abci.code = rsp.code.value(), + abci.log = rsp.log, + "sequencer rejected the transaction; the bundle is likely lost", + ); - this.metrics.increment_sequencer_submission_failure_count(); + this.metrics.increment_sequencer_submission_failure_count(); - return Poll::Ready(Ok(*this.nonce)); - } + return Poll::Ready(Ok(*this.nonce)); } - } + }, Err(error) => { error!(%error, "failed sending transaction to sequencer"); diff --git a/crates/astria-composer/tests/blackbox/helper/mod.rs b/crates/astria-composer/tests/blackbox/helper/mod.rs index 6b287a29b0..02bcd46bc9 100644 --- a/crates/astria-composer/tests/blackbox/helper/mod.rs +++ b/crates/astria-composer/tests/blackbox/helper/mod.rs @@ -274,7 +274,7 @@ pub async fn mount_broadcast_tx_sync_invalid_nonce_mock( let jsonrpc_rsp = response::Wrapper::new_with_id( Id::Num(1), Some(tx_sync::Response { - code: AbciErrorCode::INVALID_NONCE.into(), + code: tendermint::abci::Code::Err(AbciErrorCode::INVALID_NONCE.value()), data: vec![].into(), log: String::new(), hash: tendermint::Hash::Sha256([0; 32]), diff --git a/crates/astria-core/src/protocol/abci.rs b/crates/astria-core/src/protocol/abci.rs index 05d724539c..6f0d711e63 100644 --- a/crates/astria-core/src/protocol/abci.rs +++ b/crates/astria-core/src/protocol/abci.rs @@ -1,76 +1,57 @@ -use std::{ - borrow::Cow, - num::NonZeroU32, -}; +use std::num::NonZeroU32; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[allow(clippy::module_name_repetitions)] -pub struct AbciErrorCode(u32); +pub struct AbciErrorCode(NonZeroU32); #[rustfmt::skip] impl AbciErrorCode { - pub const UNSPECIFIED: Self = Self(0); - pub const UNKNOWN_PATH: Self = Self(1); - pub const INVALID_PARAMETER: Self = Self(2); - pub const INTERNAL_ERROR: Self = Self(3); - pub const INVALID_NONCE: Self = Self(4); - pub const TRANSACTION_TOO_LARGE: Self = Self(5); - pub const INSUFFICIENT_FUNDS: Self = Self(6); - pub const INVALID_CHAIN_ID: Self = Self(7); - pub const VALUE_NOT_FOUND: Self = Self(8); - pub const TRANSACTION_EXPIRED: Self = Self(9); - pub const TRANSACTION_FAILED: Self = Self(10); - pub const BAD_REQUEST: Self = Self(11); + pub const UNKNOWN_PATH: Self = Self(unsafe { NonZeroU32::new_unchecked(1) }); + pub const INVALID_PARAMETER: Self = Self(unsafe { NonZeroU32::new_unchecked(2) }); + pub const INTERNAL_ERROR: Self = Self(unsafe { NonZeroU32::new_unchecked(3) }); + pub const INVALID_NONCE: Self = Self(unsafe { NonZeroU32::new_unchecked(4) }); + pub const TRANSACTION_TOO_LARGE: Self = Self(unsafe { NonZeroU32::new_unchecked(5) }); + pub const INSUFFICIENT_FUNDS: Self = Self(unsafe { NonZeroU32::new_unchecked(6) }); + pub const INVALID_CHAIN_ID: Self = Self(unsafe { NonZeroU32::new_unchecked(7) }); + pub const VALUE_NOT_FOUND: Self = Self(unsafe { NonZeroU32::new_unchecked(8) }); + pub const TRANSACTION_EXPIRED: Self = Self(unsafe { NonZeroU32::new_unchecked(9) }); + pub const TRANSACTION_FAILED: Self = Self(unsafe { NonZeroU32::new_unchecked(10) }); + pub const BAD_REQUEST: Self = Self(unsafe { NonZeroU32::new_unchecked(11) }); } impl AbciErrorCode { + /// Returns the wrapped `NonZeroU32`. #[must_use] - pub fn info(self) -> Cow<'static, str> { - match self.0 { - 0 => "unspecified".into(), - 1 => "provided path is unknown".into(), - 2 => "one or more path parameters were invalid".into(), - 3 => "an internal server error occurred".into(), - 4 => "the provided nonce was invalid".into(), - 5 => "the provided transaction was too large".into(), - 6 => "insufficient funds".into(), - 7 => "the provided chain id was invalid".into(), - 8 => "the requested value was not found".into(), - 9 => "the transaction expired in the app's mempool".into(), - 10 => "the transaction failed to execute in prepare_proposal()".into(), - 11 => "the request payload was malformed".into(), - other => format!("unknown non-zero abci error code: {other}").into(), + pub const fn value(self) -> NonZeroU32 { + self.0 + } + + /// Returns brief information on the meaning of the error. + #[must_use] + pub fn info(self) -> String { + match self { + Self::UNKNOWN_PATH => "provided path is unknown".into(), + Self::INVALID_PARAMETER => "one or more path parameters were invalid".into(), + Self::INTERNAL_ERROR => "an internal server error occurred".into(), + Self::INVALID_NONCE => "the provided nonce was invalid".into(), + Self::TRANSACTION_TOO_LARGE => "the provided transaction was too large".into(), + Self::INSUFFICIENT_FUNDS => "insufficient funds".into(), + Self::INVALID_CHAIN_ID => "the provided chain id was invalid".into(), + Self::VALUE_NOT_FOUND => "the requested value was not found".into(), + Self::TRANSACTION_EXPIRED => "the transaction expired in the app's mempool".into(), + Self::TRANSACTION_FAILED => { + "the transaction failed to execute in prepare_proposal()".into() + } + Self::BAD_REQUEST => "the request payload was malformed".into(), + Self(other) => { + format!("invalid error code {other}: should be unreachable (this is a bug)") + } } } } impl std::fmt::Display for AbciErrorCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.info()) - } -} - -impl From for tendermint::abci::Code { - fn from(value: AbciErrorCode) -> Self { - value.0.into() - } -} - -impl From for AbciErrorCode { - fn from(value: NonZeroU32) -> Self { - match value.get() { - 1 => Self::UNKNOWN_PATH, - 2 => Self::INVALID_PARAMETER, - 3 => Self::INTERNAL_ERROR, - 4 => Self::INVALID_NONCE, - 5 => Self::TRANSACTION_TOO_LARGE, - 6 => Self::INSUFFICIENT_FUNDS, - 7 => Self::INVALID_CHAIN_ID, - 8 => Self::VALUE_NOT_FOUND, - 9 => Self::TRANSACTION_EXPIRED, - 10 => Self::TRANSACTION_FAILED, - 11 => Self::BAD_REQUEST, - other => Self(other), - } + write!(f, "{}: {}", self.0, self.info()) } } diff --git a/crates/astria-sequencer/src/accounts/query.rs b/crates/astria-sequencer/src/accounts/query.rs index 4dfe99bb1f..0d48f95b71 100644 --- a/crates/astria-sequencer/src/accounts/query.rs +++ b/crates/astria-sequencer/src/accounts/query.rs @@ -12,6 +12,7 @@ use tendermint::{ abci::{ request, response, + Code, }, block::Height, }; @@ -36,9 +37,9 @@ pub(crate) async fn balance_request( Ok(balance) => balance, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), - log: format!("failed getting balance for provided address: {err:?}"), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), + log: format!("failed getting balance for provided address: {err:#}"), height, ..response::Query::default() }; @@ -134,8 +135,8 @@ async fn preprocess_request( .find_map(|(k, v)| (k == "account").then_some(v)) else { return Err(response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), log: "path did not contain path parameter".into(), ..response::Query::default() }); @@ -144,18 +145,18 @@ async fn preprocess_request( .parse() .context("failed to parse argument as address") .map_err(|err| response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), - log: format!("address could not be constructed from provided parameter: {err:?}"), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), + log: format!("address could not be constructed from provided parameter: {err:#}"), ..response::Query::default() })?; let (snapshot, height) = match get_snapshot_and_height(storage, request.height).await { Ok(tup) => tup, Err(err) => { return Err(response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), - log: format!("failed to query internal storage for snapshot and height: {err:?}"), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), + log: format!("failed to query internal storage for snapshot and height: {err:#}"), ..response::Query::default() }); } diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index cc59071f13..fc605cd717 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -47,6 +47,7 @@ use tendermint::{ abci::{ self, types::ExecTxResult, + Code, Event, }, account, @@ -831,9 +832,9 @@ impl App { AbciErrorCode::INTERNAL_ERROR }; tx_results.push(ExecTxResult { - code: code.into(), - info: code.to_string(), - log: format!("{e:?}"), + code: Code::Err(code.value()), + info: code.info(), + log: format!("{e:#}"), ..Default::default() }); } diff --git a/crates/astria-sequencer/src/assets/query.rs b/crates/astria-sequencer/src/assets/query.rs index 7628bab106..f4f88afa1d 100644 --- a/crates/astria-sequencer/src/assets/query.rs +++ b/crates/astria-sequencer/src/assets/query.rs @@ -12,6 +12,7 @@ use prost::Message as _; use tendermint::abci::{ request, response, + Code, }; use crate::{ @@ -41,8 +42,8 @@ pub(crate) async fn denom_request( Ok(height) => height, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed getting block height: {err:#}"), ..response::Query::default() }; @@ -53,8 +54,8 @@ pub(crate) async fn denom_request( Ok(maybe_denom) => maybe_denom, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to retrieve denomination `{asset}`: {err:#}"), ..response::Query::default() }; @@ -63,8 +64,8 @@ pub(crate) async fn denom_request( let Some(denom) = maybe_denom else { return response::Query { - code: AbciErrorCode::VALUE_NOT_FOUND.into(), - info: AbciErrorCode::VALUE_NOT_FOUND.to_string(), + code: Code::Err(AbciErrorCode::VALUE_NOT_FOUND.value()), + info: AbciErrorCode::VALUE_NOT_FOUND.info(), log: format!("failed to retrieve value for denomination ID`{asset}`"), ..response::Query::default() }; @@ -93,8 +94,8 @@ fn preprocess_request( ) -> anyhow::Result { let Some(asset_id) = params.iter().find_map(|(k, v)| (k == "id").then_some(v)) else { return Err(response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), log: "path did not contain asset ID parameter".into(), ..response::Query::default() }); @@ -103,8 +104,8 @@ fn preprocess_request( .context("failed decoding hex encoded bytes") .map(asset::IbcPrefixed::new) .map_err(|err| response::Query { - code: AbciErrorCode::INVALID_PARAMETER.into(), - info: AbciErrorCode::INVALID_PARAMETER.to_string(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), + info: AbciErrorCode::INVALID_PARAMETER.info(), log: format!("asset ID could not be constructed from provided parameter: {err:#}"), ..response::Query::default() })?; @@ -124,8 +125,8 @@ pub(crate) async fn allowed_fee_assets_request( Ok(height) => height, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed getting block height: {err:#}"), ..response::Query::default() }; @@ -137,8 +138,8 @@ pub(crate) async fn allowed_fee_assets_request( Ok(fee_assets) => fee_assets, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to retrieve allowed fee assets: {err:#}"), ..response::Query::default() }; diff --git a/crates/astria-sequencer/src/bridge/query.rs b/crates/astria-sequencer/src/bridge/query.rs index 3ea29d867d..60fa07c2a2 100644 --- a/crates/astria-sequencer/src/bridge/query.rs +++ b/crates/astria-sequencer/src/bridge/query.rs @@ -11,6 +11,7 @@ use prost::Message as _; use tendermint::abci::{ request, response, + Code, }; use crate::{ @@ -25,12 +26,12 @@ fn error_query_response( msg: &str, ) -> response::Query { let log = match err { - Some(err) => format!("{msg}: {err:?}"), + Some(err) => format!("{msg}: {err:#}"), None => msg.into(), }; response::Query { - code: code.into(), - info: code.to_string(), + code: Code::Err(code.value()), + info: code.info(), log, ..response::Query::default() } @@ -250,8 +251,8 @@ fn preprocess_request(params: &[(String, String)]) -> anyhow::Result { return response::Query { - code: AbciErrorCode::UNKNOWN_PATH.into(), - info: AbciErrorCode::UNKNOWN_PATH.to_string(), - log: format!("provided path `{}` is unknown: {err:?}", request.path), + code: tendermint::abci::Code::Err(AbciErrorCode::UNKNOWN_PATH.value()), + info: AbciErrorCode::UNKNOWN_PATH.info(), + log: format!("provided path `{}` is unknown: {err:#}", request.path), ..response::Query::default() }; } diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool.rs index 8ce3d114d3..6efae965e8 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool.rs @@ -22,11 +22,14 @@ use futures::{ TryFutureExt as _, }; use prost::Message as _; -use tendermint::v0_38::abci::{ - request, - response, - MempoolRequest, - MempoolResponse, +use tendermint::{ + abci::Code, + v0_38::abci::{ + request, + response, + MempoolRequest, + MempoolResponse, + }, }; use tower::Service; use tower_abci::BoxError; @@ -126,12 +129,12 @@ async fn handle_check_tx { mempool.remove(tx_hash).await; return response::CheckTx { - code: AbciErrorCode::INVALID_PARAMETER.into(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), log: e.to_string(), info: "failed decoding bytes as a protobuf SignedTransaction".into(), ..response::CheckTx::default() @@ -153,7 +156,7 @@ async fn handle_check_tx { mempool.remove(tx_hash).await; return response::CheckTx { - code: AbciErrorCode::INVALID_PARAMETER.into(), + code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), info: "the provided bytes was not a valid protobuf-encoded SignedTransaction, or \ the signature was invalid" .into(), @@ -172,7 +175,7 @@ async fn handle_check_tx { metrics.increment_check_tx_removed_expired(); return response::CheckTx { - code: AbciErrorCode::TRANSACTION_EXPIRED.into(), + code: Code::Err(AbciErrorCode::TRANSACTION_EXPIRED.value()), info: "transaction expired in app's mempool".into(), log: "Transaction expired in the app's mempool".into(), ..response::CheckTx::default() @@ -247,7 +250,7 @@ async fn handle_check_tx { metrics.increment_check_tx_removed_failed_execution(); return response::CheckTx { - code: AbciErrorCode::TRANSACTION_FAILED.into(), + code: Code::Err(AbciErrorCode::TRANSACTION_FAILED.value()), info: "transaction failed execution in prepare_proposal()".into(), log: format!("transaction failed execution because: {err}"), ..response::CheckTx::default() @@ -270,8 +273,8 @@ async fn handle_check_tx { return response::CheckTx { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("transaction failed execution because: {err:#?}"), ..response::CheckTx::default() }; diff --git a/crates/astria-sequencer/src/transaction/query.rs b/crates/astria-sequencer/src/transaction/query.rs index 1bda4fe45d..e6a36c1e02 100644 --- a/crates/astria-sequencer/src/transaction/query.rs +++ b/crates/astria-sequencer/src/transaction/query.rs @@ -10,6 +10,7 @@ use prost::Message as _; use tendermint::abci::{ request, response, + Code, }; use crate::{ @@ -36,8 +37,8 @@ pub(crate) async fn transaction_fee_request( Ok(height) => height, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed getting block height: {err:#}"), ..response::Query::default() }; @@ -48,8 +49,8 @@ pub(crate) async fn transaction_fee_request( Ok(fees) => fees, Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed calculating fees for provided transaction: {err:#}"), ..response::Query::default() }; @@ -62,8 +63,8 @@ pub(crate) async fn transaction_fee_request( Ok(Some(trace_denom)) => trace_denom, Ok(None) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!( "failed mapping ibc denom to trace denom: {ibc_denom}; asset does not \ exist in state" @@ -73,8 +74,8 @@ pub(crate) async fn transaction_fee_request( } Err(err) => { return response::Query { - code: AbciErrorCode::INTERNAL_ERROR.into(), - info: AbciErrorCode::INTERNAL_ERROR.to_string(), + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed mapping ibc denom to trace denom: {err:#}"), ..response::Query::default() }; @@ -105,8 +106,8 @@ fn preprocess_request(request: &request::Query) -> Result tx, Err(err) => { return Err(response::Query { - code: AbciErrorCode::BAD_REQUEST.into(), - info: AbciErrorCode::BAD_REQUEST.to_string(), + code: Code::Err(AbciErrorCode::BAD_REQUEST.value()), + info: AbciErrorCode::BAD_REQUEST.info(), log: format!("failed to decode request data to unsigned transaction: {err:#}"), ..response::Query::default() }); @@ -117,8 +118,8 @@ fn preprocess_request(request: &request::Query) -> Result tx, Err(err) => { return Err(response::Query { - code: AbciErrorCode::BAD_REQUEST.into(), - info: AbciErrorCode::BAD_REQUEST.to_string(), + code: Code::Err(AbciErrorCode::BAD_REQUEST.value()), + info: AbciErrorCode::BAD_REQUEST.info(), log: format!( "failed to convert raw proto unsigned transaction to native unsigned \ transaction: {err:#}" From 9f959f4fc492599ffc4a0bfa0d6e29d26b097b4e Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Tue, 20 Aug 2024 12:32:49 +0100 Subject: [PATCH 08/18] fix(sequencer)!: fix TOCTOU issues by merging check and execution (#1332) ## Summary Introduces `ActionHandler::check_and_execute`, replacing `ActionHandler::check_stateful` and `ActionHandler::execute`. ## Background Zelic found that separating execution into `check_stateful` and `execute` lead to time-of-check-vs-time-of-use risks: while `check_stateful` for two actions `A` and `B` might each pass, the execution of action `A` might change the state such that a subsequent execution of action `B` would now fail - or worse, lead to invalid state. This patch follows Penumbra (see issues linked at the bottom) in merging them into one atomic operation (atomic in the sense that `::check_and_execute` are run sequentially). There is also a `ActionHandler::check_historic` trait method, which however is currently not used. It is left for future work to go through the individual checks and move them to `check_historic`, where applicable (for example, checking address prefixes as long as changing these is not possible after chain init). ## Changes - change `ActionHandler` trait to merge `check_stateful` and `execute` into `check_and_execute`. - inject a transaction's signer into the ephemeral object store, setting before and after a transaction is executed. Necessary because this follows the `cnidarium_component::ActionHandler` convention, but also allows simplifying - remove the notion of bech32m addresses from many state writes and reads: the prefix only matters at the boundary, not inside the system ## Testing All tests were updated and pass. NOTE: a useful test would be to craft a problematic transaction that would be rejected with the newest change. However, crafting and executing such a transaction so that it is rejected by the current sequencer but leads to incorrect is left to a follow-up. ## Breaking Changelist While no snapshots guarding against breaking app state were triggered, this is still a breaking change: if supplied with a problematic payload, a sequencer node without this patch will reach a different (and invalid) state compared a node with the present patch. ## Related Issues Original Penumbra issues and fix: https://github.com/penumbra-zone/penumbra/issues/3960 https://github.com/penumbra-zone/penumbra/issues/3960 Closes #1318 --------- Co-authored-by: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> --- Cargo.lock | 1 - crates/astria-sequencer/Cargo.toml | 1 - .../astria-sequencer/src/accounts/action.rs | 193 +++++------ crates/astria-sequencer/src/accounts/mod.rs | 54 ++++ .../src/accounts/state_ext.rs | 88 ++--- .../src/app/action_handler.rs | 25 ++ crates/astria-sequencer/src/app/mod.rs | 40 +-- crates/astria-sequencer/src/app/tests_app.rs | 10 +- .../src/app/tests_breaking_changes.rs | 4 +- .../src/app/tests_execute_transaction.rs | 33 +- .../astria-sequencer/src/authority/action.rs | 150 ++++----- .../src/authority/state_ext.rs | 29 +- .../src/bridge/bridge_lock_action.rs | 151 +++------ .../src/bridge/bridge_sudo_change_action.rs | 99 +++--- .../src/bridge/bridge_unlock_action.rs | 292 +++++------------ .../src/bridge/init_bridge_account_action.rs | 50 ++- crates/astria-sequencer/src/bridge/query.rs | 48 ++- .../astria-sequencer/src/bridge/state_ext.rs | 165 +++++----- .../astria-sequencer/src/fee_asset_change.rs | 35 +- .../src/ibc/ibc_relayer_change.rs | 29 +- .../src/ibc/ics20_transfer.rs | 26 +- .../src/ibc/ics20_withdrawal.rs | 128 ++++---- crates/astria-sequencer/src/ibc/state_ext.rs | 60 ++-- crates/astria-sequencer/src/mempool/mod.rs | 25 +- .../astria-sequencer/src/sequence/action.rs | 66 ++-- crates/astria-sequencer/src/sequencer.rs | 17 - .../astria-sequencer/src/service/mempool.rs | 3 +- crates/astria-sequencer/src/test_utils.rs | 9 + .../src/transaction/action_handler.rs | 24 -- .../src/transaction/checks.rs | 73 ++--- .../astria-sequencer/src/transaction/mod.rs | 304 ++++++------------ .../src/transaction/state_ext.rs | 51 +++ 32 files changed, 992 insertions(+), 1291 deletions(-) create mode 100644 crates/astria-sequencer/src/app/action_handler.rs delete mode 100644 crates/astria-sequencer/src/transaction/action_handler.rs create mode 100644 crates/astria-sequencer/src/transaction/state_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 74f5d556b8..d6bea69d74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -791,7 +791,6 @@ dependencies = [ "borsh", "bytes", "cnidarium", - "cnidarium-component", "divan", "futures", "hex", diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index cef3ced64a..d487930dcb 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -28,7 +28,6 @@ borsh = { version = "1", features = ["derive"] } cnidarium = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.78.0", features = [ "metrics", ] } -cnidarium-component = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.78.0" } ibc-proto = { version = "0.41.0", features = ["server"] } matchit = "0.7.2" priority-queue = "2.0.2" diff --git a/crates/astria-sequencer/src/accounts/action.rs b/crates/astria-sequencer/src/accounts/action.rs index 8d4853ea68..60cbf3c150 100644 --- a/crates/astria-sequencer/src/accounts/action.rs +++ b/crates/astria-sequencer/src/accounts/action.rs @@ -4,31 +4,123 @@ use anyhow::{ Result, }; use astria_core::{ - primitive::v1::Address, + primitive::v1::ADDRESS_LEN, protocol::transaction::v1alpha1::action::TransferAction, Protobuf, }; -use tracing::instrument; +use cnidarium::{ + StateRead, + StateWrite, +}; +use super::AddressBytes; use crate::{ accounts::{ - self, StateReadExt as _, + StateWriteExt as _, + }, + address::StateReadExt as _, + app::ActionHandler, + assets::{ + StateReadExt as _, + StateWriteExt as _, }, - address, - assets, bridge::StateReadExt as _, - transaction::action_handler::ActionHandler, + transaction::StateReadExt as _, }; -pub(crate) async fn transfer_check_stateful( +#[async_trait::async_trait] +impl ActionHandler for TransferAction { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + + ensure!( + state + .get_bridge_account_rollup_id(from) + .await + .context("failed to get bridge account rollup id")? + .is_none(), + "cannot transfer out of bridge account; BridgeUnlock must be used", + ); + + check_transfer(self, from, &state).await?; + execute_transfer(self, from, state).await?; + + Ok(()) + } +} + +pub(crate) async fn execute_transfer( + action: &TransferAction, + from: [u8; ADDRESS_LEN], + mut state: S, +) -> anyhow::Result<()> { + let fee = state + .get_transfer_base_fee() + .await + .context("failed to get transfer base fee")?; + state + .get_and_increase_block_fees(&action.fee_asset, fee, TransferAction::full_name()) + .await + .context("failed to add to block fees")?; + + // if fee payment asset is same asset as transfer asset, deduct fee + // from same balance as asset transferred + if action.asset.to_ibc_prefixed() == action.fee_asset.to_ibc_prefixed() { + // check_stateful should have already checked this arithmetic + let payment_amount = action + .amount + .checked_add(fee) + .expect("transfer amount plus fee should not overflow"); + + state + .decrease_balance(from, &action.asset, payment_amount) + .await + .context("failed decreasing `from` account balance")?; + state + .increase_balance(action.to, &action.asset, action.amount) + .await + .context("failed increasing `to` account balance")?; + } else { + // otherwise, just transfer the transfer asset and deduct fee from fee asset balance + // later + state + .decrease_balance(from, &action.asset, action.amount) + .await + .context("failed decreasing `from` account balance")?; + state + .increase_balance(action.to, &action.asset, action.amount) + .await + .context("failed increasing `to` account balance")?; + + // deduct fee from fee asset balance + state + .decrease_balance(from, &action.fee_asset, fee) + .await + .context("failed decreasing `from` account balance for fee payment")?; + } + Ok(()) +} + +pub(crate) async fn check_transfer( action: &TransferAction, + from: TAddress, state: &S, - from: Address, ) -> Result<()> where - S: accounts::StateReadExt + assets::StateReadExt + 'static, + S: StateRead, + TAddress: AddressBytes, { + state.ensure_base_prefix(&action.to).await.context( + "failed ensuring that the destination address matches the permitted base prefix", + )?; ensure!( state .is_allowed_fee_asset(&action.fee_asset) @@ -44,7 +136,7 @@ where let transfer_asset = action.asset.clone(); let from_fee_balance = state - .get_account_balance(from, &action.fee_asset) + .get_account_balance(&from, &action.fee_asset) .await .context("failed getting `from` account balance for fee payment")?; @@ -80,84 +172,3 @@ where Ok(()) } - -#[async_trait::async_trait] -impl ActionHandler for TransferAction { - async fn check_stateless(&self) -> Result<()> { - Ok(()) - } - - async fn check_stateful(&self, state: &S, from: Address) -> Result<()> - where - S: accounts::StateReadExt + address::StateReadExt + 'static, - { - state.ensure_base_prefix(&self.to).await.context( - "failed ensuring that the destination address matches the permitted base prefix", - )?; - ensure!( - state - .get_bridge_account_rollup_id(&from) - .await - .context("failed to get bridge account rollup id")? - .is_none(), - "cannot transfer out of bridge account; BridgeUnlock must be used", - ); - - transfer_check_stateful(self, state, from) - .await - .context("stateful transfer check failed") - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, from: Address) -> Result<()> - where - S: accounts::StateWriteExt + assets::StateWriteExt, - { - let fee = state - .get_transfer_base_fee() - .await - .context("failed to get transfer base fee")?; - state - .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) - .await - .context("failed to add to block fees")?; - - // if fee payment asset is same asset as transfer asset, deduct fee - // from same balance as asset transferred - if self.asset.to_ibc_prefixed() == self.fee_asset.to_ibc_prefixed() { - // check_stateful should have already checked this arithmetic - let payment_amount = self - .amount - .checked_add(fee) - .expect("transfer amount plus fee should not overflow"); - - state - .decrease_balance(from, &self.asset, payment_amount) - .await - .context("failed decreasing `from` account balance")?; - state - .increase_balance(self.to, &self.asset, self.amount) - .await - .context("failed increasing `to` account balance")?; - } else { - // otherwise, just transfer the transfer asset and deduct fee from fee asset balance - // later - state - .decrease_balance(from, &self.asset, self.amount) - .await - .context("failed decreasing `from` account balance")?; - state - .increase_balance(self.to, &self.asset, self.amount) - .await - .context("failed increasing `to` account balance")?; - - // deduct fee from fee asset balance - state - .decrease_balance(from, &self.fee_asset, fee) - .await - .context("failed decreasing `from` account balance for fee payment")?; - } - - Ok(()) - } -} diff --git a/crates/astria-sequencer/src/accounts/mod.rs b/crates/astria-sequencer/src/accounts/mod.rs index ae0b420808..ddb9fe68a9 100644 --- a/crates/astria-sequencer/src/accounts/mod.rs +++ b/crates/astria-sequencer/src/accounts/mod.rs @@ -3,7 +3,61 @@ pub(crate) mod component; pub(crate) mod query; mod state_ext; +use astria_core::{ + crypto::{ + SigningKey, + VerificationKey, + }, + primitive::v1::{ + Address, + ADDRESS_LEN, + }, + protocol::transaction::v1alpha1::SignedTransaction, +}; pub(crate) use state_ext::{ StateReadExt, StateWriteExt, }; + +pub(crate) trait AddressBytes: Send + Sync { + fn address_bytes(&self) -> [u8; ADDRESS_LEN]; +} + +impl AddressBytes for Address { + fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + self.bytes() + } +} + +impl AddressBytes for [u8; ADDRESS_LEN] { + fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + *self + } +} + +impl AddressBytes for SignedTransaction { + fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + self.address_bytes() + } +} + +impl AddressBytes for SigningKey { + fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + self.address_bytes() + } +} + +impl AddressBytes for VerificationKey { + fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + self.address_bytes() + } +} + +impl<'a, T> AddressBytes for &'a T +where + T: AddressBytes, +{ + fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + (*self).address_bytes() + } +} diff --git a/crates/astria-sequencer/src/accounts/state_ext.rs b/crates/astria-sequencer/src/accounts/state_ext.rs index fa0fe00a6c..e8a60f3d03 100644 --- a/crates/astria-sequencer/src/accounts/state_ext.rs +++ b/crates/astria-sequencer/src/accounts/state_ext.rs @@ -3,14 +3,9 @@ use anyhow::{ Result, }; use astria_core::{ - crypto::{ - SigningKey, - VerificationKey, - }, primitive::v1::{ asset, Address, - ADDRESS_LEN, }, protocol::account::v1alpha1::AssetBalance, }; @@ -26,6 +21,8 @@ use cnidarium::{ use futures::StreamExt; use tracing::instrument; +use super::AddressBytes; + /// Newtype wrapper to read and write a u32 from rocksdb. #[derive(BorshSerialize, BorshDeserialize, Debug)] struct Nonce(u32); @@ -41,57 +38,20 @@ struct Fee(u128); const ACCOUNTS_PREFIX: &str = "accounts"; const TRANSFER_BASE_FEE_STORAGE_KEY: &str = "transferfee"; -trait GetAddressBytes: Send + Sync { - fn get_address_bytes(&self) -> [u8; ADDRESS_LEN]; -} - -impl GetAddressBytes for Address { - fn get_address_bytes(&self) -> [u8; ADDRESS_LEN] { - self.bytes() - } -} - -impl GetAddressBytes for [u8; ADDRESS_LEN] { - fn get_address_bytes(&self) -> [u8; ADDRESS_LEN] { - *self - } -} - -impl GetAddressBytes for SigningKey { - fn get_address_bytes(&self) -> [u8; ADDRESS_LEN] { - self.verification_key().get_address_bytes() - } -} - -impl GetAddressBytes for VerificationKey { - fn get_address_bytes(&self) -> [u8; ADDRESS_LEN] { - self.address_bytes() - } -} - -impl<'a, T> GetAddressBytes for &'a T -where - T: GetAddressBytes, -{ - fn get_address_bytes(&self) -> [u8; ADDRESS_LEN] { - (*self).get_address_bytes() - } -} - struct StorageKey<'a, T>(&'a T); -impl<'a, T: GetAddressBytes> std::fmt::Display for StorageKey<'a, T> { +impl<'a, T: AddressBytes> std::fmt::Display for StorageKey<'a, T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(ACCOUNTS_PREFIX)?; f.write_str("/")?; - for byte in self.0.get_address_bytes() { + for byte in self.0.address_bytes() { f.write_fmt(format_args!("{byte:02x}"))?; } Ok(()) } } -fn balance_storage_key>( - address: Address, +fn balance_storage_key>( + address: TAddress, asset: TAsset, ) -> String { format!( @@ -101,7 +61,7 @@ fn balance_storage_key>( ) } -fn nonce_storage_key(address: T) -> String { +fn nonce_storage_key(address: T) -> String { format!("{}/nonce", StorageKey(&address)) } @@ -161,8 +121,13 @@ pub(crate) trait StateReadExt: StateRead + crate::assets::StateReadExt { } #[instrument(skip_all)] - async fn get_account_balance<'a, TAsset>(&self, address: Address, asset: TAsset) -> Result + async fn get_account_balance<'a, TAddress, TAsset>( + &self, + address: TAddress, + asset: TAsset, + ) -> Result where + TAddress: AddressBytes, TAsset: Into + std::fmt::Display + Send, { let Some(bytes) = self @@ -177,7 +142,7 @@ pub(crate) trait StateReadExt: StateRead + crate::assets::StateReadExt { } #[instrument(skip_all)] - async fn get_account_nonce(&self, address: T) -> Result { + async fn get_account_nonce(&self, address: T) -> Result { let bytes = self .get_raw(&nonce_storage_key(address)) .await @@ -211,13 +176,14 @@ impl StateReadExt for T {} #[async_trait] pub(crate) trait StateWriteExt: StateWrite { #[instrument(skip_all)] - fn put_account_balance( + fn put_account_balance( &mut self, - address: Address, + address: TAddress, asset: TAsset, balance: u128, ) -> Result<()> where + TAddress: AddressBytes, TAsset: Into + std::fmt::Display + Send, { let bytes = borsh::to_vec(&Balance(balance)).context("failed to serialize balance")?; @@ -226,29 +192,30 @@ pub(crate) trait StateWriteExt: StateWrite { } #[instrument(skip_all)] - fn put_account_nonce(&mut self, address: Address, nonce: u32) -> Result<()> { + fn put_account_nonce(&mut self, address: T, nonce: u32) -> Result<()> { let bytes = borsh::to_vec(&Nonce(nonce)).context("failed to serialize nonce")?; self.put_raw(nonce_storage_key(address), bytes); Ok(()) } #[instrument(skip_all)] - async fn increase_balance( + async fn increase_balance( &mut self, - address: Address, + address: TAddress, asset: TAsset, amount: u128, ) -> Result<()> where + TAddress: AddressBytes, TAsset: Into + std::fmt::Display + Send, { let asset = asset.into(); let balance = self - .get_account_balance(address, asset) + .get_account_balance(&address, asset) .await .context("failed to get account balance")?; self.put_account_balance( - address, + &address, asset, balance .checked_add(amount) @@ -259,22 +226,23 @@ pub(crate) trait StateWriteExt: StateWrite { } #[instrument(skip_all)] - async fn decrease_balance( + async fn decrease_balance( &mut self, - address: Address, + address: TAddress, asset: TAsset, amount: u128, ) -> Result<()> where + TAddress: AddressBytes, TAsset: Into + std::fmt::Display + Send, { let asset = asset.into(); let balance = self - .get_account_balance(address, asset) + .get_account_balance(&address, asset) .await .context("failed to get account balance")?; self.put_account_balance( - address, + &address, asset, balance .checked_sub(amount) diff --git a/crates/astria-sequencer/src/app/action_handler.rs b/crates/astria-sequencer/src/app/action_handler.rs new file mode 100644 index 0000000000..180f592d39 --- /dev/null +++ b/crates/astria-sequencer/src/app/action_handler.rs @@ -0,0 +1,25 @@ +use cnidarium::StateWrite; + +/// This trait is a verbatim copy of `cnidarium_component::ActionHandler`. +/// +/// It's duplicated here because all actions are foreign types, forbidding +/// the implementation of [`cnidarium_component::ActionHandler`][1] for +/// these types due to Rust orphan rules. +/// +/// [1]: https://github.com/penumbra-zone/penumbra/blob/14959350abcb8cfbf33f9aedc7463fccfd8e3f9f/crates/cnidarium-component/src/action_handler.rs#L30 +#[async_trait::async_trait] +pub(crate) trait ActionHandler { + // Commenting out for the time being as this is currentl nonot being used. Leaving this in + // for reference as this is copied from cnidarium_component. + // ``` + // type CheckStatelessContext: Clone + Send + Sync + 'static; + // async fn check_stateless(&self, context: Self::CheckStatelessContext) -> anyhow::Result<()>; + // async fn check_historical(&self, _state: Arc) -> anyhow::Result<()> { + // Ok(()) + // } + // ``` + + async fn check_stateless(&self) -> anyhow::Result<()>; + + async fn check_and_execute(&self, mut state: S) -> anyhow::Result<()>; +} diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index fc605cd717..13ec8393c4 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -7,11 +7,13 @@ mod tests_breaking_changes; #[cfg(test)] mod tests_execute_transaction; +mod action_handler; use std::{ collections::VecDeque, sync::Arc, }; +pub(crate) use action_handler::ActionHandler; use anyhow::{ anyhow, ensure, @@ -19,7 +21,6 @@ use anyhow::{ }; use astria_core::{ generated::protocol::transaction::v1alpha1 as raw, - primitive::v1::Address, protocol::{ abci::AbciErrorCode, transaction::v1alpha1::{ @@ -59,7 +60,6 @@ use tracing::{ debug, info, instrument, - Instrument as _, }; use crate::{ @@ -106,10 +106,7 @@ use crate::{ StateReadExt as _, StateWriteExt as _, }, - transaction::{ - self, - InvalidNonce, - }, + transaction::InvalidNonce, }; /// The inter-block state being written to by the application. @@ -982,46 +979,29 @@ impl App { &mut self, signed_tx: Arc, ) -> anyhow::Result> { - let signed_tx_2 = signed_tx.clone(); - let stateless = tokio::spawn( - async move { transaction::check_stateless(&signed_tx_2).await }.in_current_span(), - ); - let signed_tx_2 = signed_tx.clone(); - let state2 = self.state.clone(); - let stateful = tokio::spawn( - async move { transaction::check_stateful(&signed_tx_2, &state2).await } - .in_current_span(), - ); - - stateless + signed_tx + .check_stateless() .await - .context("stateless check task aborted while executing")? .context("stateless check failed")?; - stateful - .await - .context("stateful check task aborted while executing")? - .context("stateful check failed")?; - // At this point, the stateful checks should have completed, - // leaving us with exclusive access to the Arc. + let mut state_tx = self .state .try_begin_transaction() .expect("state Arc should be present and unique"); - transaction::execute(&signed_tx, &mut state_tx) + signed_tx + .check_and_execute(&mut state_tx) .await .context("failed executing transaction")?; - let (_, events) = state_tx.apply(); - info!(event_count = events.len(), "executed transaction"); - Ok(events) + Ok(state_tx.apply().1) } #[instrument(name = "App::end_block", skip_all)] pub(crate) async fn end_block( &mut self, height: u64, - fee_recipient: Address, + fee_recipient: [u8; 20], ) -> anyhow::Result { let state_tx = StateDelta::new(self.state.clone()); let mut arc_state_tx = Arc::new(state_tx); diff --git a/crates/astria-sequencer/src/app/tests_app.rs b/crates/astria-sequencer/src/app/tests_app.rs index 195b810999..4e6b6dbfe6 100644 --- a/crates/astria-sequencer/src/app/tests_app.rs +++ b/crates/astria-sequencer/src/app/tests_app.rs @@ -295,9 +295,9 @@ async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let mut state_tx = StateDelta::new(app.state.clone()); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, nria()) + .put_bridge_account_ibc_asset(bridge_address, nria()) .unwrap(); app.apply(state_tx); app.prepare_commit(storage.clone()).await.unwrap(); @@ -385,9 +385,9 @@ async fn app_execution_results_match_proposal_vs_after_proposal() { let asset = nria().clone(); let mut state_tx = StateDelta::new(app.state.clone()); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, &asset) + .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); app.apply(state_tx); app.prepare_commit(storage.clone()).await.unwrap(); @@ -682,7 +682,7 @@ async fn app_end_block_validator_updates() { ]; let mut app = initialize_app(None, initial_validator_set).await; - let proposer_address = astria_address(&[0u8; 20]); + let proposer_address = [0u8; 20]; let validator_updates = vec![ ValidatorUpdate { diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index 8c1bb2d097..e135b55f0b 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -107,9 +107,9 @@ async fn app_finalize_block_snapshot() { let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let mut state_tx = StateDelta::new(app.state.clone()); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, nria()) + .put_bridge_account_ibc_asset(bridge_address, nria()) .unwrap(); app.apply(state_tx); diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index e2d3449c78..6a39904305 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -34,7 +34,10 @@ use tendermint::abci::EventAttributeIndexExt as _; use crate::{ accounts::StateReadExt as _, - app::test_utils::*, + app::{ + test_utils::*, + ActionHandler as _, + }, assets::StateReadExt as _, authority::StateReadExt as _, bridge::{ @@ -357,7 +360,7 @@ async fn app_execute_transaction_ibc_relayer_change_addition() { let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); assert_eq!(app.state.get_account_nonce(alice_address).await.unwrap(), 1); - assert!(app.state.is_ibc_relayer(&alice_address).await.unwrap()); + assert!(app.state.is_ibc_relayer(alice_address).await.unwrap()); } #[tokio::test] @@ -384,7 +387,7 @@ async fn app_execute_transaction_ibc_relayer_change_deletion() { let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); assert_eq!(app.state.get_account_nonce(alice_address).await.unwrap(), 1); - assert!(!app.state.is_ibc_relayer(&alice_address).await.unwrap()); + assert!(!app.state.is_ibc_relayer(alice_address).await.unwrap()); } #[tokio::test] @@ -436,7 +439,7 @@ async fn app_execute_transaction_sudo_address_change() { assert_eq!(app.state.get_account_nonce(alice_address).await.unwrap(), 1); let sudo_address = app.state.get_sudo_address().await.unwrap(); - assert_eq!(sudo_address, new_address); + assert_eq!(sudo_address, new_address.bytes()); } #[tokio::test] @@ -600,7 +603,7 @@ async fn app_execute_transaction_init_bridge_account_ok() { assert_eq!(app.state.get_account_nonce(alice_address).await.unwrap(), 1); assert_eq!( app.state - .get_bridge_account_rollup_id(&alice_address) + .get_bridge_account_rollup_id(alice_address) .await .unwrap() .unwrap(), @@ -608,7 +611,7 @@ async fn app_execute_transaction_init_bridge_account_ok() { ); assert_eq!( app.state - .get_bridge_account_ibc_asset(&alice_address) + .get_bridge_account_ibc_asset(alice_address) .await .unwrap(), nria().to_ibc_prefixed(), @@ -678,9 +681,9 @@ async fn app_execute_transaction_bridge_lock_action_ok() { let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let mut state_tx = StateDelta::new(app.state.clone()); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, nria()) + .put_bridge_account_ibc_asset(bridge_address, nria()) .unwrap(); app.apply(state_tx); @@ -879,8 +882,6 @@ async fn app_execute_transaction_invalid_chain_id() { async fn app_stateful_check_fails_insufficient_total_balance() { use rand::rngs::OsRng; - use crate::transaction; - let mut app = initialize_app(None, vec![]).await; let alice = get_alice_signing_key(); @@ -940,7 +941,8 @@ async fn app_stateful_check_fails_insufficient_total_balance() { .into_signed(&keypair); // try double, see fails stateful check - let res = transaction::check_stateful(&signed_tx_fail, &app.state) + let res = signed_tx_fail + .check_and_execute(Arc::get_mut(&mut app.state).unwrap()) .await .unwrap_err() .root_cause() @@ -964,7 +966,8 @@ async fn app_stateful_check_fails_insufficient_total_balance() { } .into_signed(&keypair); - transaction::check_stateful(&signed_tx_pass, &app.state) + signed_tx_pass + .check_and_execute(Arc::get_mut(&mut app.state).unwrap()) .await .expect("stateful check should pass since we transferred enough to cover fee"); } @@ -991,11 +994,11 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { .unwrap(); // create bridge account - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, nria()) + .put_bridge_account_ibc_asset(bridge_address, nria()) .unwrap(); - state_tx.put_bridge_account_withdrawer_address(&bridge_address, &bridge_address); + state_tx.put_bridge_account_withdrawer_address(bridge_address, bridge_address); app.apply(state_tx); let amount = 100; diff --git a/crates/astria-sequencer/src/authority/action.rs b/crates/astria-sequencer/src/authority/action.rs index baf6220db9..24e8ba7d20 100644 --- a/crates/astria-sequencer/src/authority/action.rs +++ b/crates/astria-sequencer/src/authority/action.rs @@ -4,30 +4,39 @@ use anyhow::{ Context as _, Result, }; -use astria_core::{ - primitive::v1::Address, - protocol::transaction::v1alpha1::action::{ - FeeChange, - FeeChangeAction, - SudoAddressChangeAction, - ValidatorUpdate, - }, +use astria_core::protocol::transaction::v1alpha1::action::{ + FeeChange, + FeeChangeAction, + SudoAddressChangeAction, + ValidatorUpdate, }; -use tracing::instrument; +use cnidarium::StateWrite; use crate::{ - address, - authority, - transaction::action_handler::ActionHandler, + accounts::StateWriteExt as _, + address::StateReadExt as _, + app::ActionHandler, + authority::{ + StateReadExt as _, + StateWriteExt as _, + }, + bridge::StateWriteExt as _, + ibc::StateWriteExt as _, + sequence::StateWriteExt as _, + transaction::StateReadExt as _, }; #[async_trait::async_trait] impl ActionHandler for ValidatorUpdate { - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); // ensure signer is the valid `sudo` key in state let sudo_address = state .get_sudo_address() @@ -52,15 +61,7 @@ impl ActionHandler for ValidatorUpdate { // check that this is not the only validator, cannot remove the last one ensure!(validator_set.len() != 1, "cannot remove the last validator"); } - Ok(()) - } - #[instrument(skip_all)] - async fn execute( - &self, - state: &mut S, - _: Address, - ) -> Result<()> { // add validator update in non-consensus state to be used in end_block let mut validator_updates = state .get_validator_updates() @@ -82,11 +83,11 @@ impl ActionHandler for SudoAddressChangeAction { /// check that the signer of the transaction is the current sudo address, /// as only that address can change the sudo address - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); state .ensure_base_prefix(&self.new_address) .await @@ -97,11 +98,6 @@ impl ActionHandler for SudoAddressChangeAction { .await .context("failed to get sudo address from state")?; ensure!(sudo_address == from, "signer is not the sudo key"); - Ok(()) - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, _: Address) -> Result<()> { state .put_sudo_address(self.new_address) .context("failed to put sudo address in state")?; @@ -111,30 +107,23 @@ impl ActionHandler for SudoAddressChangeAction { #[async_trait::async_trait] impl ActionHandler for FeeChangeAction { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + /// check that the signer of the transaction is the current sudo address, /// as only that address can change the fee - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); // ensure signer is the valid `sudo` key in state let sudo_address = state .get_sudo_address() .await .context("failed to get sudo address from state")?; ensure!(sudo_address == from, "signer is not the sudo key"); - Ok(()) - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, _: Address) -> Result<()> { - use crate::{ - accounts::StateWriteExt as _, - bridge::StateWriteExt as _, - ibc::StateWriteExt as _, - sequence::StateWriteExt as _, - }; match self.fee_change { FeeChange::TransferBaseFee => { @@ -172,31 +161,28 @@ mod test { use super::*; use crate::{ - accounts::{ - StateReadExt as _, + accounts::StateReadExt as _, + bridge::StateReadExt as _, + ibc::StateReadExt as _, + sequence::StateReadExt as _, + transaction::{ StateWriteExt as _, + TransactionContext, }, - bridge::{ - StateReadExt as _, - StateWriteExt as _, - }, - ibc::{ - StateReadExt as _, - StateWriteExt as _, - }, - sequence::{ - StateReadExt as _, - StateWriteExt as _, - }, - test_utils::astria_address, }; #[tokio::test] - async fn fee_change_action_execute() { + async fn fee_change_action_executes() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); let transfer_fee = 12; + + state.put_current_source(TransactionContext { + address_bytes: [1; 20], + }); + state.put_sudo_address([1; 20]).unwrap(); + state.put_transfer_base_fee(transfer_fee).unwrap(); let fee_change = FeeChangeAction { @@ -204,10 +190,7 @@ mod test { new_value: 10, }; - fee_change - .execute(&mut state, astria_address(&[1; 20])) - .await - .unwrap(); + fee_change.check_and_execute(&mut state).await.unwrap(); assert_eq!(state.get_transfer_base_fee().await.unwrap(), 10); let sequence_base_fee = 5; @@ -218,10 +201,7 @@ mod test { new_value: 3, }; - fee_change - .execute(&mut state, astria_address(&[1; 20])) - .await - .unwrap(); + fee_change.check_and_execute(&mut state).await.unwrap(); assert_eq!(state.get_sequence_action_base_fee().await.unwrap(), 3); let sequence_byte_cost_multiplier = 2; @@ -232,10 +212,7 @@ mod test { new_value: 4, }; - fee_change - .execute(&mut state, astria_address(&[1; 20])) - .await - .unwrap(); + fee_change.check_and_execute(&mut state).await.unwrap(); assert_eq!( state .get_sequence_action_byte_cost_multiplier() @@ -252,10 +229,7 @@ mod test { new_value: 2, }; - fee_change - .execute(&mut state, astria_address(&[1; 20])) - .await - .unwrap(); + fee_change.check_and_execute(&mut state).await.unwrap(); assert_eq!(state.get_init_bridge_account_base_fee().await.unwrap(), 2); let bridge_lock_byte_cost_multiplier = 1; @@ -266,10 +240,7 @@ mod test { new_value: 2, }; - fee_change - .execute(&mut state, astria_address(&[1; 20])) - .await - .unwrap(); + fee_change.check_and_execute(&mut state).await.unwrap(); assert_eq!( state.get_bridge_lock_byte_cost_multiplier().await.unwrap(), 2 @@ -285,10 +256,7 @@ mod test { new_value: 2, }; - fee_change - .execute(&mut state, astria_address(&[1; 20])) - .await - .unwrap(); + fee_change.check_and_execute(&mut state).await.unwrap(); assert_eq!(state.get_ics20_withdrawal_base_fee().await.unwrap(), 2); } } diff --git a/crates/astria-sequencer/src/authority/state_ext.rs b/crates/astria-sequencer/src/authority/state_ext.rs index afdb4856a8..f98d18a78f 100644 --- a/crates/astria-sequencer/src/authority/state_ext.rs +++ b/crates/astria-sequencer/src/authority/state_ext.rs @@ -5,10 +5,7 @@ use anyhow::{ Context, Result, }; -use astria_core::primitive::v1::{ - Address, - ADDRESS_LEN, -}; +use astria_core::primitive::v1::ADDRESS_LEN; use async_trait::async_trait; use borsh::{ BorshDeserialize, @@ -21,7 +18,7 @@ use cnidarium::{ use tracing::instrument; use super::ValidatorSet; -use crate::address; +use crate::accounts::AddressBytes; /// Newtype wrapper to read and write an address from rocksdb. #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -32,9 +29,9 @@ const VALIDATOR_SET_STORAGE_KEY: &str = "valset"; const VALIDATOR_UPDATES_KEY: &[u8] = b"valupdates"; #[async_trait] -pub(crate) trait StateReadExt: StateRead + address::StateReadExt { +pub(crate) trait StateReadExt: StateRead { #[instrument(skip_all)] - async fn get_sudo_address(&self) -> Result
{ + async fn get_sudo_address(&self) -> Result<[u8; ADDRESS_LEN]> { let Some(bytes) = self .get_raw(SUDO_STORAGE_KEY) .await @@ -45,9 +42,7 @@ pub(crate) trait StateReadExt: StateRead + address::StateReadExt { }; let SudoAddress(address_bytes) = SudoAddress::try_from_slice(&bytes).context("invalid sudo key bytes")?; - self.try_base_prefixed(&address_bytes) - .await - .context("failed constructing address from prefixed stored in state") + Ok(address_bytes) } #[instrument(skip_all)] @@ -88,10 +83,10 @@ impl StateReadExt for T {} #[async_trait] pub(crate) trait StateWriteExt: StateWrite { #[instrument(skip_all)] - fn put_sudo_address(&mut self, address: Address) -> Result<()> { + fn put_sudo_address(&mut self, address: T) -> Result<()> { self.put_raw( SUDO_STORAGE_KEY.to_string(), - borsh::to_vec(&SudoAddress(address.bytes())) + borsh::to_vec(&SudoAddress(address.address_bytes())) .context("failed to convert sudo address to vec")?, ); Ok(()) @@ -126,7 +121,10 @@ impl StateWriteExt for T {} #[cfg(test)] mod tests { - use astria_core::protocol::transaction::v1alpha1::action::ValidatorUpdate; + use astria_core::{ + primitive::v1::ADDRESS_LEN, + protocol::transaction::v1alpha1::action::ValidatorUpdate, + }; use cnidarium::StateDelta; use super::{ @@ -137,7 +135,6 @@ mod tests { address::StateWriteExt as _, authority::ValidatorSet, test_utils::{ - astria_address, verification_key, ASTRIA_PREFIX, }, @@ -162,7 +159,7 @@ mod tests { .expect_err("no sudo address should exist at first"); // can write new - let mut address_expected = astria_address(&[42u8; 20]); + let mut address_expected = [42u8; ADDRESS_LEN]; state .put_sudo_address(address_expected) .expect("writing sudo address should not fail"); @@ -176,7 +173,7 @@ mod tests { ); // can rewrite with new value - address_expected = astria_address(&[41u8; 20]); + address_expected = [41u8; ADDRESS_LEN]; state .put_sudo_address(address_expected) .expect("writing sudo address should not fail"); diff --git a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs index 2326593150..880298fb5f 100644 --- a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs @@ -4,31 +4,30 @@ use anyhow::{ Result, }; use astria_core::{ - primitive::v1::Address, protocol::transaction::v1alpha1::action::{ BridgeLockAction, TransferAction, }, sequencerblock::v1alpha1::block::Deposit, }; -use tracing::instrument; +use cnidarium::StateWrite; use crate::{ accounts::{ - action::transfer_check_stateful, + action::{ + check_transfer, + execute_transfer, + }, StateReadExt as _, StateWriteExt as _, }, - address, + address::StateReadExt as _, + app::ActionHandler, bridge::{ StateReadExt as _, StateWriteExt as _, }, - state_ext::{ - StateReadExt, - StateWriteExt, - }, - transaction::action_handler::ActionHandler, + transaction::StateReadExt as _, }; #[async_trait::async_trait] @@ -37,31 +36,24 @@ impl ActionHandler for BridgeLockAction { Ok(()) } - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); state .ensure_base_prefix(&self.to) .await .context("failed check for base prefix of destination address")?; - let transfer_action = TransferAction { - to: self.to, - asset: self.asset.clone(), - amount: self.amount, - fee_asset: self.fee_asset.clone(), - }; - // ensure the recipient is a bridge account. let rollup_id = state - .get_bridge_account_rollup_id(&self.to) + .get_bridge_account_rollup_id(self.to) .await .context("failed to get bridge account rollup id")? .ok_or_else(|| anyhow::anyhow!("bridge lock must be sent to a bridge account"))?; let allowed_asset = state - .get_bridge_account_ibc_asset(&self.to) + .get_bridge_account_ibc_asset(self.to) .await .context("failed to get bridge account asset ID")?; ensure!( @@ -95,12 +87,6 @@ impl ActionHandler for BridgeLockAction { .saturating_add(transfer_fee); ensure!(from_balance >= fee, "insufficient funds for fee payment"); - // this performs the same checks as a normal `TransferAction` - transfer_check_stateful(&transfer_action, state, from).await - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, from: Address) -> Result<()> { let transfer_action = TransferAction { to: self.to, asset: self.asset.clone(), @@ -108,13 +94,15 @@ impl ActionHandler for BridgeLockAction { fee_asset: self.fee_asset.clone(), }; - transfer_action - .execute(state, from) - .await - .context("failed to execute bridge lock action as transfer action")?; + check_transfer(&transfer_action, from, &state).await?; + // Executes the transfer and deducts transfer feeds. + // FIXME: This is a very roundabout way of paying for fees. IMO it would be + // better to just duplicate this entire logic here so that we don't call out + // to the transfer-action logic. + execute_transfer(&transfer_action, from, &mut state).await?; let rollup_id = state - .get_bridge_account_rollup_id(&self.to) + .get_bridge_account_rollup_id(self.to) .await .context("failed to get bridge account rollup id")? .expect("recipient must be a bridge account; this is a bug in check_stateful"); @@ -127,8 +115,11 @@ impl ActionHandler for BridgeLockAction { self.destination_chain_address.clone(), ); - // the transfer fee is already deducted in `transfer_action.execute()`, + // the transfer fee is already deducted in `execute_transfer() above, // so we just deduct the bridge lock byte multiplier fee. + // FIXME: similar to what is mentioned there: this should be reworked so that + // the fee deducation logic for these actions are defined fully independently + // (even at the cost of duplicating code). let byte_cost_multiplier = state .get_bridge_lock_byte_cost_multiplier() .await @@ -157,7 +148,6 @@ pub(crate) fn get_deposit_byte_len(deposit: &Deposit) -> u128 { #[cfg(test)] mod tests { - use address::StateWriteExt; use astria_core::primitive::v1::{ asset, RollupId, @@ -166,11 +156,17 @@ mod tests { use super::*; use crate::{ + address::StateWriteExt, assets::StateWriteExt as _, test_utils::{ + assert_anyhow_error, astria_address, ASTRIA_PREFIX, }, + transaction::{ + StateWriteExt as _, + TransactionContext, + }, }; fn test_asset() -> asset::Denom { @@ -178,73 +174,18 @@ mod tests { } #[tokio::test] - async fn bridge_lock_check_stateful_fee_calc() { + async fn execute_fee_calc() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); let transfer_fee = 12; - state.put_base_prefix(ASTRIA_PREFIX).unwrap(); - state.put_transfer_base_fee(transfer_fee).unwrap(); - state.put_bridge_lock_byte_cost_multiplier(2); - - let bridge_address = astria_address(&[1; 20]); - let asset = test_asset(); - let bridge_lock = BridgeLockAction { - to: bridge_address, - asset: asset.clone(), - amount: 100, - fee_asset: asset.clone(), - destination_chain_address: "someaddress".to_string(), - }; - - let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); - state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); - state - .put_bridge_account_ibc_asset(&bridge_address, &asset) - .unwrap(); - state.put_allowed_fee_asset(&asset); - let from_address = astria_address(&[2; 20]); + state.put_current_source(TransactionContext { + address_bytes: from_address.bytes(), + }); + state.put_base_prefix(ASTRIA_PREFIX).unwrap(); - // not enough balance; should fail - state - .put_account_balance(from_address, &asset, 100) - .unwrap(); - - assert!( - bridge_lock - .check_stateful(&state, from_address) - .await - .unwrap_err() - .to_string() - .contains("insufficient funds for fee payment") - ); - - // enough balance; should pass - let expected_deposit_fee = transfer_fee - + get_deposit_byte_len(&Deposit::new( - bridge_address, - rollup_id, - 100, - asset.clone(), - "someaddress".to_string(), - )) * 2; - state - .put_account_balance(from_address, &asset, 100 + expected_deposit_fee) - .unwrap(); - bridge_lock - .check_stateful(&state, from_address) - .await - .unwrap(); - } - - #[tokio::test] - async fn bridge_lock_execute_fee_calc() { - let storage = cnidarium::TempStorage::new().await.unwrap(); - let snapshot = storage.latest_snapshot(); - let mut state = StateDelta::new(snapshot); - let transfer_fee = 12; state.put_transfer_base_fee(transfer_fee).unwrap(); state.put_bridge_lock_byte_cost_multiplier(2); @@ -259,25 +200,19 @@ mod tests { }; let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); - state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state.put_bridge_account_rollup_id(bridge_address, &rollup_id); state - .put_bridge_account_ibc_asset(&bridge_address, &asset) + .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); state.put_allowed_fee_asset(&asset); - let from_address = astria_address(&[2; 20]); - // not enough balance; should fail state .put_account_balance(from_address, &asset, 100 + transfer_fee) .unwrap(); - assert!( - bridge_lock - .execute(&mut state, from_address) - .await - .unwrap_err() - .to_string() - .eq("failed to deduct fee from account balance") + assert_anyhow_error( + &bridge_lock.check_and_execute(&mut state).await.unwrap_err(), + "insufficient funds for fee payment", ); // enough balance; should pass @@ -292,6 +227,6 @@ mod tests { state .put_account_balance(from_address, &asset, 100 + expected_deposit_fee) .unwrap(); - bridge_lock.execute(&mut state, from_address).await.unwrap(); + bridge_lock.check_and_execute(&mut state).await.unwrap(); } } diff --git a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs index 518a9417e9..483adf9af5 100644 --- a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs @@ -3,38 +3,31 @@ use anyhow::{ Context as _, Result, }; -use astria_core::{ - primitive::v1::Address, - protocol::transaction::v1alpha1::action::BridgeSudoChangeAction, -}; -use tracing::instrument; +use astria_core::protocol::transaction::v1alpha1::action::BridgeSudoChangeAction; +use cnidarium::StateWrite; use crate::{ accounts::StateWriteExt as _, - address, + address::StateReadExt as _, + app::ActionHandler, assets::StateReadExt as _, bridge::state_ext::{ StateReadExt as _, StateWriteExt as _, }, - state_ext::{ - StateReadExt, - StateWriteExt, - }, - transaction::action_handler::ActionHandler, + transaction::StateReadExt as _, }; - #[async_trait::async_trait] impl ActionHandler for BridgeSudoChangeAction { async fn check_stateless(&self) -> Result<()> { Ok(()) } - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); state .ensure_base_prefix(&self.bridge_address) .await @@ -62,7 +55,7 @@ impl ActionHandler for BridgeSudoChangeAction { // check that the sender of this tx is the authorized sudo address for the bridge account let Some(sudo_address) = state - .get_bridge_account_sudo_address(&self.bridge_address) + .get_bridge_account_sudo_address(self.bridge_address) .await .context("failed to get bridge account sudo address")? else { @@ -76,11 +69,6 @@ impl ActionHandler for BridgeSudoChangeAction { "unauthorized for bridge sudo change action", ); - Ok(()) - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, _: Address) -> Result<()> { let fee = state .get_bridge_sudo_change_base_fee() .await @@ -91,11 +79,11 @@ impl ActionHandler for BridgeSudoChangeAction { .context("failed to decrease balance for bridge sudo change fee")?; if let Some(sudo_address) = self.new_sudo_address { - state.put_bridge_account_sudo_address(&self.bridge_address, &sudo_address); + state.put_bridge_account_sudo_address(self.bridge_address, sudo_address); } if let Some(withdrawer_address) = self.new_withdrawer_address { - state.put_bridge_account_withdrawer_address(&self.bridge_address, &withdrawer_address); + state.put_bridge_account_withdrawer_address(self.bridge_address, withdrawer_address); } Ok(()) @@ -104,17 +92,21 @@ impl ActionHandler for BridgeSudoChangeAction { #[cfg(test)] mod tests { - use address::StateWriteExt; use astria_core::primitive::v1::asset; use cnidarium::StateDelta; use super::*; use crate::{ + address::StateWriteExt as _, assets::StateWriteExt as _, test_utils::{ astria_address, ASTRIA_PREFIX, }, + transaction::{ + StateWriteExt as _, + TransactionContext, + }, }; fn test_asset() -> asset::Denom { @@ -122,11 +114,14 @@ mod tests { } #[tokio::test] - async fn check_stateless_ok() { + async fn fails_with_unauthorized_if_signer_is_not_sudo_address() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); + state.put_current_source(TransactionContext { + address_bytes: [1; 20], + }); state.put_base_prefix(ASTRIA_PREFIX).unwrap(); let asset = test_asset(); @@ -134,32 +129,7 @@ mod tests { let bridge_address = astria_address(&[99; 20]); let sudo_address = astria_address(&[98; 20]); - state.put_bridge_account_sudo_address(&bridge_address, &sudo_address); - - let action = BridgeSudoChangeAction { - bridge_address, - new_sudo_address: None, - new_withdrawer_address: None, - fee_asset: asset.clone(), - }; - - action.check_stateful(&state, sudo_address).await.unwrap(); - } - - #[tokio::test] - async fn check_stateless_unauthorized() { - let storage = cnidarium::TempStorage::new().await.unwrap(); - let snapshot = storage.latest_snapshot(); - let mut state = StateDelta::new(snapshot); - - state.put_base_prefix(ASTRIA_PREFIX).unwrap(); - - let asset = test_asset(); - state.put_allowed_fee_asset(&asset); - - let bridge_address = astria_address(&[99; 20]); - let sudo_address = astria_address(&[98; 20]); - state.put_bridge_account_sudo_address(&bridge_address, &sudo_address); + state.put_bridge_account_sudo_address(bridge_address, sudo_address); let action = BridgeSudoChangeAction { bridge_address, @@ -170,7 +140,7 @@ mod tests { assert!( action - .check_stateful(&state, bridge_address) + .check_and_execute(state) .await .unwrap_err() .to_string() @@ -179,16 +149,25 @@ mod tests { } #[tokio::test] - async fn execute_ok() { + async fn executes() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); + let sudo_address = astria_address(&[98; 20]); + state.put_current_source(TransactionContext { + address_bytes: sudo_address.bytes(), + }); state.put_base_prefix(ASTRIA_PREFIX).unwrap(); state.put_bridge_sudo_change_base_fee(10); let fee_asset = test_asset(); + state.put_allowed_fee_asset(&fee_asset); + let bridge_address = astria_address(&[99; 20]); + + state.put_bridge_account_sudo_address(bridge_address, sudo_address); + let new_sudo_address = astria_address(&[98; 20]); let new_withdrawer_address = astria_address(&[97; 20]); state @@ -202,21 +181,21 @@ mod tests { fee_asset, }; - action.execute(&mut state, bridge_address).await.unwrap(); + action.check_and_execute(&mut state).await.unwrap(); assert_eq!( state - .get_bridge_account_sudo_address(&bridge_address) + .get_bridge_account_sudo_address(bridge_address) .await .unwrap(), - Some(new_sudo_address), + Some(new_sudo_address.bytes()), ); assert_eq!( state - .get_bridge_account_withdrawer_address(&bridge_address) + .get_bridge_account_withdrawer_address(bridge_address) .await .unwrap(), - Some(new_withdrawer_address), + Some(new_withdrawer_address.bytes()), ); } } diff --git a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs b/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs index 550cf5dce4..ab515171bd 100644 --- a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs @@ -11,17 +11,17 @@ use astria_core::{ TransferAction, }, }; -use tracing::instrument; +use cnidarium::StateWrite; use crate::{ - accounts::action::transfer_check_stateful, - address, - bridge::StateReadExt as _, - state_ext::{ - StateReadExt, - StateWriteExt, + accounts::action::{ + check_transfer, + execute_transfer, }, - transaction::action_handler::ActionHandler, + address::StateReadExt as _, + app::ActionHandler, + bridge::StateReadExt as _, + transaction::StateReadExt as _, }; #[async_trait::async_trait] @@ -30,11 +30,11 @@ impl ActionHandler for BridgeUnlockAction { Ok(()) } - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); state .ensure_base_prefix(&self.to) .await @@ -48,17 +48,16 @@ impl ActionHandler for BridgeUnlockAction { // the bridge address to withdraw funds from // if unset, use the tx sender's address - let bridge_address = self.bridge_address.unwrap_or(from); + let bridge_address = self.bridge_address.map_or(from, Address::bytes); - // grab the bridge account's asset let asset = state - .get_bridge_account_ibc_asset(&bridge_address) + .get_bridge_account_ibc_asset(bridge_address) .await .context("failed to get bridge's asset id, must be a bridge account")?; // check that the sender of this tx is the authorized withdrawer for the bridge account let Some(withdrawer_address) = state - .get_bridge_account_withdrawer_address(&bridge_address) + .get_bridge_account_withdrawer_address(bridge_address) .await .context("failed to get bridge account withdrawer address")? else { @@ -77,31 +76,8 @@ impl ActionHandler for BridgeUnlockAction { fee_asset: self.fee_asset.clone(), }; - // this performs the same checks as a normal `TransferAction` - transfer_check_stateful(&transfer_action, state, bridge_address).await - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, from: Address) -> Result<()> { - // the bridge address to withdraw funds from - let bridge_address = self.bridge_address.unwrap_or(from); - - let asset = state - .get_bridge_account_ibc_asset(&bridge_address) - .await - .context("failed to get bridge's asset id, must be a bridge account")?; - - let transfer_action = TransferAction { - to: self.to, - asset: asset.into(), - amount: self.amount, - fee_asset: self.fee_asset.clone(), - }; - - transfer_action - .execute(state, bridge_address) - .await - .context("failed to execute bridge unlock action as transfer action")?; + check_transfer(&transfer_action, bridge_address, &state).await?; + execute_transfer(&transfer_action, bridge_address, state).await?; Ok(()) } @@ -109,22 +85,30 @@ impl ActionHandler for BridgeUnlockAction { #[cfg(test)] mod test { - use astria_core::primitive::v1::{ - asset, - RollupId, + use astria_core::{ + primitive::v1::{ + asset, + RollupId, + }, + protocol::transaction::v1alpha1::action::BridgeUnlockAction, }; use cnidarium::StateDelta; - use super::*; use crate::{ accounts::StateWriteExt as _, address::StateWriteExt as _, + app::ActionHandler as _, assets::StateWriteExt as _, bridge::StateWriteExt as _, test_utils::{ + assert_anyhow_error, astria_address, ASTRIA_PREFIX, }, + transaction::{ + StateWriteExt as _, + TransactionContext, + }, }; fn test_asset() -> asset::Denom { @@ -132,17 +116,19 @@ mod test { } #[tokio::test] - async fn fail_non_bridge_accounts() { + async fn fails_if_bridge_address_is_not_set_and_signer_is_not_bridge() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); + state.put_current_source(TransactionContext { + address_bytes: [1; 20], + }); state.put_base_prefix(ASTRIA_PREFIX).unwrap(); let asset = test_asset(); let transfer_amount = 100; - let address = astria_address(&[1; 20]); let to_address = astria_address(&[2; 20]); let bridge_unlock = BridgeUnlockAction { @@ -156,7 +142,7 @@ mod test { // not a bridge account, should fail assert!( bridge_unlock - .check_stateful(&state, address) + .check_and_execute(state) .await .unwrap_err() .to_string() @@ -165,24 +151,25 @@ mod test { } #[tokio::test] - async fn fail_withdrawer_unset_invalid_withdrawer() { + async fn fails_if_bridge_account_has_no_withdrawer_address() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); + state.put_current_source(TransactionContext { + address_bytes: [1; 20], + }); state.put_base_prefix(ASTRIA_PREFIX).unwrap(); let asset = test_asset(); let transfer_amount = 100; - let sender_address = astria_address(&[1; 20]); let to_address = astria_address(&[2; 20]); - let bridge_address = astria_address(&[3; 20]); + // state.put_bridge_account_withdrawer_address(bridge_address, bridge_address); state - .put_bridge_account_ibc_asset(&bridge_address, &asset) + .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); - state.put_bridge_account_withdrawer_address(&bridge_address, &bridge_address); let bridge_unlock = BridgeUnlockAction { to: to_address, @@ -193,35 +180,32 @@ mod test { }; // invalid sender, doesn't match action's `from`, should fail - assert!( - bridge_unlock - .check_stateful(&state, sender_address) - .await - .unwrap_err() - .to_string() - .contains("unauthorized to unlock bridge account") + assert_anyhow_error( + &bridge_unlock.check_and_execute(state).await.unwrap_err(), + "bridge account does not have an associated withdrawer address", ); } #[tokio::test] - async fn fail_withdrawer_set_invalid_withdrawer() { + async fn fails_if_withdrawer_is_not_signer() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); + state.put_current_source(TransactionContext { + address_bytes: [1; 20], + }); state.put_base_prefix(ASTRIA_PREFIX).unwrap(); let asset = test_asset(); let transfer_amount = 100; - let sender_address = astria_address(&[1; 20]); let to_address = astria_address(&[2; 20]); - let bridge_address = astria_address(&[3; 20]); let withdrawer_address = astria_address(&[4; 20]); - state.put_bridge_account_withdrawer_address(&bridge_address, &withdrawer_address); + state.put_bridge_account_withdrawer_address(bridge_address, withdrawer_address); state - .put_bridge_account_ibc_asset(&bridge_address, &asset) + .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); let bridge_unlock = BridgeUnlockAction { @@ -233,77 +217,22 @@ mod test { }; // invalid sender, doesn't match action's bridge account's withdrawer, should fail - assert!( - bridge_unlock - .check_stateful(&state, sender_address) - .await - .unwrap_err() - .to_string() - .contains("unauthorized to unlock bridge account") + assert_anyhow_error( + &bridge_unlock.check_and_execute(state).await.unwrap_err(), + "unauthorized to unlock bridge account", ); } #[tokio::test] - async fn fee_check_stateful_from_none() { + async fn execute_with_bridge_address_unset() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); - state.put_base_prefix(ASTRIA_PREFIX).unwrap(); - - let asset = test_asset(); - let transfer_fee = 10; - let transfer_amount = 100; - state.put_transfer_base_fee(transfer_fee).unwrap(); - let bridge_address = astria_address(&[1; 20]); - let to_address = astria_address(&[2; 20]); - let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); - - state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); - state - .put_bridge_account_ibc_asset(&bridge_address, &asset) - .unwrap(); - state.put_allowed_fee_asset(&asset); - state.put_bridge_account_withdrawer_address(&bridge_address, &bridge_address); - - let bridge_unlock = BridgeUnlockAction { - to: to_address, - amount: transfer_amount, - fee_asset: asset.clone(), - memo: "{}".into(), - bridge_address: None, - }; - - // not enough balance to transfer asset; should fail - state - .put_account_balance(bridge_address, &asset, transfer_amount) - .unwrap(); - assert!( - bridge_unlock - .check_stateful(&state, bridge_address) - .await - .unwrap_err() - .to_string() - .contains("insufficient funds for transfer and fee payment") - ); - - // enough balance; should pass - state - .put_account_balance(bridge_address, &asset, transfer_amount + transfer_fee) - .unwrap(); - bridge_unlock - .check_stateful(&state, bridge_address) - .await - .unwrap(); - } - - #[tokio::test] - async fn fee_check_stateful_from_some() { - let storage = cnidarium::TempStorage::new().await.unwrap(); - let snapshot = storage.latest_snapshot(); - let mut state = StateDelta::new(snapshot); - + state.put_current_source(TransactionContext { + address_bytes: bridge_address.bytes(), + }); state.put_base_prefix(ASTRIA_PREFIX).unwrap(); let asset = test_asset(); @@ -311,69 +240,14 @@ mod test { let transfer_amount = 100; state.put_transfer_base_fee(transfer_fee).unwrap(); - let bridge_address = astria_address(&[1; 20]); - let to_address = astria_address(&[2; 20]); - let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); - - state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); - state - .put_bridge_account_ibc_asset(&bridge_address, &asset) - .unwrap(); - state.put_allowed_fee_asset(&asset); - - let withdrawer_address = astria_address(&[3; 20]); - state.put_bridge_account_withdrawer_address(&bridge_address, &withdrawer_address); - - let bridge_unlock = BridgeUnlockAction { - to: to_address, - amount: transfer_amount, - fee_asset: asset.clone(), - memo: "{}".into(), - bridge_address: Some(bridge_address), - }; - - // not enough balance to transfer asset; should fail - state - .put_account_balance(bridge_address, &asset, transfer_amount) - .unwrap(); - assert!( - bridge_unlock - .check_stateful(&state, withdrawer_address) - .await - .unwrap_err() - .to_string() - .contains("insufficient funds for transfer and fee payment") - ); - - // enough balance; should pass - state - .put_account_balance(bridge_address, &asset, transfer_amount + transfer_fee) - .unwrap(); - bridge_unlock - .check_stateful(&state, withdrawer_address) - .await - .unwrap(); - } - - #[tokio::test] - async fn execute_from_none() { - let storage = cnidarium::TempStorage::new().await.unwrap(); - let snapshot = storage.latest_snapshot(); - let mut state = StateDelta::new(snapshot); - - let asset = test_asset(); - let transfer_fee = 10; - let transfer_amount = 100; - state.put_transfer_base_fee(transfer_fee).unwrap(); - - let bridge_address = astria_address(&[1; 20]); let to_address = astria_address(&[2; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); - state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state.put_bridge_account_rollup_id(bridge_address, &rollup_id); state - .put_bridge_account_ibc_asset(&bridge_address, &asset) + .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); + state.put_bridge_account_withdrawer_address(bridge_address, bridge_address); state.put_allowed_fee_asset(&asset); let bridge_unlock = BridgeUnlockAction { @@ -388,44 +262,46 @@ mod test { state .put_account_balance(bridge_address, &asset, transfer_amount) .unwrap(); - assert!( - bridge_unlock - .execute(&mut state, bridge_address) + assert_anyhow_error( + &bridge_unlock + .check_and_execute(&mut state) .await - .unwrap_err() - .to_string() - .eq("failed to execute bridge unlock action as transfer action") + .unwrap_err(), + "insufficient funds for transfer and fee payment", ); // enough balance; should pass state .put_account_balance(bridge_address, &asset, transfer_amount + transfer_fee) .unwrap(); - bridge_unlock - .execute(&mut state, bridge_address) - .await - .unwrap(); + bridge_unlock.check_and_execute(&mut state).await.unwrap(); } #[tokio::test] - async fn execute_from_some() { + async fn execute_with_bridge_address_set() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); + let bridge_address = astria_address(&[1; 20]); + state.put_current_source(TransactionContext { + address_bytes: bridge_address.bytes(), + }); + state.put_base_prefix(ASTRIA_PREFIX).unwrap(); + let asset = test_asset(); let transfer_fee = 10; let transfer_amount = 100; state.put_transfer_base_fee(transfer_fee).unwrap(); - let bridge_address = astria_address(&[1; 20]); let to_address = astria_address(&[2; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); - state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state.put_bridge_account_rollup_id(bridge_address, &rollup_id); state - .put_bridge_account_ibc_asset(&bridge_address, &asset) + .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); + state.put_bridge_account_withdrawer_address(bridge_address, bridge_address); state.put_allowed_fee_asset(&asset); let bridge_unlock = BridgeUnlockAction { @@ -440,22 +316,18 @@ mod test { state .put_account_balance(bridge_address, &asset, transfer_amount) .unwrap(); - assert!( - bridge_unlock - .execute(&mut state, bridge_address) + assert_anyhow_error( + &bridge_unlock + .check_and_execute(&mut state) .await - .unwrap_err() - .to_string() - .eq("failed to execute bridge unlock action as transfer action") + .unwrap_err(), + "insufficient funds for transfer and fee payment", ); // enough balance; should pass state .put_account_balance(bridge_address, &asset, transfer_amount + transfer_fee) .unwrap(); - bridge_unlock - .execute(&mut state, bridge_address) - .await - .unwrap(); + bridge_unlock.check_and_execute(&mut state).await.unwrap(); } } diff --git a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs index 986a314bfe..0dbbd07c78 100644 --- a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs +++ b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs @@ -8,24 +8,21 @@ use astria_core::{ primitive::v1::Address, protocol::transaction::v1alpha1::action::InitBridgeAccountAction, }; -use tracing::instrument; +use cnidarium::StateWrite; use crate::{ accounts::{ StateReadExt as _, StateWriteExt as _, }, - address, + address::StateReadExt as _, + app::ActionHandler, assets::StateReadExt as _, bridge::state_ext::{ StateReadExt as _, StateWriteExt as _, }, - state_ext::{ - StateReadExt, - StateWriteExt, - }, - transaction::action_handler::ActionHandler, + transaction::StateReadExt as _, }; #[async_trait::async_trait] @@ -34,11 +31,11 @@ impl ActionHandler for InitBridgeAccountAction { Ok(()) } - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); if let Some(withdrawer_address) = &self.withdrawer_address { state .ensure_base_prefix(withdrawer_address) @@ -73,7 +70,12 @@ impl ActionHandler for InitBridgeAccountAction { // // after the account becomes a bridge account, it can no longer receive funds // via `TransferAction`, only via `BridgeLockAction`. - if state.get_bridge_account_rollup_id(&from).await?.is_some() { + if state + .get_bridge_account_rollup_id(from) + .await + .context("failed getting rollup ID of bridge account")? + .is_some() + { bail!("bridge account already exists"); } @@ -87,23 +89,15 @@ impl ActionHandler for InitBridgeAccountAction { "insufficient funds for bridge account initialization", ); - Ok(()) - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, from: Address) -> Result<()> { - let fee = state - .get_init_bridge_account_base_fee() - .await - .context("failed to get base fee for initializing bridge account")?; - - state.put_bridge_account_rollup_id(&from, &self.rollup_id); + state.put_bridge_account_rollup_id(from, &self.rollup_id); state - .put_bridge_account_ibc_asset(&from, &self.asset) + .put_bridge_account_ibc_asset(from, &self.asset) .context("failed to put asset ID")?; - state.put_bridge_account_sudo_address(&from, &self.sudo_address.unwrap_or(from)); - state - .put_bridge_account_withdrawer_address(&from, &self.withdrawer_address.unwrap_or(from)); + state.put_bridge_account_sudo_address(from, self.sudo_address.map_or(from, Address::bytes)); + state.put_bridge_account_withdrawer_address( + from, + self.withdrawer_address.map_or(from, Address::bytes), + ); state .decrease_balance(from, &self.fee_asset, fee) diff --git a/crates/astria-sequencer/src/bridge/query.rs b/crates/astria-sequencer/src/bridge/query.rs index 60fa07c2a2..7807ebb188 100644 --- a/crates/astria-sequencer/src/bridge/query.rs +++ b/crates/astria-sequencer/src/bridge/query.rs @@ -15,6 +15,7 @@ use tendermint::abci::{ }; use crate::{ + address::StateReadExt, assets::StateReadExt as _, bridge::StateReadExt as _, state_ext::StateReadExt as _, @@ -37,11 +38,14 @@ fn error_query_response( } } +// allow / FIXME: there is a lot of code duplication due to `error_query_response`. +// this could be significantly shortened. +#[allow(clippy::too_many_lines)] async fn get_bridge_account_info( snapshot: cnidarium::Snapshot, address: Address, ) -> anyhow::Result, response::Query> { - let rollup_id = match snapshot.get_bridge_account_rollup_id(&address).await { + let rollup_id = match snapshot.get_bridge_account_rollup_id(address).await { Ok(Some(rollup_id)) => rollup_id, Ok(None) => { return Ok(None); @@ -55,7 +59,7 @@ async fn get_bridge_account_info( } }; - let ibc_asset = match snapshot.get_bridge_account_ibc_asset(&address).await { + let ibc_asset = match snapshot.get_bridge_account_ibc_asset(address).await { Ok(asset) => asset, Err(err) => { return Err(error_query_response( @@ -84,8 +88,8 @@ async fn get_bridge_account_info( } }; - let sudo_address = match snapshot.get_bridge_account_sudo_address(&address).await { - Ok(Some(sudo_address)) => sudo_address, + let sudo_address_bytes = match snapshot.get_bridge_account_sudo_address(address).await { + Ok(Some(bytes)) => bytes, Ok(None) => { return Err(error_query_response( None, @@ -102,8 +106,20 @@ async fn get_bridge_account_info( } }; - let withdrawer_address = match snapshot - .get_bridge_account_withdrawer_address(&address) + let sudo_address = match snapshot.try_base_prefixed(&sudo_address_bytes).await { + Err(err) => { + return Err(error_query_response( + Some(err), + AbciErrorCode::INTERNAL_ERROR, + "failed to construct bech32m address from address prefix and account bytes read \ + from state", + )); + } + Ok(address) => address, + }; + + let withdrawer_address_bytes = match snapshot + .get_bridge_account_withdrawer_address(address) .await { Ok(Some(withdrawer_address)) => withdrawer_address, @@ -123,6 +139,18 @@ async fn get_bridge_account_info( } }; + let withdrawer_address = match snapshot.try_base_prefixed(&withdrawer_address_bytes).await { + Err(err) => { + return Err(error_query_response( + Some(err), + AbciErrorCode::INTERNAL_ERROR, + "failed to construct bech32m address from address prefix and account bytes read \ + from state", + )); + } + Ok(address) => address, + }; + Ok(Some(BridgeAccountInfo { rollup_id, asset: trace_asset.into(), @@ -294,15 +322,15 @@ mod test { let sudo_address = astria_address(&[1u8; 20]); let withdrawer_address = astria_address(&[2u8; 20]); state.put_block_height(1); - state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state.put_bridge_account_rollup_id(bridge_address, &rollup_id); state .put_ibc_asset(asset.as_trace_prefixed().unwrap()) .unwrap(); state - .put_bridge_account_ibc_asset(&bridge_address, &asset) + .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); - state.put_bridge_account_sudo_address(&bridge_address, &sudo_address); - state.put_bridge_account_withdrawer_address(&bridge_address, &withdrawer_address); + state.put_bridge_account_sudo_address(bridge_address, sudo_address); + state.put_bridge_account_withdrawer_address(bridge_address, withdrawer_address); storage.commit(state).await.unwrap(); let query = request::Query { diff --git a/crates/astria-sequencer/src/bridge/state_ext.rs b/crates/astria-sequencer/src/bridge/state_ext.rs index 3b8ba0b5f6..0316603c09 100644 --- a/crates/astria-sequencer/src/bridge/state_ext.rs +++ b/crates/astria-sequencer/src/bridge/state_ext.rs @@ -14,6 +14,7 @@ use astria_core::{ asset, Address, RollupId, + ADDRESS_LEN, }, sequencerblock::v1alpha1::block::Deposit, }; @@ -34,7 +35,10 @@ use tracing::{ instrument, }; -use crate::address; +use crate::{ + accounts::AddressBytes, + address, +}; /// Newtype wrapper to read and write a u128 from rocksdb. #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -60,23 +64,26 @@ const INIT_BRIDGE_ACCOUNT_BASE_FEE_STORAGE_KEY: &str = "initbridgeaccfee"; const BRIDGE_LOCK_BYTE_COST_MULTIPLIER_STORAGE_KEY: &str = "bridgelockmultiplier"; const BRIDGE_SUDO_CHANGE_FEE_STORAGE_KEY: &str = "bridgesudofee"; -struct BridgeAccountKey<'a> { +struct BridgeAccountKey<'a, T> { prefix: &'static str, - address: &'a Address, + address: &'a T, } -impl<'a> std::fmt::Display for BridgeAccountKey<'a> { +impl<'a, T> std::fmt::Display for BridgeAccountKey<'a, T> +where + T: AddressBytes, +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.prefix)?; f.write_str("/")?; - for byte in self.address.bytes() { + for byte in self.address.address_bytes() { f.write_fmt(format_args!("{byte:02x}"))?; } Ok(()) } } -fn rollup_id_storage_key(address: &Address) -> String { +fn rollup_id_storage_key(address: &T) -> String { format!( "{}/rollupid", BridgeAccountKey { @@ -86,7 +93,7 @@ fn rollup_id_storage_key(address: &Address) -> String { ) } -fn asset_id_storage_key(address: &Address) -> String { +fn asset_id_storage_key(address: &T) -> String { format!( "{}/assetid", BridgeAccountKey { @@ -108,7 +115,7 @@ fn deposit_nonce_storage_key(rollup_id: &RollupId) -> Vec { format!("depositnonce/{}", rollup_id.encode_hex::()).into() } -fn bridge_account_sudo_address_storage_key(address: &Address) -> String { +fn bridge_account_sudo_address_storage_key(address: &T) -> String { format!( "{}", BridgeAccountKey { @@ -118,7 +125,7 @@ fn bridge_account_sudo_address_storage_key(address: &Address) -> String { ) } -fn bridge_account_withdrawer_address_storage_key(address: &Address) -> String { +fn bridge_account_withdrawer_address_storage_key(address: &T) -> String { format!( "{}", BridgeAccountKey { @@ -128,7 +135,7 @@ fn bridge_account_withdrawer_address_storage_key(address: &Address) -> String { ) } -fn last_transaction_hash_for_bridge_account_storage_key(address: &Address) -> Vec { +fn last_transaction_hash_for_bridge_account_storage_key(address: &T) -> Vec { format!( "{}/lasttx", BridgeAccountKey { @@ -143,9 +150,12 @@ fn last_transaction_hash_for_bridge_account_storage_key(address: &Address) -> Ve #[async_trait] pub(crate) trait StateReadExt: StateRead + address::StateReadExt { #[instrument(skip_all)] - async fn get_bridge_account_rollup_id(&self, address: &Address) -> Result> { + async fn get_bridge_account_rollup_id( + &self, + address: T, + ) -> Result> { let Some(rollup_id_bytes) = self - .get_raw(&rollup_id_storage_key(address)) + .get_raw(&rollup_id_storage_key(&address)) .await .context("failed reading raw account rollup ID from state")? else { @@ -159,9 +169,12 @@ pub(crate) trait StateReadExt: StateRead + address::StateReadExt { } #[instrument(skip_all)] - async fn get_bridge_account_ibc_asset(&self, address: &Address) -> Result { + async fn get_bridge_account_ibc_asset( + &self, + address: T, + ) -> Result { let bytes = self - .get_raw(&asset_id_storage_key(address)) + .get_raw(&asset_id_storage_key(&address)) .await .context("failed reading raw asset ID from state")? .ok_or_else(|| anyhow!("asset ID not found"))?; @@ -171,34 +184,35 @@ pub(crate) trait StateReadExt: StateRead + address::StateReadExt { } #[instrument(skip_all)] - async fn get_bridge_account_sudo_address( + async fn get_bridge_account_sudo_address( &self, - bridge_address: &Address, - ) -> Result> { + bridge_address: T, + ) -> Result> { let Some(sudo_address_bytes) = self - .get_raw(&bridge_account_sudo_address_storage_key(bridge_address)) + .get_raw(&bridge_account_sudo_address_storage_key(&bridge_address)) .await .context("failed reading raw bridge account sudo address from state")? else { debug!("bridge account sudo address not found, returning None"); return Ok(None); }; - - let sudo_address = self.try_base_prefixed(&sudo_address_bytes).await.context( - "failed check for constructing sudo address from address bytes and prefix stored \ - retrieved from state", - )?; + let sudo_address = sudo_address_bytes.try_into().map_err(|bytes: Vec<_>| { + anyhow::format_err!( + "failed to convert address `{}` bytes read from state to fixed length address", + bytes.len() + ) + })?; Ok(Some(sudo_address)) } #[instrument(skip_all)] - async fn get_bridge_account_withdrawer_address( + async fn get_bridge_account_withdrawer_address( &self, - bridge_address: &Address, - ) -> Result> { + bridge_address: T, + ) -> Result> { let Some(withdrawer_address_bytes) = self .get_raw(&bridge_account_withdrawer_address_storage_key( - bridge_address, + &bridge_address, )) .await .context("failed reading raw bridge account withdrawer address from state")? @@ -206,15 +220,15 @@ pub(crate) trait StateReadExt: StateRead + address::StateReadExt { debug!("bridge account withdrawer address not found, returning None"); return Ok(None); }; - - let withdrawer_address = self - .try_base_prefixed(&withdrawer_address_bytes) - .await - .context( - "failed check for constructing withdrawer address from address bytes and prefix \ - stored retrieved from state", - )?; - Ok(Some(withdrawer_address)) + let addr = withdrawer_address_bytes + .try_into() + .map_err(|bytes: Vec<_>| { + anyhow::Error::msg(format!( + "failed converting `{}` bytes retrieved from storage to fixed address length", + bytes.len() + )) + })?; + Ok(Some(addr)) } #[instrument(skip_all)] @@ -347,48 +361,55 @@ impl StateReadExt for T {} #[async_trait] pub(crate) trait StateWriteExt: StateWrite { #[instrument(skip_all)] - fn put_bridge_account_rollup_id(&mut self, address: &Address, rollup_id: &RollupId) { - self.put_raw(rollup_id_storage_key(address), rollup_id.to_vec()); + fn put_bridge_account_rollup_id(&mut self, address: T, rollup_id: &RollupId) { + self.put_raw(rollup_id_storage_key(&address), rollup_id.to_vec()); } #[instrument(skip_all)] - fn put_bridge_account_ibc_asset( + fn put_bridge_account_ibc_asset( &mut self, - address: &Address, + address: TAddress, asset: TAsset, ) -> Result<()> where + TAddress: AddressBytes, TAsset: Into + std::fmt::Display, { let ibc = asset.into(); self.put_raw( - asset_id_storage_key(address), + asset_id_storage_key(&address), borsh::to_vec(&AssetId(ibc.get())).context("failed to serialize asset IDs")?, ); Ok(()) } #[instrument(skip_all)] - fn put_bridge_account_sudo_address( + fn put_bridge_account_sudo_address( &mut self, - bridge_address: &Address, - sudo_address: &Address, - ) { + bridge_address: TBridgeAddress, + sudo_address: TSudoAddress, + ) where + TBridgeAddress: AddressBytes, + TSudoAddress: AddressBytes, + { self.put_raw( - bridge_account_sudo_address_storage_key(bridge_address), - sudo_address.bytes().to_vec(), + bridge_account_sudo_address_storage_key(&bridge_address), + sudo_address.address_bytes().to_vec(), ); } #[instrument(skip_all)] - fn put_bridge_account_withdrawer_address( + fn put_bridge_account_withdrawer_address( &mut self, - bridge_address: &Address, - withdrawer_address: &Address, - ) { + bridge_address: TBridgeAddress, + withdrawer_address: TWithdrawerAddress, + ) where + TBridgeAddress: AddressBytes, + TWithdrawerAddress: AddressBytes, + { self.put_raw( - bridge_account_withdrawer_address_storage_key(bridge_address), - withdrawer_address.bytes().to_vec(), + bridge_account_withdrawer_address_storage_key(&bridge_address), + withdrawer_address.address_bytes().to_vec(), ); } @@ -465,13 +486,13 @@ pub(crate) trait StateWriteExt: StateWrite { } #[instrument(skip_all)] - fn put_last_transaction_hash_for_bridge_account( + fn put_last_transaction_hash_for_bridge_account( &mut self, - address: &Address, + address: T, tx_hash: &[u8; 32], ) { self.nonverifiable_put_raw( - last_transaction_hash_for_bridge_account_storage_key(address), + last_transaction_hash_for_bridge_account_storage_key(&address), tx_hash.to_vec(), ); } @@ -520,7 +541,7 @@ mod test { // uninitialized ok assert_eq!( - state.get_bridge_account_rollup_id(&address).await.expect( + state.get_bridge_account_rollup_id(address).await.expect( "call to get bridge account rollup id should not fail for uninitialized addresses" ), Option::None, @@ -538,10 +559,10 @@ mod test { let address = astria_address(&[42u8; 20]); // can write new - state.put_bridge_account_rollup_id(&address, &rollup_id); + state.put_bridge_account_rollup_id(address, &rollup_id); assert_eq!( state - .get_bridge_account_rollup_id(&address) + .get_bridge_account_rollup_id(address) .await .expect("a rollup ID was written and must exist inside the database") .expect("expecting return value"), @@ -551,10 +572,10 @@ mod test { // can rewrite with new value rollup_id = RollupId::new([2u8; 32]); - state.put_bridge_account_rollup_id(&address, &rollup_id); + state.put_bridge_account_rollup_id(address, &rollup_id); assert_eq!( state - .get_bridge_account_rollup_id(&address) + .get_bridge_account_rollup_id(address) .await .expect("a rollup ID was written and must exist inside the database") .expect("expecting return value"), @@ -565,10 +586,10 @@ mod test { // can write additional account and both valid let rollup_id_1 = RollupId::new([2u8; 32]); let address_1 = astria_address(&[41u8; 20]); - state.put_bridge_account_rollup_id(&address_1, &rollup_id_1); + state.put_bridge_account_rollup_id(address_1, &rollup_id_1); assert_eq!( state - .get_bridge_account_rollup_id(&address_1) + .get_bridge_account_rollup_id(address_1) .await .expect("a rollup ID was written and must exist inside the database") .expect("expecting return value"), @@ -578,7 +599,7 @@ mod test { assert_eq!( state - .get_bridge_account_rollup_id(&address) + .get_bridge_account_rollup_id(address) .await .expect("a rollup ID was written and must exist inside the database") .expect("expecting return value"), @@ -595,7 +616,7 @@ mod test { let address = astria_address(&[42u8; 20]); state - .get_bridge_account_ibc_asset(&address) + .get_bridge_account_ibc_asset(address) .await .expect_err("call to get bridge account asset ids should fail if no assets"); } @@ -611,10 +632,10 @@ mod test { // can write state - .put_bridge_account_ibc_asset(&address, &asset) + .put_bridge_account_ibc_asset(address, &asset) .expect("storing bridge account asset should not fail"); let mut result = state - .get_bridge_account_ibc_asset(&address) + .get_bridge_account_ibc_asset(address) .await .expect("bridge asset id was written and must exist inside the database"); assert_eq!( @@ -626,10 +647,10 @@ mod test { // can update asset = "asset_2".parse::().unwrap(); state - .put_bridge_account_ibc_asset(&address, &asset) + .put_bridge_account_ibc_asset(address, &asset) .expect("storing bridge account assets should not fail"); result = state - .get_bridge_account_ibc_asset(&address) + .get_bridge_account_ibc_asset(address) .await .expect("bridge asset id was written and must exist inside the database"); assert_eq!( @@ -642,18 +663,18 @@ mod test { let address_1 = astria_address(&[41u8; 20]); let asset_1 = asset_1(); state - .put_bridge_account_ibc_asset(&address_1, &asset_1) + .put_bridge_account_ibc_asset(address_1, &asset_1) .expect("storing bridge account assets should not fail"); assert_eq!( state - .get_bridge_account_ibc_asset(&address_1) + .get_bridge_account_ibc_asset(address_1) .await .expect("bridge asset id was written and must exist inside the database"), asset_1.into(), "second bridge account asset not what was expected" ); result = state - .get_bridge_account_ibc_asset(&address) + .get_bridge_account_ibc_asset(address) .await .expect("original bridge asset id was written and must exist inside the database"); assert_eq!( diff --git a/crates/astria-sequencer/src/fee_asset_change.rs b/crates/astria-sequencer/src/fee_asset_change.rs index 7ccdfdf804..d496b6581d 100644 --- a/crates/astria-sequencer/src/fee_asset_change.rs +++ b/crates/astria-sequencer/src/fee_asset_change.rs @@ -4,26 +4,31 @@ use anyhow::{ Context as _, Result, }; -use astria_core::{ - primitive::v1::Address, - protocol::transaction::v1alpha1::action::FeeAssetChangeAction, -}; +use astria_core::protocol::transaction::v1alpha1::action::FeeAssetChangeAction; use async_trait::async_trait; +use cnidarium::StateWrite; use crate::{ - assets, - assets::StateReadExt as _, - authority, - transaction::action_handler::ActionHandler, + app::ActionHandler, + assets::{ + StateReadExt as _, + StateWriteExt as _, + }, + authority::StateReadExt as _, + transaction::StateReadExt as _, }; #[async_trait] impl ActionHandler for FeeAssetChangeAction { - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); let authority_sudo_address = state .get_sudo_address() .await @@ -32,10 +37,6 @@ impl ActionHandler for FeeAssetChangeAction { authority_sudo_address == from, "unauthorized address for fee asset change" ); - Ok(()) - } - - async fn execute(&self, state: &mut S, _from: Address) -> Result<()> { match self { FeeAssetChangeAction::Addition(asset) => { state.put_allowed_fee_asset(asset); diff --git a/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs b/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs index cb37a1ac30..e454e509ee 100644 --- a/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs +++ b/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs @@ -3,16 +3,18 @@ use anyhow::{ Context as _, Result, }; -use astria_core::{ - primitive::v1::Address, - protocol::transaction::v1alpha1::action::IbcRelayerChangeAction, -}; +use astria_core::protocol::transaction::v1alpha1::action::IbcRelayerChangeAction; use async_trait::async_trait; +use cnidarium::StateWrite; use crate::{ - address, - ibc, - transaction::action_handler::ActionHandler, + address::StateReadExt as _, + app::ActionHandler, + ibc::{ + StateReadExt as _, + StateWriteExt as _, + }, + transaction::StateReadExt as _, }; #[async_trait] @@ -21,11 +23,11 @@ impl ActionHandler for IbcRelayerChangeAction { Ok(()) } - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); match self { IbcRelayerChangeAction::Addition(addr) | IbcRelayerChangeAction::Removal(addr) => { state.ensure_base_prefix(addr).await.context( @@ -42,10 +44,7 @@ impl ActionHandler for IbcRelayerChangeAction { ibc_sudo_address == from, "unauthorized address for IBC relayer change" ); - Ok(()) - } - async fn execute(&self, state: &mut S, _from: Address) -> Result<()> { match self { IbcRelayerChangeAction::Addition(address) => { state.put_ibc_relayer_address(address); diff --git a/crates/astria-sequencer/src/ibc/ics20_transfer.rs b/crates/astria-sequencer/src/ibc/ics20_transfer.rs index 1b2dac46f8..3ae8eacfd5 100644 --- a/crates/astria-sequencer/src/ibc/ics20_transfer.rs +++ b/crates/astria-sequencer/src/ibc/ics20_transfer.rs @@ -556,7 +556,7 @@ async fn execute_ics20_transfer_bridge_lock( // check if the recipient is a bridge account; if so, // ensure that the packet memo field (`destination_address`) is set. let is_bridge_lock = state - .get_bridge_account_rollup_id(&recipient) + .get_bridge_account_rollup_id(recipient) .await .context("failed to get bridge account rollup ID from state")? .is_some(); @@ -612,7 +612,7 @@ async fn execute_deposit( // ensure that the asset ID being transferred // to it is allowed. let Some(rollup_id) = state - .get_bridge_account_rollup_id(&bridge_address) + .get_bridge_account_rollup_id(bridge_address) .await .context("failed to get bridge account rollup ID from state")? else { @@ -620,7 +620,7 @@ async fn execute_deposit( }; let allowed_asset = state - .get_bridge_account_ibc_asset(&bridge_address) + .get_bridge_account_ibc_asset(bridge_address) .await .context("failed to get bridge account asset ID")?; ensure!( @@ -765,9 +765,9 @@ mod test { let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let denom = "dest_port/dest_channel/nootasset".parse::().unwrap(); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, &denom) + .put_bridge_account_ibc_asset(bridge_address, &denom) .unwrap(); let memo = memos::v1alpha1::Ics20TransferDeposit { @@ -822,9 +822,9 @@ mod test { let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let denom = "dest_port/dest_channel/nootasset".parse::().unwrap(); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, &denom) + .put_bridge_account_ibc_asset(bridge_address, &denom) .unwrap(); // use invalid memo, which should fail @@ -860,9 +860,9 @@ mod test { let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let denom = "dest_port/dest_channel/nootasset".parse::().unwrap(); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, &denom) + .put_bridge_account_ibc_asset(bridge_address, &denom) .unwrap(); // use invalid asset, which should fail @@ -1000,9 +1000,9 @@ mod test { .parse::() .unwrap(); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, &denom) + .put_bridge_account_ibc_asset(bridge_address, &denom) .unwrap(); let amount = 100; @@ -1041,9 +1041,9 @@ mod test { let denom = "nootasset".parse::().unwrap(); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); - state_tx.put_bridge_account_rollup_id(&bridge_address, &rollup_id); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); state_tx - .put_bridge_account_ibc_asset(&bridge_address, &denom) + .put_bridge_account_ibc_asset(bridge_address, &denom) .unwrap(); let packet = FungibleTokenPacketData { diff --git a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs index 859ca1d920..3dcbb3bfb8 100644 --- a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs +++ b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs @@ -12,6 +12,10 @@ use astria_core::{ }, protocol::transaction::v1alpha1::action, }; +use cnidarium::{ + StateRead, + StateWrite, +}; use ibc_types::core::channel::{ ChannelId, PortId, @@ -22,17 +26,21 @@ use penumbra_ibc::component::packet::{ SendPacketWrite as _, Unchecked, }; -use tracing::instrument; use crate::{ - accounts, - address, + accounts::{ + AddressBytes, + StateReadExt as _, + StateWriteExt as _, + }, + address::StateReadExt as _, + app::ActionHandler, bridge::StateReadExt as _, ibc::{ StateReadExt as _, StateWriteExt as _, }, - transaction::action_handler::ActionHandler, + transaction::StateReadExt as _, }; fn withdrawal_to_unchecked_ibc_packet( @@ -51,10 +59,10 @@ fn withdrawal_to_unchecked_ibc_packet( ) } -async fn ics20_withdrawal_check_stateful_bridge_account( +async fn ics20_withdrawal_check_stateful_bridge_account( action: &action::Ics20Withdrawal, state: &S, - from: Address, + from: [u8; 20], ) -> Result<()> { // bridge address checks: // - if the sender of this transaction is not a bridge account, and the tx `bridge_address` @@ -65,7 +73,7 @@ async fn ics20_withdrawal_check_stateful_bridge_account Result<()> { ensure!(self.timeout_time() != 0, "timeout time must be non-zero",); @@ -106,12 +113,12 @@ impl ActionHandler for action::Ics20Withdrawal { Ok(()) } - #[instrument(skip_all)] - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> { + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + state .ensure_base_prefix(&self.return_address) .await @@ -123,18 +130,20 @@ impl ActionHandler for action::Ics20Withdrawal { )?; } - ics20_withdrawal_check_stateful_bridge_account(self, state, from).await?; + ics20_withdrawal_check_stateful_bridge_account(self, &state, from).await?; let fee = state .get_ics20_withdrawal_base_fee() .await .context("failed to get ics20 withdrawal base fee")?; - let packet: IBCPacket = withdrawal_to_unchecked_ibc_packet(self); - state - .send_packet_check(packet) - .await - .context("packet failed send check")?; + let packet = { + let packet = withdrawal_to_unchecked_ibc_packet(self); + state + .send_packet_check(packet) + .await + .context("packet failed send check")? + }; let transfer_asset = self.denom(); @@ -173,21 +182,6 @@ impl ActionHandler for action::Ics20Withdrawal { ); } - Ok(()) - } - - #[instrument(skip_all)] - async fn execute( - &self, - state: &mut S, - from: Address, - ) -> Result<()> { - let fee = state - .get_ics20_withdrawal_base_fee() - .await - .context("failed to get ics20 withdrawal base fee")?; - let checked_packet = withdrawal_to_unchecked_ibc_packet(self).assume_checked(); - state .decrease_balance(from, self.denom(), self.amount()) .await @@ -200,11 +194,7 @@ impl ActionHandler for action::Ics20Withdrawal { // if we're the source, move tokens to the escrow account, // otherwise the tokens are just burned - if is_source( - checked_packet.source_port(), - checked_packet.source_channel(), - self.denom(), - ) { + if is_source(packet.source_port(), packet.source_channel(), self.denom()) { let channel_balance = state .get_ibc_channel_balance(self.source_channel(), self.denom()) .await @@ -221,7 +211,7 @@ impl ActionHandler for action::Ics20Withdrawal { .context("failed to update channel balance")?; } - state.send_packet_execute(checked_packet).await; + state.send_packet_execute(packet).await; Ok(()) } } @@ -236,13 +226,13 @@ fn is_source(source_port: &PortId, source_channel: &ChannelId, asset: &Denom) -> #[cfg(test)] mod tests { - use address::StateWriteExt; use astria_core::primitive::v1::RollupId; use cnidarium::StateDelta; use ibc_types::core::client::Height; use super::*; use crate::{ + address::StateWriteExt as _, bridge::StateWriteExt as _, test_utils::{ astria_address, @@ -257,13 +247,13 @@ mod tests { let state = StateDelta::new(snapshot); let denom = "test".parse::().unwrap(); - let from = astria_address(&[1u8; 20]); + let from = [1u8; 20]; let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), bridge_address: None, destination_chain_address: "test".to_string(), - return_address: from, + return_address: astria_address(&from), timeout_height: Height::new(1, 1).unwrap(), timeout_time: 1, source_channel: "channel-0".to_string().parse().unwrap(), @@ -285,12 +275,12 @@ mod tests { state.put_base_prefix(ASTRIA_PREFIX).unwrap(); // sender is a bridge address, which is also the withdrawer, so it's ok - let bridge_address = astria_address(&[1u8; 20]); + let bridge_address = [1u8; 20]; state.put_bridge_account_rollup_id( - &bridge_address, + bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), ); - state.put_bridge_account_withdrawer_address(&bridge_address, &bridge_address); + state.put_bridge_account_withdrawer_address(bridge_address, bridge_address); let denom = "test".parse::().unwrap(); let action = action::Ics20Withdrawal { @@ -298,7 +288,7 @@ mod tests { denom: denom.clone(), bridge_address: None, destination_chain_address: "test".to_string(), - return_address: bridge_address, + return_address: astria_address(&bridge_address), timeout_height: Height::new(1, 1).unwrap(), timeout_time: 1, source_channel: "channel-0".to_string().parse().unwrap(), @@ -320,12 +310,12 @@ mod tests { state.put_base_prefix(ASTRIA_PREFIX).unwrap(); // withdraw is *not* the bridge address, Ics20Withdrawal must be sent by the withdrawer - let bridge_address = astria_address(&[1u8; 20]); + let bridge_address = [1u8; 20]; state.put_bridge_account_rollup_id( - &bridge_address, + bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), ); - state.put_bridge_account_withdrawer_address(&bridge_address, &astria_address(&[2u8; 20])); + state.put_bridge_account_withdrawer_address(bridge_address, astria_address(&[2u8; 20])); let denom = "test".parse::().unwrap(); let action = action::Ics20Withdrawal { @@ -333,7 +323,7 @@ mod tests { denom: denom.clone(), bridge_address: None, destination_chain_address: "test".to_string(), - return_address: bridge_address, + return_address: astria_address(&bridge_address), timeout_height: Height::new(1, 1).unwrap(), timeout_time: 1, source_channel: "channel-0".to_string().parse().unwrap(), @@ -359,21 +349,21 @@ mod tests { state.put_base_prefix(ASTRIA_PREFIX).unwrap(); // sender the withdrawer address, so it's ok - let bridge_address = astria_address(&[1u8; 20]); - let withdrawer_address = astria_address(&[2u8; 20]); + let bridge_address = [1u8; 20]; + let withdrawer_address = [2u8; 20]; state.put_bridge_account_rollup_id( - &bridge_address, + bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), ); - state.put_bridge_account_withdrawer_address(&bridge_address, &withdrawer_address); + state.put_bridge_account_withdrawer_address(bridge_address, withdrawer_address); let denom = "test".parse::().unwrap(); let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), - bridge_address: Some(bridge_address), + bridge_address: Some(astria_address(&bridge_address)), destination_chain_address: "test".to_string(), - return_address: bridge_address, + return_address: astria_address(&bridge_address), timeout_height: Height::new(1, 1).unwrap(), timeout_time: 1, source_channel: "channel-0".to_string().parse().unwrap(), @@ -395,21 +385,21 @@ mod tests { state.put_base_prefix(ASTRIA_PREFIX).unwrap(); // sender is not the withdrawer address, so must fail - let bridge_address = astria_address(&[1u8; 20]); + let bridge_address = [1u8; 20]; let withdrawer_address = astria_address(&[2u8; 20]); state.put_bridge_account_rollup_id( - &bridge_address, + bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), ); - state.put_bridge_account_withdrawer_address(&bridge_address, &withdrawer_address); + state.put_bridge_account_withdrawer_address(bridge_address, withdrawer_address); let denom = "test".parse::().unwrap(); let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), - bridge_address: Some(bridge_address), + bridge_address: Some(astria_address(&bridge_address)), destination_chain_address: "test".to_string(), - return_address: bridge_address, + return_address: astria_address(&bridge_address), timeout_height: Height::new(1, 1).unwrap(), timeout_time: 1, source_channel: "channel-0".to_string().parse().unwrap(), @@ -434,15 +424,15 @@ mod tests { let state = StateDelta::new(snapshot); // sender is not the withdrawer address, so must fail - let not_bridge_address = astria_address(&[1u8; 20]); + let not_bridge_address = [1u8; 20]; let denom = "test".parse::().unwrap(); let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), - bridge_address: Some(not_bridge_address), + bridge_address: Some(astria_address(¬_bridge_address)), destination_chain_address: "test".to_string(), - return_address: not_bridge_address, + return_address: astria_address(¬_bridge_address), timeout_height: Height::new(1, 1).unwrap(), timeout_time: 1, source_channel: "channel-0".to_string().parse().unwrap(), diff --git a/crates/astria-sequencer/src/ibc/state_ext.rs b/crates/astria-sequencer/src/ibc/state_ext.rs index 3abde83481..880f86f6ba 100644 --- a/crates/astria-sequencer/src/ibc/state_ext.rs +++ b/crates/astria-sequencer/src/ibc/state_ext.rs @@ -5,7 +5,6 @@ use anyhow::{ }; use astria_core::primitive::v1::{ asset, - Address, ADDRESS_LEN, }; use async_trait::async_trait; @@ -23,7 +22,7 @@ use tracing::{ instrument, }; -use crate::address; +use crate::accounts::AddressBytes; /// Newtype wrapper to read and write a u128 from rocksdb. #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -40,13 +39,13 @@ struct Fee(u128); const IBC_SUDO_STORAGE_KEY: &str = "ibcsudo"; const ICS20_WITHDRAWAL_BASE_FEE_STORAGE_KEY: &str = "ics20withdrawalfee"; -struct IbcRelayerKey<'a>(&'a Address); +struct IbcRelayerKey<'a, T>(&'a T); -impl<'a> std::fmt::Display for IbcRelayerKey<'a> { +impl<'a, T: AddressBytes> std::fmt::Display for IbcRelayerKey<'a, T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("ibc-relayer")?; f.write_str("/")?; - for byte in self.0.bytes() { + for byte in self.0.address_bytes() { f.write_fmt(format_args!("{byte:02x}"))?; } Ok(()) @@ -63,12 +62,12 @@ fn channel_balance_storage_key>( ) } -fn ibc_relayer_key(address: &Address) -> String { +fn ibc_relayer_key(address: &T) -> String { IbcRelayerKey(address).to_string() } #[async_trait] -pub(crate) trait StateReadExt: StateRead + address::StateReadExt { +pub(crate) trait StateReadExt: StateRead { #[instrument(skip_all)] async fn get_ibc_channel_balance( &self, @@ -91,7 +90,7 @@ pub(crate) trait StateReadExt: StateRead + address::StateReadExt { } #[instrument(skip_all)] - async fn get_ibc_sudo_address(&self) -> Result
{ + async fn get_ibc_sudo_address(&self) -> Result<[u8; ADDRESS_LEN]> { let Some(bytes) = self .get_raw(IBC_SUDO_STORAGE_KEY) .await @@ -102,16 +101,13 @@ pub(crate) trait StateReadExt: StateRead + address::StateReadExt { }; let SudoAddress(address_bytes) = SudoAddress::try_from_slice(&bytes).context("invalid ibc sudo key bytes")?; - Ok(self.try_base_prefixed(&address_bytes).await.context( - "failed constructing ibc sudo address from address bytes and address prefix stored in \ - state", - )?) + Ok(address_bytes) } #[instrument(skip_all)] - async fn is_ibc_relayer(&self, address: &Address) -> Result { + async fn is_ibc_relayer(&self, address: T) -> Result { Ok(self - .get_raw(&ibc_relayer_key(address)) + .get_raw(&ibc_relayer_key(&address)) .await .context("failed to read ibc relayer key from state")? .is_some()) @@ -151,23 +147,23 @@ pub(crate) trait StateWriteExt: StateWrite { } #[instrument(skip_all)] - fn put_ibc_sudo_address(&mut self, address: Address) -> Result<()> { + fn put_ibc_sudo_address(&mut self, address: T) -> Result<()> { self.put_raw( IBC_SUDO_STORAGE_KEY.to_string(), - borsh::to_vec(&SudoAddress(address.bytes())) + borsh::to_vec(&SudoAddress(address.address_bytes())) .context("failed to convert sudo address to vec")?, ); Ok(()) } #[instrument(skip_all)] - fn put_ibc_relayer_address(&mut self, address: &Address) { - self.put_raw(ibc_relayer_key(address), vec![]); + fn put_ibc_relayer_address(&mut self, address: T) { + self.put_raw(ibc_relayer_key(&address), vec![]); } #[instrument(skip_all)] - fn delete_ibc_relayer_address(&mut self, address: &Address) { - self.delete(ibc_relayer_key(address)); + fn delete_ibc_relayer_address(&mut self, address: T) { + self.delete(ibc_relayer_key(&address)); } #[instrument(skip_all)] @@ -234,7 +230,7 @@ mod tests { state.put_base_prefix(ASTRIA_PREFIX).unwrap(); // can write new - let mut address = astria_address(&[42u8; 20]); + let mut address = [42u8; 20]; state .put_ibc_sudo_address(address) .expect("writing sudo address should not fail"); @@ -248,7 +244,7 @@ mod tests { ); // can rewrite with new value - address = astria_address(&[41u8; 20]); + address = [41u8; 20]; state .put_ibc_sudo_address(address) .expect("writing sudo address should not fail"); @@ -274,7 +270,7 @@ mod tests { let address = astria_address(&[42u8; 20]); assert!( !state - .is_ibc_relayer(&address) + .is_ibc_relayer(address) .await .expect("calls to properly formatted addresses should not fail"), "inputted address should've returned false" @@ -291,20 +287,20 @@ mod tests { // can write let address = astria_address(&[42u8; 20]); - state.put_ibc_relayer_address(&address); + state.put_ibc_relayer_address(address); assert!( state - .is_ibc_relayer(&address) + .is_ibc_relayer(address) .await .expect("a relayer address was written and must exist inside the database"), "stored relayer address could not be verified" ); // can delete - state.delete_ibc_relayer_address(&address); + state.delete_ibc_relayer_address(address); assert!( !state - .is_ibc_relayer(&address) + .is_ibc_relayer(address) .await .expect("calls on unset addresses should not fail"), "relayer address was not deleted as was intended" @@ -321,10 +317,10 @@ mod tests { // can write let address = astria_address(&[42u8; 20]); - state.put_ibc_relayer_address(&address); + state.put_ibc_relayer_address(address); assert!( state - .is_ibc_relayer(&address) + .is_ibc_relayer(address) .await .expect("a relayer address was written and must exist inside the database"), "stored relayer address could not be verified" @@ -332,17 +328,17 @@ mod tests { // can write multiple let address_1 = astria_address(&[41u8; 20]); - state.put_ibc_relayer_address(&address_1); + state.put_ibc_relayer_address(address_1); assert!( state - .is_ibc_relayer(&address_1) + .is_ibc_relayer(address_1) .await .expect("a relayer address was written and must exist inside the database"), "additional stored relayer address could not be verified" ); assert!( state - .is_ibc_relayer(&address) + .is_ibc_relayer(address) .await .expect("a relayer address was written and must exist inside the database"), "original stored relayer address could not be verified" diff --git a/crates/astria-sequencer/src/mempool/mod.rs b/crates/astria-sequencer/src/mempool/mod.rs index 6ccf5f9e22..28dc93b1e0 100644 --- a/crates/astria-sequencer/src/mempool/mod.rs +++ b/crates/astria-sequencer/src/mempool/mod.rs @@ -78,16 +78,13 @@ impl PartialOrd for TransactionPriority { pub(crate) struct EnqueuedTransaction { tx_hash: [u8; 32], signed_tx: Arc, - address_bytes: [u8; ADDRESS_LEN], } impl EnqueuedTransaction { fn new(signed_tx: SignedTransaction) -> Self { - let address_bytes = signed_tx.verification_key().address_bytes(); Self { tx_hash: signed_tx.sha256_of_proto_encoding(), signed_tx: Arc::new(signed_tx), - address_bytes, } } @@ -118,7 +115,7 @@ impl EnqueuedTransaction { } pub(crate) fn address_bytes(&self) -> [u8; 20] { - self.address_bytes + self.signed_tx.address_bytes() } } @@ -301,11 +298,10 @@ impl Mempool { /// removes a transaction from the mempool #[instrument(skip_all)] pub(crate) async fn remove(&self, tx_hash: [u8; 32]) { - let (signed_tx, address_bytes) = dummy_signed_tx(); + let signed_tx = dummy_signed_tx(); let enqueued_tx = EnqueuedTransaction { tx_hash, signed_tx, - address_bytes, }; self.queue.write().await.remove(&enqueued_tx); } @@ -411,26 +407,22 @@ impl Mempool { /// this `signed_tx` field is ignored in the `PartialEq` and `Hash` impls of `EnqueuedTransaction` - /// only the tx hash is considered. So we create an `EnqueuedTransaction` on the fly with the /// correct tx hash and this dummy signed tx when removing from the queue. -fn dummy_signed_tx() -> (Arc, [u8; ADDRESS_LEN]) { - static TX: OnceLock<(Arc, [u8; ADDRESS_LEN])> = OnceLock::new(); - let (signed_tx, address_bytes) = TX.get_or_init(|| { +fn dummy_signed_tx() -> Arc { + static TX: OnceLock> = OnceLock::new(); + let signed_tx = TX.get_or_init(|| { let actions = vec![]; let params = TransactionParams::builder() .nonce(0) .chain_id("dummy") .build(); let signing_key = SigningKey::from([0; 32]); - let address_bytes = signing_key.verification_key().address_bytes(); let unsigned_tx = UnsignedTransaction { actions, params, }; - ( - Arc::new(unsigned_tx.into_signed(&signing_key)), - address_bytes, - ) + Arc::new(unsigned_tx.into_signed(&signing_key)) }); - (signed_tx.clone(), *address_bytes) + signed_tx.clone() } #[cfg(test)] @@ -514,17 +506,14 @@ mod test { let tx0 = EnqueuedTransaction { tx_hash: [0; 32], signed_tx: Arc::new(get_mock_tx(0)), - address_bytes: get_mock_tx(0).address_bytes(), }; let other_tx0 = EnqueuedTransaction { tx_hash: [0; 32], signed_tx: Arc::new(get_mock_tx(1)), - address_bytes: get_mock_tx(1).address_bytes(), }; let tx1 = EnqueuedTransaction { tx_hash: [1; 32], signed_tx: Arc::new(get_mock_tx(0)), - address_bytes: get_mock_tx(0).address_bytes(), }; assert!(tx0 == other_tx0); assert!(tx0 != tx1); diff --git a/crates/astria-sequencer/src/sequence/action.rs b/crates/astria-sequencer/src/sequence/action.rs index 5b52bfe760..835386f65e 100644 --- a/crates/astria-sequencer/src/sequence/action.rs +++ b/crates/astria-sequencer/src/sequence/action.rs @@ -4,49 +4,27 @@ use anyhow::{ Result, }; use astria_core::{ - primitive::v1::Address, protocol::transaction::v1alpha1::action::SequenceAction, - Protobuf, + Protobuf as _, }; -use tracing::instrument; +use cnidarium::StateWrite; use crate::{ - accounts, accounts::{ + StateReadExt as _, + StateWriteExt as _, + }, + app::ActionHandler, + assets::{ StateReadExt, StateWriteExt, }, - assets, sequence, - transaction::action_handler::ActionHandler, + transaction::StateReadExt as _, }; #[async_trait::async_trait] impl ActionHandler for SequenceAction { - async fn check_stateful( - &self, - state: &S, - from: Address, - ) -> Result<()> - where - S: accounts::StateReadExt + assets::StateReadExt + 'static, - { - ensure!( - state.is_allowed_fee_asset(&self.fee_asset).await?, - "invalid fee asset", - ); - - let curr_balance = state - .get_account_balance(from, &self.fee_asset) - .await - .context("failed getting `from` account balance for fee payment")?; - let fee = calculate_fee_from_state(&self.data, state) - .await - .context("calculated fee overflows u128")?; - ensure!(curr_balance >= fee, "insufficient funds"); - Ok(()) - } - async fn check_stateless(&self) -> Result<()> { // TODO: do we want to place a maximum on the size of the data? // https://github.com/astriaorg/astria/issues/222 @@ -57,14 +35,28 @@ impl ActionHandler for SequenceAction { Ok(()) } - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, from: Address) -> Result<()> - where - S: assets::StateWriteExt, - { - let fee = calculate_fee_from_state(&self.data, state) + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_current_source() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + + ensure!( + state + .is_allowed_fee_asset(&self.fee_asset) + .await + .context("failed accessing state to check if fee is allowed")?, + "invalid fee asset", + ); + + let curr_balance = state + .get_account_balance(from, &self.fee_asset) + .await + .context("failed getting `from` account balance for fee payment")?; + let fee = calculate_fee_from_state(&self.data, &state) .await - .context("failed to calculate fee")?; + .context("calculated fee overflows u128")?; + ensure!(curr_balance >= fee, "insufficient funds"); state .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) diff --git a/crates/astria-sequencer/src/sequencer.rs b/crates/astria-sequencer/src/sequencer.rs index 1e7ef52bc4..3468832bba 100644 --- a/crates/astria-sequencer/src/sequencer.rs +++ b/crates/astria-sequencer/src/sequencer.rs @@ -31,9 +31,7 @@ use tracing::{ }; use crate::{ - address::StateReadExt as _, app::App, - assets::StateReadExt as _, config::Config, grpc::sequencer::SequencerServer, ibc::host_interface::AstriaHost, @@ -84,21 +82,6 @@ impl Sequencer { .context("failed to load storage backing chain state")?; let snapshot = storage.latest_snapshot(); - // the native asset should be configurable only at genesis. - // the genesis state must include the native asset's base - // denomination, and it is set in storage during init_chain. - // on subsequent startups, we load the native asset from storage. - if storage.latest_version() != u64::MAX { - let _ = snapshot - .get_native_asset() - .await - .context("failed to query state for native asset")?; - let _ = snapshot - .get_base_prefix() - .await - .context("failed to query state for base prefix")?; - } - let mempool = Mempool::new(); let app = App::new(snapshot, mempool.clone(), metrics) .await diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool.rs index 6efae965e8..838578cbd4 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool.rs @@ -41,6 +41,7 @@ use tracing::{ use crate::{ accounts, address, + app::ActionHandler as _, mempool::{ Mempool as AppMempool, RemovalReason, @@ -171,7 +172,7 @@ async fn handle_check_tx VerificationKey { let signing_key = SigningKey::new(rng); signing_key.verification_key() } + +#[track_caller] +pub(crate) fn assert_anyhow_error(error: &anyhow::Error, expected: &'static str) { + let msg = error.to_string(); + assert!( + msg.contains(expected), + "error contained different message\n\texpected: {expected}\n\tfull_error: {msg}", + ); +} diff --git a/crates/astria-sequencer/src/transaction/action_handler.rs b/crates/astria-sequencer/src/transaction/action_handler.rs deleted file mode 100644 index 17cfb7345b..0000000000 --- a/crates/astria-sequencer/src/transaction/action_handler.rs +++ /dev/null @@ -1,24 +0,0 @@ -use anyhow::Result; -use astria_core::primitive::v1::Address; -use async_trait::async_trait; -use cnidarium::{ - StateRead, - StateWrite, -}; - -#[async_trait] -pub(crate) trait ActionHandler { - async fn check_stateless(&self) -> Result<()> { - Ok(()) - } - async fn check_stateful( - &self, - _state: &S, - _from: Address, - ) -> Result<()> { - Ok(()) - } - async fn execute(&self, _state: &mut S, _from: Address) -> Result<()> { - Ok(()) - } -} diff --git a/crates/astria-sequencer/src/transaction/checks.rs b/crates/astria-sequencer/src/transaction/checks.rs index e73e18acf7..77aad88236 100644 --- a/crates/astria-sequencer/src/transaction/checks.rs +++ b/crates/astria-sequencer/src/transaction/checks.rs @@ -7,7 +7,6 @@ use anyhow::{ use astria_core::{ primitive::v1::{ asset, - Address, RollupId, }, protocol::transaction::v1alpha1::{ @@ -19,21 +18,22 @@ use astria_core::{ UnsignedTransaction, }, }; +use cnidarium::StateRead; use tracing::instrument; use crate::{ - accounts, - address, + accounts::StateReadExt as _, + address::StateReadExt as _, bridge::StateReadExt as _, ibc::StateReadExt as _, state_ext::StateReadExt as _, }; #[instrument(skip_all)] -pub(crate) async fn check_nonce_mempool(tx: &SignedTransaction, state: &S) -> anyhow::Result<()> -where - S: accounts::StateReadExt + address::StateReadExt + 'static, -{ +pub(crate) async fn check_nonce_mempool( + tx: &SignedTransaction, + state: &S, +) -> anyhow::Result<()> { let signer_address = state .try_base_prefixed(&tx.verification_key().address_bytes()) .await @@ -50,13 +50,10 @@ where } #[instrument(skip_all)] -pub(crate) async fn check_chain_id_mempool( +pub(crate) async fn check_chain_id_mempool( tx: &SignedTransaction, state: &S, -) -> anyhow::Result<()> -where - S: accounts::StateReadExt + 'static, -{ +) -> anyhow::Result<()> { let chain_id = state .get_chain_id() .await @@ -66,28 +63,18 @@ where } #[instrument(skip_all)] -pub(crate) async fn check_balance_mempool( +pub(crate) async fn check_balance_mempool( tx: &SignedTransaction, state: &S, -) -> anyhow::Result<()> -where - S: accounts::StateReadExt + address::StateReadExt + 'static, -{ - let signer_address = state - .try_base_prefixed(&tx.verification_key().address_bytes()) - .await - .context( - "failed constructing the signer address from signed transaction verification and \ - prefix provided by app state", - )?; - check_balance_for_total_fees_and_transfers(tx.unsigned_transaction(), signer_address, state) +) -> anyhow::Result<()> { + check_balance_for_total_fees_and_transfers(tx, state) .await .context("failed to check balance for total fees and transfers")?; Ok(()) } #[instrument(skip_all)] -pub(crate) async fn get_fees_for_transaction( +pub(crate) async fn get_fees_for_transaction( tx: &UnsignedTransaction, state: &S, ) -> anyhow::Result> { @@ -163,20 +150,16 @@ pub(crate) async fn get_fees_for_transaction( - tx: &UnsignedTransaction, - from: Address, +pub(crate) async fn check_balance_for_total_fees_and_transfers( + tx: &SignedTransaction, state: &S, -) -> anyhow::Result<()> -where - S: accounts::StateReadExt + 'static, -{ - let mut cost_by_asset = get_fees_for_transaction(tx, state) +) -> anyhow::Result<()> { + let mut cost_by_asset = get_fees_for_transaction(tx.unsigned_transaction(), state) .await .context("failed to get fees for transaction")?; // add values transferred within the tx to the cost - for action in &tx.actions { + for action in tx.actions() { match action { Action::Transfer(act) => { cost_by_asset @@ -198,7 +181,7 @@ where } Action::BridgeUnlock(act) => { let asset = state - .get_bridge_account_ibc_asset(&from) + .get_bridge_account_ibc_asset(tx) .await .context("failed to get bridge account asset id")?; cost_by_asset @@ -222,7 +205,7 @@ where for (asset, total_fee) in cost_by_asset { let balance = state - .get_account_balance(from, asset) + .get_account_balance(tx, asset) .await .context("failed to get account balance")?; ensure!( @@ -246,15 +229,12 @@ fn transfer_update_fees( .or_insert(transfer_fee); } -async fn sequence_update_fees( +async fn sequence_update_fees( state: &S, fee_asset: &asset::Denom, fees_by_asset: &mut HashMap, data: &[u8], -) -> anyhow::Result<()> -where - S: accounts::StateReadExt, -{ +) -> anyhow::Result<()> { let fee = crate::sequence::calculate_fee_from_state(data, state) .await .context("fee for sequence action overflowed; data too large")?; @@ -315,10 +295,6 @@ fn bridge_unlock_update_fees( #[cfg(test)] mod tests { - use address::{ - StateReadExt, - StateWriteExt, - }; use astria_core::{ primitive::v1::{ asset::Denom, @@ -338,8 +314,9 @@ mod tests { use super::*; use crate::{ - accounts::{ - StateReadExt as _, + accounts::StateWriteExt as _, + address::{ + StateReadExt, StateWriteExt as _, }, app::test_utils::*, diff --git a/crates/astria-sequencer/src/transaction/mod.rs b/crates/astria-sequencer/src/transaction/mod.rs index 1426a91804..0d89e68452 100644 --- a/crates/astria-sequencer/src/transaction/mod.rs +++ b/crates/astria-sequencer/src/transaction/mod.rs @@ -1,21 +1,16 @@ -pub(crate) mod action_handler; mod checks; pub(crate) mod query; +mod state_ext; use std::fmt; -pub(crate) use action_handler::ActionHandler; use anyhow::{ ensure, Context as _, }; -use astria_core::{ - primitive::v1::Address, - protocol::transaction::v1alpha1::{ - action::Action, - SignedTransaction, - UnsignedTransaction, - }, +use astria_core::protocol::transaction::v1alpha1::{ + action::Action, + SignedTransaction, }; pub(crate) use checks::{ check_balance_for_total_fees_and_transfers, @@ -23,12 +18,26 @@ pub(crate) use checks::{ check_chain_id_mempool, check_nonce_mempool, }; -use tracing::instrument; +use cnidarium::StateWrite; +// Conditional to quiet warnings. This object is used throughout the codebase, +// but is never explicitly named - hence Rust warns about it being unused. +#[cfg(test)] +pub(crate) use state_ext::TransactionContext; +pub(crate) use state_ext::{ + StateReadExt, + StateWriteExt, +}; use crate::{ - accounts, - address, - bridge, + accounts::{ + StateReadExt as _, + StateWriteExt as _, + }, + app::ActionHandler, + bridge::{ + StateReadExt as _, + StateWriteExt as _, + }, ibc::{ host_interface::AstriaHost, StateReadExt as _, @@ -36,64 +45,6 @@ use crate::{ state_ext::StateReadExt as _, }; -#[instrument(skip_all)] -pub(crate) async fn check_stateless(tx: &SignedTransaction) -> anyhow::Result<()> { - tx.unsigned_transaction() - .check_stateless() - .await - .context("stateless check failed") -} - -#[instrument(skip_all)] -pub(crate) async fn check_stateful(tx: &SignedTransaction, state: &S) -> anyhow::Result<()> -where - S: accounts::StateReadExt + address::StateReadExt + 'static, -{ - let signer_address = state - .try_base_prefixed(&tx.verification_key().address_bytes()) - .await - .context( - "failed constructing signed address from state and verification key contained in \ - signed transaction", - )?; - tx.unsigned_transaction() - .check_stateful(state, signer_address) - .await -} - -pub(crate) async fn execute(tx: &SignedTransaction, state: &mut S) -> anyhow::Result<()> -where - S: accounts::StateReadExt - + accounts::StateWriteExt - + address::StateReadExt - + bridge::StateReadExt - + bridge::StateWriteExt, -{ - let signer_address = state - .try_base_prefixed(&tx.verification_key().address_bytes()) - .await - .context( - "failed constructing signed address from state and verification key contained in \ - signed transaction", - )?; - - if state - .get_bridge_account_rollup_id(&signer_address) - .await - .context("failed to check account rollup id")? - .is_some() - { - state.put_last_transaction_hash_for_bridge_account( - &signer_address, - &tx.sha256_of_proto_encoding(), - ); - } - - tx.unsigned_transaction() - .execute(state, signer_address) - .await -} - #[derive(Debug)] pub(crate) struct InvalidChainId(pub(crate) String); @@ -125,11 +76,11 @@ impl fmt::Display for InvalidNonce { impl std::error::Error for InvalidNonce {} #[async_trait::async_trait] -impl ActionHandler for UnsignedTransaction { +impl ActionHandler for SignedTransaction { async fn check_stateless(&self) -> anyhow::Result<()> { - ensure!(!self.actions.is_empty(), "must have at least one action"); + ensure!(!self.actions().is_empty(), "must have at least one action"); - for action in &self.actions { + for action in self.actions() { match action { Action::Transfer(act) => act .check_stateless() @@ -193,10 +144,16 @@ impl ActionHandler for UnsignedTransaction { Ok(()) } - async fn check_stateful(&self, state: &S, from: Address) -> anyhow::Result<()> - where - S: accounts::StateReadExt + 'static, - { + // allowed / FIXME: because most lines come from delegating (and error wrapping) to the + // individual actions. This could be tidied up by implementing `ActionHandler for Action` + // and letting it delegate. + #[allow(clippy::too_many_lines)] + async fn check_and_execute(&self, mut state: S) -> anyhow::Result<()> { + // Add the current signed transaction into the ephemeral state in case + // downstream actions require access to it. + // XXX: This must be deleted at the end of `check_stateful`. + state.put_current_source(self); + // Transactions must match the chain id of the node. let chain_id = state.get_chain_id().await?; ensure!( @@ -206,169 +163,116 @@ impl ActionHandler for UnsignedTransaction { // Nonce should be equal to the number of executed transactions before this tx. // First tx has nonce 0. - let curr_nonce = state.get_account_nonce(from).await?; + let curr_nonce = state + .get_account_nonce(self.address_bytes()) + .await + .context("failed to get nonce for transaction signer")?; ensure!(curr_nonce == self.nonce(), InvalidNonce(self.nonce())); // Should have enough balance to cover all actions. - check_balance_for_total_fees_and_transfers(self, from, state) + check_balance_for_total_fees_and_transfers(self, &state) .await .context("failed to check balance for total fees and transfers")?; - for action in &self.actions { + if state + .get_bridge_account_rollup_id(self) + .await + .context("failed to check account rollup id")? + .is_some() + { + state.put_last_transaction_hash_for_bridge_account( + self, + &self.sha256_of_proto_encoding(), + ); + } + + let from_nonce = state + .get_account_nonce(self) + .await + .context("failed getting nonce of transaction signer")?; + let next_nonce = from_nonce + .checked_add(1) + .context("overflow occurred incrementing stored nonce")?; + state + .put_account_nonce(self, next_nonce) + .context("failed updating `from` nonce")?; + + // FIXME: this should create one span per `check_and_execute` + for action in self.actions() { match action { Action::Transfer(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for TransferAction")?, + .context("executing transfer action failed")?, Action::Sequence(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for SequenceAction")?, + .context("executing sequence action failed")?, Action::ValidatorUpdate(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for ValidatorUpdateAction")?, + .context("executing validor update")?, Action::SudoAddressChange(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for SudoAddressChangeAction")?, + .context("executing sudo address change failed")?, Action::FeeChange(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for FeeChangeAction")?, - Action::Ibc(_) => { + .context("executing fee change failed")?, + Action::Ibc(act) => { + // FIXME: this check should be moved to check_and_execute, as it now has + // access to the the signer through state. However, what's the correct + // ibc AppHandler call to do it? Can we just update one of the trait methods + // of crate::ibc::ics20_transfer::Ics20Transfer? ensure!( state - .is_ibc_relayer(&from) + .is_ibc_relayer(self) .await .context("failed to check if address is IBC relayer")?, "only IBC sudo address can execute IBC actions" ); + let action = act + .clone() + .with_handler::(); + action + .check_and_execute(&mut state) + .await + .context("failed executing ibc action")?; } Action::Ics20Withdrawal(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for Ics20WithdrawalAction")?, + .context("failed executing ics20 withdrawal")?, Action::IbcRelayerChange(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for IbcRelayerChangeAction")?, + .context("failed executing ibc relayer change")?, Action::FeeAssetChange(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for FeeAssetChangeAction")?, + .context("failed executing fee asseet change")?, Action::InitBridgeAccount(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for InitBridgeAccountAction")?, + .context("failed executing init bridge account")?, Action::BridgeLock(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for BridgeLockAction")?, + .context("failed executing bridge lock")?, Action::BridgeUnlock(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for BridgeUnlockAction")?, + .context("failed executing bridge unlock")?, Action::BridgeSudoChange(act) => act - .check_stateful(state, from) + .check_and_execute(&mut state) .await - .context("stateful check failed for BridgeSudoChangeAction")?, - } - } - - Ok(()) - } - - #[instrument(skip_all)] - async fn execute(&self, state: &mut S, from: Address) -> anyhow::Result<()> - where - S: accounts::StateReadExt + accounts::StateWriteExt, - { - let from_nonce = state - .get_account_nonce(from) - .await - .context("failed getting `from` nonce")?; - let next_nonce = from_nonce - .checked_add(1) - .context("overflow occurred incrementing stored nonce")?; - state - .put_account_nonce(from, next_nonce) - .context("failed updating `from` nonce")?; - - for action in &self.actions { - match action { - Action::Transfer(act) => { - act.execute(state, from) - .await - .context("execution failed for TransferAction")?; - } - Action::Sequence(act) => { - act.execute(state, from) - .await - .context("execution failed for SequenceAction")?; - } - Action::ValidatorUpdate(act) => { - act.execute(state, from) - .await - .context("execution failed for ValidatorUpdateAction")?; - } - Action::SudoAddressChange(act) => { - act.execute(state, from) - .await - .context("execution failed for SudoAddressChangeAction")?; - } - Action::FeeChange(act) => { - act.execute(state, from) - .await - .context("execution failed for FeeChangeAction")?; - } - Action::Ibc(act) => { - let action = act - .clone() - .with_handler::(); - action - .check_and_execute(&mut *state) - .await - .context("execution failed for IbcAction")?; - } - Action::Ics20Withdrawal(act) => { - act.execute(state, from) - .await - .context("execution failed for Ics20WithdrawalAction")?; - } - Action::IbcRelayerChange(act) => { - act.execute(state, from) - .await - .context("execution failed for IbcRelayerChangeAction")?; - } - Action::FeeAssetChange(act) => { - act.execute(state, from) - .await - .context("execution failed for FeeAssetChangeAction")?; - } - Action::InitBridgeAccount(act) => { - act.execute(state, from) - .await - .context("execution failed for InitBridgeAccountAction")?; - } - Action::BridgeLock(act) => { - act.execute(state, from) - .await - .context("execution failed for BridgeLockAction")?; - } - Action::BridgeUnlock(act) => { - act.execute(state, from) - .await - .context("execution failed for BridgeUnlockAction")?; - } - Action::BridgeSudoChange(act) => { - act.execute(state, from) - .await - .context("execution failed for BridgeSudoChangeAction")?; - } + .context("failed executing bridge sudo change")?, } } + // XXX: Delete the current transaction data from the ephemeral state. + state.delete_current_source(); Ok(()) } } diff --git a/crates/astria-sequencer/src/transaction/state_ext.rs b/crates/astria-sequencer/src/transaction/state_ext.rs new file mode 100644 index 0000000000..4304505b76 --- /dev/null +++ b/crates/astria-sequencer/src/transaction/state_ext.rs @@ -0,0 +1,51 @@ +use astria_core::{ + primitive::v1::ADDRESS_LEN, + protocol::transaction::v1alpha1::SignedTransaction, +}; +use cnidarium::{ + StateRead, + StateWrite, +}; + +fn current_source() -> &'static str { + "transaction/current_source" +} + +#[derive(Clone)] +pub(crate) struct TransactionContext { + pub(crate) address_bytes: [u8; ADDRESS_LEN], +} + +impl TransactionContext { + pub(crate) fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + self.address_bytes + } +} + +impl From<&SignedTransaction> for TransactionContext { + fn from(value: &SignedTransaction) -> Self { + Self { + address_bytes: value.address_bytes(), + } + } +} + +pub(crate) trait StateWriteExt: StateWrite { + fn put_current_source(&mut self, transaction: impl Into) { + let context: TransactionContext = transaction.into(); + self.object_put(current_source(), context); + } + + fn delete_current_source(&mut self) { + self.object_delete(current_source()); + } +} + +pub(crate) trait StateReadExt: StateRead { + fn get_current_source(&self) -> Option { + self.object_get(current_source()) + } +} + +impl StateReadExt for T {} +impl StateWriteExt for T {} From d47a3745a7f3adf8f94d53c86bbcf8378b07be15 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Tue, 20 Aug 2024 14:28:51 +0100 Subject: [PATCH 09/18] fix(sequencer)!: take funds from bridge in ics20 withdrawals (#1344) ## Summary Fixes ics20 withdrawals by actually taking funds from the provided bridge address. ## Background Prior to this change, funds were always taken from the signer of the `astria.protocol.v1alpha1.Ics20Withdrawal` action, even if its `bridge_address` field was set. ## Changes - Uses `astria.protocol.v1alpha1.Ics20Withdrawal.bridge_address` to take funds in an ics20 withdrawal if set (and if it passes bridge checks) - Remove unnecessary state accesses to check if enough funds are present: just try to decrease them. If they fail, then the entire action fails. ## Testing All present tests have been updated to check if the correct withdrawal target is found. There should be tests for the entire `Ics20Withdrawal::check_and_execute` flow. However, adding an ibc channel and port does not seem to be currently possible because `penumbra_ibc` does not export the channel `StateWriteExt` trait. This is left to a followup. ## Breaking Changelist This is a breaking change because two different versions of sequencer will come to different conclusions on which account to take funds from. ## Related Issues Closes https://github.com/astriaorg/astria/issues/1308 --- .../astria-sequencer/src/bridge/state_ext.rs | 6 + .../src/ibc/ics20_withdrawal.rs | 258 ++++++++---------- 2 files changed, 113 insertions(+), 151 deletions(-) diff --git a/crates/astria-sequencer/src/bridge/state_ext.rs b/crates/astria-sequencer/src/bridge/state_ext.rs index 0316603c09..8e12ba6949 100644 --- a/crates/astria-sequencer/src/bridge/state_ext.rs +++ b/crates/astria-sequencer/src/bridge/state_ext.rs @@ -149,6 +149,12 @@ fn last_transaction_hash_for_bridge_account_storage_key(address #[async_trait] pub(crate) trait StateReadExt: StateRead + address::StateReadExt { + #[instrument(skip_all)] + async fn is_a_bridge_account(&self, address: T) -> anyhow::Result { + let maybe_id = self.get_bridge_account_rollup_id(address).await?; + Ok(maybe_id.is_some()) + } + #[instrument(skip_all)] async fn get_bridge_account_rollup_id( &self, diff --git a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs index 3dcbb3bfb8..477519df59 100644 --- a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs +++ b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs @@ -1,5 +1,4 @@ use anyhow::{ - anyhow, bail, ensure, Context as _, @@ -30,7 +29,6 @@ use penumbra_ibc::component::packet::{ use crate::{ accounts::{ AddressBytes, - StateReadExt as _, StateWriteExt as _, }, address::StateReadExt as _, @@ -59,30 +57,29 @@ fn withdrawal_to_unchecked_ibc_packet( ) } -async fn ics20_withdrawal_check_stateful_bridge_account( +/// Establishes the withdrawal target. +/// +/// The function returns the following addresses under the following conditions: +/// 1. `from` if `action.bridge_address` is unset and `from` is *not* a bridge account; +/// 2. `from` if `action.bridge_address` is unset and `from` is a bridge account and `from` is its +/// stored withdrawer address. +/// 3. `action.bridge_address` if `action.bridge_address` is set and a bridge account and `from` is +/// its stored withdrawer address. +async fn establish_withdrawal_target( action: &action::Ics20Withdrawal, state: &S, from: [u8; 20], -) -> Result<()> { - // bridge address checks: - // - if the sender of this transaction is not a bridge account, and the tx `bridge_address` - // field is None, don't need to do any bridge related checks as it's a normal user withdrawal. - // - if the sender of this transaction is a bridge account, and the tx `bridge_address` field is - // None, check that the withdrawer address is the same as the transaction sender. - // - if the tx `bridge_address` field is Some, check that the `bridge_address` is a valid - // bridge, and check that the withdrawer address is the same as the transaction sender. - - let is_sender_bridge = state - .get_bridge_account_rollup_id(from) - .await - .context("failed to get bridge account rollup id")? - .is_some(); - - if !is_sender_bridge && action.bridge_address.is_none() { - return Ok(()); +) -> Result<[u8; 20]> { + if action.bridge_address.is_none() + && !state + .is_a_bridge_account(from) + .await + .context("failed to get bridge account rollup id")? + { + return Ok(from); } - // if `action.bridge_address` is Some, but it's not a valid bridge account, + // if `action.bridge_address` is set, but it's not a valid bridge account, // the `get_bridge_account_withdrawer_address` step will fail. let bridge_address = action.bridge_address.map_or(from, Address::bytes); @@ -99,7 +96,7 @@ async fn ics20_withdrawal_check_stateful_bridge_account( "sender does not match bridge withdrawer address; unauthorized" ); - Ok(()) + Ok(bridge_address) } #[async_trait::async_trait] @@ -130,7 +127,9 @@ impl ActionHandler for action::Ics20Withdrawal { )?; } - ics20_withdrawal_check_stateful_bridge_account(self, &state, from).await?; + let withdrawal_target = establish_withdrawal_target(self, &state, from) + .await + .context("failed establishing which account to withdraw funds from")?; let fee = state .get_ics20_withdrawal_base_fee() @@ -145,47 +144,10 @@ impl ActionHandler for action::Ics20Withdrawal { .context("packet failed send check")? }; - let transfer_asset = self.denom(); - - let from_fee_balance = state - .get_account_balance(from, self.fee_asset()) - .await - .context("failed getting `from` account balance for fee payment")?; - - // if fee asset is same as transfer asset, ensure accounts has enough funds - // to cover both the fee and the amount transferred - if self.fee_asset().to_ibc_prefixed() == transfer_asset.to_ibc_prefixed() { - let payment_amount = self - .amount() - .checked_add(fee) - .ok_or(anyhow!("transfer amount plus fee overflowed"))?; - - ensure!( - from_fee_balance >= payment_amount, - "insufficient funds for transfer and fee payment" - ); - } else { - // otherwise, check the fee asset account has enough to cover the fees, - // and the transfer asset account has enough to cover the transfer - ensure!( - from_fee_balance >= fee, - "insufficient funds for fee payment" - ); - - let from_transfer_balance = state - .get_account_balance(from, transfer_asset) - .await - .context("failed to get account balance in transfer check")?; - ensure!( - from_transfer_balance >= self.amount(), - "insufficient funds for transfer" - ); - } - state - .decrease_balance(from, self.denom(), self.amount()) + .decrease_balance(withdrawal_target, self.denom(), self.amount()) .await - .context("failed to decrease sender balance")?; + .context("failed to decrease sender or bridge balance")?; state .decrease_balance(from, self.fee_asset(), fee) @@ -235,13 +197,14 @@ mod tests { address::StateWriteExt as _, bridge::StateWriteExt as _, test_utils::{ + assert_anyhow_error, astria_address, ASTRIA_PREFIX, }, }; #[tokio::test] - async fn check_stateful_bridge_account_not_bridge() { + async fn sender_is_withdrawal_target_if_bridge_is_not_set_and_sender_is_not_bridge() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let state = StateDelta::new(snapshot); @@ -261,13 +224,16 @@ mod tests { memo: String::new(), }; - ics20_withdrawal_check_stateful_bridge_account(&action, &state, from) - .await - .unwrap(); + assert_eq!( + establish_withdrawal_target(&action, &state, from) + .await + .unwrap(), + from + ); } #[tokio::test] - async fn check_stateful_bridge_account_sender_is_bridge_bridge_address_none_ok() { + async fn sender_is_withdrawal_target_if_bridge_is_unset_but_sender_is_bridge() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); @@ -296,97 +262,91 @@ mod tests { memo: String::new(), }; - ics20_withdrawal_check_stateful_bridge_account(&action, &state, bridge_address) - .await - .unwrap(); + assert_eq!( + establish_withdrawal_target(&action, &state, bridge_address) + .await + .unwrap(), + bridge_address, + ); } - #[tokio::test] - async fn check_stateful_bridge_account_sender_is_bridge_bridge_address_none_invalid() { - let storage = cnidarium::TempStorage::new().await.unwrap(); - let snapshot = storage.latest_snapshot(); - let mut state = StateDelta::new(snapshot); + mod bridge_sender_is_rejected_because_it_is_not_a_withdrawer { + use super::*; - state.put_base_prefix(ASTRIA_PREFIX).unwrap(); + fn bridge_address() -> [u8; 20] { + [1; 20] + } - // withdraw is *not* the bridge address, Ics20Withdrawal must be sent by the withdrawer - let bridge_address = [1u8; 20]; - state.put_bridge_account_rollup_id( - bridge_address, - &RollupId::from_unhashed_bytes("testrollupid"), - ); - state.put_bridge_account_withdrawer_address(bridge_address, astria_address(&[2u8; 20])); + fn denom() -> Denom { + "test".parse().unwrap() + } - let denom = "test".parse::().unwrap(); - let action = action::Ics20Withdrawal { - amount: 1, - denom: denom.clone(), - bridge_address: None, - destination_chain_address: "test".to_string(), - return_address: astria_address(&bridge_address), - timeout_height: Height::new(1, 1).unwrap(), - timeout_time: 1, - source_channel: "channel-0".to_string().parse().unwrap(), - fee_asset: denom.clone(), - memo: String::new(), - }; + fn action() -> action::Ics20Withdrawal { + action::Ics20Withdrawal { + amount: 1, + denom: denom(), + bridge_address: None, + destination_chain_address: "test".to_string(), + return_address: astria_address(&[1; 20]), + timeout_height: Height::new(1, 1).unwrap(), + timeout_time: 1, + source_channel: "channel-0".to_string().parse().unwrap(), + fee_asset: denom(), + memo: String::new(), + } + } - let err = ics20_withdrawal_check_stateful_bridge_account(&action, &state, bridge_address) - .await - .unwrap_err(); - assert!( - err.to_string() - .contains("sender does not match bridge withdrawer address; unauthorized") - ); - } + async fn run_test(action: action::Ics20Withdrawal) { + let storage = cnidarium::TempStorage::new().await.unwrap(); + let snapshot = storage.latest_snapshot(); + let mut state = StateDelta::new(snapshot); - #[tokio::test] - async fn check_stateful_bridge_account_bridge_address_some_ok() { - let storage = cnidarium::TempStorage::new().await.unwrap(); - let snapshot = storage.latest_snapshot(); - let mut state = StateDelta::new(snapshot); + state.put_base_prefix(ASTRIA_PREFIX).unwrap(); - state.put_base_prefix(ASTRIA_PREFIX).unwrap(); + // withdraw is *not* the bridge address, Ics20Withdrawal must be sent by the withdrawer + state.put_bridge_account_rollup_id( + bridge_address(), + &RollupId::from_unhashed_bytes("testrollupid"), + ); + state.put_bridge_account_withdrawer_address( + bridge_address(), + astria_address(&[2u8; 20]), + ); - // sender the withdrawer address, so it's ok - let bridge_address = [1u8; 20]; - let withdrawer_address = [2u8; 20]; - state.put_bridge_account_rollup_id( - bridge_address, - &RollupId::from_unhashed_bytes("testrollupid"), - ); - state.put_bridge_account_withdrawer_address(bridge_address, withdrawer_address); + assert_anyhow_error( + &establish_withdrawal_target(&action, &state, bridge_address()) + .await + .unwrap_err(), + "sender does not match bridge withdrawer address; unauthorized", + ); + } - let denom = "test".parse::().unwrap(); - let action = action::Ics20Withdrawal { - amount: 1, - denom: denom.clone(), - bridge_address: Some(astria_address(&bridge_address)), - destination_chain_address: "test".to_string(), - return_address: astria_address(&bridge_address), - timeout_height: Height::new(1, 1).unwrap(), - timeout_time: 1, - source_channel: "channel-0".to_string().parse().unwrap(), - fee_asset: denom.clone(), - memo: String::new(), - }; + #[tokio::test] + async fn bridge_unset() { + let mut action = action(); + action.bridge_address = None; + run_test(action).await; + } - ics20_withdrawal_check_stateful_bridge_account(&action, &state, withdrawer_address) - .await - .unwrap(); + #[tokio::test] + async fn bridge_set() { + let mut action = action(); + action.bridge_address = Some(astria_address(&bridge_address())); + run_test(action).await; + } } #[tokio::test] - async fn check_stateful_bridge_account_bridge_address_some_invalid_sender() { + async fn bridge_sender_is_withdrawal_target() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); state.put_base_prefix(ASTRIA_PREFIX).unwrap(); - // sender is not the withdrawer address, so must fail + // sender the withdrawer address, so it's ok let bridge_address = [1u8; 20]; - let withdrawer_address = astria_address(&[2u8; 20]); + let withdrawer_address = [2u8; 20]; state.put_bridge_account_rollup_id( bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), @@ -407,18 +367,16 @@ mod tests { memo: String::new(), }; - let err = ics20_withdrawal_check_stateful_bridge_account(&action, &state, bridge_address) - .await - .unwrap_err(); - assert!( - err.to_string() - .contains("sender does not match bridge withdrawer address; unauthorized") + assert_eq!( + establish_withdrawal_target(&action, &state, withdrawer_address) + .await + .unwrap(), + bridge_address, ); } #[tokio::test] - async fn ics20_withdrawal_check_stateful_bridge_account_bridge_address_some_invalid_bridge_account() - { + async fn bridge_is_rejected_as_withdrawal_target_because_it_has_no_withdrawer_address_set() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let state = StateDelta::new(snapshot); @@ -440,13 +398,11 @@ mod tests { memo: String::new(), }; - let err = - ics20_withdrawal_check_stateful_bridge_account(&action, &state, not_bridge_address) + assert_anyhow_error( + &establish_withdrawal_target(&action, &state, not_bridge_address) .await - .unwrap_err(); - assert!( - err.to_string() - .contains("bridge address must have a withdrawer address set") + .unwrap_err(), + "bridge address must have a withdrawer address set", ); } } From ee31f2dec88b238694076a26858300a7dc1604e4 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Tue, 20 Aug 2024 15:49:15 +0100 Subject: [PATCH 10/18] refactor(core, sequencer)!: require that bridge unlock address always be set (#1339) ## Summary Requires that `astria.protocol.v1alpha1.BridgeUnlockAction.bridge_address` is always. ## Background Before this patch, the transaction signer containing the bridge unlock action was used as the bridge address if the bridge address was not set. This means that setting the bridge address to the signer or leaving it unset resultes in the same behavior. This patch makes the bridge address a requirement so that a bridge unlock is easier to understand at a first glance (i.e. without needing to remember implementation or spec details), and because two separate actions should not lead to the same result. ## Changes - Make `astria.protocol.v1alpha1.BridgeUnlockAction.bridge_address` a required field. ## Testing Tests have been updated to reflect this change: explicitly setteing the bridge address to the signer makes those tests pass again that relied on the implicit behaviour. ## Breaking Changelist This is a protocl breaking change because newer sequencers will reject bridge unlock actions without a bridge address. ## Related Issues Closes #1338 --- crates/astria-bridge-contracts/src/lib.rs | 2 +- .../helpers/test_bridge_withdrawer.rs | 2 +- .../astria.protocol.transactions.v1alpha1.rs | 4 +- .../protocol/transaction/v1alpha1/action.rs | 74 +++++++++--------- .../astria-sequencer/src/accounts/action.rs | 13 +++- .../src/app/tests_breaking_changes.rs | 2 +- .../src/app/tests_execute_transaction.rs | 2 +- .../src/bridge/bridge_unlock_action.rs | 77 ++++--------------- .../transactions/v1alpha1/types.proto | 4 +- 9 files changed, 67 insertions(+), 113 deletions(-) diff --git a/crates/astria-bridge-contracts/src/lib.rs b/crates/astria-bridge-contracts/src/lib.rs index 9587a81e23..49b4b0161d 100644 --- a/crates/astria-bridge-contracts/src/lib.rs +++ b/crates/astria-bridge-contracts/src/lib.rs @@ -458,7 +458,7 @@ where amount, memo, fee_asset: self.fee_asset.clone(), - bridge_address: Some(self.bridge_address), + bridge_address: self.bridge_address, }; Ok(Action::BridgeUnlock(action)) diff --git a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/test_bridge_withdrawer.rs b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/test_bridge_withdrawer.rs index 2d48ca54a1..012988fef7 100644 --- a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/test_bridge_withdrawer.rs +++ b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/test_bridge_withdrawer.rs @@ -429,7 +429,7 @@ pub fn make_bridge_unlock_action(receipt: &TransactionReceipt) -> Action { }) .unwrap(), fee_asset: denom, - bridge_address: Some(default_bridge_address()), + bridge_address: default_bridge_address(), }; Action::BridgeUnlock(inner) } diff --git a/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs b/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs index 56ecdd511b..fd8f2cf0a0 100644 --- a/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.protocol.transactions.v1alpha1.rs @@ -389,9 +389,7 @@ pub struct BridgeUnlockAction { /// memo for double spend prevention #[prost(string, tag = "4")] pub memo: ::prost::alloc::string::String, - /// the address of the bridge account to transfer from, - /// if the bridge account's withdrawer address is not the same as the bridge address. - /// if unset, the signer of the transaction is used. + /// the address of the bridge account to transfer from #[prost(message, optional, tag = "5")] pub bridge_address: ::core::option::Option< super::super::super::primitive::v1::Address, diff --git a/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs b/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs index 3ba5c03050..6fb4f3d2cc 100644 --- a/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs +++ b/crates/astria-core/src/protocol/transaction/v1alpha1/action.rs @@ -1546,10 +1546,8 @@ pub struct BridgeUnlockAction { pub fee_asset: asset::Denom, // memo for double spend protection. pub memo: String, - // the address of the bridge account to transfer from, - // if the bridge account's withdrawer address is not the same as the bridge address. - // if unset, the signer of the transaction is used. - pub bridge_address: Option
, + // the address of the bridge account to transfer from. + pub bridge_address: Address, } impl Protobuf for BridgeUnlockAction { @@ -1559,11 +1557,11 @@ impl Protobuf for BridgeUnlockAction { #[must_use] fn into_raw(self) -> raw::BridgeUnlockAction { raw::BridgeUnlockAction { - to: Some(self.to.to_raw()), + to: Some(self.to.into_raw()), amount: Some(self.amount.into()), fee_asset: self.fee_asset.to_string(), memo: self.memo, - bridge_address: self.bridge_address.map(Address::into_raw), + bridge_address: Some(self.bridge_address.into_raw()), } } @@ -1574,7 +1572,7 @@ impl Protobuf for BridgeUnlockAction { amount: Some(self.amount.into()), fee_asset: self.fee_asset.to_string(), memo: self.memo.clone(), - bridge_address: self.bridge_address.as_ref().map(Address::to_raw), + bridge_address: Some(self.bridge_address.to_raw()), } } @@ -1587,29 +1585,32 @@ impl Protobuf for BridgeUnlockAction { /// - if the `amount` field is invalid /// - if the `fee_asset` field is invalid /// - if the `from` field is invalid - fn try_from_raw(proto: raw::BridgeUnlockAction) -> Result { - let Some(to) = proto.to else { - return Err(BridgeUnlockActionError::field_not_set("to")); - }; - let to = Address::try_from_raw(&to).map_err(BridgeUnlockActionError::address)?; - let amount = proto - .amount - .ok_or(BridgeUnlockActionError::missing_amount())?; - let fee_asset = proto - .fee_asset + fn try_from_raw(proto: raw::BridgeUnlockAction) -> Result { + let raw::BridgeUnlockAction { + to, + amount, + fee_asset, + memo, + bridge_address, + } = proto; + let to = to + .ok_or_else(|| BridgeUnlockActionError::field_not_set("to")) + .and_then(|to| Address::try_from_raw(&to).map_err(BridgeUnlockActionError::address))?; + let amount = amount.ok_or_else(|| BridgeUnlockActionError::field_not_set("amount"))?; + let fee_asset = fee_asset .parse() - .map_err(BridgeUnlockActionError::invalid_fee_asset)?; - let bridge_address = proto - .bridge_address - .as_ref() - .map(Address::try_from_raw) - .transpose() - .map_err(BridgeUnlockActionError::invalid_bridge_address)?; + .map_err(BridgeUnlockActionError::fee_asset)?; + + let bridge_address = bridge_address + .ok_or_else(|| BridgeUnlockActionError::field_not_set("bridge_address")) + .and_then(|to| { + Address::try_from_raw(&to).map_err(BridgeUnlockActionError::bridge_address) + })?; Ok(Self { to, amount: amount.into(), fee_asset, - memo: proto.memo, + memo, bridge_address, }) } @@ -1646,18 +1647,17 @@ impl BridgeUnlockActionError { } #[must_use] - fn missing_amount() -> Self { - Self(BridgeUnlockActionErrorKind::MissingAmount) - } - - #[must_use] - fn invalid_fee_asset(err: asset::ParseDenomError) -> Self { - Self(BridgeUnlockActionErrorKind::InvalidFeeAsset(err)) + fn fee_asset(source: asset::ParseDenomError) -> Self { + Self(BridgeUnlockActionErrorKind::FeeAsset { + source, + }) } #[must_use] - fn invalid_bridge_address(err: AddressError) -> Self { - Self(BridgeUnlockActionErrorKind::InvalidBridgeAddress(err)) + fn bridge_address(source: AddressError) -> Self { + Self(BridgeUnlockActionErrorKind::BridgeAddress { + source, + }) } } @@ -1667,12 +1667,10 @@ enum BridgeUnlockActionErrorKind { FieldNotSet(&'static str), #[error("the `to` field was invalid")] Address { source: AddressError }, - #[error("the `amount` field was not set")] - MissingAmount, #[error("the `fee_asset` field was invalid")] - InvalidFeeAsset(#[source] asset::ParseDenomError), + FeeAsset { source: asset::ParseDenomError }, #[error("the `bridge_address` field was invalid")] - InvalidBridgeAddress(#[source] AddressError), + BridgeAddress { source: AddressError }, } #[allow(clippy::module_name_repetitions)] diff --git a/crates/astria-sequencer/src/accounts/action.rs b/crates/astria-sequencer/src/accounts/action.rs index 60cbf3c150..fbf3227191 100644 --- a/crates/astria-sequencer/src/accounts/action.rs +++ b/crates/astria-sequencer/src/accounts/action.rs @@ -4,7 +4,6 @@ use anyhow::{ Result, }; use astria_core::{ - primitive::v1::ADDRESS_LEN, protocol::transaction::v1alpha1::action::TransferAction, Protobuf, }; @@ -57,11 +56,17 @@ impl ActionHandler for TransferAction { } } -pub(crate) async fn execute_transfer( +pub(crate) async fn execute_transfer( action: &TransferAction, - from: [u8; ADDRESS_LEN], + from: TAddress, mut state: S, -) -> anyhow::Result<()> { +) -> anyhow::Result<()> +where + S: StateWrite, + TAddress: AddressBytes, +{ + let from = from.address_bytes(); + let fee = state .get_transfer_base_fee() .await diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index e135b55f0b..f6065ecf0e 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -289,7 +289,7 @@ async fn app_execute_transaction_with_every_action_snapshot() { amount: 10, fee_asset: nria().into(), memo: "{}".into(), - bridge_address: None, + bridge_address: astria_address(&bridge.address_bytes()), } .into(), BridgeSudoChangeAction { diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index 6a39904305..3db427a574 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -1028,7 +1028,7 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { amount, fee_asset: nria().into(), memo: "{ \"msg\": \"lilywashere\" }".into(), - bridge_address: None, + bridge_address, }; let tx = UnsignedTransaction { diff --git a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs b/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs index ab515171bd..a49e90c311 100644 --- a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs @@ -4,12 +4,9 @@ use anyhow::{ Context as _, Result, }; -use astria_core::{ - primitive::v1::Address, - protocol::transaction::v1alpha1::action::{ - BridgeUnlockAction, - TransferAction, - }, +use astria_core::protocol::transaction::v1alpha1::action::{ + BridgeUnlockAction, + TransferAction, }; use cnidarium::StateWrite; @@ -39,25 +36,19 @@ impl ActionHandler for BridgeUnlockAction { .ensure_base_prefix(&self.to) .await .context("failed check for base prefix of destination address")?; - if let Some(bridge_address) = &self.bridge_address { - state - .ensure_base_prefix(bridge_address) - .await - .context("failed check for base prefix of bridge address")?; - } - - // the bridge address to withdraw funds from - // if unset, use the tx sender's address - let bridge_address = self.bridge_address.map_or(from, Address::bytes); + state + .ensure_base_prefix(&self.bridge_address) + .await + .context("failed check for base prefix of bridge address")?; let asset = state - .get_bridge_account_ibc_asset(bridge_address) + .get_bridge_account_ibc_asset(self.bridge_address) .await .context("failed to get bridge's asset id, must be a bridge account")?; // check that the sender of this tx is the authorized withdrawer for the bridge account let Some(withdrawer_address) = state - .get_bridge_account_withdrawer_address(bridge_address) + .get_bridge_account_withdrawer_address(self.bridge_address) .await .context("failed to get bridge account withdrawer address")? else { @@ -76,15 +67,15 @@ impl ActionHandler for BridgeUnlockAction { fee_asset: self.fee_asset.clone(), }; - check_transfer(&transfer_action, bridge_address, &state).await?; - execute_transfer(&transfer_action, bridge_address, state).await?; + check_transfer(&transfer_action, self.bridge_address, &state).await?; + execute_transfer(&transfer_action, self.bridge_address, state).await?; Ok(()) } } #[cfg(test)] -mod test { +mod tests { use astria_core::{ primitive::v1::{ asset, @@ -115,41 +106,6 @@ mod test { "test".parse().unwrap() } - #[tokio::test] - async fn fails_if_bridge_address_is_not_set_and_signer_is_not_bridge() { - let storage = cnidarium::TempStorage::new().await.unwrap(); - let snapshot = storage.latest_snapshot(); - let mut state = StateDelta::new(snapshot); - - state.put_current_source(TransactionContext { - address_bytes: [1; 20], - }); - state.put_base_prefix(ASTRIA_PREFIX).unwrap(); - - let asset = test_asset(); - let transfer_amount = 100; - - let to_address = astria_address(&[2; 20]); - - let bridge_unlock = BridgeUnlockAction { - to: to_address, - amount: transfer_amount, - fee_asset: asset, - memo: "{}".into(), - bridge_address: None, - }; - - // not a bridge account, should fail - assert!( - bridge_unlock - .check_and_execute(state) - .await - .unwrap_err() - .to_string() - .contains("failed to get bridge's asset id, must be a bridge account") - ); - } - #[tokio::test] async fn fails_if_bridge_account_has_no_withdrawer_address() { let storage = cnidarium::TempStorage::new().await.unwrap(); @@ -166,7 +122,6 @@ mod test { let to_address = astria_address(&[2; 20]); let bridge_address = astria_address(&[3; 20]); - // state.put_bridge_account_withdrawer_address(bridge_address, bridge_address); state .put_bridge_account_ibc_asset(bridge_address, &asset) .unwrap(); @@ -176,7 +131,7 @@ mod test { amount: transfer_amount, fee_asset: asset.clone(), memo: "{}".into(), - bridge_address: Some(bridge_address), + bridge_address, }; // invalid sender, doesn't match action's `from`, should fail @@ -213,7 +168,7 @@ mod test { amount: transfer_amount, fee_asset: asset, memo: "{}".into(), - bridge_address: Some(bridge_address), + bridge_address, }; // invalid sender, doesn't match action's bridge account's withdrawer, should fail @@ -255,7 +210,7 @@ mod test { amount: transfer_amount, fee_asset: asset.clone(), memo: "{}".into(), - bridge_address: None, + bridge_address, }; // not enough balance; should fail @@ -309,7 +264,7 @@ mod test { amount: transfer_amount, fee_asset: asset.clone(), memo: "{}".into(), - bridge_address: Some(bridge_address), + bridge_address, }; // not enough balance; should fail diff --git a/proto/protocolapis/astria/protocol/transactions/v1alpha1/types.proto b/proto/protocolapis/astria/protocol/transactions/v1alpha1/types.proto index 6d52627815..19069f44a5 100644 --- a/proto/protocolapis/astria/protocol/transactions/v1alpha1/types.proto +++ b/proto/protocolapis/astria/protocol/transactions/v1alpha1/types.proto @@ -211,9 +211,7 @@ message BridgeUnlockAction { string fee_asset = 3; // memo for double spend prevention string memo = 4; - // the address of the bridge account to transfer from, - // if the bridge account's withdrawer address is not the same as the bridge address. - // if unset, the signer of the transaction is used. + // the address of the bridge account to transfer from astria.primitive.v1.Address bridge_address = 5; } From 2ce5fd9812f91a0e55245c945dc197a757558f68 Mon Sep 17 00:00:00 2001 From: Lily Johnson <35852084+Lilyjjo@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:21:33 -0400 Subject: [PATCH 11/18] feat(sequencer): rewrite memool to have per-account transaction storage and maintenance (#1323) ## Summary This rewrite makes our mempool similar to Geth's/Reth's with per-account transaction storage and maintenance. This rewrite tracks 'pending' transactions that are ready to execute, and 'parked' transactions that could be ready for execution in the future on a per-account basis. This rewrite adds minor new functionality from the previous mempool and changes the mempool interface. Was co-written with @Fraser999. ## Background Prior to this rewrite, the mempool was not in a state to be improved upon. All transactions, ready to execute or not, were being stored together in a giant queue. This structure didn't allow for mempool optimizations such as performing mempool upkeep on a per account basis-- instead the only option was to loop through all transactions on the per-block maintenance upkeep. ### `mempool/mod.rs` Changes This file contains the rewrite of the mempool interface. Instead of returning a modifiable queue of all transactions to the builder in `prepare_proposal()`, now we return a copy of the transactions. Instead of removing transactions as we process them, we only remove transactions during the `finalize_block()` logic, updating accounts to reflect their new nonces. The main app-exposed mempool calls are: - `insert()`, which will only insert a transaction if it meets the mempool's transaction policies. - `remove_tx_invalid()`, to be used by `prepare_proposal()` to remove transactions that fail execution from the mempool. - `builder_queue()`, returns a copy of the ready-to-execute transactions sorted by priority order. - `run_maintenance()`, updates the stored transactions post new block height increases. The mempool implements the following transaction policies: 1. Nonce replacement is not allowed. 2. Accounts cannot have more than `PARKED_SIZE_LIMIT` transactions in their parked queues (currently set to 15). 3. There is no account limit on pending transactions. 4. Transactions will expire and can be removed after `TX_TTL` time (currently set to 4 minutes). 5. If an account has a transaction removed for being invalid or expired, all transactions for that account with a higher nonce can be removed as well. This is due to the fact that we do not execute failing transactions, so a transaction 'failing' will mean that further account nonces will not be able to execute either. ### `mempool/transactions_container.rs` This is a new file containing the data structures for the per-account transaction logic. Several new code level constructs are added: - `TimemarkedTransaction`: a `SignedTransaction` wrapper that also stores the time that the transaction was first seen by the mempool. This is used for implementing the transaction expiry policy. - The trait `TransactionsForAccount` with types `PendingTransactionsForAccount` and `ParkedTransactionForAccount`. This is used to house the per-account logic of storing pending and parked transactions, respectively. - `TransactionsContainer`: a struct with a generic over `TransactionsForAccount`. Types `PendingTransactions` and `ParkedTransactions` are defined over this as the mempool level containers for pending and parked transactions. ### Other notable changes - All transaction eviction reasons are surfaced to the `CheckTx()` CometBFT mempool logic. TBD on how to surface this to the end user. - Fixed hidden bug in the app's `finalize_block()` logic which was updating the mempool with the previous block's state. - Returned transactions for block building are ordered by nonce difference and then time first seen. The previous implementation only did not sort by time first seen. ### Mempool Overview The overall mempool structure as of this PR for Astria is shown in the below diagram: ![image](https://github.com/user-attachments/assets/daf26a2b-4083-49ec-adb5-0f4ac5959c00) ## Testing - Unit tests were written for all desired behaviors. - Ran locally with a single node. - The mempool's benchmarks were modified to work with the new interface, showing a 2x speed increase on mempools sized 100 transactions and 10x speed increase on 10,000 transactions. ## TODO still - Rewrite and add new metrics. - Implement pending/parked logic that takes into consideration and account's balance. - Rewrite benchmarks to better represent expected user behavior. ## Related Issues closes #1154, #1150, #1334 --- Cargo.lock | 12 - crates/astria-core/src/protocol/abci.rs | 8 +- crates/astria-sequencer/Cargo.toml | 1 - crates/astria-sequencer/src/app/mod.rs | 68 +- crates/astria-sequencer/src/app/test_utils.rs | 12 +- crates/astria-sequencer/src/app/tests_app.rs | 28 +- crates/astria-sequencer/src/grpc/sequencer.rs | 19 +- .../src/mempool/benchmarks.rs | 87 +- crates/astria-sequencer/src/mempool/mod.rs | 1031 +++++------ .../src/mempool/transactions_container.rs | 1584 +++++++++++++++++ .../astria-sequencer/src/service/consensus.rs | 13 +- .../astria-sequencer/src/service/mempool.rs | 61 +- 12 files changed, 2198 insertions(+), 726 deletions(-) create mode 100644 crates/astria-sequencer/src/mempool/transactions_container.rs diff --git a/Cargo.lock b/Cargo.lock index d6bea69d74..dfd9a0a4b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -802,7 +802,6 @@ dependencies = [ "penumbra-ibc", "penumbra-proto", "penumbra-tower-trace", - "priority-queue", "prost", "rand 0.8.5", "rand_chacha 0.3.1", @@ -5947,17 +5946,6 @@ dependencies = [ "uint", ] -[[package]] -name = "priority-queue" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509354d8a769e8d0b567d6821b84495c60213162761a732d68ce87c964bd347f" -dependencies = [ - "autocfg", - "equivalent", - "indexmap 2.2.6", -] - [[package]] name = "proc-macro-crate" version = "1.3.1" diff --git a/crates/astria-core/src/protocol/abci.rs b/crates/astria-core/src/protocol/abci.rs index 6f0d711e63..c70c9165dd 100644 --- a/crates/astria-core/src/protocol/abci.rs +++ b/crates/astria-core/src/protocol/abci.rs @@ -16,7 +16,9 @@ impl AbciErrorCode { pub const VALUE_NOT_FOUND: Self = Self(unsafe { NonZeroU32::new_unchecked(8) }); pub const TRANSACTION_EXPIRED: Self = Self(unsafe { NonZeroU32::new_unchecked(9) }); pub const TRANSACTION_FAILED: Self = Self(unsafe { NonZeroU32::new_unchecked(10) }); - pub const BAD_REQUEST: Self = Self(unsafe { NonZeroU32::new_unchecked(11) }); + pub const TRANSACTION_INSERTION_FAILED: Self = Self(unsafe { NonZeroU32::new_unchecked(11) }); + pub const LOWER_NONCE_INVALIDATED: Self = Self(unsafe { NonZeroU32::new_unchecked(12) }); + pub const BAD_REQUEST: Self = Self(unsafe { NonZeroU32::new_unchecked(13) }); } impl AbciErrorCode { @@ -42,6 +44,10 @@ impl AbciErrorCode { Self::TRANSACTION_FAILED => { "the transaction failed to execute in prepare_proposal()".into() } + Self::TRANSACTION_INSERTION_FAILED => { + "the transaction failed insertion into the mempool".into() + } + Self::LOWER_NONCE_INVALIDATED => "lower nonce was invalidated in mempool".into(), Self::BAD_REQUEST => "the request payload was malformed".into(), Self(other) => { format!("invalid error code {other}: should be unreachable (this is a bug)") diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index d487930dcb..3647d34af5 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -30,7 +30,6 @@ cnidarium = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.7 ] } ibc-proto = { version = "0.41.0", features = ["server"] } matchit = "0.7.2" -priority-queue = "2.0.2" tower = "0.4" tower-abci = "0.12.0" tower-actor = "0.1.0" diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 13ec8393c4..522457ecd0 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -63,9 +63,10 @@ use tracing::{ }; use crate::{ - accounts, accounts::{ + self, component::AccountsComponent, + StateReadExt, StateWriteExt as _, }, address::StateWriteExt as _, @@ -484,11 +485,21 @@ impl App { let mut included_signed_txs = Vec::new(); let mut failed_tx_count: usize = 0; let mut execution_results = Vec::new(); - let mut txs_to_readd_to_mempool = Vec::new(); + let mut excluded_txs: usize = 0; + + // get copy of transactions to execute from mempool + let current_account_nonce_getter = + |address: [u8; 20]| self.state.get_account_nonce(address); + let pending_txs = self + .mempool + .builder_queue(current_account_nonce_getter) + .await + .expect("failed to fetch pending transactions"); - while let Some((enqueued_tx, priority)) = self.mempool.pop().await { - let tx_hash_base64 = telemetry::display::base64(&enqueued_tx.tx_hash()).to_string(); - let tx = enqueued_tx.signed_tx(); + let mut unused_count = pending_txs.len(); + for (tx_hash, tx) in pending_txs { + unused_count = unused_count.saturating_sub(1); + let tx_hash_base64 = telemetry::display::base64(&tx_hash).to_string(); let bytes = tx.to_raw().encode_to_vec(); let tx_len = bytes.len(); info!(transaction_hash = %tx_hash_base64, "executing transaction"); @@ -503,7 +514,7 @@ impl App { tx_data_bytes = tx_len, "excluding remaining transactions: max cometBFT data limit reached" ); - txs_to_readd_to_mempool.push((enqueued_tx, priority)); + excluded_txs = excluded_txs.saturating_add(1); // break from loop, as the block is full break; @@ -526,7 +537,7 @@ impl App { tx_data_bytes = tx_sequence_data_bytes, "excluding transaction: max block sequenced data limit reached" ); - txs_to_readd_to_mempool.push((enqueued_tx, priority)); + excluded_txs = excluded_txs.saturating_add(1); // continue as there might be non-sequence txs that can fit continue; @@ -558,18 +569,21 @@ impl App { ); if e.downcast_ref::().is_some() { - // we re-insert the tx into the mempool if it failed to execute + // we don't remove the tx from mempool if it failed to execute // due to an invalid nonce, as it may be valid in the future. // if it's invalid due to the nonce being too low, it'll be // removed from the mempool in `update_mempool_after_finalization`. - txs_to_readd_to_mempool.push((enqueued_tx, priority)); } else { failed_tx_count = failed_tx_count.saturating_add(1); - // the transaction should be removed from the cometbft mempool + // remove the failing transaction from the mempool + // + // this will remove any transactions from the same sender + // as well, as the dependent nonces will not be able + // to execute self.mempool - .track_removal_comet_bft( - enqueued_tx.tx_hash(), + .remove_tx_invalid( + tx, RemovalReason::FailedPrepareProposal(e.to_string()), ) .await; @@ -586,15 +600,12 @@ impl App { ); } self.metrics.set_prepare_proposal_excluded_transactions( - txs_to_readd_to_mempool - .len() - .saturating_add(failed_tx_count), + excluded_txs.saturating_add(failed_tx_count), ); - self.mempool.insert_all(txs_to_readd_to_mempool).await; - let mempool_len = self.mempool.len().await; - debug!(mempool_len, "finished executing transactions from mempool"); - self.metrics.set_transactions_in_mempool_total(mempool_len); + debug!("{unused_count} leftover pending transactions"); + self.metrics + .set_transactions_in_mempool_total(self.mempool.len().await); self.execution_results = Some(execution_results); Ok((validated_txs, included_signed_txs)) @@ -805,10 +816,6 @@ impl App { // skip the first two transactions, as they are the rollup data commitments for tx in finalize_block.txs.iter().skip(2) { - // remove any included txs from the mempool - let tx_hash = Sha256::digest(tx).into(); - self.mempool.remove(tx_hash).await; - let signed_tx = signed_transaction_from_bytes(tx) .context("protocol error; only valid txs should be finalized")?; @@ -880,6 +887,10 @@ impl App { state_tx .put_sequencer_block(sequencer_block) .context("failed to write sequencer block to state")?; + + // update the priority of any txs in the mempool based on the updated app state + update_mempool_after_finalization(&mut self.mempool, &state_tx).await; + // events that occur after end_block are ignored here; // there should be none anyways. let _ = self.apply(state_tx); @@ -890,11 +901,6 @@ impl App { .await .context("failed to prepare commit")?; - // update the priority of any txs in the mempool based on the updated app state - update_mempool_after_finalization(&mut self.mempool, self.state.clone()) - .await - .context("failed to update mempool after finalization")?; - Ok(abci::response::FinalizeBlock { events: end_block.events, validator_updates: end_block.validator_updates, @@ -1121,10 +1127,10 @@ impl App { // the mempool is large. async fn update_mempool_after_finalization( mempool: &mut Mempool, - state: S, -) -> anyhow::Result<()> { + state: &S, +) { let current_account_nonce_getter = |address: [u8; 20]| state.get_account_nonce(address); - mempool.run_maintenance(current_account_nonce_getter).await + mempool.run_maintenance(current_account_nonce_getter).await; } /// relevant data of a block being executed. diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index 09b3571a0a..ebb1e9eabb 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use astria_core::{ crypto::SigningKey, primitive::v1::RollupId, @@ -138,7 +140,11 @@ pub(crate) async fn initialize_app( app } -pub(crate) fn get_mock_tx(nonce: u32) -> SignedTransaction { +pub(crate) fn mock_tx( + nonce: u32, + signer: &SigningKey, + rollup_name: &str, +) -> Arc { let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(nonce) @@ -146,7 +152,7 @@ pub(crate) fn get_mock_tx(nonce: u32) -> SignedTransaction { .build(), actions: vec![ SequenceAction { - rollup_id: RollupId::from_unhashed_bytes([0; 32]), + rollup_id: RollupId::from_unhashed_bytes(rollup_name.as_bytes()), data: Bytes::from_static(&[0x99]), fee_asset: "astria".parse().unwrap(), } @@ -154,5 +160,5 @@ pub(crate) fn get_mock_tx(nonce: u32) -> SignedTransaction { ], }; - tx.into_signed(&get_alice_signing_key()) + Arc::new(tx.into_signed(signer)) } diff --git a/crates/astria-sequencer/src/app/tests_app.rs b/crates/astria-sequencer/src/app/tests_app.rs index 4e6b6dbfe6..58ec904b17 100644 --- a/crates/astria-sequencer/src/app/tests_app.rs +++ b/crates/astria-sequencer/src/app/tests_app.rs @@ -452,7 +452,7 @@ async fn app_execution_results_match_proposal_vs_after_proposal() { // don't commit the result, now call prepare_proposal with the same data. // this will reset the app state. // this simulates executing the same block as a validator (specifically the proposer). - app.mempool.insert(signed_tx, 0).await.unwrap(); + app.mempool.insert(Arc::new(signed_tx), 0).await.unwrap(); let proposer_address = [88u8; 20].to_vec().try_into().unwrap(); let prepare_proposal = PrepareProposal { @@ -473,6 +473,12 @@ async fn app_execution_results_match_proposal_vs_after_proposal() { assert_eq!(prepare_proposal_result.txs, finalize_block.txs); assert_eq!(app.executed_proposal_hash, Hash::default()); assert_eq!(app.validator_address.unwrap(), proposer_address); + // run maintence to clear out transactions + let current_account_nonce_getter = |address: [u8; 20]| app.state.get_account_nonce(address); + app.mempool + .run_maintenance(current_account_nonce_getter) + .await; + assert_eq!(app.mempool.len().await, 0); // call process_proposal - should not re-execute anything. @@ -561,8 +567,8 @@ async fn app_prepare_proposal_cometbft_max_bytes_overflow_ok() { } .into_signed(&alice); - app.mempool.insert(tx_pass, 0).await.unwrap(); - app.mempool.insert(tx_overflow, 0).await.unwrap(); + app.mempool.insert(Arc::new(tx_pass), 0).await.unwrap(); + app.mempool.insert(Arc::new(tx_overflow), 0).await.unwrap(); // send to prepare_proposal let prepare_args = abci::request::PrepareProposal { @@ -581,6 +587,12 @@ async fn app_prepare_proposal_cometbft_max_bytes_overflow_ok() { .await .expect("too large transactions should not cause prepare proposal to fail"); + // run maintence to clear out transactions + let current_account_nonce_getter = |address: [u8; 20]| app.state.get_account_nonce(address); + app.mempool + .run_maintenance(current_account_nonce_getter) + .await; + // see only first tx made it in assert_eq!( result.txs.len(), @@ -634,8 +646,8 @@ async fn app_prepare_proposal_sequencer_max_bytes_overflow_ok() { } .into_signed(&alice); - app.mempool.insert(tx_pass, 0).await.unwrap(); - app.mempool.insert(tx_overflow, 0).await.unwrap(); + app.mempool.insert(Arc::new(tx_pass), 0).await.unwrap(); + app.mempool.insert(Arc::new(tx_overflow), 0).await.unwrap(); // send to prepare_proposal let prepare_args = abci::request::PrepareProposal { @@ -654,6 +666,12 @@ async fn app_prepare_proposal_sequencer_max_bytes_overflow_ok() { .await .expect("too large transactions should not cause prepare proposal to fail"); + // run maintence to clear out transactions + let current_account_nonce_getter = |address: [u8; 20]| app.state.get_account_nonce(address); + app.mempool + .run_maintenance(current_account_nonce_getter) + .await; + // see only first tx made it in assert_eq!( result.txs.len(), diff --git a/crates/astria-sequencer/src/grpc/sequencer.rs b/crates/astria-sequencer/src/grpc/sequencer.rs index 44c941f172..f4c89ff392 100644 --- a/crates/astria-sequencer/src/grpc/sequencer.rs +++ b/crates/astria-sequencer/src/grpc/sequencer.rs @@ -264,13 +264,20 @@ mod test { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let nonce = 99; - let tx = crate::app::test_utils::get_mock_tx(nonce); + // insert a transaction with a nonce gap + let gapped_nonce = 99; + let tx = crate::app::test_utils::mock_tx(gapped_nonce, &get_alice_signing_key(), "test"); mempool.insert(tx, 0).await.unwrap(); - // insert a tx with lower nonce also, but we should get the highest nonce - let lower_nonce = 98; - let tx = crate::app::test_utils::get_mock_tx(lower_nonce); + // insert a transaction at the current nonce + let account_nonce = 0; + let tx = crate::app::test_utils::mock_tx(account_nonce, &get_alice_signing_key(), "test"); + mempool.insert(tx, 0).await.unwrap(); + + // insert a transactions one above account nonce (not gapped) + let sequential_nonce = 1; + let tx: Arc = + crate::app::test_utils::mock_tx(sequential_nonce, &get_alice_signing_key(), "test"); mempool.insert(tx, 0).await.unwrap(); let server = Arc::new(SequencerServer::new(storage.clone(), mempool)); @@ -279,7 +286,7 @@ mod test { }; let request = Request::new(request); let response = server.get_pending_nonce(request).await.unwrap(); - assert_eq!(response.into_inner().inner, nonce); + assert_eq!(response.into_inner().inner, sequential_nonce); } #[tokio::test] diff --git a/crates/astria-sequencer/src/mempool/benchmarks.rs b/crates/astria-sequencer/src/mempool/benchmarks.rs index dacf377f00..d8dbb7e878 100644 --- a/crates/astria-sequencer/src/mempool/benchmarks.rs +++ b/crates/astria-sequencer/src/mempool/benchmarks.rs @@ -2,7 +2,10 @@ use std::{ collections::HashMap, - sync::OnceLock, + sync::{ + Arc, + OnceLock, + }, time::Duration, }; @@ -25,7 +28,6 @@ use astria_core::{ UnsignedTransaction, }, }; -use bytes::Bytes; use sha2::{ Digest as _, Sha256, @@ -57,8 +59,8 @@ fn signing_keys() -> impl Iterator { } /// Returns a static ref to a collection of `MAX_INITIAL_TXS + 1` transactions. -fn transactions() -> &'static Vec { - static TXS: OnceLock> = OnceLock::new(); +fn transactions() -> &'static Vec> { + static TXS: OnceLock>> = OnceLock::new(); TXS.get_or_init(|| { let mut nonces_and_chain_ids = HashMap::new(); signing_keys() @@ -76,14 +78,15 @@ fn transactions() -> &'static Vec { .build(); let sequence_action = SequenceAction { rollup_id: RollupId::new([1; 32]), - data: Bytes::from_static(&[2; 1000]), + data: vec![2; 1000].into(), fee_asset: Denom::IbcPrefixed(IbcPrefixed::new([3; 32])), }; - UnsignedTransaction { + let tx = UnsignedTransaction { actions: vec![Action::Sequence(sequence_action)], params, } - .into_signed(signing_key) + .into_signed(signing_key); + Arc::new(tx) }) .take(MAX_INITIAL_TXS + 1) .collect() @@ -161,8 +164,10 @@ fn init_mempool() -> Mempool { for i in 0..super::REMOVAL_CACHE_SIZE { let hash = Sha256::digest(i.to_le_bytes()).into(); mempool - .track_removal_comet_bft(hash, RemovalReason::Expired) - .await; + .comet_bft_removal_cache + .write() + .await + .add(hash, RemovalReason::Expired); } }); mempool @@ -170,7 +175,7 @@ fn init_mempool() -> Mempool { /// Returns the first transaction from the static `transactions()` not included in the initialized /// mempool, i.e. the one at index `T::size()`. -fn get_unused_tx() -> SignedTransaction { +fn get_unused_tx() -> Arc { transactions().get(T::checked_size()).unwrap().clone() } @@ -199,7 +204,9 @@ fn insert(bencher: divan::Bencher) { }); } -/// Benchmarks `Mempool::pop` on a mempool with the given number of existing entries. +/// Benchmarks `Mempool::builder_queue` on a mempool with the given number of existing entries. +/// +/// Note: this benchmark doesn't capture the nuances of dealing with parked vs pending transactions. #[divan::bench( max_time = MAX_TIME, types = [ @@ -209,22 +216,30 @@ fn insert(bencher: divan::Bencher) { mempool_with_100000_txs ] )] -fn pop(bencher: divan::Bencher) { +fn builder_queue(bencher: divan::Bencher) { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap(); + let mocked_current_account_nonce_getter = |_: [u8; 20]| async move { Ok(0_u32) }; bencher .with_inputs(|| init_mempool::()) .bench_values(move |mempool| { runtime.block_on(async { - mempool.pop().await.unwrap(); + mempool + .builder_queue(mocked_current_account_nonce_getter) + .await + .unwrap(); }); }); } -/// Benchmarks `Mempool::remove` for a single transaction on a mempool with the given number of -/// existing entries. +/// Benchmarks `Mempool::remove_tx_invalid` for a single transaction on a mempool with the given +/// number of existing entries. +/// +/// Note about this benchmark: `remove_tx_invalid()` will remove all higher nonces. To keep this +/// benchmark comparable with the previous mempool, we're removing the highest nonce. In the future +/// it would be better to have this bench remove the midpoint. #[divan::bench( max_time = MAX_TIME, types = [ @@ -234,42 +249,23 @@ fn pop(bencher: divan::Bencher) { mempool_with_100000_txs ] )] -fn remove(bencher: divan::Bencher) { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - bencher - .with_inputs(|| { - let tx_hash = transactions().first().unwrap().sha256_of_proto_encoding(); - (init_mempool::(), tx_hash) - }) - .bench_values(move |(mempool, tx_hash)| { - runtime.block_on(async { - mempool.remove(tx_hash).await; - }); - }); -} - -/// Benchmarks `Mempool::track_removal_comet_bft` for a single new transaction on a mempool with -/// the `comet_bft_removal_cache` filled. -/// -/// Note that the number of entries in the main cache is irrelevant here. -#[divan::bench(max_time = MAX_TIME)] -fn track_removal_comet_bft(bencher: divan::Bencher) { +fn remove_tx_invalid(bencher: divan::Bencher) { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap(); bencher .with_inputs(|| { - let tx_hash = transactions().first().unwrap().sha256_of_proto_encoding(); - (init_mempool::(), tx_hash) + let signed_tx = transactions() + .get(T::checked_size().saturating_sub(1)) + .cloned() + .unwrap(); + (init_mempool::(), signed_tx) }) - .bench_values(move |(mempool, tx_hash)| { + .bench_values(move |(mempool, signed_tx)| { runtime.block_on(async { mempool - .track_removal_comet_bft(tx_hash, RemovalReason::Expired) + .remove_tx_invalid(signed_tx, RemovalReason::Expired) .await; }); }); @@ -313,7 +309,7 @@ fn run_maintenance(bencher: divan::Bencher) { .build() .unwrap(); // Set the new nonce so that the entire `REMOVAL_CACHE_SIZE` entries in the - // `comet_bft_removal_cache` are replaced (assuming this test case has enough txs). + // `comet_bft_removal_cache` are filled (assuming this test case has enough txs). // allow: this is test-only code, using small values, and where the result is not critical. #[allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)] let new_nonce = (super::REMOVAL_CACHE_SIZE as u32 / u32::from(SIGNER_COUNT)) + 1; @@ -325,10 +321,7 @@ fn run_maintenance(bencher: divan::Bencher) { .with_inputs(|| init_mempool::()) .bench_values(move |mempool| { runtime.block_on(async { - mempool - .run_maintenance(current_account_nonce_getter) - .await - .unwrap(); + mempool.run_maintenance(current_account_nonce_getter).await; }); }); } diff --git a/crates/astria-sequencer/src/mempool/mod.rs b/crates/astria-sequencer/src/mempool/mod.rs index 28dc93b1e0..1106d74bef 100644 --- a/crates/astria-sequencer/src/mempool/mod.rs +++ b/crates/astria-sequencer/src/mempool/mod.rs @@ -1,154 +1,58 @@ mod benchmarks; +mod transactions_container; use std::{ - cmp::{ - self, - Ordering, - }, collections::{ HashMap, VecDeque, }, future::Future, num::NonZeroUsize, - sync::{ - Arc, - OnceLock, - }, + sync::Arc, }; -use anyhow::Context; -use astria_core::{ - crypto::SigningKey, - primitive::v1::ADDRESS_LEN, - protocol::transaction::v1alpha1::{ - SignedTransaction, - TransactionParams, - UnsignedTransaction, - }, -}; -use priority_queue::PriorityQueue; +use astria_core::protocol::transaction::v1alpha1::SignedTransaction; use tokio::{ - sync::RwLock, - time::{ - Duration, - Instant, + join, + sync::{ + RwLock, + RwLockWriteGuard, }, + time::Duration, }; use tracing::{ - debug, + error, instrument, }; +pub(crate) use transactions_container::InsertionError; +use transactions_container::{ + ParkedTransactions, + PendingTransactions, + TimemarkedTransaction, +}; -type MempoolQueue = PriorityQueue; - -/// Used to prioritize transactions in the mempool. -/// -/// The priority is calculated as the difference between the transaction nonce and the current -/// account nonce. The lower the difference, the higher the priority. -#[derive(Clone, Debug)] -pub(crate) struct TransactionPriority { - nonce_diff: u32, - time_first_seen: Instant, -} - -impl PartialEq for TransactionPriority { - fn eq(&self, other: &Self) -> bool { - self.nonce_diff == other.nonce_diff - } -} - -impl Eq for TransactionPriority {} - -impl Ord for TransactionPriority { - fn cmp(&self, other: &Self) -> Ordering { - // we want to execute the lowest nonce first, - // so lower nonce difference means higher priority - self.nonce_diff.cmp(&other.nonce_diff).reverse() - } -} - -impl PartialOrd for TransactionPriority { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Clone, Debug)] -pub(crate) struct EnqueuedTransaction { - tx_hash: [u8; 32], - signed_tx: Arc, -} - -impl EnqueuedTransaction { - fn new(signed_tx: SignedTransaction) -> Self { - Self { - tx_hash: signed_tx.sha256_of_proto_encoding(), - signed_tx: Arc::new(signed_tx), - } - } - - fn priority( - &self, - current_account_nonce: u32, - time_first_seen: Option, - ) -> anyhow::Result { - let Some(nonce_diff) = self.signed_tx.nonce().checked_sub(current_account_nonce) else { - return Err(anyhow::anyhow!( - "transaction nonce {} is less than current account nonce {current_account_nonce}", - self.signed_tx.nonce() - )); - }; - - Ok(TransactionPriority { - nonce_diff, - time_first_seen: time_first_seen.unwrap_or(Instant::now()), - }) - } - - pub(crate) fn tx_hash(&self) -> [u8; 32] { - self.tx_hash - } - - pub(crate) fn signed_tx(&self) -> Arc { - self.signed_tx.clone() - } - - pub(crate) fn address_bytes(&self) -> [u8; 20] { - self.signed_tx.address_bytes() - } -} - -/// Only consider `self.tx_hash` for equality. This is consistent with the impl for std `Hash`. -impl PartialEq for EnqueuedTransaction { - fn eq(&self, other: &Self) -> bool { - self.tx_hash == other.tx_hash - } -} - -impl Eq for EnqueuedTransaction {} - -/// Only consider `self.tx_hash` when hashing. This is consistent with the impl for equality. -impl std::hash::Hash for EnqueuedTransaction { - fn hash(&self, state: &mut H) { - self.tx_hash.hash(state); - } -} - -#[derive(Debug, Clone)] +#[derive(Debug, Eq, PartialEq, Clone)] pub(crate) enum RemovalReason { Expired, + NonceStale, + LowerNonceInvalidated, FailedPrepareProposal(String), + FailedCheckTx(String), } -const TX_TTL: Duration = Duration::from_secs(600); // 10 minutes +/// How long transactions are considered valid in the mempool. +const TX_TTL: Duration = Duration::from_secs(240); +/// Max number of parked transactions allowed per account. +const MAX_PARKED_TXS_PER_ACCOUNT: usize = 15; +/// Max number of transactions to keep in the removal cache. Should be larger than the max number of +/// transactions allowed in the cometBFT mempool. const REMOVAL_CACHE_SIZE: usize = 4096; /// `RemovalCache` is used to signal to `CometBFT` that a /// transaction can be removed from the `CometBFT` mempool. /// -/// This is useful for when a transaction fails execution or when a transaction -/// has expired in the app's mempool. +/// This is useful for when a transaction fails execution or when +/// a transaction is invalidated due to mempool removal policies. #[derive(Clone)] pub(crate) struct RemovalCache { cache: HashMap<[u8; 32], RemovalReason>, @@ -165,13 +69,14 @@ impl RemovalCache { } } - /// returns Some(RemovalReason) if transaction is cached and - /// removes the entry from the cache at the same time + /// Returns Some(RemovalReason) if the transaction is cached and + /// removes the entry from the cache if present. fn remove(&mut self, tx_hash: [u8; 32]) -> Option { self.cache.remove(&tx_hash) } - /// adds the transaction to the cache + /// Adds the transaction to the cache, will preserve the original + /// `RemovalReason` if already in the cache. fn add(&mut self, tx_hash: [u8; 32], reason: RemovalReason) { if self.cache.contains_key(&tx_hash) { return; @@ -191,590 +96,510 @@ impl RemovalCache { } } -/// [`Mempool`] is an internally-synchronized wrapper around a prioritized queue of transactions -/// awaiting execution. +/// [`Mempool`] is an account-based structure for maintaining transactions for execution. +/// +/// The transactions are split between pending and parked, where pending transactions are ready for +/// execution and parked transactions could be executable in the future. /// -/// The priority is calculated as the difference between the transaction nonce and the current -/// account nonce. The lower the difference, the higher the priority. +/// The mempool exposes the pending transactions through `builder_queue()`, which returns a copy of +/// all pending transactions sorted in the order in which they should be executed. The sort order +/// is firstly by the difference between the transaction nonce and the account's current nonce +/// (ascending), and then by time first seen (ascending). +/// +/// The mempool implements the following policies: +/// 1. Nonce replacement is not allowed. +/// 2. Accounts cannot have more than `MAX_PARKED_TXS_PER_ACCOUNT` transactions in their parked +/// queues. +/// 3. There is no account limit on pending transactions. +/// 4. Transactions will expire and can be removed after `TX_TTL` time. +/// 5. If an account has a transaction removed for being invalid or expired, all transactions for +/// that account with a higher nonce will be removed as well. This is due to the fact that we do +/// not execute failing transactions, so a transaction 'failing' will mean that further account +/// nonces will not be able to execute either. /// /// Future extensions to this mempool can include: /// - maximum mempool size -/// - fee-based ordering -/// - transaction expiration +/// - account balance aware pending queue #[derive(Clone)] pub(crate) struct Mempool { - queue: Arc>, + pending: Arc>, + parked: Arc>>, comet_bft_removal_cache: Arc>, - tx_ttl: Duration, } impl Mempool { #[must_use] pub(crate) fn new() -> Self { Self { - queue: Arc::new(RwLock::new(MempoolQueue::new())), + pending: Arc::new(RwLock::new(PendingTransactions::new(TX_TTL))), + parked: Arc::new(RwLock::new(ParkedTransactions::new(TX_TTL))), comet_bft_removal_cache: Arc::new(RwLock::new(RemovalCache::new( NonZeroUsize::try_from(REMOVAL_CACHE_SIZE) .expect("Removal cache cannot be zero sized"), ))), - tx_ttl: TX_TTL, } } - /// returns the number of transactions in the mempool + /// Returns the number of transactions in the mempool. #[must_use] #[instrument(skip_all)] pub(crate) async fn len(&self) -> usize { - self.queue.read().await.len() + #[rustfmt::skip] + let (pending_len, parked_len) = join!( + async { self.pending.read().await.len() }, + async { self.parked.read().await.len() } + ); + pending_len.saturating_add(parked_len) } - /// inserts a transaction into the mempool - /// - /// note: the oldest timestamp from found priorities is maintained. + /// Inserts a transaction into the mempool and does not allow for transaction replacement. + /// Will return the reason for insertion failure if failure occurs. #[instrument(skip_all)] pub(crate) async fn insert( &self, - tx: SignedTransaction, + tx: Arc, current_account_nonce: u32, - ) -> anyhow::Result<()> { - let enqueued_tx = EnqueuedTransaction::new(tx); - let fresh_priority = enqueued_tx.priority(current_account_nonce, None)?; - Self::update_or_insert(&mut *self.queue.write().await, enqueued_tx, &fresh_priority); - - Ok(()) + ) -> anyhow::Result<(), InsertionError> { + let timemarked_tx = TimemarkedTransaction::new(tx); + + let (mut pending, mut parked) = self.acquire_both_locks().await; + + // try insert into pending (will fail if nonce is gapped or already present) + match pending.add(timemarked_tx.clone(), current_account_nonce) { + Err(InsertionError::NonceGap) => { + // Release the lock asap. + drop(pending); + // try to add to parked queue + parked.add(timemarked_tx, current_account_nonce) + } + error @ Err( + InsertionError::AlreadyPresent + | InsertionError::NonceTooLow + | InsertionError::NonceTaken + | InsertionError::AccountSizeLimit, + ) => error, + Ok(()) => { + // check parked for txs able to be promoted + let to_promote = parked.pop_front_account( + timemarked_tx.address(), + timemarked_tx + .nonce() + .checked_add(1) + .expect("failed to increment nonce in promotion"), + ); + // Release the lock asap. + drop(parked); + for ttx in to_promote { + if let Err(error) = pending.add(ttx, current_account_nonce) { + error!( + current_account_nonce, + "failed to promote transaction during insertion: {error:#}" + ); + } + } + Ok(()) + } + } } - /// inserts all the given transactions into the mempool - /// - /// note: the oldest timestamp from found priorities for an `EnqueuedTransaction` is maintained. - #[instrument(skip_all)] - pub(crate) async fn insert_all(&self, txs: Vec<(EnqueuedTransaction, TransactionPriority)>) { - let mut queue = self.queue.write().await; - - for (enqueued_tx, priority) in txs { - Self::update_or_insert(&mut queue, enqueued_tx, &priority); - } + /// Returns a copy of all transactions and their hashes ready for execution, sorted first by the + /// difference between a transaction and the account's current nonce and then by the time that + /// the transaction was first seen by the appside mempool. + pub(crate) async fn builder_queue( + &self, + current_account_nonce_getter: F, + ) -> anyhow::Result)>> + where + F: Fn([u8; 20]) -> O, + O: Future>, + { + self.pending + .read() + .await + .builder_queue(current_account_nonce_getter) + .await } - /// inserts or updates the transaction in a timestamp preserving manner + /// Removes the target transaction and all transactions for associated account with higher + /// nonces. /// - /// note: updates the priority using the `possible_priority`'s nonce diff. - fn update_or_insert( - queue: &mut PriorityQueue, - enqueued_tx: EnqueuedTransaction, - possible_priority: &TransactionPriority, + /// This function should only be used to remove invalid/failing transactions and not executed + /// transactions. Executed transactions will be removed in the `run_maintenance()` function. + pub(crate) async fn remove_tx_invalid( + &self, + signed_tx: Arc, + reason: RemovalReason, ) { - let oldest_timestamp = queue.get_priority(&enqueued_tx).map_or( - possible_priority.time_first_seen, - |prev_priority| { - possible_priority - .time_first_seen - .min(prev_priority.time_first_seen) - }, - ); - - let priority = TransactionPriority { - nonce_diff: possible_priority.nonce_diff, - time_first_seen: oldest_timestamp, + let tx_hash = signed_tx.sha256_of_proto_encoding(); + let address = signed_tx.verification_key().address_bytes(); + + // Try to remove from pending. + let removed_txs = match self.pending.write().await.remove(signed_tx) { + Ok(mut removed_txs) => { + // Remove all of parked. + removed_txs.append(&mut self.parked.write().await.clear_account(&address)); + removed_txs + } + Err(signed_tx) => { + // Not found in pending, try to remove from parked and if not found, just return. + match self.parked.write().await.remove(signed_tx) { + Ok(removed_txs) => removed_txs, + Err(_) => return, + } + } }; - let tx_hash = enqueued_tx.tx_hash; - if queue.push(enqueued_tx, priority).is_none() { - // emit if didn't already exist - tracing::trace!( - tx_hash = %telemetry::display::hex(&tx_hash), - "inserted transaction into mempool" - ); + // Add all removed to removal cache for cometbft. + let mut removal_cache = self.comet_bft_removal_cache.write().await; + // Add the original tx first, since it will also be listed in `removed_txs`. The second + // attempt to add it inside the loop below will be a no-op. + removal_cache.add(tx_hash, reason); + for removed_tx in removed_txs { + removal_cache.add(removed_tx, RemovalReason::LowerNonceInvalidated); } } - /// pops the transaction with the highest priority from the mempool - #[must_use] - #[instrument(skip_all)] - pub(crate) async fn pop(&self) -> Option<(EnqueuedTransaction, TransactionPriority)> { - self.queue.write().await.pop() - } - - /// removes a transaction from the mempool - #[instrument(skip_all)] - pub(crate) async fn remove(&self, tx_hash: [u8; 32]) { - let signed_tx = dummy_signed_tx(); - let enqueued_tx = EnqueuedTransaction { - tx_hash, - signed_tx, - }; - self.queue.write().await.remove(&enqueued_tx); - } - - /// signal that the transaction should be removed from the `CometBFT` mempool - #[instrument(skip_all)] - pub(crate) async fn track_removal_comet_bft(&self, tx_hash: [u8; 32], reason: RemovalReason) { - self.comet_bft_removal_cache - .write() - .await - .add(tx_hash, reason); - } - - /// checks if a transaction was flagged to be removed from the `CometBFT` mempool - /// and removes entry + /// Checks if a transaction was flagged to be removed from the `CometBFT` mempool. Will + /// remove the transaction from the cache if it is present. #[instrument(skip_all)] pub(crate) async fn check_removed_comet_bft(&self, tx_hash: [u8; 32]) -> Option { self.comet_bft_removal_cache.write().await.remove(tx_hash) } - /// Updates the priority of the txs in the mempool based on the current state, and removes any - /// that are now invalid. + /// Updates stored transactions to reflect current blockchain state. Will remove transactions + /// that have stale nonces and will remove transaction that are expired. /// - /// *NOTE*: this function locks the mempool until every tx has been checked. This could - /// potentially stall consensus from moving to the next round if the mempool is large. + /// All removed transactions are added to the CometBFT removal cache to aid with CometBFT + /// mempool maintenance. #[instrument(skip_all)] - pub(crate) async fn run_maintenance( - &self, - current_account_nonce_getter: F, - ) -> anyhow::Result<()> + pub(crate) async fn run_maintenance(&self, current_account_nonce_getter: F) where - F: Fn([u8; ADDRESS_LEN]) -> O, + F: Fn([u8; 20]) -> O, O: Future>, { - let mut txs_to_remove = Vec::new(); - let mut current_account_nonces = HashMap::new(); - - let mut queue = self.queue.write().await; - let mut removal_cache = self.comet_bft_removal_cache.write().await; - for (enqueued_tx, priority) in queue.iter_mut() { - let address_bytes = enqueued_tx.address_bytes(); - - // check if the transactions has expired - if priority.time_first_seen.elapsed() > self.tx_ttl { - // tx has expired, set to remove and add to removal cache - txs_to_remove.push(enqueued_tx.clone()); - removal_cache.add(enqueued_tx.tx_hash, RemovalReason::Expired); - continue; + let (mut pending, mut parked) = self.acquire_both_locks().await; + + // clean accounts of stale and expired transactions + let mut removed_txs = pending.clean_accounts(¤t_account_nonce_getter).await; + removed_txs.append(&mut parked.clean_accounts(¤t_account_nonce_getter).await); + + // run promotion logic in case transactions not in this mempool advanced account state + let to_promote = parked.find_promotables(¤t_account_nonce_getter).await; + // Release the lock asap. + drop(parked); + for (ttx, current_account_nonce) in to_promote { + if let Err(error) = pending.add(ttx, current_account_nonce) { + error!( + current_account_nonce, + "failed to promote transaction during maintenance: {error:#}" + ); } - - // Try to get the current account nonce from the ones already retrieved. - let current_account_nonce = if let Some(nonce) = - current_account_nonces.get(&address_bytes) - { - *nonce - } else { - // Fall back to getting via the getter and adding it to the local temp collection. - let nonce = current_account_nonce_getter(enqueued_tx.address_bytes()) - .await - .context("failed to fetch account nonce")?; - current_account_nonces.insert(address_bytes, nonce); - nonce - }; - match enqueued_tx.priority(current_account_nonce, Some(priority.time_first_seen)) { - Ok(new_priority) => *priority = new_priority, - Err(e) => { - debug!( - transaction_hash = %telemetry::display::base64(&enqueued_tx.tx_hash), - error = AsRef::::as_ref(&e), - "account nonce is now greater than tx nonce; dropping tx from mempool", - ); - txs_to_remove.push(enqueued_tx.clone()); - } - }; } - for enqueued_tx in txs_to_remove { - queue.remove(&enqueued_tx); + // add to removal cache for cometbft + let mut removal_cache = self.comet_bft_removal_cache.write().await; + for (tx_hash, reason) in removed_txs { + removal_cache.add(tx_hash, reason); } - - Ok(()) } - /// returns the pending nonce for the given address, - /// if it exists in the mempool. + /// Returns the highest pending nonce for the given address if it exists in the mempool. Note: + /// does not take into account gapped nonces in the parked queue. For example, if the + /// pending queue for an account has nonces [0,1] and the parked queue has [3], [1] will be + /// returned. #[instrument(skip_all)] - pub(crate) async fn pending_nonce(&self, address: [u8; ADDRESS_LEN]) -> Option { - let inner = self.queue.read().await; - let mut nonce = None; - for (tx, _priority) in inner.iter() { - if tx.address_bytes() == address { - nonce = Some(cmp::max(nonce.unwrap_or_default(), tx.signed_tx.nonce())); - } - } - nonce + pub(crate) async fn pending_nonce(&self, address: [u8; 20]) -> Option { + self.pending.read().await.pending_nonce(address) } -} -/// This exists to provide a `SignedTransaction` for the purposes of removing an entry from the -/// queue where we only have the tx hash available. -/// -/// The queue is indexed by `EnqueuedTransaction` which internally needs a `SignedTransaction`, but -/// this `signed_tx` field is ignored in the `PartialEq` and `Hash` impls of `EnqueuedTransaction` - -/// only the tx hash is considered. So we create an `EnqueuedTransaction` on the fly with the -/// correct tx hash and this dummy signed tx when removing from the queue. -fn dummy_signed_tx() -> Arc { - static TX: OnceLock> = OnceLock::new(); - let signed_tx = TX.get_or_init(|| { - let actions = vec![]; - let params = TransactionParams::builder() - .nonce(0) - .chain_id("dummy") - .build(); - let signing_key = SigningKey::from([0; 32]); - let unsigned_tx = UnsignedTransaction { - actions, - params, - }; - Arc::new(unsigned_tx.into_signed(&signing_key)) - }); - signed_tx.clone() + async fn acquire_both_locks( + &self, + ) -> ( + RwLockWriteGuard, + RwLockWriteGuard>, + ) { + let pending = self.pending.write().await; + let parked = self.parked.write().await; + (pending, parked) + } } #[cfg(test)] mod test { - use std::{ - hash::{ - Hash, - Hasher, - }, - time::Duration, - }; + use astria_core::crypto::SigningKey; use super::*; - use crate::app::test_utils::get_mock_tx; - - #[test] - fn transaction_priority_should_error_if_invalid() { - let enqueued_tx = EnqueuedTransaction::new(get_mock_tx(0)); - let priority = enqueued_tx.priority(1, None); - assert!( - priority - .unwrap_err() - .to_string() - .contains("less than current account nonce") - ); - } - - // From https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html - #[test] - // allow: we want explicit assertions here to match the documented expected behavior. - #[allow(clippy::nonminimal_bool)] - fn transaction_priority_comparisons_should_be_consistent() { - let high = TransactionPriority { - nonce_diff: 0, - time_first_seen: Instant::now(), - }; - let low = TransactionPriority { - nonce_diff: 1, - time_first_seen: Instant::now(), - }; - - assert!(high.partial_cmp(&high) == Some(Ordering::Equal)); - assert!(high.partial_cmp(&low) == Some(Ordering::Greater)); - assert!(low.partial_cmp(&high) == Some(Ordering::Less)); - - // 1. a == b if and only if partial_cmp(a, b) == Some(Equal) - assert!(high == high); // Some(Equal) - assert!(!(high == low)); // Some(Greater) - assert!(!(low == high)); // Some(Less) - - // 2. a < b if and only if partial_cmp(a, b) == Some(Less) - assert!(low < high); // Some(Less) - assert!(!(high < high)); // Some(Equal) - assert!(!(high < low)); // Some(Greater) - - // 3. a > b if and only if partial_cmp(a, b) == Some(Greater) - assert!(high > low); // Some(Greater) - assert!(!(high > high)); // Some(Equal) - assert!(!(low > high)); // Some(Less) - - // 4. a <= b if and only if a < b || a == b - assert!(low <= high); // a < b - assert!(high <= high); // a == b - assert!(!(high <= low)); // a > b - - // 5. a >= b if and only if a > b || a == b - assert!(high >= low); // a > b - assert!(high >= high); // a == b - assert!(!(low >= high)); // a < b - - // 6. a != b if and only if !(a == b) - assert!(high != low); // asserted !(high == low) above - assert!(low != high); // asserted !(low == high) above - assert!(!(high != high)); // asserted high == high above - } - - #[test] - // From https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq - fn enqueued_tx_hash_and_eq_should_be_consistent() { - // Check enqueued txs compare equal if and only if their tx hashes are equal. - let tx0 = EnqueuedTransaction { - tx_hash: [0; 32], - signed_tx: Arc::new(get_mock_tx(0)), - }; - let other_tx0 = EnqueuedTransaction { - tx_hash: [0; 32], - signed_tx: Arc::new(get_mock_tx(1)), - }; - let tx1 = EnqueuedTransaction { - tx_hash: [1; 32], - signed_tx: Arc::new(get_mock_tx(0)), - }; - assert!(tx0 == other_tx0); - assert!(tx0 != tx1); - - // Check enqueued txs' std hashes compare equal if and only if their tx hashes are equal. - let std_hash = |enqueued_tx: &EnqueuedTransaction| -> u64 { - let mut hasher = std::hash::DefaultHasher::new(); - enqueued_tx.hash(&mut hasher); - hasher.finish() - }; - assert!(std_hash(&tx0) == std_hash(&other_tx0)); - assert!(std_hash(&tx0) != std_hash(&tx1)); - } + use crate::app::test_utils::mock_tx; #[tokio::test] - async fn should_insert_and_pop() { + async fn insert() { let mempool = Mempool::new(); + let signing_key = SigningKey::from([1; 32]); - // Priority 0 (highest priority). - let tx0 = get_mock_tx(0); - mempool.insert(tx0.clone(), 0).await.unwrap(); - - // Priority 1. - let tx1 = get_mock_tx(1); - mempool.insert(tx1.clone(), 0).await.unwrap(); + // sign and insert nonce 1 + let tx1 = mock_tx(1, &signing_key, "test"); + assert!( + mempool.insert(tx1.clone(), 0).await.is_ok(), + "should be able to insert nonce 1 transaction into mempool" + ); - assert_eq!(mempool.len().await, 2); + // try to insert again + assert_eq!( + mempool.insert(tx1.clone(), 0).await.unwrap_err(), + InsertionError::AlreadyPresent, + "already present" + ); - // Should pop priority 0 first. - let (tx, priority) = mempool.pop().await.unwrap(); + // try to replace nonce + let tx1_replacement = mock_tx(1, &signing_key, "test_0"); assert_eq!( - tx.signed_tx.sha256_of_proto_encoding(), - tx0.sha256_of_proto_encoding() + mempool + .insert(tx1_replacement.clone(), 0) + .await + .unwrap_err(), + InsertionError::NonceTaken, + "nonce replace not allowed" ); - assert_eq!(priority.nonce_diff, 0); - assert_eq!(mempool.len().await, 1); - // Should pop priority 1 second. - let (tx, priority) = mempool.pop().await.unwrap(); + // add too low nonce + let tx0 = mock_tx(0, &signing_key, "test"); assert_eq!( - tx.signed_tx.sha256_of_proto_encoding(), - tx1.sha256_of_proto_encoding() + mempool.insert(tx0.clone(), 1).await.unwrap_err(), + InsertionError::NonceTooLow, + "nonce too low" ); - assert_eq!(priority.nonce_diff, 1); - assert_eq!(mempool.len().await, 0); } #[tokio::test] - async fn should_remove() { - let mempool = Mempool::new(); - let tx_count = 5_usize; - - let current_account_nonce = 0; - let txs: Vec<_> = (0..tx_count) - .map(|index| { - let enqueued_tx = - EnqueuedTransaction::new(get_mock_tx(u32::try_from(index).unwrap())); - let priority = enqueued_tx.priority(current_account_nonce, None).unwrap(); - (enqueued_tx, priority) - }) - .collect(); - mempool.insert_all(txs.clone()).await; - assert_eq!(mempool.len().await, tx_count); - - // Remove the last tx. - let last_tx_hash = txs.last().unwrap().0.tx_hash; - mempool.remove(last_tx_hash).await; - let mut expected_remaining_count = tx_count.checked_sub(1).unwrap(); - assert_eq!(mempool.len().await, expected_remaining_count); - - // Removing it again should have no effect. - mempool.remove(last_tx_hash).await; - assert_eq!(mempool.len().await, expected_remaining_count); - - // Remove the first tx. - mempool.remove(txs.first().unwrap().0.tx_hash).await; - expected_remaining_count = expected_remaining_count.checked_sub(1).unwrap(); - assert_eq!(mempool.len().await, expected_remaining_count); - - // Check the next tx popped is the second priority. - let (tx, priority) = mempool.pop().await.unwrap(); - assert_eq!(tx.tx_hash, txs[1].0.tx_hash()); - assert_eq!(priority.nonce_diff, 1); - } + async fn single_account_flow_extensive() { + // This test tries to hit the more complex edges of the mempool with a single account. + // The test adds the nonces [1,2,0,4], creates a builder queue with the account + // nonce at 1, and then cleans the pool to nonce 4. This tests some of the + // odder edge cases that can be hit if a node goes offline or fails to see + // some transactions that other nodes include into their proposed blocks. - #[tokio::test] - async fn should_update_priorities() { let mempool = Mempool::new(); + let signing_key = SigningKey::from([1; 32]); + let signing_address = signing_key.verification_key().address_bytes(); - // Insert txs signed by alice with nonces 0 and 1. - mempool.insert(get_mock_tx(0), 0).await.unwrap(); - mempool.insert(get_mock_tx(1), 0).await.unwrap(); - - // Insert txs from a different signer with nonces 100 and 102. - let other = SigningKey::from([1; 32]); - let other_mock_tx = |nonce: u32| -> SignedTransaction { - let actions = get_mock_tx(0).actions().to_vec(); - UnsignedTransaction { - params: TransactionParams::builder() - .nonce(nonce) - .chain_id("test") - .build(), - actions, - } - .into_signed(&other) - }; - mempool.insert(other_mock_tx(100), 0).await.unwrap(); - mempool.insert(other_mock_tx(102), 0).await.unwrap(); + // add nonces in odd order to trigger insertion promotion logic + // sign and insert nonce 1 + let tx1 = mock_tx(1, &signing_key, "test"); + assert!( + mempool.insert(tx1.clone(), 0).await.is_ok(), + "should be able to insert nonce 1 transaction into mempool" + ); - assert_eq!(mempool.len().await, 4); + // sign and insert nonce 2 + let tx2 = mock_tx(2, &signing_key, "test"); + assert!( + mempool.insert(tx2.clone(), 0).await.is_ok(), + "should be able to insert nonce 2 transaction into mempool" + ); - let alice = crate::app::test_utils::get_alice_signing_key(); + // sign and insert nonce 0 + let tx0 = mock_tx(0, &signing_key, "test"); + assert!( + mempool.insert(tx0.clone(), 0).await.is_ok(), + "should be able to insert nonce 0 transaction into mempool" + ); - // Create a getter fn which will returns 1 for alice's current account nonce, and 101 for - // the other signer's. - let current_account_nonce_getter = |address: [u8; ADDRESS_LEN]| { - let alice = alice.clone(); - let other = other.clone(); - async move { - if address == alice.address_bytes() { - return Ok(1); - } - if address == other.address_bytes() { - return Ok(101); - } - Err(anyhow::anyhow!("invalid address")) + // sign and insert nonce 4 + let tx4 = mock_tx(4, &signing_key, "test"); + assert!( + mempool.insert(tx4.clone(), 0).await.is_ok(), + "should be able to insert nonce 4 transaction into mempool" + ); + + // assert size + assert_eq!(mempool.len().await, 4); + + // mock nonce getter with nonce at 1 + let current_account_nonce_getter = |address: [u8; 20]| async move { + if address == signing_address { + return Ok(1); } + Err(anyhow::anyhow!("invalid address")) }; - // Update the priorities. Alice's first tx (with nonce 0) and other's first (with nonce - // 100) should both get purged. - mempool - .run_maintenance(current_account_nonce_getter) + // grab building queue, should return transactions [1,2] since [0] was below and [4] is + // gapped + let builder_queue = mempool + .builder_queue(current_account_nonce_getter) .await - .unwrap(); + .expect("failed to get builder queue"); + + // see contains first two transactions that should be pending + assert_eq!(builder_queue[0].1.nonce(), 1, "nonce should be one"); + assert_eq!(builder_queue[1].1.nonce(), 2, "nonce should be two"); + + // see mempool's transactions just cloned, not consumed + assert_eq!(mempool.len().await, 4); - assert_eq!(mempool.len().await, 2); + // run maintenance with simulated nonce to remove the nonces 0,1,2 and promote 4 from parked + // to pending + let current_account_nonce_getter = |address: [u8; 20]| async move { + if address == signing_address { + return Ok(4); + } + Err(anyhow::anyhow!("invalid address")) + }; + mempool.run_maintenance(current_account_nonce_getter).await; - // Alice's remaining tx should be the highest priority (nonce diff of 1 - 1 == 0). - let (tx, priority) = mempool.pop().await.unwrap(); - assert_eq!(tx.signed_tx.nonce(), 1); - assert_eq!(*tx.signed_tx.verification_key(), alice.verification_key()); - assert_eq!(priority.nonce_diff, 0); + // assert mempool at 1 + assert_eq!(mempool.len().await, 1); - // Other's remaining tx should be the highest priority (nonce diff of 102 - 101 == 1). - let (tx, priority) = mempool.pop().await.unwrap(); - assert_eq!(tx.signed_tx.nonce(), 102); - assert_eq!(*tx.signed_tx.verification_key(), other.verification_key()); - assert_eq!(priority.nonce_diff, 1); + // see transaction [4] properly promoted + let mut builder_queue = mempool + .builder_queue(current_account_nonce_getter) + .await + .expect("failed to get builder queue"); + let (_, returned_tx) = builder_queue.pop().expect("should return last transaction"); + assert_eq!(returned_tx.nonce(), 4, "nonce should be four"); } - #[tokio::test(start_paused = true)] - async fn transaction_timestamp_not_overwritten_insert() { + #[tokio::test] + async fn remove_invalid() { let mempool = Mempool::new(); + let signing_key = SigningKey::from([1; 32]); - let insert_time = Instant::now(); - let tx = get_mock_tx(0); - mempool.insert(tx.clone(), 0).await.unwrap(); - - // pass time - tokio::time::advance(Duration::from_secs(60)).await; - assert_eq!( - insert_time.elapsed(), - Duration::from_secs(60), - "time should have advanced" + // sign and insert nonces 0,1 and 3,4,5 + let tx0 = mock_tx(0, &signing_key, "test"); + assert!( + mempool.insert(tx0.clone(), 0).await.is_ok(), + "should be able to insert nonce 0 transaction into mempool" ); - - // re-insert - mempool.insert(tx, 0).await.unwrap(); - - // check that the timestamp was not overwritten in insert() - let (_, tx_priority) = mempool - .pop() - .await - .expect("transaction was added, should exist"); - assert_eq!( - tx_priority.time_first_seen.duration_since(insert_time), - Duration::from_secs(0), - "Tracked time should be the same" + let tx1 = mock_tx(1, &signing_key, "test"); + assert!( + mempool.insert(tx1.clone(), 0).await.is_ok(), + "should be able to insert nonce 1 transaction into mempool" ); - } + let tx3 = mock_tx(3, &signing_key, "test"); + assert!( + mempool.insert(tx3.clone(), 0).await.is_ok(), + "should be able to insert nonce 3 transaction into mempool" + ); + let tx4 = mock_tx(4, &signing_key, "test"); + assert!( + mempool.insert(tx4.clone(), 0).await.is_ok(), + "should be able to insert nonce 4 transaction into mempool" + ); + let tx5 = mock_tx(5, &signing_key, "test"); + assert!( + mempool.insert(tx5.clone(), 0).await.is_ok(), + "should be able to insert nonce 5 transaction into mempool" + ); + assert_eq!(mempool.len().await, 5); - #[tokio::test(start_paused = true)] - async fn transaction_timestamp_not_overwritten_insert_all() { - let mempool = Mempool::new(); + let removal_reason = RemovalReason::FailedPrepareProposal("reason".to_string()); - let insert_time = Instant::now(); - let tx = get_mock_tx(0); - mempool.insert(tx.clone(), 0).await.unwrap(); + // remove 4, should remove 4 and 5 + mempool + .remove_tx_invalid(tx4.clone(), removal_reason.clone()) + .await; + assert_eq!(mempool.len().await, 3); - // pass time - tokio::time::advance(Duration::from_secs(60)).await; - assert_eq!( - insert_time.elapsed(), - Duration::from_secs(60), - "time should have advanced" - ); + // remove 4 again is also ok + mempool + .remove_tx_invalid( + tx4.clone(), + RemovalReason::NonceStale, // shouldn't be inserted into removal cache + ) + .await; + assert_eq!(mempool.len().await, 3); + + // remove 1, should remove 1 and 3 + mempool + .remove_tx_invalid(tx1.clone(), removal_reason.clone()) + .await; + assert_eq!(mempool.len().await, 1); - // re-insert with new priority with higher timestamp - let enqueued_tx = EnqueuedTransaction::new(tx); - let new_priority = TransactionPriority { - nonce_diff: 0, - time_first_seen: Instant::now(), - }; - mempool.insert_all(vec![(enqueued_tx, new_priority)]).await; + // remove 0 + mempool + .remove_tx_invalid(tx0.clone(), removal_reason.clone()) + .await; + assert_eq!(mempool.len().await, 0); - // check that the timestamp was not overwritten in insert() - let (_, tx_priority) = mempool - .pop() - .await - .expect("transaction was added, should exist"); - assert_eq!( - tx_priority.time_first_seen.duration_since(insert_time), - Duration::from_secs(0), - "Tracked time should be the same" - ); + // assert that all were added to the cometbft removal cache + // and the expected reasons were tracked + assert!(matches!( + mempool + .check_removed_comet_bft(tx0.sha256_of_proto_encoding()) + .await, + Some(RemovalReason::FailedPrepareProposal(_)) + )); + assert!(matches!( + mempool + .check_removed_comet_bft(tx1.sha256_of_proto_encoding()) + .await, + Some(RemovalReason::FailedPrepareProposal(_)) + )); + assert!(matches!( + mempool + .check_removed_comet_bft(tx3.sha256_of_proto_encoding()) + .await, + Some(RemovalReason::LowerNonceInvalidated) + )); + assert!(matches!( + mempool + .check_removed_comet_bft(tx4.sha256_of_proto_encoding()) + .await, + Some(RemovalReason::FailedPrepareProposal(_)) + )); + assert!(matches!( + mempool + .check_removed_comet_bft(tx5.sha256_of_proto_encoding()) + .await, + Some(RemovalReason::LowerNonceInvalidated) + )); } #[tokio::test] async fn should_get_pending_nonce() { let mempool = Mempool::new(); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_key_1 = SigningKey::from([2; 32]); + let signing_key_2 = SigningKey::from([3; 32]); + let signing_address_0 = signing_key_0.verification_key().address_bytes(); + let signing_address_1 = signing_key_1.verification_key().address_bytes(); + let signing_address_2 = signing_key_2.verification_key().address_bytes(); + + // sign and insert nonces 0,1 + let tx0 = mock_tx(0, &signing_key_0, "test"); + assert!( + mempool.insert(tx0.clone(), 0).await.is_ok(), + "should be able to insert nonce 0 transaction into mempool" + ); + let tx1 = mock_tx(1, &signing_key_0, "test"); + assert!( + mempool.insert(tx1.clone(), 0).await.is_ok(), + "should be able to insert nonce 1 transaction into mempool" + ); - // Insert txs signed by alice with nonces 0 and 1. - mempool.insert(get_mock_tx(0), 0).await.unwrap(); - mempool.insert(get_mock_tx(1), 0).await.unwrap(); - - // Insert txs from a different signer with nonces 100 and 101. - let other = SigningKey::from([1; 32]); - let other_mock_tx = |nonce: u32| -> SignedTransaction { - let actions = get_mock_tx(0).actions().to_vec(); - UnsignedTransaction { - params: TransactionParams::builder() - .nonce(nonce) - .chain_id("test") - .build(), - actions, - } - .into_signed(&other) - }; - mempool.insert(other_mock_tx(100), 0).await.unwrap(); - mempool.insert(other_mock_tx(101), 0).await.unwrap(); + // sign and insert nonces 100, 101 + let tx100 = mock_tx(100, &signing_key_1, "test"); + assert!( + mempool.insert(tx100.clone(), 100).await.is_ok(), + "should be able to insert nonce 100 transaction into mempool" + ); + let tx101 = mock_tx(101, &signing_key_1, "test"); + assert!( + mempool.insert(tx101.clone(), 100).await.is_ok(), + "should be able to insert nonce 101 transaction into mempool" + ); assert_eq!(mempool.len().await, 4); - // Check the pending nonce for alice is 1 and for the other signer is 101. - let alice = crate::app::test_utils::get_alice_signing_key(); - assert_eq!( - mempool.pending_nonce(alice.address_bytes()).await.unwrap(), - 1 - ); - assert_eq!( - mempool.pending_nonce(other.address_bytes()).await.unwrap(), - 101 - ); + // Check the pending nonces + assert_eq!(mempool.pending_nonce(signing_address_0).await.unwrap(), 1); + assert_eq!(mempool.pending_nonce(signing_address_1).await.unwrap(), 101); - // Check the pending nonce for an address with no enqueued txs is `None`. - assert!(mempool.pending_nonce([1; 20]).await.is_none()); + // Check the pending nonce for an address with no txs is `None`. + assert!(mempool.pending_nonce(signing_address_2).await.is_none()); } #[tokio::test] - async fn tx_cache_size() { + async fn tx_removal_cache() { let mut tx_cache = RemovalCache::new(NonZeroUsize::try_from(2).unwrap()); let tx_0 = [0u8; 32]; @@ -814,10 +639,18 @@ mod test { ); } - #[test] - fn enqueued_transaction_can_be_instantiated() { - // This just tests that the constructor does not fail. - let signed_tx = crate::app::test_utils::get_mock_tx(0); - let _ = EnqueuedTransaction::new(signed_tx); + #[tokio::test] + async fn tx_removal_cache_preserves_first_reason() { + let mut tx_cache = RemovalCache::new(NonZeroUsize::try_from(2).unwrap()); + + let tx_0 = [0u8; 32]; + + tx_cache.add(tx_0, RemovalReason::Expired); + tx_cache.add(tx_0, RemovalReason::LowerNonceInvalidated); + + assert!( + matches!(tx_cache.remove(tx_0), Some(RemovalReason::Expired)), + "first removal reason should be presenved" + ); } } diff --git a/crates/astria-sequencer/src/mempool/transactions_container.rs b/crates/astria-sequencer/src/mempool/transactions_container.rs new file mode 100644 index 0000000000..73fccd94fe --- /dev/null +++ b/crates/astria-sequencer/src/mempool/transactions_container.rs @@ -0,0 +1,1584 @@ +use std::{ + cmp::Ordering, + collections::{ + hash_map, + BTreeMap, + HashMap, + }, + fmt, + future::Future, + mem, + sync::Arc, +}; + +use anyhow::Context; +use astria_core::protocol::transaction::v1alpha1::SignedTransaction; +use tokio::time::{ + Duration, + Instant, +}; +use tracing::error; + +use super::RemovalReason; + +pub(super) type PendingTransactions = TransactionsContainer; +pub(super) type ParkedTransactions = + TransactionsContainer>; + +/// `TimemarkedTransaction` is a wrapper around a signed transaction used to keep track of when that +/// transaction was first seen in the mempool. +#[derive(Clone, Debug)] +pub(super) struct TimemarkedTransaction { + signed_tx: Arc, + tx_hash: [u8; 32], + time_first_seen: Instant, + address: [u8; 20], +} + +impl TimemarkedTransaction { + pub(super) fn new(signed_tx: Arc) -> Self { + Self { + tx_hash: signed_tx.sha256_of_proto_encoding(), + address: signed_tx.verification_key().address_bytes(), + signed_tx, + time_first_seen: Instant::now(), + } + } + + fn priority(&self, current_account_nonce: u32) -> anyhow::Result { + let Some(nonce_diff) = self.signed_tx.nonce().checked_sub(current_account_nonce) else { + return Err(anyhow::anyhow!( + "transaction nonce {} is less than current account nonce {current_account_nonce}", + self.signed_tx.nonce() + )); + }; + + Ok(TransactionPriority { + nonce_diff, + time_first_seen: self.time_first_seen, + }) + } + + fn is_expired(&self, now: Instant, ttl: Duration) -> bool { + now.saturating_duration_since(self.time_first_seen) > ttl + } + + pub(super) fn nonce(&self) -> u32 { + self.signed_tx.nonce() + } + + pub(super) fn address(&self) -> &[u8; 20] { + &self.address + } +} + +impl fmt::Display for TimemarkedTransaction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "tx_hash: {}, address: {}, signer: {}, nonce: {}, chain ID: {}", + telemetry::display::base64(&self.tx_hash), + telemetry::display::base64(&self.address), + self.signed_tx.verification_key(), + self.signed_tx.nonce(), + self.signed_tx.chain_id(), + ) + } +} + +#[derive(Clone, Copy, Debug)] +struct TransactionPriority { + nonce_diff: u32, + time_first_seen: Instant, +} + +impl PartialEq for TransactionPriority { + fn eq(&self, other: &Self) -> bool { + self.nonce_diff == other.nonce_diff && self.time_first_seen == other.time_first_seen + } +} + +impl Eq for TransactionPriority {} + +impl Ord for TransactionPriority { + fn cmp(&self, other: &Self) -> Ordering { + // we want to first order by nonce difference + // lower nonce diff means higher priority + let nonce_diff = self.nonce_diff.cmp(&other.nonce_diff).reverse(); + + // then by timestamp if equal + if nonce_diff == Ordering::Equal { + // lower timestamp means higher priority + return self.time_first_seen.cmp(&other.time_first_seen).reverse(); + } + nonce_diff + } +} + +impl PartialOrd for TransactionPriority { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub(crate) enum InsertionError { + AlreadyPresent, + NonceTooLow, + NonceTaken, + NonceGap, + AccountSizeLimit, +} + +impl fmt::Display for InsertionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InsertionError::AlreadyPresent => { + write!(f, "transaction already exists in the mempool") + } + InsertionError::NonceTooLow => { + write!(f, "given nonce has already been used previously") + } + InsertionError::NonceTaken => write!(f, "given nonce already exists in the mempool"), + InsertionError::NonceGap => write!(f, "gap in the pending nonce sequence"), + InsertionError::AccountSizeLimit => write!( + f, + "maximum number of pending transactions has been reached for the given account" + ), + } + } +} + +/// Transactions for a single account where the sequence of nonces must not have any gaps. +#[derive(Clone, Default, Debug)] +pub(super) struct PendingTransactionsForAccount { + txs: BTreeMap, +} + +impl PendingTransactionsForAccount { + fn highest_nonce(&self) -> Option { + self.txs.last_key_value().map(|(nonce, _)| *nonce) + } +} + +impl TransactionsForAccount for PendingTransactionsForAccount { + fn txs(&self) -> &BTreeMap { + &self.txs + } + + fn txs_mut(&mut self) -> &mut BTreeMap { + &mut self.txs + } + + fn is_at_tx_limit(&self) -> bool { + false + } + + fn is_sequential_nonce_precondition_met( + &self, + ttx: &TimemarkedTransaction, + current_account_nonce: u32, + ) -> bool { + // If the `ttx` nonce is 0, precondition is met iff the current account nonce is also at + // zero + let Some(previous_nonce) = ttx.signed_tx.nonce().checked_sub(1) else { + return current_account_nonce == 0; + }; + + // Precondition is met if the previous nonce is in the existing txs, or if the tx's nonce + // is equal to the account nonce + self.txs().contains_key(&previous_nonce) || ttx.signed_tx.nonce() == current_account_nonce + } +} + +/// Transactions for a single account where gaps are allowed in the sequence of nonces, and with an +/// upper bound on the number of transactions. +#[derive(Clone, Default, Debug)] +pub(super) struct ParkedTransactionsForAccount { + txs: BTreeMap, +} + +impl ParkedTransactionsForAccount { + /// Returns contiguous transactions from front of queue starting from target nonce, removing the + /// transactions in the process. + /// + /// Note: this function only operates on the front of the queue. If the target nonce is not at + /// the front, an error will be logged and nothing will be returned. + fn pop_front_contiguous( + &mut self, + mut target_nonce: u32, + ) -> impl Iterator { + let mut split_at = 0; + for nonce in self.txs.keys() { + if *nonce == target_nonce { + let Some(next_target) = target_nonce.checked_add(1) else { + // We've got contiguous nonces up to `u32::MAX`; return everything. + return mem::take(&mut self.txs).into_values(); + }; + target_nonce = next_target; + split_at = next_target; + } else { + break; + } + } + + if split_at == 0 { + error!(target_nonce, "expected nonce to be present"); + } + + let mut split_off = self.txs.split_off(&split_at); + // The higher nonces are returned in `split_off`, but we want to keep these in `self.txs`, + // so swap the two collections. + mem::swap(&mut split_off, &mut self.txs); + split_off.into_values() + } +} + +impl TransactionsForAccount + for ParkedTransactionsForAccount +{ + fn txs(&self) -> &BTreeMap { + &self.txs + } + + fn txs_mut(&mut self) -> &mut BTreeMap { + &mut self.txs + } + + fn is_at_tx_limit(&self) -> bool { + self.txs.len() >= MAX_TX_COUNT + } + + fn is_sequential_nonce_precondition_met(&self, _: &TimemarkedTransaction, _: u32) -> bool { + true + } +} + +/// `TransactionsForAccount` is a trait for a collection of transactions belonging to a single +/// account. +pub(super) trait TransactionsForAccount: Default { + fn new() -> Self + where + Self: Sized + Default, + { + Self::default() + } + + fn txs(&self) -> &BTreeMap; + + fn txs_mut(&mut self) -> &mut BTreeMap; + + fn is_at_tx_limit(&self) -> bool; + + /// Returns `Ok` if adding `ttx` would not break the nonce precondition, i.e. sequential + /// nonces with no gaps if in `SequentialNonces` mode. + fn is_sequential_nonce_precondition_met( + &self, + ttx: &TimemarkedTransaction, + current_account_nonce: u32, + ) -> bool; + + /// Adds transaction to the container. Note: does NOT allow for nonce replacement. + /// Will fail if in `SequentialNonces` mode and adding the transaction would create a nonce gap. + /// + /// `current_account_nonce` should be the account's nonce in the latest chain state. + /// + /// Note: if the account `current_account_nonce` ever decreases, this is a logic error + /// and could mess up the validity of `SequentialNonces` containers. + fn add( + &mut self, + ttx: TimemarkedTransaction, + current_account_nonce: u32, + ) -> Result<(), InsertionError> { + if self.is_at_tx_limit() { + return Err(InsertionError::AccountSizeLimit); + } + + if ttx.nonce() < current_account_nonce { + return Err(InsertionError::NonceTooLow); + } + + if let Some(existing_ttx) = self.txs().get(&ttx.signed_tx.nonce()) { + return Err(if existing_ttx.tx_hash == ttx.tx_hash { + InsertionError::AlreadyPresent + } else { + InsertionError::NonceTaken + }); + } + + if !self.is_sequential_nonce_precondition_met(&ttx, current_account_nonce) { + return Err(InsertionError::NonceGap); + } + + self.txs_mut().insert(ttx.signed_tx.nonce(), ttx); + + Ok(()) + } + + /// Removes transactions with the given nonce and higher. + /// + /// Note: the given nonce is expected to be present. If it's absent, an error is logged and no + /// transactions are removed. + /// + /// Returns the hashes of the removed transactions. + fn remove(&mut self, nonce: u32) -> Vec<[u8; 32]> { + if !self.txs().contains_key(&nonce) { + error!(nonce, "transaction with given nonce not found"); + return Vec::new(); + } + + self.txs_mut() + .split_off(&nonce) + .values() + .map(|ttx| ttx.tx_hash) + .collect() + } + + /// Returns the transaction with the lowest nonce. + fn front(&self) -> Option<&TimemarkedTransaction> { + self.txs().first_key_value().map(|(_, ttx)| ttx) + } + + /// Removes transactions below the given nonce. Returns the hashes of the removed transactions. + fn register_latest_account_nonce( + &mut self, + current_account_nonce: u32, + ) -> impl Iterator { + let mut split_off = self.txs_mut().split_off(¤t_account_nonce); + mem::swap(&mut split_off, self.txs_mut()); + split_off.into_values().map(|ttx| ttx.tx_hash) + } + + #[cfg(test)] + fn contains_tx(&self, tx_hash: &[u8; 32]) -> bool { + self.txs().values().any(|ttx| ttx.tx_hash == *tx_hash) + } +} + +/// `TransactionsContainer` is a container used for managing transactions for multiple accounts. +#[derive(Clone, Debug)] +pub(super) struct TransactionsContainer { + /// A map of collections of transactions, indexed by the account address. + txs: HashMap<[u8; 20], T>, + tx_ttl: Duration, +} + +impl TransactionsContainer { + pub(super) fn new(tx_ttl: Duration) -> Self { + TransactionsContainer:: { + txs: HashMap::new(), + tx_ttl, + } + } + + /// Adds the transaction to the container. + /// + /// `current_account_nonce` should be the current nonce of the account associated with the + /// transaction. If this ever decreases, the `TransactionsContainer` containers could become + /// invalid. + pub(super) fn add( + &mut self, + ttx: TimemarkedTransaction, + current_account_nonce: u32, + ) -> Result<(), InsertionError> { + match self.txs.entry(*ttx.address()) { + hash_map::Entry::Occupied(entry) => { + entry.into_mut().add(ttx, current_account_nonce)?; + } + hash_map::Entry::Vacant(entry) => { + let mut txs = T::new(); + txs.add(ttx, current_account_nonce)?; + entry.insert(txs); + } + } + Ok(()) + } + + /// Removes the given transaction and any transactions with higher nonces for the relevant + /// account. + /// + /// If `signed_tx` existed, returns `Ok` with the hashes of the removed transactions. If + /// `signed_tx` was not in the collection, it is returned via `Err`. + pub(super) fn remove( + &mut self, + signed_tx: Arc, + ) -> Result, Arc> { + let address = signed_tx.verification_key().address_bytes(); + + // Take the collection for this account out of `self` temporarily. + let Some(mut account_txs) = self.txs.remove(&address) else { + return Err(signed_tx); + }; + + let removed = account_txs.remove(signed_tx.nonce()); + + // Re-add the collection to `self` if it's not empty. + if !account_txs.txs().is_empty() { + let _ = self.txs.insert(address, account_txs); + } + + if removed.is_empty() { + return Err(signed_tx); + } + + Ok(removed) + } + + /// Removes all of the transactions for the given account and returns the hashes of the removed + /// transactions. + pub(super) fn clear_account(&mut self, address: &[u8; 20]) -> Vec<[u8; 32]> { + self.txs + .remove(address) + .map(|account_txs| account_txs.txs().values().map(|ttx| ttx.tx_hash).collect()) + .unwrap_or_default() + } + + /// Cleans all of the accounts in the container. Removes any transactions with stale nonces and + /// evicts all transactions from accounts whose lowest transaction has expired. + /// + /// Returns all transactions that have been removed with the reason why they have been removed. + pub(super) async fn clean_accounts( + &mut self, + current_account_nonce_getter: &F, + ) -> Vec<([u8; 32], RemovalReason)> + where + F: Fn([u8; 20]) -> O, + O: Future>, + { + // currently just removes stale nonces and will clear accounts if the + // transactions are older than the TTL + let mut accounts_to_remove = Vec::new(); + let mut removed_txs = Vec::new(); + let now = Instant::now(); + for (address, account_txs) in &mut self.txs { + // check if first tx is older than the TTL, if so, remove all transactions + if let Some(first_tx) = account_txs.front() { + if first_tx.is_expired(now, self.tx_ttl) { + // first is stale, rest popped for invalidation + removed_txs.push((first_tx.tx_hash, RemovalReason::Expired)); + removed_txs.extend( + account_txs + .txs() + .values() + .skip(1) + .map(|ttx| (ttx.tx_hash, RemovalReason::LowerNonceInvalidated)), + ); + account_txs.txs_mut().clear(); + } else { + // clean to newest nonce + let current_account_nonce = match current_account_nonce_getter(*address).await { + Ok(nonce) => nonce, + Err(error) => { + error!( + address = %telemetry::display::base64(address), + "failed to fetch nonce from state when cleaning accounts: {error:#}" + ); + continue; + } + }; + removed_txs.extend( + account_txs + .register_latest_account_nonce(current_account_nonce) + .map(|tx_hash| (tx_hash, RemovalReason::NonceStale)), + ); + } + } + + if account_txs.txs().is_empty() { + accounts_to_remove.push(*address); + } + } + + // remove empty accounts + for account in accounts_to_remove { + self.txs.remove(&account); + } + + removed_txs + } + + /// Returns the number of transactions in the container. + pub(super) fn len(&self) -> usize { + self.txs + .values() + .map(|account_txs| account_txs.txs().len()) + .sum() + } + + #[cfg(test)] + fn contains_tx(&self, tx_hash: &[u8; 32]) -> bool { + self.txs + .values() + .any(|account_txs| account_txs.contains_tx(tx_hash)) + } +} + +impl TransactionsContainer { + /// Returns the highest nonce for an account. + pub(super) fn pending_nonce(&self, address: [u8; 20]) -> Option { + self.txs + .get(&address) + .and_then(PendingTransactionsForAccount::highest_nonce) + } + + /// Returns a copy of transactions and their hashes sorted by nonce difference and then time + /// first seen. + pub(super) async fn builder_queue( + &self, + current_account_nonce_getter: F, + ) -> anyhow::Result)>> + where + F: Fn([u8; 20]) -> O, + O: Future>, + { + // Used to hold the values in Vec for sorting. + struct QueueEntry { + tx: Arc, + tx_hash: [u8; 32], + priority: TransactionPriority, + } + + let mut queue = Vec::with_capacity(self.len()); + // Add all transactions to the queue. + for (address, account_txs) in &self.txs { + let current_account_nonce = current_account_nonce_getter(*address) + .await + .context("failed to fetch account nonce for builder queue")?; + for ttx in account_txs.txs.values() { + let priority = match ttx.priority(current_account_nonce) { + Ok(priority) => priority, + Err(error) => { + // mempool could be off due to node connectivity issues + error!( + tx_hash = %telemetry::display::base64(&ttx.tx_hash), + "failed to add pending tx to builder queue: {error:#}" + ); + continue; + } + }; + queue.push(QueueEntry { + tx: ttx.signed_tx.clone(), + tx_hash: ttx.tx_hash, + priority, + }); + } + } + + // Sort the queue and return the relevant data. Note that the sorted queue will be ordered + // from lowest to highest priority, so we need to reverse the order before returning. + queue.sort_unstable_by_key(|entry| entry.priority); + Ok(queue + .into_iter() + .rev() + .map(|entry| (entry.tx_hash, entry.tx)) + .collect()) + } +} + +impl TransactionsContainer> { + /// Removes and returns the transactions from the front of an account, similar to + /// `find_promotables`. Useful for when needing to promote transactions from a specific + /// account instead of all accounts. + pub(super) fn pop_front_account( + &mut self, + account: &[u8; 20], + target_nonce: u32, + ) -> Vec { + // Take the collection for this account out of `self` temporarily. + let Some(mut account_txs) = self.txs.remove(account) else { + return Vec::new(); + }; + + let removed = account_txs.pop_front_contiguous(target_nonce); + + // Re-add the collection to `self` if it's not empty. + if !account_txs.txs().is_empty() { + let _ = self.txs.insert(*account, account_txs); + } + removed.collect() + } + + /// Removes and returns transactions along with their account's current nonce that are lower + /// than or equal to that nonce. This is helpful when needing to promote transactions from + /// parked to pending during mempool maintenance. + pub(super) async fn find_promotables( + &mut self, + current_account_nonce_getter: &F, + ) -> Vec<(TimemarkedTransaction, u32)> + where + F: Fn([u8; 20]) -> O, + O: Future>, + { + let mut accounts_to_remove = Vec::new(); + let mut promoted_txs = Vec::new(); + + for (address, account_txs) in &mut self.txs { + let current_account_nonce = match current_account_nonce_getter(*address).await { + Ok(nonce) => nonce, + Err(error) => { + error!( + address = %telemetry::display::base64(address), + "failed to fetch nonce from state when finding promotables: {error:#}" + ); + continue; + } + }; + + // find transactions that can be promoted + // note: can use current account nonce as target because this logic + // is only handling the case where transactions we didn't have in our + // local mempool were ran that would enable the parked transactions to + // be valid + promoted_txs.extend( + account_txs + .pop_front_contiguous(current_account_nonce) + .map(|ttx| (ttx, current_account_nonce)), + ); + + if account_txs.txs.is_empty() { + accounts_to_remove.push(*address); + } + } + + // remove empty accounts + for account in accounts_to_remove { + self.txs.remove(&account); + } + + promoted_txs + } +} + +#[cfg(test)] +mod test { + use astria_core::crypto::SigningKey; + + use super::*; + use crate::app::test_utils::mock_tx; + + const MAX_PARKED_TXS_PER_ACCOUNT: usize = 15; + const TX_TTL: Duration = Duration::from_secs(2); + + fn mock_ttx(nonce: u32, signer: &SigningKey) -> TimemarkedTransaction { + TimemarkedTransaction::new(mock_tx(nonce, signer, "test")) + } + + #[test] + fn transaction_priority_should_error_if_invalid() { + let ttx = TimemarkedTransaction::new(mock_tx(0, &[1; 32].into(), "test")); + let priority = ttx.priority(1); + + assert!( + priority + .unwrap_err() + .to_string() + .contains("less than current account nonce") + ); + } + + // From https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html + #[test] + // allow: we want explicit assertions here to match the documented expected behavior. + #[allow(clippy::nonminimal_bool)] + fn transaction_priority_comparisons_should_be_consistent_nonce_diff() { + let instant = Instant::now(); + + let high = TransactionPriority { + nonce_diff: 0, + time_first_seen: instant, + }; + let low = TransactionPriority { + nonce_diff: 1, + time_first_seen: instant, + }; + + assert!(high.partial_cmp(&high) == Some(Ordering::Equal)); + assert!(high.partial_cmp(&low) == Some(Ordering::Greater)); + assert!(low.partial_cmp(&high) == Some(Ordering::Less)); + + // 1. a == b if and only if partial_cmp(a, b) == Some(Equal) + assert!(high == high); // Some(Equal) + assert!(!(high == low)); // Some(Greater) + assert!(!(low == high)); // Some(Less) + + // 2. a < b if and only if partial_cmp(a, b) == Some(Less) + assert!(low < high); // Some(Less) + assert!(!(high < high)); // Some(Equal) + assert!(!(high < low)); // Some(Greater) + + // 3. a > b if and only if partial_cmp(a, b) == Some(Greater) + assert!(high > low); // Some(Greater) + assert!(!(high > high)); // Some(Equal) + assert!(!(low > high)); // Some(Less) + + // 4. a <= b if and only if a < b || a == b + assert!(low <= high); // a < b + assert!(high <= high); // a == b + assert!(!(high <= low)); // a > b + + // 5. a >= b if and only if a > b || a == b + assert!(high >= low); // a > b + assert!(high >= high); // a == b + assert!(!(low >= high)); // a < b + + // 6. a != b if and only if !(a == b) + assert!(high != low); // asserted !(high == low) above + assert!(low != high); // asserted !(low == high) above + assert!(!(high != high)); // asserted high == high above + } + + // From https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html + #[test] + // allow: we want explicit assertions here to match the documented expected behavior. + #[allow(clippy::nonminimal_bool)] + fn transaction_priority_comparisons_should_be_consistent_time_gap() { + let high = TransactionPriority { + nonce_diff: 0, + time_first_seen: Instant::now(), + }; + let low = TransactionPriority { + nonce_diff: 0, + time_first_seen: Instant::now() + Duration::from_micros(10), + }; + + assert!(high.partial_cmp(&high) == Some(Ordering::Equal)); + assert!(high.partial_cmp(&low) == Some(Ordering::Greater)); + assert!(low.partial_cmp(&high) == Some(Ordering::Less)); + + // 1. a == b if and only if partial_cmp(a, b) == Some(Equal) + assert!(high == high); // Some(Equal) + assert!(!(high == low)); // Some(Greater) + assert!(!(low == high)); // Some(Less) + + // 2. a < b if and only if partial_cmp(a, b) == Some(Less) + assert!(low < high); // Some(Less) + assert!(!(high < high)); // Some(Equal) + assert!(!(high < low)); // Some(Greater) + + // 3. a > b if and only if partial_cmp(a, b) == Some(Greater) + assert!(high > low); // Some(Greater) + assert!(!(high > high)); // Some(Equal) + assert!(!(low > high)); // Some(Less) + + // 4. a <= b if and only if a < b || a == b + assert!(low <= high); // a < b + assert!(high <= high); // a == b + assert!(!(high <= low)); // a > b + + // 5. a >= b if and only if a > b || a == b + assert!(high >= low); // a > b + assert!(high >= high); // a == b + assert!(!(low >= high)); // a < b + + // 6. a != b if and only if !(a == b) + assert!(high != low); // asserted !(high == low) above + assert!(low != high); // asserted !(low == high) above + assert!(!(high != high)); // asserted high == high above + } + + #[test] + fn parked_transactions_for_account_add() { + let mut parked_txs = ParkedTransactionsForAccount::::new(); + + // transactions to add + let ttx_1 = mock_ttx(1, &[1; 32].into()); + let ttx_3 = mock_ttx(3, &[1; 32].into()); + let ttx_5 = mock_ttx(5, &[1; 32].into()); + + let current_account_nonce = 2; + parked_txs + .add(ttx_3.clone(), current_account_nonce) + .unwrap(); + assert!(parked_txs.contains_tx(&ttx_3.tx_hash)); + assert_eq!( + parked_txs.add(ttx_3, current_account_nonce).unwrap_err(), + InsertionError::AlreadyPresent + ); + + // add gapped transaction + parked_txs.add(ttx_5, current_account_nonce).unwrap(); + + // fail adding too low nonce + assert_eq!( + parked_txs.add(ttx_1, current_account_nonce).unwrap_err(), + InsertionError::NonceTooLow + ); + } + + #[test] + fn parked_transactions_for_account_size_limit() { + let mut parked_txs = ParkedTransactionsForAccount::<2>::new(); + + // transactions to add + let ttx_1 = mock_ttx(1, &[1; 32].into()); + let ttx_3 = mock_ttx(3, &[1; 32].into()); + let ttx_5 = mock_ttx(5, &[1; 32].into()); + + let current_account_nonce = 0; + parked_txs + .add(ttx_3.clone(), current_account_nonce) + .unwrap(); + parked_txs.add(ttx_5, current_account_nonce).unwrap(); + + // fail with size limit hit + assert_eq!( + parked_txs.add(ttx_1, current_account_nonce).unwrap_err(), + InsertionError::AccountSizeLimit + ); + } + + #[test] + fn pending_transactions_for_account_add() { + let mut pending_txs = PendingTransactionsForAccount::new(); + + // transactions to add + let ttx_0 = mock_ttx(0, &[1; 32].into()); + let ttx_1 = mock_ttx(1, &[1; 32].into()); + let ttx_2 = mock_ttx(2, &[1; 32].into()); + let ttx_3 = mock_ttx(3, &[1; 32].into()); + + let current_account_nonce = 1; + + // too low nonces not added + assert_eq!( + pending_txs.add(ttx_0, current_account_nonce).unwrap_err(), + InsertionError::NonceTooLow + ); + assert!(pending_txs.txs().is_empty()); + + // too high nonces with empty container not added + assert_eq!( + pending_txs + .add(ttx_2.clone(), current_account_nonce) + .unwrap_err(), + InsertionError::NonceGap + ); + assert!(pending_txs.txs().is_empty()); + + // add ok + pending_txs + .add(ttx_1.clone(), current_account_nonce) + .unwrap(); + assert_eq!( + pending_txs.add(ttx_1, current_account_nonce).unwrap_err(), + InsertionError::AlreadyPresent + ); + + // gapped transaction not allowed + assert_eq!( + pending_txs.add(ttx_3, current_account_nonce).unwrap_err(), + InsertionError::NonceGap + ); + + // can add consecutive + pending_txs.add(ttx_2, current_account_nonce).unwrap(); + } + + #[test] + fn transactions_for_account_remove() { + let mut account_txs = PendingTransactionsForAccount::new(); + + // transactions to add + let ttx_0 = mock_ttx(0, &[1; 32].into()); + let ttx_1 = mock_ttx(1, &[1; 32].into()); + let ttx_2 = mock_ttx(2, &[1; 32].into()); + let ttx_3 = mock_ttx(3, &[1; 32].into()); + + account_txs.add(ttx_0.clone(), 0).unwrap(); + account_txs.add(ttx_1.clone(), 0).unwrap(); + account_txs.add(ttx_2.clone(), 0).unwrap(); + account_txs.add(ttx_3.clone(), 0).unwrap(); + + // remove from end will only remove end + assert_eq!( + account_txs.remove(3), + vec![ttx_3.tx_hash], + "only one transaction should've been removed" + ); + assert_eq!(account_txs.txs().len(), 3); + + // remove same again return nothing + assert_eq!( + account_txs.remove(3).len(), + 0, + "no transaction should be removed" + ); + assert_eq!(account_txs.txs().len(), 3); + + // remove from start will remove all + assert_eq!( + account_txs.remove(0), + vec![ttx_0.tx_hash, ttx_1.tx_hash, ttx_2.tx_hash,], + "three transactions should've been removed" + ); + assert!(account_txs.txs().is_empty()); + } + + #[test] + fn parked_transactions_for_account_pop_front_contiguous() { + let mut parked_txs = ParkedTransactionsForAccount::::new(); + + // transactions to add + let ttx_0 = mock_ttx(0, &[1; 32].into()); + let ttx_2 = mock_ttx(2, &[1; 32].into()); + let ttx_3 = mock_ttx(3, &[1; 32].into()); + let ttx_4 = mock_ttx(4, &[1; 32].into()); + + parked_txs.add(ttx_0.clone(), 0).unwrap(); + parked_txs.add(ttx_2.clone(), 0).unwrap(); + parked_txs.add(ttx_3.clone(), 0).unwrap(); + parked_txs.add(ttx_4.clone(), 0).unwrap(); + + // lowest nonce not target nonce is noop + assert_eq!( + parked_txs.pop_front_contiguous(2).count(), + 0, + "no transaction should've been removed" + ); + assert_eq!(parked_txs.txs().len(), 4); + + // will remove single value + assert_eq!( + parked_txs + .pop_front_contiguous(0) + .map(|ttx| ttx.tx_hash) + .collect::>(), + vec![ttx_0.tx_hash], + "single transaction should've been returned" + ); + assert_eq!(parked_txs.txs().len(), 3); + + // will remove multiple values + assert_eq!( + parked_txs + .pop_front_contiguous(2) + .map(|ttx| ttx.tx_hash) + .collect::>(), + vec![ttx_2.tx_hash, ttx_3.tx_hash, ttx_4.tx_hash], + "multiple transaction should've been returned" + ); + assert!(parked_txs.txs().is_empty()); + } + + #[test] + fn pending_transactions_for_account_highest_nonce() { + let mut pending_txs = PendingTransactionsForAccount::new(); + + // no transactions ok + assert!( + pending_txs.highest_nonce().is_none(), + "no transactions will return None" + ); + + // transactions to add + let ttx_0 = mock_ttx(0, &[1; 32].into()); + let ttx_1 = mock_ttx(1, &[1; 32].into()); + let ttx_2 = mock_ttx(2, &[1; 32].into()); + + pending_txs.add(ttx_0, 0).unwrap(); + pending_txs.add(ttx_1, 0).unwrap(); + pending_txs.add(ttx_2, 0).unwrap(); + + // will return last transaction + assert_eq!( + pending_txs.highest_nonce(), + Some(2), + "highest nonce should be returned" + ); + } + + #[test] + fn transactions_for_account_front() { + let mut parked_txs = ParkedTransactionsForAccount::::new(); + + // no transactions ok + assert!( + parked_txs.front().is_none(), + "no transactions will return None" + ); + + // transactions to add + let ttx_0 = mock_ttx(0, &[1; 32].into()); + let ttx_2 = mock_ttx(2, &[1; 32].into()); + + parked_txs.add(ttx_0.clone(), 0).unwrap(); + parked_txs.add(ttx_2, 0).unwrap(); + + // will return first transaction + assert_eq!( + parked_txs.front().unwrap().tx_hash, + ttx_0.tx_hash, + "lowest transaction should be returned" + ); + } + + #[test] + fn transactions_for_account_register_latest_account_nonce() { + let mut parked_txs = ParkedTransactionsForAccount::::new(); + + // transactions to add + let ttx_0 = mock_ttx(0, &[1; 32].into()); + let ttx_2 = mock_ttx(2, &[1; 32].into()); + let ttx_3 = mock_ttx(3, &[1; 32].into()); + let ttx_4 = mock_ttx(4, &[1; 32].into()); + + parked_txs.add(ttx_0.clone(), 0).unwrap(); + parked_txs.add(ttx_2.clone(), 0).unwrap(); + parked_txs.add(ttx_3.clone(), 0).unwrap(); + parked_txs.add(ttx_4.clone(), 0).unwrap(); + + // matching nonce will not be removed + assert_eq!( + parked_txs.register_latest_account_nonce(0).count(), + 0, + "no transaction should've been removed" + ); + assert_eq!(parked_txs.txs().len(), 4); + + // fast forwarding to non existing middle nonce ok + assert_eq!( + parked_txs + .register_latest_account_nonce(1) + .collect::>(), + vec![ttx_0.tx_hash], + "ttx_0 should've been removed" + ); + assert_eq!(parked_txs.txs().len(), 3); + + // fast forwarding to existing nonce ok + assert_eq!( + parked_txs + .register_latest_account_nonce(3) + .collect::>(), + vec![ttx_2.tx_hash], + "one transaction should've been removed" + ); + assert_eq!(parked_txs.txs().len(), 2); + + // fast forwarding to much higher nonce ok + assert_eq!( + parked_txs + .register_latest_account_nonce(10) + .collect::>(), + vec![ttx_3.tx_hash, ttx_4.tx_hash], + "two transactions should've been removed" + ); + assert!(parked_txs.txs().is_empty()); + } + + #[test] + fn transactions_container_add() { + let mut pending_txs = PendingTransactions::new(TX_TTL); + + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + + let signing_key_1 = SigningKey::from([2; 32]); + let signing_address_1 = signing_key_1.address_bytes(); + + // transactions to add to accounts + let ttx_s0_0_0 = mock_ttx(0, &signing_key_0); + // Same nonce and signer as `ttx_s0_0_0`, but different rollup name, hence different tx. + let ttx_s0_0_1 = TimemarkedTransaction::new(mock_tx(0, &signing_key_0, "other")); + let ttx_s0_2_0 = mock_ttx(2, &signing_key_0); + let ttx_s1_0_0 = mock_ttx(0, &signing_key_1); + + // transactions to add for account 1 + + // initially no accounts should exist + assert!( + pending_txs.txs.is_empty(), + "no accounts should exist at first" + ); + + // adding too low nonce shouldn't create account + assert_eq!( + pending_txs.add(ttx_s0_0_0.clone(), 1).unwrap_err(), + InsertionError::NonceTooLow, + "shouldn't be able to add nonce too low transaction" + ); + assert!( + pending_txs.txs.is_empty(), + "failed adds to new accounts shouldn't create account" + ); + + // add one transaction + pending_txs.add(ttx_s0_0_0.clone(), 0).unwrap(); + assert_eq!(pending_txs.txs.len(), 1, "one account should exist"); + + // re-adding transaction should fail + assert_eq!( + pending_txs.add(ttx_s0_0_0, 0).unwrap_err(), + InsertionError::AlreadyPresent, + "re-adding same transaction should fail" + ); + + // nonce replacement fails + assert_eq!( + pending_txs.add(ttx_s0_0_1, 0).unwrap_err(), + InsertionError::NonceTaken, + "nonce replacement not supported" + ); + + // nonce gaps not supported + assert_eq!( + pending_txs.add(ttx_s0_2_0, 0).unwrap_err(), + InsertionError::NonceGap, + "gapped nonces in pending transactions not allowed" + ); + + // add transactions for account 2 + pending_txs.add(ttx_s1_0_0, 0).unwrap(); + + // check internal structures + assert_eq!(pending_txs.txs.len(), 2, "two accounts should exist"); + assert_eq!( + pending_txs.txs.get(&signing_address_0).unwrap().txs().len(), + 1, + "one transaction should be in the original account" + ); + assert_eq!( + pending_txs.txs.get(&signing_address_1).unwrap().txs().len(), + 1, + "one transaction should be in the second account" + ); + assert_eq!( + pending_txs.len(), + 2, + "should only have two transactions tracked" + ); + } + + #[test] + fn transactions_container_remove() { + let mut pending_txs = PendingTransactions::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_key_1 = SigningKey::from([2; 32]); + + // transactions to add to accounts + let ttx_s0_0 = mock_ttx(0, &signing_key_0); + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + let ttx_s1_0 = mock_ttx(0, &signing_key_1); + let ttx_s1_1 = mock_ttx(1, &signing_key_1); + + // remove on empty returns the tx in Err variant. + assert!( + pending_txs.remove(ttx_s0_0.signed_tx.clone()).is_err(), + "zero transactions should be removed from non existing accounts" + ); + + // add transactions + pending_txs.add(ttx_s0_0.clone(), 0).unwrap(); + pending_txs.add(ttx_s0_1.clone(), 0).unwrap(); + pending_txs.add(ttx_s1_0.clone(), 0).unwrap(); + pending_txs.add(ttx_s1_1.clone(), 0).unwrap(); + + // remove should remove tx and higher + assert_eq!( + pending_txs.remove(ttx_s0_0.signed_tx.clone()).unwrap(), + vec![ttx_s0_0.tx_hash, ttx_s0_1.tx_hash], + "rest of transactions for account should be removed when targeting bottom nonce" + ); + assert_eq!(pending_txs.txs.len(), 1, "empty account should be removed"); + assert_eq!( + pending_txs.len(), + 2, + "should only have two transactions tracked" + ); + assert!( + pending_txs.contains_tx(&ttx_s1_0.tx_hash), + "other account should be untouched" + ); + assert!( + pending_txs.contains_tx(&ttx_s1_1.tx_hash), + "other account should be untouched" + ); + } + + #[test] + fn transactions_container_clear_account() { + let mut pending_txs = PendingTransactions::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + + let signing_key_1 = SigningKey::from([2; 32]); + + // transactions to add to accounts + let ttx_s0_0 = mock_ttx(0, &signing_key_0); + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + let ttx_s1_0 = mock_ttx(0, &signing_key_1); + + // clear all on empty returns zero + assert!( + pending_txs.clear_account(&signing_address_0).is_empty(), + "zero transactions should be removed from clearing non existing accounts" + ); + + // add transactions + pending_txs.add(ttx_s0_0.clone(), 0).unwrap(); + pending_txs.add(ttx_s0_1.clone(), 0).unwrap(); + pending_txs.add(ttx_s1_0.clone(), 0).unwrap(); + + // clear should return all transactions + assert_eq!( + pending_txs.clear_account(&signing_address_0), + vec![ttx_s0_0.tx_hash, ttx_s0_1.tx_hash], + "all transactions should be returned from clearing account" + ); + + assert_eq!(pending_txs.txs.len(), 1, "empty account should be removed"); + assert_eq!( + pending_txs.len(), + 1, + "should only have one transaction tracked" + ); + assert!( + pending_txs.contains_tx(&ttx_s1_0.tx_hash), + "other account should be untouched" + ); + } + + #[tokio::test] + async fn transactions_container_clean_accounts() { + let mut pending_txs = PendingTransactions::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + let signing_key_1 = SigningKey::from([2; 32]); + let signing_address_1 = signing_key_1.address_bytes(); + let signing_key_2 = SigningKey::from([3; 32]); + let signing_address_2 = signing_key_2.address_bytes(); + + // transactions to add to accounts + let ttx_s0_0 = mock_ttx(0, &signing_key_0); + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + let ttx_s0_2 = mock_ttx(2, &signing_key_0); + let ttx_s1_0 = mock_ttx(0, &signing_key_1); + let ttx_s1_1 = mock_ttx(1, &signing_key_1); + let ttx_s1_2 = mock_ttx(2, &signing_key_1); + let ttx_s2_0 = mock_ttx(0, &signing_key_2); + let ttx_s2_1 = mock_ttx(1, &signing_key_2); + let ttx_s2_2 = mock_ttx(2, &signing_key_2); + + // add transactions + pending_txs.add(ttx_s0_0.clone(), 0).unwrap(); + pending_txs.add(ttx_s0_1.clone(), 0).unwrap(); + pending_txs.add(ttx_s0_2.clone(), 0).unwrap(); + pending_txs.add(ttx_s1_0.clone(), 0).unwrap(); + pending_txs.add(ttx_s1_1.clone(), 0).unwrap(); + pending_txs.add(ttx_s1_2.clone(), 0).unwrap(); + pending_txs.add(ttx_s2_0.clone(), 0).unwrap(); + pending_txs.add(ttx_s2_1.clone(), 0).unwrap(); + pending_txs.add(ttx_s2_2.clone(), 0).unwrap(); + + // current nonce getter + // should pop none from signing_address_0, one from signing_address_1, and all from + // signing_address_2 + let current_account_nonce_getter = |address: [u8; 20]| async move { + if address == signing_address_0 { + Ok(0) + } else if address == signing_address_1 { + Ok(1) + } else if address == signing_address_2 { + Ok(4) + } else { + Err(anyhow::anyhow!("invalid address")) + } + }; + + let removed_txs = pending_txs + .clean_accounts(¤t_account_nonce_getter) + .await; + + assert_eq!( + removed_txs.len(), + 4, + "four transactions should've been popped" + ); + assert_eq!(pending_txs.txs.len(), 2, "empty accounts should be removed"); + assert_eq!( + pending_txs.len(), + 5, + "5 transactions should be remaining from original 9" + ); + assert!(pending_txs.contains_tx(&ttx_s0_0.tx_hash)); + assert!(pending_txs.contains_tx(&ttx_s0_1.tx_hash)); + assert!(pending_txs.contains_tx(&ttx_s0_2.tx_hash)); + assert!(pending_txs.contains_tx(&ttx_s1_1.tx_hash)); + assert!(pending_txs.contains_tx(&ttx_s1_2.tx_hash)); + + assert_eq!( + pending_txs.txs.get(&signing_address_0).unwrap().txs().len(), + 3 + ); + assert_eq!( + pending_txs.txs.get(&signing_address_1).unwrap().txs().len(), + 2 + ); + for (_, reason) in removed_txs { + assert!( + matches!(reason, RemovalReason::NonceStale), + "removal reason should be stale nonce" + ); + } + } + + #[tokio::test(start_paused = true)] + async fn transactions_container_clean_accounts_expired_transactions() { + let mut pending_txs = PendingTransactions::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + let signing_key_1 = SigningKey::from([2; 32]); + let signing_address_1 = signing_key_1.address_bytes(); + + // transactions to add to accounts + let ttx_s0_0 = mock_ttx(0, &signing_key_0); + + // pass time to make first transaction stale + tokio::time::advance(TX_TTL.saturating_add(Duration::from_nanos(1))).await; + + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + let ttx_s1_0 = mock_ttx(0, &signing_key_1); + + // add transactions + pending_txs.add(ttx_s0_0.clone(), 0).unwrap(); + pending_txs.add(ttx_s0_1.clone(), 0).unwrap(); + pending_txs.add(ttx_s1_0.clone(), 0).unwrap(); + + // current nonce getter + // all nonces should be valid + let current_account_nonce_getter = |address: [u8; 20]| async move { + if address == signing_address_0 || address == signing_address_1 { + return Ok(0); + } + Err(anyhow::anyhow!("invalid address")) + }; + + let removed_txs = pending_txs + .clean_accounts(¤t_account_nonce_getter) + .await; + + assert_eq!( + removed_txs.len(), + 2, + "two transactions should've been popped" + ); + assert_eq!(pending_txs.txs.len(), 1, "empty accounts should be removed"); + assert_eq!( + pending_txs.len(), + 1, + "1 transaction should be remaining from original 3" + ); + assert!( + pending_txs.contains_tx(&ttx_s1_0.tx_hash), + "not expired account should be untouched" + ); + + // check removal reasons + assert_eq!( + removed_txs[0], + (ttx_s0_0.tx_hash, RemovalReason::Expired), + "first should be first pushed tx with removal reason as expired" + ); + assert_eq!( + removed_txs[1], + (ttx_s0_1.tx_hash, RemovalReason::LowerNonceInvalidated), + "second should be second added tx with removal reason as lower nonce invalidation" + ); + } + + #[test] + fn pending_transactions_pending_nonce() { + let mut pending_txs = PendingTransactions::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + + let signing_key_1 = SigningKey::from([2; 32]); + let signing_address_1 = signing_key_1.address_bytes(); + + // transactions to add for account 0 + let ttx_s0_0 = mock_ttx(0, &signing_key_0); + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + + pending_txs.add(ttx_s0_0, 0).unwrap(); + pending_txs.add(ttx_s0_1, 0).unwrap(); + + // empty account returns zero + assert!( + pending_txs.pending_nonce(signing_address_1).is_none(), + "empty account should return None" + ); + + // non empty account returns highest nonce + assert_eq!( + pending_txs.pending_nonce(signing_address_0), + Some(1), + "should return highest nonce" + ); + } + + #[tokio::test] + async fn pending_transactions_builder_queue() { + let mut pending_txs = PendingTransactions::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + let signing_key_1 = SigningKey::from([2; 32]); + let signing_address_1 = signing_key_1.address_bytes(); + + // transactions to add to accounts + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + let ttx_s1_1 = mock_ttx(1, &signing_key_1); + let ttx_s1_2 = mock_ttx(2, &signing_key_1); + let ttx_s1_3 = mock_ttx(3, &signing_key_1); + + // add transactions + pending_txs.add(ttx_s0_1.clone(), 1).unwrap(); + pending_txs.add(ttx_s1_1.clone(), 1).unwrap(); + pending_txs.add(ttx_s1_2.clone(), 1).unwrap(); + pending_txs.add(ttx_s1_3.clone(), 1).unwrap(); + + // current nonce getter + // should return all transactions from signing_key_0 and last two from signing_key_1 + let current_account_nonce_getter = |address: [u8; 20]| async move { + if address == signing_address_0 { + Ok(1) + } else if address == signing_address_1 { + Ok(2) + } else { + Err(anyhow::anyhow!("invalid address")) + } + }; + + // get builder queue + let builder_queue = pending_txs + .builder_queue(¤t_account_nonce_getter) + .await + .expect("building builders queue should work"); + assert_eq!( + builder_queue.len(), + 3, + "three transactions should've been popped" + ); + + // check that the transactions are in the expected order + let (first_tx_hash, _) = builder_queue[0]; + assert_eq!( + first_tx_hash, ttx_s0_1.tx_hash, + "expected earliest transaction with lowest nonce difference (0) to be first" + ); + let (second_tx_hash, _) = builder_queue[1]; + assert_eq!( + second_tx_hash, ttx_s1_2.tx_hash, + "expected other low nonce diff (0) to be second" + ); + let (third_tx_hash, _) = builder_queue[2]; + assert_eq!( + third_tx_hash, ttx_s1_3.tx_hash, + "expected highest nonce diff to be last" + ); + + // ensure transactions not removed + assert_eq!( + pending_txs.len(), + 4, + "no transactions should've been removed" + ); + } + + #[tokio::test] + async fn parked_transactions_pop_front_account() { + let mut parked_txs = ParkedTransactions::::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + let signing_key_1 = SigningKey::from([2; 32]); + let signing_address_1 = signing_key_1.address_bytes(); + + // transactions to add to accounts + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + let ttx_s1_1 = mock_ttx(1, &signing_key_1); + let ttx_s1_2 = mock_ttx(2, &signing_key_1); + let ttx_s1_4 = mock_ttx(4, &signing_key_1); + + // add transactions + parked_txs.add(ttx_s0_1.clone(), 0).unwrap(); + parked_txs.add(ttx_s1_1.clone(), 0).unwrap(); + parked_txs.add(ttx_s1_2.clone(), 0).unwrap(); + parked_txs.add(ttx_s1_4.clone(), 0).unwrap(); + + // pop from account 1 + assert_eq!( + parked_txs.pop_front_account(&signing_address_0, 1).len(), + 1, + "one transactions should've been popped" + ); + assert_eq!(parked_txs.txs.len(), 1, "empty accounts should be removed"); + + // pop from account 2 + assert_eq!( + parked_txs.pop_front_account(&signing_address_1, 1).len(), + 2, + "two transactions should've been popped" + ); + assert_eq!( + parked_txs.txs.len(), + 1, + "non empty accounts should not be removed" + ); + + assert_eq!( + parked_txs.len(), + 1, + "1 transactions should be remaining from original 4" + ); + assert!(parked_txs.contains_tx(&ttx_s1_4.tx_hash)); + } + + #[tokio::test] + async fn parked_transactions_find_promotables() { + let mut parked_txs = ParkedTransactions::::new(TX_TTL); + let signing_key_0 = SigningKey::from([1; 32]); + let signing_address_0 = signing_key_0.address_bytes(); + let signing_key_1 = SigningKey::from([2; 32]); + let signing_address_1 = signing_key_1.address_bytes(); + + // transactions to add to accounts + let ttx_s0_1 = mock_ttx(1, &signing_key_0); + let ttx_s0_2 = mock_ttx(2, &signing_key_0); + let ttx_s0_3 = mock_ttx(3, &signing_key_0); + let ttx_s1_1 = mock_ttx(1, &signing_key_1); + let ttx_s1_2 = mock_ttx(2, &signing_key_1); + let ttx_s1_4 = mock_ttx(4, &signing_key_1); + + // add transactions + parked_txs.add(ttx_s0_1.clone(), 0).unwrap(); + parked_txs.add(ttx_s0_2.clone(), 0).unwrap(); + parked_txs.add(ttx_s0_3.clone(), 0).unwrap(); + parked_txs.add(ttx_s1_1.clone(), 0).unwrap(); + parked_txs.add(ttx_s1_2.clone(), 0).unwrap(); + parked_txs.add(ttx_s1_4.clone(), 0).unwrap(); + + // current nonce getter + // should pop all from signing_address_0 and two from signing_address_1 + let current_account_nonce_getter = |address: [u8; 20]| async move { + if address == signing_address_0 || address == signing_address_1 { + return Ok(1); + } + Err(anyhow::anyhow!("invalid address")) + }; + + assert_eq!( + parked_txs + .find_promotables(¤t_account_nonce_getter) + .await + .len(), + 5, + "five transactions should've been popped" + ); + assert_eq!(parked_txs.txs.len(), 1, "empty accounts should be removed"); + assert_eq!( + parked_txs.len(), + 1, + "1 transactions should be remaining from original 6" + ); + assert!(parked_txs.contains_tx(&ttx_s1_4.tx_hash)); + } +} diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index a57706023b..0049267657 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -202,6 +202,7 @@ mod test { use std::{ collections::HashMap, str::FromStr, + sync::Arc, }; use astria_core::{ @@ -287,12 +288,12 @@ mod test { let (mut consensus_service, mempool) = new_consensus_service(Some(signing_key.verification_key())).await; let tx = make_unsigned_tx(); - let signed_tx = tx.into_signed(&signing_key); - let tx_bytes = signed_tx.clone().into_raw().encode_to_vec(); + let signed_tx = Arc::new(tx.into_signed(&signing_key)); + let tx_bytes = signed_tx.to_raw().encode_to_vec(); let txs = vec![tx_bytes.into()]; mempool.insert(signed_tx.clone(), 0).await.unwrap(); - let res = generate_rollup_datas_commitment(&vec![signed_tx], HashMap::new()); + let res = generate_rollup_datas_commitment(&vec![(*signed_tx).clone()], HashMap::new()); let prepare_proposal = new_prepare_proposal_request(); let prepare_proposal_response = consensus_service @@ -492,10 +493,10 @@ mod test { new_consensus_service(Some(signing_key.verification_key())).await; let tx = make_unsigned_tx(); - let signed_tx = tx.into_signed(&signing_key); - let tx_bytes = signed_tx.clone().into_raw().encode_to_vec(); + let signed_tx = Arc::new(tx.into_signed(&signing_key)); + let tx_bytes = signed_tx.to_raw().encode_to_vec(); let txs = vec![tx_bytes.clone().into()]; - let res = generate_rollup_datas_commitment(&vec![signed_tx.clone()], HashMap::new()); + let res = generate_rollup_datas_commitment(&vec![(*signed_tx).clone()], HashMap::new()); let block_data = res.into_transactions(txs.clone()); let data_hash = diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool.rs index 838578cbd4..33bd874207 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool.rs @@ -1,5 +1,6 @@ use std::{ pin::Pin, + sync::Arc, task::{ Context, Poll, @@ -127,7 +128,6 @@ async fn handle_check_tx MAX_TX_SIZE { - mempool.remove(tx_hash).await; metrics.increment_check_tx_removed_too_large(); return response::CheckTx { code: Code::Err(AbciErrorCode::TRANSACTION_TOO_LARGE.value()), @@ -143,7 +143,6 @@ async fn handle_check_tx tx, Err(e) => { - mempool.remove(tx_hash).await; return response::CheckTx { code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), log: e.to_string(), @@ -155,7 +154,6 @@ async fn handle_check_tx tx, Err(e) => { - mempool.remove(tx_hash).await; return response::CheckTx { code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), info: "the provided bytes was not a valid protobuf-encoded SignedTransaction, or \ @@ -173,7 +171,6 @@ async fn handle_check_tx { metrics.increment_check_tx_removed_expired(); @@ -257,6 +255,34 @@ async fn handle_check_tx { + return response::CheckTx { + code: Code::Err(AbciErrorCode::INVALID_NONCE.value()), + info: "transaction removed from app mempool due to stale nonce".into(), + log: "Transaction from app mempool due to stale nonce".into(), + ..response::CheckTx::default() + }; + } + RemovalReason::LowerNonceInvalidated => { + return response::CheckTx { + code: Code::Err(AbciErrorCode::LOWER_NONCE_INVALIDATED.value()), + info: "transaction removed from app mempool due to lower nonce being \ + invalidated" + .into(), + log: "Transaction removed from app mempool due to lower nonce being \ + invalidated" + .into(), + ..response::CheckTx::default() + }; + } + RemovalReason::FailedCheckTx(err) => { + return response::CheckTx { + code: Code::Err(AbciErrorCode::TRANSACTION_FAILED.value()), + info: "transaction failed check tx".into(), + log: format!("transaction failed check tx because: {err}"), + ..response::CheckTx::default() + }; + } } }; @@ -285,13 +311,18 @@ async fn handle_check_tx Date: Tue, 20 Aug 2024 09:11:34 -0700 Subject: [PATCH 12/18] fix(ci): enable bridge withdrawer building with tag (#1374) ## Summary Updates the image build to work on a specific tag when binary name and image name don't match ## Background The `astria-bridge-withdrawer` binary is pushed to an `evm-bridge-withdrawer` docker package and the tag used is `bridge-withdrawer-v` for releases. This is potentially suboptimal but has already been done. Our image build script doesn't work with this setup. ## Changes - update image build to consider `binary-name` without `astria` prefix and take a separate `package-name` for most instances these are the same but for when they are not it supports it. There is also a `binary-prefix` which can be set to provide more flexibility for the future. ## Testing CI/CD tests ## Related Issues closes https://github.com/astriaorg/astria/issues/1373 --- .github/workflows/build.yml | 2 +- .github/workflows/docker-build.yml | 10 +++++----- .github/workflows/reusable-docker-build.yml | 13 ++++++++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5a73872f9..1e8ddcfc10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: with: package-name: 'conductor' tag: ${{ inputs.tag }} - + composer: uses: './.github/workflows/reusable-build.yml' with: diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 53ba569426..9b111c4a90 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -49,7 +49,7 @@ jobs: with: depot-project-id: mhgvgvsjnx package-name: composer - target-binary: astria-composer + binary-name: composer tag: ${{ inputs.tag }} secrets: inherit @@ -64,7 +64,7 @@ jobs: with: depot-project-id: zrh9t1d84s package-name: conductor - target-binary: astria-conductor + binary-name: conductor tag: ${{ inputs.tag }} secrets: inherit @@ -79,7 +79,7 @@ jobs: with: depot-project-id: brzhxfbv9b package-name: sequencer - target-binary: astria-sequencer + binary-name: sequencer tag: ${{ inputs.tag }} secrets: inherit @@ -94,7 +94,7 @@ jobs: with: depot-project-id: 86q4kz4wfs package-name: sequencer-relayer - target-binary: astria-sequencer-relayer + binary-name: sequencer-relayer tag: ${{ inputs.tag }} secrets: inherit @@ -109,7 +109,7 @@ jobs: with: depot-project-id: dl81f3fc6x package-name: evm-bridge-withdrawer - target-binary: astria-bridge-withdrawer + binary-name: bridge-withdrawer tag: ${{ inputs.tag }} secrets: inherit diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml index 4ce69b6edf..b50484a8e0 100644 --- a/.github/workflows/reusable-docker-build.yml +++ b/.github/workflows/reusable-docker-build.yml @@ -9,9 +9,12 @@ on: package-name: required: true type: string - target-binary: + binary-name: required: true type: string + binary-prefix: + type: string + default: astria tag: required: false type: string @@ -23,7 +26,7 @@ on: env: REGISTRY: ghcr.io FULL_REF: ${{ inputs.tag && format('refs/tags/{0}', inputs.tag) || github.ref }} - + FULL_BINARY_NAME: ${{ inputs.binary-prefix }}-${{ inputs.binary-name }} jobs: build-and-push: runs-on: ubuntu-latest @@ -31,7 +34,7 @@ jobs: contents: read id-token: write packages: write - if: startsWith(inputs.tag, inputs.package-name) || !inputs.tag && (startsWith(github.ref, format('refs/tags/{0}-v', inputs.package-name)) || github.ref == 'refs/heads/main' || github.event_name == 'pull_request' || github.event_name == 'merge_group') + if: startsWith(inputs.tag, inputs.binary-name) || !inputs.tag && (startsWith(github.ref, format('refs/tags/{0}-v', inputs.binary-name)) || github.ref == 'refs/heads/main' || github.event_name == 'pull_request' || github.event_name == 'merge_group') steps: # Checking out the repo - uses: actions/checkout@v4 @@ -59,7 +62,7 @@ jobs: images: ${{ format('ghcr.io/astriaorg/{0}', inputs.package-name) }} tags: | type=ref,event=pr - type=match,pattern=refs/tags/${{ inputs.package-name }}-v(.*),group=1,enable=${{ startsWith(env.FULL_REF, 'refs/tags/') }},value=${{ env.FULL_REF }} + type=match,pattern=refs/tags/${{ inputs.binary-name }}-v(.*),group=1,enable=${{ startsWith(env.FULL_REF, 'refs/tags/') }},value=${{ env.FULL_REF }} type=sha # set latest tag for `main` branch type=raw,value=latest,enable=${{ env.FULL_REF == format('refs/heads/{0}', 'main') }} @@ -72,7 +75,7 @@ jobs: context: . file: containerfiles/Dockerfile build-args: | - TARGETBINARY=${{ inputs.target-binary }} + TARGETBINARY=${{ env.FULL_BINARY_NAME }} platforms: "linux/amd64,linux/arm64" push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'astriaorg/astria' }} tags: ${{ steps.metadata.outputs.tags }} From f69ffe22a53b063984c83c5993798e249b39c46d Mon Sep 17 00:00:00 2001 From: Jordan Oroshiba Date: Tue, 20 Aug 2024 15:59:15 -0700 Subject: [PATCH 13/18] fix(proto): fix import name mismatch (#1380) ## Summary Updates the generated path names to match the actual proto dependency name. ## Background Prost makes assumptions on how modules will be imported, where the proto and rust generated code modules match, but does not auto generate the module. We had a mismatch of singular -> plural in two places this resolves that. ## Changes - updates generated rust `account` and `transaction` modules to `accounts` and `transactions` ## Testing test suite --- .../src/bridge_withdrawer/startup.rs | 8 ++++---- .../tests/blackbox/helpers/mock_cometbft.rs | 2 +- crates/astria-composer/src/executor/tests.rs | 4 ++-- crates/astria-composer/tests/blackbox/geth_collector.rs | 2 +- crates/astria-composer/tests/blackbox/grpc_collector.rs | 2 +- .../tests/blackbox/helper/mock_sequencer.rs | 2 +- crates/astria-composer/tests/blackbox/helper/mod.rs | 2 +- crates/astria-core/src/generated/mod.rs | 4 ++-- crates/astria-core/src/protocol/account/mod.rs | 2 +- crates/astria-core/src/protocol/transaction/mod.rs | 2 +- crates/astria-core/src/sequencerblock/v1alpha1/block.rs | 2 +- crates/astria-sequencer-client/src/extension_trait.rs | 6 +++--- crates/astria-sequencer-client/src/tests/http.rs | 6 +++--- crates/astria-sequencer/src/app/mod.rs | 2 +- crates/astria-sequencer/src/service/info/mod.rs | 2 +- crates/astria-sequencer/src/service/mempool.rs | 2 +- crates/astria-sequencer/src/transaction/query.rs | 2 +- 17 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs index 52412ccdc6..a040347ec9 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs @@ -284,16 +284,16 @@ impl Startup { ); let proto_tx = - astria_core::generated::protocol::transaction::v1alpha1::SignedTransaction::decode( + astria_core::generated::protocol::transactions::v1alpha1::SignedTransaction::decode( &*last_transaction.tx, ) .wrap_err_with(|| format!( - "failed to decode data in Sequencer CometBFT transaction as `{}`", - astria_core::generated::protocol::transaction::v1alpha1::SignedTransaction::full_name(), + "failed to decode data in Sequencer CometBFT transaction as `{}`", + astria_core::generated::protocol::transactions::v1alpha1::SignedTransaction::full_name(), ))?; let tx = SignedTransaction::try_from_raw(proto_tx) - .wrap_err_with(|| format!("failed to verify {}", astria_core::generated::protocol::transaction::v1alpha1::SignedTransaction::full_name()))?; + .wrap_err_with(|| format!("failed to verify {}", astria_core::generated::protocol::transactions::v1alpha1::SignedTransaction::full_name()))?; info!( last_bridge_account_tx.hash = %telemetry::display::hex(&tx_hash), diff --git a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs index 29b17f1c34..d6c7a0ea2d 100644 --- a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs +++ b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs @@ -341,7 +341,7 @@ fn prepare_broadcast_tx_commit_response(response: tx_commit::Response) -> Mock { /// Convert a `Request` object to a `SignedTransaction` pub fn signed_tx_from_request(request: &wiremock::Request) -> SignedTransaction { - use astria_core::generated::protocol::transaction::v1alpha1::SignedTransaction as RawSignedTransaction; + use astria_core::generated::protocol::transactions::v1alpha1::SignedTransaction as RawSignedTransaction; use prost::Message as _; let wrapped_tx_sync_req: tendermint_rpc::request::Wrapper = diff --git a/crates/astria-composer/src/executor/tests.rs b/crates/astria-composer/src/executor/tests.rs index 0529fd449f..9aa0ea265f 100644 --- a/crates/astria-composer/src/executor/tests.rs +++ b/crates/astria-composer/src/executor/tests.rs @@ -4,7 +4,7 @@ use std::{ }; use astria_core::{ - generated::protocol::account::v1alpha1::NonceResponse, + generated::protocol::accounts::v1alpha1::NonceResponse, primitive::v1::{ RollupId, ROLLUP_ID_LEN, @@ -174,7 +174,7 @@ async fn mount_default_nonce_query_mock(server: &MockServer) -> MockGuard { /// Convert a `Request` object to a `SignedTransaction` fn signed_tx_from_request(request: &Request) -> SignedTransaction { - use astria_core::generated::protocol::transaction::v1alpha1::SignedTransaction as RawSignedTransaction; + use astria_core::generated::protocol::transactions::v1alpha1::SignedTransaction as RawSignedTransaction; use prost::Message as _; let wrapped_tx_sync_req: request::Wrapper = diff --git a/crates/astria-composer/tests/blackbox/geth_collector.rs b/crates/astria-composer/tests/blackbox/geth_collector.rs index ae87a59b06..e6bce832e9 100644 --- a/crates/astria-composer/tests/blackbox/geth_collector.rs +++ b/crates/astria-composer/tests/blackbox/geth_collector.rs @@ -1,7 +1,7 @@ use std::time::Duration; use astria_core::{ - generated::protocol::account::v1alpha1::NonceResponse, + generated::protocol::accounts::v1alpha1::NonceResponse, primitive::v1::RollupId, }; use ethers::types::Transaction; diff --git a/crates/astria-composer/tests/blackbox/grpc_collector.rs b/crates/astria-composer/tests/blackbox/grpc_collector.rs index e1ebfe70b3..8a4a963acc 100644 --- a/crates/astria-composer/tests/blackbox/grpc_collector.rs +++ b/crates/astria-composer/tests/blackbox/grpc_collector.rs @@ -6,7 +6,7 @@ use astria_core::{ grpc_collector_service_client::GrpcCollectorServiceClient, SubmitRollupTransactionRequest, }, - protocol::account::v1alpha1::NonceResponse, + protocol::accounts::v1alpha1::NonceResponse, }, primitive::v1::RollupId, }; diff --git a/crates/astria-composer/tests/blackbox/helper/mock_sequencer.rs b/crates/astria-composer/tests/blackbox/helper/mock_sequencer.rs index 546fce21d7..da8dea87e0 100644 --- a/crates/astria-composer/tests/blackbox/helper/mock_sequencer.rs +++ b/crates/astria-composer/tests/blackbox/helper/mock_sequencer.rs @@ -29,7 +29,7 @@ use wiremock::{ }; pub async fn start() -> (MockServer, MockGuard) { - use astria_core::generated::protocol::account::v1alpha1::NonceResponse; + use astria_core::generated::protocol::accounts::v1alpha1::NonceResponse; let server = MockServer::start().await; let startup_guard = mount_abci_query_mock( &server, diff --git a/crates/astria-composer/tests/blackbox/helper/mod.rs b/crates/astria-composer/tests/blackbox/helper/mod.rs index 02bcd46bc9..8747680d97 100644 --- a/crates/astria-composer/tests/blackbox/helper/mod.rs +++ b/crates/astria-composer/tests/blackbox/helper/mod.rs @@ -153,7 +153,7 @@ pub async fn loop_until_composer_is_ready(addr: SocketAddr) { } fn signed_tx_from_request(request: &Request) -> SignedTransaction { - use astria_core::generated::protocol::transaction::v1alpha1::SignedTransaction as RawSignedTransaction; + use astria_core::generated::protocol::transactions::v1alpha1::SignedTransaction as RawSignedTransaction; use prost::Message as _; let wrapped_tx_sync_req: request::Wrapper = diff --git a/crates/astria-core/src/generated/mod.rs b/crates/astria-core/src/generated/mod.rs index 5762c6e3c5..d476b83f21 100644 --- a/crates/astria-core/src/generated/mod.rs +++ b/crates/astria-core/src/generated/mod.rs @@ -69,7 +69,7 @@ pub mod primitive { #[path = ""] pub mod protocol { #[path = ""] - pub mod account { + pub mod accounts { #[path = "astria.protocol.accounts.v1alpha1.rs"] pub mod v1alpha1; } @@ -96,7 +96,7 @@ pub mod protocol { } } #[path = ""] - pub mod transaction { + pub mod transactions { pub mod v1alpha1 { include!("astria.protocol.transactions.v1alpha1.rs"); diff --git a/crates/astria-core/src/protocol/account/mod.rs b/crates/astria-core/src/protocol/account/mod.rs index 465b5ded61..99391df50d 100644 --- a/crates/astria-core/src/protocol/account/mod.rs +++ b/crates/astria-core/src/protocol/account/mod.rs @@ -1,3 +1,3 @@ pub mod v1alpha1; -use crate::generated::protocol::account::v1alpha1 as raw; +use crate::generated::protocol::accounts::v1alpha1 as raw; diff --git a/crates/astria-core/src/protocol/transaction/mod.rs b/crates/astria-core/src/protocol/transaction/mod.rs index 5607443876..1f44391b58 100644 --- a/crates/astria-core/src/protocol/transaction/mod.rs +++ b/crates/astria-core/src/protocol/transaction/mod.rs @@ -1,3 +1,3 @@ pub mod v1alpha1; -use crate::generated::protocol::transaction::v1alpha1 as raw; +use crate::generated::protocol::transactions::v1alpha1 as raw; diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs index 140f8c3706..a6304c8baa 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs @@ -727,7 +727,7 @@ impl SequencerBlock { let mut rollup_datas = IndexMap::new(); for elem in data_list { let raw_tx = - crate::generated::protocol::transaction::v1alpha1::SignedTransaction::decode( + crate::generated::protocol::transactions::v1alpha1::SignedTransaction::decode( &*elem, ) .map_err(SequencerBlockError::signed_transaction_protobuf_decode)?; diff --git a/crates/astria-sequencer-client/src/extension_trait.rs b/crates/astria-sequencer-client/src/extension_trait.rs index 5180f0b13f..7f68100582 100644 --- a/crates/astria-sequencer-client/src/extension_trait.rs +++ b/crates/astria-sequencer-client/src/extension_trait.rs @@ -448,7 +448,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::account::v1alpha1::BalanceResponse::decode( + astria_core::generated::protocol::accounts::v1alpha1::BalanceResponse::decode( &*response.value, ) .map_err(|e| { @@ -530,7 +530,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::account::v1alpha1::NonceResponse::decode( + astria_core::generated::protocol::accounts::v1alpha1::NonceResponse::decode( &*response.value, ) .map_err(|e| { @@ -627,7 +627,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::transaction::v1alpha1::TransactionFeeResponse::decode( + astria_core::generated::protocol::transactions::v1alpha1::TransactionFeeResponse::decode( &*response.value, ) .map_err(|e| { diff --git a/crates/astria-sequencer-client/src/tests/http.rs b/crates/astria-sequencer-client/src/tests/http.rs index a7f40c365f..df339f0691 100644 --- a/crates/astria-sequencer-client/src/tests/http.rs +++ b/crates/astria-sequencer-client/src/tests/http.rs @@ -163,7 +163,7 @@ fn create_signed_transaction() -> SignedTransaction { #[tokio::test] async fn get_latest_nonce() { - use astria_core::generated::protocol::account::v1alpha1::NonceResponse; + use astria_core::generated::protocol::accounts::v1alpha1::NonceResponse; let MockSequencer { server, client, @@ -190,7 +190,7 @@ async fn get_latest_nonce() { #[tokio::test] async fn get_latest_balance() { - use astria_core::generated::protocol::account::v1alpha1::{ + use astria_core::generated::protocol::accounts::v1alpha1::{ AssetBalance, BalanceResponse, }; @@ -317,7 +317,7 @@ async fn get_bridge_account_last_transaction_hash() { #[tokio::test] async fn get_transaction_fee() { - use astria_core::generated::protocol::transaction::v1alpha1::{ + use astria_core::generated::protocol::transactions::v1alpha1::{ TransactionFee, TransactionFeeResponse, }; diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 522457ecd0..bdfcbd416d 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -20,7 +20,7 @@ use anyhow::{ Context, }; use astria_core::{ - generated::protocol::transaction::v1alpha1 as raw, + generated::protocol::transactions::v1alpha1 as raw, protocol::{ abci::AbciErrorCode, transaction::v1alpha1::{ diff --git a/crates/astria-sequencer/src/service/info/mod.rs b/crates/astria-sequencer/src/service/info/mod.rs index d64c46689c..c0135c13ea 100644 --- a/crates/astria-sequencer/src/service/info/mod.rs +++ b/crates/astria-sequencer/src/service/info/mod.rs @@ -204,7 +204,7 @@ mod test { #[tokio::test] async fn handle_balance_query() { use astria_core::{ - generated::protocol::account::v1alpha1 as raw, + generated::protocol::accounts::v1alpha1 as raw, protocol::account::v1alpha1::AssetBalance, }; diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool.rs index 33bd874207..cfabd145bc 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool.rs @@ -10,7 +10,7 @@ use std::{ use anyhow::Context as _; use astria_core::{ - generated::protocol::transaction::v1alpha1 as raw, + generated::protocol::transactions::v1alpha1 as raw, protocol::{ abci::AbciErrorCode, transaction::v1alpha1::SignedTransaction, diff --git a/crates/astria-sequencer/src/transaction/query.rs b/crates/astria-sequencer/src/transaction/query.rs index e6a36c1e02..bcb0df0538 100644 --- a/crates/astria-sequencer/src/transaction/query.rs +++ b/crates/astria-sequencer/src/transaction/query.rs @@ -1,5 +1,5 @@ use astria_core::{ - generated::protocol::transaction::v1alpha1::UnsignedTransaction as RawUnsignedTransaction, + generated::protocol::transactions::v1alpha1::UnsignedTransaction as RawUnsignedTransaction, protocol::{ abci::AbciErrorCode, transaction::v1alpha1::UnsignedTransaction, From 1f4a35931233136892a336af16ab30fd4d0f9688 Mon Sep 17 00:00:00 2001 From: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:20:52 +0100 Subject: [PATCH 14/18] perf(sequencer): add benchmark for prepare_proposal (ENG-660) (#1337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Addition of a new benchmark target to assess the performance of the prepare_proposal method in sequencer's `App`. ## Background Previous perf work has indicated this is a bottleneck. However, making that determination was done via spamoor which is slightly convoluted to run. Before working to improve the performance, we want to have a faster feedback loop on the effects of updates, hence the need for a benchmark which is easy to run and which isolates the slow function. ## Changes - Added benchmark to `app` module. Currently this has only one case: a mempool filled with transactions containing exclusively transfers. This matches the shape of the data being sent when using spamoor. - Added `benchmark` feature to enable sharing some of the existing test utils. ## Testing This is a new test. Example of running `cargo bench --features=benchmark -qp astria-sequencer app` on my Ryzen 7900X: ``` Timer precision: 10 ns astria_sequencer fastest │ slowest │ median │ mean │ samples │ iters ╰─ app │ │ │ │ │ ╰─ benchmarks │ │ │ │ │ ╰─ execute_transactions_prepare_proposal 11.63 s │ 14.68 s │ 12.74 s │ 12.88 s │ 10 │ 10 ``` Since rebasing after #1317 has merged, the same run shows (as expected) a slowdown: ``` astria_sequencer fastest │ slowest │ median │ mean │ samples │ iters ╰─ app │ │ │ │ │ ╰─ benchmarks │ │ │ │ │ ╰─ execute_transactions_prepare_proposal 14.49 s │ 17 s │ 16.52 s │ 15.98 s │ 8 │ 8 ``` ## Related Issues Closes #1314. --- crates/astria-sequencer/Cargo.toml | 5 +- crates/astria-sequencer/src/app/benchmarks.rs | 113 +++++++++++++++ crates/astria-sequencer/src/app/mod.rs | 10 +- crates/astria-sequencer/src/app/test_utils.rs | 6 + .../astria-sequencer/src/benchmark_utils.rs | 132 ++++++++++++++++++ crates/astria-sequencer/src/lib.rs | 4 +- .../src/mempool/benchmarks.rs | 97 +++---------- crates/astria-sequencer/src/mempool/mod.rs | 1 + crates/astria-sequencer/src/test_utils.rs | 18 +-- 9 files changed, 288 insertions(+), 98 deletions(-) create mode 100644 crates/astria-sequencer/src/app/benchmarks.rs create mode 100644 crates/astria-sequencer/src/benchmark_utils.rs diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index 3647d34af5..d31f05df58 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://astria.org" name = "astria-sequencer" [features] -default = [] +benchmark = ["divan"] [dependencies] astria-core = { path = "../astria-core", features = ["server", "serde"] } @@ -37,7 +37,7 @@ tower-http = { version = "0.4", features = ["cors"] } async-trait = { workspace = true } bytes = { workspace = true } -divan = { workspace = true } +divan = { workspace = true, optional = true } futures = { workspace = true } hex = { workspace = true, features = ["serde"] } ibc-types = { workspace = true, features = ["with_serde"] } @@ -77,3 +77,4 @@ astria-build-info = { path = "../astria-build-info", features = ["build"] } [[bench]] name = "benchmark" harness = false +required-features = ["benchmark"] diff --git a/crates/astria-sequencer/src/app/benchmarks.rs b/crates/astria-sequencer/src/app/benchmarks.rs new file mode 100644 index 0000000000..db5448f668 --- /dev/null +++ b/crates/astria-sequencer/src/app/benchmarks.rs @@ -0,0 +1,113 @@ +//! To run the benchmark, from the root of the monorepo, run: +//! ```sh +//! cargo bench --features=benchmark -qp astria-sequencer app +//! ``` + +use std::time::Duration; + +use astria_core::sequencer::{ + Account, + AddressPrefixes, + GenesisState, + UncheckedGenesisState, +}; +use cnidarium::Storage; +use penumbra_ibc::params::IBCParameters; + +use crate::{ + app::{ + test_utils, + App, + }, + benchmark_utils::{ + self, + TxTypes, + SIGNER_COUNT, + }, + proposal::block_size_constraints::BlockSizeConstraints, + test_utils::{ + astria_address, + nria, + ASTRIA_PREFIX, + }, +}; + +/// The max time for any benchmark. +const MAX_TIME: Duration = Duration::from_secs(120); +/// The value provided to `BlockSizeConstraints::new` to constrain block sizes. +/// +/// Taken from the actual value seen in `prepare_proposal.max_tx_bytes` when handling +/// `prepare_proposal` during stress testing using spamoor. +const COMETBFT_MAX_TX_BYTES: usize = 22_019_254; + +struct Fixture { + app: App, + _storage: Storage, +} + +impl Fixture { + /// Initializes a new `App` instance with the genesis accounts derived from the secret keys of + /// `benchmark_utils::signing_keys()`, and inserts transactions into the app mempool. + async fn new() -> Fixture { + let accounts = benchmark_utils::signing_keys() + .enumerate() + .take(usize::from(SIGNER_COUNT)) + .map(|(index, signing_key)| Account { + address: astria_address(&signing_key.address_bytes()), + balance: 10u128 + .pow(19) + .saturating_add(u128::try_from(index).unwrap()), + }) + .collect::>(); + let address_prefixes = AddressPrefixes { + base: ASTRIA_PREFIX.into(), + }; + let first_address = accounts.first().unwrap().address; + let unchecked_genesis_state = UncheckedGenesisState { + accounts, + address_prefixes, + authority_sudo_address: first_address, + ibc_sudo_address: first_address, + ibc_relayer_addresses: vec![], + native_asset_base_denomination: nria(), + ibc_params: IBCParameters::default(), + allowed_fee_assets: vec![nria().into()], + fees: test_utils::default_fees(), + }; + let genesis_state = GenesisState::try_from(unchecked_genesis_state).unwrap(); + + let (app, storage) = + test_utils::initialize_app_with_storage(Some(genesis_state), vec![]).await; + + for tx in benchmark_utils::transactions(TxTypes::AllTransfers) { + app.mempool.insert(tx.clone(), 0).await.unwrap(); + } + Fixture { + app, + _storage: storage, + } + } +} + +#[divan::bench(max_time = MAX_TIME)] +fn execute_transactions_prepare_proposal(bencher: divan::Bencher) { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + let mut fixture = runtime.block_on(async { Fixture::new().await }); + bencher + .with_inputs(|| BlockSizeConstraints::new(COMETBFT_MAX_TX_BYTES).unwrap()) + .bench_local_refs(|constraints| { + let (_tx_bytes, included_txs) = runtime.block_on(async { + fixture + .app + .execute_transactions_prepare_proposal(constraints) + .await + .unwrap() + }); + // Ensure we actually processed some txs. This will trip if execution fails for all + // txs, or more likely, if the mempool becomes exhausted of txs. + assert!(!included_txs.is_empty()); + }); +} diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index bdfcbd416d..9119729fb9 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -1,4 +1,6 @@ -#[cfg(test)] +#[cfg(feature = "benchmark")] +mod benchmarks; +#[cfg(any(test, feature = "benchmark"))] pub(crate) mod test_utils; #[cfg(test)] mod tests_app; @@ -944,7 +946,7 @@ impl App { } #[instrument(name = "App::begin_block", skip_all)] - pub(crate) async fn begin_block( + async fn begin_block( &mut self, begin_block: &abci::request::BeginBlock, ) -> anyhow::Result> { @@ -981,7 +983,7 @@ impl App { /// Executes a signed transaction. #[instrument(name = "App::execute_transaction", skip_all)] - pub(crate) async fn execute_transaction( + async fn execute_transaction( &mut self, signed_tx: Arc, ) -> anyhow::Result> { @@ -1004,7 +1006,7 @@ impl App { } #[instrument(name = "App::end_block", skip_all)] - pub(crate) async fn end_block( + async fn end_block( &mut self, height: u64, fee_recipient: [u8; 20], diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index ebb1e9eabb..fcc4223fa1 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -37,6 +37,7 @@ pub(crate) const CAROL_ADDRESS: &str = "60709e2d391864b732b4f0f51e387abb76743871 pub(crate) const JUDY_ADDRESS: &str = "bc5b91da07778eeaf622d0dcf4d7b4233525998d"; pub(crate) const TED_ADDRESS: &str = "4c4f91d8a918357ab5f6f19c1e179968fc39bb44"; +#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) fn get_alice_signing_key() -> SigningKey { // this secret key corresponds to ALICE_ADDRESS let alice_secret_bytes: [u8; 32] = @@ -47,6 +48,7 @@ pub(crate) fn get_alice_signing_key() -> SigningKey { SigningKey::from(alice_secret_bytes) } +#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) fn get_bridge_signing_key() -> SigningKey { let bridge_secret_bytes: [u8; 32] = hex::decode("db4982e01f3eba9e74ac35422fcd49aa2b47c3c535345c7e7da5220fe3a0ce79") @@ -56,6 +58,7 @@ pub(crate) fn get_bridge_signing_key() -> SigningKey { SigningKey::from(bridge_secret_bytes) } +#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) fn default_genesis_accounts() -> Vec { vec![ Account { @@ -73,6 +76,7 @@ pub(crate) fn default_genesis_accounts() -> Vec { ] } +#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) fn default_fees() -> Fees { Fees { transfer_base_fee: 12, @@ -132,6 +136,7 @@ pub(crate) async fn initialize_app_with_storage( (app, storage.clone()) } +#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) async fn initialize_app( genesis_state: Option, genesis_validators: Vec, @@ -140,6 +145,7 @@ pub(crate) async fn initialize_app( app } +#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) fn mock_tx( nonce: u32, signer: &SigningKey, diff --git a/crates/astria-sequencer/src/benchmark_utils.rs b/crates/astria-sequencer/src/benchmark_utils.rs new file mode 100644 index 0000000000..ca1b30311b --- /dev/null +++ b/crates/astria-sequencer/src/benchmark_utils.rs @@ -0,0 +1,132 @@ +use std::{ + collections::HashMap, + sync::{ + Arc, + OnceLock, + }, +}; + +use astria_core::{ + crypto::SigningKey, + primitive::v1::{ + asset::{ + Denom, + IbcPrefixed, + }, + RollupId, + }, + protocol::transaction::v1alpha1::{ + action::{ + Action, + SequenceAction, + TransferAction, + }, + SignedTransaction, + TransactionParams, + UnsignedTransaction, + }, +}; + +use crate::test_utils::{ + astria_address, + nria, +}; + +/// The number of different signers of transactions, and also the number of different chain IDs. +pub(crate) const SIGNER_COUNT: u8 = 10; +/// The number of transfers per transaction. +/// +/// 2866 chosen after experimentation of spamming composer. +pub(crate) const TRANSFERS_PER_TX: usize = 2866; + +const SEQUENCE_ACTION_TX_COUNT: usize = 100_001; +const TRANSFERS_TX_COUNT: usize = 10_000; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub(crate) enum TxTypes { + AllSequenceActions, + AllTransfers, +} + +/// Returns an endlessly-repeating iterator over `SIGNER_COUNT` separate signing keys. +pub(crate) fn signing_keys() -> impl Iterator { + static SIGNING_KEYS: OnceLock> = OnceLock::new(); + SIGNING_KEYS + .get_or_init(|| { + (0..SIGNER_COUNT) + .map(|i| SigningKey::from([i; 32])) + .collect() + }) + .iter() + .cycle() +} + +/// Returns a static ref to a collection of `MAX_INITIAL_TXS + 1` transactions. +pub(crate) fn transactions(tx_types: TxTypes) -> &'static Vec> { + static TXS: OnceLock>>> = OnceLock::new(); + TXS.get_or_init(|| { + let mut map = HashMap::new(); + map.insert(TxTypes::AllSequenceActions, sequence_actions()); + map.insert(TxTypes::AllTransfers, transfers()); + map + }) + .get(&tx_types) + .unwrap() +} + +fn sequence_actions() -> Vec> { + let mut nonces_and_chain_ids = HashMap::new(); + signing_keys() + .map(move |signing_key| { + let verification_key = signing_key.verification_key(); + let (nonce, chain_id) = nonces_and_chain_ids + .entry(verification_key) + .or_insert_with(|| (0_u32, format!("chain-{}", signing_key.verification_key()))); + *nonce = (*nonce).wrapping_add(1); + let params = TransactionParams::builder() + .nonce(*nonce) + .chain_id(chain_id.as_str()) + .build(); + let sequence_action = SequenceAction { + rollup_id: RollupId::new([1; 32]), + data: vec![2; 1000].into(), + fee_asset: Denom::IbcPrefixed(IbcPrefixed::new([3; 32])), + }; + let tx = UnsignedTransaction { + actions: vec![Action::Sequence(sequence_action)], + params, + } + .into_signed(signing_key); + Arc::new(tx) + }) + .take(SEQUENCE_ACTION_TX_COUNT) + .collect() +} + +fn transfers() -> Vec> { + let sender = signing_keys().next().unwrap(); + let receiver = signing_keys().nth(1).unwrap(); + let to = astria_address(&receiver.address_bytes()); + let action = Action::from(TransferAction { + to, + amount: 1, + asset: nria().into(), + fee_asset: nria().into(), + }); + (0..TRANSFERS_TX_COUNT) + .map(|nonce| { + let params = TransactionParams::builder() + .nonce(u32::try_from(nonce).unwrap()) + .chain_id("test") + .build(); + let tx = UnsignedTransaction { + actions: std::iter::repeat(action.clone()) + .take(TRANSFERS_PER_TX) + .collect(), + params, + } + .into_signed(sender); + Arc::new(tx) + }) + .collect() +} diff --git a/crates/astria-sequencer/src/lib.rs b/crates/astria-sequencer/src/lib.rs index dc84d73303..ca41a14735 100644 --- a/crates/astria-sequencer/src/lib.rs +++ b/crates/astria-sequencer/src/lib.rs @@ -4,6 +4,8 @@ mod api_state_ext; pub(crate) mod app; pub(crate) mod assets; pub(crate) mod authority; +#[cfg(feature = "benchmark")] +pub(crate) mod benchmark_utils; pub(crate) mod bridge; mod build_info; pub(crate) mod component; @@ -19,7 +21,7 @@ mod sequencer; pub(crate) mod service; pub(crate) mod state_ext; pub(crate) mod storage_keys; -#[cfg(test)] +#[cfg(any(test, feature = "benchmark"))] pub(crate) mod test_utils; pub(crate) mod transaction; mod utils; diff --git a/crates/astria-sequencer/src/mempool/benchmarks.rs b/crates/astria-sequencer/src/mempool/benchmarks.rs index d8dbb7e878..0b61dc4630 100644 --- a/crates/astria-sequencer/src/mempool/benchmarks.rs +++ b/crates/astria-sequencer/src/mempool/benchmarks.rs @@ -1,97 +1,30 @@ +//! To run the benchmark, from the root of the monorepo, run: +//! ```sh +//! cargo bench --features=benchmark -qp astria-sequencer mempool +//! ``` #![allow(non_camel_case_types)] use std::{ - collections::HashMap, - sync::{ - Arc, - OnceLock, - }, + sync::Arc, time::Duration, }; -use astria_core::{ - crypto::SigningKey, - primitive::v1::{ - asset::{ - Denom, - IbcPrefixed, - }, - RollupId, - }, - protocol::transaction::v1alpha1::{ - action::{ - Action, - SequenceAction, - }, - SignedTransaction, - TransactionParams, - UnsignedTransaction, - }, -}; +use astria_core::protocol::transaction::v1alpha1::SignedTransaction; use sha2::{ Digest as _, Sha256, }; -use super::{ - Mempool, - RemovalReason, +use crate::{ + benchmark_utils::SIGNER_COUNT, + mempool::{ + Mempool, + RemovalReason, + }, }; -/// The maximum number of transactions with which to initialize the mempool. -const MAX_INITIAL_TXS: usize = 100_000; /// The max time for any benchmark. const MAX_TIME: Duration = Duration::from_secs(30); -/// The number of different signers of transactions, and also the number of different chain IDs. -const SIGNER_COUNT: u8 = 10; - -/// Returns an endlessly-repeating iterator over `SIGNER_COUNT` separate signing keys. -fn signing_keys() -> impl Iterator { - static SIGNING_KEYS: OnceLock> = OnceLock::new(); - SIGNING_KEYS - .get_or_init(|| { - (0..SIGNER_COUNT) - .map(|i| SigningKey::from([i; 32])) - .collect() - }) - .iter() - .cycle() -} - -/// Returns a static ref to a collection of `MAX_INITIAL_TXS + 1` transactions. -fn transactions() -> &'static Vec> { - static TXS: OnceLock>> = OnceLock::new(); - TXS.get_or_init(|| { - let mut nonces_and_chain_ids = HashMap::new(); - signing_keys() - .map(move |signing_key| { - let verification_key = signing_key.verification_key(); - let (nonce, chain_id) = nonces_and_chain_ids - .entry(verification_key) - .or_insert_with(|| { - (0_u32, format!("chain-{}", signing_key.verification_key())) - }); - *nonce = (*nonce).wrapping_add(1); - let params = TransactionParams::builder() - .nonce(*nonce) - .chain_id(chain_id.as_str()) - .build(); - let sequence_action = SequenceAction { - rollup_id: RollupId::new([1; 32]), - data: vec![2; 1000].into(), - fee_asset: Denom::IbcPrefixed(IbcPrefixed::new([3; 32])), - }; - let tx = UnsignedTransaction { - actions: vec![Action::Sequence(sequence_action)], - params, - } - .into_signed(signing_key); - Arc::new(tx) - }) - .take(MAX_INITIAL_TXS + 1) - .collect() - }) -} /// This trait exists so we can get better output from `divan` by configuring the various mempool /// sizes as types rather than consts. With types we get output like: @@ -112,7 +45,7 @@ trait MempoolSize { fn size() -> usize; fn checked_size() -> usize { - assert!(Self::size() <= MAX_INITIAL_TXS); + assert!(Self::size() <= transactions().len()); Self::size() } } @@ -149,6 +82,10 @@ impl MempoolSize for mempool_with_100000_txs { } } +fn transactions() -> &'static Vec> { + crate::benchmark_utils::transactions(crate::benchmark_utils::TxTypes::AllSequenceActions) +} + /// Returns a new `Mempool` initialized with the number of transactions specified by `T::size()` /// taken from the static `transactions()`, and with a full `comet_bft_removal_cache`. fn init_mempool() -> Mempool { diff --git a/crates/astria-sequencer/src/mempool/mod.rs b/crates/astria-sequencer/src/mempool/mod.rs index 1106d74bef..d8162f4952 100644 --- a/crates/astria-sequencer/src/mempool/mod.rs +++ b/crates/astria-sequencer/src/mempool/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "benchmark")] mod benchmarks; mod transactions_container; diff --git a/crates/astria-sequencer/src/test_utils.rs b/crates/astria-sequencer/src/test_utils.rs index c25099ad52..7684f8f02f 100644 --- a/crates/astria-sequencer/src/test_utils.rs +++ b/crates/astria-sequencer/src/test_utils.rs @@ -1,12 +1,6 @@ -use astria_core::{ - crypto::{ - SigningKey, - VerificationKey, - }, - primitive::v1::{ - asset::TracePrefixed, - Address, - }, +use astria_core::primitive::v1::{ + asset::TracePrefixed, + Address, }; pub(crate) const ASTRIA_PREFIX: &str = "astria"; @@ -32,13 +26,15 @@ pub(crate) fn nria() -> TracePrefixed { "nria".parse().unwrap() } -pub(crate) fn verification_key(seed: u64) -> VerificationKey { +#[cfg(test)] +pub(crate) fn verification_key(seed: u64) -> astria_core::crypto::VerificationKey { use rand::SeedableRng as _; let rng = rand_chacha::ChaChaRng::seed_from_u64(seed); - let signing_key = SigningKey::new(rng); + let signing_key = astria_core::crypto::SigningKey::new(rng); signing_key.verification_key() } +#[cfg(test)] #[track_caller] pub(crate) fn assert_anyhow_error(error: &anyhow::Error, expected: &'static str) { let msg = error.to_string(); From 781c4c5c54b654cf4d3906d03ea615f59199eb37 Mon Sep 17 00:00:00 2001 From: Ethan Oroshiba Date: Wed, 21 Aug 2024 08:05:31 -0500 Subject: [PATCH 15/18] fix(sequencer)!: fix block fees (#1343) ## Summary Corrected block fee reporting so that proposer is paid the correct fees. ## Background Previously, only `TransferAction` and `SequenceAction` called `get_and_increase_block_fees()`, the method for increasing the running fees for the block. In `end_block`, this was used for calculating how much to increase the proposer's balance meaning that they were only paid the fees for `Transfer` and `Sequence` actions. ## Changes - Implemented `get_and_increase_block_fees()` in all actions which charge fees. ## Testing Added unit tests for each action, with the exception of `Ics20Withdrawal`. This test should be implemented, but will require a more in-depth configuration. ## Breaking Changelist - App hash changed, snapshot has been regenerated. ## Related Issues closes #1333 --------- Co-authored-by: Richard Janis Goldschmidt Co-authored-by: Fraser Hutchison <190532+Fraser999@users.noreply.github.com> Co-authored-by: noot <36753753+noot@users.noreply.github.com> --- .../astria-sequencer/src/accounts/action.rs | 2 +- crates/astria-sequencer/src/app/mod.rs | 2 + ..._changes__app_finalize_block_snapshot.snap | 60 ++-- .../src/app/tests_block_fees.rs | 335 ++++++++++++++++++ .../src/app/tests_execute_transaction.rs | 50 --- .../src/bridge/bridge_lock_action.rs | 10 +- .../src/bridge/bridge_sudo_change_action.rs | 15 +- .../src/bridge/init_bridge_account_action.rs | 11 +- .../src/ibc/ics20_withdrawal.rs | 7 + 9 files changed, 403 insertions(+), 89 deletions(-) create mode 100644 crates/astria-sequencer/src/app/tests_block_fees.rs diff --git a/crates/astria-sequencer/src/accounts/action.rs b/crates/astria-sequencer/src/accounts/action.rs index fbf3227191..3d665341b7 100644 --- a/crates/astria-sequencer/src/accounts/action.rs +++ b/crates/astria-sequencer/src/accounts/action.rs @@ -5,7 +5,7 @@ use anyhow::{ }; use astria_core::{ protocol::transaction::v1alpha1::action::TransferAction, - Protobuf, + Protobuf as _, }; use cnidarium::{ StateRead, diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 9119729fb9..5300077ee8 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -5,6 +5,8 @@ pub(crate) mod test_utils; #[cfg(test)] mod tests_app; #[cfg(test)] +mod tests_block_fees; +#[cfg(test)] mod tests_breaking_changes; #[cfg(test)] mod tests_execute_transaction; diff --git a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap index 7d4b7f970c..4647bcb04f 100644 --- a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap +++ b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap @@ -3,36 +3,36 @@ source: crates/astria-sequencer/src/app/tests_breaking_changes.rs expression: app.app_hash.as_bytes() --- [ - 72, - 87, - 29, - 138, - 91, - 221, - 71, + 36, 66, - 219, - 212, - 171, - 126, - 223, - 176, - 198, - 154, - 10, - 234, - 253, - 99, - 213, - 78, - 92, - 228, - 89, - 204, - 197, + 30, + 16, + 122, + 103, + 62, + 67, + 145, + 180, + 247, + 139, + 229, + 200, + 86, 193, - 90, - 57, - 205, - 2 + 28, + 71, + 235, + 32, + 134, + 143, + 239, + 229, + 8, + 86, + 190, + 164, + 81, + 230, + 24, + 149 ] diff --git a/crates/astria-sequencer/src/app/tests_block_fees.rs b/crates/astria-sequencer/src/app/tests_block_fees.rs new file mode 100644 index 0000000000..4801e661ea --- /dev/null +++ b/crates/astria-sequencer/src/app/tests_block_fees.rs @@ -0,0 +1,335 @@ +use std::sync::Arc; + +use astria_core::{ + primitive::v1::RollupId, + protocol::transaction::v1alpha1::{ + action::{ + BridgeLockAction, + BridgeSudoChangeAction, + InitBridgeAccountAction, + SequenceAction, + TransferAction, + }, + TransactionParams, + UnsignedTransaction, + }, + sequencerblock::v1alpha1::block::Deposit, +}; +use cnidarium::StateDelta; +use tendermint::abci::EventAttributeIndexExt as _; + +use crate::{ + accounts::{ + StateReadExt as _, + StateWriteExt as _, + }, + app::test_utils::{ + get_alice_signing_key, + get_bridge_signing_key, + initialize_app, + BOB_ADDRESS, + }, + assets::StateReadExt as _, + bridge::{ + get_deposit_byte_len, + StateWriteExt as _, + }, + sequence::{ + calculate_fee_from_state, + StateWriteExt as _, + }, + test_utils::{ + astria_address, + astria_address_from_hex_string, + nria, + }, +}; + +#[tokio::test] +async fn transaction_execution_records_fee_event() { + let mut app = initialize_app(None, vec![]).await; + + // transfer funds from Alice to Bob + let alice = get_alice_signing_key(); + let bob_address = astria_address_from_hex_string(BOB_ADDRESS); + let value = 333_333; + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions: vec![ + TransferAction { + to: bob_address, + amount: value, + asset: nria().into(), + fee_asset: nria().into(), + } + .into(), + ], + }; + + let signed_tx = Arc::new(tx.into_signed(&alice)); + + let events = app.execute_transaction(signed_tx).await.unwrap(); + let transfer_fee = app.state.get_transfer_base_fee().await.unwrap(); + let event = events.first().unwrap(); + assert_eq!(event.kind, "tx.fees"); + assert_eq!( + event.attributes[0], + ("asset", nria().to_string()).index().into() + ); + assert_eq!( + event.attributes[1], + ("feeAmount", transfer_fee.to_string()).index().into() + ); + assert_eq!( + event.attributes[2], + ( + "actionType", + "astria.protocol.transactions.v1alpha1.TransferAction" + ) + .index() + .into() + ); +} + +#[tokio::test] +async fn ensure_correct_block_fees_transfer() { + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + let transfer_base_fee = 1; + state_tx.put_transfer_base_fee(transfer_base_fee).unwrap(); + app.apply(state_tx); + + let alice = get_alice_signing_key(); + let bob_address = astria_address_from_hex_string(BOB_ADDRESS); + let actions = vec![ + TransferAction { + to: bob_address, + amount: 1000, + asset: nria().into(), + fee_asset: nria().into(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + assert_eq!(total_block_fees, transfer_base_fee); +} + +#[tokio::test] +async fn ensure_correct_block_fees_sequence() { + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + state_tx.put_sequence_action_base_fee(1); + state_tx.put_sequence_action_byte_cost_multiplier(1); + app.apply(state_tx); + + let alice = get_alice_signing_key(); + let data = b"hello world".to_vec(); + + let actions = vec![ + SequenceAction { + rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), + data: data.clone().into(), + fee_asset: nria().into(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + let expected_fees = calculate_fee_from_state(&data, &app.state).await.unwrap(); + assert_eq!(total_block_fees, expected_fees); +} + +#[tokio::test] +async fn ensure_correct_block_fees_init_bridge_acct() { + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + let init_bridge_account_base_fee = 1; + state_tx.put_init_bridge_account_base_fee(init_bridge_account_base_fee); + app.apply(state_tx); + + let alice = get_alice_signing_key(); + + let actions = vec![ + InitBridgeAccountAction { + rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), + asset: nria().into(), + fee_asset: nria().into(), + sudo_address: None, + withdrawer_address: None, + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + assert_eq!(total_block_fees, init_bridge_account_base_fee); +} + +#[tokio::test] +async fn ensure_correct_block_fees_bridge_lock() { + let alice = get_alice_signing_key(); + let bridge = get_bridge_signing_key(); + let bridge_address = astria_address(&bridge.address_bytes()); + let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); + + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + + let transfer_base_fee = 1; + let bridge_lock_byte_cost_multiplier = 1; + + state_tx.put_transfer_base_fee(transfer_base_fee).unwrap(); + state_tx.put_bridge_lock_byte_cost_multiplier(bridge_lock_byte_cost_multiplier); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); + state_tx + .put_bridge_account_ibc_asset(bridge_address, nria()) + .unwrap(); + app.apply(state_tx); + + let actions = vec![ + BridgeLockAction { + to: bridge_address, + amount: 1, + asset: nria().into(), + fee_asset: nria().into(), + destination_chain_address: rollup_id.to_string(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let test_deposit = Deposit::new( + bridge_address, + rollup_id, + 1, + nria().into(), + rollup_id.to_string(), + ); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + let expected_fees = transfer_base_fee + + (get_deposit_byte_len(&test_deposit) * bridge_lock_byte_cost_multiplier); + assert_eq!(total_block_fees, expected_fees); +} + +#[tokio::test] +async fn ensure_correct_block_fees_bridge_sudo_change() { + let alice = get_alice_signing_key(); + let alice_address = astria_address(&alice.address_bytes()); + let bridge = get_bridge_signing_key(); + let bridge_address = astria_address(&bridge.address_bytes()); + + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + + let sudo_change_base_fee = 1; + state_tx.put_bridge_sudo_change_base_fee(sudo_change_base_fee); + state_tx.put_bridge_account_sudo_address(bridge_address, alice_address); + state_tx + .increase_balance(bridge_address, nria(), 1) + .await + .unwrap(); + app.apply(state_tx); + + let actions = vec![ + BridgeSudoChangeAction { + bridge_address, + new_sudo_address: None, + new_withdrawer_address: None, + fee_asset: nria().into(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + assert_eq!(total_block_fees, sudo_change_base_fee); +} + +// TODO(https://github.com/astriaorg/astria/issues/1382): Add test to ensure correct block fees for ICS20 withdrawal diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index 3db427a574..e88d0b5227 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -30,7 +30,6 @@ use astria_core::{ use bytes::Bytes; use cnidarium::StateDelta; use penumbra_ibc::params::IBCParameters; -use tendermint::abci::EventAttributeIndexExt as _; use crate::{ accounts::StateReadExt as _, @@ -1052,52 +1051,3 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { "bridge should've transferred out whole balance" ); } - -#[tokio::test] -async fn transaction_execution_records_fee_event() { - let mut app = initialize_app(None, vec![]).await; - - // transfer funds from Alice to Bob - let alice = get_alice_signing_key(); - let bob_address = astria_address_from_hex_string(BOB_ADDRESS); - let value = 333_333; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ - TransferAction { - to: bob_address, - amount: value, - asset: nria().into(), - fee_asset: nria().into(), - } - .into(), - ], - }; - - let signed_tx = Arc::new(tx.into_signed(&alice)); - - let events = app.execute_transaction(signed_tx).await.unwrap(); - let transfer_fee = app.state.get_transfer_base_fee().await.unwrap(); - let event = events.first().unwrap(); - assert_eq!(event.kind, "tx.fees"); - assert_eq!( - event.attributes[0], - ("asset", nria().to_string()).index().into() - ); - assert_eq!( - event.attributes[1], - ("feeAmount", transfer_fee.to_string()).index().into() - ); - assert_eq!( - event.attributes[2], - ( - "actionType", - "astria.protocol.transactions.v1alpha1.TransferAction" - ) - .index() - .into() - ); -} diff --git a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs index 880298fb5f..b2375aae24 100644 --- a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs @@ -9,6 +9,7 @@ use astria_core::{ TransferAction, }, sequencerblock::v1alpha1::block::Deposit, + Protobuf as _, }; use cnidarium::StateWrite; @@ -23,6 +24,7 @@ use crate::{ }, address::StateReadExt as _, app::ActionHandler, + assets::StateWriteExt as _, bridge::{ StateReadExt as _, StateWriteExt as _, @@ -125,7 +127,10 @@ impl ActionHandler for BridgeLockAction { .await .context("failed to get byte cost multiplier")?; let fee = byte_cost_multiplier.saturating_mul(get_deposit_byte_len(&deposit)); - + state + .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) + .await + .context("failed to add to block fees")?; state .decrease_balance(from, &self.fee_asset, fee) .await @@ -156,8 +161,7 @@ mod tests { use super::*; use crate::{ - address::StateWriteExt, - assets::StateWriteExt as _, + address::StateWriteExt as _, test_utils::{ assert_anyhow_error, astria_address, diff --git a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs index 483adf9af5..1ebe568d53 100644 --- a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs @@ -3,14 +3,20 @@ use anyhow::{ Context as _, Result, }; -use astria_core::protocol::transaction::v1alpha1::action::BridgeSudoChangeAction; +use astria_core::{ + protocol::transaction::v1alpha1::action::BridgeSudoChangeAction, + Protobuf as _, +}; use cnidarium::StateWrite; use crate::{ accounts::StateWriteExt as _, address::StateReadExt as _, app::ActionHandler, - assets::StateReadExt as _, + assets::{ + StateReadExt as _, + StateWriteExt as _, + }, bridge::state_ext::{ StateReadExt as _, StateWriteExt as _, @@ -73,6 +79,10 @@ impl ActionHandler for BridgeSudoChangeAction { .get_bridge_sudo_change_base_fee() .await .context("failed to get bridge sudo change fee")?; + state + .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) + .await + .context("failed to add to block fees")?; state .decrease_balance(self.bridge_address, &self.fee_asset, fee) .await @@ -98,7 +108,6 @@ mod tests { use super::*; use crate::{ address::StateWriteExt as _, - assets::StateWriteExt as _, test_utils::{ astria_address, ASTRIA_PREFIX, diff --git a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs index 0dbbd07c78..bb1ae84d25 100644 --- a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs +++ b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs @@ -7,6 +7,7 @@ use anyhow::{ use astria_core::{ primitive::v1::Address, protocol::transaction::v1alpha1::action::InitBridgeAccountAction, + Protobuf as _, }; use cnidarium::StateWrite; @@ -17,7 +18,10 @@ use crate::{ }, address::StateReadExt as _, app::ActionHandler, - assets::StateReadExt as _, + assets::{ + StateReadExt as _, + StateWriteExt as _, + }, bridge::state_ext::{ StateReadExt as _, StateWriteExt as _, @@ -98,7 +102,10 @@ impl ActionHandler for InitBridgeAccountAction { from, self.withdrawer_address.map_or(from, Address::bytes), ); - + state + .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) + .await + .context("failed to get and increase block fees")?; state .decrease_balance(from, &self.fee_asset, fee) .await diff --git a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs index 477519df59..314d44c60f 100644 --- a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs +++ b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs @@ -10,6 +10,7 @@ use astria_core::{ Address, }, protocol::transaction::v1alpha1::action, + Protobuf as _, }; use cnidarium::{ StateRead, @@ -33,6 +34,7 @@ use crate::{ }, address::StateReadExt as _, app::ActionHandler, + assets::StateWriteExt as _, bridge::StateReadExt as _, ibc::{ StateReadExt as _, @@ -144,6 +146,11 @@ impl ActionHandler for action::Ics20Withdrawal { .context("packet failed send check")? }; + state + .get_and_increase_block_fees(self.fee_asset(), fee, Self::full_name()) + .await + .context("failed to get and increase block fees")?; + state .decrease_balance(withdrawal_target, self.denom(), self.amount()) .await From 59a615a6a6163c5adae22cbba9d000681ae4ec99 Mon Sep 17 00:00:00 2001 From: Jordan Oroshiba Date: Wed, 21 Aug 2024 08:56:40 -0700 Subject: [PATCH 16/18] feat(conductor)!: support disabled celestia auth (#1372) ## Summary Updates conductor celestia client to support using no auth token, adds a config field to specify using no token. ## Background When originally built celestia-node required an auth token, it can now be run with the auth token disabled. We do this by default in our charts, but our code always specifies an auth header which will still be rejected if empty by celestia node. ## Changes - Add `ASTRIA_CONDUCTOR_NO_CELESTIA_AUTH` config env var - When `no_celestia_auth` is true, makes celestia node requests without auth header. ## Testing CI/CD smoke tests use no token, blockbox tests use a token to verify both paths still work. ## Breaking Changelist - ASTRIA_CONDUCTOR_NO_CELESTIA_AUTH config env var added ## Related Issues closes https://github.com/astriaorg/astria/issues/1370 --- charts/evm-rollup/Chart.yaml | 2 +- charts/evm-rollup/templates/configmap.yaml | 1 + charts/evm-stack/Chart.lock | 6 +++--- charts/evm-stack/Chart.yaml | 4 ++-- crates/astria-conductor/local.env.example | 8 +++++++- .../astria-conductor/src/celestia/builder.rs | 19 ++++++++++++------- crates/astria-conductor/src/conductor.rs | 8 +++++++- crates/astria-conductor/src/config.rs | 3 +++ .../tests/blackbox/helpers/mod.rs | 1 + dev/values/rollup/dev.yaml | 2 +- 10 files changed, 38 insertions(+), 16 deletions(-) diff --git a/charts/evm-rollup/Chart.yaml b/charts/evm-rollup/Chart.yaml index 096c152ff7..d2f2154d51 100644 --- a/charts/evm-rollup/Chart.yaml +++ b/charts/evm-rollup/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.25.3 +version: 0.25.4 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/evm-rollup/templates/configmap.yaml b/charts/evm-rollup/templates/configmap.yaml index 10fc39d5fa..21fd5df39c 100644 --- a/charts/evm-rollup/templates/configmap.yaml +++ b/charts/evm-rollup/templates/configmap.yaml @@ -35,6 +35,7 @@ data: OTEL_SERVICE_NAME: "{{ tpl .Values.otel.serviceNamePrefix . }}-conductor" {{- if not .Values.global.dev }} {{- else }} + ASTRIA_CONDUCTOR_NO_CELESTIA_AUTH: "{{ not .Values.config.celestia.token }}" {{- end }} --- apiVersion: v1 diff --git a/charts/evm-stack/Chart.lock b/charts/evm-stack/Chart.lock index 0721955d81..b531f1528a 100644 --- a/charts/evm-stack/Chart.lock +++ b/charts/evm-stack/Chart.lock @@ -1,7 +1,7 @@ dependencies: - name: evm-rollup repository: file://../evm-rollup - version: 0.25.3 + version: 0.25.4 - name: composer repository: file://../composer version: 0.1.1 @@ -17,5 +17,5 @@ dependencies: - name: blockscout-stack repository: https://blockscout.github.io/helm-charts version: 1.6.2 -digest: sha256:75189d68ee2ddbb135ec487b4aee663fd2d096ae19608efc2d6ebfdec9d8c4a0 -generated: "2024-08-12T22:12:07.880246+03:00" +digest: sha256:695498fcbe82a100ca333b058196730eed9173df8528871585f40453c182d964 +generated: "2024-08-15T12:40:34.762702-07:00" diff --git a/charts/evm-stack/Chart.yaml b/charts/evm-stack/Chart.yaml index e1cc2a2465..ec97e8def6 100644 --- a/charts/evm-stack/Chart.yaml +++ b/charts/evm-stack/Chart.yaml @@ -15,11 +15,11 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.2 +version: 0.3.3 dependencies: - name: evm-rollup - version: 0.25.3 + version: 0.25.4 repository: "file://../evm-rollup" - name: composer version: 0.1.1 diff --git a/crates/astria-conductor/local.env.example b/crates/astria-conductor/local.env.example index ee3c50e7f6..6237d3109b 100644 --- a/crates/astria-conductor/local.env.example +++ b/crates/astria-conductor/local.env.example @@ -6,9 +6,15 @@ # 12000 milliseconds is the default Celestia block time. ASTRIA_CONDUCTOR_CELESTIA_BLOCK_TIME_MS=12000 +# Disable using the auth header with celestia jsonrpc. Celestia nodes can be run +# without authentication, in which case this should be set to true. +ASTRIA_CONDUCTOR_NO_CELESTIA_AUTH=false + # The bearer token to retrieve sequencer blocks as blobs from Celestia. # The token is obtained by running `celestia bridge auth ` -# on the host running the celestia node. +# on the host running the celestia node. +# +# Only used if ASTRIA_CONDUCTOR_NO_CELESTIA_AUTH is set to false ASTRIA_CONDUCTOR_CELESTIA_BEARER_TOKEN="" # The URL of the celestia node to fetch blocks from. This URL must contain diff --git a/crates/astria-conductor/src/celestia/builder.rs b/crates/astria-conductor/src/celestia/builder.rs index a14246ddee..7860d439ee 100644 --- a/crates/astria-conductor/src/celestia/builder.rs +++ b/crates/astria-conductor/src/celestia/builder.rs @@ -19,7 +19,7 @@ use crate::{ pub(crate) struct Builder { pub(crate) celestia_block_time: Duration, pub(crate) celestia_http_endpoint: String, - pub(crate) celestia_token: String, + pub(crate) celestia_token: Option, pub(crate) executor: executor::Handle, pub(crate) sequencer_cometbft_client: SequencerClient, pub(crate) sequencer_requests_per_second: u32, @@ -41,7 +41,7 @@ impl Builder { metrics, } = self; - let celestia_client = create_celestia_client(celestia_http_endpoint, &celestia_token) + let celestia_client = create_celestia_client(celestia_http_endpoint, celestia_token) .wrap_err("failed initializing client for Celestia HTTP RPC")?; Ok(Reader { @@ -56,16 +56,21 @@ impl Builder { } } -fn create_celestia_client(endpoint: String, bearer_token: &str) -> eyre::Result { +fn create_celestia_client( + endpoint: String, + bearer_token: Option, +) -> eyre::Result { use jsonrpsee::http_client::{ HeaderMap, HttpClientBuilder, }; let mut headers = HeaderMap::new(); - let auth_value = format!("Bearer {bearer_token}").parse().wrap_err( - "failed to construct Authorization header value from provided Celestia bearer token", - )?; - headers.insert(http::header::AUTHORIZATION, auth_value); + if let Some(token) = bearer_token { + let auth_value = format!("Bearer {token}").parse().wrap_err( + "failed to construct Authorization header value from provided Celestia bearer token", + )?; + headers.insert(http::header::AUTHORIZATION, auth_value); + } let client = HttpClientBuilder::default() .set_headers(headers) .build(endpoint) diff --git a/crates/astria-conductor/src/conductor.rs b/crates/astria-conductor/src/conductor.rs index b924294db2..ff857257f3 100644 --- a/crates/astria-conductor/src/conductor.rs +++ b/crates/astria-conductor/src/conductor.rs @@ -141,9 +141,15 @@ impl Conductor { } if cfg.execution_commit_level.is_with_firm() { + let celestia_token = if cfg.no_celestia_auth { + None + } else { + Some(cfg.celestia_bearer_token) + }; + let reader = celestia::Builder { celestia_http_endpoint: cfg.celestia_node_http_url, - celestia_token: cfg.celestia_bearer_token, + celestia_token, celestia_block_time: Duration::from_millis(cfg.celestia_block_time_ms), executor: executor_handle.clone(), sequencer_cometbft_client: sequencer_cometbft_client.clone(), diff --git a/crates/astria-conductor/src/config.rs b/crates/astria-conductor/src/config.rs index 699f95f1c9..3ee6a96723 100644 --- a/crates/astria-conductor/src/config.rs +++ b/crates/astria-conductor/src/config.rs @@ -44,6 +44,9 @@ pub struct Config { /// URL of the Celestia Node HTTP RPC pub celestia_node_http_url: String, + /// Disables using the bearer token auth header for the Celestia jsonrpc + pub no_celestia_auth: bool, + /// The JWT bearer token supplied with each jsonrpc call pub celestia_bearer_token: String, diff --git a/crates/astria-conductor/tests/blackbox/helpers/mod.rs b/crates/astria-conductor/tests/blackbox/helpers/mod.rs index 6ea3c75e72..ff07a3b0f6 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mod.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mod.rs @@ -468,6 +468,7 @@ fn make_config() -> Config { Config { celestia_block_time_ms: 12000, celestia_node_http_url: "http://127.0.0.1:26658".into(), + no_celestia_auth: false, celestia_bearer_token: CELESTIA_BEARER_TOKEN.into(), sequencer_grpc_url: "http://127.0.0.1:8080".into(), sequencer_cometbft_url: "http://127.0.0.1:26657".into(), diff --git a/dev/values/rollup/dev.yaml b/dev/values/rollup/dev.yaml index d45305ad24..11d8da3f19 100644 --- a/dev/values/rollup/dev.yaml +++ b/dev/values/rollup/dev.yaml @@ -100,7 +100,7 @@ evm-rollup: celestia: rpc: "http://celestia-service.astria-dev-cluster.svc.cluster.local:26658" - token: "http://celestia-service.astria-dev-cluster.svc.cluster.local:5353" + token: "" resources: conductor: From 10b196a57a49d3e96b27a567c2d9b285b9c64828 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:12:45 -0400 Subject: [PATCH 17/18] fix(sequencer): bump penumbra dep to fix ibc state access bug (#1389) ## Summary Bumps penumbra deps to no longer use penumbra-specific key. ## Summary The `penumbra_ibc::component::packet::IBCPacket::send_packet_check` attempts to read a timestamp stored under a penumbra specific key which we never write. This was an oversight in penumbra and recently fixed. ## Testing Unblocks e2e testing. ## Related Issues https://github.com/penumbra-zone/penumbra/issues/4812 https://github.com/penumbra-zone/penumbra/pull/4822 --- Cargo.lock | 1656 ++++++++--------- Cargo.toml | 7 +- crates/astria-sequencer/Cargo.toml | 2 +- .../src/ibc/ics20_transfer.rs | 17 +- .../src/ibc/ics20_withdrawal.rs | 7 +- 5 files changed, 798 insertions(+), 891 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfd9a0a4b7..1ccc39b5a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aead" version = "0.4.3" @@ -85,17 +91,17 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-rlp" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bytes", ] @@ -125,47 +131,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -173,9 +180,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "ark-bls12-377" @@ -203,6 +210,7 @@ dependencies = [ "blake2", "derivative", "digest 0.10.7", + "rayon", "sha2 0.10.8", ] @@ -220,6 +228,7 @@ dependencies = [ "hashbrown 0.13.2", "itertools 0.10.5", "num-traits", + "rayon", "zeroize", ] @@ -232,7 +241,6 @@ dependencies = [ "ark-bls12-377", "ark-ec", "ark-ff 0.4.2", - "ark-r1cs-std", "ark-std 0.4.0", ] @@ -270,6 +278,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", + "rayon", "rustc_version 0.4.0", "zeroize", ] @@ -314,7 +323,7 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint", "num-traits", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -332,6 +341,7 @@ dependencies = [ "ark-relations", "ark-serialize 0.4.2", "ark-std 0.4.0", + "rayon", ] [[package]] @@ -345,6 +355,7 @@ dependencies = [ "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", + "rayon", ] [[package]] @@ -404,7 +415,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -439,13 +450,14 @@ checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", "rand 0.8.5", + "rayon", ] [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" @@ -455,9 +467,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert-json-diff" @@ -471,13 +483,14 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", + "libc", "predicates", "predicates-core", "predicates-tree", @@ -532,7 +545,7 @@ dependencies = [ "tendermint-rpc", "tokio", "tokio-stream", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tonic 0.10.2", "tracing", "tryhard", @@ -611,7 +624,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-test", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tonic 0.10.2", "tonic-health", "tracing", @@ -643,7 +656,7 @@ dependencies = [ "hex", "http", "humantime", - "indexmap 2.2.6", + "indexmap 2.4.0", "insta", "itertools 0.12.1", "itoa", @@ -664,7 +677,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tonic 0.10.2", "tower", "tracing", @@ -699,7 +712,7 @@ dependencies = [ "ed25519-consensus", "hex", "ibc-types", - "indexmap 2.2.6", + "indexmap 2.4.0", "insta", "pbjson", "pbjson-types", @@ -734,7 +747,7 @@ name = "astria-grpc-mock" version = "0.1.0" dependencies = [ "assert-json-diff", - "erased-serde 0.4.4", + "erased-serde 0.4.5", "futures", "indenter", "prost", @@ -895,7 +908,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-test", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tonic 0.10.2", "tower", "tracing", @@ -967,7 +980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] @@ -977,7 +990,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", ] [[package]] @@ -997,20 +1021,20 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -1055,16 +1079,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -1125,7 +1149,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -1154,6 +1178,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64-serde" version = "0.7.0" @@ -1219,19 +1249,19 @@ dependencies = [ "lazycell", "peeking_take_while", "prettyplease", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "bip32" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" dependencies = [ "bs58", "hmac", @@ -1253,9 +1283,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitmaps" @@ -1305,18 +1335,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "constant_time_eq 0.3.0", ] [[package]] name = "blake3" -version = "1.5.1" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "cc", "cfg-if", "constant_time_eq 0.3.0", @@ -1355,9 +1385,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ "borsh-derive", "cfg_aliases", @@ -1365,15 +1395,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", "syn_derive", ] @@ -1390,9 +1420,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1410,12 +1440,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.6", + "regex-automata 0.4.7", "serde", ] @@ -1431,17 +1461,11 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" -[[package]] -name = "bytecount" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" - [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" [[package]] name = "byteorder" @@ -1451,9 +1475,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" dependencies = [ "serde", ] @@ -1471,9 +1495,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -1487,19 +1511,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.22", - "serde", - "serde_json", -] - [[package]] name = "cargo_metadata" version = "0.18.1" @@ -1508,7 +1519,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -1522,12 +1533,13 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.92" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -1650,9 +1662,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" @@ -1681,9 +1693,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1691,7 +1703,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -1727,9 +1739,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -1738,9 +1750,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -1748,34 +1760,34 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clippy_config" @@ -1792,7 +1804,7 @@ name = "clippy_utils" version = "0.1.77" source = "git+https://github.com/rust-lang/rust-clippy?rev=ac4c2094a6030530661bee3876e0228ddfeb6b8b#ac4c2094a6030530661bee3876e0228ddfeb6b8b" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "clippy_config", "itertools 0.11.0", "rustc-semver", @@ -1800,8 +1812,8 @@ dependencies = [ [[package]] name = "cnidarium" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "async-trait", @@ -1829,8 +1841,8 @@ dependencies = [ [[package]] name = "cnidarium-component" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "async-trait", @@ -1920,9 +1932,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colour" @@ -1957,9 +1969,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1984,9 +1996,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" dependencies = [ "cfg-if", "cpufeatures", @@ -2016,9 +2028,9 @@ version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "unicode-xid 0.2.4", + "unicode-xid 0.2.5", ] [[package]] @@ -2045,9 +2057,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core2" @@ -2060,28 +2072,38 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ + "crossbeam-epoch", "crossbeam-utils", ] @@ -2096,9 +2118,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -2194,9 +2216,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -2204,27 +2226,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "strsim 0.10.0", - "syn 2.0.58", + "strsim", + "syn 2.0.75", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -2234,7 +2256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -2242,15 +2264,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2258,9 +2280,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -2281,43 +2303,16 @@ dependencies = [ [[package]] name = "deadpool-runtime" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" - -[[package]] -name = "decaf377" -version = "0.4.0" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75bb0f9fde498b60e4563c9346bbd4527d4ff4930a43c404ceb4cf63166c9ea4" -dependencies = [ - "anyhow", - "ark-bls12-377", - "ark-ec", - "ark-ed-on-bls12-377", - "ark-ff 0.4.2", - "ark-groth16", - "ark-r1cs-std", - "ark-relations", - "ark-serialize 0.4.2", - "ark-snark", - "ark-std 0.4.0", - "hex", - "num-bigint", - "once_cell", - "thiserror", - "tracing", - "tracing-subscriber 0.2.25", - "zeroize", -] +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" [[package]] name = "decaf377" -version = "0.5.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80011d442d81fccfbefb5bd0d20bf70f111ca544ffed943d335dacf6a85713" +checksum = "2097c5f69d06259112bea2024ddc41095c5001b503448f84ac169efc7cc8fd75" dependencies = [ - "anyhow", "ark-bls12-377", "ark-ec", "ark-ed-on-bls12-377", @@ -2328,36 +2323,37 @@ dependencies = [ "ark-serialize 0.4.2", "ark-snark", "ark-std 0.4.0", + "cfg-if", + "hashbrown 0.14.5", "hex", "num-bigint", "once_cell", - "thiserror", - "tracing", - "tracing-subscriber 0.2.25", + "rand_core 0.6.4", + "subtle", "zeroize", ] [[package]] name = "decaf377-fmd" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "ark-ff 0.4.2", "ark-serialize 0.4.2", "bitvec", "blake2b_simd 1.0.2", - "decaf377 0.5.0", + "decaf377", "rand_core 0.6.4", "thiserror", ] [[package]] name = "decaf377-ka" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "ark-ff 0.4.2", - "decaf377 0.5.0", + "decaf377", "hex", "rand_core 0.6.4", "thiserror", @@ -2367,20 +2363,20 @@ dependencies = [ [[package]] name = "decaf377-rdsa" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2356bb010273c2b6e4e928b2bb442ddaa255ec242c16ff46cf9c3811fefa5ace" +checksum = "437967a34e0699b50b986a72ce6c4e2e5930bde85ec8f3749701f7e50d6d32b0" dependencies = [ "ark-ff 0.4.2", "ark-serialize 0.4.2", "blake2b_simd 0.5.11", - "byteorder", - "decaf377 0.5.0", + "decaf377", "digest 0.9.0", "hex", "rand_core 0.6.4", "serde", "thiserror", + "zeroize", ] [[package]] @@ -2409,20 +2405,20 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 1.0.109", + "syn 2.0.75", ] [[package]] @@ -2502,13 +2498,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -2531,9 +2527,9 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -2544,9 +2540,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dylint" @@ -2556,13 +2552,13 @@ checksum = "4d6259cf4df09300534dcfa6a49918bb442327111e370c656b31f1c10ec08145" dependencies = [ "ansi_term", "anyhow", - "cargo_metadata 0.18.1", + "cargo_metadata", "dirs", "dylint_internal", "is-terminal", "log", "once_cell", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "tempfile", @@ -2577,8 +2573,8 @@ checksum = "9400420c9ffa71c6b1b75d84225a150e3428eb12159e5bf4f56285bd9eb1c095" dependencies = [ "ansi_term", "anyhow", - "bitflags 2.5.0", - "cargo_metadata 0.18.1", + "bitflags 2.6.0", + "cargo_metadata", "git2", "home", "if_chain", @@ -2595,13 +2591,13 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fc05c7103dfadd497486bbbf941899888f4e19271da70b58f66385247230c2" dependencies = [ - "cargo_metadata 0.18.1", + "cargo_metadata", "dylint_internal", "paste", "rustversion", "serde", "thiserror", - "toml 0.8.12", + "toml 0.8.19", ] [[package]] @@ -2611,7 +2607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "785fa52ac8fe5f1056a71955f4ebda3ca90c269ef28b172ad85a3901752d5fdf" dependencies = [ "anyhow", - "cargo_metadata 0.18.1", + "cargo_metadata", "compiletest_rs", "dylint", "dylint_internal", @@ -2668,9 +2664,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -2731,9 +2727,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" dependencies = [ "once_cell", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -2766,32 +2762,24 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", + "typeid", ] [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - [[package]] name = "eth-keystore" version = "0.5.0" @@ -2919,13 +2907,13 @@ dependencies = [ "ethers-core", "eyre", "prettyplease", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "regex", "serde", "serde_json", - "syn 2.0.58", - "toml 0.8.12", + "syn 2.0.75", + "toml 0.8.19", "walkdir", ] @@ -2939,10 +2927,10 @@ dependencies = [ "const-hex", "ethers-contract-abigen", "ethers-core", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "serde_json", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -2951,9 +2939,9 @@ version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bytes", - "cargo_metadata 0.18.1", + "cargo_metadata", "chrono", "const-hex", "elliptic-curve", @@ -2968,11 +2956,11 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.58", + "syn 2.0.75", "tempfile", "thiserror", "tiny-keccak", - "unicode-xid 0.2.4", + "unicode-xid 0.2.5", ] [[package]] @@ -3070,6 +3058,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -3099,9 +3108,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fastrlp" @@ -3109,7 +3118,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "auto_impl", "bytes", ] @@ -3126,9 +3135,9 @@ dependencies = [ [[package]] name = "figment" -version = "0.10.16" +version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdefe49ed1057d124dc81a0681c30dd07de56ad96e32adc7b64e8f28eaab31c4" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" dependencies = [ "atomic", "parking_lot", @@ -3141,14 +3150,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -3171,12 +3180,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -3236,9 +3245,9 @@ dependencies = [ [[package]] name = "futures-bounded" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e2774cc104e198ef3d3e1ff4ab40f86fa3245d6cb6a3a46174f21463cee173" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" dependencies = [ "futures-timer", "futures-util", @@ -3308,9 +3317,9 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -3395,9 +3404,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -3418,7 +3427,7 @@ version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", "libgit2-sys", "log", @@ -3442,8 +3451,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -3481,10 +3490,10 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.6", + "indexmap 2.4.0", "slab", "tokio", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tracing", ] @@ -3511,9 +3520,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -3556,6 +3565,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -3649,9 +3664,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -3677,9 +3692,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -3770,9 +3785,9 @@ dependencies = [ [[package]] name = "ibc-types" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba606d86e2015991f86a129935dbaeacd94beab72fb90a733c1b1ea76be708a2" +checksum = "f45534bc1118d30f6537040bdf822f17245dcb5467a14094070f7365d49428df" dependencies = [ "ibc-types-core-channel", "ibc-types-core-client", @@ -3788,9 +3803,9 @@ dependencies = [ [[package]] name = "ibc-types-core-channel" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fb64ef52086b727e5ae01da0e773f8ca9172ec1fd9d0aa1a79c0c2c610b17a" +checksum = "1dcce2afa6b83fc6f6bd0d626d3f31aaf62a9e9087fcef24e0f705148915cb56" dependencies = [ "anyhow", "bytes", @@ -3821,9 +3836,9 @@ dependencies = [ [[package]] name = "ibc-types-core-client" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db9d4b136b9e84ccf581fec02bb9ebc4478ac0f145c526760ed4310b98741e7" +checksum = "99ea8df52f9218da5f8e7daed1b22ca6b01b64711950b1c72a493f2d11660b9f" dependencies = [ "anyhow", "bytes", @@ -3848,9 +3863,9 @@ dependencies = [ [[package]] name = "ibc-types-core-commitment" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2c527e14707dd0b2c7e6e2f6f62b0655c83154ae3eb1504e441d9d8f454ac6" +checksum = "4b3583a2b7bd4d7f0b75177b619e9b0e0c317ece069170a348675913ad9a8125" dependencies = [ "anyhow", "bytes", @@ -3883,9 +3898,9 @@ dependencies = [ [[package]] name = "ibc-types-core-connection" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8a326c00e9ba48059407478c826237fe39cc90dd2b47182484192926904fe7" +checksum = "5a989af1209864891c95645585fb2048720087968ae419288949965aa65fc4b5" dependencies = [ "anyhow", "bytes", @@ -3913,9 +3928,9 @@ dependencies = [ [[package]] name = "ibc-types-domain-type" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abc9619b9dd7201804f45fc7f335dda72d2e4d6f82d96e8fe3abf4585e6101b" +checksum = "fabf22b6da00e7d41dd50e8f3009fc112be5f8c9cdc131d3e37ed264844f5131" dependencies = [ "anyhow", "bytes", @@ -3924,9 +3939,9 @@ dependencies = [ [[package]] name = "ibc-types-identifier" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "405880cf06fef65f51c5c91b7efbdcbc8d7eba0ac16b43538b36ebd17f21edea" +checksum = "bd1070c50d4f031474472d404a77847a32233396cd8397b1145cfd555f88573d" dependencies = [ "displaydoc", "serde", @@ -3935,9 +3950,9 @@ dependencies = [ [[package]] name = "ibc-types-lightclients-tendermint" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab22446058bd5afa50d64f8519a9107bbc5101ee65373df896314f52afa0fc6" +checksum = "50390dbcbcb4d6f34a9ab4a1823196813c6171a9a7c28f0c6f498162c3d3aa0b" dependencies = [ "anyhow", "bytes", @@ -3972,9 +3987,9 @@ dependencies = [ [[package]] name = "ibc-types-path" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a29e6fd8871fdced76402a3008219abf8773e527a46f120e0d76d6a3bb9706c1" +checksum = "3088ab0bd2a33ccd4fb522497a65a23a540be699f63342ff3c22268708a08271" dependencies = [ "bytes", "derive_more", @@ -3995,9 +4010,9 @@ dependencies = [ [[package]] name = "ibc-types-timestamp" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d2e763838dbef62ca8a1344b4dd5b3919d685b4c61874183724644c912237a" +checksum = "a108c721a477aaf2f3fd8c392577d6c71f03b5c54c8cd09d58365ab7aa16182b" dependencies = [ "bytes", "displaydoc", @@ -4014,9 +4029,9 @@ dependencies = [ [[package]] name = "ibc-types-transfer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad973ca1fbad8d0d1632ec0a329aecff8731bbb96395b7553d6b9fd749356d34" +checksum = "0f7198d1f63d8428a96a60b2534dbf2bba5594d36745db6166538ef9d89c3fef" dependencies = [ "displaydoc", "serde", @@ -4124,7 +4139,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -4148,12 +4163,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -4190,9 +4205,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.38.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" +checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" dependencies = [ "console", "lazy_static", @@ -4203,9 +4218,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -4218,15 +4233,21 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "isahc" version = "1.7.2" @@ -4239,7 +4260,7 @@ dependencies = [ "curl", "curl-sys", "encoding_rs", - "event-listener", + "event-listener 2.5.3", "futures-lite", "http", "log", @@ -4313,18 +4334,18 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -4360,7 +4381,7 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tracing", "url", ] @@ -4372,7 +4393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" dependencies = [ "anyhow", - "async-lock", + "async-lock 2.8.0", "async-trait", "beef", "futures-timer", @@ -4418,7 +4439,7 @@ checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" dependencies = [ "heck 0.4.1", "proc-macro-crate 1.3.1", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -4441,7 +4462,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tower", "tracing", ] @@ -4512,9 +4533,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -4524,9 +4545,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libgit2-sys" @@ -4544,12 +4565,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -4570,9 +4591,9 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ec70441b2fb35355076726a6bc466c932e9bdc66f6a11c6c0aa17c7ab9be0" +checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" dependencies = [ "bs58", "hkdf", @@ -4589,8 +4610,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", + "redox_syscall", ] [[package]] @@ -4625,9 +4647,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.16" +version = "1.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" +checksum = "fdc53a7799a7496ebc9fd29f31f7df80e83c9bda5299768af5f9e59eeea74647" dependencies = [ "cc", "libc", @@ -4643,15 +4665,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -4659,29 +4681,20 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" dependencies = [ "cc", "libc", ] -[[package]] -name = "matchers" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "matchers" version = "0.1.0" @@ -4699,21 +4712,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.6.4", - "zeroize", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "metrics" @@ -4733,7 +4734,7 @@ checksum = "9bf4e7146e30ad172c42c39b3246864bd2d3c6396780711a1baf749cfe423e21" dependencies = [ "base64 0.21.7", "hyper", - "indexmap 2.2.6", + "indexmap 2.4.0", "ipnet", "metrics", "metrics-util", @@ -4750,7 +4751,7 @@ checksum = "8b07a5eb561b8cbc16be2d216faf7757f9baf3bfb94dbb0fae3df8387a5bb47f" dependencies = [ "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "metrics", "num_cpus", "quanta", @@ -4771,22 +4772,32 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4806,26 +4817,26 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[package]] name = "moka" -version = "0.12.5" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1911e88d5831f748a4097a43862d129e3c6fca831eecac9b8db6d01d93c9de2" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" dependencies = [ - "async-lock", + "async-lock 3.4.0", "async-trait", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", + "event-listener 5.3.1", "futures-util", "once_cell", "parking_lot", "quanta", "rustc_version 0.4.0", - "skeptic", "smallvec", "tagptr", "thiserror", "triomphe", - "uuid 1.8.0", + "uuid 1.10.0", ] [[package]] @@ -4919,40 +4930,16 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -4965,7 +4952,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -4979,34 +4966,11 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -5018,29 +4982,29 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -5079,7 +5043,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "auto_impl", "bytes", "ethereum-types", @@ -5093,7 +5057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" dependencies = [ "bytes", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -5106,9 +5070,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -5213,9 +5177,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "4.2.0" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" +checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6" dependencies = [ "num-traits", ] @@ -5234,11 +5198,11 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parity-scale-codec" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", @@ -5248,12 +5212,12 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 2.0.0", - "proc-macro2 1.0.79", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -5266,9 +5230,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -5276,22 +5240,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbjson" @@ -5366,10 +5330,10 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "proc-macro2-diagnostics", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -5380,9 +5344,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "peg" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "400bcab7d219c38abf8bd7cc2054eb9bbbd4312d66f6a5557d572a203f646f61" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" dependencies = [ "peg-macros", "peg-runtime", @@ -5390,20 +5354,20 @@ dependencies = [ [[package]] name = "peg-macros" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e61cce859b76d19090f62da50a9fe92bab7c2a5f09e183763559a2ac392c90" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" dependencies = [ "peg-runtime", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", ] [[package]] name = "peg-runtime" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36bae92c60fa2398ce4678b98b2c4b5a7c61099961ca1fa305aec04a9ad28922" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" [[package]] name = "pem" @@ -5416,8 +5380,8 @@ dependencies = [ [[package]] name = "penumbra-asset" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "ark-ff 0.4.2", @@ -5429,7 +5393,7 @@ dependencies = [ "bech32 0.8.1", "blake2b_simd 1.0.2", "bytes", - "decaf377 0.5.0", + "decaf377", "decaf377-fmd", "decaf377-rdsa", "derivative", @@ -5454,8 +5418,8 @@ dependencies = [ [[package]] name = "penumbra-ibc" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "ark-ff 0.4.2", @@ -5491,8 +5455,8 @@ dependencies = [ [[package]] name = "penumbra-keys" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "aes", "anyhow", @@ -5507,7 +5471,7 @@ dependencies = [ "blake2b_simd 1.0.2", "bytes", "chacha20poly1305", - "decaf377 0.5.0", + "decaf377", "decaf377-fmd", "decaf377-ka", "decaf377-rdsa", @@ -5535,8 +5499,8 @@ dependencies = [ [[package]] name = "penumbra-num" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "ark-ff 0.4.2", @@ -5550,7 +5514,7 @@ dependencies = [ "bech32 0.8.1", "blake2b_simd 1.0.2", "bytes", - "decaf377 0.5.0", + "decaf377", "decaf377-fmd", "decaf377-rdsa", "derivative", @@ -5571,8 +5535,8 @@ dependencies = [ [[package]] name = "penumbra-proto" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "async-trait", @@ -5600,8 +5564,8 @@ dependencies = [ [[package]] name = "penumbra-sct" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "ark-ff 0.4.2", @@ -5615,7 +5579,7 @@ dependencies = [ "chrono", "cnidarium", "cnidarium-component", - "decaf377 0.5.0", + "decaf377", "decaf377-rdsa", "hex", "im", @@ -5636,8 +5600,8 @@ dependencies = [ [[package]] name = "penumbra-tct" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "ark-ed-on-bls12-377", "ark-ff 0.4.2", @@ -5646,7 +5610,7 @@ dependencies = [ "ark-serialize 0.4.2", "async-trait", "blake2b_simd 1.0.2", - "decaf377 0.5.0", + "decaf377", "derivative", "futures", "hash_hasher", @@ -5664,8 +5628,8 @@ dependencies = [ [[package]] name = "penumbra-tower-trace" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "futures", "hex", @@ -5677,7 +5641,7 @@ dependencies = [ "tendermint-proto", "tokio", "tokio-stream", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tonic 0.10.2", "tower", "tower-service", @@ -5686,11 +5650,12 @@ dependencies = [ [[package]] name = "penumbra-txhash" -version = "0.78.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.78.0#14959350abcb8cfbf33f9aedc7463fccfd8e3f9f" +version = "0.80.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=87adc8d6b15f6081c1adf169daed4ca8873bd9f6#87adc8d6b15f6081c1adf169daed4ca8873bd9f6" dependencies = [ "anyhow", "blake2b_simd 1.0.2", + "getrandom 0.2.15", "hex", "penumbra-proto", "penumbra-tct", @@ -5705,9 +5670,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -5716,12 +5681,12 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap 2.4.0", ] [[package]] @@ -5749,9 +5714,9 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -5811,59 +5776,40 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "poseidon-parameters" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58236ff8bf455c13046c92f041e887c4fd0e64819387a81177d6c70ebeb41711" -dependencies = [ - "anyhow", - "ark-ff 0.4.2", - "num-integer", -] - -[[package]] -name = "poseidon-paramgen" -version = "0.4.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69506f91189a68bff6c0e4f8c2beaf6b053430be7743059a0110477e9c28fda" +checksum = "f6df50e93cde74d26eb66c9674fccde32172e915a420fe2a73fda39ab377f709" dependencies = [ - "anyhow", - "ark-ff 0.4.2", - "ark-std 0.4.0", - "getrandom 0.2.14", - "merlin", - "num", - "num-bigint", - "poseidon-parameters", - "rand_core 0.6.4", + "decaf377", ] [[package]] name = "poseidon-permutation" -version = "0.5.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a022268b53cec1e99c4bd8c81be249709e971b78a433d9f5556e31f3cd7730b0" +checksum = "03c4e1e8d622017ece288f1a1b06f0bfeaacaa4166fa155a91103317299452e2" dependencies = [ "ark-ff 0.4.2", "ark-r1cs-std", "ark-relations", "ark-std 0.4.0", + "decaf377", "poseidon-parameters", ] [[package]] name = "poseidon377" -version = "0.6.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11dbcae1c9e4624576dd7631a1f2419a18afeaeef104263d70b6f20256ea5b72" +checksum = "ae0544874afdaf74b69efc90795c66ea7a494faeb2981a0585d46c757ee2fa94" dependencies = [ "ark-ec", - "ark-ed-on-bls12-377", "ark-ff 0.4.2", "ark-groth16", "ark-r1cs-std", @@ -5871,13 +5817,9 @@ dependencies = [ "ark-serialize 0.4.2", "ark-snark", "ark-std 0.4.0", - "decaf377 0.4.0", - "num-bigint", - "once_cell", + "decaf377", "poseidon-parameters", - "poseidon-paramgen", "poseidon-permutation", - "tracing", ] [[package]] @@ -5888,15 +5830,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" -version = "3.1.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", @@ -5908,15 +5853,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -5924,12 +5869,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ - "proc-macro2 1.0.79", - "syn 2.0.58", + "proc-macro2 1.0.86", + "syn 2.0.75", ] [[package]] @@ -5956,15 +5901,6 @@ dependencies = [ "toml_edit 0.19.15", ] -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -5981,7 +5917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "version_check", ] @@ -5992,7 +5928,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "version_check", ] @@ -6008,9 +5944,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -6021,34 +5957,34 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", "version_check", "yansi", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", "unarray", ] [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -6056,9 +5992,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck 0.5.0", @@ -6071,43 +6007,32 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.58", + "syn 2.0.75", "tempfile", ] [[package]] name = "prost-derive" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] -[[package]] -name = "pulldown-cmark" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" -dependencies = [ - "bitflags 2.5.0", - "memchr", - "unicase", -] - [[package]] name = "quanta" version = "0.12.3" @@ -6138,7 +6063,7 @@ version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", ] [[package]] @@ -6206,7 +6131,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", ] [[package]] @@ -6238,43 +6163,63 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.0.1" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "bitflags 2.5.0", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -6288,13 +6233,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -6311,9 +6256,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" @@ -6396,7 +6341,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.14", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -6429,7 +6374,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -6452,9 +6397,9 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "ruint" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -6476,15 +6421,15 @@ dependencies = [ [[package]] name = "ruint-macro" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rust-embed" -version = "8.3.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -6493,22 +6438,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.3.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "rust-embed-utils", - "syn 2.0.58", + "syn 2.0.75", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.3.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" dependencies = [ "globset", "sha2 0.10.8", @@ -6517,9 +6462,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -6554,7 +6499,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.22", + "semver 1.0.23", ] [[package]] @@ -6571,11 +6516,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -6584,9 +6529,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", @@ -6627,15 +6572,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe-proc-macro2" @@ -6704,9 +6649,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "cfg-if", "derive_more", @@ -6716,12 +6661,12 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.79", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", "quote", "syn 1.0.109", ] @@ -6779,11 +6724,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -6792,9 +6737,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -6820,9 +6765,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] @@ -6850,40 +6795,41 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -6915,16 +6861,16 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -6943,15 +6889,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.4.0", "serde", "serde_derive", "serde_json", @@ -6961,14 +6907,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -6977,7 +6923,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.4.0", "itoa", "ryu", "serde", @@ -7059,9 +7005,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -7078,9 +7024,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "simple_asn1" @@ -7104,21 +7050,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "skeptic" -version = "0.13.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" -dependencies = [ - "bytecount", - "cargo_metadata 0.14.2", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", -] - [[package]] name = "sketches-ddsketch" version = "0.2.2" @@ -7153,9 +7084,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -7205,12 +7136,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -7219,31 +7144,31 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.79", + "heck 0.5.0", + "proc-macro2 1.0.86", "quote", "rustversion", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "subtle-encoding" @@ -7266,18 +7191,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.58" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", "unicode-ident", ] @@ -7289,9 +7214,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -7335,14 +7260,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand 2.0.2", + "fastrand 2.1.0", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7392,9 +7318,9 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74994da9de4b1144837a367ca2c60c650f5526a7c1a54760a3020959b522e474" +checksum = "9b8090d0eef9ad57b1b913b5e358e26145c86017e87338136509b94383a4af25" dependencies = [ "derive_more", "flex-error", @@ -7432,12 +7358,12 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.14", + "getrandom 0.2.15", "peg", "pin-project", "rand 0.8.5", "reqwest", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_bytes", "serde_json", @@ -7451,7 +7377,7 @@ dependencies = [ "tokio", "tracing", "url", - "uuid 1.8.0", + "uuid 1.10.0", "walkdir", ] @@ -7506,22 +7432,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -7578,9 +7504,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -7593,22 +7519,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7623,13 +7548,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -7694,19 +7619,18 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "futures-util", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -7732,21 +7656,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.9", + "toml_edit 0.22.20", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -7757,46 +7681,35 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.4.0", "serde", "serde_spanned", "toml_datetime", "winnow 0.5.40", ] -[[package]] -name = "toml_edit" -version = "0.20.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.4.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.9" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.4.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.6", + "winnow 0.6.18", ] [[package]] @@ -7865,10 +7778,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" dependencies = [ "prettyplease", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "prost-build", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -7899,7 +7812,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tower-layer", "tower-service", "tracing", @@ -7934,7 +7847,7 @@ dependencies = [ "pin-project", "thiserror", "tokio", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tower", "tracing", ] @@ -7945,7 +7858,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "futures-core", "futures-util", @@ -7959,15 +7872,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -7987,9 +7900,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] @@ -8022,17 +7935,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -8057,7 +7959,7 @@ dependencies = [ "smallvec", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-subscriber 0.3.18", "web-time", ] @@ -8078,20 +7980,7 @@ version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ - "ansi_term", - "chrono", - "lazy_static", - "matchers 0.0.1", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", "tracing-core", - "tracing-log 0.1.4", - "tracing-serde", ] [[package]] @@ -8100,7 +7989,7 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "matchers 0.1.0", + "matchers", "nu-ansi-term", "once_cell", "regex", @@ -8111,7 +8000,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-serde", ] @@ -8169,6 +8058,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typenum" version = "1.17.0" @@ -8208,15 +8103,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -8240,9 +8126,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -8252,15 +8138,15 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ "generic-array", "subtle", @@ -8298,9 +8184,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -8322,9 +8208,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" @@ -8332,17 +8218,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "serde", ] [[package]] name = "uuid" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", ] [[package]] @@ -8364,7 +8250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" dependencies = [ "anyhow", - "cargo_metadata 0.18.1", + "cargo_metadata", "cfg-if", "git2", "regex", @@ -8375,9 +8261,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" @@ -8390,9 +8276,9 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "walkdir" @@ -8427,34 +8313,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -8464,9 +8351,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8474,28 +8361,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -8547,11 +8434,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -8566,7 +8453,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", ] [[package]] @@ -8584,7 +8471,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -8604,17 +8500,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -8625,9 +8522,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -8637,9 +8534,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -8649,9 +8546,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -8661,9 +8564,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -8673,9 +8576,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -8685,9 +8588,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -8697,9 +8600,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -8712,9 +8615,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -8787,29 +8690,30 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -8820,16 +8724,16 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.79", + "proc-macro2 1.0.86", "quote", - "syn 2.0.58", + "syn 2.0.75", ] [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index e55b7f13e5..a05d327add 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,9 +77,10 @@ metrics = "0.22.1" once_cell = "1.17.1" pbjson-types = "0.6" # Note that when updating the penumbra versions, vendored types in `proto/sequencerapis/astria_vendored` may need to be updated as well. -penumbra-ibc = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.78.0", default-features = false } -penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.78.0" } -penumbra-tower-trace = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.78.0" } +# can update to a tagged release when https://github.com/penumbra-zone/penumbra/pull/4822 is included +penumbra-ibc = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "87adc8d6b15f6081c1adf169daed4ca8873bd9f6", default-features = false } +penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "87adc8d6b15f6081c1adf169daed4ca8873bd9f6" } +penumbra-tower-trace = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "87adc8d6b15f6081c1adf169daed4ca8873bd9f6" } pin-project-lite = "0.2.13" prost = "0.12" rand = "0.8.5" diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index d31f05df58..e1bd1ddd4c 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -25,7 +25,7 @@ telemetry = { package = "astria-telemetry", path = "../astria-telemetry", featur anyhow = "1" borsh = { version = "1", features = ["derive"] } -cnidarium = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.78.0", features = [ +cnidarium = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "87adc8d6b15f6081c1adf169daed4ca8873bd9f6", features = [ "metrics", ] } ibc-proto = { version = "0.41.0", features = ["server"] } diff --git a/crates/astria-sequencer/src/ibc/ics20_transfer.rs b/crates/astria-sequencer/src/ibc/ics20_transfer.rs index 3ae8eacfd5..b632474bcb 100644 --- a/crates/astria-sequencer/src/ibc/ics20_transfer.rs +++ b/crates/astria-sequencer/src/ibc/ics20_transfer.rs @@ -293,18 +293,21 @@ impl AppHandlerExecute for Ics20Transfer { .context("failed to refund tokens during timeout_packet_execute") } - async fn acknowledge_packet_execute(mut state: S, msg: &MsgAcknowledgement) { + async fn acknowledge_packet_execute( + mut state: S, + msg: &MsgAcknowledgement, + ) -> anyhow::Result<()> { let ack: TokenTransferAcknowledgement = serde_json::from_slice( msg.acknowledgement.as_slice(), ) .expect("valid acknowledgement, should have been checked in acknowledge_packet_check"); if ack.is_successful() { - return; + return Ok(()); } // we put source and dest as chain_a (the source) as we're refunding tokens, // and the destination chain of the refund is the source. - if let Err(e) = execute_ics20_transfer( + execute_ics20_transfer( &mut state, &msg.packet.data, &msg.packet.port_on_a, @@ -314,13 +317,7 @@ impl AppHandlerExecute for Ics20Transfer { true, ) .await - { - let error: &dyn std::error::Error = e.as_ref(); - tracing::error!( - error, - "failed to refund tokens during acknowledge_packet_execute", - ); - } + .context("failed to refund tokens during acknowledge_packet_execute") } } diff --git a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs index 314d44c60f..cf5d77221a 100644 --- a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs +++ b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs @@ -40,6 +40,7 @@ use crate::{ StateReadExt as _, StateWriteExt as _, }, + state_ext::StateReadExt as _, transaction::StateReadExt as _, }; @@ -138,10 +139,14 @@ impl ActionHandler for action::Ics20Withdrawal { .await .context("failed to get ics20 withdrawal base fee")?; + let current_timestamp = state + .get_block_timestamp() + .await + .context("failed to get block timestamp")?; let packet = { let packet = withdrawal_to_unchecked_ibc_packet(self); state - .send_packet_check(packet) + .send_packet_check(packet, current_timestamp) .await .context("packet failed send check")? }; From acff940abd2dd9857e038323ed6eb8aa88016a87 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Thu, 22 Aug 2024 14:06:14 +0200 Subject: [PATCH 18/18] refactor(core, proto)!: define app genesis state in proto (#1346) ## Summary Defines `astria.protocol.genesis.v1alpha1.GenesisAppState`, replacing the Rust-as-JSON definition. ## Background All protocol relevant Astria types are supposed to be defined in Astria's protobuf spec. With https://github.com/astriaorg/astria/pull/1285 having redefined the memos as protobuf message, this patch migrates the last type to protobuf spec. ## Changes - Define `astria.protocol.genesis.v1alpha1.GenesisAppState` and related protobuf messages - Remove the `astria-core::sequencer::GenesisState` module - Update Sequencer in terms of the protobuf type - Update all charts and snapshots (especially the genesis template) ## Testing All tests have been updated to use the new types, including snapshot tests. The genesis state is only read at `init-chain` and does not affect the evaluation of the state machine. Hence no tests should change as long as the same data is passed in (which is reflected by `tests-breaking-changes` leading to the same hash). ## Breaking Changelist This is a breaking change because an old sequencer would not understand a new (json-formatted) genesis app state and vise versa. ## Related issues Closes https://github.com/astriaorg/astria/issues/1347 --- charts/sequencer/Chart.yaml | 2 +- .../files/cometbft/config/genesis.json | 18 +- charts/sequencer/templates/_helpers.tpl | 4 + crates/astria-core/Cargo.toml | 2 +- crates/astria-core/src/crypto.rs | 19 +- .../astria.protocol.genesis.v1alpha1.rs | 126 +++ .../astria.protocol.genesis.v1alpha1.serde.rs | 779 ++++++++++++++++++ crates/astria-core/src/generated/mod.rs | 12 + crates/astria-core/src/lib.rs | 1 - crates/astria-core/src/primitive/v1/u128.rs | 6 + .../astria-core/src/protocol/genesis/mod.rs | 1 + ...1__tests__genesis_state_is_unchanged.snap} | 57 +- .../src/protocol/genesis/v1alpha1.rs | 778 +++++++++++++++++ crates/astria-core/src/protocol/mod.rs | 1 + crates/astria-core/src/sequencer.rs | 390 --------- .../src/genesis_example.rs | 90 +- .../src/accounts/component.rs | 3 +- crates/astria-sequencer/src/app/benchmarks.rs | 45 +- crates/astria-sequencer/src/app/mod.rs | 3 +- crates/astria-sequencer/src/app/test_utils.rs | 84 +- crates/astria-sequencer/src/app/tests_app.rs | 18 +- .../src/app/tests_breaking_changes.rs | 75 +- .../src/app/tests_execute_transaction.rs | 116 +-- .../astria-sequencer/src/bridge/component.rs | 3 +- crates/astria-sequencer/src/ibc/component.rs | 5 +- .../src/sequence/component.rs | 3 +- .../astria-sequencer/src/service/consensus.rs | 42 +- .../protocol/genesis/v1alpha1/types.proto | 47 ++ tools/protobuf-compiler/src/main.rs | 4 + 29 files changed, 2077 insertions(+), 657 deletions(-) create mode 100644 crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs create mode 100644 crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs create mode 100644 crates/astria-core/src/protocol/genesis/mod.rs rename crates/astria-core/src/{snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap => protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap} (50%) create mode 100644 crates/astria-core/src/protocol/genesis/v1alpha1.rs delete mode 100644 crates/astria-core/src/sequencer.rs create mode 100644 proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto diff --git a/charts/sequencer/Chart.yaml b/charts/sequencer/Chart.yaml index bb8d4fc527..577becef95 100644 --- a/charts/sequencer/Chart.yaml +++ b/charts/sequencer/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.19.1 +version: 0.20.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. diff --git a/charts/sequencer/files/cometbft/config/genesis.json b/charts/sequencer/files/cometbft/config/genesis.json index 4e8e7a4ff4..9e266902c2 100644 --- a/charts/sequencer/files/cometbft/config/genesis.json +++ b/charts/sequencer/files/cometbft/config/genesis.json @@ -3,13 +3,13 @@ "app_state": { "native_asset_base_denomination": "{{ .Values.genesis.nativeAssetBaseDenomination }}", "fees": { - "transfer_base_fee": {{ .Values.genesis.fees.transferBaseFee }}, - "sequence_base_fee": {{ .Values.genesis.fees.sequenceBaseFee }}, - "sequence_byte_cost_multiplier": {{ .Values.genesis.fees.sequenceByteCostMultiplier }}, - "init_bridge_account_base_fee": {{ .Values.genesis.fees.initBridgeAccountBaseFee }}, - "bridge_lock_byte_cost_multiplier": {{ .Values.genesis.fees.bridgeLockByteCostMultiplier }}, - "bridge_sudo_change_fee": {{ .Values.genesis.fees.bridgeSudoChangeFee }}, - "ics20_withdrawal_base_fee": {{ .Values.genesis.fees.ics20WithdrawalBaseFee }} + "transfer_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.transferBaseFee }}, + "sequence_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.sequenceBaseFee }}, + "sequence_byte_cost_multiplier": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.sequenceByteCostMultiplier }}, + "init_bridge_account_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.initBridgeAccountBaseFee }}, + "bridge_lock_byte_cost_multiplier": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.bridgeLockByteCostMultiplier }}, + "bridge_sudo_change_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.bridgeSudoChangeFee }}, + "ics20_withdrawal_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.ics20WithdrawalBaseFee }} }, "allowed_fee_assets": [ {{- range $index, $value := .Values.genesis.allowedFeeAssets }} @@ -17,7 +17,7 @@ "{{ $value }}" {{- end }} ], - "ibc_params": { + "ibc_parameters": { "ibc_enabled": {{ .Values.genesis.ibc.enabled }}, "inbound_ics20_transfers_enabled": {{ .Values.genesis.ibc.inboundEnabled }}, "outbound_ics20_transfers_enabled": {{ .Values.genesis.ibc.outboundEnabled }} @@ -30,7 +30,7 @@ {{- if $index }},{{- end }} { "address": {{ include "sequencer.address" $value.address }}, - "balance": {{ toString $value.balance | replace "\"" "" }} + "balance": {{ include "sequencer.toUint128Proto" ( toString $value.balance | replace "\"" "" ) }} } {{- end }} ], diff --git a/charts/sequencer/templates/_helpers.tpl b/charts/sequencer/templates/_helpers.tpl index bab0a2380a..2dc1da25ab 100644 --- a/charts/sequencer/templates/_helpers.tpl +++ b/charts/sequencer/templates/_helpers.tpl @@ -71,3 +71,7 @@ name: {{ .Values.moniker }}-sequencer-metrics {{/* New sequencer address */}} {{- define "sequencer.address"}}{ "bech32m": "{{ . }}" } {{- end }} + +{{/* uint64 fee converted to a astria proto Uint128 with only lo set */}} +{{- define "sequencer.toUint128Proto"}}{ "lo": {{ . }} } +{{- end }} diff --git a/crates/astria-core/Cargo.toml b/crates/astria-core/Cargo.toml index 049919b07a..3c40345d58 100644 --- a/crates/astria-core/Cargo.toml +++ b/crates/astria-core/Cargo.toml @@ -57,7 +57,7 @@ base64-serde = ["dep:base64-serde"] brotli = ["dep:brotli"] [dev-dependencies] +astria-core = { path = ".", features = ["serde"] } insta = { workspace = true, features = ["json"] } rand = { workspace = true } tempfile = { workspace = true } -astria-core = { path = ".", features = ["serde"] } diff --git a/crates/astria-core/src/crypto.rs b/crates/astria-core/src/crypto.rs index 69f017fb78..77da8f7b80 100644 --- a/crates/astria-core/src/crypto.rs +++ b/crates/astria-core/src/crypto.rs @@ -36,7 +36,11 @@ use zeroize::{ ZeroizeOnDrop, }; -use crate::primitive::v1::ADDRESS_LEN; +use crate::primitive::v1::{ + Address, + AddressError, + ADDRESS_LEN, +}; /// An Ed25519 signing key. // *Implementation note*: this is currently a refinement type around @@ -82,6 +86,19 @@ impl SigningKey { pub fn address_bytes(&self) -> [u8; ADDRESS_LEN] { self.verification_key().address_bytes() } + + /// Attempts to create an Astria bech32m `[Address]` with the given prefix. + /// + /// # Errors + /// Returns an [`AddressError`] if an address could not be constructed + /// with the given prefix. Usually if the prefix was too long or contained + /// characters not allowed by bech32m. + pub fn try_address(&self, prefix: &str) -> Result { + Address::builder() + .prefix(prefix) + .array(self.address_bytes()) + .try_build() + } } impl Debug for SigningKey { diff --git a/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs new file mode 100644 index 0000000000..17ccce5d82 --- /dev/null +++ b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs @@ -0,0 +1,126 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GenesisAppState { + #[prost(string, tag = "1")] + pub chain_id: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub address_prefixes: ::core::option::Option, + #[prost(message, repeated, tag = "3")] + pub accounts: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "4")] + pub authority_sudo_address: ::core::option::Option< + super::super::super::primitive::v1::Address, + >, + #[prost(message, optional, tag = "5")] + pub ibc_sudo_address: ::core::option::Option< + super::super::super::primitive::v1::Address, + >, + #[prost(message, repeated, tag = "6")] + pub ibc_relayer_addresses: ::prost::alloc::vec::Vec< + super::super::super::primitive::v1::Address, + >, + #[prost(string, tag = "7")] + pub native_asset_base_denomination: ::prost::alloc::string::String, + #[prost(message, optional, tag = "8")] + pub ibc_parameters: ::core::option::Option, + #[prost(string, repeated, tag = "9")] + pub allowed_fee_assets: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, optional, tag = "10")] + pub fees: ::core::option::Option, +} +impl ::prost::Name for GenesisAppState { + const NAME: &'static str = "GenesisAppState"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Account { + #[prost(message, optional, tag = "1")] + pub address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub balance: ::core::option::Option, +} +impl ::prost::Name for Account { + const NAME: &'static str = "Account"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AddressPrefixes { + #[prost(string, tag = "1")] + pub base: ::prost::alloc::string::String, +} +impl ::prost::Name for AddressPrefixes { + const NAME: &'static str = "AddressPrefixes"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +/// IBC configuration data. +#[derive(Copy)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IbcParameters { + /// Whether IBC (forming connections, processing IBC packets) is enabled. + #[prost(bool, tag = "1")] + pub ibc_enabled: bool, + /// Whether inbound ICS-20 transfers are enabled + #[prost(bool, tag = "2")] + pub inbound_ics20_transfers_enabled: bool, + /// Whether outbound ICS-20 transfers are enabled + #[prost(bool, tag = "3")] + pub outbound_ics20_transfers_enabled: bool, +} +impl ::prost::Name for IbcParameters { + const NAME: &'static str = "IbcParameters"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Fees { + #[prost(message, optional, tag = "1")] + pub transfer_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "2")] + pub sequence_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "3")] + pub sequence_byte_cost_multiplier: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "4")] + pub init_bridge_account_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "5")] + pub bridge_lock_byte_cost_multiplier: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "6")] + pub bridge_sudo_change_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "7")] + pub ics20_withdrawal_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, +} +impl ::prost::Name for Fees { + const NAME: &'static str = "Fees"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} diff --git a/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs new file mode 100644 index 0000000000..4636c57237 --- /dev/null +++ b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs @@ -0,0 +1,779 @@ +impl serde::Serialize for Account { + #[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_some() { + len += 1; + } + if self.balance.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.Account", len)?; + if let Some(v) = self.address.as_ref() { + struct_ser.serialize_field("address", v)?; + } + if let Some(v) = self.balance.as_ref() { + struct_ser.serialize_field("balance", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Account { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "balance", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + Balance, + } + 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), + "balance" => Ok(GeneratedField::Balance), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Account; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.Account") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut balance__ = 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__ = map_.next_value()?; + } + GeneratedField::Balance => { + if balance__.is_some() { + return Err(serde::de::Error::duplicate_field("balance")); + } + balance__ = map_.next_value()?; + } + } + } + Ok(Account { + address: address__, + balance: balance__, + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.Account", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for AddressPrefixes { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.base.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.AddressPrefixes", len)?; + if !self.base.is_empty() { + struct_ser.serialize_field("base", &self.base)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AddressPrefixes { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "base", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Base, + } + 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 { + "base" => Ok(GeneratedField::Base), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AddressPrefixes; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.AddressPrefixes") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut base__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Base => { + if base__.is_some() { + return Err(serde::de::Error::duplicate_field("base")); + } + base__ = Some(map_.next_value()?); + } + } + } + Ok(AddressPrefixes { + base: base__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.AddressPrefixes", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Fees { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.transfer_base_fee.is_some() { + len += 1; + } + if self.sequence_base_fee.is_some() { + len += 1; + } + if self.sequence_byte_cost_multiplier.is_some() { + len += 1; + } + if self.init_bridge_account_base_fee.is_some() { + len += 1; + } + if self.bridge_lock_byte_cost_multiplier.is_some() { + len += 1; + } + if self.bridge_sudo_change_fee.is_some() { + len += 1; + } + if self.ics20_withdrawal_base_fee.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.Fees", len)?; + if let Some(v) = self.transfer_base_fee.as_ref() { + struct_ser.serialize_field("transferBaseFee", v)?; + } + if let Some(v) = self.sequence_base_fee.as_ref() { + struct_ser.serialize_field("sequenceBaseFee", v)?; + } + if let Some(v) = self.sequence_byte_cost_multiplier.as_ref() { + struct_ser.serialize_field("sequenceByteCostMultiplier", v)?; + } + if let Some(v) = self.init_bridge_account_base_fee.as_ref() { + struct_ser.serialize_field("initBridgeAccountBaseFee", v)?; + } + if let Some(v) = self.bridge_lock_byte_cost_multiplier.as_ref() { + struct_ser.serialize_field("bridgeLockByteCostMultiplier", v)?; + } + if let Some(v) = self.bridge_sudo_change_fee.as_ref() { + struct_ser.serialize_field("bridgeSudoChangeFee", v)?; + } + if let Some(v) = self.ics20_withdrawal_base_fee.as_ref() { + struct_ser.serialize_field("ics20WithdrawalBaseFee", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Fees { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "transfer_base_fee", + "transferBaseFee", + "sequence_base_fee", + "sequenceBaseFee", + "sequence_byte_cost_multiplier", + "sequenceByteCostMultiplier", + "init_bridge_account_base_fee", + "initBridgeAccountBaseFee", + "bridge_lock_byte_cost_multiplier", + "bridgeLockByteCostMultiplier", + "bridge_sudo_change_fee", + "bridgeSudoChangeFee", + "ics20_withdrawal_base_fee", + "ics20WithdrawalBaseFee", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TransferBaseFee, + SequenceBaseFee, + SequenceByteCostMultiplier, + InitBridgeAccountBaseFee, + BridgeLockByteCostMultiplier, + BridgeSudoChangeFee, + Ics20WithdrawalBaseFee, + } + 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 { + "transferBaseFee" | "transfer_base_fee" => Ok(GeneratedField::TransferBaseFee), + "sequenceBaseFee" | "sequence_base_fee" => Ok(GeneratedField::SequenceBaseFee), + "sequenceByteCostMultiplier" | "sequence_byte_cost_multiplier" => Ok(GeneratedField::SequenceByteCostMultiplier), + "initBridgeAccountBaseFee" | "init_bridge_account_base_fee" => Ok(GeneratedField::InitBridgeAccountBaseFee), + "bridgeLockByteCostMultiplier" | "bridge_lock_byte_cost_multiplier" => Ok(GeneratedField::BridgeLockByteCostMultiplier), + "bridgeSudoChangeFee" | "bridge_sudo_change_fee" => Ok(GeneratedField::BridgeSudoChangeFee), + "ics20WithdrawalBaseFee" | "ics20_withdrawal_base_fee" => Ok(GeneratedField::Ics20WithdrawalBaseFee), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Fees; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.Fees") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut transfer_base_fee__ = None; + let mut sequence_base_fee__ = None; + let mut sequence_byte_cost_multiplier__ = None; + let mut init_bridge_account_base_fee__ = None; + let mut bridge_lock_byte_cost_multiplier__ = None; + let mut bridge_sudo_change_fee__ = None; + let mut ics20_withdrawal_base_fee__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TransferBaseFee => { + if transfer_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("transferBaseFee")); + } + transfer_base_fee__ = map_.next_value()?; + } + GeneratedField::SequenceBaseFee => { + if sequence_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("sequenceBaseFee")); + } + sequence_base_fee__ = map_.next_value()?; + } + GeneratedField::SequenceByteCostMultiplier => { + if sequence_byte_cost_multiplier__.is_some() { + return Err(serde::de::Error::duplicate_field("sequenceByteCostMultiplier")); + } + sequence_byte_cost_multiplier__ = map_.next_value()?; + } + GeneratedField::InitBridgeAccountBaseFee => { + if init_bridge_account_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("initBridgeAccountBaseFee")); + } + init_bridge_account_base_fee__ = map_.next_value()?; + } + GeneratedField::BridgeLockByteCostMultiplier => { + if bridge_lock_byte_cost_multiplier__.is_some() { + return Err(serde::de::Error::duplicate_field("bridgeLockByteCostMultiplier")); + } + bridge_lock_byte_cost_multiplier__ = map_.next_value()?; + } + GeneratedField::BridgeSudoChangeFee => { + if bridge_sudo_change_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("bridgeSudoChangeFee")); + } + bridge_sudo_change_fee__ = map_.next_value()?; + } + GeneratedField::Ics20WithdrawalBaseFee => { + if ics20_withdrawal_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("ics20WithdrawalBaseFee")); + } + ics20_withdrawal_base_fee__ = map_.next_value()?; + } + } + } + Ok(Fees { + transfer_base_fee: transfer_base_fee__, + sequence_base_fee: sequence_base_fee__, + sequence_byte_cost_multiplier: sequence_byte_cost_multiplier__, + init_bridge_account_base_fee: init_bridge_account_base_fee__, + bridge_lock_byte_cost_multiplier: bridge_lock_byte_cost_multiplier__, + bridge_sudo_change_fee: bridge_sudo_change_fee__, + ics20_withdrawal_base_fee: ics20_withdrawal_base_fee__, + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.Fees", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GenesisAppState { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.chain_id.is_empty() { + len += 1; + } + if self.address_prefixes.is_some() { + len += 1; + } + if !self.accounts.is_empty() { + len += 1; + } + if self.authority_sudo_address.is_some() { + len += 1; + } + if self.ibc_sudo_address.is_some() { + len += 1; + } + if !self.ibc_relayer_addresses.is_empty() { + len += 1; + } + if !self.native_asset_base_denomination.is_empty() { + len += 1; + } + if self.ibc_parameters.is_some() { + len += 1; + } + if !self.allowed_fee_assets.is_empty() { + len += 1; + } + if self.fees.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.GenesisAppState", len)?; + if !self.chain_id.is_empty() { + struct_ser.serialize_field("chainId", &self.chain_id)?; + } + if let Some(v) = self.address_prefixes.as_ref() { + struct_ser.serialize_field("addressPrefixes", v)?; + } + if !self.accounts.is_empty() { + struct_ser.serialize_field("accounts", &self.accounts)?; + } + if let Some(v) = self.authority_sudo_address.as_ref() { + struct_ser.serialize_field("authoritySudoAddress", v)?; + } + if let Some(v) = self.ibc_sudo_address.as_ref() { + struct_ser.serialize_field("ibcSudoAddress", v)?; + } + if !self.ibc_relayer_addresses.is_empty() { + struct_ser.serialize_field("ibcRelayerAddresses", &self.ibc_relayer_addresses)?; + } + if !self.native_asset_base_denomination.is_empty() { + struct_ser.serialize_field("nativeAssetBaseDenomination", &self.native_asset_base_denomination)?; + } + if let Some(v) = self.ibc_parameters.as_ref() { + struct_ser.serialize_field("ibcParameters", v)?; + } + if !self.allowed_fee_assets.is_empty() { + struct_ser.serialize_field("allowedFeeAssets", &self.allowed_fee_assets)?; + } + if let Some(v) = self.fees.as_ref() { + struct_ser.serialize_field("fees", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GenesisAppState { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "chain_id", + "chainId", + "address_prefixes", + "addressPrefixes", + "accounts", + "authority_sudo_address", + "authoritySudoAddress", + "ibc_sudo_address", + "ibcSudoAddress", + "ibc_relayer_addresses", + "ibcRelayerAddresses", + "native_asset_base_denomination", + "nativeAssetBaseDenomination", + "ibc_parameters", + "ibcParameters", + "allowed_fee_assets", + "allowedFeeAssets", + "fees", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ChainId, + AddressPrefixes, + Accounts, + AuthoritySudoAddress, + IbcSudoAddress, + IbcRelayerAddresses, + NativeAssetBaseDenomination, + IbcParameters, + AllowedFeeAssets, + Fees, + } + 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 { + "chainId" | "chain_id" => Ok(GeneratedField::ChainId), + "addressPrefixes" | "address_prefixes" => Ok(GeneratedField::AddressPrefixes), + "accounts" => Ok(GeneratedField::Accounts), + "authoritySudoAddress" | "authority_sudo_address" => Ok(GeneratedField::AuthoritySudoAddress), + "ibcSudoAddress" | "ibc_sudo_address" => Ok(GeneratedField::IbcSudoAddress), + "ibcRelayerAddresses" | "ibc_relayer_addresses" => Ok(GeneratedField::IbcRelayerAddresses), + "nativeAssetBaseDenomination" | "native_asset_base_denomination" => Ok(GeneratedField::NativeAssetBaseDenomination), + "ibcParameters" | "ibc_parameters" => Ok(GeneratedField::IbcParameters), + "allowedFeeAssets" | "allowed_fee_assets" => Ok(GeneratedField::AllowedFeeAssets), + "fees" => Ok(GeneratedField::Fees), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GenesisAppState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.GenesisAppState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut chain_id__ = None; + let mut address_prefixes__ = None; + let mut accounts__ = None; + let mut authority_sudo_address__ = None; + let mut ibc_sudo_address__ = None; + let mut ibc_relayer_addresses__ = None; + let mut native_asset_base_denomination__ = None; + let mut ibc_parameters__ = None; + let mut allowed_fee_assets__ = None; + let mut fees__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ChainId => { + if chain_id__.is_some() { + return Err(serde::de::Error::duplicate_field("chainId")); + } + chain_id__ = Some(map_.next_value()?); + } + GeneratedField::AddressPrefixes => { + if address_prefixes__.is_some() { + return Err(serde::de::Error::duplicate_field("addressPrefixes")); + } + address_prefixes__ = map_.next_value()?; + } + GeneratedField::Accounts => { + if accounts__.is_some() { + return Err(serde::de::Error::duplicate_field("accounts")); + } + accounts__ = Some(map_.next_value()?); + } + GeneratedField::AuthoritySudoAddress => { + if authority_sudo_address__.is_some() { + return Err(serde::de::Error::duplicate_field("authoritySudoAddress")); + } + authority_sudo_address__ = map_.next_value()?; + } + GeneratedField::IbcSudoAddress => { + if ibc_sudo_address__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcSudoAddress")); + } + ibc_sudo_address__ = map_.next_value()?; + } + GeneratedField::IbcRelayerAddresses => { + if ibc_relayer_addresses__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcRelayerAddresses")); + } + ibc_relayer_addresses__ = Some(map_.next_value()?); + } + GeneratedField::NativeAssetBaseDenomination => { + if native_asset_base_denomination__.is_some() { + return Err(serde::de::Error::duplicate_field("nativeAssetBaseDenomination")); + } + native_asset_base_denomination__ = Some(map_.next_value()?); + } + GeneratedField::IbcParameters => { + if ibc_parameters__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcParameters")); + } + ibc_parameters__ = map_.next_value()?; + } + GeneratedField::AllowedFeeAssets => { + if allowed_fee_assets__.is_some() { + return Err(serde::de::Error::duplicate_field("allowedFeeAssets")); + } + allowed_fee_assets__ = Some(map_.next_value()?); + } + GeneratedField::Fees => { + if fees__.is_some() { + return Err(serde::de::Error::duplicate_field("fees")); + } + fees__ = map_.next_value()?; + } + } + } + Ok(GenesisAppState { + chain_id: chain_id__.unwrap_or_default(), + address_prefixes: address_prefixes__, + accounts: accounts__.unwrap_or_default(), + authority_sudo_address: authority_sudo_address__, + ibc_sudo_address: ibc_sudo_address__, + ibc_relayer_addresses: ibc_relayer_addresses__.unwrap_or_default(), + native_asset_base_denomination: native_asset_base_denomination__.unwrap_or_default(), + ibc_parameters: ibc_parameters__, + allowed_fee_assets: allowed_fee_assets__.unwrap_or_default(), + fees: fees__, + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.GenesisAppState", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for IbcParameters { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.ibc_enabled { + len += 1; + } + if self.inbound_ics20_transfers_enabled { + len += 1; + } + if self.outbound_ics20_transfers_enabled { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.IbcParameters", len)?; + if self.ibc_enabled { + struct_ser.serialize_field("ibcEnabled", &self.ibc_enabled)?; + } + if self.inbound_ics20_transfers_enabled { + struct_ser.serialize_field("inboundIcs20TransfersEnabled", &self.inbound_ics20_transfers_enabled)?; + } + if self.outbound_ics20_transfers_enabled { + struct_ser.serialize_field("outboundIcs20TransfersEnabled", &self.outbound_ics20_transfers_enabled)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for IbcParameters { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "ibc_enabled", + "ibcEnabled", + "inbound_ics20_transfers_enabled", + "inboundIcs20TransfersEnabled", + "outbound_ics20_transfers_enabled", + "outboundIcs20TransfersEnabled", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + IbcEnabled, + InboundIcs20TransfersEnabled, + OutboundIcs20TransfersEnabled, + } + 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 { + "ibcEnabled" | "ibc_enabled" => Ok(GeneratedField::IbcEnabled), + "inboundIcs20TransfersEnabled" | "inbound_ics20_transfers_enabled" => Ok(GeneratedField::InboundIcs20TransfersEnabled), + "outboundIcs20TransfersEnabled" | "outbound_ics20_transfers_enabled" => Ok(GeneratedField::OutboundIcs20TransfersEnabled), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = IbcParameters; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.IbcParameters") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut ibc_enabled__ = None; + let mut inbound_ics20_transfers_enabled__ = None; + let mut outbound_ics20_transfers_enabled__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::IbcEnabled => { + if ibc_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcEnabled")); + } + ibc_enabled__ = Some(map_.next_value()?); + } + GeneratedField::InboundIcs20TransfersEnabled => { + if inbound_ics20_transfers_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("inboundIcs20TransfersEnabled")); + } + inbound_ics20_transfers_enabled__ = Some(map_.next_value()?); + } + GeneratedField::OutboundIcs20TransfersEnabled => { + if outbound_ics20_transfers_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("outboundIcs20TransfersEnabled")); + } + outbound_ics20_transfers_enabled__ = Some(map_.next_value()?); + } + } + } + Ok(IbcParameters { + ibc_enabled: ibc_enabled__.unwrap_or_default(), + inbound_ics20_transfers_enabled: inbound_ics20_transfers_enabled__.unwrap_or_default(), + outbound_ics20_transfers_enabled: outbound_ics20_transfers_enabled__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.IbcParameters", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/mod.rs b/crates/astria-core/src/generated/mod.rs index d476b83f21..4b78c07fe0 100644 --- a/crates/astria-core/src/generated/mod.rs +++ b/crates/astria-core/src/generated/mod.rs @@ -84,6 +84,18 @@ pub mod protocol { pub mod v1alpha1; } #[path = ""] + pub mod genesis { + pub mod v1alpha1 { + include!("astria.protocol.genesis.v1alpha1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impls { + use super::*; + include!("astria.protocol.genesis.v1alpha1.serde.rs"); + } + } + } + #[path = ""] pub mod memos { pub mod v1alpha1 { include!("astria.protocol.memos.v1alpha1.rs"); diff --git a/crates/astria-core/src/lib.rs b/crates/astria-core/src/lib.rs index 29c87603ef..4fecf8dddd 100644 --- a/crates/astria-core/src/lib.rs +++ b/crates/astria-core/src/lib.rs @@ -12,7 +12,6 @@ pub mod crypto; pub mod execution; pub mod primitive; pub mod protocol; -pub mod sequencer; pub mod sequencerblock; #[cfg(feature = "brotli")] diff --git a/crates/astria-core/src/primitive/v1/u128.rs b/crates/astria-core/src/primitive/v1/u128.rs index fbfb4208b1..ee77646a27 100644 --- a/crates/astria-core/src/primitive/v1/u128.rs +++ b/crates/astria-core/src/primitive/v1/u128.rs @@ -40,6 +40,12 @@ impl From for u128 { } } +impl<'a> From<&'a u128> for Uint128 { + fn from(primitive: &'a u128) -> Self { + (*primitive).into() + } +} + #[cfg(test)] mod tests { use crate::generated::primitive::v1::Uint128; diff --git a/crates/astria-core/src/protocol/genesis/mod.rs b/crates/astria-core/src/protocol/genesis/mod.rs new file mode 100644 index 0000000000..32a5a9d4fd --- /dev/null +++ b/crates/astria-core/src/protocol/genesis/mod.rs @@ -0,0 +1 @@ +pub mod v1alpha1; diff --git a/crates/astria-core/src/snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap b/crates/astria-core/src/protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap similarity index 50% rename from crates/astria-core/src/snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap rename to crates/astria-core/src/protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap index fde7a2b1e4..faa12162a8 100644 --- a/crates/astria-core/src/snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap +++ b/crates/astria-core/src/protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap @@ -1,9 +1,10 @@ --- -source: crates/astria-core/src/sequencer.rs +source: crates/astria-core/src/protocol/genesis/v1alpha1.rs expression: genesis_state() --- { - "address_prefixes": { + "chainId": "astria-1", + "addressPrefixes": { "base": "astria" }, "accounts": [ @@ -11,28 +12,34 @@ expression: genesis_state() "address": { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, - "balance": 1000000000000000000 + "balance": { + "lo": "1000000000000000000" + } }, { "address": { "bech32m": "astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z" }, - "balance": 1000000000000000000 + "balance": { + "lo": "1000000000000000000" + } }, { "address": { "bech32m": "astria1vpcfutferpjtwv457r63uwr6hdm8gwr3pxt5ny" }, - "balance": 1000000000000000000 + "balance": { + "lo": "1000000000000000000" + } } ], - "authority_sudo_address": { + "authoritySudoAddress": { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, - "ibc_sudo_address": { + "ibcSudoAddress": { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, - "ibc_relayer_addresses": [ + "ibcRelayerAddresses": [ { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, @@ -40,22 +47,36 @@ expression: genesis_state() "bech32m": "astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z" } ], - "native_asset_base_denomination": "nria", - "ibc_params": { + "nativeAssetBaseDenomination": "nria", + "ibcParameters": { "ibcEnabled": true, "inboundIcs20TransfersEnabled": true, "outboundIcs20TransfersEnabled": true }, - "allowed_fee_assets": [ + "allowedFeeAssets": [ "nria" ], "fees": { - "transfer_base_fee": 12, - "sequence_base_fee": 32, - "sequence_byte_cost_multiplier": 1, - "init_bridge_account_base_fee": 48, - "bridge_lock_byte_cost_multiplier": 1, - "bridge_sudo_change_fee": 24, - "ics20_withdrawal_base_fee": 24 + "transferBaseFee": { + "lo": "12" + }, + "sequenceBaseFee": { + "lo": "32" + }, + "sequenceByteCostMultiplier": { + "lo": "1" + }, + "initBridgeAccountBaseFee": { + "lo": "48" + }, + "bridgeLockByteCostMultiplier": { + "lo": "1" + }, + "bridgeSudoChangeFee": { + "lo": "24" + }, + "ics20WithdrawalBaseFee": { + "lo": "24" + } } } diff --git a/crates/astria-core/src/protocol/genesis/v1alpha1.rs b/crates/astria-core/src/protocol/genesis/v1alpha1.rs new file mode 100644 index 0000000000..2131d1fccc --- /dev/null +++ b/crates/astria-core/src/protocol/genesis/v1alpha1.rs @@ -0,0 +1,778 @@ +use std::convert::Infallible; + +pub use penumbra_ibc::params::IBCParameters; + +use crate::{ + generated::protocol::genesis::v1alpha1 as raw, + primitive::v1::{ + asset::{ + self, + denom::ParseTracePrefixedError, + ParseDenomError, + }, + Address, + AddressError, + ADDRESS_LEN, + }, + Protobuf, +}; + +/// The genesis state of Astria's Sequencer. +/// +/// Verified to only contain valid fields (right now, addresses that have the same base prefix +/// as set in `GenesisState::address_prefixes::base`). +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(try_from = "raw::GenesisAppState", into = "raw::GenesisAppState") +)] +pub struct GenesisAppState { + chain_id: String, + address_prefixes: AddressPrefixes, + accounts: Vec, + authority_sudo_address: crate::primitive::v1::Address, + ibc_sudo_address: crate::primitive::v1::Address, + ibc_relayer_addresses: Vec, + native_asset_base_denomination: asset::TracePrefixed, + ibc_parameters: IBCParameters, + allowed_fee_assets: Vec, + fees: Fees, +} + +impl GenesisAppState { + #[must_use] + pub fn address_prefixes(&self) -> &AddressPrefixes { + &self.address_prefixes + } + + #[must_use] + pub fn accounts(&self) -> &[Account] { + &self.accounts + } + + #[must_use] + pub fn authority_sudo_address(&self) -> &Address { + &self.authority_sudo_address + } + + #[must_use] + pub fn chain_id(&self) -> &str { + &self.chain_id + } + + #[must_use] + pub fn ibc_sudo_address(&self) -> &Address { + &self.ibc_sudo_address + } + + #[must_use] + pub fn ibc_relayer_addresses(&self) -> &[Address] { + &self.ibc_relayer_addresses + } + + #[must_use] + pub fn native_asset_base_denomination(&self) -> &asset::TracePrefixed { + &self.native_asset_base_denomination + } + + #[must_use] + pub fn ibc_parameters(&self) -> &IBCParameters { + &self.ibc_parameters + } + + #[must_use] + pub fn allowed_fee_assets(&self) -> &[asset::Denom] { + &self.allowed_fee_assets + } + + #[must_use] + pub fn fees(&self) -> &Fees { + &self.fees + } + + fn ensure_address_has_base_prefix( + &self, + address: &Address, + field: &str, + ) -> Result<(), Box> { + if self.address_prefixes.base != address.prefix() { + return Err(Box::new(AddressDoesNotMatchBase { + base_prefix: self.address_prefixes.base.clone(), + address: *address, + field: field.to_string(), + })); + } + Ok(()) + } + + fn ensure_all_addresses_have_base_prefix(&self) -> Result<(), Box> { + for (i, account) in self.accounts.iter().enumerate() { + self.ensure_address_has_base_prefix( + &account.address, + &format!(".accounts[{i}].address"), + )?; + } + self.ensure_address_has_base_prefix( + &self.authority_sudo_address, + ".authority_sudo_address", + )?; + self.ensure_address_has_base_prefix(&self.ibc_sudo_address, ".ibc_sudo_address")?; + for (i, address) in self.ibc_relayer_addresses.iter().enumerate() { + self.ensure_address_has_base_prefix(address, &format!(".ibc_relayer_addresses[{i}]"))?; + } + Ok(()) + } +} + +impl Protobuf for GenesisAppState { + type Error = GenesisAppStateError; + type Raw = raw::GenesisAppState; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + address_prefixes, + accounts, + authority_sudo_address, + chain_id, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_parameters, + allowed_fee_assets, + fees, + } = raw; + let address_prefixes = address_prefixes + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("address_prefixes")) + .and_then(|aps| { + AddressPrefixes::try_from_raw_ref(aps).map_err(Self::Error::address_prefixes) + })?; + let accounts = accounts + .iter() + .map(Account::try_from_raw_ref) + .collect::, _>>() + .map_err(Self::Error::accounts)?; + + let authority_sudo_address = authority_sudo_address + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("authority_sudo_address")) + .and_then(|addr| { + Address::try_from_raw(addr).map_err(Self::Error::authority_sudo_address) + })?; + let ibc_sudo_address = ibc_sudo_address + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("ibc_sudo_address")) + .and_then(|addr| Address::try_from_raw(addr).map_err(Self::Error::ibc_sudo_address))?; + + let ibc_relayer_addresses = ibc_relayer_addresses + .iter() + .map(Address::try_from_raw) + .collect::>() + .map_err(Self::Error::ibc_relayer_addresses)?; + + let native_asset_base_denomination = native_asset_base_denomination + .parse() + .map_err(Self::Error::native_asset_base_denomination)?; + + let ibc_parameters = { + let params = ibc_parameters + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("ibc_parameters"))?; + IBCParameters::try_from_raw_ref(params).expect("conversion is infallible") + }; + + let allowed_fee_assets = allowed_fee_assets + .iter() + .map(|asset| asset.parse()) + .collect::>() + .map_err(Self::Error::allowed_fee_assets)?; + + let fees = fees + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("fees")) + .and_then(|fees| Fees::try_from_raw_ref(fees).map_err(Self::Error::fees))?; + + let this = Self { + address_prefixes, + accounts, + authority_sudo_address, + chain_id: chain_id.clone(), + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_parameters, + allowed_fee_assets, + fees, + }; + this.ensure_all_addresses_have_base_prefix() + .map_err(Self::Error::address_does_not_match_base)?; + Ok(this) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + address_prefixes, + accounts, + authority_sudo_address, + chain_id, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_parameters, + allowed_fee_assets, + fees, + } = self; + Self::Raw { + address_prefixes: Some(address_prefixes.to_raw()), + accounts: accounts.iter().map(Account::to_raw).collect(), + authority_sudo_address: Some(authority_sudo_address.to_raw()), + chain_id: chain_id.clone(), + ibc_sudo_address: Some(ibc_sudo_address.to_raw()), + ibc_relayer_addresses: ibc_relayer_addresses.iter().map(Address::to_raw).collect(), + native_asset_base_denomination: native_asset_base_denomination.to_string(), + ibc_parameters: Some(ibc_parameters.to_raw()), + allowed_fee_assets: allowed_fee_assets.iter().map(ToString::to_string).collect(), + fees: Some(fees.to_raw()), + } + } +} + +impl TryFrom for GenesisAppState { + type Error = ::Error; + + fn try_from(value: raw::GenesisAppState) -> Result { + Self::try_from_raw(value) + } +} + +impl From for raw::GenesisAppState { + fn from(value: GenesisAppState) -> Self { + value.into_raw() + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct GenesisAppStateError(GenesisAppStateErrorKind); + +impl GenesisAppStateError { + fn accounts(source: AccountError) -> Self { + Self(GenesisAppStateErrorKind::Accounts { + source, + }) + } + + fn address_prefixes(source: AddressPrefixesError) -> Self { + Self(GenesisAppStateErrorKind::AddressPrefixes { + source, + }) + } + + fn address_does_not_match_base(source: Box) -> Self { + Self(GenesisAppStateErrorKind::AddressDoesNotMatchBase { + source, + }) + } + + fn allowed_fee_assets(source: ParseDenomError) -> Self { + Self(GenesisAppStateErrorKind::AllowedFeeAssets { + source, + }) + } + + fn authority_sudo_address(source: AddressError) -> Self { + Self(GenesisAppStateErrorKind::AuthoritySudoAddress { + source, + }) + } + + fn fees(source: FeesError) -> Self { + Self(GenesisAppStateErrorKind::Fees { + source, + }) + } + + fn field_not_set(name: &'static str) -> Self { + Self(GenesisAppStateErrorKind::FieldNotSet { + name, + }) + } + + fn ibc_relayer_addresses(source: AddressError) -> Self { + Self(GenesisAppStateErrorKind::IbcRelayerAddresses { + source, + }) + } + + fn ibc_sudo_address(source: AddressError) -> Self { + Self(GenesisAppStateErrorKind::IbcSudoAddress { + source, + }) + } + + fn native_asset_base_denomination(source: ParseTracePrefixedError) -> Self { + Self(GenesisAppStateErrorKind::NativeAssetBaseDenomination { + source, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", GenesisAppState::full_name())] +enum GenesisAppStateErrorKind { + #[error("`accounts` field was invalid")] + Accounts { source: AccountError }, + #[error("`address_prefixes` field was invalid")] + AddressPrefixes { source: AddressPrefixesError }, + #[error("one of the provided addresses did not match the provided base prefix")] + AddressDoesNotMatchBase { + source: Box, + }, + #[error("`allowed_fee_assets` field was invalid")] + AllowedFeeAssets { source: ParseDenomError }, + #[error("`authority_sudo_address` field was invalid")] + AuthoritySudoAddress { source: AddressError }, + #[error("`fees` field was invalid")] + Fees { source: FeesError }, + #[error("`ibc_sudo_address` field was invalid")] + IbcSudoAddress { source: AddressError }, + #[error("`ibc_relayer_addresses` field was invalid")] + IbcRelayerAddresses { source: AddressError }, + #[error("field was not set: `{name}`")] + FieldNotSet { name: &'static str }, + #[error("`native_asset_base_denomination` field was invalid")] + NativeAssetBaseDenomination { source: ParseTracePrefixedError }, +} + +#[derive(Debug, thiserror::Error)] +#[error("address `{address}` at `{field}` does not have `{base_prefix}`")] +struct AddressDoesNotMatchBase { + base_prefix: String, + address: Address, + field: String, +} + +#[derive(Clone, Copy, Debug)] +pub struct Account { + pub address: Address, + pub balance: u128, +} + +impl Protobuf for Account { + type Error = AccountError; + type Raw = raw::Account; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + address, + balance, + } = raw; + let address = address + .as_ref() + .ok_or_else(|| AccountError::field_not_set("address")) + .and_then(|addr| Address::try_from_raw(addr).map_err(Self::Error::address))?; + let balance = balance + .ok_or_else(|| AccountError::field_not_set("balance")) + .map(Into::into)?; + Ok(Self { + address, + balance, + }) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + address, + balance, + } = self; + Self::Raw { + address: Some(address.to_raw()), + balance: Some((*balance).into()), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct AccountError(AccountErrorKind); + +impl AccountError { + fn address(source: AddressError) -> Self { + Self(AccountErrorKind::Address { + source, + }) + } + + fn field_not_set(name: &'static str) -> Self { + Self(AccountErrorKind::FieldNotSet { + name, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", Account::full_name())] +enum AccountErrorKind { + #[error("`address` field was invalid")] + Address { source: AddressError }, + #[error("field was not set: `{name}`")] + FieldNotSet { name: &'static str }, +} + +#[derive(Clone, Debug)] +pub struct AddressPrefixes { + pub base: String, +} + +impl Protobuf for AddressPrefixes { + type Error = AddressPrefixesError; + type Raw = raw::AddressPrefixes; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + base, + } = raw; + try_construct_dummy_address_from_prefix(base).map_err(Self::Error::base)?; + Ok(Self { + base: base.to_string(), + }) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + base, + } = self; + Self::Raw { + base: base.clone(), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct AddressPrefixesError(AddressPrefixesErrorKind); + +impl AddressPrefixesError { + fn base(source: AddressError) -> Self { + Self(AddressPrefixesErrorKind::Base { + source, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", AddressPrefixes::full_name())] +enum AddressPrefixesErrorKind { + #[error("`base` cannot be used to construct Astria addresses")] + Base { source: AddressError }, +} + +impl Protobuf for IBCParameters { + type Error = Infallible; + type Raw = raw::IbcParameters; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + Ok((*raw).into()) + } + + fn to_raw(&self) -> Self::Raw { + self.clone().into() + } +} + +impl From for raw::IbcParameters { + fn from(value: IBCParameters) -> Self { + let IBCParameters { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } = value; + Self { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } + } +} + +impl From for IBCParameters { + fn from(value: raw::IbcParameters) -> Self { + let raw::IbcParameters { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } = value; + Self { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } + } +} + +#[derive(Clone, Debug)] +pub struct Fees { + pub transfer_base_fee: u128, + pub sequence_base_fee: u128, + pub sequence_byte_cost_multiplier: u128, + pub init_bridge_account_base_fee: u128, + pub bridge_lock_byte_cost_multiplier: u128, + pub bridge_sudo_change_fee: u128, + pub ics20_withdrawal_base_fee: u128, +} + +impl Protobuf for Fees { + type Error = FeesError; + type Raw = raw::Fees; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + transfer_base_fee, + sequence_base_fee, + sequence_byte_cost_multiplier, + init_bridge_account_base_fee, + bridge_lock_byte_cost_multiplier, + bridge_sudo_change_fee, + ics20_withdrawal_base_fee, + } = raw; + let transfer_base_fee = transfer_base_fee + .ok_or_else(|| Self::Error::field_not_set("transfer_base_fee"))? + .into(); + let sequence_base_fee = sequence_base_fee + .ok_or_else(|| Self::Error::field_not_set("sequence_base_fee"))? + .into(); + let sequence_byte_cost_multiplier = sequence_byte_cost_multiplier + .ok_or_else(|| Self::Error::field_not_set("sequence_byte_cost_multiplier"))? + .into(); + let init_bridge_account_base_fee = init_bridge_account_base_fee + .ok_or_else(|| Self::Error::field_not_set("init_bridge_account_base_fee"))? + .into(); + let bridge_lock_byte_cost_multiplier = bridge_lock_byte_cost_multiplier + .ok_or_else(|| Self::Error::field_not_set("bridge_lock_byte_cost_multiplier"))? + .into(); + let bridge_sudo_change_fee = bridge_sudo_change_fee + .ok_or_else(|| Self::Error::field_not_set("bridge_sudo_change_fee"))? + .into(); + let ics20_withdrawal_base_fee = ics20_withdrawal_base_fee + .ok_or_else(|| Self::Error::field_not_set("ics20_withdrawal_base_fee"))? + .into(); + Ok(Self { + transfer_base_fee, + sequence_base_fee, + sequence_byte_cost_multiplier, + init_bridge_account_base_fee, + bridge_lock_byte_cost_multiplier, + bridge_sudo_change_fee, + ics20_withdrawal_base_fee, + }) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + transfer_base_fee, + sequence_base_fee, + sequence_byte_cost_multiplier, + init_bridge_account_base_fee, + bridge_lock_byte_cost_multiplier, + bridge_sudo_change_fee, + ics20_withdrawal_base_fee, + } = self; + Self::Raw { + transfer_base_fee: Some(transfer_base_fee.into()), + sequence_base_fee: Some(sequence_base_fee.into()), + sequence_byte_cost_multiplier: Some(sequence_byte_cost_multiplier.into()), + init_bridge_account_base_fee: Some(init_bridge_account_base_fee.into()), + bridge_lock_byte_cost_multiplier: Some(bridge_lock_byte_cost_multiplier.into()), + bridge_sudo_change_fee: Some(bridge_sudo_change_fee.into()), + ics20_withdrawal_base_fee: Some(ics20_withdrawal_base_fee.into()), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct FeesError(FeesErrorKind); + +impl FeesError { + fn field_not_set(name: &'static str) -> Self { + Self(FeesErrorKind::FieldNotSet { + name, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", Fees::full_name())] +enum FeesErrorKind { + #[error("field was not set: `{name}`")] + FieldNotSet { name: &'static str }, +} + +/// Constructs a dummy address from a given `prefix`, otherwise fail. +fn try_construct_dummy_address_from_prefix(prefix: &str) -> Result<(), AddressError> { + Address::builder() + .array([0u8; ADDRESS_LEN]) + .prefix(prefix) + .try_build() + .map(|_| ()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::primitive::v1::Address; + + const ASTRIA_ADDRESS_PREFIX: &str = "astria"; + + fn alice() -> Address { + Address::builder() + .prefix(ASTRIA_ADDRESS_PREFIX) + .slice(hex::decode("1c0c490f1b5528d8173c5de46d131160e4b2c0c3").unwrap()) + .try_build() + .unwrap() + } + + fn bob() -> Address { + Address::builder() + .prefix(ASTRIA_ADDRESS_PREFIX) + .slice(hex::decode("34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a").unwrap()) + .try_build() + .unwrap() + } + + fn charlie() -> Address { + Address::builder() + .prefix(ASTRIA_ADDRESS_PREFIX) + .slice(hex::decode("60709e2d391864b732b4f0f51e387abb76743871").unwrap()) + .try_build() + .unwrap() + } + + fn mallory() -> Address { + Address::builder() + .prefix("other") + .slice(hex::decode("60709e2d391864b732b4f0f51e387abb76743871").unwrap()) + .try_build() + .unwrap() + } + + fn proto_genesis_state() -> raw::GenesisAppState { + raw::GenesisAppState { + accounts: vec![ + raw::Account { + address: Some(alice().to_raw()), + balance: Some(1_000_000_000_000_000_000.into()), + }, + raw::Account { + address: Some(bob().to_raw()), + balance: Some(1_000_000_000_000_000_000.into()), + }, + raw::Account { + address: Some(charlie().to_raw()), + balance: Some(1_000_000_000_000_000_000.into()), + }, + ], + address_prefixes: Some(raw::AddressPrefixes { + base: "astria".into(), + }), + authority_sudo_address: Some(alice().to_raw()), + chain_id: "astria-1".to_string(), + ibc_sudo_address: Some(alice().to_raw()), + ibc_relayer_addresses: vec![alice().to_raw(), bob().to_raw()], + native_asset_base_denomination: "nria".to_string(), + ibc_parameters: Some(raw::IbcParameters { + ibc_enabled: true, + inbound_ics20_transfers_enabled: true, + outbound_ics20_transfers_enabled: true, + }), + allowed_fee_assets: vec!["nria".into()], + fees: Some(raw::Fees { + transfer_base_fee: Some(12.into()), + sequence_base_fee: Some(32.into()), + sequence_byte_cost_multiplier: Some(1.into()), + init_bridge_account_base_fee: Some(48.into()), + bridge_lock_byte_cost_multiplier: Some(1.into()), + bridge_sudo_change_fee: Some(24.into()), + ics20_withdrawal_base_fee: Some(24.into()), + }), + } + } + + fn genesis_state() -> GenesisAppState { + proto_genesis_state().try_into().unwrap() + } + + #[test] + fn mismatched_addresses_are_caught() { + #[track_caller] + fn assert_bad_prefix(unchecked: raw::GenesisAppState, bad_field: &'static str) { + match GenesisAppState::try_from(unchecked) + .expect_err( + "converting to genesis state should have produced an error, but a valid state \ + was returned", + ) + .0 + { + GenesisAppStateErrorKind::AddressDoesNotMatchBase { + source, + } => { + let AddressDoesNotMatchBase { + base_prefix, + address, + field, + } = *source; + assert_eq!(base_prefix, ASTRIA_ADDRESS_PREFIX); + assert_eq!(address, mallory()); + assert_eq!(field, bad_field); + } + other => panic!( + "expected: `GenesisAppStateErrorKind::AddressDoesNotMatchBase\ngot: {other:?}`" + ), + }; + } + assert_bad_prefix( + raw::GenesisAppState { + authority_sudo_address: Some(mallory().to_raw()), + ..proto_genesis_state() + }, + ".authority_sudo_address", + ); + assert_bad_prefix( + raw::GenesisAppState { + ibc_sudo_address: Some(mallory().to_raw()), + ..proto_genesis_state() + }, + ".ibc_sudo_address", + ); + assert_bad_prefix( + raw::GenesisAppState { + ibc_relayer_addresses: vec![alice().to_raw(), mallory().to_raw()], + ..proto_genesis_state() + }, + ".ibc_relayer_addresses[1]", + ); + assert_bad_prefix( + raw::GenesisAppState { + accounts: vec![ + raw::Account { + address: Some(alice().to_raw()), + balance: Some(10.into()), + }, + raw::Account { + address: Some(mallory().to_raw()), + balance: Some(10.into()), + }, + ], + ..proto_genesis_state() + }, + ".accounts[1].address", + ); + } + + #[cfg(feature = "serde")] + #[test] + fn genesis_state_is_unchanged() { + insta::assert_json_snapshot!(genesis_state()); + } +} diff --git a/crates/astria-core/src/protocol/mod.rs b/crates/astria-core/src/protocol/mod.rs index 2d30299b22..e285ce4d67 100644 --- a/crates/astria-core/src/protocol/mod.rs +++ b/crates/astria-core/src/protocol/mod.rs @@ -8,6 +8,7 @@ pub mod abci; pub mod account; pub mod asset; pub mod bridge; +pub mod genesis; pub mod memos; pub mod transaction; diff --git a/crates/astria-core/src/sequencer.rs b/crates/astria-core/src/sequencer.rs deleted file mode 100644 index 4138e96b6e..0000000000 --- a/crates/astria-core/src/sequencer.rs +++ /dev/null @@ -1,390 +0,0 @@ -//! Sequencer specific types that are needed outside of it. -pub use penumbra_ibc::params::IBCParameters; - -use crate::primitive::v1::{ - asset::{ - self, - TracePrefixed, - }, - Address, -}; - -/// The genesis state of Astria's Sequencer. -/// -/// Verified to only contain valid fields (right now, addresses that have the same base prefix -/// as set in `GenesisState::address_prefixes::base`). -/// -/// *Note on the implementation:* access to all fields is through getters to uphold invariants, -/// but most returned values themselves have publicly exposed fields. This is to make it easier -/// to construct an [`UncheckedGenesisState`]. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr( - feature = "serde", - serde(try_from = "UncheckedGenesisState", into = "UncheckedGenesisState") -)] -pub struct GenesisState { - address_prefixes: AddressPrefixes, - accounts: Vec, - authority_sudo_address: Address, - ibc_sudo_address: Address, - ibc_relayer_addresses: Vec
, - native_asset_base_denomination: TracePrefixed, - ibc_params: IBCParameters, - allowed_fee_assets: Vec, - fees: Fees, -} - -impl GenesisState { - #[must_use] - pub fn address_prefixes(&self) -> &AddressPrefixes { - &self.address_prefixes - } - - #[must_use] - pub fn accounts(&self) -> &[Account] { - &self.accounts - } - - #[must_use] - pub fn authority_sudo_address(&self) -> &Address { - &self.authority_sudo_address - } - - #[must_use] - pub fn ibc_sudo_address(&self) -> &Address { - &self.ibc_sudo_address - } - - #[must_use] - pub fn ibc_relayer_addresses(&self) -> &[Address] { - &self.ibc_relayer_addresses - } - - #[must_use] - pub fn native_asset_base_denomination(&self) -> &TracePrefixed { - &self.native_asset_base_denomination - } - - #[must_use] - pub fn ibc_params(&self) -> &IBCParameters { - &self.ibc_params - } - - #[must_use] - pub fn allowed_fee_assets(&self) -> &[asset::Denom] { - &self.allowed_fee_assets - } - - #[must_use] - pub fn fees(&self) -> &Fees { - &self.fees - } -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct VerifyGenesisError(Box); - -#[derive(Debug, thiserror::Error)] -enum VerifyGenesisErrorKind { - #[error("address `{address}` at `{field}` does not have `{base_prefix}`")] - AddressDoesNotMatchBase { - base_prefix: String, - address: Address, - field: String, - }, -} - -impl From for VerifyGenesisError { - fn from(value: VerifyGenesisErrorKind) -> Self { - Self(Box::new(value)) - } -} - -impl TryFrom for GenesisState { - type Error = VerifyGenesisError; - - fn try_from(value: UncheckedGenesisState) -> Result { - value.ensure_all_addresses_have_base_prefix()?; - - let UncheckedGenesisState { - address_prefixes, - accounts, - authority_sudo_address, - ibc_sudo_address, - ibc_relayer_addresses, - native_asset_base_denomination, - ibc_params, - allowed_fee_assets, - fees, - } = value; - - Ok(Self { - address_prefixes, - accounts, - authority_sudo_address, - ibc_sudo_address, - ibc_relayer_addresses, - native_asset_base_denomination, - ibc_params, - allowed_fee_assets, - fees, - }) - } -} - -/// The unchecked genesis state for the application. -#[derive(Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct UncheckedGenesisState { - pub address_prefixes: AddressPrefixes, - pub accounts: Vec, - pub authority_sudo_address: Address, - pub ibc_sudo_address: Address, - pub ibc_relayer_addresses: Vec
, - pub native_asset_base_denomination: TracePrefixed, - pub ibc_params: IBCParameters, - pub allowed_fee_assets: Vec, - pub fees: Fees, -} - -impl UncheckedGenesisState { - fn ensure_address_has_base_prefix( - &self, - address: &Address, - field: &str, - ) -> Result<(), VerifyGenesisError> { - if self.address_prefixes.base != address.prefix() { - return Err(VerifyGenesisErrorKind::AddressDoesNotMatchBase { - base_prefix: self.address_prefixes.base.clone(), - address: *address, - field: field.to_string(), - } - .into()); - } - Ok(()) - } - - fn ensure_all_addresses_have_base_prefix(&self) -> Result<(), VerifyGenesisError> { - for (i, account) in self.accounts.iter().enumerate() { - self.ensure_address_has_base_prefix( - &account.address, - &format!(".accounts[{i}].address"), - )?; - } - self.ensure_address_has_base_prefix( - &self.authority_sudo_address, - ".authority_sudo_address", - )?; - self.ensure_address_has_base_prefix(&self.ibc_sudo_address, ".ibc_sudo_address")?; - for (i, address) in self.ibc_relayer_addresses.iter().enumerate() { - self.ensure_address_has_base_prefix(address, &format!(".ibc_relayer_addresses[{i}]"))?; - } - Ok(()) - } -} - -impl From for UncheckedGenesisState { - fn from(value: GenesisState) -> Self { - let GenesisState { - address_prefixes, - accounts, - authority_sudo_address, - ibc_sudo_address, - ibc_relayer_addresses, - native_asset_base_denomination, - ibc_params, - allowed_fee_assets, - fees, - } = value; - Self { - address_prefixes, - accounts, - authority_sudo_address, - ibc_sudo_address, - ibc_relayer_addresses, - native_asset_base_denomination, - ibc_params, - allowed_fee_assets, - fees, - } - } -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Fees { - pub transfer_base_fee: u128, - pub sequence_base_fee: u128, - pub sequence_byte_cost_multiplier: u128, - pub init_bridge_account_base_fee: u128, - pub bridge_lock_byte_cost_multiplier: u128, - pub bridge_sudo_change_fee: u128, - pub ics20_withdrawal_base_fee: u128, -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Account { - pub address: Address, - pub balance: u128, -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct AddressPrefixes { - pub base: String, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::primitive::v1::Address; - - const ASTRIA_ADDRESS_PREFIX: &str = "astria"; - - fn alice() -> Address { - Address::builder() - .prefix(ASTRIA_ADDRESS_PREFIX) - .slice(hex::decode("1c0c490f1b5528d8173c5de46d131160e4b2c0c3").unwrap()) - .try_build() - .unwrap() - } - - fn bob() -> Address { - Address::builder() - .prefix(ASTRIA_ADDRESS_PREFIX) - .slice(hex::decode("34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a").unwrap()) - .try_build() - .unwrap() - } - - fn charlie() -> Address { - Address::builder() - .prefix(ASTRIA_ADDRESS_PREFIX) - .slice(hex::decode("60709e2d391864b732b4f0f51e387abb76743871").unwrap()) - .try_build() - .unwrap() - } - - fn mallory() -> Address { - Address::builder() - .prefix("other") - .slice(hex::decode("60709e2d391864b732b4f0f51e387abb76743871").unwrap()) - .try_build() - .unwrap() - } - - fn unchecked_genesis_state() -> UncheckedGenesisState { - UncheckedGenesisState { - accounts: vec![ - Account { - address: alice(), - balance: 1_000_000_000_000_000_000, - }, - Account { - address: bob(), - balance: 1_000_000_000_000_000_000, - }, - Account { - address: charlie(), - balance: 1_000_000_000_000_000_000, - }, - ], - address_prefixes: AddressPrefixes { - base: "astria".into(), - }, - authority_sudo_address: alice(), - ibc_sudo_address: alice(), - ibc_relayer_addresses: vec![alice(), bob()], - native_asset_base_denomination: "nria".parse().unwrap(), - ibc_params: IBCParameters { - ibc_enabled: true, - inbound_ics20_transfers_enabled: true, - outbound_ics20_transfers_enabled: true, - }, - allowed_fee_assets: vec!["nria".parse().unwrap()], - fees: Fees { - transfer_base_fee: 12, - sequence_base_fee: 32, - sequence_byte_cost_multiplier: 1, - init_bridge_account_base_fee: 48, - bridge_lock_byte_cost_multiplier: 1, - bridge_sudo_change_fee: 24, - ics20_withdrawal_base_fee: 24, - }, - } - } - - fn genesis_state() -> GenesisState { - unchecked_genesis_state().try_into().unwrap() - } - - #[test] - fn mismatched_addresses_are_caught() { - #[track_caller] - fn assert_bad_prefix(unchecked: UncheckedGenesisState, bad_field: &'static str) { - match *GenesisState::try_from(unchecked) - .expect_err( - "converting to genesis state should have produced an error, but a valid state \ - was returned", - ) - .0 - { - VerifyGenesisErrorKind::AddressDoesNotMatchBase { - base_prefix, - address, - field, - } => { - assert_eq!(base_prefix, ASTRIA_ADDRESS_PREFIX); - assert_eq!(address, mallory()); - assert_eq!(field, bad_field); - } - }; - } - assert_bad_prefix( - UncheckedGenesisState { - authority_sudo_address: mallory(), - ..unchecked_genesis_state() - }, - ".authority_sudo_address", - ); - assert_bad_prefix( - UncheckedGenesisState { - ibc_sudo_address: mallory(), - ..unchecked_genesis_state() - }, - ".ibc_sudo_address", - ); - assert_bad_prefix( - UncheckedGenesisState { - ibc_relayer_addresses: vec![alice(), mallory()], - ..unchecked_genesis_state() - }, - ".ibc_relayer_addresses[1]", - ); - assert_bad_prefix( - UncheckedGenesisState { - accounts: vec![ - Account { - address: alice(), - balance: 10, - }, - Account { - address: mallory(), - balance: 10, - }, - ], - ..unchecked_genesis_state() - }, - ".accounts[1].address", - ); - } - - #[cfg(feature = "serde")] - #[test] - fn genesis_state_is_unchanged() { - insta::assert_json_snapshot!(genesis_state()); - } -} diff --git a/crates/astria-sequencer-utils/src/genesis_example.rs b/crates/astria-sequencer-utils/src/genesis_example.rs index 05d66baa07..b28083fb82 100644 --- a/crates/astria-sequencer-utils/src/genesis_example.rs +++ b/crates/astria-sequencer-utils/src/genesis_example.rs @@ -5,15 +5,15 @@ use std::{ }; use astria_core::{ + generated::protocol::genesis::v1alpha1::IbcParameters, primitive::v1::Address, - sequencer::{ + protocol::genesis::v1alpha1::{ Account, AddressPrefixes, Fees, - GenesisState, - IBCParameters, - UncheckedGenesisState, + GenesisAppState, }, + Protobuf, }; use astria_eyre::eyre::{ Result, @@ -46,47 +46,61 @@ fn charlie() -> Address { .unwrap() } -fn genesis_state() -> GenesisState { - UncheckedGenesisState { - accounts: vec![ - Account { - address: alice(), - balance: 1_000_000_000_000_000_000, - }, - Account { - address: bob(), - balance: 1_000_000_000_000_000_000, - }, - Account { - address: charlie(), - balance: 1_000_000_000_000_000_000, - }, - ], - address_prefixes: AddressPrefixes { - base: "astria".into(), +fn accounts() -> Vec { + vec![ + Account { + address: alice(), + balance: 1_000_000_000_000_000_000, }, - authority_sudo_address: alice(), - ibc_sudo_address: alice(), - ibc_relayer_addresses: vec![alice(), bob()], + Account { + address: bob(), + balance: 1_000_000_000_000_000_000, + }, + Account { + address: charlie(), + balance: 1_000_000_000_000_000_000, + }, + ] +} + +fn address_prefixes() -> AddressPrefixes { + AddressPrefixes { + base: "astria".into(), + } +} + +fn proto_genesis_state() -> astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + accounts: accounts().into_iter().map(Protobuf::into_raw).collect(), + address_prefixes: Some(address_prefixes().into_raw()), + authority_sudo_address: Some(alice().to_raw()), + chain_id: "test-1".into(), + ibc_sudo_address: Some(alice().to_raw()), + ibc_relayer_addresses: vec![alice().to_raw(), bob().to_raw()], native_asset_base_denomination: "nria".parse().unwrap(), - ibc_params: IBCParameters { + ibc_parameters: Some(IbcParameters { ibc_enabled: true, inbound_ics20_transfers_enabled: true, outbound_ics20_transfers_enabled: true, - }, + }), allowed_fee_assets: vec!["nria".parse().unwrap()], - fees: Fees { - transfer_base_fee: 12, - sequence_base_fee: 32, - sequence_byte_cost_multiplier: 1, - init_bridge_account_base_fee: 48, - bridge_lock_byte_cost_multiplier: 1, - bridge_sudo_change_fee: 24, - ics20_withdrawal_base_fee: 24, - }, + fees: Some( + Fees { + transfer_base_fee: 12, + sequence_base_fee: 32, + sequence_byte_cost_multiplier: 1, + init_bridge_account_base_fee: 48, + bridge_lock_byte_cost_multiplier: 1, + bridge_sudo_change_fee: 24, + ics20_withdrawal_base_fee: 24, + } + .into_raw(), + ), } - .try_into() - .unwrap() +} + +fn genesis_state() -> GenesisAppState { + GenesisAppState::try_from_raw(proto_genesis_state()).unwrap() } #[derive(clap::Args, Debug)] diff --git a/crates/astria-sequencer/src/accounts/component.rs b/crates/astria-sequencer/src/accounts/component.rs index 615cb24bbd..e08d7b357a 100644 --- a/crates/astria-sequencer/src/accounts/component.rs +++ b/crates/astria-sequencer/src/accounts/component.rs @@ -4,6 +4,7 @@ use anyhow::{ Context, Result, }; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use tendermint::abci::request::{ BeginBlock, EndBlock, @@ -21,7 +22,7 @@ pub(crate) struct AccountsComponent; #[async_trait::async_trait] impl Component for AccountsComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "AccountsComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> diff --git a/crates/astria-sequencer/src/app/benchmarks.rs b/crates/astria-sequencer/src/app/benchmarks.rs index db5448f668..b667720662 100644 --- a/crates/astria-sequencer/src/app/benchmarks.rs +++ b/crates/astria-sequencer/src/app/benchmarks.rs @@ -5,14 +5,14 @@ use std::time::Duration; -use astria_core::sequencer::{ - Account, - AddressPrefixes, - GenesisState, - UncheckedGenesisState, +use astria_core::{ + protocol::genesis::v1alpha1::{ + Account, + GenesisAppState, + }, + Protobuf, }; use cnidarium::Storage; -use penumbra_ibc::params::IBCParameters; use crate::{ app::{ @@ -25,11 +25,7 @@ use crate::{ SIGNER_COUNT, }, proposal::block_size_constraints::BlockSizeConstraints, - test_utils::{ - astria_address, - nria, - ASTRIA_PREFIX, - }, + test_utils::astria_address, }; /// The max time for any benchmark. @@ -58,23 +54,18 @@ impl Fixture { .pow(19) .saturating_add(u128::try_from(index).unwrap()), }) + .map(Protobuf::into_raw) .collect::>(); - let address_prefixes = AddressPrefixes { - base: ASTRIA_PREFIX.into(), - }; - let first_address = accounts.first().unwrap().address; - let unchecked_genesis_state = UncheckedGenesisState { - accounts, - address_prefixes, - authority_sudo_address: first_address, - ibc_sudo_address: first_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![nria().into()], - fees: test_utils::default_fees(), - }; - let genesis_state = GenesisState::try_from(unchecked_genesis_state).unwrap(); + let first_address = accounts.first().cloned().unwrap().address; + let genesis_state = GenesisAppState::try_from_raw( + astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + accounts, + authority_sudo_address: first_address.clone(), + ibc_sudo_address: first_address.clone(), + ..crate::app::test_utils::proto_genesis_state() + }, + ) + .unwrap(); let (app, storage) = test_utils::initialize_app_with_storage(Some(genesis_state), vec![]).await; diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 5300077ee8..e27feb6ec0 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -27,6 +27,7 @@ use astria_core::{ generated::protocol::transactions::v1alpha1 as raw, protocol::{ abci::AbciErrorCode, + genesis::v1alpha1::GenesisAppState, transaction::v1alpha1::{ action::ValidatorUpdate, Action, @@ -209,7 +210,7 @@ impl App { pub(crate) async fn init_chain( &mut self, storage: Storage, - genesis_state: astria_core::sequencer::GenesisState, + genesis_state: GenesisAppState, genesis_validators: Vec, chain_id: String, ) -> anyhow::Result { diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index fcc4223fa1..05ffb9b7e0 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -3,26 +3,26 @@ use std::sync::Arc; use astria_core::{ crypto::SigningKey, primitive::v1::RollupId, - protocol::transaction::v1alpha1::{ - action::{ - SequenceAction, - ValidatorUpdate, + protocol::{ + genesis::v1alpha1::{ + Account, + AddressPrefixes, + GenesisAppState, + }, + transaction::v1alpha1::{ + action::{ + SequenceAction, + ValidatorUpdate, + }, + SignedTransaction, + TransactionParams, + UnsignedTransaction, }, - SignedTransaction, - TransactionParams, - UnsignedTransaction, - }, - sequencer::{ - Account, - AddressPrefixes, - Fees, - GenesisState, - UncheckedGenesisState, }, + Protobuf, }; use bytes::Bytes; use cnidarium::Storage; -use penumbra_ibc::params::IBCParameters; use crate::{ app::App, @@ -58,7 +58,6 @@ pub(crate) fn get_bridge_signing_key() -> SigningKey { SigningKey::from(bridge_secret_bytes) } -#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) fn default_genesis_accounts() -> Vec { vec![ Account { @@ -77,8 +76,8 @@ pub(crate) fn default_genesis_accounts() -> Vec { } #[cfg_attr(feature = "benchmark", allow(dead_code))] -pub(crate) fn default_fees() -> Fees { - Fees { +pub(crate) fn default_fees() -> astria_core::protocol::genesis::v1alpha1::Fees { + astria_core::protocol::genesis::v1alpha1::Fees { transfer_base_fee: 12, sequence_base_fee: 32, sequence_byte_cost_multiplier: 1, @@ -89,28 +88,45 @@ pub(crate) fn default_fees() -> Fees { } } -pub(crate) fn unchecked_genesis_state() -> UncheckedGenesisState { - UncheckedGenesisState { - accounts: default_genesis_accounts(), - address_prefixes: AddressPrefixes { - base: crate::test_utils::ASTRIA_PREFIX.into(), - }, - authority_sudo_address: astria_address_from_hex_string(JUDY_ADDRESS), - ibc_sudo_address: astria_address_from_hex_string(TED_ADDRESS), +pub(crate) fn address_prefixes() -> AddressPrefixes { + AddressPrefixes { + base: crate::test_utils::ASTRIA_PREFIX.into(), + } +} + +pub(crate) fn proto_genesis_state() +-> astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + use astria_core::generated::protocol::genesis::v1alpha1::{ + GenesisAppState, + IbcParameters, + }; + GenesisAppState { + address_prefixes: Some(address_prefixes().to_raw()), + accounts: default_genesis_accounts() + .into_iter() + .map(Protobuf::into_raw) + .collect(), + authority_sudo_address: Some(astria_address_from_hex_string(JUDY_ADDRESS).to_raw()), + chain_id: "test-1".to_string(), + ibc_sudo_address: Some(astria_address_from_hex_string(TED_ADDRESS).to_raw()), ibc_relayer_addresses: vec![], - native_asset_base_denomination: crate::test_utils::nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![crate::test_utils::nria().into()], - fees: default_fees(), + native_asset_base_denomination: crate::test_utils::nria().to_string(), + ibc_parameters: Some(IbcParameters { + ibc_enabled: true, + inbound_ics20_transfers_enabled: true, + outbound_ics20_transfers_enabled: true, + }), + allowed_fee_assets: vec![crate::test_utils::nria().to_string()], + fees: Some(default_fees().to_raw()), } } -pub(crate) fn genesis_state() -> GenesisState { - unchecked_genesis_state().try_into().unwrap() +pub(crate) fn genesis_state() -> GenesisAppState { + proto_genesis_state().try_into().unwrap() } pub(crate) async fn initialize_app_with_storage( - genesis_state: Option, + genesis_state: Option, genesis_validators: Vec, ) -> (App, Storage) { let storage = cnidarium::TempStorage::new() @@ -138,7 +154,7 @@ pub(crate) async fn initialize_app_with_storage( #[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) async fn initialize_app( - genesis_state: Option, + genesis_state: Option, genesis_validators: Vec, ) -> App { let (app, _storage) = initialize_app_with_storage(genesis_state, genesis_validators).await; diff --git a/crates/astria-sequencer/src/app/tests_app.rs b/crates/astria-sequencer/src/app/tests_app.rs index 58ec904b17..aa6d9551ad 100644 --- a/crates/astria-sequencer/src/app/tests_app.rs +++ b/crates/astria-sequencer/src/app/tests_app.rs @@ -5,16 +5,18 @@ use astria_core::{ asset::TracePrefixed, RollupId, }, - protocol::transaction::v1alpha1::{ - action::{ - BridgeLockAction, - SequenceAction, - TransferAction, + protocol::{ + genesis::v1alpha1::Account, + transaction::v1alpha1::{ + action::{ + BridgeLockAction, + SequenceAction, + TransferAction, + }, + TransactionParams, + UnsignedTransaction, }, - TransactionParams, - UnsignedTransaction, }, - sequencer::Account, sequencerblock::v1alpha1::block::Deposit, }; use cnidarium::StateDelta; diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index f6065ecf0e..6909cc833d 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -16,29 +16,27 @@ use std::{ use astria_core::{ primitive::v1::RollupId, - protocol::transaction::v1alpha1::{ - action::{ - BridgeLockAction, - BridgeSudoChangeAction, - BridgeUnlockAction, - IbcRelayerChangeAction, - SequenceAction, - TransferAction, - ValidatorUpdate, + protocol::{ + genesis::v1alpha1::Account, + transaction::v1alpha1::{ + action::{ + BridgeLockAction, + BridgeSudoChangeAction, + BridgeUnlockAction, + IbcRelayerChangeAction, + SequenceAction, + TransferAction, + ValidatorUpdate, + }, + Action, + TransactionParams, + UnsignedTransaction, }, - Action, - TransactionParams, - UnsignedTransaction, - }, - sequencer::{ - Account, - AddressPrefixes, - UncheckedGenesisState, }, sequencerblock::v1alpha1::block::Deposit, + Protobuf, }; use cnidarium::StateDelta; -use penumbra_ibc::params::IBCParameters; use prost::{ bytes::Bytes, Message as _, @@ -53,12 +51,12 @@ use tendermint::{ use crate::{ app::test_utils::{ - default_fees, default_genesis_accounts, get_alice_signing_key, get_bridge_signing_key, initialize_app, initialize_app_with_storage, + proto_genesis_state, BOB_ADDRESS, CAROL_ADDRESS, }, @@ -72,26 +70,6 @@ use crate::{ }, }; -/// XXX: This should be expressed in terms of `crate::app::test_utils::unchecked_genesis_state` to -/// be consistent everywhere. `get_alice_signing_key` already is, why not this? -fn unchecked_genesis_state() -> UncheckedGenesisState { - let alice = get_alice_signing_key(); - let alice_address = astria_address(&alice.address_bytes()); - UncheckedGenesisState { - accounts: vec![], - address_prefixes: AddressPrefixes { - base: ASTRIA_PREFIX.into(), - }, - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![nria().into()], - fees: default_fees(), - } -} - #[tokio::test] async fn app_genesis_snapshot() { let app = initialize_app(None, vec![]).await; @@ -192,15 +170,20 @@ async fn app_execute_transaction_with_every_action_snapshot() { let bridge_address = astria_address(&bridge.address_bytes()); let bob_address = astria_address_from_hex_string(BOB_ADDRESS); let carol_address = astria_address_from_hex_string(CAROL_ADDRESS); - let mut accounts = default_genesis_accounts(); - accounts.push(Account { - address: bridge_address, - balance: 1_000_000_000, - }); - let genesis_state = UncheckedGenesisState { + let accounts = { + let mut acc = default_genesis_accounts(); + acc.push(Account { + address: bridge_address, + balance: 1_000_000_000, + }); + acc.into_iter().map(Protobuf::into_raw).collect() + }; + let genesis_state = astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { accounts, - ..unchecked_genesis_state() + authority_sudo_address: Some(alice.try_address(ASTRIA_PREFIX).unwrap().to_raw()), + ibc_sudo_address: Some(alice.try_address(ASTRIA_PREFIX).unwrap().to_raw()), + ..proto_genesis_state() } .try_into() .unwrap(); diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index e88d0b5227..c433c48fcf 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -6,35 +6,39 @@ use astria_core::{ asset, RollupId, }, - protocol::transaction::v1alpha1::{ - action::{ - BridgeLockAction, - BridgeUnlockAction, - IbcRelayerChangeAction, - SequenceAction, - SudoAddressChangeAction, - TransferAction, - ValidatorUpdate, + protocol::{ + genesis::v1alpha1::GenesisAppState, + transaction::v1alpha1::{ + action::{ + BridgeLockAction, + BridgeUnlockAction, + IbcRelayerChangeAction, + SequenceAction, + SudoAddressChangeAction, + TransferAction, + ValidatorUpdate, + }, + Action, + TransactionParams, + UnsignedTransaction, }, - Action, - TransactionParams, - UnsignedTransaction, - }, - sequencer::{ - AddressPrefixes, - GenesisState, - UncheckedGenesisState, }, sequencerblock::v1alpha1::block::Deposit, + Protobuf as _, }; use bytes::Bytes; use cnidarium::StateDelta; -use penumbra_ibc::params::IBCParameters; +use super::test_utils::get_alice_signing_key; use crate::{ accounts::StateReadExt as _, app::{ - test_utils::*, + test_utils::{ + get_bridge_signing_key, + initialize_app, + BOB_ADDRESS, + CAROL_ADDRESS, + }, ActionHandler as _, }, assets::StateReadExt as _, @@ -49,6 +53,7 @@ use crate::{ astria_address, astria_address_from_hex_string, nria, + ASTRIA_PREFIX, }, transaction::{ InvalidChainId, @@ -56,27 +61,26 @@ use crate::{ }, }; -/// XXX: This should be expressed in terms of `crate::app::test_utils::unchecked_genesis_state` to -/// be consistent everywhere. `get_alice_sining_key` already is, why not this?? -fn unchecked_genesis_state() -> UncheckedGenesisState { - let alice = get_alice_signing_key(); - UncheckedGenesisState { - accounts: default_genesis_accounts(), - address_prefixes: AddressPrefixes { - base: crate::test_utils::ASTRIA_PREFIX.into(), - }, - authority_sudo_address: crate::test_utils::astria_address(&alice.address_bytes()), - ibc_sudo_address: crate::test_utils::astria_address(&alice.address_bytes()), - ibc_relayer_addresses: vec![], - native_asset_base_denomination: crate::test_utils::nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![crate::test_utils::nria().into()], - fees: default_fees(), +fn proto_genesis_state() -> astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + authority_sudo_address: Some( + get_alice_signing_key() + .try_address(ASTRIA_PREFIX) + .unwrap() + .to_raw(), + ), + ibc_sudo_address: Some( + get_alice_signing_key() + .try_address(ASTRIA_PREFIX) + .unwrap() + .to_raw(), + ), + ..crate::app::test_utils::proto_genesis_state() } } -fn genesis_state() -> GenesisState { - unchecked_genesis_state().try_into().unwrap() +fn genesis_state() -> GenesisAppState { + GenesisAppState::try_from_raw(proto_genesis_state()).unwrap() } fn test_asset() -> asset::Denom { @@ -367,9 +371,10 @@ async fn app_execute_transaction_ibc_relayer_change_deletion() { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let genesis_state = UncheckedGenesisState { - ibc_relayer_addresses: vec![alice_address], - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state.ibc_relayer_addresses.push(alice_address.to_raw()); + state } .try_into() .unwrap(); @@ -393,10 +398,13 @@ async fn app_execute_transaction_ibc_relayer_change_deletion() { async fn app_execute_transaction_ibc_relayer_change_invalid() { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let genesis_state = UncheckedGenesisState { - ibc_sudo_address: astria_address(&[0; 20]), - ibc_relayer_addresses: vec![alice_address], - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state + .ibc_sudo_address + .replace(astria_address(&[0; 20]).to_raw()); + state.ibc_relayer_addresses.push(alice_address.to_raw()); + state } .try_into() .unwrap(); @@ -447,10 +455,15 @@ async fn app_execute_transaction_sudo_address_change_error() { let alice_address = astria_address(&alice.address_bytes()); let authority_sudo_address = astria_address_from_hex_string(CAROL_ADDRESS); - let genesis_state = UncheckedGenesisState { - authority_sudo_address, - ibc_sudo_address: astria_address(&[0u8; 20]), - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state + .authority_sudo_address + .replace(authority_sudo_address.to_raw()); + state + .ibc_sudo_address + .replace(astria_address(&[0u8; 20]).to_raw()); + state } .try_into() .unwrap(); @@ -509,9 +522,10 @@ async fn app_execute_transaction_fee_asset_change_removal() { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let genesis_state = UncheckedGenesisState { - allowed_fee_assets: vec![nria().into(), test_asset()], - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state.allowed_fee_assets.push(test_asset().to_string()); + state } .try_into() .unwrap(); diff --git a/crates/astria-sequencer/src/bridge/component.rs b/crates/astria-sequencer/src/bridge/component.rs index cdc8f9b98b..88c105e9d0 100644 --- a/crates/astria-sequencer/src/bridge/component.rs +++ b/crates/astria-sequencer/src/bridge/component.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use anyhow::Result; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use tendermint::abci::request::{ BeginBlock, EndBlock, @@ -15,7 +16,7 @@ pub(crate) struct BridgeComponent; #[async_trait::async_trait] impl Component for BridgeComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "BridgeComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> { diff --git a/crates/astria-sequencer/src/ibc/component.rs b/crates/astria-sequencer/src/ibc/component.rs index 3a44b6865d..6f0ac0d8db 100644 --- a/crates/astria-sequencer/src/ibc/component.rs +++ b/crates/astria-sequencer/src/ibc/component.rs @@ -4,6 +4,7 @@ use anyhow::{ Context, Result, }; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use penumbra_ibc::{ component::Ibc, genesis::Content, @@ -27,14 +28,14 @@ pub(crate) struct IbcComponent; #[async_trait::async_trait] impl Component for IbcComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "IbcComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> { Ibc::init_chain( &mut state, Some(&Content { - ibc_params: app_state.ibc_params().clone(), + ibc_params: app_state.ibc_parameters().clone(), }), ) .await; diff --git a/crates/astria-sequencer/src/sequence/component.rs b/crates/astria-sequencer/src/sequence/component.rs index 84265321a5..742fb6ea1f 100644 --- a/crates/astria-sequencer/src/sequence/component.rs +++ b/crates/astria-sequencer/src/sequence/component.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use anyhow::Result; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use tendermint::abci::request::{ BeginBlock, EndBlock, @@ -15,7 +16,7 @@ pub(crate) struct SequenceComponent; #[async_trait::async_trait] impl Component for SequenceComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "SequenceComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> { diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index 0049267657..f5b6c9d56a 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -2,6 +2,7 @@ use anyhow::{ bail, Context, }; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use cnidarium::Storage; use tendermint::v0_38::abci::{ request, @@ -125,9 +126,8 @@ impl Consensus { bail!("database already initialized"); } - let genesis_state: astria_core::sequencer::GenesisState = - serde_json::from_slice(&init_chain.app_state_bytes) - .context("failed to parse app_state in genesis file")?; + let genesis_state: GenesisAppState = serde_json::from_slice(&init_chain.app_state_bytes) + .context("failed to parse genesis app state from init chain request")?; let app_hash = self .app .init_chain( @@ -216,11 +216,6 @@ mod test { TransactionParams, UnsignedTransaction, }, - sequencer::{ - Account, - AddressPrefixes, - UncheckedGenesisState, - }, }; use bytes::Bytes; use prost::Message as _; @@ -233,7 +228,6 @@ mod test { use super::*; use crate::{ - app::test_utils::default_fees, mempool::Mempool, metrics::Metrics, proposal::commitment::generate_rollup_datas_commitment, @@ -446,26 +440,22 @@ mod test { } async fn new_consensus_service(funded_key: Option) -> (Consensus, Mempool) { - let accounts = if funded_key.is_some() { - vec![Account { - address: crate::test_utils::astria_address(&funded_key.unwrap().address_bytes()), - balance: 10u128.pow(19), - }] + let accounts = if let Some(funded_key) = funded_key { + vec![ + astria_core::generated::protocol::genesis::v1alpha1::Account { + address: Some( + crate::test_utils::astria_address(&funded_key.address_bytes()).to_raw(), + ), + balance: Some(10u128.pow(19).into()), + }, + ] } else { vec![] }; - let genesis_state = UncheckedGenesisState { - accounts, - address_prefixes: AddressPrefixes { - base: crate::test_utils::ASTRIA_PREFIX.into(), - }, - authority_sudo_address: crate::test_utils::astria_address(&[0; 20]), - ibc_sudo_address: crate::test_utils::astria_address(&[0; 20]), - ibc_relayer_addresses: vec![], - native_asset_base_denomination: crate::test_utils::nria(), - ibc_params: penumbra_ibc::params::IBCParameters::default(), - allowed_fee_assets: vec!["nria".parse().unwrap()], - fees: default_fees(), + let genesis_state = { + let mut state = crate::app::test_utils::proto_genesis_state(); + state.accounts = accounts; + state } .try_into() .unwrap(); diff --git a/proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto b/proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto new file mode 100644 index 0000000000..89fa8fb3ae --- /dev/null +++ b/proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package astria.protocol.genesis.v1alpha1; + +import "astria/primitive/v1/types.proto"; + +message GenesisAppState { + string chain_id = 1; + AddressPrefixes address_prefixes = 2; + repeated Account accounts = 3; + astria.primitive.v1.Address authority_sudo_address = 4; + astria.primitive.v1.Address ibc_sudo_address = 5; + repeated astria.primitive.v1.Address ibc_relayer_addresses = 6; + string native_asset_base_denomination = 7; + IbcParameters ibc_parameters = 8; + repeated string allowed_fee_assets = 9; + Fees fees = 10; +} + +message Account { + astria.primitive.v1.Address address = 1; + astria.primitive.v1.Uint128 balance = 2; +} + +message AddressPrefixes { + string base = 1; +} + +// IBC configuration data. +message IbcParameters { + // Whether IBC (forming connections, processing IBC packets) is enabled. + bool ibc_enabled = 1; + // Whether inbound ICS-20 transfers are enabled + bool inbound_ics20_transfers_enabled = 2; + // Whether outbound ICS-20 transfers are enabled + bool outbound_ics20_transfers_enabled = 3; +} + +message Fees { + astria.primitive.v1.Uint128 transfer_base_fee = 1; + astria.primitive.v1.Uint128 sequence_base_fee = 2; + astria.primitive.v1.Uint128 sequence_byte_cost_multiplier = 3; + astria.primitive.v1.Uint128 init_bridge_account_base_fee = 4; + astria.primitive.v1.Uint128 bridge_lock_byte_cost_multiplier = 5; + astria.primitive.v1.Uint128 bridge_sudo_change_fee = 6; + astria.primitive.v1.Uint128 ics20_withdrawal_base_fee = 7; +} diff --git a/tools/protobuf-compiler/src/main.rs b/tools/protobuf-compiler/src/main.rs index 20700a0d40..5404a5392e 100644 --- a/tools/protobuf-compiler/src/main.rs +++ b/tools/protobuf-compiler/src/main.rs @@ -77,6 +77,10 @@ fn main() { "crate::generated::astria_vendored::tendermint::abci::ValidatorUpdate", ) .type_attribute(".astria.primitive.v1.Uint128", "#[derive(Copy)]") + .type_attribute( + ".astria.protocol.genesis.v1alpha1.IbcParameters", + "#[derive(Copy)]", + ) .use_arc_self(true) // override prost-types with pbjson-types .compile_well_known_types(true)