diff --git a/Cargo.lock b/Cargo.lock index 09f047fd84..c6d6a525ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,6 +793,7 @@ dependencies = [ "sha2 0.10.8", "tendermint", "tendermint-proto", + "thiserror", "tokio", "tonic 0.10.2", "tower", diff --git a/charts/evm-rollup/Chart.yaml b/charts/evm-rollup/Chart.yaml index 1d724302e9..d977b65fe9 100644 --- a/charts/evm-rollup/Chart.yaml +++ b/charts/evm-rollup/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.19.2 +version: 0.20.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/evm-rollup/templates/configmap.yaml b/charts/evm-rollup/templates/configmap.yaml index 8ff1db99ff..a54405b7df 100644 --- a/charts/evm-rollup/templates/configmap.yaml +++ b/charts/evm-rollup/templates/configmap.yaml @@ -67,6 +67,7 @@ data: OTEL_SERVICE_NAME: "{{ tpl .Values.config.rollup.otel.serviceNamePrefix . }}-composer" {{- if not .Values.global.dev }} {{- else }} + ASTRIA_COMPOSER_SEQUENCER_ADDRESS_PREFIX: "{{ .Values.config.sequencer.addressPrefixes.base }}" {{- end }} --- {{- if .Values.config.faucet.enabled }} diff --git a/charts/evm-rollup/values.yaml b/charts/evm-rollup/values.yaml index 5e550e2ac6..5972209a0c 100644 --- a/charts/evm-rollup/values.yaml +++ b/charts/evm-rollup/values.yaml @@ -168,6 +168,8 @@ config: # Configuration related to sequencer connection for rollup sequencer: + addressPrefixes: + base: "astria" chainId: "" # Block height to start syncing rollup from initialBlockHeight: "2" diff --git a/charts/sequencer/Chart.yaml b/charts/sequencer/Chart.yaml index 5768d10122..d65d5b1493 100644 --- a/charts/sequencer/Chart.yaml +++ b/charts/sequencer/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.15.8 +version: 0.16.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/sequencer/files/cometbft/config/genesis.json b/charts/sequencer/files/cometbft/config/genesis.json index 4474934237..f5496991d0 100644 --- a/charts/sequencer/files/cometbft/config/genesis.json +++ b/charts/sequencer/files/cometbft/config/genesis.json @@ -1,16 +1,6 @@ { "app_hash": "", "app_state": { - "accounts": [ - {{- range $index, $value := .Values.config.sequencer.genesisAccounts }} - {{- if $index }},{{- end }} - { - "address": "{{ $value.address }}", - "balance": {{ toString $value.balance | replace "\"" "" }} - } - {{- end }} - ], - "authority_sudo_address": "{{ .Values.config.sequencer.authoritySudoAddress }}", "native_asset_base_denomination": "{{ .Values.config.sequencer.nativeAssetBaseDenomination }}", "fees": { "transfer_base_fee": 12, @@ -27,20 +17,50 @@ "{{ $value }}" {{- end }} ], - "ibc_sudo_address": "{{ .Values.config.sequencer.ibc.sudoAddress }}", - "ibc_relayer_addresses": [ - {{- range $index, $value := .Values.config.sequencer.ibc.relayerAddresses }} - {{- if $index }},{{- end }} - "{{ $value }}" - {{- end }} - ], "ibc_params": { "ibc_enabled": {{ .Values.config.sequencer.ibc.enabled }}, "inbound_ics20_transfers_enabled": {{ .Values.config.sequencer.ibc.inboundEnabled }}, "outbound_ics20_transfers_enabled": {{ .Values.config.sequencer.ibc.outboundEnabled }} - } + }, {{- if not .Values.global.dev }} + "accounts": [ + {{- range $index, $value := .Values.config.sequencer.genesisAccounts }} + {{- if $index }},{{- end }} + { + "address": "{{ $value.address }}", + "balance": {{ toString $value.balance | replace "\"" "" }} + } + {{- end }} + ], + "authority_sudo_address": "{{ .Values.config.sequencer.authoritySudoAddress }}", + "ibc_sudo_address": "{{ .Values.config.sequencer.ibc.sudoAddress }}", + "ibc_relayer_addresses": [ + {{- range $index, $value := .Values.config.sequencer.ibc.relayerAddresses }} + {{- if $index }},{{- end }} + "{{ $value }}" + {{- end }} + ] {{- else }} + "address_prefixes": { + "base": "{{ .Values.config.sequencer.addressPrefixes.base }}" + }, + "accounts": [ + {{- range $index, $value := .Values.config.sequencer.genesisAccounts }} + {{- if $index }},{{- end }} + { + "address": {{ include "sequencer.address" $value.address }}, + "balance": {{ toString $value.balance | replace "\"" "" }} + } + {{- end }} + ], + "authority_sudo_address": {{ include "sequencer.address" .Values.config.sequencer.authoritySudoAddress }}, + "ibc_sudo_address": {{ include "sequencer.address" .Values.config.sequencer.ibc.sudoAddress }}, + "ibc_relayer_addresses": [ + {{- range $index, $value := .Values.config.sequencer.ibc.relayerAddresses }} + {{- if $index }},{{- end }} + {{ include "sequencer.address" $value }} + {{- end }} + ] {{- end}} }, "chain_id": "{{ .Values.config.cometBFT.chainId }}", diff --git a/charts/sequencer/templates/_helpers.tpl b/charts/sequencer/templates/_helpers.tpl index c0dcb1fd2d..d0eae4a6b7 100644 --- a/charts/sequencer/templates/_helpers.tpl +++ b/charts/sequencer/templates/_helpers.tpl @@ -67,3 +67,7 @@ Selector labels app: {{ include "sequencer.name" . }} name: {{ .Values.config.moniker }}-sequencer-metrics {{- end }} + +{{/* New sequencer address */}} +{{- define "sequencer.address"}}{ "bech32m": "{{ . }}" } +{{- end }} diff --git a/charts/sequencer/values.yaml b/charts/sequencer/values.yaml index d25c6a0ad3..4e024491ba 100644 --- a/charts/sequencer/values.yaml +++ b/charts/sequencer/values.yaml @@ -23,6 +23,8 @@ images: config: moniker: "node" sequencer: + addressPrefixes: + base: "astria" authoritySudoAddress: 1c0c490f1b5528d8173c5de46d131160e4b2c0c3 nativeAssetBaseDenomination: nria allowedFeeAssets: @@ -37,15 +39,15 @@ config: - 34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a # Note large balances must be strings support templating with the u128 size account balances genesisAccounts: - - address: 1c0c490f1b5528d8173c5de46d131160e4b2c0c3 - balance: "333333333333333333" - - address: 34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a - balance: "333333333333333333" - - address: 60709e2d391864b732b4f0f51e387abb76743871 - balance: "333333333333333333" - # NOTE - the following address matches the privKey that funds the sequencer-faucet - - address: 00d75b270542084a54fcf0d0f6eab0402982d156 - balance: "333333333333333333" + - address: 1c0c490f1b5528d8173c5de46d131160e4b2c0c3 + balance: "333333333333333333" + - address: 34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a + balance: "333333333333333333" + - address: 60709e2d391864b732b4f0f51e387abb76743871 + balance: "333333333333333333" + # NOTE - the following address matches the privKey that funds the sequencer-faucet + - address: 00d75b270542084a54fcf0d0f6eab0402982d156 + balance: "333333333333333333" metrics: enabled: false diff --git a/crates/astria-bridge-withdrawer/local.env.example b/crates/astria-bridge-withdrawer/local.env.example index a2e66c0c86..b01f3ce07a 100644 --- a/crates/astria-bridge-withdrawer/local.env.example +++ b/crates/astria-bridge-withdrawer/local.env.example @@ -31,6 +31,9 @@ ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_CHAIN_ID="astria" # transactions. The file should contain a hex-encoded Ed25519 secret key. ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_KEY_PATH=/path/to/priv_sequencer_key.json +# The prefix that will be used to construct bech32m sequencer addresses. +ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_ADDRESS_PREFIX=astria + # The fee asset denomination to use for the bridge account's transactions. ASTRIA_BRIDGE_WITHDRAWER_FEE_ASSET_DENOMINATION="nria" diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/convert.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/convert.rs index 7507ec2e27..87c2d67560 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/convert.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/convert.rs @@ -9,7 +9,6 @@ use astria_core::{ Denom, }, Address, - ASTRIA_ADDRESS_PREFIX, }, protocol::transaction::v1alpha1::{ action::{ @@ -60,6 +59,7 @@ pub(crate) fn event_to_action( rollup_asset_denom: Denom, asset_withdrawal_divisor: u128, bridge_address: Address, + sequencer_address_prefix: &str, ) -> eyre::Result { let action = match event_with_metadata.event { WithdrawalEvent::Sequencer(event) => event_to_bridge_unlock( @@ -78,6 +78,7 @@ pub(crate) fn event_to_action( rollup_asset_denom, asset_withdrawal_divisor, bridge_address, + sequencer_address_prefix, ) .wrap_err("failed to convert ics20 withdrawal event to action")?, }; @@ -102,8 +103,10 @@ fn event_to_bridge_unlock( transaction_hash, }; let action = BridgeUnlockAction { - to: Address::try_from_bech32m(&event.destination_chain_address) - .wrap_err("failed to parse destination chain address as bech32m")?, + to: event + .destination_chain_address + .parse() + .wrap_err("failed to parse destination chain address")?, amount: event .amount .as_u128() @@ -119,6 +122,8 @@ fn event_to_bridge_unlock( Ok(Action::BridgeUnlock(action)) } +// FIXME: Get this to work for now, but replace this with a builder. +#[allow(clippy::too_many_arguments)] fn event_to_ics20_withdrawal( event: Ics20WithdrawalFilter, block_number: U64, @@ -127,6 +132,7 @@ fn event_to_ics20_withdrawal( rollup_asset_denom: Denom, asset_withdrawal_divisor: u128, bridge_address: Address, + sequencer_address_prefix: &str, ) -> eyre::Result { // TODO: make this configurable const ICS20_WITHDRAWAL_TIMEOUT: Duration = Duration::from_secs(300); @@ -155,7 +161,7 @@ fn event_to_ics20_withdrawal( // bytes, but this won't work otherwise. return_address: Address::builder() .array(sender) - .prefix(ASTRIA_ADDRESS_PREFIX) + .prefix(sequencer_address_prefix) .try_build() .wrap_err("failed to construct return address")?, amount: event @@ -204,12 +210,7 @@ mod tests { event: WithdrawalEvent::Sequencer(SequencerWithdrawalFilter { sender: [0u8; 20].into(), amount: 99.into(), - destination_chain_address: Address::builder() - .array([1u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap() - .to_string(), + destination_chain_address: crate::astria_address([1u8; 20]).to_string(), }), block_number: 1.into(), transaction_hash: [2u8; 32].into(), @@ -220,6 +221,7 @@ mod tests { denom.clone(), 1, crate::astria_address([99u8; 20]), + crate::ASTRIA_ADDRESS_PREFIX, ) .unwrap(); let Action::BridgeUnlock(action) = action else { @@ -248,12 +250,7 @@ mod tests { event: WithdrawalEvent::Sequencer(SequencerWithdrawalFilter { sender: [0u8; 20].into(), amount: 990.into(), - destination_chain_address: Address::builder() - .array([1u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap() - .to_string(), + destination_chain_address: crate::astria_address([1u8; 20]).to_string(), }), block_number: 1.into(), transaction_hash: [2u8; 32].into(), @@ -265,6 +262,7 @@ mod tests { denom.clone(), divisor, crate::astria_address([99u8; 20]), + crate::ASTRIA_ADDRESS_PREFIX, ) .unwrap(); let Action::BridgeUnlock(action) = action else { @@ -289,7 +287,7 @@ mod tests { #[test] fn event_to_ics20_withdrawal() { let denom = "transfer/channel-0/utia".parse::().unwrap(); - let destination_chain_address = "address".to_string(); + let destination_chain_address = crate::astria_address([1u8; 20]).to_string(); let event_with_meta = EventWithMetadata { event: WithdrawalEvent::Ics20(Ics20WithdrawalFilter { sender: [0u8; 20].into(), @@ -308,6 +306,7 @@ mod tests { denom.clone(), 1, bridge_address, + crate::ASTRIA_ADDRESS_PREFIX, ) .unwrap(); let Action::Ics20Withdrawal(mut action) = action else { diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/test_utils.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/test_utils.rs index 1e1c3c1962..b916d86f0b 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/test_utils.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/test_utils.rs @@ -3,7 +3,6 @@ use std::{ time::Duration, }; -use astria_core::primitive::v1::ASTRIA_ADDRESS_PREFIX; use ethers::{ abi::Tokenizable, core::utils::Anvil, @@ -33,11 +32,7 @@ impl Default for ConfigureAstriaWithdrawerDeployer { fn default() -> Self { Self { base_chain_asset_precision: 18, - base_chain_bridge_address: astria_core::primitive::v1::Address::builder() - .array([0u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(), + base_chain_bridge_address: crate::astria_address([0u8; 20]), base_chain_asset_denomination: "test-denom".to_string(), } } @@ -122,11 +117,7 @@ impl Default for ConfigureAstriaBridgeableERC20Deployer { Self { bridge_address: Address::zero(), base_chain_asset_precision: 18, - base_chain_bridge_address: astria_core::primitive::v1::Address::builder() - .array([0u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(), + base_chain_bridge_address: crate::astria_address([0u8; 20]), base_chain_asset_denomination: "testdenom".to_string(), name: "test-token".to_string(), symbol: "TT".to_string(), diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/watcher.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/watcher.rs index 812bec4e32..41abd0939e 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/watcher.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/ethereum/watcher.rs @@ -65,6 +65,7 @@ pub(crate) struct Builder { pub(crate) state: Arc, pub(crate) rollup_asset_denom: Denom, pub(crate) bridge_address: Address, + pub(crate) sequencer_address_prefix: String, } impl Builder { @@ -77,6 +78,7 @@ impl Builder { state, rollup_asset_denom, bridge_address, + sequencer_address_prefix, } = self; let contract_address = address_from_string(ðereum_contract_address) @@ -100,6 +102,7 @@ impl Builder { bridge_address, state, shutdown_token: shutdown_token.clone(), + sequencer_address_prefix, }) } } @@ -113,6 +116,7 @@ pub(crate) struct Watcher { bridge_address: Address, state: Arc, shutdown_token: CancellationToken, + sequencer_address_prefix: String, } impl Watcher { @@ -130,6 +134,7 @@ impl Watcher { bridge_address, state, shutdown_token, + sequencer_address_prefix, } = self; let (event_tx, event_rx) = mpsc::channel(100); @@ -143,6 +148,7 @@ impl Watcher { rollup_asset_denom, bridge_address, asset_withdrawal_divisor, + sequencer_address_prefix, }; tokio::task::spawn(batcher.run()); @@ -332,6 +338,7 @@ struct Batcher { rollup_asset_denom: Denom, bridge_address: Address, asset_withdrawal_divisor: u128, + sequencer_address_prefix: String, } impl Batcher { @@ -385,7 +392,14 @@ impl Batcher { block_number: meta.block_number, transaction_hash: meta.transaction_hash, }; - let action = event_to_action(event_with_metadata, self.fee_asset_id, self.rollup_asset_denom.clone(), self.asset_withdrawal_divisor, self.bridge_address).wrap_err("failed to convert event to action")?; + let action = event_to_action( + event_with_metadata, + self.fee_asset_id, + self.rollup_asset_denom.clone(), + self.asset_withdrawal_divisor, + self.bridge_address, + &self.sequencer_address_prefix, + ).wrap_err("failed to convert event to action")?; if meta.block_number.as_u64() == curr_batch.rollup_height { // block number was the same; add event to current batch @@ -433,10 +447,7 @@ fn address_from_string(s: &str) -> Result { mod tests { use asset::default_native_asset; use astria_core::{ - primitive::v1::{ - Address, - ASTRIA_ADDRESS_PREFIX, - }, + primitive::v1::Address, protocol::transaction::v1alpha1::Action, }; use ethers::{ @@ -523,11 +534,7 @@ mod tests { let contract = AstriaWithdrawer::new(contract_address, signer.clone()); let value: U256 = 999.into(); // 10^3 - 1 - let recipient = Address::builder() - .array([1u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(); + let recipient = crate::astria_address([1u8; 20]); let tx = contract .withdraw_to_sequencer(recipient.to_string()) .value(value); @@ -545,11 +552,7 @@ mod tests { let contract = AstriaWithdrawer::new(contract_address, signer.clone()); let value = 1_000_000_000.into(); - let recipient = Address::builder() - .array([1u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(); + let recipient = crate::astria_address([1u8; 20]); let receipt = send_sequencer_withdraw_transaction(&contract, value, recipient).await; let expected_event = EventWithMetadata { event: WithdrawalEvent::Sequencer(SequencerWithdrawalFilter { @@ -562,8 +565,15 @@ mod tests { }; let bridge_address = crate::astria_address([1u8; 20]); let denom = default_native_asset(); - let expected_action = - event_to_action(expected_event, denom.id(), denom.clone(), 1, bridge_address).unwrap(); + let expected_action = event_to_action( + expected_event, + denom.id(), + denom.clone(), + 1, + bridge_address, + crate::ASTRIA_ADDRESS_PREFIX, + ) + .unwrap(); let Action::BridgeUnlock(expected_action) = expected_action else { panic!("expected action to be BridgeUnlock, got {expected_action:?}"); }; @@ -586,6 +596,7 @@ mod tests { state: Arc::new(State::new()), rollup_asset_denom: denom, bridge_address, + sequencer_address_prefix: crate::ASTRIA_ADDRESS_PREFIX.into(), } .build() .unwrap(); @@ -653,9 +664,15 @@ mod tests { }; let bridge_address = crate::astria_address([1u8; 20]); let denom = "transfer/channel-0/utia".parse::().unwrap(); - let Action::Ics20Withdrawal(mut expected_action) = - event_to_action(expected_event, denom.id(), denom.clone(), 1, bridge_address).unwrap() - else { + let Action::Ics20Withdrawal(mut expected_action) = event_to_action( + expected_event, + denom.id(), + denom.clone(), + 1, + bridge_address, + crate::ASTRIA_ADDRESS_PREFIX, + ) + .unwrap() else { panic!("expected action to be Ics20Withdrawal"); }; expected_action.timeout_time = 0; // zero this for testing @@ -678,6 +695,7 @@ mod tests { state: Arc::new(State::new()), rollup_asset_denom: denom, bridge_address, + sequencer_address_prefix: crate::ASTRIA_ADDRESS_PREFIX.into(), } .build() .unwrap(); @@ -759,11 +777,7 @@ mod tests { mint_tokens(&contract, 2_000_000_000.into(), wallet.address()).await; let value = 1_000_000_000.into(); - let recipient = Address::builder() - .array([1u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(); + let recipient = crate::astria_address([1u8; 20]); let receipt = send_sequencer_withdraw_transaction_erc20(&contract, value, recipient).await; let expected_event = EventWithMetadata { event: WithdrawalEvent::Sequencer(SequencerWithdrawalFilter { @@ -776,8 +790,15 @@ mod tests { }; let denom = default_native_asset(); let bridge_address = crate::astria_address([1u8; 20]); - let expected_action = - event_to_action(expected_event, denom.id(), denom.clone(), 1, bridge_address).unwrap(); + let expected_action = event_to_action( + expected_event, + denom.id(), + denom.clone(), + 1, + bridge_address, + crate::ASTRIA_ADDRESS_PREFIX, + ) + .unwrap(); let Action::BridgeUnlock(expected_action) = expected_action else { panic!("expected action to be BridgeUnlock, got {expected_action:?}"); }; @@ -800,6 +821,7 @@ mod tests { state: Arc::new(State::new()), rollup_asset_denom: denom, bridge_address, + sequencer_address_prefix: crate::ASTRIA_ADDRESS_PREFIX.into(), } .build() .unwrap(); @@ -877,9 +899,15 @@ mod tests { }; let denom = "transfer/channel-0/utia".parse::().unwrap(); let bridge_address = crate::astria_address([1u8; 20]); - let Action::Ics20Withdrawal(mut expected_action) = - event_to_action(expected_event, denom.id(), denom.clone(), 1, bridge_address).unwrap() - else { + let Action::Ics20Withdrawal(mut expected_action) = event_to_action( + expected_event, + denom.id(), + denom.clone(), + 1, + bridge_address, + crate::ASTRIA_ADDRESS_PREFIX, + ) + .unwrap() else { panic!("expected action to be Ics20Withdrawal"); }; expected_action.timeout_time = 0; // zero this for testing @@ -902,6 +930,7 @@ mod tests { state: Arc::new(State::new()), rollup_asset_denom: denom, bridge_address, + sequencer_address_prefix: crate::ASTRIA_ADDRESS_PREFIX.into(), } .build() .unwrap(); diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs index 795eefda32..c9a0d2a3da 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs @@ -7,12 +7,9 @@ use std::{ time::Duration, }; -use astria_core::primitive::v1::{ - asset::{ - self, - Denom, - }, - Address, +use astria_core::primitive::v1::asset::{ + self, + Denom, }; use astria_eyre::eyre::{ self, @@ -75,6 +72,7 @@ impl BridgeWithdrawer { sequencer_cometbft_endpoint, sequencer_chain_id, sequencer_key_path, + sequencer_address_prefix, fee_asset_denomination, ethereum_contract_address, ethereum_rpc_endpoint, @@ -91,6 +89,7 @@ impl BridgeWithdrawer { sequencer_cometbft_endpoint, sequencer_chain_id, sequencer_key_path, + sequencer_address_prefix: sequencer_address_prefix.clone(), state: state.clone(), expected_fee_asset_id: asset::Id::from_str_unchecked(&fee_asset_denomination), min_expected_fee_asset_balance: u128::from(min_expected_fee_asset_balance), @@ -99,7 +98,9 @@ impl BridgeWithdrawer { .build() .wrap_err("failed to initialize submitter")?; - let sequencer_bridge_address = Address::try_from_bech32m(&cfg.sequencer_bridge_address) + let sequencer_bridge_address = cfg + .sequencer_bridge_address + .parse() .wrap_err("failed to parse sequencer bridge address")?; let ethereum_watcher = watcher::Builder { @@ -112,6 +113,7 @@ impl BridgeWithdrawer { .parse::() .wrap_err("failed to parse ROLLUP_ASSET_DENOMINATION as Denom")?, bridge_address: sequencer_bridge_address, + sequencer_address_prefix: sequencer_address_prefix.clone(), } .build() .wrap_err("failed to build ethereum watcher")?; @@ -358,11 +360,15 @@ pub(crate) fn flatten_result(res: Result, JoinError>) -> eyre } } +#[cfg(test)] +pub(crate) const ASTRIA_ADDRESS_PREFIX: &str = "astria"; + /// Constructs an [`Address`] prefixed by `"astria"`. #[cfg(test)] -pub(crate) fn astria_address(array: [u8; astria_core::primitive::v1::ADDRESS_LEN]) -> Address { - use astria_core::primitive::v1::ASTRIA_ADDRESS_PREFIX; - Address::builder() +pub(crate) fn astria_address( + array: [u8; astria_core::primitive::v1::ADDRESS_LEN], +) -> astria_core::primitive::v1::Address { + astria_core::primitive::v1::Address::builder() .array(array) .prefix(ASTRIA_ADDRESS_PREFIX) .try_build() diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs index 782f9c4362..8261a5f20c 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs @@ -58,6 +58,7 @@ impl Handle { pub(crate) struct Builder { pub(crate) shutdown_token: CancellationToken, pub(crate) sequencer_key_path: String, + pub(crate) sequencer_address_prefix: String, pub(crate) sequencer_chain_id: String, pub(crate) sequencer_cometbft_endpoint: String, pub(crate) state: Arc, @@ -72,6 +73,7 @@ impl Builder { let Self { shutdown_token, sequencer_key_path, + sequencer_address_prefix, sequencer_chain_id, sequencer_cometbft_endpoint, state, @@ -80,9 +82,12 @@ impl Builder { metrics, } = self; - let signer = super::signer::SequencerKey::try_from_path(sequencer_key_path) - .wrap_err("failed to load sequencer private ky")?; - info!(address = %telemetry::display::hex(&signer.address), "loaded sequencer signer"); + let signer = super::signer::SequencerKey::builder() + .path(sequencer_key_path) + .prefix(sequencer_address_prefix) + .try_build() + .wrap_err("failed to load sequencer private key")?; + info!(address = %signer.address(), "loaded sequencer signer"); let sequencer_cometbft_client = sequencer_client::HttpClient::new(&*sequencer_cometbft_endpoint) 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 b1e6fdc3d1..32206d4517 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs @@ -200,7 +200,7 @@ impl Submitter { let fee_asset_balances = get_latest_balance( self.sequencer_cometbft_client.clone(), self.state.clone(), - self.signer.address, + *self.signer.address(), ) .await .wrap_err("failed to get latest balance")?; @@ -270,7 +270,7 @@ impl Submitter { let last_transaction_hash_resp = get_bridge_account_last_transaction_hash( self.sequencer_cometbft_client.clone(), self.state.clone(), - self.signer.address, + *self.signer.address(), ) .await .wrap_err("failed to fetch last transaction hash by the bridge account")?; @@ -329,7 +329,7 @@ async fn process_batch( // get nonce and make unsigned transaction let nonce = get_latest_nonce( sequencer_cometbft_client.clone(), - sequencer_key.address, + *sequencer_key.address(), state.clone(), metrics, ) @@ -342,15 +342,11 @@ async fn process_batch( params: TransactionParams::builder() .nonce(nonce) .chain_id(sequencer_chain_id) - .try_build() - .context( - "failed to construct transcation parameters from latest nonce and configured \ - sequencer chain ID", - )?, + .build(), }; // sign transaction - let signed = unsigned.into_signed(&sequencer_key.signing_key); + let signed = unsigned.into_signed(sequencer_key.signing_key()); debug!(tx_hash = %telemetry::display::hex(&signed.sha256_of_proto_encoding()), "signed transaction"); // submit transaction and handle response diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/signer.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/signer.rs index ce4cbe1dbd..d5a0c8be23 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/signer.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/signer.rs @@ -1,55 +1,107 @@ use std::{ fs, - path::Path, + path::{ + Path, + PathBuf, + }, }; use astria_core::{ crypto::SigningKey, - primitive::v1::{ - Address, - ASTRIA_ADDRESS_PREFIX, - }, + primitive::v1::Address, }; use astria_eyre::eyre::{ self, + bail, eyre, Context, }; pub(crate) struct SequencerKey { - pub(crate) address: Address, - pub(crate) signing_key: SigningKey, + address: Address, + signing_key: SigningKey, } -impl SequencerKey { - /// Construct a `SequencerKey` from a file. +pub(crate) struct SequencerKeyBuilder { + path: Option, + prefix: Option, +} + +impl SequencerKeyBuilder { + /// Sets the path from which the sequencey key is read. /// - /// The file should contain a hex-encoded ed25519 secret key. - pub(crate) fn try_from_path>(path: P) -> eyre::Result { - let hex = fs::read_to_string(path.as_ref()).wrap_err_with(|| { - format!( - "failed to read sequencer key from path: {}", - path.as_ref().display() - ) + /// The file at `path` should contain a hex-encoded ed25519 secret key. + pub(crate) fn path>(self, path: P) -> Self { + Self { + path: Some(path.as_ref().to_path_buf()), + ..self + } + } + + /// Sets the prefix for constructing a bech32m sequencer address. + /// + /// The prefix must be a valid bech32 human-readable-prefix (Hrp). + pub(crate) fn prefix>(self, prefix: S) -> Self { + Self { + prefix: Some(prefix.as_ref().to_string()), + ..self + } + } + + pub(crate) fn try_build(self) -> eyre::Result { + let Some(path) = self.path else { + bail!("path to sequencer key file must be set"); + }; + let Some(prefix) = self.prefix else { + bail!( + "a prefix to construct bech32m complicant astria addresses from the signing key \ + must be set" + ); + }; + let hex = fs::read_to_string(&path).wrap_err_with(|| { + format!("failed to read sequencer key from path: {}", path.display()) })?; let bytes: [u8; 32] = hex::decode(hex.trim()) - .wrap_err_with(|| format!("failed to decode hex: {}", path.as_ref().display()))? + .wrap_err_with(|| format!("failed to decode hex: {}", path.display()))? .try_into() .map_err(|_| { eyre!( "invalid private key length; must be 32 bytes: {}", - path.as_ref().display() + path.display() ) })?; let signing_key = SigningKey::from(bytes); + let address = Address::builder() + .array(signing_key.address_bytes()) + .prefix(&prefix) + .try_build() + .wrap_err_with(|| { + format!( + "failed constructing valid sequencer address using the provided prefix \ + `{prefix}`" + ) + })?; - Ok(Self { - address: Address::builder() - .array(signing_key.verification_key().address_bytes()) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .wrap_err("failed to construct Sequencer address")?, + Ok(SequencerKey { + address, signing_key, }) } } + +impl SequencerKey { + pub(crate) fn builder() -> SequencerKeyBuilder { + SequencerKeyBuilder { + path: None, + prefix: None, + } + } + + pub(crate) fn address(&self) -> &Address { + &self.address + } + + pub(crate) fn signing_key(&self) -> &SigningKey { + &self.signing_key + } +} diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/tests.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/tests.rs index 05f177445f..d44d777bb6 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/tests.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/tests.rs @@ -10,14 +10,10 @@ use astria_core::{ bridge::Ics20WithdrawalFromRollupMemo, crypto::SigningKey, generated::protocol::account::v1alpha1::NonceResponse, - primitive::v1::{ - asset::{ - self, - default_native_asset, - Denom, - }, - Address, - ASTRIA_ADDRESS_PREFIX, + primitive::v1::asset::{ + self, + default_native_asset, + Denom, }, protocol::{ account::v1alpha1::AssetBalance, @@ -153,6 +149,7 @@ impl TestSubmitter { let (submitter, submitter_handle) = submitter::Builder { shutdown_token: shutdown_token.clone(), sequencer_key_path, + sequencer_address_prefix: "astria".into(), sequencer_chain_id: SEQUENCER_CHAIN_ID.to_string(), sequencer_cometbft_endpoint, state, @@ -281,11 +278,7 @@ fn make_ics20_withdrawal_action() -> Action { let inner = Ics20Withdrawal { denom: denom.clone(), destination_chain_address, - return_address: Address::builder() - .array([0u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(), + return_address: crate::astria_address([0u8; 20]), amount: 99, memo: serde_json::to_string(&Ics20WithdrawalFromRollupMemo { memo: "hello".to_string(), @@ -307,11 +300,7 @@ fn make_ics20_withdrawal_action() -> Action { fn make_bridge_unlock_action() -> Action { let denom = default_native_asset(); let inner = BridgeUnlockAction { - to: Address::builder() - .array([0u8; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(), + to: crate::astria_address([0u8; 20]), amount: 99, memo: serde_json::to_vec(&BridgeUnlockMemo { block_number: DEFAULT_LAST_ROLLUP_HEIGHT.into(), @@ -384,8 +373,7 @@ fn make_signed_bridge_transaction() -> SignedTransaction { params: TransactionParams::builder() .nonce(DEFAULT_SEQUENCER_NONCE) .chain_id(SEQUENCER_CHAIN_ID) - .try_build() - .unwrap(), + .build(), actions, } .into_signed(&alice_key) diff --git a/crates/astria-bridge-withdrawer/src/config.rs b/crates/astria-bridge-withdrawer/src/config.rs index 12275779c1..abd458d5b9 100644 --- a/crates/astria-bridge-withdrawer/src/config.rs +++ b/crates/astria-bridge-withdrawer/src/config.rs @@ -27,6 +27,8 @@ pub struct Config { pub ethereum_contract_address: String, // The rpc endpoint of the evm rollup. pub ethereum_rpc_endpoint: String, + // The address prefix to use when constructing sequencer addresses using the signing key. + pub sequencer_address_prefix: String, // The socket address at which the bridge service will server healthz, readyz, and status // calls. pub api_addr: String, diff --git a/crates/astria-bridge-withdrawer/src/lib.rs b/crates/astria-bridge-withdrawer/src/lib.rs index 8ce4dae86e..623892b393 100644 --- a/crates/astria-bridge-withdrawer/src/lib.rs +++ b/crates/astria-bridge-withdrawer/src/lib.rs @@ -4,8 +4,11 @@ mod build_info; pub(crate) mod config; pub(crate) mod metrics; -#[cfg(test)] -pub(crate) use bridge_withdrawer::astria_address; pub use bridge_withdrawer::BridgeWithdrawer; +#[cfg(test)] +pub(crate) use bridge_withdrawer::{ + astria_address, + ASTRIA_ADDRESS_PREFIX, +}; pub use build_info::BUILD_INFO; pub use config::Config; diff --git a/crates/astria-cli/src/cli/sequencer.rs b/crates/astria-cli/src/cli/sequencer.rs index affb28d639..f4e2d79f99 100644 --- a/crates/astria-cli/src/cli/sequencer.rs +++ b/crates/astria-cli/src/cli/sequencer.rs @@ -1,14 +1,8 @@ -use std::str::FromStr; - use astria_sequencer_client::Address; use clap::{ Args, Subcommand, }; -use color_eyre::{ - eyre, - eyre::Context, -}; /// Interact with a Sequencer node #[derive(Debug, Subcommand)] @@ -18,6 +12,11 @@ pub enum Command { #[command(subcommand)] command: AccountCommand, }, + /// Utilities for constructing and inspecting sequencer addresses + Address { + #[command(subcommand)] + command: AddressCommand, + }, /// Commands for interacting with Sequencer balances Balance { #[command(subcommand)] @@ -50,6 +49,12 @@ pub enum AccountCommand { Nonce(BasicAccountArgs), } +#[derive(Debug, Subcommand)] +pub enum AddressCommand { + /// Construct a bech32m Sequencer address given a public key + Bech32m(Bech32mAddressArgs), +} + #[derive(Debug, Subcommand)] pub enum BalanceCommand { /// Get the balance of a Sequencer account @@ -96,16 +101,29 @@ pub struct BasicAccountArgs { )] pub(crate) sequencer_url: String, /// The address of the Sequencer account - pub(crate) address: SequencerAddressArg, + pub(crate) address: Address, +} + +#[derive(Args, Debug)] +pub struct Bech32mAddressArgs { + /// The hex formatted byte part of the bech32m address + #[arg(long)] + pub(crate) bytes: String, + /// The human readable prefix (Hrp) of the bech32m adress + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, } #[derive(Args, Debug)] pub struct TransferArgs { // The address of the Sequencer account to send amount to - pub(crate) to_address: SequencerAddressArg, + pub(crate) to_address: Address, // The amount being sent #[arg(long)] pub(crate) amount: u128, + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, /// The private key of account being sent from #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] // TODO: https://github.com/astriaorg/astria/issues/594 @@ -131,6 +149,9 @@ pub struct TransferArgs { #[derive(Args, Debug)] pub struct FeeAssetChangeArgs { + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, // TODO: https://github.com/astriaorg/astria/issues/594 // Don't use a plain text private, prefer wrapper like from // the secrecy crate with specialized `Debug` and `Drop` implementations @@ -158,6 +179,9 @@ pub struct FeeAssetChangeArgs { #[derive(Args, Debug)] pub struct IbcRelayerChangeArgs { + /// The prefix to construct a bech32m address given the private key. + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, // TODO: https://github.com/astriaorg/astria/issues/594 // Don't use a plain text private, prefer wrapper like from // the secrecy crate with specialized `Debug` and `Drop` implementations @@ -180,11 +204,14 @@ pub struct IbcRelayerChangeArgs { pub sequencer_chain_id: String, /// The address to add or remove as an IBC relayer #[arg(long)] - pub(crate) address: SequencerAddressArg, + pub(crate) address: Address, } #[derive(Args, Debug)] pub struct InitBridgeAccountArgs { + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, // TODO: https://github.com/astriaorg/astria/issues/594 // Don't use a plain text private, prefer wrapper like from // the secrecy crate with specialized `Debug` and `Drop` implementations @@ -214,12 +241,15 @@ pub struct InitBridgeAccountArgs { #[derive(Args, Debug)] pub struct BridgeLockArgs { /// The address of the Sequencer account to lock amount to - pub(crate) to_address: SequencerAddressArg, + pub(crate) to_address: Address, /// The amount being locked #[arg(long)] pub(crate) amount: u128, #[arg(long)] pub(crate) destination_chain_address: String, + /// The prefix to construct a bech32m address given the private key. + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, // TODO: https://github.com/astriaorg/astria/issues/594 // Don't use a plain text private, prefer wrapper like from // the secrecy crate with specialized `Debug` and `Drop` implementations @@ -242,23 +272,6 @@ pub struct BridgeLockArgs { pub sequencer_chain_id: String, } -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct SequencerAddressArg(pub(crate) Address); - -impl FromStr for SequencerAddressArg { - type Err = eyre::Report; - - /// Parse a string into a Sequencer Address - fn from_str(s: &str) -> eyre::Result { - let address_bytes = hex::decode(s).wrap_err( - "failed to decode address. address should be 20 bytes long. do not prefix with 0x", - )?; - let address = - crate::try_astria_address(&address_bytes).wrap_err("failed to create address")?; - Ok(Self(address)) - } -} - #[derive(Debug, Subcommand)] pub enum BlockHeightCommand { /// Get the current block height of the Sequencer node @@ -285,6 +298,9 @@ pub struct BlockHeightGetArgs { #[derive(Args, Debug)] pub struct SudoAddressChangeArgs { + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, // TODO: https://github.com/astriaorg/astria/issues/594 // Don't use a plain text private, prefer wrapper like from // the secrecy crate with specialized `Debug` and `Drop` implementations @@ -307,7 +323,7 @@ pub struct SudoAddressChangeArgs { pub sequencer_chain_id: String, /// The new address to take over sudo privileges #[arg(long)] - pub(crate) address: SequencerAddressArg, + pub(crate) address: Address, } #[derive(Args, Debug)] @@ -326,6 +342,9 @@ pub struct ValidatorUpdateArgs { default_value = crate::cli::DEFAULT_SEQUENCER_CHAIN_ID )] pub sequencer_chain_id: String, + /// The bech32m prefix that will be used for constructing addresses using the private key + #[arg(long, default_value = "astria")] + pub(crate) prefix: String, /// The private key of the sudo account authorizing change #[arg(long, env = "SEQUENCER_PRIVATE_KEY")] // TODO: https://github.com/astriaorg/astria/issues/594 @@ -340,28 +359,3 @@ pub struct ValidatorUpdateArgs { #[arg(long)] pub(crate) power: u32, } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_sequencer_address_arg_from_str_valid() { - let hex_str = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"; - let bytes = hex::decode(hex_str).unwrap(); - let expected_address = crate::try_astria_address(&bytes).unwrap(); - - let sequencer_address_arg: SequencerAddressArg = hex_str.parse().unwrap(); - assert_eq!(sequencer_address_arg, SequencerAddressArg(expected_address)); - } - - #[test] - fn test_sequencer_address_arg_from_str_invalid() { - let hex_str = "invalidhexstr"; - let result: eyre::Result = hex_str.parse(); - assert!(result.is_err()); - - let error_message = format!("{:?}", result.unwrap_err()); - assert!(error_message.contains("failed to decode address")); - } -} diff --git a/crates/astria-cli/src/commands/mod.rs b/crates/astria-cli/src/commands/mod.rs index cf7930c600..6336de004a 100644 --- a/crates/astria-cli/src/commands/mod.rs +++ b/crates/astria-cli/src/commands/mod.rs @@ -15,6 +15,7 @@ use crate::cli::{ }, sequencer::{ AccountCommand, + AddressCommand, BalanceCommand, BlockHeightCommand, Command as SequencerCommand, @@ -71,6 +72,11 @@ pub async fn run(cli: Cli) -> eyre::Result<()> { AccountCommand::Balance(args) => sequencer::get_balance(&args).await?, AccountCommand::Nonce(args) => sequencer::get_nonce(&args).await?, }, + SequencerCommand::Address { + command, + } => match command { + AddressCommand::Bech32m(args) => sequencer::make_bech32m(&args)?, + }, SequencerCommand::Balance { command, } => match command { diff --git a/crates/astria-cli/src/commands/sequencer.rs b/crates/astria-cli/src/commands/sequencer.rs index 204e5acdf3..c7b6523b2b 100644 --- a/crates/astria-cli/src/commands/sequencer.rs +++ b/crates/astria-cli/src/commands/sequencer.rs @@ -1,8 +1,12 @@ use astria_core::{ crypto::SigningKey, primitive::v1::{ - asset, - asset::default_native_asset, + asset::{ + self, + default_native_asset, + }, + Address, + ADDRESS_LEN, }, protocol::transaction::v1alpha1::{ action::{ @@ -37,6 +41,7 @@ use rand::rngs::OsRng; use crate::cli::sequencer::{ BasicAccountArgs, + Bech32mAddressArgs, BlockHeightGetArgs, BridgeLockArgs, FeeAssetChangeArgs, @@ -96,16 +101,15 @@ pub(crate) fn create_account() { /// * If the http client cannot be created /// * If the balance cannot be retrieved pub(crate) async fn get_balance(args: &BasicAccountArgs) -> eyre::Result<()> { - let address = &args.address; let sequencer_client = HttpClient::new(args.sequencer_url.as_str()) .wrap_err("failed constructing http sequencer client")?; let res = sequencer_client - .get_latest_balance(address.0) + .get_latest_balance(args.address) .await .wrap_err("failed to get balance")?; - println!("Balances for address {}:", hex::encode(address.0)); + println!("Balances for address: {}", args.address); for balance in res.balances { println!(" asset ID: {}", balance.denom.id()); println!(" {} {}", balance.balance, balance.denom); @@ -124,16 +128,15 @@ pub(crate) async fn get_balance(args: &BasicAccountArgs) -> eyre::Result<()> { /// * If the http client cannot be created /// * If the balance cannot be retrieved pub(crate) async fn get_nonce(args: &BasicAccountArgs) -> eyre::Result<()> { - let address = &args.address; let sequencer_client = HttpClient::new(args.sequencer_url.as_str()) .wrap_err("failed constructing http sequencer client")?; let res = sequencer_client - .get_latest_nonce(address.0) + .get_latest_nonce(args.address) .await .wrap_err("failed to get nonce")?; - println!("Nonce for address {}:", address.0); + println!("Nonce for address {}", args.address); println!(" {} at height {}", res.nonce, res.height); Ok(()) @@ -164,6 +167,22 @@ pub(crate) async fn get_block_height(args: &BlockHeightGetArgs) -> eyre::Result< Ok(()) } +/// Returns a bech32m sequencer address given a prefix and hex-encoded byte slice +pub(crate) fn make_bech32m(args: &Bech32mAddressArgs) -> eyre::Result<()> { + use hex::FromHex as _; + let bytes = <[u8; ADDRESS_LEN]>::from_hex(&args.bytes) + .wrap_err("failed decoding provided hex bytes")?; + let address = Address::builder() + .array(bytes) + .prefix(&args.prefix) + .try_build() + .wrap_err( + "failed constructing a valid bech32m address from the provided hex bytes and prefix", + )?; + println!("{address}"); + Ok(()) +} + /// Gets the latest block height of a Sequencer node /// /// # Arguments @@ -178,9 +197,10 @@ pub(crate) async fn send_transfer(args: &TransferArgs) -> eyre::Result<()> { let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), Action::Transfer(TransferAction { - to: args.to_address.0, + to: args.to_address, amount: args.amount, asset_id: default_native_asset().id(), fee_asset_id: default_native_asset().id(), @@ -208,8 +228,9 @@ pub(crate) async fn ibc_relayer_add(args: &IbcRelayerChangeArgs) -> eyre::Result let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), - Action::IbcRelayerChange(IbcRelayerChangeAction::Addition(args.address.0)), + Action::IbcRelayerChange(IbcRelayerChangeAction::Addition(args.address)), ) .await .wrap_err("failed to submit IbcRelayerChangeAction::Addition transaction")?; @@ -233,8 +254,9 @@ pub(crate) async fn ibc_relayer_remove(args: &IbcRelayerChangeArgs) -> eyre::Res let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), - Action::IbcRelayerChange(IbcRelayerChangeAction::Removal(args.address.0)), + Action::IbcRelayerChange(IbcRelayerChangeAction::Removal(args.address)), ) .await .wrap_err("failed to submit IbcRelayerChangeAction::Removal transaction")?; @@ -261,6 +283,7 @@ pub(crate) async fn init_bridge_account(args: &InitBridgeAccountArgs) -> eyre::R let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), Action::InitBridgeAccount(InitBridgeAccountAction { rollup_id, @@ -294,9 +317,10 @@ pub(crate) async fn bridge_lock(args: &BridgeLockArgs) -> eyre::Result<()> { let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), Action::BridgeLock(BridgeLockAction { - to: args.to_address.0, + to: args.to_address, asset_id: default_native_asset().id(), amount: args.amount, fee_asset_id: default_native_asset().id(), @@ -325,6 +349,7 @@ pub(crate) async fn fee_asset_add(args: &FeeAssetChangeArgs) -> eyre::Result<()> let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), Action::FeeAssetChange(FeeAssetChangeAction::Addition( asset::Id::from_str_unchecked(&args.asset), @@ -352,6 +377,7 @@ pub(crate) async fn fee_asset_remove(args: &FeeAssetChangeArgs) -> eyre::Result< let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), Action::FeeAssetChange(FeeAssetChangeAction::Removal( asset::Id::from_str_unchecked(&args.asset), @@ -379,9 +405,10 @@ pub(crate) async fn sudo_address_change(args: &SudoAddressChangeArgs) -> eyre::R let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), Action::SudoAddressChange(SudoAddressChangeAction { - new_address: args.address.0, + new_address: args.address, }), ) .await @@ -415,6 +442,7 @@ pub(crate) async fn validator_update(args: &ValidatorUpdateArgs) -> eyre::Result let res = submit_transaction( args.sequencer_url.as_str(), args.sequencer_chain_id.clone(), + &args.prefix, args.private_key.as_str(), Action::ValidatorUpdate(validator_update), ) @@ -429,6 +457,7 @@ pub(crate) async fn validator_update(args: &ValidatorUpdateArgs) -> eyre::Result async fn submit_transaction( sequencer_url: &str, chain_id: String, + prefix: &str, private_key: &str, action: Action, ) -> eyre::Result { @@ -441,7 +470,11 @@ async fn submit_transaction( .map_err(|_| eyre!("invalid private key length; must be 32 bytes"))?; let sequencer_key = SigningKey::from(private_key_bytes); - let from_address = crate::astria_address(sequencer_key.verification_key().address_bytes()); + let from_address = Address::builder() + .array(sequencer_key.verification_key().address_bytes()) + .prefix(prefix) + .try_build() + .wrap_err("failed constructing a valid from address from the provided prefix")?; println!("sending tx from address: {from_address}"); let nonce_res = sequencer_client @@ -453,8 +486,7 @@ async fn submit_transaction( params: TransactionParams::builder() .nonce(nonce_res.nonce) .chain_id(chain_id) - .try_build() - .wrap_err("failed to construct transaction params from provided chain ID")?, + .build(), actions: vec![action], } .into_signed(&sequencer_key); diff --git a/crates/astria-cli/src/lib.rs b/crates/astria-cli/src/lib.rs index 2559502a00..2c16dcbda6 100644 --- a/crates/astria-cli/src/lib.rs +++ b/crates/astria-cli/src/lib.rs @@ -1,30 +1,3 @@ -use astria_core::primitive::v1::{ - Address, - AddressError, -}; - pub mod cli; pub mod commands; pub mod types; - -const ADDRESS_PREFIX: &str = "astria"; - -/// Constructs an [`Address`] prefixed by `"astria"`. -pub(crate) fn astria_address(array: [u8; astria_core::primitive::v1::ADDRESS_LEN]) -> Address { - Address::builder() - .array(array) - .prefix(ADDRESS_PREFIX) - .try_build() - .unwrap() -} - -/// Tries to construct an [`Address`] prefixed by `"astria"` from a byte slice. -/// -/// # Errors -/// Fails if the slice does not contain 20 bytes. -pub(crate) fn try_astria_address(slice: &[u8]) -> Result { - Address::builder() - .slice(slice) - .prefix(ADDRESS_PREFIX) - .try_build() -} diff --git a/crates/astria-composer/local.env.example b/crates/astria-composer/local.env.example index 915df6266c..c746153556 100644 --- a/crates/astria-composer/local.env.example +++ b/crates/astria-composer/local.env.example @@ -39,6 +39,9 @@ ASTRIA_COMPOSER_ROLLUPS="astriachain::ws://127.0.0.1:8545" # transactions. The file should contain a hex-encoded Ed25519 secret key. ASTRIA_COMPOSER_PRIVATE_KEY_FILE=/path/to/priv_sequencer_key.json +# The prefix that will be used to construct bech32m sequencer addresses. +ASTRIA_COMPOSER_SEQUENCER_ADDRESS_PREFIX=astria + # Block time in milliseconds, used to force submitting of finished bundles. # Should match the sequencer node configuration for 'timeout_commit', as # specified in https://docs.tendermint.com/v0.34/tendermint-core/configuration.html diff --git a/crates/astria-composer/src/composer.rs b/crates/astria-composer/src/composer.rs index 9dcea0582a..cbdedac5c3 100644 --- a/crates/astria-composer/src/composer.rs +++ b/crates/astria-composer/src/composer.rs @@ -128,6 +128,7 @@ impl Composer { sequencer_url: cfg.sequencer_url.clone(), sequencer_chain_id: cfg.sequencer_chain_id.clone(), private_key_file: cfg.private_key_file.clone(), + sequencer_address_prefix: cfg.sequencer_address_prefix.clone(), block_time_ms: cfg.block_time_ms, max_bytes_per_bundle: cfg.max_bytes_per_bundle, bundle_queue_capacity: cfg.bundle_queue_capacity, diff --git a/crates/astria-composer/src/config.rs b/crates/astria-composer/src/config.rs index 07a4a5d09c..1cbf56ac6c 100644 --- a/crates/astria-composer/src/config.rs +++ b/crates/astria-composer/src/config.rs @@ -34,6 +34,9 @@ pub struct Config { /// Path to private key for the sequencer account used for signing transactions pub private_key_file: String, + // The address prefix to use when constructing sequencer addresses using the signing key. + pub sequencer_address_prefix: String, + /// Sequencer block time in milliseconds #[serde(alias = "max_submit_interval_ms")] pub block_time_ms: u64, diff --git a/crates/astria-composer/src/executor/builder.rs b/crates/astria-composer/src/executor/builder.rs index 2509c0963c..871a4f1b33 100644 --- a/crates/astria-composer/src/executor/builder.rs +++ b/crates/astria-composer/src/executor/builder.rs @@ -6,10 +6,7 @@ use std::{ use astria_core::{ crypto::SigningKey, - primitive::v1::{ - Address, - ASTRIA_ADDRESS_PREFIX, - }, + primitive::v1::Address, protocol::transaction::v1alpha1::action::SequenceAction, }; use astria_eyre::eyre::{ @@ -30,6 +27,7 @@ pub(crate) struct Builder { pub(crate) sequencer_url: String, pub(crate) sequencer_chain_id: String, pub(crate) private_key_file: String, + pub(crate) sequencer_address_prefix: String, pub(crate) block_time_ms: u64, pub(crate) max_bytes_per_bundle: usize, pub(crate) bundle_queue_capacity: usize, @@ -43,6 +41,7 @@ impl Builder { sequencer_url, sequencer_chain_id, private_key_file, + sequencer_address_prefix, block_time_ms, max_bytes_per_bundle, bundle_queue_capacity, @@ -58,7 +57,7 @@ impl Builder { })?; let sequencer_address = Address::builder() - .prefix(ASTRIA_ADDRESS_PREFIX) + .prefix(sequencer_address_prefix) .array(sequencer_key.verification_key().address_bytes()) .try_build() .wrap_err("failed constructing a sequencer address from private key")?; diff --git a/crates/astria-composer/src/executor/mod.rs b/crates/astria-composer/src/executor/mod.rs index a769a1f3cc..e2b35e7341 100644 --- a/crates/astria-composer/src/executor/mod.rs +++ b/crates/astria-composer/src/executor/mod.rs @@ -575,8 +575,7 @@ impl Future for SubmitFut { let params = TransactionParams::builder() .nonce(*this.nonce) .chain_id(&*this.chain_id) - .try_build() - .expect("configured chain ID is valid"); + .build(); let tx = UnsignedTransaction { actions: this.bundle.clone().into_actions(), params, @@ -656,8 +655,7 @@ impl Future for SubmitFut { let params = TransactionParams::builder() .nonce(*this.nonce) .chain_id(&*this.chain_id) - .try_build() - .expect("configured chain ID is valid"); + .build(); let tx = UnsignedTransaction { actions: this.bundle.clone().into_actions(), params, diff --git a/crates/astria-composer/src/executor/tests.rs b/crates/astria-composer/src/executor/tests.rs index 900b884299..07482610c3 100644 --- a/crates/astria-composer/src/executor/tests.rs +++ b/crates/astria-composer/src/executor/tests.rs @@ -93,6 +93,7 @@ async fn setup() -> (MockServer, MockGuard, Config, NamedTempFile) { sequencer_url: server.uri(), sequencer_chain_id: "test-chain-1".to_string(), private_key_file: keyfile.path().to_string_lossy().to_string(), + sequencer_address_prefix: "astria".into(), block_time_ms: 2000, max_bytes_per_bundle: 1000, bundle_queue_capacity: 10, @@ -214,6 +215,7 @@ async fn full_bundle() { sequencer_url: cfg.sequencer_url.clone(), sequencer_chain_id: cfg.sequencer_chain_id.clone(), private_key_file: cfg.private_key_file.clone(), + sequencer_address_prefix: "astria".into(), block_time_ms: cfg.block_time_ms, max_bytes_per_bundle: cfg.max_bytes_per_bundle, bundle_queue_capacity: cfg.bundle_queue_capacity, @@ -307,6 +309,7 @@ async fn bundle_triggered_by_block_timer() { sequencer_url: cfg.sequencer_url.clone(), sequencer_chain_id: cfg.sequencer_chain_id.clone(), private_key_file: cfg.private_key_file.clone(), + sequencer_address_prefix: "astria".into(), block_time_ms: cfg.block_time_ms, max_bytes_per_bundle: cfg.max_bytes_per_bundle, bundle_queue_capacity: cfg.bundle_queue_capacity, @@ -393,6 +396,7 @@ async fn two_seq_actions_single_bundle() { sequencer_url: cfg.sequencer_url.clone(), sequencer_chain_id: cfg.sequencer_chain_id.clone(), private_key_file: cfg.private_key_file.clone(), + sequencer_address_prefix: "astria".into(), block_time_ms: cfg.block_time_ms, max_bytes_per_bundle: cfg.max_bytes_per_bundle, bundle_queue_capacity: cfg.bundle_queue_capacity, diff --git a/crates/astria-composer/tests/blackbox/helper/mod.rs b/crates/astria-composer/tests/blackbox/helper/mod.rs index ea802b2896..82940aa3be 100644 --- a/crates/astria-composer/tests/blackbox/helper/mod.rs +++ b/crates/astria-composer/tests/blackbox/helper/mod.rs @@ -95,6 +95,7 @@ pub async fn spawn_composer(rollup_ids: &[&str]) -> TestComposer { rollups, sequencer_url, private_key_file: keyfile.path().to_string_lossy().to_string(), + sequencer_address_prefix: "astria".into(), block_time_ms: 2000, max_bytes_per_bundle: 200_000, bundle_queue_capacity: 10, diff --git a/crates/astria-core/src/bridge.rs b/crates/astria-core/src/bridge.rs index da58999ba8..822fde35a7 100644 --- a/crates/astria-core/src/bridge.rs +++ b/crates/astria-core/src/bridge.rs @@ -39,7 +39,6 @@ pub struct Ics20TransferDepositMemo { #[cfg(test)] mod test { use super::*; - use crate::primitive::v1::ASTRIA_ADDRESS_PREFIX; #[test] fn ics20_withdrawal_from_rollup_memo_snapshot() { @@ -47,7 +46,7 @@ mod test { memo: "hello".to_string(), bridge_address: Address::builder() .array([99; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) + .prefix("astria") .try_build() .unwrap(), block_number: 1, diff --git a/crates/astria-core/src/crypto.rs b/crates/astria-core/src/crypto.rs index 1c3e919635..6150751913 100644 --- a/crates/astria-core/src/crypto.rs +++ b/crates/astria-core/src/crypto.rs @@ -76,6 +76,12 @@ impl SigningKey { key: self.0.verification_key(), } } + + /// Returns the address bytes of the verification key associated with this signing key. + #[must_use] + pub fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + self.verification_key().address_bytes() + } } impl Debug for SigningKey { diff --git a/crates/astria-core/src/primitive/v1/asset/denom.rs b/crates/astria-core/src/primitive/v1/asset/denom.rs index 0ccf471f6f..9a6e34616d 100644 --- a/crates/astria-core/src/primitive/v1/asset/denom.rs +++ b/crates/astria-core/src/primitive/v1/asset/denom.rs @@ -471,6 +471,13 @@ pub struct IbcPrefixed { } impl IbcPrefixed { + #[must_use] + pub fn new(id: [u8; 32]) -> Self { + Self { + id, + } + } + #[must_use] pub fn id(&self) -> super::Id { super::Id::new(self.id) @@ -509,6 +516,72 @@ impl FromStr for IbcPrefixed { } } +#[cfg(feature = "serde")] +mod serde_impl { + use serde::{ + Deserialize, + Deserializer, + Serialize, + Serializer, + }; + + macro_rules! impl_serde { + ($($type:ty),*$(,)?) => { + $( + impl<'de> Deserialize<'de> for $type { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de::Error as _; + let s = <&str>::deserialize(deserializer)?; + s.parse().map_err(D::Error::custom) + } + } + + impl Serialize for $type { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } + } + )* + } + } + + impl_serde!(super::Denom, super::TracePrefixed, super::IbcPrefixed); + + #[cfg(test)] + mod tests { + use super::super::IbcPrefixed; + use crate::primitive::v1::asset::{ + denom::TracePrefixed, + Denom, + }; + + fn trace_prefixed() -> TracePrefixed { + "a/trace/pre/fixed/denom".parse().unwrap() + } + fn ibc_prefixed() -> IbcPrefixed { + use sha2::{ + Digest as _, + Sha256, + }; + let bytes: [u8; 32] = Sha256::digest("a/trace/pre/fixed/denom").into(); + IbcPrefixed::new(bytes) + } + #[test] + fn snapshots() { + insta::assert_json_snapshot!(ibc_prefixed()); + insta::assert_json_snapshot!(trace_prefixed()); + insta::assert_json_snapshot!(Denom::from(ibc_prefixed())); + insta::assert_json_snapshot!(Denom::from(trace_prefixed())); + } + } +} + #[cfg(test)] mod tests { use super::{ diff --git a/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-2.snap b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-2.snap new file mode 100644 index 0000000000..d5d31139e9 --- /dev/null +++ b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-2.snap @@ -0,0 +1,5 @@ +--- +source: crates/astria-core/src/primitive/v1/asset/denom.rs +expression: trace_prefixed() +--- +"a/trace/pre/fixed/denom" diff --git a/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-3.snap b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-3.snap new file mode 100644 index 0000000000..886789c84d --- /dev/null +++ b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-3.snap @@ -0,0 +1,5 @@ +--- +source: crates/astria-core/src/primitive/v1/asset/denom.rs +expression: "Denom::from(ibc_prefixed())" +--- +"ibc/fe811daa715ce8dbd147308a12ec1edfe736f3a23f4a757bab28e76364a274fd" diff --git a/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-4.snap b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-4.snap new file mode 100644 index 0000000000..bad6c055bb --- /dev/null +++ b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots-4.snap @@ -0,0 +1,5 @@ +--- +source: crates/astria-core/src/primitive/v1/asset/denom.rs +expression: "Denom::from(trace_prefixed())" +--- +"a/trace/pre/fixed/denom" diff --git a/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots.snap b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots.snap new file mode 100644 index 0000000000..aab4dbc263 --- /dev/null +++ b/crates/astria-core/src/primitive/v1/asset/snapshots/astria_core__primitive__v1__asset__denom__serde_impl__tests__snapshots.snap @@ -0,0 +1,5 @@ +--- +source: crates/astria-core/src/primitive/v1/asset/denom.rs +expression: ibc_prefixed() +--- +"ibc/fe811daa715ce8dbd147308a12ec1edfe736f3a23f4a757bab28e76364a274fd" diff --git a/crates/astria-core/src/primitive/v1/mod.rs b/crates/astria-core/src/primitive/v1/mod.rs index 2af5046607..8a3a8051f2 100644 --- a/crates/astria-core/src/primitive/v1/mod.rs +++ b/crates/astria-core/src/primitive/v1/mod.rs @@ -1,6 +1,8 @@ pub mod asset; pub mod u128; +use std::str::FromStr; + use base64::{ display::Base64Display, prelude::BASE64_STANDARD, @@ -17,8 +19,6 @@ use crate::{ }; pub const ADDRESS_LEN: usize = 20; -/// The human readable prefix of astria addresses (also known as bech32 HRP). -pub const ASTRIA_ADDRESS_PREFIX: &str = "astria"; pub const ROLLUP_ID_LEN: usize = 32; pub const FEE_ASSET_ID_LEN: usize = 32; @@ -373,42 +373,8 @@ impl<'a, 'b> AddressBuilder, WithPrefix<'b>> { } } -// Private setters only used within this crate to not leak bech32 -impl AddressBuilder { - pub(crate) fn array__( - self, - array: [u8; ADDRESS_LEN], - ) -> AddressBuilder<[u8; ADDRESS_LEN], TPrefix> { - AddressBuilder { - bytes: array, - prefix: self.prefix, - } - } - - pub(crate) fn hrp__(self, prefix: bech32::Hrp) -> AddressBuilder { - AddressBuilder { - bytes: self.bytes, - prefix, - } - } -} - -// private builder to not leak bech32 -impl AddressBuilder<[u8; ADDRESS_LEN], bech32::Hrp> { - pub(crate) fn build(self) -> Address { - Address { - bytes: self.bytes, - prefix: self.prefix, - } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr( - feature = "serde", - derive(serde::Serialize), - derive(serde::Deserialize) -)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( feature = "serde", serde(into = "raw::Address", try_from = "raw::Address") @@ -429,19 +395,9 @@ impl Address { self.bytes } - /// Convert a string containing a bech32m string to an astria address. - /// - /// # Errors - /// Returns an error if: - /// + `input` is not bech32m encoded. - /// + the decoded data contained in `input` is not 20 bytes long. - /// + the bech32 hrp prefix exceeds 16 bytes. - pub fn try_from_bech32m(input: &str) -> Result { - let (hrp, bytes) = bech32::decode(input).map_err(AddressError::bech32m_decode)?; - Self::builder() - .slice(bytes) - .prefix(hrp.as_str()) - .try_build() + #[must_use] + pub fn prefix(&self) -> &str { + self.prefix.as_str() } /// Convert [`Address`] to a [`raw::Address`]. @@ -450,7 +406,7 @@ impl Address { #[must_use] pub fn to_raw(&self) -> raw::Address { let bech32m = bech32::encode_lower::(self.prefix, &self.bytes()) - .expect("must not fail because len(prefix) + len(bytes) <= 63 < BECH32M::CODELENGTH"); + .expect("should not fail because len(prefix) + len(bytes) <= 63 < BECH32M::CODELENGTH"); // allow: the field is deprecated, but we must still fill it in #[allow(deprecated)] raw::Address { @@ -470,6 +426,7 @@ impl Address { /// /// Returns an error if the account buffer was not 20 bytes long. pub fn try_from_raw(raw: &raw::Address) -> Result { + const ASTRIA_ADDRESS_PREFIX: &str = "astria"; // allow: `Address::inner` field is deprecated, but we must still check it #[allow(deprecated)] let raw::Address { @@ -483,24 +440,30 @@ impl Address { .try_build(); } if inner.is_empty() { - return Self::try_from_bech32m(bech32m); + return bech32m.parse(); } Err(AddressError::fields_are_mutually_exclusive()) } } -impl AsRef<[u8]> for Address { - fn as_ref(&self) -> &[u8] { - &self.bytes - } -} - impl From
for raw::Address { fn from(value: Address) -> Self { value.into_raw() } } +impl FromStr for Address { + type Err = AddressError; + + fn from_str(s: &str) -> Result { + let (hrp, bytes) = bech32::decode(s).map_err(AddressError::bech32m_decode)?; + Self::builder() + .slice(bytes) + .prefix(hrp.as_str()) + .try_build() + } +} + impl TryFrom for Address { type Error = AddressError; @@ -545,7 +508,6 @@ where #[cfg(test)] mod tests { use bytes::Bytes; - use insta::assert_json_snapshot; use super::{ raw, @@ -553,8 +515,8 @@ mod tests { AddressError, AddressErrorKind, ADDRESS_LEN, - ASTRIA_ADDRESS_PREFIX, }; + const ASTRIA_ADDRESS_PREFIX: &str = "astria"; #[track_caller] fn assert_wrong_address_bytes(bad_account: &[u8]) { @@ -583,8 +545,10 @@ mod tests { assert_wrong_address_bytes(&[42; 100]); } + #[cfg(feature = "serde")] #[test] fn snapshots() { + use insta::assert_json_snapshot; let address = Address::builder() .array([42; 20]) .prefix(ASTRIA_ADDRESS_PREFIX) diff --git a/crates/astria-core/src/protocol/test_utils.rs b/crates/astria-core/src/protocol/test_utils.rs index ec3ce8d156..f1569e8ec4 100644 --- a/crates/astria-core/src/protocol/test_utils.rs +++ b/crates/astria-core/src/protocol/test_utils.rs @@ -109,8 +109,7 @@ impl ConfigureSequencerBlock { params: TransactionParams::builder() .nonce(1) .chain_id(chain_id.clone()) - .try_build() - .unwrap(), + .build(), }; vec![unsigned_transaction.into_signed(&signing_key)] }; diff --git a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs index 2a561849a5..4ca67df1e1 100644 --- a/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs +++ b/crates/astria-core/src/protocol/transaction/v1alpha1/mod.rs @@ -11,7 +11,7 @@ use crate::{ SigningKey, VerificationKey, }, - primitive::v1::Address, + primitive::v1::ADDRESS_LEN, }; pub mod action; @@ -79,11 +79,8 @@ pub struct SignedTransaction { } impl SignedTransaction { - pub fn address(&self) -> Address { - crate::primitive::v1::Address::builder() - .array__(self.verification_key.address_bytes()) - .hrp__(self.transaction.hrp()) - .build() + pub fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + self.verification_key.address_bytes() } /// Returns the transaction hash. @@ -231,10 +228,6 @@ pub struct UnsignedTransaction { } impl UnsignedTransaction { - fn hrp(&self) -> bech32::Hrp { - self.params.hrp - } - #[must_use] pub fn nonce(&self) -> u32 { self.params.nonce @@ -311,8 +304,7 @@ impl UnsignedTransaction { let Some(params) = params else { return Err(UnsignedTransactionError::unset_params()); }; - let params = TransactionParams::try_from_raw(params) - .map_err(UnsignedTransactionError::transaction_params)?; + let params = TransactionParams::from_raw(params); let actions: Vec<_> = actions .into_iter() .map(Action::try_from_raw) @@ -364,12 +356,6 @@ impl UnsignedTransactionError { fn decode_any(inner: prost::DecodeError) -> Self { Self(UnsignedTransactionErrorKind::DecodeAny(inner)) } - - fn transaction_params(source: TransactionParamsError) -> Self { - Self(UnsignedTransactionErrorKind::TransactionParams { - source, - }) - } } #[derive(Debug, thiserror::Error)] @@ -389,28 +375,6 @@ enum UnsignedTransactionErrorKind { raw::UnsignedTransaction::type_url() )] DecodeAny(#[source] prost::DecodeError), - #[error("`params` field was invalid")] - TransactionParams { source: TransactionParamsError }, -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct TransactionParamsError(TransactionParamsErrorKind); - -impl TransactionParamsError { - fn chain_id_not_bech32_compatible(source: bech32::primitives::hrp::Error) -> Self { - Self(TransactionParamsErrorKind::ChainIdNotBech32Compatible { - source, - }) - } -} - -#[derive(Debug, thiserror::Error)] -enum TransactionParamsErrorKind { - #[error("the name extracted from the chain ID cannot be used as an address prefix")] - ChainIdNotBech32Compatible { - source: bech32::primitives::hrp::Error, - }, } pub struct TransactionParamsBuilder> { @@ -454,19 +418,16 @@ impl<'a> TransactionParamsBuilder> { /// # Errors /// Returns an error if the set chain ID does not contain a chain name that can be turned into /// a bech32 human readable prefix (everything before the first dash i.e. `-`). - pub fn try_build(self) -> Result { + #[must_use] + pub fn build(self) -> TransactionParams { let Self { nonce, chain_id, } = self; - let chain_id = chain_id.as_ref().trim().to_string(); - let hrp = bech32::Hrp::parse(chain_id.split_once('-').map_or(&chain_id, |tup| tup.0)) - .map_err(TransactionParamsError::chain_id_not_bech32_compatible)?; - Ok(TransactionParams { + TransactionParams { nonce, - chain_id, - hrp, - }) + chain_id: chain_id.into(), + } } } @@ -474,7 +435,6 @@ impl<'a> TransactionParamsBuilder> { pub struct TransactionParams { nonce: u32, chain_id: String, - hrp: bech32::Hrp, } impl TransactionParams { @@ -500,12 +460,13 @@ impl TransactionParams { /// /// # Errors /// See [`TransactionParamsBuilder::try_build`] for errors returned by this method. - pub fn try_from_raw(proto: raw::TransactionParams) -> Result { + #[must_use] + pub fn from_raw(proto: raw::TransactionParams) -> Self { let raw::TransactionParams { nonce, chain_id, } = proto; - Self::builder().nonce(nonce).chain_id(chain_id).try_build() + Self::builder().nonce(nonce).chain_id(chain_id).build() } } @@ -516,10 +477,10 @@ mod test { primitive::v1::{ asset::default_native_asset, Address, - ASTRIA_ADDRESS_PREFIX, }, protocol::transaction::v1alpha1::action::TransferAction, }; + const ASTRIA_ADDRESS_PREFIX: &str = "astria"; #[test] fn signed_transaction_hash() { @@ -546,11 +507,10 @@ mod test { fee_asset_id: default_native_asset().id(), }; - let params = TransactionParams::try_from_raw(raw::TransactionParams { + let params = TransactionParams::from_raw(raw::TransactionParams { nonce: 1, chain_id: "test-1".to_string(), - }) - .unwrap(); + }); let unsigned = UnsignedTransaction { actions: vec![transfer.into()], params, @@ -584,11 +544,10 @@ mod test { fee_asset_id: default_native_asset().id(), }; - let params = TransactionParams::try_from_raw(raw::TransactionParams { + let params = TransactionParams::from_raw(raw::TransactionParams { nonce: 1, chain_id: "test-1".to_string(), - }) - .unwrap(); + }); let unsigned = UnsignedTransaction { actions: vec![transfer.into()], params, diff --git a/crates/astria-sequencer-client/src/extension_trait.rs b/crates/astria-sequencer-client/src/extension_trait.rs index 4b881906ea..f57c915e9a 100644 --- a/crates/astria-sequencer-client/src/extension_trait.rs +++ b/crates/astria-sequencer-client/src/extension_trait.rs @@ -8,10 +8,7 @@ //! The example below works with the feature `"http"` set. //! ```no_run //! # tokio_test::block_on(async { -//! use astria_core::primitive::v1::{ -//! Address, -//! ASTRIA_ADDRESS_PREFIX, -//! }; +//! use astria_core::primitive::v1::Address; //! use astria_sequencer_client::SequencerClientExt as _; //! use tendermint_rpc::HttpClient; //! @@ -20,7 +17,7 @@ //! .array(hex_literal::hex!( //! "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF" //! )) -//! .prefix(ASTRIA_ADDRESS_PREFIX) +//! .prefix("astria") //! .try_build() //! .unwrap(); //! let height = 5u32; @@ -435,9 +432,8 @@ pub trait SequencerClientExt: Client { where HeightT: Into + Send, { - const PREFIX: &[u8] = b"accounts/balance/"; - - let path = make_path_from_prefix_and_address(PREFIX, address.bytes()); + const PREFIX: &str = "accounts/balance"; + let path = format!("{PREFIX}/{address}"); let response = self .abci_query(Some(path), vec![], Some(height.into()), false) @@ -518,9 +514,8 @@ pub trait SequencerClientExt: Client { where HeightT: Into + Send, { - const PREFIX: &[u8] = b"accounts/nonce/"; - - let path = make_path_from_prefix_and_address(PREFIX, address.bytes()); + const PREFIX: &str = "accounts/nonce"; + let path = format!("{PREFIX}/{address}"); let response = self .abci_query(Some(path), vec![], Some(height.into()), false) @@ -552,9 +547,8 @@ pub trait SequencerClientExt: Client { &self, address: Address, ) -> Result { - const PREFIX: &[u8] = b"bridge/account_last_tx_hash/"; - - let path = make_path_from_prefix_and_address(PREFIX, address.bytes()); + const PREFIX: &str = "bridge/account_last_tx_hash"; + let path = format!("{PREFIX}/{address}"); let response = self .abci_query(Some(path), vec![], None, false) @@ -617,24 +611,3 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("broadcast_tx_commit", e)) } } - -pub(super) fn make_path_from_prefix_and_address( - prefix: &'static [u8], - address: [u8; 20], -) -> String { - let address_hex_len = address - .len() - .checked_mul(2) - .expect("`20 * 2` should not overflow"); - let path_len = prefix - .len() - .checked_add(address_hex_len) - .expect("`prefix` should not be greater than `usize::MAX - 40`"); - let mut path = vec![0u8; path_len]; - path[..prefix.len()].copy_from_slice(prefix); - hex::encode_to_slice(address, &mut path[prefix.len()..]).expect( - "this is a bug: a buffer of sufficient size must have been allocated to hold 20 hex \ - encoded bytes", - ); - String::from_utf8(path).expect("this is a bug: all bytes in the path buffer should be ascii") -} diff --git a/crates/astria-sequencer-client/src/tests/http.rs b/crates/astria-sequencer-client/src/tests/http.rs index 365f2d8882..3a46df9ae8 100644 --- a/crates/astria-sequencer-client/src/tests/http.rs +++ b/crates/astria-sequencer-client/src/tests/http.rs @@ -7,7 +7,6 @@ use astria_core::{ default_native_asset, }, Address, - ASTRIA_ADDRESS_PREFIX, }, protocol::transaction::v1alpha1::{ action::TransferAction, @@ -46,7 +45,7 @@ use crate::{ const ALICE_ADDRESS_BYTES: [u8; 20] = hex!("1c0c490f1b5528d8173c5de46d131160e4b2c0c3"); const BOB_ADDRESS_BYTES: [u8; 20] = hex!("34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a"); - +const ASTRIA_ADDRESS_PREFIX: &str = "astria"; fn alice_address() -> Address { Address::builder() .array(ALICE_ADDRESS_BYTES) @@ -161,8 +160,7 @@ fn create_signed_transaction() -> SignedTransaction { params: TransactionParams::builder() .nonce(1) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions, } .into_signed(&alice_key) @@ -180,8 +178,12 @@ async fn get_latest_nonce() { height: 10, nonce: 1, }; - let _guard = - register_abci_query_response(&server, "accounts/nonce/", expected_response.clone()).await; + let _guard = register_abci_query_response( + &server, + &format!("accounts/nonce/{}", alice_address()), + expected_response.clone(), + ) + .await; let actual_response = client .get_latest_nonce(alice_address()) @@ -210,8 +212,12 @@ async fn get_latest_balance() { balance: Some(10u128.pow(18).into()), }], }; - let _guard = - register_abci_query_response(&server, "accounts/balance/", expected_response.clone()).await; + let _guard = register_abci_query_response( + &server, + &format!("accounts/balance/{}", alice_address()), + expected_response.clone(), + ) + .await; let actual_response = client .get_latest_balance(alice_address()) diff --git a/crates/astria-sequencer-client/src/tests/mod.rs b/crates/astria-sequencer-client/src/tests/mod.rs index 84296d8748..7a5472a212 100644 --- a/crates/astria-sequencer-client/src/tests/mod.rs +++ b/crates/astria-sequencer-client/src/tests/mod.rs @@ -1,13 +1,2 @@ #[cfg(feature = "http")] mod http; - -#[test] -fn constructing_path_gives_expected_string() { - use super::extension_trait::make_path_from_prefix_and_address; - - const ADDRESS: [u8; 20] = hex_literal::hex!("1c0c490f1b5528d8173c5de46d131160e4b2c0c3"); - const PREFIX: &[u8] = b"a/path/to/an/address/"; - let expected = "a/path/to/an/address/1c0c490f1b5528d8173c5de46d131160e4b2c0c3"; - let actual = make_path_from_prefix_and_address(PREFIX, ADDRESS); - assert_eq!(expected, actual); -} diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index 61f7527ac7..0cd30c17c1 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -50,6 +50,7 @@ serde_json = { workspace = true } sha2 = { workspace = true } tendermint-proto = { workspace = true } tendermint = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true, features = ["rt", "tracing"] } tonic = { workspace = true } tracing = { workspace = true } diff --git a/crates/astria-sequencer/src/accounts/action.rs b/crates/astria-sequencer/src/accounts/action.rs index 585e6cb2bc..1d030084ac 100644 --- a/crates/astria-sequencer/src/accounts/action.rs +++ b/crates/astria-sequencer/src/accounts/action.rs @@ -81,6 +81,11 @@ pub(crate) async fn transfer_check_stateful( #[async_trait::async_trait] impl ActionHandler for TransferAction { + async fn check_stateless(&self) -> Result<()> { + crate::address::ensure_base_prefix(&self.to).context("destination address is invalid")?; + Ok(()) + } + async fn check_stateful( &self, state: &S, diff --git a/crates/astria-sequencer/src/accounts/query.rs b/crates/astria-sequencer/src/accounts/query.rs index c3cfb259c4..4dfe99bb1f 100644 --- a/crates/astria-sequencer/src/accounts/query.rs +++ b/crates/astria-sequencer/src/accounts/query.rs @@ -140,11 +140,9 @@ async fn preprocess_request( ..response::Query::default() }); }; - let address = hex::decode(address) - .context("failed decoding hex encoded bytes") - .and_then(|addr| { - crate::try_astria_address(&addr).context("failed constructing address from bytes") - }) + let address = address + .parse() + .context("failed to parse argument as address") .map_err(|err| response::Query { code: AbciErrorCode::INVALID_PARAMETER.into(), info: AbciErrorCode::INVALID_PARAMETER.to_string(), diff --git a/crates/astria-sequencer/src/accounts/snapshots/astria_sequencer__accounts__state_ext__test__storage_keys_have_not_changed-2.snap b/crates/astria-sequencer/src/accounts/snapshots/astria_sequencer__accounts__state_ext__test__storage_keys_have_not_changed-2.snap new file mode 100644 index 0000000000..df1b0bf0c8 --- /dev/null +++ b/crates/astria-sequencer/src/accounts/snapshots/astria_sequencer__accounts__state_ext__test__storage_keys_have_not_changed-2.snap @@ -0,0 +1,5 @@ +--- +source: crates/astria-sequencer/src/accounts/state_ext.rs +expression: nonce_storage_key(address) +--- +accounts/1c0c490f1b5528d8173c5de46d131160e4b2c0c3/nonce diff --git a/crates/astria-sequencer/src/accounts/snapshots/astria_sequencer__accounts__state_ext__test__storage_keys_have_not_changed.snap b/crates/astria-sequencer/src/accounts/snapshots/astria_sequencer__accounts__state_ext__test__storage_keys_have_not_changed.snap new file mode 100644 index 0000000000..9bdf8ef2fe --- /dev/null +++ b/crates/astria-sequencer/src/accounts/snapshots/astria_sequencer__accounts__state_ext__test__storage_keys_have_not_changed.snap @@ -0,0 +1,5 @@ +--- +source: crates/astria-sequencer/src/accounts/state_ext.rs +expression: "balance_storage_key(address, id)" +--- +accounts/1c0c490f1b5528d8173c5de46d131160e4b2c0c3/balance/000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f diff --git a/crates/astria-sequencer/src/accounts/state_ext.rs b/crates/astria-sequencer/src/accounts/state_ext.rs index 2482d5c0e9..1037cd6a62 100644 --- a/crates/astria-sequencer/src/accounts/state_ext.rs +++ b/crates/astria-sequencer/src/accounts/state_ext.rs @@ -37,20 +37,28 @@ struct Fee(u128); const ACCOUNTS_PREFIX: &str = "accounts"; const TRANSFER_BASE_FEE_STORAGE_KEY: &str = "transferfee"; -fn storage_key(address: &str) -> String { - format!("{ACCOUNTS_PREFIX}/{address}") +struct StorageKey<'a>(&'a Address); + +impl<'a> std::fmt::Display for StorageKey<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(ACCOUNTS_PREFIX)?; + f.write_str("/")?; + for byte in self.0.bytes() { + f.write_fmt(format_args!("{byte:02x}"))?; + } + Ok(()) + } } - fn balance_storage_key(address: Address, asset: asset::Id) -> String { format!( "{}/balance/{}", - storage_key(&address.encode_hex::()), + StorageKey(&address), asset.encode_hex::() ) } fn nonce_storage_key(address: Address) -> String { - format!("{}/nonce", storage_key(&address.encode_hex::())) + format!("{}/nonce", StorageKey(&address)) } #[async_trait] @@ -59,7 +67,7 @@ pub(crate) trait StateReadExt: StateRead { async fn get_account_balances(&self, address: Address) -> Result> { use crate::asset::state_ext::StateReadExt as _; - let prefix = format!("{}/balance/", storage_key(&address.encode_hex::())); + let prefix = format!("{}/balance/", StorageKey(&address)); let mut balances: Vec = Vec::new(); let mut stream = std::pin::pin!(self.prefix_keys(&prefix)); @@ -230,20 +238,30 @@ impl StateWriteExt for T {} #[cfg(test)] mod test { use astria_core::{ - primitive::v1::asset::{ - default_native_asset, - Id, - DEFAULT_NATIVE_ASSET_DENOM, + primitive::v1::{ + asset::{ + default_native_asset, + Id, + DEFAULT_NATIVE_ASSET_DENOM, + }, + Address, }, protocol::account::v1alpha1::AssetBalance, }; use cnidarium::StateDelta; + use insta::assert_snapshot; use super::{ StateReadExt as _, StateWriteExt as _, }; - use crate::asset; + use crate::{ + accounts::state_ext::{ + balance_storage_key, + nonce_storage_key, + }, + asset, + }; #[tokio::test] async fn get_account_nonce_uninitialized_returns_zero() { @@ -252,7 +270,7 @@ mod test { let state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let nonce_expected = 0u32; // uninitialized accounts return zero @@ -273,7 +291,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let nonce_expected = 0u32; // can write new @@ -311,7 +329,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let nonce_expected = 2u32; // can write new @@ -328,7 +346,7 @@ mod test { ); // writing additional account preserves first account's values - let address_1 = crate::astria_address([41u8; 20]); + let address_1 = crate::address::base_prefixed([41u8; 20]); let nonce_expected_1 = 3u32; state @@ -359,7 +377,7 @@ mod test { let state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let asset = Id::from_str_unchecked("asset_0"); let amount_expected = 0u128; @@ -381,7 +399,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let asset = Id::from_str_unchecked("asset_0"); let mut amount_expected = 1u128; @@ -423,7 +441,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let asset = Id::from_str_unchecked("asset_0"); let amount_expected = 1u128; @@ -443,7 +461,7 @@ mod test { // writing to other accounts does not affect original account // create needed variables - let address_1 = crate::astria_address([41u8; 20]); + let address_1 = crate::address::base_prefixed([41u8; 20]); let amount_expected_1 = 2u128; state @@ -476,7 +494,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let asset_0 = Id::from_str_unchecked("asset_0"); let asset_1 = Id::from_str_unchecked("asset_1"); let amount_expected_0 = 1u128; @@ -515,7 +533,7 @@ mod test { let state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); // see that call was ok let balances = state @@ -559,7 +577,7 @@ mod test { .expect("should be able to call other trait method on state object"); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let amount_expected_0 = 1u128; let amount_expected_1 = 2u128; let amount_expected_2 = 3u128; @@ -606,7 +624,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let asset = Id::from_str_unchecked("asset_0"); let amount_increase = 2u128; @@ -647,7 +665,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let asset = Id::from_str_unchecked("asset_0"); let amount_increase = 2u128; @@ -689,7 +707,7 @@ mod test { let mut state = StateDelta::new(snapshot); // create needed variables - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let asset = Id::from_str_unchecked("asset_0"); let amount_increase = 2u128; @@ -705,4 +723,19 @@ mod test { .await .expect_err("should not be able to subtract larger balance than what existed"); } + + #[test] + fn storage_keys_have_not_changed() { + let address: Address = "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" + .parse() + .unwrap(); + let mut next = 0; + let id = astria_core::primitive::v1::asset::Id::new([0u8; 32].map(|_| { + let this = next; + next += 1; + this + })); + assert_snapshot!(balance_storage_key(address, id)); + assert_snapshot!(nonce_storage_key(address)); + } } diff --git a/crates/astria-sequencer/src/address/mod.rs b/crates/astria-sequencer/src/address/mod.rs new file mode 100644 index 0000000000..65a9b00009 --- /dev/null +++ b/crates/astria-sequencer/src/address/mod.rs @@ -0,0 +1,111 @@ +use anyhow::ensure; +use astria_core::primitive::v1::{ + Address, + AddressError, + ADDRESS_LEN, +}; + +mod state_ext; +#[cfg(not(test))] +pub(crate) use regular::*; +pub(crate) use state_ext::{ + StateReadExt, + StateWriteExt, +}; +#[cfg(test)] +pub(crate) use testonly::*; + +pub(crate) fn base_prefixed(arr: [u8; ADDRESS_LEN]) -> Address { + Address::builder() + .array(arr) + .prefix(get_base_prefix()) + .try_build() + .expect("the prefix must have been set as a valid bech32 prefix, so this should never fail") +} + +pub(crate) fn try_base_prefixed(slice: &[u8]) -> Result { + Address::builder() + .slice(slice) + .prefix(get_base_prefix()) + .try_build() +} + +pub(crate) fn ensure_base_prefix(address: &Address) -> anyhow::Result<()> { + ensure!( + get_base_prefix() == address.prefix(), + "address has prefix `{}` but only `{}` is permitted", + address.prefix(), + crate::address::get_base_prefix(), + ); + Ok(()) +} + +#[cfg(not(test))] +mod regular { + //! Logic to be used for a normal debug or release build of sequencer. + + use std::sync::OnceLock; + + use anyhow::Context as _; + + static BASE_PREFIX: OnceLock = OnceLock::new(); + + pub(crate) fn initialize_base_prefix(base_prefix: &str) -> anyhow::Result<()> { + // construct a dummy address to see if we can construct it; fail otherwise. + try_construct_dummy_address_from_prefix(base_prefix) + .context("failed constructing a dummy address from the provided prefix")?; + + BASE_PREFIX.set(base_prefix.to_string()).expect( + "THIS IS A BUG: attempted to set the base prefix more than once; it should only be set + once when serving the `InitChain` consensus request, or immediately after Sequencer is + restarted. It cannot be initialized twice or concurrently from more than one task or \ + thread.", + ); + + Ok(()) + } + + pub(crate) fn get_base_prefix() -> &'static str { + BASE_PREFIX + .get() + .expect( + "the base prefix must have been set while serving the `InitChain` consensus \ + request or upon Sequencer restart; if not set, the chain was initialized \ + incorrectly, or the base prefix not read from storage", + ) + .as_str() + } + + fn try_construct_dummy_address_from_prefix( + s: &str, + ) -> Result<(), astria_core::primitive::v1::AddressError> { + use astria_core::primitive::v1::{ + Address, + ADDRESS_LEN, + }; + // construct a dummy address to see if we can construct it; fail otherwise. + Address::builder() + .array([0u8; ADDRESS_LEN]) + .prefix(s) + .try_build() + .map(|_| ()) + } +} + +#[cfg(test)] +mod testonly { + // allow: this has to match the definition of the non-test function + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn initialize_base_prefix(base_prefix: &str) -> anyhow::Result<()> { + assert_eq!( + base_prefix, + get_base_prefix(), + "all tests should be initialized with a \"astria\" as the base prefix" + ); + Ok(()) + } + + pub(crate) fn get_base_prefix() -> &'static str { + "astria" + } +} diff --git a/crates/astria-sequencer/src/address/state_ext.rs b/crates/astria-sequencer/src/address/state_ext.rs new file mode 100644 index 0000000000..69c8efc8c1 --- /dev/null +++ b/crates/astria-sequencer/src/address/state_ext.rs @@ -0,0 +1,62 @@ +use anyhow::{ + bail, + Context as _, + Result, +}; +use async_trait::async_trait; +use cnidarium::{ + StateRead, + StateWrite, +}; +use tracing::instrument; + +fn base_prefix_key() -> &'static str { + "prefixes/base" +} + +#[async_trait] +pub(crate) trait StateReadExt: StateRead { + #[instrument(skip(self))] + async fn get_base_prefix(&self) -> Result { + let Some(bytes) = self + .get_raw(base_prefix_key()) + .await + .context("failed reading address base prefix")? + else { + bail!("no base prefix found"); + }; + String::from_utf8(bytes).context("prefix retrieved from storage is not valid utf8") + } +} + +impl StateReadExt for T {} + +#[async_trait] +pub(crate) trait StateWriteExt: StateWrite { + #[instrument(skip(self))] + fn put_base_prefix(&mut self, prefix: &str) { + self.put_raw(base_prefix_key().into(), prefix.into()); + } +} + +impl StateWriteExt for T {} + +#[cfg(test)] +mod test { + use cnidarium::StateDelta; + + use super::{ + StateReadExt as _, + StateWriteExt as _, + }; + + #[tokio::test] + async fn put_and_get_base_prefix() { + let storage = cnidarium::TempStorage::new().await.unwrap(); + let snapshot = storage.latest_snapshot(); + let mut state = StateDelta::new(snapshot); + + state.put_base_prefix("astria"); + assert_eq!("astria", &state.get_base_prefix().await.unwrap()); + } +} diff --git a/crates/astria-sequencer/src/api_state_ext.rs b/crates/astria-sequencer/src/api_state_ext.rs index 318e6ecf50..a90332f77f 100644 --- a/crates/astria-sequencer/src/api_state_ext.rs +++ b/crates/astria-sequencer/src/api_state_ext.rs @@ -401,7 +401,7 @@ mod test { let mut deposits = vec![]; for _ in 0..2 { let rollup_id = RollupId::new(rng.gen()); - let bridge_address = crate::astria_address([rng.gen(); 20]); + let bridge_address = crate::address::base_prefixed([rng.gen(); 20]); let amount = rng.gen::(); let asset_id = Id::from_str_unchecked(&rng.gen::().to_string()); let destination_chain_address = rng.gen::().to_string(); diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 835ed045ea..eeb53963f2 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -67,6 +67,7 @@ use crate::{ StateWriteExt as _, }, }, + address::StateWriteExt as _, api_state_ext::StateWriteExt as _, authority::{ component::{ @@ -215,6 +216,10 @@ impl App { .try_begin_transaction() .expect("state Arc should not be referenced elsewhere"); + crate::address::initialize_base_prefix(&genesis_state.address_prefixes.base) + .context("failed setting global base prefix")?; + state_tx.put_base_prefix(&genesis_state.address_prefixes.base); + crate::asset::initialize_native_asset(&genesis_state.native_asset_base_denomination); state_tx.put_native_asset_denom(&genesis_state.native_asset_base_denomination); state_tx.put_chain_id_and_revision_number(chain_id.try_into().context("invalid chain ID")?); @@ -967,7 +972,7 @@ impl App { /// Executes a signed transaction. #[instrument(name = "App::execute_transaction", skip_all, fields( signed_transaction_hash = %telemetry::display::base64(&signed_tx.sha256_of_proto_encoding()), - sender = %signed_tx.address(), + sender_address_bytes = %telemetry::display::base64(&signed_tx.address_bytes()), ))] pub(crate) async fn execute_transaction( &mut self, 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 a67b233e57..8a19543be3 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() --- [ - 41, - 228, - 97, - 117, - 186, - 216, - 14, - 124, - 120, - 230, - 147, - 115, - 248, + 236, + 73, + 13, + 196, + 227, + 141, + 8, + 200, + 204, + 104, + 101, + 173, 226, - 235, - 95, - 212, - 241, - 56, - 39, - 56, - 215, - 180, - 137, - 112, - 49, - 167, - 148, + 22, + 155, + 40, + 102, + 150, + 205, + 99, + 31, + 146, + 109, + 130, + 38, + 208, + 88, + 107, + 26, 152, - 27, - 246, - 58 + 22, + 74 ] diff --git a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap index 60775912aa..6d73e94390 100644 --- a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap +++ b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap @@ -3,36 +3,36 @@ source: crates/astria-sequencer/src/app/tests_breaking_changes.rs expression: app.app_hash.as_bytes() --- [ - 163, - 252, - 79, - 42, + 183, + 90, + 240, + 218, + 162, + 11, + 38, + 107, + 135, + 22, + 241, + 45, + 229, + 255, 59, - 158, - 121, - 89, - 93, - 237, - 94, - 3, - 111, - 78, - 21, - 66, - 163, + 198, + 247, + 33, + 39, + 108, + 41, + 22, + 106, + 29, + 95, + 25, + 90, + 1, + 120, + 134, 130, - 185, - 16, - 233, - 171, - 0, - 214, - 121, - 217, - 138, - 246, - 152, - 176, - 52, - 148 + 94 ] diff --git a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_genesis_snapshot.snap b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_genesis_snapshot.snap index 50e733cecc..41ab56cbc9 100644 --- a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_genesis_snapshot.snap +++ b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_genesis_snapshot.snap @@ -3,36 +3,36 @@ source: crates/astria-sequencer/src/app/tests_breaking_changes.rs expression: app.app_hash.as_bytes() --- [ - 9, - 0, + 65, + 219, + 201, + 82, 255, - 231, - 184, + 77, + 62, + 112, + 134, + 219, + 249, + 17, + 143, + 126, 194, + 58, + 86, + 113, + 222, + 141, + 69, + 59, + 118, + 127, + 121, + 44, 27, - 158, - 152, - 223, - 174, - 96, - 156, - 162, - 246, - 226, - 186, - 211, - 188, - 166, - 27, - 221, - 218, - 7, - 109, - 104, - 172, - 49, - 20, - 96, - 204, - 88 + 128, + 120, + 30, + 110, + 160 ] diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index 15072f61d5..5f972b359f 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -24,7 +24,9 @@ use crate::{ genesis::{ self, Account, + AddressPrefixes, GenesisState, + UncheckedGenesisState, }, mempool::Mempool, metrics::Metrics, @@ -34,7 +36,7 @@ use crate::{ pub(crate) fn address_from_hex_string(s: &str) -> Address { let bytes = hex::decode(s).unwrap(); let arr: [u8; ADDRESS_LEN] = bytes.try_into().unwrap(); - crate::astria_address(arr) + crate::address::base_prefixed(arr) } pub(crate) const ALICE_ADDRESS: &str = "1c0c490f1b5528d8173c5de46d131160e4b2c0c3"; @@ -51,7 +53,7 @@ pub(crate) fn get_alice_signing_key_and_address() -> (SigningKey, Address) { .try_into() .unwrap(); let alice_signing_key = SigningKey::from(alice_secret_bytes); - let alice = crate::astria_address(alice_signing_key.verification_key().address_bytes()); + let alice = crate::address::base_prefixed(alice_signing_key.verification_key().address_bytes()); (alice_signing_key, alice) } @@ -62,7 +64,8 @@ pub(crate) fn get_bridge_signing_key_and_address() -> (SigningKey, Address) { .try_into() .unwrap(); let bridge_signing_key = SigningKey::from(bridge_secret_bytes); - let bridge = crate::astria_address(bridge_signing_key.verification_key().address_bytes()); + let bridge = + crate::address::base_prefixed(bridge_signing_key.verification_key().address_bytes()); (bridge_signing_key, bridge) } @@ -95,6 +98,26 @@ pub(crate) fn default_fees() -> genesis::Fees { } } +pub(crate) fn unchecked_genesis_state() -> UncheckedGenesisState { + UncheckedGenesisState { + accounts: default_genesis_accounts(), + address_prefixes: AddressPrefixes { + base: crate::address::get_base_prefix().to_string(), + }, + authority_sudo_address: address_from_hex_string(JUDY_ADDRESS), + ibc_sudo_address: address_from_hex_string(TED_ADDRESS), + ibc_relayer_addresses: vec![], + native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), + ibc_params: IBCParameters::default(), + allowed_fee_assets: vec![default_native_asset()], + fees: default_fees(), + } +} + +pub(crate) fn genesis_state() -> GenesisState { + unchecked_genesis_state().try_into().unwrap() +} + pub(crate) async fn initialize_app_with_storage( genesis_state: Option, genesis_validators: Vec, @@ -107,16 +130,7 @@ pub(crate) async fn initialize_app_with_storage( let metrics = Box::leak(Box::new(Metrics::new())); let mut app = App::new(snapshot, mempool, metrics).await.unwrap(); - let genesis_state = genesis_state.unwrap_or_else(|| GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: address_from_hex_string(JUDY_ADDRESS), - ibc_sudo_address: address_from_hex_string(TED_ADDRESS), - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - }); + let genesis_state = genesis_state.unwrap_or_else(self::genesis_state); app.init_chain( storage.clone(), @@ -145,8 +159,7 @@ pub(crate) fn get_mock_tx(nonce: u32) -> SignedTransaction { params: TransactionParams::builder() .nonce(nonce) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes([0; 32]), diff --git a/crates/astria-sequencer/src/app/tests_app.rs b/crates/astria-sequencer/src/app/tests_app.rs index 6317c3232f..5884c22654 100644 --- a/crates/astria-sequencer/src/app/tests_app.rs +++ b/crates/astria-sequencer/src/app/tests_app.rs @@ -239,8 +239,7 @@ async fn app_transfer_block_fees_to_sudo() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ TransferAction { to: bob_address, @@ -300,7 +299,7 @@ async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { let (alice_signing_key, _) = get_alice_signing_key_and_address(); let (mut app, storage) = initialize_app_with_storage(None, vec![]).await; - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let asset_id = get_native_asset().id(); @@ -330,8 +329,7 @@ async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![lock_action.into(), sequence_action.into()], }; @@ -391,7 +389,7 @@ async fn app_execution_results_match_proposal_vs_after_proposal() { let (alice_signing_key, _) = get_alice_signing_key_and_address(); let (mut app, storage) = initialize_app_with_storage(None, vec![]).await; - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let asset_id = get_native_asset().id(); @@ -421,8 +419,7 @@ async fn app_execution_results_match_proposal_vs_after_proposal() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![lock_action.into(), sequence_action.into()], }; @@ -546,8 +543,7 @@ async fn app_prepare_proposal_cometbft_max_bytes_overflow_ok() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), @@ -562,8 +558,7 @@ async fn app_prepare_proposal_cometbft_max_bytes_overflow_ok() { params: TransactionParams::builder() .nonce(1) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), @@ -621,8 +616,7 @@ async fn app_prepare_proposal_sequencer_max_bytes_overflow_ok() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), @@ -637,8 +631,7 @@ async fn app_prepare_proposal_sequencer_max_bytes_overflow_ok() { params: TransactionParams::builder() .nonce(1) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from([1u8; 32]), @@ -704,7 +697,7 @@ async fn app_end_block_validator_updates() { ]; let mut app = initialize_app(None, initial_validator_set).await; - let proposer_address = crate::astria_address([0u8; 20]); + let proposer_address = crate::address::base_prefixed([0u8; 20]); let validator_updates = vec![ validator::Update { diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index cf48be5998..72bbd68b5a 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -62,10 +62,32 @@ use crate::{ }, asset::get_native_asset, bridge::state_ext::StateWriteExt as _, - genesis::GenesisState, + genesis::{ + AddressPrefixes, + UncheckedGenesisState, + }, proposal::commitment::generate_rollup_datas_commitment, }; +/// XXX: This should be expressed in terms of `crate::app::test_utils::unchecked_genesis_state` to +/// be consistent everywhere. `get_alice_signing_key` already is, why not this? +fn unchecked_genesis_state() -> UncheckedGenesisState { + let (_, alice_address) = get_alice_signing_key_and_address(); + UncheckedGenesisState { + accounts: vec![], + address_prefixes: AddressPrefixes { + base: crate::address::get_base_prefix().to_string(), + }, + authority_sudo_address: alice_address, + ibc_sudo_address: alice_address, + ibc_relayer_addresses: vec![], + native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), + ibc_params: IBCParameters::default(), + allowed_fee_assets: vec![default_native_asset()], + fees: default_fees(), + } +} + #[tokio::test] async fn app_genesis_snapshot() { let app = initialize_app(None, vec![]).await; @@ -77,7 +99,7 @@ async fn app_finalize_block_snapshot() { let (alice_signing_key, _) = get_alice_signing_key_and_address(); let (mut app, storage) = initialize_app_with_storage(None, vec![]).await; - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let asset_id = get_native_asset().id(); @@ -110,8 +132,7 @@ async fn app_finalize_block_snapshot() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![lock_action.into(), sequence_action.into()], }; @@ -168,7 +189,7 @@ async fn app_execute_transaction_with_every_action_snapshot() { use crate::genesis::Account; - let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); + let (alice_signing_key, _) = get_alice_signing_key_and_address(); let (bridge_signing_key, bridge_address) = get_bridge_signing_key_and_address(); let bob_address = address_from_hex_string(BOB_ADDRESS); let carol_address = address_from_hex_string(CAROL_ADDRESS); @@ -178,16 +199,12 @@ async fn app_execute_transaction_with_every_action_snapshot() { balance: 1_000_000_000, }); - let genesis_state = GenesisState { + let genesis_state = UncheckedGenesisState { accounts, - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - }; + ..unchecked_genesis_state() + } + .try_into() + .unwrap(); let (mut app, storage) = initialize_app_with_storage(Some(genesis_state), vec![]).await; // setup for ValidatorUpdate action @@ -204,8 +221,7 @@ async fn app_execute_transaction_with_every_action_snapshot() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ TransferAction { to: bob_address, @@ -242,8 +258,7 @@ async fn app_execute_transaction_with_every_action_snapshot() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ InitBridgeAccountAction { rollup_id, @@ -262,8 +277,7 @@ async fn app_execute_transaction_with_every_action_snapshot() { params: TransactionParams::builder() .chain_id("test") .nonce(1) - .try_build() - .unwrap(), + .build(), actions: vec![ BridgeLockAction { to: bridge_address, diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index b93ed518e6..87fbcb77df 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -38,7 +38,11 @@ use crate::{ StateReadExt as _, StateWriteExt, }, - genesis::GenesisState, + genesis::{ + AddressPrefixes, + GenesisState, + UncheckedGenesisState, + }, ibc::state_ext::StateReadExt as _, sequence::calculate_fee_from_state, state_ext::StateReadExt as _, @@ -48,6 +52,29 @@ use crate::{ }, }; +/// XXX: This should be expressed in terms of `crate::app::test_utils::unchecked_genesis_state` to +/// be consistent everywhere. `get_alice_sining_key` already is, why not this?? +fn unchecked_genesis_state() -> UncheckedGenesisState { + let (_, alice_address) = get_alice_signing_key_and_address(); + UncheckedGenesisState { + accounts: default_genesis_accounts(), + address_prefixes: AddressPrefixes { + base: crate::address::get_base_prefix().to_string(), + }, + authority_sudo_address: alice_address, + ibc_sudo_address: alice_address, + ibc_relayer_addresses: vec![], + native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), + ibc_params: IBCParameters::default(), + allowed_fee_assets: vec![default_native_asset()], + fees: default_fees(), + } +} + +fn genesis_state() -> GenesisState { + unchecked_genesis_state().try_into().unwrap() +} + #[tokio::test] async fn app_execute_transaction_transfer() { let mut app = initialize_app(None, vec![]).await; @@ -60,8 +87,7 @@ async fn app_execute_transaction_transfer() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ TransferAction { to: bob_address, @@ -118,8 +144,7 @@ async fn app_execute_transaction_transfer_not_native_token() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ TransferAction { to: bob_address, @@ -185,8 +210,7 @@ async fn app_execute_transaction_transfer_balance_too_low_for_fee() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ TransferAction { to: bob, @@ -226,8 +250,7 @@ async fn app_execute_transaction_sequence() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), @@ -264,8 +287,7 @@ async fn app_execute_transaction_invalid_fee_asset() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), @@ -284,17 +306,7 @@ async fn app_execute_transaction_invalid_fee_asset() { async fn app_execute_transaction_validator_update() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - }; - let mut app = initialize_app(Some(genesis_state), vec![]).await; + let mut app = initialize_app(Some(genesis_state()), vec![]).await; let pub_key = tendermint::public_key::PublicKey::from_raw_ed25519(&[1u8; 32]).unwrap(); let update = tendermint::validator::Update { @@ -306,8 +318,7 @@ async fn app_execute_transaction_validator_update() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![Action::ValidatorUpdate(update.clone())], }; @@ -324,24 +335,13 @@ async fn app_execute_transaction_validator_update() { async fn app_execute_transaction_ibc_relayer_change_addition() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - allowed_fee_assets: vec![default_native_asset()], - ibc_params: IBCParameters::default(), - fees: default_fees(), - }; - let mut app = initialize_app(Some(genesis_state), vec![]).await; + let mut app = initialize_app(Some(genesis_state()), vec![]).await; let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![IbcRelayerChangeAction::Addition(alice_address).into()], }; @@ -355,24 +355,19 @@ async fn app_execute_transaction_ibc_relayer_change_addition() { async fn app_execute_transaction_ibc_relayer_change_deletion() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, + let genesis_state = UncheckedGenesisState { ibc_relayer_addresses: vec![alice_address], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - allowed_fee_assets: vec![default_native_asset()], - ibc_params: IBCParameters::default(), - fees: default_fees(), - }; + ..unchecked_genesis_state() + } + .try_into() + .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![IbcRelayerChangeAction::Removal(alice_address).into()], }; @@ -386,24 +381,20 @@ async fn app_execute_transaction_ibc_relayer_change_deletion() { async fn app_execute_transaction_ibc_relayer_change_invalid() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: crate::astria_address([0; 20]), + let genesis_state = UncheckedGenesisState { + ibc_sudo_address: crate::address::base_prefixed([0; 20]), ibc_relayer_addresses: vec![alice_address], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - allowed_fee_assets: vec![default_native_asset()], - ibc_params: IBCParameters::default(), - fees: default_fees(), - }; + ..unchecked_genesis_state() + } + .try_into() + .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![IbcRelayerChangeAction::Removal(alice_address).into()], }; @@ -415,17 +406,7 @@ async fn app_execute_transaction_ibc_relayer_change_invalid() { async fn app_execute_transaction_sudo_address_change() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - }; - let mut app = initialize_app(Some(genesis_state), vec![]).await; + let mut app = initialize_app(Some(genesis_state()), vec![]).await; let new_address = address_from_hex_string(BOB_ADDRESS); @@ -433,8 +414,7 @@ async fn app_execute_transaction_sudo_address_change() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![Action::SudoAddressChange(SudoAddressChangeAction { new_address, })], @@ -451,26 +431,22 @@ async fn app_execute_transaction_sudo_address_change() { #[tokio::test] async fn app_execute_transaction_sudo_address_change_error() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); - let sudo_address = address_from_hex_string(CAROL_ADDRESS); + let authority_sudo_address = address_from_hex_string(CAROL_ADDRESS); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: sudo_address, - ibc_sudo_address: crate::astria_address([0u8; 20]), - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - }; + let genesis_state = UncheckedGenesisState { + authority_sudo_address, + ibc_sudo_address: crate::address::base_prefixed([0u8; 20]), + ..unchecked_genesis_state() + } + .try_into() + .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![Action::SudoAddressChange(SudoAddressChangeAction { new_address: alice_address, })], @@ -492,17 +468,7 @@ async fn app_execute_transaction_fee_asset_change_addition() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - }; - let mut app = initialize_app(Some(genesis_state), vec![]).await; + let mut app = initialize_app(Some(genesis_state()), vec![]).await; let new_asset = asset::Id::from_str_unchecked("test"); @@ -510,8 +476,7 @@ async fn app_execute_transaction_fee_asset_change_addition() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![Action::FeeAssetChange(FeeAssetChangeAction::Addition( new_asset, ))], @@ -531,24 +496,19 @@ async fn app_execute_transaction_fee_asset_change_removal() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); let test_asset = "test".parse::().unwrap(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), + let genesis_state = UncheckedGenesisState { allowed_fee_assets: vec![default_native_asset(), test_asset.clone()], - fees: default_fees(), - }; + ..unchecked_genesis_state() + } + .try_into() + .unwrap(); let mut app = initialize_app(Some(genesis_state), vec![]).await; let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![Action::FeeAssetChange(FeeAssetChangeAction::Removal( test_asset.id(), ))], @@ -570,26 +530,15 @@ async fn app_execute_transaction_fee_asset_change_removal() { async fn app_execute_transaction_fee_asset_change_invalid() { use astria_core::protocol::transaction::v1alpha1::action::FeeAssetChangeAction; - let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); + let (alice_signing_key, _) = get_alice_signing_key_and_address(); - let genesis_state = GenesisState { - accounts: default_genesis_accounts(), - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - }; - let mut app = initialize_app(Some(genesis_state), vec![]).await; + let mut app = initialize_app(Some(genesis_state()), vec![]).await; let tx = UnsignedTransaction { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![Action::FeeAssetChange(FeeAssetChangeAction::Removal( get_native_asset().id(), ))], @@ -629,8 +578,7 @@ async fn app_execute_transaction_init_bridge_account_ok() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![action.into()], }; @@ -687,8 +635,8 @@ async fn app_execute_transaction_init_bridge_account_account_already_registered( params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), + actions: vec![action.into()], }; @@ -706,8 +654,7 @@ async fn app_execute_transaction_init_bridge_account_account_already_registered( params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![action.into()], }; @@ -720,7 +667,7 @@ async fn app_execute_transaction_bridge_lock_action_ok() { let (alice_signing_key, alice_address) = get_alice_signing_key_and_address(); let mut app = initialize_app(None, vec![]).await; - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let asset_id = get_native_asset().id(); @@ -743,8 +690,7 @@ async fn app_execute_transaction_bridge_lock_action_ok() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![action.into()], }; @@ -807,7 +753,7 @@ async fn app_execute_transaction_bridge_lock_action_invalid_for_eoa() { let mut app = initialize_app(None, vec![]).await; // don't actually register this address as a bridge address - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let asset_id = get_native_asset().id(); let amount = 100; @@ -822,8 +768,7 @@ async fn app_execute_transaction_bridge_lock_action_invalid_for_eoa() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![action.into()], }; @@ -843,8 +788,7 @@ async fn app_execute_transaction_invalid_nonce() { params: TransactionParams::builder() .nonce(1) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), @@ -890,8 +834,7 @@ async fn app_execute_transaction_invalid_chain_id() { params: TransactionParams::builder() .nonce(0) .chain_id("wrong-chain") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), @@ -937,7 +880,7 @@ async fn app_stateful_check_fails_insufficient_total_balance() { // create a new key; will have 0 balance let keypair = SigningKey::new(OsRng); - let keypair_address = crate::astria_address(keypair.verification_key().address_bytes()); + let keypair_address = crate::address::base_prefixed(keypair.verification_key().address_bytes()); // figure out needed fee for a single transfer let data = b"hello world".to_vec(); @@ -950,8 +893,7 @@ async fn app_stateful_check_fails_insufficient_total_balance() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ TransferAction { to: keypair_address, @@ -972,8 +914,7 @@ async fn app_stateful_check_fails_insufficient_total_balance() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), @@ -1004,8 +945,7 @@ async fn app_stateful_check_fails_insufficient_total_balance() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), @@ -1061,8 +1001,7 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![action.into()], }; @@ -1084,8 +1023,7 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![action.into()], }; diff --git a/crates/astria-sequencer/src/authority/action.rs b/crates/astria-sequencer/src/authority/action.rs index 6ffb834aa2..c9986d8b45 100644 --- a/crates/astria-sequencer/src/authority/action.rs +++ b/crates/astria-sequencer/src/authority/action.rs @@ -74,6 +74,12 @@ impl ActionHandler for tendermint::validator::Update { #[async_trait::async_trait] impl ActionHandler for SudoAddressChangeAction { + async fn check_stateless(&self) -> Result<()> { + crate::address::ensure_base_prefix(&self.new_address) + .context("desired new sudo address has an unsupported prefix")?; + Ok(()) + } + /// check that the signer of the transaction is the current sudo address, /// as only that address can change the sudo address async fn check_stateful( @@ -194,7 +200,7 @@ mod test { }; fee_change - .execute(&mut state, crate::astria_address([1; 20])) + .execute(&mut state, crate::address::base_prefixed([1; 20])) .await .unwrap(); assert_eq!(state.get_transfer_base_fee().await.unwrap(), 10); @@ -208,7 +214,7 @@ mod test { }; fee_change - .execute(&mut state, crate::astria_address([1; 20])) + .execute(&mut state, crate::address::base_prefixed([1; 20])) .await .unwrap(); assert_eq!(state.get_sequence_action_base_fee().await.unwrap(), 3); @@ -222,7 +228,7 @@ mod test { }; fee_change - .execute(&mut state, crate::astria_address([1; 20])) + .execute(&mut state, crate::address::base_prefixed([1; 20])) .await .unwrap(); assert_eq!( @@ -242,7 +248,7 @@ mod test { }; fee_change - .execute(&mut state, crate::astria_address([1; 20])) + .execute(&mut state, crate::address::base_prefixed([1; 20])) .await .unwrap(); assert_eq!(state.get_init_bridge_account_base_fee().await.unwrap(), 2); @@ -256,7 +262,7 @@ mod test { }; fee_change - .execute(&mut state, crate::astria_address([1; 20])) + .execute(&mut state, crate::address::base_prefixed([1; 20])) .await .unwrap(); assert_eq!( @@ -275,7 +281,7 @@ mod test { }; fee_change - .execute(&mut state, crate::astria_address([1; 20])) + .execute(&mut state, crate::address::base_prefixed([1; 20])) .await .unwrap(); assert_eq!(state.get_ics20_withdrawal_base_fee().await.unwrap(), 2); diff --git a/crates/astria-sequencer/src/authority/state_ext.rs b/crates/astria-sequencer/src/authority/state_ext.rs index 35aceb3c5b..3eaa5c51e6 100644 --- a/crates/astria-sequencer/src/authority/state_ext.rs +++ b/crates/astria-sequencer/src/authority/state_ext.rs @@ -100,7 +100,7 @@ pub(crate) trait StateReadExt: StateRead { }; let SudoAddress(address_bytes) = SudoAddress::try_from_slice(&bytes).context("invalid sudo key bytes")?; - Ok(crate::astria_address(address_bytes)) + Ok(crate::address::base_prefixed(address_bytes)) } #[instrument(skip(self))] @@ -205,7 +205,7 @@ mod test { .expect_err("no sudo address should exist at first"); // can write new - let mut address_expected = crate::astria_address([42u8; 20]); + let mut address_expected = crate::address::base_prefixed([42u8; 20]); state .put_sudo_address(address_expected) .expect("writing sudo address should not fail"); @@ -219,7 +219,7 @@ mod test { ); // can rewrite with new value - address_expected = crate::astria_address([41u8; 20]); + address_expected = crate::address::base_prefixed([41u8; 20]); state .put_sudo_address(address_expected) .expect("writing sudo address should not fail"); diff --git a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs index ff94060810..65ed87ef9e 100644 --- a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs @@ -34,6 +34,12 @@ use crate::{ #[async_trait::async_trait] impl ActionHandler for BridgeLockAction { + async fn check_stateless(&self) -> Result<()> { + crate::address::ensure_base_prefix(&self.to) + .context("destination address has an unsupported prefix")?; + Ok(()) + } + async fn check_stateful( &self, state: &S, @@ -167,7 +173,7 @@ mod test { state.put_transfer_base_fee(transfer_fee).unwrap(); state.put_bridge_lock_byte_cost_multiplier(2); - let bridge_address = crate::astria_address([1; 20]); + let bridge_address = crate::address::base_prefixed([1; 20]); let asset_id = asset::Id::from_str_unchecked("test"); let bridge_lock = BridgeLockAction { to: bridge_address, @@ -184,7 +190,7 @@ mod test { .unwrap(); state.put_allowed_fee_asset(asset_id); - let from_address = crate::astria_address([2; 20]); + let from_address = crate::address::base_prefixed([2; 20]); // not enough balance; should fail state @@ -226,7 +232,7 @@ mod test { state.put_transfer_base_fee(transfer_fee).unwrap(); state.put_bridge_lock_byte_cost_multiplier(2); - let bridge_address = crate::astria_address([1; 20]); + let bridge_address = crate::address::base_prefixed([1; 20]); let asset_id = asset::Id::from_str_unchecked("test"); let bridge_lock = BridgeLockAction { to: bridge_address, @@ -243,7 +249,7 @@ mod test { .unwrap(); state.put_allowed_fee_asset(asset_id); - let from_address = crate::astria_address([2; 20]); + let from_address = crate::address::base_prefixed([2; 20]); // not enough balance; should fail state diff --git a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs index 03c99ab927..177f81078a 100644 --- a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs @@ -24,6 +24,22 @@ use crate::{ #[async_trait::async_trait] impl ActionHandler for BridgeSudoChangeAction { + async fn check_stateless(&self) -> Result<()> { + crate::address::ensure_base_prefix(&self.bridge_address) + .context("bridge address has an unsupported prefix")?; + self.new_sudo_address + .as_ref() + .map(crate::address::ensure_base_prefix) + .transpose() + .context("new sudo address has an unsupported prefix")?; + self.new_withdrawer_address + .as_ref() + .map(crate::address::ensure_base_prefix) + .transpose() + .context("new withdrawer address has an unsupported prefix")?; + Ok(()) + } + async fn check_stateful( &self, state: &S, @@ -95,8 +111,8 @@ mod tests { let asset_id = Id::from_str_unchecked("test"); state.put_allowed_fee_asset(asset_id); - let bridge_address = crate::astria_address([99; 20]); - let sudo_address = crate::astria_address([98; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); + let sudo_address = crate::address::base_prefixed([98; 20]); state.put_bridge_account_sudo_address(&bridge_address, &sudo_address); let action = BridgeSudoChangeAction { @@ -118,8 +134,8 @@ mod tests { let asset_id = Id::from_str_unchecked("test"); state.put_allowed_fee_asset(asset_id); - let bridge_address = crate::astria_address([99; 20]); - let sudo_address = crate::astria_address([98; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); + let sudo_address = crate::address::base_prefixed([98; 20]); state.put_bridge_account_sudo_address(&bridge_address, &sudo_address); let action = BridgeSudoChangeAction { @@ -147,9 +163,9 @@ mod tests { state.put_bridge_sudo_change_base_fee(10); let fee_asset_id = Id::from_str_unchecked("test"); - let bridge_address = crate::astria_address([99; 20]); - let new_sudo_address = crate::astria_address([98; 20]); - let new_withdrawer_address = crate::astria_address([97; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); + let new_sudo_address = crate::address::base_prefixed([98; 20]); + let new_withdrawer_address = crate::address::base_prefixed([97; 20]); state .put_account_balance(bridge_address, fee_asset_id, 10) .unwrap(); diff --git a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs b/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs index 7a12b9fd19..563f2d37ac 100644 --- a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs @@ -25,6 +25,17 @@ use crate::{ #[async_trait::async_trait] impl ActionHandler for BridgeUnlockAction { + async fn check_stateless(&self) -> Result<()> { + crate::address::ensure_base_prefix(&self.to) + .context("destination address has an unsupported prefix")?; + self.bridge_address + .as_ref() + .map(crate::address::ensure_base_prefix) + .transpose() + .context("bridge address has an unsupported prefix")?; + Ok(()) + } + async fn check_stateful( &self, state: &S, @@ -115,8 +126,8 @@ mod test { let asset_id = asset::Id::from_str_unchecked("test"); let transfer_amount = 100; - let address = crate::astria_address([1; 20]); - let to_address = crate::astria_address([2; 20]); + let address = crate::address::base_prefixed([1; 20]); + let to_address = crate::address::base_prefixed([2; 20]); let bridge_unlock = BridgeUnlockAction { to: to_address, @@ -146,10 +157,10 @@ mod test { let asset_id = asset::Id::from_str_unchecked("test"); let transfer_amount = 100; - let sender_address = crate::astria_address([1; 20]); - let to_address = crate::astria_address([2; 20]); + let sender_address = crate::address::base_prefixed([1; 20]); + let to_address = crate::address::base_prefixed([2; 20]); - let bridge_address = crate::astria_address([3; 20]); + let bridge_address = crate::address::base_prefixed([3; 20]); state .put_bridge_account_asset_id(&bridge_address, &asset_id) .unwrap(); @@ -183,11 +194,11 @@ mod test { let asset_id = asset::Id::from_str_unchecked("test"); let transfer_amount = 100; - let sender_address = crate::astria_address([1; 20]); - let to_address = crate::astria_address([2; 20]); + let sender_address = crate::address::base_prefixed([1; 20]); + let to_address = crate::address::base_prefixed([2; 20]); - let bridge_address = crate::astria_address([3; 20]); - let withdrawer_address = crate::astria_address([4; 20]); + let bridge_address = crate::address::base_prefixed([3; 20]); + let withdrawer_address = crate::address::base_prefixed([4; 20]); state.put_bridge_account_withdrawer_address(&bridge_address, &withdrawer_address); state .put_bridge_account_asset_id(&bridge_address, &asset_id) @@ -223,8 +234,8 @@ mod test { let transfer_amount = 100; state.put_transfer_base_fee(transfer_fee).unwrap(); - let bridge_address = crate::astria_address([1; 20]); - let to_address = crate::astria_address([2; 20]); + let bridge_address = crate::address::base_prefixed([1; 20]); + let to_address = crate::address::base_prefixed([2; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); @@ -276,8 +287,8 @@ mod test { let transfer_amount = 100; state.put_transfer_base_fee(transfer_fee).unwrap(); - let bridge_address = crate::astria_address([1; 20]); - let to_address = crate::astria_address([2; 20]); + let bridge_address = crate::address::base_prefixed([1; 20]); + let to_address = crate::address::base_prefixed([2; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); @@ -286,7 +297,7 @@ mod test { .unwrap(); state.put_allowed_fee_asset(asset_id); - let withdrawer_address = crate::astria_address([3; 20]); + let withdrawer_address = crate::address::base_prefixed([3; 20]); state.put_bridge_account_withdrawer_address(&bridge_address, &withdrawer_address); let bridge_unlock = BridgeUnlockAction { @@ -331,8 +342,8 @@ mod test { let transfer_amount = 100; state.put_transfer_base_fee(transfer_fee).unwrap(); - let bridge_address = crate::astria_address([1; 20]); - let to_address = crate::astria_address([2; 20]); + let bridge_address = crate::address::base_prefixed([1; 20]); + let to_address = crate::address::base_prefixed([2; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); @@ -383,8 +394,8 @@ mod test { let transfer_amount = 100; state.put_transfer_base_fee(transfer_fee).unwrap(); - let bridge_address = crate::astria_address([1; 20]); - let to_address = crate::astria_address([2; 20]); + let bridge_address = crate::address::base_prefixed([1; 20]); + let to_address = crate::address::base_prefixed([2; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"test_rollup_id"); state.put_bridge_account_rollup_id(&bridge_address, &rollup_id); diff --git a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs index 101ba9a302..9ac76b112a 100644 --- a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs +++ b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs @@ -28,6 +28,22 @@ use crate::{ #[async_trait::async_trait] impl ActionHandler for InitBridgeAccountAction { + async fn check_stateless(&self) -> Result<()> { + self.withdrawer_address + .as_ref() + .map(crate::address::ensure_base_prefix) + .transpose() + .context("the withdrawer address has an unsupported prefix")?; + + self.sudo_address + .as_ref() + .map(crate::address::ensure_base_prefix) + .transpose() + .context("the sudo address has an unsupported")?; + + Ok(()) + } + async fn check_stateful( &self, state: &S, diff --git a/crates/astria-sequencer/src/bridge/query.rs b/crates/astria-sequencer/src/bridge/query.rs index 65a50303e5..b93268f4c3 100644 --- a/crates/astria-sequencer/src/bridge/query.rs +++ b/crates/astria-sequencer/src/bridge/query.rs @@ -86,11 +86,9 @@ fn preprocess_request(params: &[(String, String)]) -> anyhow::Result String { - format!("{BRIDGE_ACCOUNT_PREFIX}/{address}") +struct BridgeAccountKey<'a> { + prefix: &'static str, + address: &'a Address, +} + +impl<'a> std::fmt::Display for BridgeAccountKey<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.prefix)?; + f.write_str("/")?; + for byte in self.address.bytes() { + f.write_fmt(format_args!("{byte:02x}"))?; + } + Ok(()) + } } fn rollup_id_storage_key(address: &Address) -> String { format!( "{}/rollupid", - bridge_account_storage_key(&address.encode_hex::()) + BridgeAccountKey { + prefix: BRIDGE_ACCOUNT_PREFIX, + address + } ) } fn asset_id_storage_key(address: &Address) -> String { format!( "{}/assetid", - bridge_account_storage_key(&address.encode_hex::()) + BridgeAccountKey { + prefix: BRIDGE_ACCOUNT_PREFIX, + address + } ) } @@ -96,17 +113,32 @@ fn deposit_nonce_storage_key(rollup_id: &RollupId) -> Vec { } fn bridge_account_sudo_address_storage_key(address: &Address) -> String { - format!("{BRIDGE_ACCOUNT_SUDO_PREFIX}/{address}") + format!( + "{}", + BridgeAccountKey { + prefix: BRIDGE_ACCOUNT_SUDO_PREFIX, + address + } + ) } fn bridge_account_withdrawer_address_storage_key(address: &Address) -> String { - format!("{BRIDGE_ACCOUNT_WITHDRAWER_PREFIX}/{address}") + format!( + "{}", + BridgeAccountKey { + prefix: BRIDGE_ACCOUNT_WITHDRAWER_PREFIX, + address + } + ) } fn last_transaction_hash_for_bridge_account_storage_key(address: &Address) -> Vec { format!( "{}/lasttx", - bridge_account_storage_key(&address.encode_hex::()) + BridgeAccountKey { + prefix: BRIDGE_ACCOUNT_PREFIX, + address + } ) .as_bytes() .to_vec() @@ -155,10 +187,7 @@ pub(crate) trait StateReadExt: StateRead { return Ok(None); }; - let sudo_address = Address::builder() - .slice(sudo_address_bytes) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() + let sudo_address = crate::address::try_base_prefixed(&sudo_address_bytes) .context("invalid sudo address bytes")?; Ok(Some(sudo_address)) } @@ -179,10 +208,7 @@ pub(crate) trait StateReadExt: StateRead { return Ok(None); }; - let withdrawer_address = Address::builder() - .slice(withdrawer_address_bytes) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() + let withdrawer_address = crate::address::try_base_prefixed(&withdrawer_address_bytes) .context("invalid withdrawer address bytes")?; Ok(Some(withdrawer_address)) } @@ -450,13 +476,19 @@ mod test { use astria_core::{ primitive::v1::{ asset::Id, + Address, RollupId, }, sequencerblock::v1alpha1::block::Deposit, }; use cnidarium::StateDelta; + use insta::assert_snapshot; use super::{ + asset_id_storage_key, + bridge_account_sudo_address_storage_key, + bridge_account_withdrawer_address_storage_key, + rollup_id_storage_key, StateReadExt as _, StateWriteExt as _, }; @@ -467,7 +499,7 @@ mod test { let snapshot = storage.latest_snapshot(); let state = StateDelta::new(snapshot); - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); // uninitialized ok assert_eq!( @@ -486,7 +518,7 @@ mod test { let mut state = StateDelta::new(snapshot); let mut rollup_id = RollupId::new([1u8; 32]); - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); // can write new state.put_bridge_account_rollup_id(&address, &rollup_id); @@ -515,7 +547,7 @@ mod test { // can write additional account and both valid let rollup_id_1 = RollupId::new([2u8; 32]); - let address_1 = crate::astria_address([41u8; 20]); + let address_1 = crate::address::base_prefixed([41u8; 20]); state.put_bridge_account_rollup_id(&address_1, &rollup_id_1); assert_eq!( state @@ -544,7 +576,7 @@ mod test { let snapshot = storage.latest_snapshot(); let state = StateDelta::new(snapshot); - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); state .get_bridge_account_asset_id(&address) .await @@ -557,7 +589,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); let mut asset = Id::from_str_unchecked("asset_0"); // can write @@ -588,7 +620,7 @@ mod test { ); // writing to other account also ok - let address_1 = crate::astria_address([41u8; 20]); + let address_1 = crate::address::base_prefixed([41u8; 20]); let asset_1 = Id::from_str_unchecked("asset_0"); state .put_bridge_account_asset_id(&address_1, &asset_1) @@ -711,7 +743,7 @@ mod test { let mut state = StateDelta::new(snapshot); let rollup_id = RollupId::new([1u8; 32]); - let bridge_address = crate::astria_address([42u8; 20]); + let bridge_address = crate::address::base_prefixed([42u8; 20]); let mut amount = 10u128; let asset = Id::from_str_unchecked("asset_0"); let destination_chain_address = "0xdeadbeef"; @@ -823,7 +855,7 @@ mod test { let mut state = StateDelta::new(snapshot); let rollup_id_0 = RollupId::new([1u8; 32]); - let bridge_address = crate::astria_address([42u8; 20]); + let bridge_address = crate::address::base_prefixed([42u8; 20]); let amount = 10u128; let asset = Id::from_str_unchecked("asset_0"); let destination_chain_address = "0xdeadbeef"; @@ -894,7 +926,7 @@ mod test { let mut state = StateDelta::new(snapshot); let rollup_id = RollupId::new([1u8; 32]); - let bridge_address = crate::astria_address([42u8; 20]); + let bridge_address = crate::address::base_prefixed([42u8; 20]); let amount = 10u128; let asset = Id::from_str_unchecked("asset_0"); let destination_chain_address = "0xdeadbeef"; @@ -949,7 +981,7 @@ mod test { let mut state = StateDelta::new(snapshot); let rollup_id = RollupId::new([1u8; 32]); - let bridge_address = crate::astria_address([42u8; 20]); + let bridge_address = crate::address::base_prefixed([42u8; 20]); let amount = 10u128; let asset = Id::from_str_unchecked("asset_0"); let destination_chain_address = "0xdeadbeef"; @@ -1041,7 +1073,7 @@ mod test { let mut state = StateDelta::new(snapshot); let rollup_id = RollupId::new([1u8; 32]); - let bridge_address = crate::astria_address([42u8; 20]); + let bridge_address = crate::address::base_prefixed([42u8; 20]); let amount = 10u128; let asset = Id::from_str_unchecked("asset_0"); let destination_chain_address = "0xdeadbeef"; @@ -1112,4 +1144,16 @@ mod test { "nonce should have been deleted also" ); } + + #[test] + fn storage_keys_have_not_changed() { + let address: Address = "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" + .parse() + .unwrap(); + + assert_snapshot!(rollup_id_storage_key(&address)); + assert_snapshot!(asset_id_storage_key(&address)); + assert_snapshot!(bridge_account_sudo_address_storage_key(&address)); + assert_snapshot!(bridge_account_withdrawer_address_storage_key(&address)); + } } diff --git a/crates/astria-sequencer/src/config.rs b/crates/astria-sequencer/src/config.rs index edcbb17e91..234a67ca5e 100644 --- a/crates/astria-sequencer/src/config.rs +++ b/crates/astria-sequencer/src/config.rs @@ -5,8 +5,6 @@ use serde::{ Serialize, }; -pub(crate) const ADDRESS_PREFIX: &str = "astria"; - // Allowed `struct_excessive_bools` because this is used as a container // for deserialization. Making this a builder-pattern is not actionable. #[allow(clippy::struct_excessive_bools)] diff --git a/crates/astria-sequencer/src/genesis.rs b/crates/astria-sequencer/src/genesis.rs index 54081e9c32..e484fc0781 100644 --- a/crates/astria-sequencer/src/genesis.rs +++ b/crates/astria-sequencer/src/genesis.rs @@ -4,29 +4,157 @@ use astria_core::primitive::v1::{ }; use penumbra_ibc::params::IBCParameters; use serde::{ - de::Error as _, Deserialize, - Deserializer, + Serialize, }; /// The genesis state for the application. -#[derive(Debug, Deserialize)] +/// +/// Verified to only contain valid fields (right now, addresses that have the same base prefix +/// as set in `GenesisState::address_prefixes::base`). +/// +/// **NOTE:** The fields should not be publicly accessible to guarantee invariants. However, +/// it's easy to just go along with this for now. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(try_from = "UncheckedGenesisState", into = "UncheckedGenesisState")] pub(crate) struct GenesisState { + pub(crate) address_prefixes: AddressPrefixes, pub(crate) accounts: Vec, - #[serde(deserialize_with = "deserialize_address")] pub(crate) authority_sudo_address: Address, - #[serde(deserialize_with = "deserialize_address")] pub(crate) ibc_sudo_address: Address, - #[serde(deserialize_with = "deserialize_addresses")] pub(crate) ibc_relayer_addresses: Vec
, pub(crate) native_asset_base_denomination: String, pub(crate) ibc_params: IBCParameters, - #[serde(deserialize_with = "deserialize_assets")] pub(crate) allowed_fee_assets: Vec, pub(crate) fees: Fees, } -#[derive(Debug, Deserialize)] +#[derive(Debug, thiserror::Error)] +// allow: this error is only seen at chain init and never after so perf impact of too large enum +// variants is negligible +#[allow(clippy::result_large_err)] +pub(crate) enum VerifyGenesisError { + #[error("address `{address}` at `{field}` does not have `{base_prefix}`")] + AddressDoesNotMatchBase { + base_prefix: String, + address: Address, + field: String, + }, +} + +impl TryFrom for GenesisState { + type Error = VerifyGenesisError; + + fn try_from(value: UncheckedGenesisState) -> Result { + value.ensure_all_addresses_have_base_prefix()?; + + let UncheckedGenesisState { + address_prefixes, + accounts, + authority_sudo_address, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_params, + allowed_fee_assets, + fees, + } = value; + + Ok(Self { + address_prefixes, + accounts, + authority_sudo_address, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_params, + allowed_fee_assets, + fees, + }) + } +} + +/// The unchecked genesis state for the application. +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct UncheckedGenesisState { + pub(crate) address_prefixes: AddressPrefixes, + pub(crate) accounts: Vec, + pub(crate) authority_sudo_address: Address, + pub(crate) ibc_sudo_address: Address, + pub(crate) ibc_relayer_addresses: Vec
, + pub(crate) native_asset_base_denomination: String, + pub(crate) ibc_params: IBCParameters, + pub(crate) allowed_fee_assets: Vec, + pub(crate) fees: Fees, +} + +impl UncheckedGenesisState { + // allow: as for the enum definition itself: this only happens at init-chain and is negligible + #[allow(clippy::result_large_err)] + fn ensure_address_has_base_prefix( + &self, + address: &Address, + field: &str, + ) -> Result<(), VerifyGenesisError> { + if self.address_prefixes.base != address.prefix() { + return Err(VerifyGenesisError::AddressDoesNotMatchBase { + base_prefix: self.address_prefixes.base.clone(), + address: *address, + field: field.to_string(), + }); + } + Ok(()) + } + + // allow: as for the enum definition itself: this only happens at init-chain and is negligible + #[allow(clippy::result_large_err)] + fn ensure_all_addresses_have_base_prefix(&self) -> Result<(), VerifyGenesisError> { + for (i, account) in self.accounts.iter().enumerate() { + self.ensure_address_has_base_prefix( + &account.address, + &format!(".accounts[{i}].address"), + )?; + } + self.ensure_address_has_base_prefix( + &self.authority_sudo_address, + ".authority_sudo_address", + )?; + self.ensure_address_has_base_prefix(&self.ibc_sudo_address, ".ibc_sudo_address")?; + for (i, address) in self.ibc_relayer_addresses.iter().enumerate() { + self.ensure_address_has_base_prefix(address, &format!(".ibc_relayer_addresses[{i}]"))?; + } + Ok(()) + } +} + +impl From for UncheckedGenesisState { + fn from(value: GenesisState) -> Self { + let GenesisState { + address_prefixes, + accounts, + authority_sudo_address, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_params, + allowed_fee_assets, + fees, + } = value; + Self { + address_prefixes, + accounts, + authority_sudo_address, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_params, + allowed_fee_assets, + fees, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct Fees { pub(crate) transfer_base_fee: u128, pub(crate) sequence_base_fee: u128, @@ -37,100 +165,162 @@ pub(crate) struct Fees { pub(crate) ics20_withdrawal_base_fee: u128, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct Account { - #[serde(deserialize_with = "deserialize_address")] pub(crate) address: Address, pub(crate) balance: u128, } -fn deserialize_address<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - use serde::de::Error as _; - let bytes: Vec = hex::serde::deserialize(deserializer)?; - crate::try_astria_address(&bytes) - .map_err(|e| D::Error::custom(format!("failed constructing address from bytes: {e:?}"))) -} - -fn deserialize_addresses<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let address_strings = serde_json::Value::deserialize(deserializer)?; - let address_strings = address_strings - .as_array() - .ok_or(D::Error::custom("expected array of strings"))?; - - address_strings - .iter() - .map(|s| { - let s = s.as_str().ok_or(D::Error::custom("expected string"))?; - let bytes: Vec = hex::decode(s) - .map_err(|e| D::Error::custom(format!("failed decoding hex string: {e}")))?; - crate::try_astria_address(&bytes).map_err(|e| { - D::Error::custom(format!("failed constructing address from bytes: {e:?}")) - }) - }) - .collect() -} - -fn deserialize_assets<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let strings: Vec = serde::Deserialize::deserialize(deserializer)?; - strings - .iter() - .map(|s| s.parse()) - .collect::>() - .map_err(|e| D::Error::custom(format!("failed parsing asset: {e:?}"))) +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct AddressPrefixes { + pub(crate) base: String, } #[cfg(test)] mod test { + use astria_core::primitive::v1::Address; + use super::*; - #[test] - fn genesis_deserialize_addresses() { - let genesis_str = r#"{ - "accounts": [ - { - "address": "1c0c490f1b5528d8173c5de46d131160e4b2c0c3", - "balance": 1000000000000000000 - }, - { - "address": "34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a", - "balance": 1000000000000000000 - }, - { - "address": "60709e2d391864b732b4f0f51e387abb76743871", - "balance": 1000000000000000000 - } + const ASTRIA_ADDRESS_PREFIX: &str = "astria"; + + fn alice() -> Address { + Address::builder() + .prefix(ASTRIA_ADDRESS_PREFIX) + .slice(hex::decode("1c0c490f1b5528d8173c5de46d131160e4b2c0c3").unwrap()) + .try_build() + .unwrap() + } + + fn bob() -> Address { + Address::builder() + .prefix(ASTRIA_ADDRESS_PREFIX) + .slice(hex::decode("34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a").unwrap()) + .try_build() + .unwrap() + } + + fn charlie() -> Address { + Address::builder() + .prefix(ASTRIA_ADDRESS_PREFIX) + .slice(hex::decode("60709e2d391864b732b4f0f51e387abb76743871").unwrap()) + .try_build() + .unwrap() + } + + fn mallory() -> Address { + Address::builder() + .prefix("other") + .slice(hex::decode("60709e2d391864b732b4f0f51e387abb76743871").unwrap()) + .try_build() + .unwrap() + } + + fn unchecked_genesis_state() -> UncheckedGenesisState { + UncheckedGenesisState { + accounts: vec![ + Account { + address: alice(), + balance: 1_000_000_000_000_000_000, + }, + Account { + address: bob(), + balance: 1_000_000_000_000_000_000, + }, + Account { + address: charlie(), + balance: 1_000_000_000_000_000_000, + }, ], - "authority_sudo_address": "1c0c490f1b5528d8173c5de46d131160e4b2c0c3", - "ibc_sudo_address": "1c0c490f1b5528d8173c5de46d131160e4b2c0c3", - "ibc_relayer_addresses": ["1c0c490f1b5528d8173c5de46d131160e4b2c0c3", "34fec43c7fcab9aef3b3cf8aba855e41ee69ca3a"], - "ibc_params": { - "ibc_enabled": true, - "inbound_ics20_transfers_enabled": true, - "outbound_ics20_transfers_enabled": true + address_prefixes: AddressPrefixes { + base: "astria".into(), + }, + authority_sudo_address: alice(), + ibc_sudo_address: alice(), + ibc_relayer_addresses: vec![alice(), bob()], + native_asset_base_denomination: "nria".to_string(), + ibc_params: IBCParameters { + ibc_enabled: true, + inbound_ics20_transfers_enabled: true, + outbound_ics20_transfers_enabled: true, + }, + allowed_fee_assets: vec!["nria".parse().unwrap()], + fees: Fees { + transfer_base_fee: 12, + sequence_base_fee: 32, + sequence_byte_cost_multiplier: 1, + init_bridge_account_base_fee: 48, + bridge_lock_byte_cost_multiplier: 1, + bridge_sudo_change_fee: 24, + ics20_withdrawal_base_fee: 24, + }, + } + } + + fn genesis_state() -> GenesisState { + unchecked_genesis_state().try_into().unwrap() + } + + #[test] + fn mismatched_addresses_are_caught() { + #[track_caller] + fn assert_bad_prefix(unchecked: UncheckedGenesisState, bad_field: &'static str) { + match GenesisState::try_from(unchecked).expect_err( + "converting to genesis state should have produced an error, but a valid state was \ + returned", + ) { + VerifyGenesisError::AddressDoesNotMatchBase { + base_prefix, + address, + field, + } => { + assert_eq!(base_prefix, ASTRIA_ADDRESS_PREFIX); + assert_eq!(address, mallory()); + assert_eq!(field, bad_field); + } + }; + } + assert_bad_prefix( + UncheckedGenesisState { + authority_sudo_address: mallory(), + ..unchecked_genesis_state() + }, + ".authority_sudo_address", + ); + assert_bad_prefix( + UncheckedGenesisState { + ibc_sudo_address: mallory(), + ..unchecked_genesis_state() + }, + ".ibc_sudo_address", + ); + assert_bad_prefix( + UncheckedGenesisState { + ibc_relayer_addresses: vec![alice(), mallory()], + ..unchecked_genesis_state() }, - "fees": { - "transfer_base_fee": 12, - "sequence_base_fee": 32, - "sequence_byte_cost_multiplier": 1, - "init_bridge_account_base_fee": 48, - "bridge_lock_byte_cost_multiplier": 1, - "bridge_sudo_change_fee": 24, - "ics20_withdrawal_base_fee": 24 + ".ibc_relayer_addresses[1]", + ); + assert_bad_prefix( + UncheckedGenesisState { + accounts: vec![ + Account { + address: alice(), + balance: 10, + }, + Account { + address: mallory(), + balance: 10, + }, + ], + ..unchecked_genesis_state() }, - "native_asset_base_denomination": "nria", - "allowed_fee_assets": ["nria"] - } - "#; - let genesis: GenesisState = serde_json::from_str(genesis_str).unwrap(); - assert_eq!(genesis.ibc_relayer_addresses.len(), 2); + ".accounts[1].address", + ); + } + + #[test] + fn genesis_state_is_unchanged() { + insta::assert_json_snapshot!(genesis_state()); } } diff --git a/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs b/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs index b9a12b17c2..f60f8726bd 100644 --- a/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs +++ b/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs @@ -23,6 +23,17 @@ use crate::{ #[async_trait] impl ActionHandler for IbcRelayerChangeAction { + async fn check_stateless(&self) -> Result<()> { + match self { + IbcRelayerChangeAction::Addition(addr) | IbcRelayerChangeAction::Removal(addr) => { + crate::address::ensure_base_prefix(addr) + .context("provided address to be added or removed has an unsupported prefix")?; + } + } + + Ok(()) + } + async fn check_stateful(&self, state: &S, from: Address) -> Result<()> { let ibc_sudo_address = state .get_ibc_sudo_address() diff --git a/crates/astria-sequencer/src/ibc/ics20_transfer.rs b/crates/astria-sequencer/src/ibc/ics20_transfer.rs index dee59e653c..2182dad612 100644 --- a/crates/astria-sequencer/src/ibc/ics20_transfer.rs +++ b/crates/astria-sequencer/src/ibc/ics20_transfer.rs @@ -429,7 +429,7 @@ async fn execute_ics20_transfer( } // the IBC packet should have the address as a bech32 string - let recipient = Address::try_from_bech32m(&recipient).context("invalid recipient address")?; + let recipient = recipient.parse().context("invalid recipient address")?; let is_prefixed = denom_trace.starts_with_str(&format!("{source_port}/{source_channel}")); let is_source = if is_refund { @@ -716,7 +716,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state_tx = StateDelta::new(snapshot.clone()); - let recipient = crate::try_astria_address( + let recipient = crate::address::try_base_prefixed( &hex::decode("1c0c490f1b5528d8173c5de46d131160e4b2c0c3").unwrap(), ) .unwrap(); @@ -758,7 +758,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state_tx = StateDelta::new(snapshot.clone()); - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let denom = "dest_port/dest_channel/nootasset".parse::().unwrap(); @@ -815,7 +815,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state_tx = StateDelta::new(snapshot.clone()); - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let denom = "dest_port/dest_channel/nootasset".parse::().unwrap(); @@ -853,7 +853,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state_tx = StateDelta::new(snapshot.clone()); - let bridge_address = crate::astria_address([99; 20]); + let bridge_address = crate::address::base_prefixed([99; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let denom = "dest_port/dest_channel/nootasset".parse::().unwrap(); @@ -891,7 +891,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state_tx = StateDelta::new(snapshot.clone()); - let recipient_address = crate::astria_address([1; 20]); + let recipient_address = crate::address::base_prefixed([1; 20]); let amount = 100; let base_denom = "nootasset".parse::().unwrap(); state_tx @@ -944,7 +944,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state_tx = StateDelta::new(snapshot.clone()); - let recipient_address = crate::astria_address([1; 20]); + let recipient_address = crate::address::base_prefixed([1; 20]); let amount = 100; let base_denom = "nootasset".parse::().unwrap(); state_tx @@ -997,7 +997,7 @@ mod test { let snapshot = storage.latest_snapshot(); let mut state_tx = StateDelta::new(snapshot.clone()); - let bridge_address = crate::astria_address([99u8; 20]); + let bridge_address = crate::address::base_prefixed([99u8; 20]); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); let denom = "dest_port/dest_channel/nootasset" .parse::() @@ -1040,7 +1040,7 @@ mod test { let mut state_tx = StateDelta::new(snapshot.clone()); let destination_chain_address = "destinationchainaddress".to_string(); - let bridge_address = crate::astria_address([99u8; 20]); + let bridge_address = crate::address::base_prefixed([99u8; 20]); let denom = "nootasset".parse::().unwrap(); let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); diff --git a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs index 39f963c19d..4fbcfe1572 100644 --- a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs +++ b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs @@ -102,6 +102,14 @@ impl ActionHandler for action::Ics20Withdrawal { async fn check_stateless(&self) -> Result<()> { ensure!(self.timeout_time() != 0, "timeout time must be non-zero",); + crate::address::ensure_base_prefix(&self.return_address) + .context("return address has an unsupported prefix")?; + self.bridge_address + .as_ref() + .map(crate::address::ensure_base_prefix) + .transpose() + .context("bridge address has an unsupported prefix")?; + // NOTE (from penumbra): we could validate the destination chain address as bech32 to // prevent mistyped addresses, but this would preclude sending to chains that don't // use bech32 addresses. @@ -237,7 +245,7 @@ mod tests { let state = StateDelta::new(snapshot); let denom = "test".parse::().unwrap(); - let from = crate::astria_address([1u8; 20]); + let from = crate::address::base_prefixed([1u8; 20]); let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), @@ -264,7 +272,7 @@ mod tests { let mut state = StateDelta::new(snapshot); // sender is a bridge address, which is also the withdrawer, so it's ok - let bridge_address = crate::astria_address([1u8; 20]); + let bridge_address = crate::address::base_prefixed([1u8; 20]); state.put_bridge_account_rollup_id( &bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), @@ -298,14 +306,14 @@ mod tests { let mut state = StateDelta::new(snapshot); // withdraw is *not* the bridge address, Ics20Withdrawal must be sent by the withdrawer - let bridge_address = crate::astria_address([1u8; 20]); + let bridge_address = crate::address::base_prefixed([1u8; 20]); state.put_bridge_account_rollup_id( &bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), ); state.put_bridge_account_withdrawer_address( &bridge_address, - &crate::astria_address([2u8; 20]), + &crate::address::base_prefixed([2u8; 20]), ); let denom = "test".parse::().unwrap(); @@ -338,8 +346,8 @@ mod tests { let mut state = StateDelta::new(snapshot); // sender the withdrawer address, so it's ok - let bridge_address = crate::astria_address([1u8; 20]); - let withdrawer_address = crate::astria_address([2u8; 20]); + let bridge_address = crate::address::base_prefixed([1u8; 20]); + let withdrawer_address = crate::address::base_prefixed([2u8; 20]); state.put_bridge_account_rollup_id( &bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), @@ -372,8 +380,8 @@ mod tests { let mut state = StateDelta::new(snapshot); // sender is not the withdrawer address, so must fail - let bridge_address = crate::astria_address([1u8; 20]); - let withdrawer_address = crate::astria_address([2u8; 20]); + let bridge_address = crate::address::base_prefixed([1u8; 20]); + let withdrawer_address = crate::address::base_prefixed([2u8; 20]); state.put_bridge_account_rollup_id( &bridge_address, &RollupId::from_unhashed_bytes("testrollupid"), @@ -411,7 +419,7 @@ mod tests { let state = StateDelta::new(snapshot); // sender is not the withdrawer address, so must fail - let not_bridge_address = crate::astria_address([1u8; 20]); + let not_bridge_address = crate::address::base_prefixed([1u8; 20]); let denom = "test".parse::().unwrap(); let action = action::Ics20Withdrawal { diff --git a/crates/astria-sequencer/src/ibc/snapshots/astria_sequencer__ibc__state_ext__test__storage_keys_have_not_changed.snap b/crates/astria-sequencer/src/ibc/snapshots/astria_sequencer__ibc__state_ext__test__storage_keys_have_not_changed.snap new file mode 100644 index 0000000000..2358f2e37d --- /dev/null +++ b/crates/astria-sequencer/src/ibc/snapshots/astria_sequencer__ibc__state_ext__test__storage_keys_have_not_changed.snap @@ -0,0 +1,5 @@ +--- +source: crates/astria-sequencer/src/ibc/state_ext.rs +expression: "super::ibc_relayer_key(&address)" +--- +ibc-relayer/1c0c490f1b5528d8173c5de46d131160e4b2c0c3 diff --git a/crates/astria-sequencer/src/ibc/state_ext.rs b/crates/astria-sequencer/src/ibc/state_ext.rs index a016dc9453..fe7fcda768 100644 --- a/crates/astria-sequencer/src/ibc/state_ext.rs +++ b/crates/astria-sequencer/src/ibc/state_ext.rs @@ -39,6 +39,19 @@ struct Fee(u128); const IBC_SUDO_STORAGE_KEY: &str = "ibcsudo"; const ICS20_WITHDRAWAL_BASE_FEE_STORAGE_KEY: &str = "ics20withdrawalfee"; +struct IbcRelayerKey<'a>(&'a Address); + +impl<'a> std::fmt::Display for IbcRelayerKey<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("ibc-relayer")?; + f.write_str("/")?; + for byte in self.0.bytes() { + f.write_fmt(format_args!("{byte:02x}"))?; + } + Ok(()) + } +} + fn channel_balance_storage_key(channel: &ChannelId, asset: asset::Id) -> String { format!( "ibc-data/{channel}/balance/{}", @@ -47,7 +60,7 @@ fn channel_balance_storage_key(channel: &ChannelId, asset: asset::Id) -> String } fn ibc_relayer_key(address: &Address) -> String { - format!("ibc-relayer/{}", address.encode_hex::()) + IbcRelayerKey(address).to_string() } #[async_trait] @@ -78,7 +91,7 @@ pub(crate) trait StateReadExt: StateRead { }; let SudoAddress(address_bytes) = SudoAddress::try_from_slice(&bytes).context("invalid ibc sudo key bytes")?; - Ok(crate::astria_address(address_bytes)) + Ok(crate::address::base_prefixed(address_bytes)) } #[instrument(skip(self))] @@ -154,9 +167,13 @@ impl StateWriteExt for T {} #[cfg(test)] mod test { - use astria_core::primitive::v1::asset::Id; + use astria_core::primitive::v1::{ + asset::Id, + Address, + }; use cnidarium::StateDelta; use ibc_types::core::channel::ChannelId; + use insta::assert_snapshot; use super::{ StateReadExt as _, @@ -183,7 +200,7 @@ mod test { let mut state = StateDelta::new(snapshot); // can write new - let mut address = crate::astria_address([42u8; 20]); + let mut address = crate::address::base_prefixed([42u8; 20]); state .put_ibc_sudo_address(address) .expect("writing sudo address should not fail"); @@ -197,7 +214,7 @@ mod test { ); // can rewrite with new value - address = crate::astria_address([41u8; 20]); + address = crate::address::base_prefixed([41u8; 20]); state .put_ibc_sudo_address(address) .expect("writing sudo address should not fail"); @@ -218,7 +235,7 @@ mod test { let state = StateDelta::new(snapshot); // unset address returns false - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); assert!( !state .is_ibc_relayer(&address) @@ -235,7 +252,7 @@ mod test { let mut state = StateDelta::new(snapshot); // can write - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); state.put_ibc_relayer_address(&address); assert!( state @@ -263,7 +280,7 @@ mod test { let mut state = StateDelta::new(snapshot); // can write - let address = crate::astria_address([42u8; 20]); + let address = crate::address::base_prefixed([42u8; 20]); state.put_ibc_relayer_address(&address); assert!( state @@ -274,7 +291,7 @@ mod test { ); // can write multiple - let address_1 = crate::astria_address([41u8; 20]); + let address_1 = crate::address::base_prefixed([41u8; 20]); state.put_ibc_relayer_address(&address_1); assert!( state @@ -422,4 +439,13 @@ mod test { "set balance for channel/asset pair not what was expected" ); } + + #[test] + fn storage_keys_have_not_changed() { + let address: Address = "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" + .parse() + .unwrap(); + + assert_snapshot!(super::ibc_relayer_key(&address)); + } } diff --git a/crates/astria-sequencer/src/lib.rs b/crates/astria-sequencer/src/lib.rs index 0425796406..c410f94725 100644 --- a/crates/astria-sequencer/src/lib.rs +++ b/crates/astria-sequencer/src/lib.rs @@ -1,4 +1,5 @@ pub(crate) mod accounts; +pub(crate) mod address; mod api_state_ext; pub(crate) mod app; pub(crate) mod asset; @@ -21,32 +22,7 @@ pub(crate) mod state_ext; pub(crate) mod transaction; mod utils; -use astria_core::primitive::v1::{ - Address, - AddressError, -}; pub use build_info::BUILD_INFO; pub use config::Config; -pub(crate) use config::ADDRESS_PREFIX; pub use sequencer::Sequencer; pub use telemetry; - -/// Constructs an [`Address`] prefixed by `"astria"`. -pub(crate) fn astria_address(array: [u8; astria_core::primitive::v1::ADDRESS_LEN]) -> Address { - Address::builder() - .array(array) - .prefix(ADDRESS_PREFIX) - .try_build() - .unwrap() -} - -/// Tries to construct an [`Address`] prefixed by `"astria"` from a byte slice. -/// -/// # Errors -/// Fails if the slice does not contain 20 bytes. -pub(crate) fn try_astria_address(slice: &[u8]) -> Result { - Address::builder() - .slice(slice) - .prefix(ADDRESS_PREFIX) - .try_build() -} diff --git a/crates/astria-sequencer/src/mempool.rs b/crates/astria-sequencer/src/mempool.rs index 490a4739df..a33035d65e 100644 --- a/crates/astria-sequencer/src/mempool.rs +++ b/crates/astria-sequencer/src/mempool.rs @@ -78,7 +78,7 @@ pub(crate) struct EnqueuedTransaction { impl EnqueuedTransaction { fn new(signed_tx: SignedTransaction) -> Self { - let address = crate::astria_address(signed_tx.verification_key().address_bytes()); + let address = crate::address::base_prefixed(signed_tx.verification_key().address_bytes()); Self { tx_hash: signed_tx.sha256_of_proto_encoding(), signed_tx: Arc::new(signed_tx), @@ -405,10 +405,9 @@ fn dummy_signed_tx() -> (Arc, Address) { let params = TransactionParams::builder() .nonce(0) .chain_id("dummy") - .try_build() - .expect("all params are valid"); + .build(); let signing_key = SigningKey::from([0; 32]); - let address = crate::astria_address(signing_key.verification_key().address_bytes()); + let address = crate::address::base_prefixed(signing_key.verification_key().address_bytes()); let unsigned_tx = UnsignedTransaction { actions, params, @@ -499,17 +498,23 @@ mod test { let tx0 = EnqueuedTransaction { tx_hash: [0; 32], signed_tx: Arc::new(get_mock_tx(0)), - address: crate::astria_address(get_mock_tx(0).verification_key().address_bytes()), + address: crate::address::base_prefixed( + get_mock_tx(0).verification_key().address_bytes(), + ), }; let other_tx0 = EnqueuedTransaction { tx_hash: [0; 32], signed_tx: Arc::new(get_mock_tx(1)), - address: crate::astria_address(get_mock_tx(1).verification_key().address_bytes()), + address: crate::address::base_prefixed( + get_mock_tx(1).verification_key().address_bytes(), + ), }; let tx1 = EnqueuedTransaction { tx_hash: [1; 32], signed_tx: Arc::new(get_mock_tx(0)), - address: crate::astria_address(get_mock_tx(0).verification_key().address_bytes()), + address: crate::address::base_prefixed( + get_mock_tx(0).verification_key().address_bytes(), + ), }; assert!(tx0 == other_tx0); assert!(tx0 != tx1); @@ -611,8 +616,7 @@ mod test { params: TransactionParams::builder() .nonce(nonce) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions, } .into_signed(&other_signing_key) @@ -625,7 +629,7 @@ mod test { let (alice_signing_key, alice_address) = crate::app::test_utils::get_alice_signing_key_and_address(); let other_address = - crate::astria_address(other_signing_key.verification_key().address_bytes()); + crate::address::base_prefixed(other_signing_key.verification_key().address_bytes()); // Create a getter fn which will returns 1 for alice's current account nonce, and 101 for // the other signer's. @@ -750,8 +754,7 @@ mod test { params: TransactionParams::builder() .nonce(nonce) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions, } .into_signed(&other_signing_key) @@ -765,13 +768,13 @@ mod test { let alice_address = crate::app::test_utils::get_alice_signing_key_and_address().1; assert_eq!(mempool.pending_nonce(&alice_address).await.unwrap(), 1); let other_address = - crate::astria_address(other_signing_key.verification_key().address_bytes()); + crate::address::base_prefixed(other_signing_key.verification_key().address_bytes()); assert_eq!(mempool.pending_nonce(&other_address).await.unwrap(), 101); // Check the pending nonce for an address with no enqueued txs is `None`. assert!( mempool - .pending_nonce(&crate::astria_address([1; 20])) + .pending_nonce(&crate::address::base_prefixed([1; 20])) .await .is_none() ); diff --git a/crates/astria-sequencer/src/proposal/commitment.rs b/crates/astria-sequencer/src/proposal/commitment.rs index a915baf711..e495a5147a 100644 --- a/crates/astria-sequencer/src/proposal/commitment.rs +++ b/crates/astria-sequencer/src/proposal/commitment.rs @@ -117,7 +117,7 @@ mod test { fee_asset_id: get_native_asset().id(), }; let transfer_action = TransferAction { - to: crate::astria_address([0u8; 20]), + to: crate::address::base_prefixed([0u8; 20]), amount: 1, asset_id: get_native_asset().id(), fee_asset_id: get_native_asset().id(), @@ -128,8 +128,7 @@ mod test { params: TransactionParams::builder() .nonce(0) .chain_id("test-chain-1") - .try_build() - .unwrap(), + .build(), actions: vec![sequence_action.clone().into(), transfer_action.into()], }; @@ -145,8 +144,7 @@ mod test { params: TransactionParams::builder() .nonce(0) .chain_id("test-chain-1") - .try_build() - .unwrap(), + .build(), actions: vec![sequence_action.into()], }; @@ -174,7 +172,7 @@ mod test { fee_asset_id: get_native_asset().id(), }; let transfer_action = TransferAction { - to: crate::astria_address([0u8; 20]), + to: crate::address::base_prefixed([0u8; 20]), amount: 1, asset_id: get_native_asset().id(), fee_asset_id: get_native_asset().id(), @@ -185,8 +183,7 @@ mod test { params: TransactionParams::builder() .nonce(0) .chain_id("test-chain-1") - .try_build() - .unwrap(), + .build(), actions: vec![sequence_action.into(), transfer_action.into()], }; diff --git a/crates/astria-sequencer/src/sequencer.rs b/crates/astria-sequencer/src/sequencer.rs index 9ca0ffd5a6..e78b7585d8 100644 --- a/crates/astria-sequencer/src/sequencer.rs +++ b/crates/astria-sequencer/src/sequencer.rs @@ -31,6 +31,7 @@ use tracing::{ }; use crate::{ + address::StateReadExt as _, app::App, config::Config, grpc::sequencer::SequencerServer, @@ -91,6 +92,12 @@ impl Sequencer { .await .context("failed to get native asset from storage")?; crate::asset::initialize_native_asset(&native_asset); + let base_prefix = snapshot + .get_base_prefix() + .await + .context("failed to get address base prefix from storage")?; + crate::address::initialize_base_prefix(&base_prefix) + .context("failed to initialize global address base prefix")?; } let mempool = Mempool::new(); diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index d099c04176..eb978c0053 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -249,6 +249,10 @@ mod test { use crate::{ app::test_utils::default_fees, asset::get_native_asset, + genesis::{ + AddressPrefixes, + UncheckedGenesisState, + }, mempool::Mempool, metrics::Metrics, proposal::commitment::generate_rollup_datas_commitment, @@ -259,8 +263,7 @@ mod test { params: TransactionParams::builder() .nonce(0) .chain_id("test") - .try_build() - .unwrap(), + .build(), actions: vec![ SequenceAction { rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), @@ -461,34 +464,30 @@ mod test { } } - impl Default for GenesisState { - fn default() -> Self { - Self { - accounts: vec![], - authority_sudo_address: crate::astria_address([0; 20]), - ibc_sudo_address: crate::astria_address([0; 20]), - ibc_relayer_addresses: vec![], - native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), - ibc_params: penumbra_ibc::params::IBCParameters::default(), - allowed_fee_assets: vec![default_native_asset()], - fees: default_fees(), - } - } - } - async fn new_consensus_service(funded_key: Option) -> (Consensus, Mempool) { let accounts = if funded_key.is_some() { vec![crate::genesis::Account { - address: crate::astria_address(funded_key.unwrap().address_bytes()), + address: crate::address::base_prefixed(funded_key.unwrap().address_bytes()), balance: 10u128.pow(19), }] } else { vec![] }; - let genesis_state = GenesisState { + let genesis_state = UncheckedGenesisState { accounts, - ..Default::default() - }; + address_prefixes: AddressPrefixes { + base: crate::address::get_base_prefix().to_string(), + }, + authority_sudo_address: crate::address::base_prefixed([0; 20]), + ibc_sudo_address: crate::address::base_prefixed([0; 20]), + ibc_relayer_addresses: vec![], + native_asset_base_denomination: DEFAULT_NATIVE_ASSET_DENOM.to_string(), + ibc_params: penumbra_ibc::params::IBCParameters::default(), + allowed_fee_assets: vec![default_native_asset()], + fees: default_fees(), + } + .try_into() + .unwrap(); let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); diff --git a/crates/astria-sequencer/src/service/info/mod.rs b/crates/astria-sequencer/src/service/info/mod.rs index 9f71357d87..ac5d140c71 100644 --- a/crates/astria-sequencer/src/service/info/mod.rs +++ b/crates/astria-sequencer/src/service/info/mod.rs @@ -210,7 +210,7 @@ mod test { initialize_native_asset(DEFAULT_NATIVE_ASSET_DENOM); - let address = crate::try_astria_address( + let address = crate::address::try_base_prefixed( &hex::decode("a034c743bed8f26cb8ee7b8db2230fd8347ae131").unwrap(), ) .unwrap(); @@ -223,7 +223,7 @@ mod test { storage.commit(state).await.unwrap(); let info_request = InfoRequest::Query(request::Query { - path: "accounts/balance/a034c743bed8f26cb8ee7b8db2230fd8347ae131".to_string(), + path: format!("accounts/balance/{address}"), data: vec![].into(), height: u32::try_from(height).unwrap().into(), prove: false, diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool.rs index a38389123f..0469819b2a 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool.rs @@ -221,7 +221,7 @@ async fn handle_check_tx( // tx is valid, push to mempool let current_account_nonce = state - .get_account_nonce(crate::astria_address( + .get_account_nonce(crate::address::base_prefixed( signed_tx.verification_key().address_bytes(), )) .await diff --git a/crates/astria-sequencer/src/snapshots/astria_sequencer__genesis__test__genesis_state_is_unchanged.snap b/crates/astria-sequencer/src/snapshots/astria_sequencer__genesis__test__genesis_state_is_unchanged.snap new file mode 100644 index 0000000000..17211f5bed --- /dev/null +++ b/crates/astria-sequencer/src/snapshots/astria_sequencer__genesis__test__genesis_state_is_unchanged.snap @@ -0,0 +1,61 @@ +--- +source: crates/astria-sequencer/src/genesis.rs +expression: genesis_state() +--- +{ + "address_prefixes": { + "base": "astria" + }, + "accounts": [ + { + "address": { + "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" + }, + "balance": 1000000000000000000 + }, + { + "address": { + "bech32m": "astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z" + }, + "balance": 1000000000000000000 + }, + { + "address": { + "bech32m": "astria1vpcfutferpjtwv457r63uwr6hdm8gwr3pxt5ny" + }, + "balance": 1000000000000000000 + } + ], + "authority_sudo_address": { + "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" + }, + "ibc_sudo_address": { + "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" + }, + "ibc_relayer_addresses": [ + { + "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" + }, + { + "bech32m": "astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z" + } + ], + "native_asset_base_denomination": "nria", + "ibc_params": { + "ibcEnabled": true, + "inboundIcs20TransfersEnabled": true, + "outboundIcs20TransfersEnabled": true + }, + "allowed_fee_assets": [ + "nria" + ], + "fees": { + "transfer_base_fee": 12, + "sequence_base_fee": 32, + "sequence_byte_cost_multiplier": 1, + "init_bridge_account_base_fee": 48, + "bridge_lock_byte_cost_multiplier": 1, + "bridge_sudo_change_fee": 24, + "ics20_withdrawal_base_fee": 24 + } +} diff --git a/crates/astria-sequencer/src/transaction/checks.rs b/crates/astria-sequencer/src/transaction/checks.rs index 9734d8708b..3e6d7068c2 100644 --- a/crates/astria-sequencer/src/transaction/checks.rs +++ b/crates/astria-sequencer/src/transaction/checks.rs @@ -31,7 +31,7 @@ pub(crate) async fn check_nonce_mempool( tx: &SignedTransaction, state: &S, ) -> anyhow::Result<()> { - let signer_address = crate::astria_address(tx.verification_key().address_bytes()); + let signer_address = crate::address::base_prefixed(tx.verification_key().address_bytes()); let curr_nonce = state .get_account_nonce(signer_address) .await @@ -56,7 +56,7 @@ pub(crate) async fn check_balance_mempool( tx: &SignedTransaction, state: &S, ) -> anyhow::Result<()> { - let signer_address = crate::astria_address(tx.verification_key().address_bytes()); + let signer_address = crate::address::base_prefixed(tx.verification_key().address_bytes()); check_balance_for_total_fees(tx.unsigned_transaction(), signer_address, state).await?; Ok(()) } @@ -340,7 +340,7 @@ mod test { asset_id: other_asset, amount, fee_asset_id: native_asset, - to: crate::astria_address([0; ADDRESS_LEN]), + to: crate::address::base_prefixed([0; ADDRESS_LEN]), }), Action::Sequence(SequenceAction { rollup_id: RollupId::from_unhashed_bytes([0; 32]), @@ -352,8 +352,7 @@ mod test { let params = TransactionParams::builder() .nonce(0) .chain_id("test-chain-id") - .try_build() - .unwrap(); + .build(); let tx = UnsignedTransaction { actions, params, @@ -404,7 +403,7 @@ mod test { asset_id: other_asset, amount, fee_asset_id: native_asset, - to: crate::astria_address([0; ADDRESS_LEN]), + to: crate::address::base_prefixed([0; ADDRESS_LEN]), }), Action::Sequence(SequenceAction { rollup_id: RollupId::from_unhashed_bytes([0; 32]), @@ -416,8 +415,7 @@ mod test { let params = TransactionParams::builder() .nonce(0) .chain_id("test-chain-id") - .try_build() - .unwrap(); + .build(); let tx = UnsignedTransaction { actions, params, diff --git a/crates/astria-sequencer/src/transaction/mod.rs b/crates/astria-sequencer/src/transaction/mod.rs index 16638a5685..07a32a6c1e 100644 --- a/crates/astria-sequencer/src/transaction/mod.rs +++ b/crates/astria-sequencer/src/transaction/mod.rs @@ -47,7 +47,7 @@ pub(crate) async fn check_stateful( tx: &SignedTransaction, state: &S, ) -> anyhow::Result<()> { - let signer_address = crate::astria_address(tx.verification_key().address_bytes()); + let signer_address = crate::address::base_prefixed(tx.verification_key().address_bytes()); tx.unsigned_transaction() .check_stateful(state, signer_address) .await @@ -62,7 +62,7 @@ pub(crate) async fn execute( StateWriteExt as _, }; - let signer_address = crate::astria_address(tx.verification_key().address_bytes()); + let signer_address = crate::address::base_prefixed(tx.verification_key().address_bytes()); if state .get_bridge_account_rollup_id(&signer_address) diff --git a/dev/values/validators/node0.yml b/dev/values/validators/node0.yml index c58627c0c0..46b79644dc 100644 --- a/dev/values/validators/node0.yml +++ b/dev/values/validators/node0.yml @@ -5,6 +5,32 @@ global: config: moniker: node0 + sequencer: + addressPrefixes: + base: "astria" + authoritySudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + nativeAssetBaseDenomination: nria + allowedFeeAssets: + - nria + ibc: + enabled: true + inboundEnabled: true + outboundEnabled: true + sudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + relayerAddresses: + - astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + - astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + # Note large balances must be strings support templating with the u128 size account balances + genesisAccounts: + - address: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + balance: "333333333333333333" + - address: astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + balance: "333333333333333333" + - address: astria1vpcfutferpjtwv457r63uwr6hdm8gwr3pxt5ny + balance: "333333333333333333" + # NOTE - the following address matches the privKey that funds the sequencer-faucet + - address: astria1qrt4kfc9ggyy548u7rg0d64sgq5c952kzk9tg9 + balance: "333333333333333333" # Values for CometBFT node configuration cometBFT: diff --git a/dev/values/validators/node1.yml b/dev/values/validators/node1.yml index 9285d72106..dfb1ba355c 100644 --- a/dev/values/validators/node1.yml +++ b/dev/values/validators/node1.yml @@ -4,6 +4,32 @@ global: config: moniker: 'node1' + sequencer: + addressPrefixes: + base: "astria" + authoritySudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + nativeAssetBaseDenomination: nria + allowedFeeAssets: + - nria + ibc: + enabled: true + inboundEnabled: true + outboundEnabled: true + sudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + relayerAddresses: + - astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + - astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + # Note large balances must be strings support templating with the u128 size account balances + genesisAccounts: + - address: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + balance: "333333333333333333" + - address: astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + balance: "333333333333333333" + - address: astria1vpcfutferpjtwv457r63uwr6hdm8gwr3pxt5ny + balance: "333333333333333333" + # NOTE - the following address matches the privKey that funds the sequencer-faucet + - address: astria1qrt4kfc9ggyy548u7rg0d64sgq5c952kzk9tg9 + balance: "333333333333333333" # Values for CometBFT node configuration cometBFT: diff --git a/dev/values/validators/node2.yml b/dev/values/validators/node2.yml index ef0c706e1d..42d133f407 100644 --- a/dev/values/validators/node2.yml +++ b/dev/values/validators/node2.yml @@ -4,6 +4,32 @@ global: config: moniker: 'node2' + sequencer: + addressPrefixes: + base: "astria" + authoritySudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + nativeAssetBaseDenomination: nria + allowedFeeAssets: + - nria + ibc: + enabled: true + inboundEnabled: true + outboundEnabled: true + sudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + relayerAddresses: + - astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + - astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + # Note large balances must be strings support templating with the u128 size account balances + genesisAccounts: + - address: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + balance: "333333333333333333" + - address: astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + balance: "333333333333333333" + - address: astria1vpcfutferpjtwv457r63uwr6hdm8gwr3pxt5ny + balance: "333333333333333333" + # NOTE - the following address matches the privKey that funds the sequencer-faucet + - address: astria1qrt4kfc9ggyy548u7rg0d64sgq5c952kzk9tg9 + balance: "333333333333333333" # Values for CometBFT node configuration cometBFT: diff --git a/dev/values/validators/single.yml b/dev/values/validators/single.yml index b5f15f051a..b9a0fede6f 100644 --- a/dev/values/validators/single.yml +++ b/dev/values/validators/single.yml @@ -4,6 +4,33 @@ global: config: moniker: node0 + sequencer: + addressPrefixes: + base: "astria" + authoritySudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + nativeAssetBaseDenomination: nria + allowedFeeAssets: + - nria + ibc: + enabled: true + inboundEnabled: true + outboundEnabled: true + sudoAddress: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + relayerAddresses: + - astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + - astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + # Note large balances must be strings support templating with the u128 size account balances + genesisAccounts: + - address: astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm + balance: "333333333333333333" + - address: astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z + balance: "333333333333333333" + - address: astria1vpcfutferpjtwv457r63uwr6hdm8gwr3pxt5ny + balance: "333333333333333333" + # NOTE - the following address matches the privKey that funds the sequencer-faucet + - address: astria1qrt4kfc9ggyy548u7rg0d64sgq5c952kzk9tg9 + balance: "333333333333333333" + cometBFT: validators: - name: core