Skip to content

Commit

Permalink
add builder
Browse files Browse the repository at this point in the history
  • Loading branch information
Lilyjjo committed Sep 17, 2024
1 parent 6b5dae9 commit c12d6e6
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
19 changes: 11 additions & 8 deletions crates/astria-core/src/protocol/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RollupId, Vec<Deposit>> = HashMap::new();
for deposit in deposits {
if let Some(entry) = deposits_map.get_mut(deposit.rollup_id()) {
Expand Down
230 changes: 230 additions & 0 deletions crates/astria-core/src/protocol/transaction/v1alpha1/action_groups.rs
Original file line number Diff line number Diff line change
@@ -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(crate) struct BundlableGeneral;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct General;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct BundlableSudo;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Sudo;

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ActionGroup {
BundlableGeneral(BundlableGeneral),
General(General),
BundlableSudo(BundlableSudo),
Sudo(Sudo),
}

impl From<BundlableGeneral> for ActionGroup {
fn from(val: BundlableGeneral) -> ActionGroup {
ActionGroup::BundlableGeneral(val)
}
}

impl From<General> for ActionGroup {
fn from(val: General) -> ActionGroup {
ActionGroup::General(val)
}
}

impl From<BundlableSudo> for ActionGroup {
fn from(val: BundlableSudo) -> ActionGroup {
ActionGroup::BundlableSudo(val)
}
}

impl From<Sudo> 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<ActionGroup>,
inner: Vec<Action>,
}

impl Actions {
pub(super) fn actions(&self) -> &[Action] {
&self.inner
}

#[must_use]
pub(super) fn into_actions(self) -> Vec<Action> {
self.inner
}

pub(super) fn group(&self) -> &Option<ActionGroup> {
&self.group
}

pub(super) fn from_list_of_actions(actions: Vec<Action>) -> Result<Self, ActionGroupError> {
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,
})
}
}
Loading

0 comments on commit c12d6e6

Please sign in to comment.