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"
}