diff --git a/crates/astria-sequencer/src/accounts/component.rs b/crates/astria-sequencer/src/accounts/component.rs index 3bb0af3556..dfa1917d66 100644 --- a/crates/astria-sequencer/src/accounts/component.rs +++ b/crates/astria-sequencer/src/accounts/component.rs @@ -32,6 +32,9 @@ impl Component for AccountsComponent { .put_account_balance(account.address, native_asset.id(), account.balance) .context("failed writing account balance to state")?; } + state + .put_ibc_sudo_address(app_state.ibc_sudo_address) + .context("failed to set IBC sudo key")?; Ok(()) } diff --git a/crates/astria-sequencer/src/accounts/state_ext.rs b/crates/astria-sequencer/src/accounts/state_ext.rs index ca74bb9460..80c8288c12 100644 --- a/crates/astria-sequencer/src/accounts/state_ext.rs +++ b/crates/astria-sequencer/src/accounts/state_ext.rs @@ -6,6 +6,7 @@ use astria_core::sequencer::v1alpha1::{ account::AssetBalance, asset, Address, + ADDRESS_LEN, }; use async_trait::async_trait; use borsh::{ @@ -32,8 +33,14 @@ struct Nonce(u32); #[derive(BorshSerialize, BorshDeserialize, Debug)] struct Balance(u128); +/// Newtype wrapper to read and write an address from rocksdb. +#[derive(BorshSerialize, BorshDeserialize, Debug)] +struct SudoAddress([u8; ADDRESS_LEN]); + const ACCOUNTS_PREFIX: &str = "accounts"; +const IBC_SUDO_STORAGE_KEY: &str = "ibcsudo"; + fn storage_key(address: &str) -> String { format!("{ACCOUNTS_PREFIX}/{address}") } @@ -151,6 +158,21 @@ pub(crate) trait StateReadExt: StateRead { let Balance(balance) = Balance::try_from_slice(&bytes).context("invalid balance bytes")?; Ok(balance) } + + #[instrument(skip(self))] + async fn get_ibc_sudo_address(&self) -> Result
{ + let Some(bytes) = self + .get_raw(IBC_SUDO_STORAGE_KEY) + .await + .context("failed reading raw ibc sudo key from state")? + else { + // ibc sudo key must be set + return Err(anyhow::anyhow!("ibc sudo key not found")); + }; + let SudoAddress(address) = + SudoAddress::try_from_slice(&bytes).context("invalid ibc sudo key bytes")?; + Ok(Address(address)) + } } impl StateReadExt for T {} @@ -193,6 +215,17 @@ pub(crate) trait StateWriteExt: StateWrite { self.put_raw(channel_balance_storage_key(channel, asset), bytes); Ok(()) } + + #[instrument(skip(self))] + fn put_ibc_sudo_address(&mut self, address: Address) -> Result<()> { + self.put_raw( + IBC_SUDO_STORAGE_KEY.to_string(), + SudoAddress(address.0) + .try_to_vec() + .context("failed to convert sudo address to vec")?, + ); + Ok(()) + } } impl StateWriteExt for T {} diff --git a/crates/astria-sequencer/src/app.rs b/crates/astria-sequencer/src/app.rs index 8efaf5690a..98c831b17f 100644 --- a/crates/astria-sequencer/src/app.rs +++ b/crates/astria-sequencer/src/app.rs @@ -178,7 +178,7 @@ impl App { AuthorityComponent::init_chain( &mut state_tx, &AuthorityComponentAppState { - authority_sudo_key: genesis_state.authority_sudo_key, + authority_sudo_address: genesis_state.authority_sudo_address, genesis_validators, }, ) @@ -772,7 +772,8 @@ mod test { let genesis_state = genesis_state.unwrap_or_else(|| GenesisState { accounts: default_genesis_accounts(), - authority_sudo_key: Address::from([0; 20]), + authority_sudo_address: Address::from([0; 20]), + ibc_sudo_address: Address::from([0; 20]), native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), }); @@ -1097,7 +1098,8 @@ mod test { let genesis_state = GenesisState { accounts: default_genesis_accounts(), - authority_sudo_key: alice_address, + authority_sudo_address: alice_address, + ibc_sudo_address: alice_address, native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), }; let mut app = initialize_app(Some(genesis_state), vec![]).await; @@ -1128,7 +1130,8 @@ mod test { let genesis_state = GenesisState { accounts: default_genesis_accounts(), - authority_sudo_key: alice_address, + authority_sudo_address: alice_address, + ibc_sudo_address: alice_address, native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), }; let mut app = initialize_app(Some(genesis_state), vec![]).await; @@ -1157,7 +1160,8 @@ mod test { let genesis_state = GenesisState { accounts: default_genesis_accounts(), - authority_sudo_key: sudo_address, + authority_sudo_address: sudo_address, + ibc_sudo_address: [0u8; 20].into(), native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), }; let mut app = initialize_app(Some(genesis_state), vec![]).await; @@ -1186,7 +1190,8 @@ mod test { let genesis_state = GenesisState { accounts: default_genesis_accounts(), - authority_sudo_key: alice_address, + authority_sudo_address: alice_address, + ibc_sudo_address: [0u8; 20].into(), native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), }; let mut app = initialize_app(Some(genesis_state), vec![]).await; @@ -1332,7 +1337,8 @@ mod test { async fn app_commit() { let genesis_state = GenesisState { accounts: default_genesis_accounts(), - authority_sudo_key: Address::from([0; 20]), + authority_sudo_address: Address::from([0; 20]), + ibc_sudo_address: Address::from([0; 20]), native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), }; diff --git a/crates/astria-sequencer/src/authority/component.rs b/crates/astria-sequencer/src/authority/component.rs index 2185896e92..c7a5a9c8c7 100644 --- a/crates/astria-sequencer/src/authority/component.rs +++ b/crates/astria-sequencer/src/authority/component.rs @@ -26,7 +26,7 @@ pub(crate) struct AuthorityComponent; #[derive(Debug)] pub(crate) struct AuthorityComponentAppState { - pub(crate) authority_sudo_key: Address, + pub(crate) authority_sudo_address: Address, pub(crate) genesis_validators: Vec, } @@ -38,7 +38,7 @@ impl Component for AuthorityComponent { async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> { // set sudo key and initial validator set state - .put_sudo_address(app_state.authority_sudo_key) + .put_sudo_address(app_state.authority_sudo_address) .context("failed to set sudo key")?; state .put_validator_set(ValidatorSet::new_from_updates( diff --git a/crates/astria-sequencer/src/genesis.rs b/crates/astria-sequencer/src/genesis.rs index f24bd2364c..654273af2e 100644 --- a/crates/astria-sequencer/src/genesis.rs +++ b/crates/astria-sequencer/src/genesis.rs @@ -9,7 +9,9 @@ use serde::{ pub(crate) struct GenesisState { pub(crate) accounts: Vec, #[serde(deserialize_with = "deserialize_address")] - pub(crate) authority_sudo_key: Address, + pub(crate) authority_sudo_address: Address, + #[serde(deserialize_with = "deserialize_address")] + pub(crate) ibc_sudo_address: Address, pub(crate) native_asset_base_denomination: String, } diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index a90698d1ae..c1935f4047 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -502,7 +502,8 @@ mod test { fn default() -> Self { Self { accounts: vec![], - authority_sudo_key: Address::from([0; 20]), + authority_sudo_address: Address::from([0; 20]), + ibc_sudo_address: Address::from([0; 20]), native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), } } diff --git a/crates/astria-sequencer/src/transaction/mod.rs b/crates/astria-sequencer/src/transaction/mod.rs index 7d37e49cc0..9f6ed7e71b 100644 --- a/crates/astria-sequencer/src/transaction/mod.rs +++ b/crates/astria-sequencer/src/transaction/mod.rs @@ -161,7 +161,14 @@ impl ActionHandler for UnsignedTransaction { .await .context("stateful check failed for SudoAddressChangeAction")?, Action::Ibc(_) => { - // no-op; IBC actions merge check_stateful and execute. + let ibc_sudo_address = state + .get_ibc_sudo_address() + .await + .context("failed to get IBC sudo address")?; + ensure!( + from == ibc_sudo_address, + "only IBC sudo address can execute IBC actions" + ); } Action::Ics20Withdrawal(act) => act .check_stateful(state, from) diff --git a/crates/astria-sequencer/test-genesis-app-state.json b/crates/astria-sequencer/test-genesis-app-state.json index fb22c4a93b..0f2fffb80c 100644 --- a/crates/astria-sequencer/test-genesis-app-state.json +++ b/crates/astria-sequencer/test-genesis-app-state.json @@ -13,6 +13,7 @@ "balance": 1000000000000000000 } ], - "authority_sudo_key": "1c0c490f1b5528d8173c5de46d131160e4b2c0c3", + "authority_sudo_address": "1c0c490f1b5528d8173c5de46d131160e4b2c0c3", + "ibc_sudo_address": "1c0c490f1b5528d8173c5de46d131160e4b2c0c3", "native_asset_base_denomination": "nria" }