diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs index e831043307..a1d1076a6f 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs @@ -158,13 +158,16 @@ impl Submitter { .wrap_err("failed to get nonce from sequencer")?; debug!(nonce, "fetched latest nonce"); - let unsigned = UnsignedTransaction { - actions, - params: TransactionParams::builder() - .nonce(nonce) - .chain_id(sequencer_chain_id) - .build(), - }; + let unsigned = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(nonce) + .chain_id(sequencer_chain_id) + .build(), + ) + .build() + .wrap_err("failed to build unsigned transaction")?; // sign transaction let signed = unsigned.into_signed(signer.signing_key()); diff --git a/crates/astria-cli/src/commands/bridge/submit.rs b/crates/astria-cli/src/commands/bridge/submit.rs index c332a5a6d1..c3a531894a 100644 --- a/crates/astria-cli/src/commands/bridge/submit.rs +++ b/crates/astria-cli/src/commands/bridge/submit.rs @@ -129,14 +129,17 @@ async fn submit_transaction( .await .wrap_err("failed to get nonce")?; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(nonce_res.nonce) - .chain_id(chain_id) - .build(), - actions, - } - .into_signed(signing_key); + let tx = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(nonce_res.nonce) + .chain_id(chain_id) + .build(), + ) + .build() + .expect("failed to build transaction from actions") + .into_signed(signing_key); let res = client .submit_transaction_sync(tx) .await diff --git a/crates/astria-cli/src/commands/sequencer.rs b/crates/astria-cli/src/commands/sequencer.rs index 46259e4763..75221d66c9 100644 --- a/crates/astria-cli/src/commands/sequencer.rs +++ b/crates/astria-cli/src/commands/sequencer.rs @@ -475,14 +475,17 @@ async fn submit_transaction( .await .wrap_err("failed to get nonce")?; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(nonce_res.nonce) - .chain_id(chain_id) - .build(), - actions: vec![action], - } - .into_signed(&sequencer_key); + let tx = UnsignedTransaction::builder() + .actions(vec![action]) + .params( + TransactionParams::builder() + .nonce(nonce_res.nonce) + .chain_id(chain_id) + .build(), + ) + .build() + .expect("failed to build transaction from actions") + .into_signed(&sequencer_key); let res = sequencer_client .submit_transaction_sync(tx) .await diff --git a/crates/astria-composer/src/executor/mod.rs b/crates/astria-composer/src/executor/mod.rs index 0f2533cd79..19aa778050 100644 --- a/crates/astria-composer/src/executor/mod.rs +++ b/crates/astria-composer/src/executor/mod.rs @@ -681,11 +681,12 @@ impl Future for SubmitFut { .nonce(*this.nonce) .chain_id(&*this.chain_id) .build(); - let tx = UnsignedTransaction { - actions: this.bundle.clone().into_actions(), - params, - } - .into_signed(this.signing_key); + let tx = UnsignedTransaction::builder() + .actions(this.bundle.clone().into_actions()) + .params(params) + .build() + .expect("failed to build transaction from actions") + .into_signed(this.signing_key); info!( nonce.actual = *this.nonce, bundle = %telemetry::display::json(&SizedBundleReport(this.bundle)), @@ -759,11 +760,12 @@ impl Future for SubmitFut { .nonce(*this.nonce) .chain_id(&*this.chain_id) .build(); - let tx = UnsignedTransaction { - actions: this.bundle.clone().into_actions(), - params, - } - .into_signed(this.signing_key); + let tx = UnsignedTransaction::builder() + .actions(this.bundle.clone().into_actions()) + .params(params) + .build() + .expect("failed to build transaction from actions") + .into_signed(this.signing_key); info!( nonce.resubmission = *this.nonce, bundle = %telemetry::display::json(&SizedBundleReport(this.bundle)), diff --git a/crates/astria-core/src/protocol/test_utils.rs b/crates/astria-core/src/protocol/test_utils.rs index 1f399a4227..015ae58945 100644 --- a/crates/astria-core/src/protocol/test_utils.rs +++ b/crates/astria-core/src/protocol/test_utils.rs @@ -104,16 +104,19 @@ impl ConfigureSequencerBlock { let txs = if actions.is_empty() { vec![] } else { - let unsigned_transaction = UnsignedTransaction { - actions, - params: TransactionParams::builder() - .nonce(1) - .chain_id(chain_id.clone()) - .build(), - }; + let unsigned_transaction = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(1) + .chain_id(chain_id.clone()) + .build(), + ) + .build() + .expect("failed to build unsigned transaction"); + vec![unsigned_transaction.into_signed(&signing_key)] }; - let mut deposits_map: HashMap> = HashMap::new(); for deposit in deposits { if let Some(entry) = deposits_map.get_mut(deposit.rollup_id()) { diff --git a/crates/astria-core/src/protocol/transaction/v1alpha1/action_groups.rs b/crates/astria-core/src/protocol/transaction/v1alpha1/action_groups.rs new file mode 100644 index 0000000000..154b616ebb --- /dev/null +++ b/crates/astria-core/src/protocol/transaction/v1alpha1/action_groups.rs @@ -0,0 +1,230 @@ +use super::{ + action::{ + BridgeLockAction, + BridgeSudoChangeAction, + BridgeUnlockAction, + FeeAssetChangeAction, + FeeChangeAction, + IbcRelayerChangeAction, + Ics20Withdrawal, + InitBridgeAccountAction, + SequenceAction, + SudoAddressChangeAction, + TransferAction, + ValidatorUpdate, + }, + Action, +}; +trait Sealed {} + +//#[expect(private_bounds)] +trait BelongsToGroup: Sealed { + fn belongs_to_group(&self) -> ActionGroup; +} + +macro_rules! impl_belong_to_group { + ($($act:ty),*$(,)?) => { + $( + impl Sealed for $act {} + + impl BelongsToGroup for $act { + fn belongs_to_group(&self) -> ActionGroup { + Self::ACTION_GROUP.into() + } + } + )* + } +} + +impl SequenceAction { + const ACTION_GROUP: BundlableGeneral = BundlableGeneral; +} + +impl TransferAction { + const ACTION_GROUP: BundlableGeneral = BundlableGeneral; +} + +impl ValidatorUpdate { + const ACTION_GROUP: BundlableGeneral = BundlableGeneral; +} + +impl SudoAddressChangeAction { + const ACTION_GROUP: Sudo = Sudo; +} + +impl IbcRelayerChangeAction { + const ACTION_GROUP: BundlableSudo = BundlableSudo; +} + +impl Ics20Withdrawal { + const ACTION_GROUP: BundlableGeneral = BundlableGeneral; +} + +impl FeeAssetChangeAction { + const ACTION_GROUP: BundlableSudo = BundlableSudo; +} + +impl InitBridgeAccountAction { + const ACTION_GROUP: General = General; +} + +impl BridgeLockAction { + const ACTION_GROUP: BundlableGeneral = BundlableGeneral; +} + +impl BridgeUnlockAction { + const ACTION_GROUP: BundlableGeneral = BundlableGeneral; +} + +impl BridgeSudoChangeAction { + const ACTION_GROUP: General = General; +} + +impl FeeChangeAction { + const ACTION_GROUP: BundlableSudo = BundlableSudo; +} + +impl_belong_to_group!( + SequenceAction, + TransferAction, + ValidatorUpdate, + SudoAddressChangeAction, + IbcRelayerChangeAction, + Ics20Withdrawal, + InitBridgeAccountAction, + BridgeLockAction, + BridgeUnlockAction, + BridgeSudoChangeAction, + FeeChangeAction, + FeeAssetChangeAction +); + +impl Sealed for Action {} +impl BelongsToGroup for Action { + fn belongs_to_group(&self) -> ActionGroup { + match self { + Action::Sequence(act) => act.belongs_to_group(), + Action::Transfer(act) => act.belongs_to_group(), + Action::ValidatorUpdate(act) => act.belongs_to_group(), + Action::SudoAddressChange(act) => act.belongs_to_group(), + Action::IbcRelayerChange(act) => act.belongs_to_group(), + Action::Ics20Withdrawal(act) => act.belongs_to_group(), + Action::InitBridgeAccount(act) => act.belongs_to_group(), + Action::BridgeLock(act) => act.belongs_to_group(), + Action::BridgeUnlock(act) => act.belongs_to_group(), + Action::BridgeSudoChange(act) => act.belongs_to_group(), + Action::FeeChange(act) => act.belongs_to_group(), + Action::FeeAssetChange(act) => act.belongs_to_group(), + Action::Ibc(_) => BundlableGeneral.into(), /* TODO: can't use implement on act + * directly since it lives in a externa + * crate */ + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BundlableGeneral; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct General; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BundlableSudo; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Sudo; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ActionGroup { + BundlableGeneral(BundlableGeneral), + General(General), + BundlableSudo(BundlableSudo), + Sudo(Sudo), +} + +impl From for ActionGroup { + fn from(val: BundlableGeneral) -> ActionGroup { + ActionGroup::BundlableGeneral(val) + } +} + +impl From for ActionGroup { + fn from(val: General) -> ActionGroup { + ActionGroup::General(val) + } +} + +impl From for ActionGroup { + fn from(val: BundlableSudo) -> ActionGroup { + ActionGroup::BundlableSudo(val) + } +} + +impl From for ActionGroup { + fn from(val: Sudo) -> ActionGroup { + ActionGroup::Sudo(val) + } +} + +#[derive(Debug, thiserror::Error)] +enum ActionGroupErrorKind { + #[error("input contains mixed action types")] + Mixed, + #[error("input attempted to bundle non bundleable action type")] + NotBundleable, +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct ActionGroupError(ActionGroupErrorKind); +impl ActionGroupError { + fn mixed() -> Self { + Self(ActionGroupErrorKind::Mixed) + } + + fn not_bundlable() -> Self { + Self(ActionGroupErrorKind::NotBundleable) + } +} + +/// Invariants: `group` is set if `inner` is not empty. +#[derive(Clone, Debug)] +pub(super) struct Actions { + group: Option, + inner: Vec, +} + +impl Actions { + pub(super) fn actions(&self) -> &[Action] { + &self.inner + } + + #[must_use] + pub(super) fn into_actions(self) -> Vec { + self.inner + } + + pub(super) fn group(&self) -> &Option { + &self.group + } + + pub(super) fn from_list_of_actions(actions: Vec) -> Result { + let mut group = None; + for action in &actions { + if group.is_none() { + group = Some(action.belongs_to_group()); + } else if group != Some(action.belongs_to_group()) { + return Err(ActionGroupError::mixed()); + } + } + + // assert size constraints on non-bundlable action groups + if (Some(ActionGroup::General(General)) == group || Some(ActionGroup::Sudo(Sudo)) == group) + && actions.len() > 1 + { + return Err(ActionGroupError::not_bundlable()); + } + Ok(Self { + group, + inner: actions, + }) + } +} diff --git a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs index d534c9c548..f1b569dca9 100644 --- a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs +++ b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs @@ -1,3 +1,7 @@ +use action_groups::{ + ActionGroup, + Actions, +}; use bytes::Bytes; use prost::{ Message as _, @@ -21,6 +25,7 @@ use crate::{ }; pub mod action; +pub mod action_groups; pub use action::Action; #[derive(Debug, thiserror::Error)] @@ -198,7 +203,12 @@ impl SignedTransaction { #[must_use] pub fn actions(&self) -> &[Action] { - &self.transaction.actions + self.transaction.actions.actions() + } + + #[must_use] + pub fn group(&self) -> &Option { + self.transaction.actions.group() } #[must_use] @@ -226,14 +236,85 @@ impl SignedTransaction { } } +pub struct UnsignedTransactionBuilder { + actions: Vec, + params: TransactionParams, +} + +impl UnsignedTransactionBuilder { + fn new() -> Self { + Self { + actions: Vec::new(), + params: TransactionParams::from_raw(raw::TransactionParams { + nonce: 0, + chain_id: String::new(), + }), + } + } +} + +impl UnsignedTransactionBuilder { + #[must_use] + pub fn actions(self, actions: Vec) -> Self { + Self { + actions, + ..self + } + } + + #[must_use] + pub fn params(self, params: TransactionParams) -> Self { + Self { + params, + ..self + } + } + + /// Builds the `UnsignedTransaction` from the builder. + /// + /// # Errors + /// + /// This function will return an error if the actions list contains actions + /// of different `ActionGroup` types or violated an `ActionGroup`'s bundling constraints. + pub fn build(self) -> Result { + let Self { + actions, + params, + } = self; + + let actions = Actions::from_list_of_actions(actions) + .map_err(UnsignedTransactionError::action_group)?; + + Ok(UnsignedTransaction { + actions, + params, + }) + } +} + #[derive(Clone, Debug)] #[allow(clippy::module_name_repetitions)] pub struct UnsignedTransaction { - pub actions: Vec, - pub params: TransactionParams, + actions: Actions, + params: TransactionParams, } impl UnsignedTransaction { + #[must_use] + pub fn builder() -> UnsignedTransactionBuilder { + UnsignedTransactionBuilder::new() + } + + #[must_use] + pub fn into_actions(self) -> Vec { + self.actions.into_actions() + } + + #[must_use] + pub fn actions(&self) -> &[Action] { + self.actions.actions() + } + #[must_use] pub fn nonce(&self) -> u32 { self.params.nonce @@ -262,7 +343,11 @@ impl UnsignedTransaction { actions, params, } = self; - let actions = actions.into_iter().map(Action::into_raw).collect(); + let actions = actions + .into_actions() + .into_iter() + .map(Action::into_raw) + .collect(); raw::UnsignedTransaction { actions, params: Some(params.into_raw()), @@ -283,7 +368,7 @@ impl UnsignedTransaction { actions, params, } = self; - let actions = actions.iter().map(Action::to_raw).collect(); + let actions = actions.actions().iter().map(Action::to_raw).collect(); let params = params.clone().into_raw(); raw::UnsignedTransaction { actions, @@ -317,10 +402,10 @@ impl UnsignedTransaction { .collect::>() .map_err(UnsignedTransactionError::action)?; - Ok(Self { - actions, - params, - }) + UnsignedTransactionBuilder::new() + .actions(actions) + .params(params) + .build() } /// Attempt to convert from a protobuf [`pbjson_types::Any`]. @@ -362,6 +447,10 @@ impl UnsignedTransactionError { fn decode_any(inner: prost::DecodeError) -> Self { Self(UnsignedTransactionErrorKind::DecodeAny(inner)) } + + fn action_group(inner: action_groups::ActionGroupError) -> Self { + Self(UnsignedTransactionErrorKind::ActionGroup(inner)) + } } #[derive(Debug, thiserror::Error)] @@ -381,6 +470,8 @@ enum UnsignedTransactionErrorKind { raw::UnsignedTransaction::type_url() )] DecodeAny(#[source] prost::DecodeError), + #[error("failed to construct valid `Actions` from list of actions")] + ActionGroup(#[source] action_groups::ActionGroupError), } pub struct TransactionParamsBuilder> { @@ -557,10 +648,7 @@ enum TransactionFeeResponseErrorKind { mod test { use super::*; use crate::{ - primitive::v1::{ - asset, - Address, - }, + primitive::v1::Address, protocol::transaction::v1alpha1::action::TransferAction, }; const ASTRIA_ADDRESS_PREFIX: &str = "astria"; @@ -598,10 +686,12 @@ mod test { nonce: 1, chain_id: "test-1".to_string(), }); - let unsigned = UnsignedTransaction { - actions: vec![transfer.into()], - params, - }; + + let unsigned = UnsignedTransaction::builder() + .actions(vec![transfer.into()]) + .params(params) + .build() + .expect("failed to build unsigned transaction"); let tx = SignedTransaction { signature, @@ -635,12 +725,14 @@ mod test { nonce: 1, chain_id: "test-1".to_string(), }); - let unsigned = UnsignedTransaction { - actions: vec![transfer.into()], - params, - }; - let signed_tx = unsigned.into_signed(&signing_key); + let unsigned_tx = UnsignedTransaction::builder() + .actions(vec![transfer.into()]) + .params(params) + .build() + .expect("failed to build unsigned transaction"); + + let signed_tx = unsigned_tx.into_signed(&signing_key); let raw = signed_tx.to_raw(); // `try_from_raw` verifies the signature diff --git a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs index a1805c9532..2f48424a76 100644 --- a/crates/astria-core/src/sequencerblock/v1alpha1/block.rs +++ b/crates/astria-core/src/sequencerblock/v1alpha1/block.rs @@ -735,7 +735,7 @@ impl SequencerBlock { .map_err(SequencerBlockError::signed_transaction_protobuf_decode)?; let signed_tx = SignedTransaction::try_from_raw(raw_tx) .map_err(SequencerBlockError::raw_signed_transaction_conversion)?; - for action in signed_tx.into_unsigned().actions { + for action in signed_tx.into_unsigned().into_actions() { // XXX: The fee asset is dropped. We shjould explain why that's ok. if let action::Action::Sequence(action::SequenceAction { rollup_id, diff --git a/crates/astria-sequencer-client/src/tests/http.rs b/crates/astria-sequencer-client/src/tests/http.rs index 5bfbc89f00..7c0752ad22 100644 --- a/crates/astria-sequencer-client/src/tests/http.rs +++ b/crates/astria-sequencer-client/src/tests/http.rs @@ -157,14 +157,17 @@ fn create_signed_transaction() -> SignedTransaction { } .into(), ]; - UnsignedTransaction { - params: TransactionParams::builder() - .nonce(1) - .chain_id("test") - .build(), - actions, - } - .into_signed(&alice_key) + UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .chain_id("test") + .nonce(1) + .build(), + ) + .build() + .unwrap() + .into_signed(&alice_key) } #[tokio::test] diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 66a9e2c5a8..2513a9a8f8 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -25,6 +25,7 @@ use astria_core::{ genesis::v1alpha1::GenesisAppState, transaction::v1alpha1::{ action::ValidatorUpdate, + action_groups::ActionGroup, Action, SignedTransaction, }, @@ -537,7 +538,7 @@ impl App { // check if tx's sequence data will fit into sequence block let tx_sequence_data_bytes = tx .unsigned_transaction() - .actions + .actions() .iter() .filter_map(Action::as_sequence) .fold(0usize, |acc, seq| acc.saturating_add(seq.data.len())); @@ -657,7 +658,7 @@ impl App { // check if tx's sequence data will fit into sequence block let tx_sequence_data_bytes = tx .unsigned_transaction() - .actions + .actions() .iter() .filter_map(Action::as_sequence) .fold(0usize, |acc, seq| acc.saturating_add(seq.data.len())); @@ -1024,10 +1025,10 @@ impl App { // flag mempool for cleaning if we ran a fee change action self.recost_mempool = self.recost_mempool - || signed_tx - .actions() - .iter() - .any(|action| matches!(action, Action::FeeAssetChange(_) | Action::FeeChange(_))); + || matches!(signed_tx.group(), Some(ActionGroup::BundlableSudo(_))) + && signed_tx.actions().iter().any(|action| { + matches!(action, Action::FeeAssetChange(_) | Action::FeeChange(_)) + }); Ok(state_tx.apply().1) } diff --git a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_execute_transaction_with_every_action_snapshot.snap b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_execute_transaction_with_every_action_snapshot.snap index 3e900c2423..5fc3a6fb73 100644 --- a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_execute_transaction_with_every_action_snapshot.snap +++ b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_execute_transaction_with_every_action_snapshot.snap @@ -3,36 +3,36 @@ source: crates/astria-sequencer/src/app/tests_breaking_changes.rs expression: app.app_hash.as_bytes() --- [ - 18, - 206, - 200, - 101, - 160, - 129, - 124, - 188, - 249, - 181, + 233, + 91, + 186, + 227, + 243, + 121, + 116, + 152, + 3, + 78, + 219, + 120, + 70, + 57, + 166, + 123, + 68, 44, - 218, - 209, - 251, - 208, - 119, - 122, + 54, + 18, + 61, + 104, + 123, + 99, + 61, + 221, 211, - 105, + 2, + 208, + 148, 201, - 75, - 214, - 56, - 145, - 239, - 132, - 48, - 0, - 79, - 13, - 53, - 156 + 235 ] diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index afe705fb6e..678a9090e7 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -223,20 +223,23 @@ pub(crate) fn mock_tx( signer: &SigningKey, rollup_name: &str, ) -> Arc { - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(nonce) - .chain_id("test") - .build(), - actions: vec![ + let tx = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(rollup_name.as_bytes()), data: Bytes::from_static(&[0x99]), fee_asset: denom_0(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(nonce) + .chain_id("test") + .build(), + ) + .build() + .expect("Failed to build unsigned transaction"); Arc::new(tx.into_signed(signer)) } diff --git a/crates/astria-sequencer/src/app/tests_app/mempool.rs b/crates/astria-sequencer/src/app/tests_app/mempool.rs index 65d327af80..807451cd6d 100644 --- a/crates/astria-sequencer/src/app/tests_app/mempool.rs +++ b/crates/astria-sequencer/src/app/tests_app/mempool.rs @@ -48,23 +48,25 @@ async fn trigger_cleaning() { let (mut app, storage) = initialize_app_with_storage(None, vec![]).await; app.prepare_commit(storage.clone()).await.unwrap(); app.commit(storage.clone()).await; - let sudo = get_judy_signing_key(); // create tx which will cause mempool cleaning flag to be set - let tx_trigger = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_trigger = UnsignedTransaction::builder() + .actions(vec![ FeeChangeAction { fee_change: FeeChange::TransferBaseFee, new_value: 10, } .into(), - ], - } - .into_signed(&sudo); + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&get_judy_signing_key()); app.mempool .insert( @@ -147,24 +149,25 @@ async fn do_not_trigger_cleaning() { app.prepare_commit(storage.clone()).await.unwrap(); app.commit(storage.clone()).await; - let alice = get_alice_signing_key(); - // create tx which will fail execution and not trigger flag // (wrong sudo signer) - let tx_fail = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_fail = UnsignedTransaction::builder() + .actions(vec![ FeeChangeAction { fee_change: FeeChange::TransferBaseFee, new_value: 10, } .into(), - ], - } - .into_signed(&alice); + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&get_alice_signing_key()); app.mempool .insert( @@ -224,12 +227,8 @@ async fn maintenance_recosting_promotes() { // create tx which will not be included in block due to // having insufficient funds (transaction will be recosted to enable) - let tx_fail_recost_funds = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_fail_recost_funds = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: astria_address_from_hex_string(CAROL_ADDRESS), amount: 1u128, @@ -237,9 +236,16 @@ async fn maintenance_recosting_promotes() { fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&get_bob_signing_key()); + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&get_bob_signing_key()); let mut bob_funds = HashMap::new(); bob_funds.insert(nria().into(), 11); @@ -256,20 +262,23 @@ async fn maintenance_recosting_promotes() { .unwrap(); // create tx which will enable recost tx to pass - let tx_recost = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_recost = UnsignedTransaction::builder() + .actions(vec![ FeeChangeAction { fee_change: FeeChange::TransferBaseFee, new_value: 10, // originally 12 } .into(), - ], - } - .into_signed(&get_judy_signing_key()); + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&get_judy_signing_key()); let mut judy_funds = HashMap::new(); judy_funds.insert(nria().into(), 0); @@ -397,12 +406,8 @@ async fn maintenance_funds_added_promotes() { // create tx that will not be included in block due to // having no funds (will be sent transfer to then enable) - let tx_fail_transfer_funds = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_fail_transfer_funds = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: astria_address_from_hex_string(BOB_ADDRESS), amount: 10u128, @@ -410,9 +415,16 @@ async fn maintenance_funds_added_promotes() { fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&get_carol_signing_key()); + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&get_carol_signing_key()); let mut carol_funds = HashMap::new(); carol_funds.insert(nria().into(), 0); @@ -429,12 +441,8 @@ async fn maintenance_funds_added_promotes() { .unwrap(); // create tx which will enable no funds to pass - let tx_fund = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_fund = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: astria_address_from_hex_string(CAROL_ADDRESS), amount: 22u128, @@ -442,9 +450,16 @@ async fn maintenance_funds_added_promotes() { fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&get_alice_signing_key()); + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&get_alice_signing_key()); let mut alice_funds = HashMap::new(); alice_funds.insert(nria().into(), 100); diff --git a/crates/astria-sequencer/src/app/tests_app/mod.rs b/crates/astria-sequencer/src/app/tests_app/mod.rs index 6869d0bf90..cc17dabe69 100644 --- a/crates/astria-sequencer/src/app/tests_app/mod.rs +++ b/crates/astria-sequencer/src/app/tests_app/mod.rs @@ -231,12 +231,8 @@ async fn app_transfer_block_fees_to_sudo() { // transfer funds from Alice to Bob; use native token for fee payment let bob_address = astria_address_from_hex_string(BOB_ADDRESS); let amount = 333_333; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: bob_address, amount, @@ -244,8 +240,15 @@ async fn app_transfer_block_fees_to_sudo() { fee_asset: nria().into(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction"); let signed_tx = tx.into_signed(&alice); @@ -321,13 +324,17 @@ async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { data: Bytes::from_static(b"hello world"), fee_asset: nria().into(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![lock_action.into(), sequence_action.into()], - }; + + let tx = UnsignedTransaction::builder() + .actions(vec![lock_action.into(), sequence_action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction"); let signed_tx = tx.into_signed(&alice); @@ -414,13 +421,17 @@ async fn app_execution_results_match_proposal_vs_after_proposal() { data: Bytes::from_static(b"hello world"), fee_asset: nria().into(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![lock_action.into(), sequence_action.into()], - }; + + let tx = UnsignedTransaction::builder() + .actions(vec![lock_action.into(), sequence_action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction"); let signed_tx = tx.into_signed(&alice); @@ -551,36 +562,43 @@ async fn app_prepare_proposal_cometbft_max_bytes_overflow_ok() { // create txs which will cause cometBFT overflow let alice = get_alice_signing_key(); - let tx_pass = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_pass = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), data: Bytes::copy_from_slice(&[1u8; 100_000]), fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&alice); - let tx_overflow = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(1) - .chain_id("test") - .build(), - actions: vec![ + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&alice); + + let tx_overflow = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), data: Bytes::copy_from_slice(&[1u8; 100_000]), fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&alice); + ]) + .params( + TransactionParams::builder() + .nonce(1) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&alice); app.mempool .insert( @@ -643,36 +661,43 @@ async fn app_prepare_proposal_sequencer_max_bytes_overflow_ok() { // create txs which will cause sequencer overflow (max is currently 256_000 bytes) let alice = get_alice_signing_key(); - let tx_pass = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_pass = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), data: Bytes::copy_from_slice(&[1u8; 200_000]), fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&alice); - let tx_overflow = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(1) - .chain_id("test") - .build(), - actions: vec![ + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&alice); + + let tx_overflow = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), data: Bytes::copy_from_slice(&[1u8; 100_000]), fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&alice); + ]) + .params( + TransactionParams::builder() + .nonce(1) + .chain_id("test") + .build(), + ) + .build() + .expect("failed to build unsigned transaction") + .into_signed(&alice); app.mempool .insert( diff --git a/crates/astria-sequencer/src/app/tests_block_fees.rs b/crates/astria-sequencer/src/app/tests_block_fees.rs index 29c2292c0e..472f5645f4 100644 --- a/crates/astria-sequencer/src/app/tests_block_fees.rs +++ b/crates/astria-sequencer/src/app/tests_block_fees.rs @@ -53,12 +53,8 @@ async fn transaction_execution_records_fee_event() { 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![ + let tx = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: bob_address, amount: value, @@ -66,9 +62,15 @@ async fn transaction_execution_records_fee_event() { fee_asset: nria().into(), } .into(), - ], - }; - + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); let events = app.execute_transaction(signed_tx).await.unwrap(); @@ -114,13 +116,16 @@ async fn ensure_correct_block_fees_transfer() { .into(), ]; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions, - }; + let tx = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -155,13 +160,16 @@ async fn ensure_correct_block_fees_sequence() { .into(), ]; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions, - }; + let tx = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -198,13 +206,16 @@ async fn ensure_correct_block_fees_init_bridge_acct() { .into(), ]; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions, - }; + let tx = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -252,13 +263,16 @@ async fn ensure_correct_block_fees_bridge_lock() { .into(), ]; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions, - }; + let tx = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx.clone()).await.unwrap(); @@ -314,13 +328,16 @@ async fn ensure_correct_block_fees_bridge_sudo_change() { .into(), ]; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions, - }; + let tx = UnsignedTransaction::builder() + .actions(actions) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index 473b4d5eaa..729c348306 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -9,10 +9,7 @@ //! These are due to the extensive setup needed to test them. //! If changes are made to the execution results of these actions, manual testing is required. -use std::{ - collections::HashMap, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use astria_core::{ primitive::v1::RollupId, @@ -20,55 +17,30 @@ use astria_core::{ genesis::v1alpha1::Account, transaction::v1alpha1::{ action::{ - BridgeLockAction, - BridgeSudoChangeAction, - BridgeUnlockAction, - IbcRelayerChangeAction, - SequenceAction, - TransferAction, - ValidatorUpdate, + BridgeLockAction, BridgeSudoChangeAction, BridgeUnlockAction, FeeChange, + FeeChangeAction, IbcRelayerChangeAction, Ics20Withdrawal, SequenceAction, + TransferAction, ValidatorUpdate, }, - Action, - TransactionParams, - UnsignedTransaction, + Action, TransactionParams, UnsignedTransaction, }, }, sequencerblock::v1alpha1::block::Deposit, Protobuf, }; use cnidarium::StateDelta; -use prost::{ - bytes::Bytes, - Message as _, -}; -use tendermint::{ - abci, - abci::types::CommitInfo, - block::Round, - Hash, - Time, -}; +use ibc_types::core::client::Height; +use prost::{bytes::Bytes, Message as _}; +use tendermint::{abci, abci::types::CommitInfo, block::Round, Hash, Time}; use crate::{ app::test_utils::{ - default_genesis_accounts, - get_alice_signing_key, - get_bridge_signing_key, - initialize_app, - initialize_app_with_storage, - proto_genesis_state, - BOB_ADDRESS, - CAROL_ADDRESS, + default_genesis_accounts, get_alice_signing_key, get_bridge_signing_key, initialize_app, + initialize_app_with_storage, proto_genesis_state, BOB_ADDRESS, CAROL_ADDRESS, }, authority::StateReadExt as _, bridge::StateWriteExt as _, proposal::commitment::generate_rollup_datas_commitment, - test_utils::{ - astria_address, - astria_address_from_hex_string, - nria, - ASTRIA_PREFIX, - }, + test_utils::{astria_address, astria_address_from_hex_string, nria, ASTRIA_PREFIX}, }; #[tokio::test] @@ -111,13 +83,17 @@ async fn app_finalize_block_snapshot() { data: Bytes::from_static(b"hello world"), fee_asset: nria().into(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![lock_action.into(), sequence_action.into()], - }; + + let tx = UnsignedTransaction::builder() + .actions(vec![lock_action.into(), sequence_action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = tx.into_signed(&alice); @@ -164,9 +140,7 @@ async fn app_finalize_block_snapshot() { #[tokio::test] async fn app_execute_transaction_with_every_action_snapshot() { use astria_core::protocol::transaction::v1alpha1::action::{ - FeeAssetChangeAction, - InitBridgeAccountAction, - SudoAddressChangeAction, + FeeAssetChangeAction, InitBridgeAccountAction, SudoAddressChangeAction, }; let alice = get_alice_signing_key(); @@ -201,12 +175,8 @@ async fn app_execute_transaction_with_every_action_snapshot() { let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx_bundlable_general = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: bob_address, amount: 333_333, @@ -221,6 +191,18 @@ async fn app_execute_transaction_with_every_action_snapshot() { } .into(), Action::ValidatorUpdate(update.clone()), + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); + + let tx_bundlable_sudo = UnsignedTransaction::builder() + .actions(vec![ IbcRelayerChangeAction::Addition(bob_address).into(), IbcRelayerChangeAction::Addition(carol_address).into(), IbcRelayerChangeAction::Removal(bob_address).into(), @@ -228,41 +210,65 @@ async fn app_execute_transaction_with_every_action_snapshot() { FeeAssetChangeAction::Addition("test-0".parse().unwrap()).into(), FeeAssetChangeAction::Addition("test-1".parse().unwrap()).into(), FeeAssetChangeAction::Removal("test-0".parse().unwrap()).into(), - SudoAddressChangeAction { - new_address: bob_address, - } - .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(1) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); - let signed_tx = Arc::new(tx.into_signed(&alice)); - app.execute_transaction(signed_tx).await.unwrap(); + let tx_sudo = UnsignedTransaction::builder() + .actions(vec![SudoAddressChangeAction { + new_address: bob_address, + } + .into()]) + .params( + TransactionParams::builder() + .nonce(2) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ - InitBridgeAccountAction { - rollup_id, - asset: nria().into(), - fee_asset: nria().into(), - sudo_address: None, - withdrawer_address: None, - } - .into(), - ], - }; + let signed_tx_general_bundlable = Arc::new(tx_bundlable_general.into_signed(&alice)); + app.execute_transaction(signed_tx_general_bundlable) + .await + .unwrap(); + + let signed_tx_sudo_bundlable = Arc::new(tx_bundlable_sudo.into_signed(&alice)); + app.execute_transaction(signed_tx_sudo_bundlable) + .await + .unwrap(); + + let signed_tx_sudo = Arc::new(tx_sudo.into_signed(&alice)); + app.execute_transaction(signed_tx_sudo).await.unwrap(); + + let tx = UnsignedTransaction::builder() + .actions(vec![InitBridgeAccountAction { + rollup_id, + asset: nria().into(), + fee_asset: nria().into(), + sudo_address: None, + withdrawer_address: None, + } + .into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&bridge)); app.execute_transaction(signed_tx).await.unwrap(); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .chain_id("test") - .nonce(1) - .build(), - actions: vec![ + let tx_bridge_bundlable = UnsignedTransaction::builder() + .actions(vec![ BridgeLockAction { to: bridge_address, amount: 100, @@ -281,17 +287,37 @@ async fn app_execute_transaction_with_every_action_snapshot() { rollup_withdrawal_event_id: "a-rollup-defined-hash".to_string(), } .into(), - BridgeSudoChangeAction { - bridge_address, - new_sudo_address: Some(bob_address), - new_withdrawer_address: Some(bob_address), - fee_asset: nria().into(), - } - .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(1) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); - let signed_tx = Arc::new(tx.into_signed(&bridge)); + let signed_tx = Arc::new(tx_bridge_bundlable.into_signed(&bridge)); + app.execute_transaction(signed_tx).await.unwrap(); + + let tx_bridge = UnsignedTransaction::builder() + .actions(vec![BridgeSudoChangeAction { + bridge_address, + new_sudo_address: Some(bob_address), + new_withdrawer_address: Some(bob_address), + fee_asset: nria().into(), + } + .into()]) + .params( + TransactionParams::builder() + .nonce(2) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); + + let signed_tx = Arc::new(tx_bridge.into_signed(&bridge)); app.execute_transaction(signed_tx).await.unwrap(); let sudo_address = app.state.get_sudo_address().await.unwrap(); @@ -302,3 +328,186 @@ async fn app_execute_transaction_with_every_action_snapshot() { insta::assert_json_snapshot!(app.app_hash.as_bytes()); } + +// Note: this tests every action except for `Ics20Withdrawal` and `IbcRelay`. +// +// If new actions are added to the app, they must be added to this test, +// and the respective PR must be marked as breaking. +#[allow(clippy::too_many_lines)] +#[tokio::test] +async fn app_transaction_bundles_stable() { + use astria_core::protocol::transaction::v1alpha1::action::{ + FeeAssetChangeAction, InitBridgeAccountAction, SudoAddressChangeAction, + }; + + let bridge = get_bridge_signing_key(); + let bridge_address = astria_address(&bridge.address_bytes()); + let bob_address = astria_address_from_hex_string(BOB_ADDRESS); + let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); + + assert!( + UnsignedTransaction::builder() + .actions(vec![ + TransferAction { + to: bob_address, + amount: 333_333, + asset: nria().into(), + fee_asset: nria().into(), + } + .into(), + SequenceAction { + rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), + data: Bytes::from_static(b"hello world"), + fee_asset: nria().into(), + } + .into(), + Action::ValidatorUpdate(ValidatorUpdate { + power: 100, + verification_key: crate::test_utils::verification_key(1), + }), + BridgeLockAction { + to: bridge_address, + amount: 100, + asset: nria().into(), + fee_asset: nria().into(), + destination_chain_address: "nootwashere".to_string(), + } + .into(), + BridgeUnlockAction { + to: bob_address, + amount: 10, + fee_asset: nria().into(), + memo: String::new(), + bridge_address: astria_address(&bridge.address_bytes()), + rollup_block_number: 1, + rollup_withdrawal_event_id: "a-rollup-defined-hash".to_string(), + } + .into(), + Ics20Withdrawal { + amount: 1, + denom: nria().into(), + bridge_address: None, + destination_chain_address: "test".to_string(), + return_address: bob_address, + timeout_height: Height::new(1, 1).unwrap(), + timeout_time: 1, + source_channel: "channel-0".to_string().parse().unwrap(), + fee_asset: nria().into(), + memo: String::new(), + use_compat_address: false, + } + .into(), + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .is_ok(), + "should be able to construct general bundle" + ); + + assert!( + UnsignedTransaction::builder() + .actions(vec![ + IbcRelayerChangeAction::Addition(bob_address).into(), + FeeAssetChangeAction::Removal("test-0".parse().unwrap()).into(), + FeeChangeAction { + fee_change: FeeChange::TransferBaseFee, + new_value: 10, + } + .into(), + ]) + .params( + TransactionParams::builder() + .nonce(1) + .chain_id("test") + .build(), + ) + .build() + .is_ok(), + "should be able to construct sudo bundle" + ); + + assert!(UnsignedTransaction::builder() + .actions(vec![ + SudoAddressChangeAction { + new_address: bob_address, + } + .into(), + SudoAddressChangeAction { + new_address: bob_address, + } + .into(), + ]) + .params( + TransactionParams::builder() + .nonce(2) + .chain_id("test") + .build(), + ) + .build() + .unwrap_err() + .to_string() + .contains("failed to construct valid `Actions`")); + + assert!(UnsignedTransaction::builder() + .actions(vec![ + InitBridgeAccountAction { + rollup_id, + asset: nria().into(), + fee_asset: nria().into(), + sudo_address: None, + withdrawer_address: None, + } + .into(), + InitBridgeAccountAction { + rollup_id, + asset: nria().into(), + fee_asset: nria().into(), + sudo_address: None, + withdrawer_address: None, + } + .into(), + ]) + .params( + TransactionParams::builder() + .nonce(2) + .chain_id("test") + .build(), + ) + .build() + .unwrap_err() + .to_string() + .contains("failed to construct valid `Actions`")); + + assert!(UnsignedTransaction::builder() + .actions(vec![ + BridgeSudoChangeAction { + bridge_address, + new_sudo_address: Some(bob_address), + new_withdrawer_address: Some(bob_address), + fee_asset: nria().into(), + } + .into(), + BridgeSudoChangeAction { + bridge_address, + new_sudo_address: Some(bob_address), + new_withdrawer_address: Some(bob_address), + fee_asset: nria().into(), + } + .into(), + ]) + .params( + TransactionParams::builder() + .nonce(2) + .chain_id("test") + .build(), + ) + .build() + .unwrap_err() + .to_string() + .contains("failed to construct valid `Actions`")); +} diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index 0be551232a..1f765612fb 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -103,12 +103,8 @@ async fn app_execute_transaction_transfer() { let alice_address = astria_address(&alice.address_bytes()); 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![ + let tx = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: bob_address, amount: value, @@ -116,8 +112,15 @@ async fn app_execute_transaction_transfer() { fee_asset: crate::test_utils::nria().into(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -160,12 +163,8 @@ async fn app_execute_transaction_transfer_not_native_token() { // transfer funds from Alice to Bob; use native token for fee payment let bob_address = astria_address_from_hex_string(BOB_ADDRESS); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: bob_address, amount: value, @@ -173,8 +172,15 @@ async fn app_execute_transaction_transfer_not_native_token() { fee_asset: nria().into(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -225,12 +231,8 @@ async fn app_execute_transaction_transfer_balance_too_low_for_fee() { let bob = astria_address_from_hex_string(BOB_ADDRESS); // 0-value transfer; only fee is deducted from sender - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: bob, amount: 0, @@ -238,8 +240,15 @@ async fn app_execute_transaction_transfer_balance_too_low_for_fee() { fee_asset: nria().into(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&keypair)); let res = app @@ -266,20 +275,23 @@ async fn app_execute_transaction_sequence() { let data = Bytes::from_static(b"hello world"); let fee = calculate_fee_from_state(&data, &app.state).await.unwrap(); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), data, fee_asset: nria().into(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -301,20 +313,23 @@ async fn app_execute_transaction_invalid_fee_asset() { let alice = get_alice_signing_key(); let data = Bytes::from_static(b"hello world"); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let tx = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), data, fee_asset: test_asset(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); assert!(app.execute_transaction(signed_tx).await.is_err()); @@ -332,13 +347,16 @@ async fn app_execute_transaction_validator_update() { verification_key: crate::test_utils::verification_key(1), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![Action::ValidatorUpdate(update.clone())], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![Action::ValidatorUpdate(update.clone())]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -359,13 +377,18 @@ async fn app_execute_transaction_ibc_relayer_change_addition() { let mut app = initialize_app(Some(genesis_state()), vec![]).await; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![IbcRelayerChangeAction::Addition(alice_address).into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![Action::IbcRelayerChange( + IbcRelayerChangeAction::Addition(alice_address), + )]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -387,14 +410,16 @@ async fn app_execute_transaction_ibc_relayer_change_deletion() { .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![IbcRelayerChangeAction::Removal(alice_address).into()], - }; - + let tx = UnsignedTransaction::builder() + .actions(vec![IbcRelayerChangeAction::Removal(alice_address).into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); 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); @@ -417,13 +442,16 @@ async fn app_execute_transaction_ibc_relayer_change_invalid() { .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![IbcRelayerChangeAction::Removal(alice_address).into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![IbcRelayerChangeAction::Removal(alice_address).into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); assert!(app.execute_transaction(signed_tx).await.is_err()); @@ -438,15 +466,18 @@ async fn app_execute_transaction_sudo_address_change() { let new_address = astria_address_from_hex_string(BOB_ADDRESS); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![Action::SudoAddressChange(SudoAddressChangeAction { + let tx = UnsignedTransaction::builder() + .actions(vec![Action::SudoAddressChange(SudoAddressChangeAction { new_address, - })], - }; + })]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -476,15 +507,18 @@ async fn app_execute_transaction_sudo_address_change_error() { .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![Action::SudoAddressChange(SudoAddressChangeAction { + let tx = UnsignedTransaction::builder() + .actions(vec![Action::SudoAddressChange(SudoAddressChangeAction { new_address: alice_address, - })], - }; + })]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); let res = app @@ -505,15 +539,18 @@ async fn app_execute_transaction_fee_asset_change_addition() { let mut app = initialize_app(Some(genesis_state()), vec![]).await; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![Action::FeeAssetChange(FeeAssetChangeAction::Addition( - test_asset(), - ))], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![Action::FeeAssetChange( + FeeAssetChangeAction::Addition(test_asset()), + )]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -538,15 +575,18 @@ async fn app_execute_transaction_fee_asset_change_removal() { .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![Action::FeeAssetChange(FeeAssetChangeAction::Removal( + let tx = UnsignedTransaction::builder() + .actions(vec![Action::FeeAssetChange(FeeAssetChangeAction::Removal( test_asset(), - ))], - }; + ))]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -563,15 +603,18 @@ async fn app_execute_transaction_fee_asset_change_invalid() { let mut app = initialize_app(Some(genesis_state()), vec![]).await; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![Action::FeeAssetChange(FeeAssetChangeAction::Removal( + let tx = UnsignedTransaction::builder() + .actions(vec![Action::FeeAssetChange(FeeAssetChangeAction::Removal( nria().into(), - ))], - }; + ))]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); let res = app @@ -604,13 +647,17 @@ async fn app_execute_transaction_init_bridge_account_ok() { sudo_address: None, withdrawer_address: None, }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![action.into()], - }; + + let tx = UnsignedTransaction::builder() + .actions(vec![action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); @@ -660,14 +707,16 @@ async fn app_execute_transaction_init_bridge_account_account_already_registered( sudo_address: None, withdrawer_address: None, }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - - actions: vec![action.into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx).await.unwrap(); @@ -679,13 +728,17 @@ async fn app_execute_transaction_init_bridge_account_account_already_registered( sudo_address: None, withdrawer_address: None, }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![action.into()], - }; + + let tx = UnsignedTransaction::builder() + .actions(vec![action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); assert!(app.execute_transaction(signed_tx).await.is_err()); @@ -716,13 +769,16 @@ async fn app_execute_transaction_bridge_lock_action_ok() { fee_asset: nria().into(), destination_chain_address: "nootwashere".to_string(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![action.into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); @@ -795,13 +851,16 @@ async fn app_execute_transaction_bridge_lock_action_invalid_for_eoa() { fee_asset: nria().into(), destination_chain_address: "nootwashere".to_string(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![action.into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); assert!(app.execute_transaction(signed_tx).await.is_err()); @@ -816,20 +875,24 @@ async fn app_execute_transaction_invalid_nonce() { // create tx with invalid nonce 1 let data = Bytes::from_static(b"hello world"); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(1) - .chain_id("test") - .build(), - actions: vec![ + + let tx = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), data, fee_asset: nria().into(), } .into(), - ], - }; + ]) + .params( + TransactionParams::builder() + .nonce(1) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); let response = app.execute_transaction(signed_tx).await; @@ -863,21 +926,23 @@ async fn app_execute_transaction_invalid_chain_id() { // create tx with invalid nonce 1 let data = Bytes::from_static(b"hello world"); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("wrong-chain") - .build(), - actions: vec![ + let tx = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), data, fee_asset: nria().into(), } .into(), - ], - }; - + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("wrong-chain") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); let response = app.execute_transaction(signed_tx).await; @@ -920,12 +985,8 @@ async fn app_stateful_check_fails_insufficient_total_balance() { .unwrap(); // transfer just enough to cover single sequence fee with data - let signed_tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let signed_tx = UnsignedTransaction::builder() + .actions(vec![ TransferAction { to: keypair_address, amount: fee, @@ -933,20 +994,21 @@ async fn app_stateful_check_fails_insufficient_total_balance() { fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&alice); - - // make transfer + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap() + .into_signed(&alice); app.execute_transaction(Arc::new(signed_tx)).await.unwrap(); // build double transfer exceeding balance - let signed_tx_fail = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let signed_tx_fail = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), data: data.clone(), @@ -959,10 +1021,16 @@ async fn app_stateful_check_fails_insufficient_total_balance() { fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&keypair); - + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap() + .into_signed(&keypair); // try double, see fails stateful check let res = signed_tx_fail .check_and_execute(Arc::get_mut(&mut app.state).unwrap()) @@ -973,21 +1041,24 @@ async fn app_stateful_check_fails_insufficient_total_balance() { assert!(res.contains("insufficient funds for asset")); // build single transfer to see passes - let signed_tx_pass = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + let signed_tx_pass = UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), data, fee_asset: nria().into(), } .into(), - ], - } - .into_signed(&keypair); + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap() + .into_signed(&keypair); signed_tx_pass .check_and_execute(Arc::get_mut(&mut app.state).unwrap()) @@ -1032,13 +1103,16 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { fee_asset: nria().into(), destination_chain_address: "nootwashere".to_string(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![action.into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); @@ -1056,13 +1130,16 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { rollup_withdrawal_event_id: "id-from-rollup".to_string(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![action.into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&bridge)); app.execute_transaction(signed_tx) @@ -1103,13 +1180,17 @@ async fn app_execute_transaction_action_index_correctly_increments() { fee_asset: nria().into(), destination_chain_address: "nootwashere".to_string(), }; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![action.clone().into(), action.into()], - }; + + let tx = UnsignedTransaction::builder() + .actions(vec![action.clone().into(), action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); app.execute_transaction(signed_tx.clone()).await.unwrap(); @@ -1139,12 +1220,9 @@ async fn transaction_execution_records_deposit_event() { state_tx .put_bridge_account_ibc_asset(bob_address, nria()) .unwrap(); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + + let tx = UnsignedTransaction::builder() + .actions(vec![ BridgeLockAction { to: bob_address, amount: 1, @@ -1153,9 +1231,15 @@ async fn transaction_execution_records_deposit_event() { destination_chain_address: "test_chain_address".to_string(), } .into(), - ], - }; - + ]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + ) + .build() + .unwrap(); let signed_tx = Arc::new(tx.into_signed(&alice)); let expected_deposit = Deposit::new( diff --git a/crates/astria-sequencer/src/benchmark_utils.rs b/crates/astria-sequencer/src/benchmark_utils.rs index 2e5cfe8225..4508e91017 100644 --- a/crates/astria-sequencer/src/benchmark_utils.rs +++ b/crates/astria-sequencer/src/benchmark_utils.rs @@ -94,11 +94,12 @@ fn sequence_actions() -> Vec> { 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); + let tx = UnsignedTransaction::builder() + .actions(vec![Action::Sequence(sequence_action)]) + .params(params) + .build() + .expect("failed to build transaction from actions") + .into_signed(signing_key); Arc::new(tx) }) .take(SEQUENCE_ACTION_TX_COUNT) @@ -121,13 +122,16 @@ fn transfers() -> Vec> { .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); + let tx = UnsignedTransaction::builder() + .actions( + std::iter::repeat(action.clone()) + .take(TRANSFERS_PER_TX) + .collect(), + ) + .params(params) + .build() + .expect("failed to build transaction from actions") + .into_signed(sender); Arc::new(tx) }) .collect() diff --git a/crates/astria-sequencer/src/proposal/commitment.rs b/crates/astria-sequencer/src/proposal/commitment.rs index 1578b47208..f07868e7a1 100644 --- a/crates/astria-sequencer/src/proposal/commitment.rs +++ b/crates/astria-sequencer/src/proposal/commitment.rs @@ -121,13 +121,17 @@ mod test { }; let signing_key = SigningKey::new(OsRng); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test-chain-1") - .build(), - actions: vec![sequence_action.clone().into(), transfer_action.into()], - }; + + let tx = UnsignedTransaction::builder() + .actions(vec![sequence_action.clone().into(), transfer_action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test-chain-1") + .build(), + ) + .build() + .unwrap(); let signed_tx = tx.into_signed(&signing_key); let txs = vec![signed_tx]; @@ -137,13 +141,16 @@ mod test { } = generate_rollup_datas_commitment(&txs, HashMap::new()); let signing_key = SigningKey::new(OsRng); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test-chain-1") - .build(), - actions: vec![sequence_action.into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![sequence_action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test-chain-1") + .build(), + ) + .build() + .unwrap(); let signed_tx = tx.into_signed(&signing_key); let txs = vec![signed_tx]; @@ -175,13 +182,16 @@ mod test { }; let signing_key = SigningKey::new(OsRng); - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test-chain-1") - .build(), - actions: vec![sequence_action.into(), transfer_action.into()], - }; + let tx = UnsignedTransaction::builder() + .actions(vec![sequence_action.clone().into(), transfer_action.into()]) + .params( + TransactionParams::builder() + .nonce(0) + .chain_id("test-chain-1") + .build(), + ) + .build() + .unwrap(); let signed_tx = tx.into_signed(&signing_key); let txs = vec![signed_tx]; diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index 4fd31287cb..3f7437d980 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -237,20 +237,23 @@ mod test { }; fn make_unsigned_tx() -> UnsignedTransaction { - UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ + UnsignedTransaction::builder() + .actions(vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), data: Bytes::from_static(b"hello world"), fee_asset: crate::test_utils::nria().into(), } .into(), - ], - } + ]) + .params( + TransactionParams::builder() + .chain_id("test") + .nonce(0) + .build(), + ) + .build() + .unwrap() } fn new_prepare_proposal_request() -> request::PrepareProposal { diff --git a/crates/astria-sequencer/src/transaction/checks.rs b/crates/astria-sequencer/src/transaction/checks.rs index 8eac5f5205..c2d723ed9c 100644 --- a/crates/astria-sequencer/src/transaction/checks.rs +++ b/crates/astria-sequencer/src/transaction/checks.rs @@ -91,7 +91,7 @@ pub(crate) async fn get_fees_for_transaction( .wrap_err("failed to get bridge sudo change fee")?; let mut fees_by_asset = HashMap::new(); - for (i, action) in tx.actions.iter().enumerate() { + for (i, action) in tx.actions().iter().enumerate() { match action { Action::Transfer(act) => { transfer_update_fees(&act.fee_asset, &mut fees_by_asset, transfer_fee); @@ -405,10 +405,11 @@ mod tests { .nonce(0) .chain_id("test-chain-id") .build(); - let tx = UnsignedTransaction { - actions, - params, - }; + let tx = UnsignedTransaction::builder() + .actions(actions) + .params(params) + .build() + .unwrap(); let signed_tx = tx.into_signed(&alice); check_balance_for_total_fees_and_transfers(&signed_tx, &state_tx) @@ -471,10 +472,11 @@ mod tests { .nonce(0) .chain_id("test-chain-id") .build(); - let tx = UnsignedTransaction { - actions, - params, - }; + let tx = UnsignedTransaction::builder() + .actions(actions) + .params(params) + .build() + .unwrap(); let signed_tx = tx.into_signed(&alice); let err = check_balance_for_total_fees_and_transfers(&signed_tx, &state_tx)