From acff940abd2dd9857e038323ed6eb8aa88016a87 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Thu, 22 Aug 2024 14:06:14 +0200 Subject: [PATCH] refactor(core, proto)!: define app genesis state in proto (#1346) ## Summary Defines `astria.protocol.genesis.v1alpha1.GenesisAppState`, replacing the Rust-as-JSON definition. ## Background All protocol relevant Astria types are supposed to be defined in Astria's protobuf spec. With https://github.com/astriaorg/astria/pull/1285 having redefined the memos as protobuf message, this patch migrates the last type to protobuf spec. ## Changes - Define `astria.protocol.genesis.v1alpha1.GenesisAppState` and related protobuf messages - Remove the `astria-core::sequencer::GenesisState` module - Update Sequencer in terms of the protobuf type - Update all charts and snapshots (especially the genesis template) ## Testing All tests have been updated to use the new types, including snapshot tests. The genesis state is only read at `init-chain` and does not affect the evaluation of the state machine. Hence no tests should change as long as the same data is passed in (which is reflected by `tests-breaking-changes` leading to the same hash). ## Breaking Changelist This is a breaking change because an old sequencer would not understand a new (json-formatted) genesis app state and vise versa. ## Related issues Closes https://github.com/astriaorg/astria/issues/1347 --- charts/sequencer/Chart.yaml | 2 +- .../files/cometbft/config/genesis.json | 18 +- charts/sequencer/templates/_helpers.tpl | 4 + crates/astria-core/Cargo.toml | 2 +- crates/astria-core/src/crypto.rs | 19 +- .../astria.protocol.genesis.v1alpha1.rs | 126 +++ .../astria.protocol.genesis.v1alpha1.serde.rs | 779 ++++++++++++++++++ crates/astria-core/src/generated/mod.rs | 12 + crates/astria-core/src/lib.rs | 1 - crates/astria-core/src/primitive/v1/u128.rs | 6 + .../astria-core/src/protocol/genesis/mod.rs | 1 + ...1__tests__genesis_state_is_unchanged.snap} | 57 +- .../src/protocol/genesis/v1alpha1.rs | 778 +++++++++++++++++ crates/astria-core/src/protocol/mod.rs | 1 + crates/astria-core/src/sequencer.rs | 390 --------- .../src/genesis_example.rs | 90 +- .../src/accounts/component.rs | 3 +- crates/astria-sequencer/src/app/benchmarks.rs | 45 +- crates/astria-sequencer/src/app/mod.rs | 3 +- crates/astria-sequencer/src/app/test_utils.rs | 84 +- crates/astria-sequencer/src/app/tests_app.rs | 18 +- .../src/app/tests_breaking_changes.rs | 75 +- .../src/app/tests_execute_transaction.rs | 116 +-- .../astria-sequencer/src/bridge/component.rs | 3 +- crates/astria-sequencer/src/ibc/component.rs | 5 +- .../src/sequence/component.rs | 3 +- .../astria-sequencer/src/service/consensus.rs | 42 +- .../protocol/genesis/v1alpha1/types.proto | 47 ++ tools/protobuf-compiler/src/main.rs | 4 + 29 files changed, 2077 insertions(+), 657 deletions(-) create mode 100644 crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs create mode 100644 crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs create mode 100644 crates/astria-core/src/protocol/genesis/mod.rs rename crates/astria-core/src/{snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap => protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap} (50%) create mode 100644 crates/astria-core/src/protocol/genesis/v1alpha1.rs delete mode 100644 crates/astria-core/src/sequencer.rs create mode 100644 proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto diff --git a/charts/sequencer/Chart.yaml b/charts/sequencer/Chart.yaml index bb8d4fc527..577becef95 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.19.1 +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 # follow Semantic Versioning. They should reflect the version the application is using. diff --git a/charts/sequencer/files/cometbft/config/genesis.json b/charts/sequencer/files/cometbft/config/genesis.json index 4e8e7a4ff4..9e266902c2 100644 --- a/charts/sequencer/files/cometbft/config/genesis.json +++ b/charts/sequencer/files/cometbft/config/genesis.json @@ -3,13 +3,13 @@ "app_state": { "native_asset_base_denomination": "{{ .Values.genesis.nativeAssetBaseDenomination }}", "fees": { - "transfer_base_fee": {{ .Values.genesis.fees.transferBaseFee }}, - "sequence_base_fee": {{ .Values.genesis.fees.sequenceBaseFee }}, - "sequence_byte_cost_multiplier": {{ .Values.genesis.fees.sequenceByteCostMultiplier }}, - "init_bridge_account_base_fee": {{ .Values.genesis.fees.initBridgeAccountBaseFee }}, - "bridge_lock_byte_cost_multiplier": {{ .Values.genesis.fees.bridgeLockByteCostMultiplier }}, - "bridge_sudo_change_fee": {{ .Values.genesis.fees.bridgeSudoChangeFee }}, - "ics20_withdrawal_base_fee": {{ .Values.genesis.fees.ics20WithdrawalBaseFee }} + "transfer_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.transferBaseFee }}, + "sequence_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.sequenceBaseFee }}, + "sequence_byte_cost_multiplier": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.sequenceByteCostMultiplier }}, + "init_bridge_account_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.initBridgeAccountBaseFee }}, + "bridge_lock_byte_cost_multiplier": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.bridgeLockByteCostMultiplier }}, + "bridge_sudo_change_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.bridgeSudoChangeFee }}, + "ics20_withdrawal_base_fee": {{ include "sequencer.toUint128Proto" .Values.genesis.fees.ics20WithdrawalBaseFee }} }, "allowed_fee_assets": [ {{- range $index, $value := .Values.genesis.allowedFeeAssets }} @@ -17,7 +17,7 @@ "{{ $value }}" {{- end }} ], - "ibc_params": { + "ibc_parameters": { "ibc_enabled": {{ .Values.genesis.ibc.enabled }}, "inbound_ics20_transfers_enabled": {{ .Values.genesis.ibc.inboundEnabled }}, "outbound_ics20_transfers_enabled": {{ .Values.genesis.ibc.outboundEnabled }} @@ -30,7 +30,7 @@ {{- if $index }},{{- end }} { "address": {{ include "sequencer.address" $value.address }}, - "balance": {{ toString $value.balance | replace "\"" "" }} + "balance": {{ include "sequencer.toUint128Proto" ( toString $value.balance | replace "\"" "" ) }} } {{- end }} ], diff --git a/charts/sequencer/templates/_helpers.tpl b/charts/sequencer/templates/_helpers.tpl index bab0a2380a..2dc1da25ab 100644 --- a/charts/sequencer/templates/_helpers.tpl +++ b/charts/sequencer/templates/_helpers.tpl @@ -71,3 +71,7 @@ name: {{ .Values.moniker }}-sequencer-metrics {{/* New sequencer address */}} {{- define "sequencer.address"}}{ "bech32m": "{{ . }}" } {{- end }} + +{{/* uint64 fee converted to a astria proto Uint128 with only lo set */}} +{{- define "sequencer.toUint128Proto"}}{ "lo": {{ . }} } +{{- end }} diff --git a/crates/astria-core/Cargo.toml b/crates/astria-core/Cargo.toml index 049919b07a..3c40345d58 100644 --- a/crates/astria-core/Cargo.toml +++ b/crates/astria-core/Cargo.toml @@ -57,7 +57,7 @@ base64-serde = ["dep:base64-serde"] brotli = ["dep:brotli"] [dev-dependencies] +astria-core = { path = ".", features = ["serde"] } insta = { workspace = true, features = ["json"] } rand = { workspace = true } tempfile = { workspace = true } -astria-core = { path = ".", features = ["serde"] } diff --git a/crates/astria-core/src/crypto.rs b/crates/astria-core/src/crypto.rs index 69f017fb78..77da8f7b80 100644 --- a/crates/astria-core/src/crypto.rs +++ b/crates/astria-core/src/crypto.rs @@ -36,7 +36,11 @@ use zeroize::{ ZeroizeOnDrop, }; -use crate::primitive::v1::ADDRESS_LEN; +use crate::primitive::v1::{ + Address, + AddressError, + ADDRESS_LEN, +}; /// An Ed25519 signing key. // *Implementation note*: this is currently a refinement type around @@ -82,6 +86,19 @@ impl SigningKey { pub fn address_bytes(&self) -> [u8; ADDRESS_LEN] { self.verification_key().address_bytes() } + + /// Attempts to create an Astria bech32m `[Address]` with the given prefix. + /// + /// # Errors + /// Returns an [`AddressError`] if an address could not be constructed + /// with the given prefix. Usually if the prefix was too long or contained + /// characters not allowed by bech32m. + pub fn try_address(&self, prefix: &str) -> Result { + Address::builder() + .prefix(prefix) + .array(self.address_bytes()) + .try_build() + } } impl Debug for SigningKey { diff --git a/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs new file mode 100644 index 0000000000..17ccce5d82 --- /dev/null +++ b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.rs @@ -0,0 +1,126 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GenesisAppState { + #[prost(string, tag = "1")] + pub chain_id: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub address_prefixes: ::core::option::Option, + #[prost(message, repeated, tag = "3")] + pub accounts: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "4")] + pub authority_sudo_address: ::core::option::Option< + super::super::super::primitive::v1::Address, + >, + #[prost(message, optional, tag = "5")] + pub ibc_sudo_address: ::core::option::Option< + super::super::super::primitive::v1::Address, + >, + #[prost(message, repeated, tag = "6")] + pub ibc_relayer_addresses: ::prost::alloc::vec::Vec< + super::super::super::primitive::v1::Address, + >, + #[prost(string, tag = "7")] + pub native_asset_base_denomination: ::prost::alloc::string::String, + #[prost(message, optional, tag = "8")] + pub ibc_parameters: ::core::option::Option, + #[prost(string, repeated, tag = "9")] + pub allowed_fee_assets: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(message, optional, tag = "10")] + pub fees: ::core::option::Option, +} +impl ::prost::Name for GenesisAppState { + const NAME: &'static str = "GenesisAppState"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Account { + #[prost(message, optional, tag = "1")] + pub address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub balance: ::core::option::Option, +} +impl ::prost::Name for Account { + const NAME: &'static str = "Account"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AddressPrefixes { + #[prost(string, tag = "1")] + pub base: ::prost::alloc::string::String, +} +impl ::prost::Name for AddressPrefixes { + const NAME: &'static str = "AddressPrefixes"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +/// IBC configuration data. +#[derive(Copy)] +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IbcParameters { + /// Whether IBC (forming connections, processing IBC packets) is enabled. + #[prost(bool, tag = "1")] + pub ibc_enabled: bool, + /// Whether inbound ICS-20 transfers are enabled + #[prost(bool, tag = "2")] + pub inbound_ics20_transfers_enabled: bool, + /// Whether outbound ICS-20 transfers are enabled + #[prost(bool, tag = "3")] + pub outbound_ics20_transfers_enabled: bool, +} +impl ::prost::Name for IbcParameters { + const NAME: &'static str = "IbcParameters"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Fees { + #[prost(message, optional, tag = "1")] + pub transfer_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "2")] + pub sequence_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "3")] + pub sequence_byte_cost_multiplier: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "4")] + pub init_bridge_account_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "5")] + pub bridge_lock_byte_cost_multiplier: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "6")] + pub bridge_sudo_change_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, + #[prost(message, optional, tag = "7")] + pub ics20_withdrawal_base_fee: ::core::option::Option< + super::super::super::primitive::v1::Uint128, + >, +} +impl ::prost::Name for Fees { + const NAME: &'static str = "Fees"; + const PACKAGE: &'static str = "astria.protocol.genesis.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("astria.protocol.genesis.v1alpha1.{}", Self::NAME) + } +} diff --git a/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs new file mode 100644 index 0000000000..4636c57237 --- /dev/null +++ b/crates/astria-core/src/generated/astria.protocol.genesis.v1alpha1.serde.rs @@ -0,0 +1,779 @@ +impl serde::Serialize for Account { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.address.is_some() { + len += 1; + } + if self.balance.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.Account", len)?; + if let Some(v) = self.address.as_ref() { + struct_ser.serialize_field("address", v)?; + } + if let Some(v) = self.balance.as_ref() { + struct_ser.serialize_field("balance", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Account { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "balance", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + Balance, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "address" => Ok(GeneratedField::Address), + "balance" => Ok(GeneratedField::Balance), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Account; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.Account") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut balance__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = map_.next_value()?; + } + GeneratedField::Balance => { + if balance__.is_some() { + return Err(serde::de::Error::duplicate_field("balance")); + } + balance__ = map_.next_value()?; + } + } + } + Ok(Account { + address: address__, + balance: balance__, + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.Account", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for AddressPrefixes { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.base.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.AddressPrefixes", len)?; + if !self.base.is_empty() { + struct_ser.serialize_field("base", &self.base)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AddressPrefixes { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "base", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Base, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "base" => Ok(GeneratedField::Base), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AddressPrefixes; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.AddressPrefixes") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut base__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Base => { + if base__.is_some() { + return Err(serde::de::Error::duplicate_field("base")); + } + base__ = Some(map_.next_value()?); + } + } + } + Ok(AddressPrefixes { + base: base__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.AddressPrefixes", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for Fees { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.transfer_base_fee.is_some() { + len += 1; + } + if self.sequence_base_fee.is_some() { + len += 1; + } + if self.sequence_byte_cost_multiplier.is_some() { + len += 1; + } + if self.init_bridge_account_base_fee.is_some() { + len += 1; + } + if self.bridge_lock_byte_cost_multiplier.is_some() { + len += 1; + } + if self.bridge_sudo_change_fee.is_some() { + len += 1; + } + if self.ics20_withdrawal_base_fee.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.Fees", len)?; + if let Some(v) = self.transfer_base_fee.as_ref() { + struct_ser.serialize_field("transferBaseFee", v)?; + } + if let Some(v) = self.sequence_base_fee.as_ref() { + struct_ser.serialize_field("sequenceBaseFee", v)?; + } + if let Some(v) = self.sequence_byte_cost_multiplier.as_ref() { + struct_ser.serialize_field("sequenceByteCostMultiplier", v)?; + } + if let Some(v) = self.init_bridge_account_base_fee.as_ref() { + struct_ser.serialize_field("initBridgeAccountBaseFee", v)?; + } + if let Some(v) = self.bridge_lock_byte_cost_multiplier.as_ref() { + struct_ser.serialize_field("bridgeLockByteCostMultiplier", v)?; + } + if let Some(v) = self.bridge_sudo_change_fee.as_ref() { + struct_ser.serialize_field("bridgeSudoChangeFee", v)?; + } + if let Some(v) = self.ics20_withdrawal_base_fee.as_ref() { + struct_ser.serialize_field("ics20WithdrawalBaseFee", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Fees { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "transfer_base_fee", + "transferBaseFee", + "sequence_base_fee", + "sequenceBaseFee", + "sequence_byte_cost_multiplier", + "sequenceByteCostMultiplier", + "init_bridge_account_base_fee", + "initBridgeAccountBaseFee", + "bridge_lock_byte_cost_multiplier", + "bridgeLockByteCostMultiplier", + "bridge_sudo_change_fee", + "bridgeSudoChangeFee", + "ics20_withdrawal_base_fee", + "ics20WithdrawalBaseFee", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + TransferBaseFee, + SequenceBaseFee, + SequenceByteCostMultiplier, + InitBridgeAccountBaseFee, + BridgeLockByteCostMultiplier, + BridgeSudoChangeFee, + Ics20WithdrawalBaseFee, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "transferBaseFee" | "transfer_base_fee" => Ok(GeneratedField::TransferBaseFee), + "sequenceBaseFee" | "sequence_base_fee" => Ok(GeneratedField::SequenceBaseFee), + "sequenceByteCostMultiplier" | "sequence_byte_cost_multiplier" => Ok(GeneratedField::SequenceByteCostMultiplier), + "initBridgeAccountBaseFee" | "init_bridge_account_base_fee" => Ok(GeneratedField::InitBridgeAccountBaseFee), + "bridgeLockByteCostMultiplier" | "bridge_lock_byte_cost_multiplier" => Ok(GeneratedField::BridgeLockByteCostMultiplier), + "bridgeSudoChangeFee" | "bridge_sudo_change_fee" => Ok(GeneratedField::BridgeSudoChangeFee), + "ics20WithdrawalBaseFee" | "ics20_withdrawal_base_fee" => Ok(GeneratedField::Ics20WithdrawalBaseFee), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Fees; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.Fees") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut transfer_base_fee__ = None; + let mut sequence_base_fee__ = None; + let mut sequence_byte_cost_multiplier__ = None; + let mut init_bridge_account_base_fee__ = None; + let mut bridge_lock_byte_cost_multiplier__ = None; + let mut bridge_sudo_change_fee__ = None; + let mut ics20_withdrawal_base_fee__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::TransferBaseFee => { + if transfer_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("transferBaseFee")); + } + transfer_base_fee__ = map_.next_value()?; + } + GeneratedField::SequenceBaseFee => { + if sequence_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("sequenceBaseFee")); + } + sequence_base_fee__ = map_.next_value()?; + } + GeneratedField::SequenceByteCostMultiplier => { + if sequence_byte_cost_multiplier__.is_some() { + return Err(serde::de::Error::duplicate_field("sequenceByteCostMultiplier")); + } + sequence_byte_cost_multiplier__ = map_.next_value()?; + } + GeneratedField::InitBridgeAccountBaseFee => { + if init_bridge_account_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("initBridgeAccountBaseFee")); + } + init_bridge_account_base_fee__ = map_.next_value()?; + } + GeneratedField::BridgeLockByteCostMultiplier => { + if bridge_lock_byte_cost_multiplier__.is_some() { + return Err(serde::de::Error::duplicate_field("bridgeLockByteCostMultiplier")); + } + bridge_lock_byte_cost_multiplier__ = map_.next_value()?; + } + GeneratedField::BridgeSudoChangeFee => { + if bridge_sudo_change_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("bridgeSudoChangeFee")); + } + bridge_sudo_change_fee__ = map_.next_value()?; + } + GeneratedField::Ics20WithdrawalBaseFee => { + if ics20_withdrawal_base_fee__.is_some() { + return Err(serde::de::Error::duplicate_field("ics20WithdrawalBaseFee")); + } + ics20_withdrawal_base_fee__ = map_.next_value()?; + } + } + } + Ok(Fees { + transfer_base_fee: transfer_base_fee__, + sequence_base_fee: sequence_base_fee__, + sequence_byte_cost_multiplier: sequence_byte_cost_multiplier__, + init_bridge_account_base_fee: init_bridge_account_base_fee__, + bridge_lock_byte_cost_multiplier: bridge_lock_byte_cost_multiplier__, + bridge_sudo_change_fee: bridge_sudo_change_fee__, + ics20_withdrawal_base_fee: ics20_withdrawal_base_fee__, + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.Fees", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GenesisAppState { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.chain_id.is_empty() { + len += 1; + } + if self.address_prefixes.is_some() { + len += 1; + } + if !self.accounts.is_empty() { + len += 1; + } + if self.authority_sudo_address.is_some() { + len += 1; + } + if self.ibc_sudo_address.is_some() { + len += 1; + } + if !self.ibc_relayer_addresses.is_empty() { + len += 1; + } + if !self.native_asset_base_denomination.is_empty() { + len += 1; + } + if self.ibc_parameters.is_some() { + len += 1; + } + if !self.allowed_fee_assets.is_empty() { + len += 1; + } + if self.fees.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.GenesisAppState", len)?; + if !self.chain_id.is_empty() { + struct_ser.serialize_field("chainId", &self.chain_id)?; + } + if let Some(v) = self.address_prefixes.as_ref() { + struct_ser.serialize_field("addressPrefixes", v)?; + } + if !self.accounts.is_empty() { + struct_ser.serialize_field("accounts", &self.accounts)?; + } + if let Some(v) = self.authority_sudo_address.as_ref() { + struct_ser.serialize_field("authoritySudoAddress", v)?; + } + if let Some(v) = self.ibc_sudo_address.as_ref() { + struct_ser.serialize_field("ibcSudoAddress", v)?; + } + if !self.ibc_relayer_addresses.is_empty() { + struct_ser.serialize_field("ibcRelayerAddresses", &self.ibc_relayer_addresses)?; + } + if !self.native_asset_base_denomination.is_empty() { + struct_ser.serialize_field("nativeAssetBaseDenomination", &self.native_asset_base_denomination)?; + } + if let Some(v) = self.ibc_parameters.as_ref() { + struct_ser.serialize_field("ibcParameters", v)?; + } + if !self.allowed_fee_assets.is_empty() { + struct_ser.serialize_field("allowedFeeAssets", &self.allowed_fee_assets)?; + } + if let Some(v) = self.fees.as_ref() { + struct_ser.serialize_field("fees", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GenesisAppState { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "chain_id", + "chainId", + "address_prefixes", + "addressPrefixes", + "accounts", + "authority_sudo_address", + "authoritySudoAddress", + "ibc_sudo_address", + "ibcSudoAddress", + "ibc_relayer_addresses", + "ibcRelayerAddresses", + "native_asset_base_denomination", + "nativeAssetBaseDenomination", + "ibc_parameters", + "ibcParameters", + "allowed_fee_assets", + "allowedFeeAssets", + "fees", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + ChainId, + AddressPrefixes, + Accounts, + AuthoritySudoAddress, + IbcSudoAddress, + IbcRelayerAddresses, + NativeAssetBaseDenomination, + IbcParameters, + AllowedFeeAssets, + Fees, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "chainId" | "chain_id" => Ok(GeneratedField::ChainId), + "addressPrefixes" | "address_prefixes" => Ok(GeneratedField::AddressPrefixes), + "accounts" => Ok(GeneratedField::Accounts), + "authoritySudoAddress" | "authority_sudo_address" => Ok(GeneratedField::AuthoritySudoAddress), + "ibcSudoAddress" | "ibc_sudo_address" => Ok(GeneratedField::IbcSudoAddress), + "ibcRelayerAddresses" | "ibc_relayer_addresses" => Ok(GeneratedField::IbcRelayerAddresses), + "nativeAssetBaseDenomination" | "native_asset_base_denomination" => Ok(GeneratedField::NativeAssetBaseDenomination), + "ibcParameters" | "ibc_parameters" => Ok(GeneratedField::IbcParameters), + "allowedFeeAssets" | "allowed_fee_assets" => Ok(GeneratedField::AllowedFeeAssets), + "fees" => Ok(GeneratedField::Fees), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GenesisAppState; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.GenesisAppState") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut chain_id__ = None; + let mut address_prefixes__ = None; + let mut accounts__ = None; + let mut authority_sudo_address__ = None; + let mut ibc_sudo_address__ = None; + let mut ibc_relayer_addresses__ = None; + let mut native_asset_base_denomination__ = None; + let mut ibc_parameters__ = None; + let mut allowed_fee_assets__ = None; + let mut fees__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::ChainId => { + if chain_id__.is_some() { + return Err(serde::de::Error::duplicate_field("chainId")); + } + chain_id__ = Some(map_.next_value()?); + } + GeneratedField::AddressPrefixes => { + if address_prefixes__.is_some() { + return Err(serde::de::Error::duplicate_field("addressPrefixes")); + } + address_prefixes__ = map_.next_value()?; + } + GeneratedField::Accounts => { + if accounts__.is_some() { + return Err(serde::de::Error::duplicate_field("accounts")); + } + accounts__ = Some(map_.next_value()?); + } + GeneratedField::AuthoritySudoAddress => { + if authority_sudo_address__.is_some() { + return Err(serde::de::Error::duplicate_field("authoritySudoAddress")); + } + authority_sudo_address__ = map_.next_value()?; + } + GeneratedField::IbcSudoAddress => { + if ibc_sudo_address__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcSudoAddress")); + } + ibc_sudo_address__ = map_.next_value()?; + } + GeneratedField::IbcRelayerAddresses => { + if ibc_relayer_addresses__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcRelayerAddresses")); + } + ibc_relayer_addresses__ = Some(map_.next_value()?); + } + GeneratedField::NativeAssetBaseDenomination => { + if native_asset_base_denomination__.is_some() { + return Err(serde::de::Error::duplicate_field("nativeAssetBaseDenomination")); + } + native_asset_base_denomination__ = Some(map_.next_value()?); + } + GeneratedField::IbcParameters => { + if ibc_parameters__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcParameters")); + } + ibc_parameters__ = map_.next_value()?; + } + GeneratedField::AllowedFeeAssets => { + if allowed_fee_assets__.is_some() { + return Err(serde::de::Error::duplicate_field("allowedFeeAssets")); + } + allowed_fee_assets__ = Some(map_.next_value()?); + } + GeneratedField::Fees => { + if fees__.is_some() { + return Err(serde::de::Error::duplicate_field("fees")); + } + fees__ = map_.next_value()?; + } + } + } + Ok(GenesisAppState { + chain_id: chain_id__.unwrap_or_default(), + address_prefixes: address_prefixes__, + accounts: accounts__.unwrap_or_default(), + authority_sudo_address: authority_sudo_address__, + ibc_sudo_address: ibc_sudo_address__, + ibc_relayer_addresses: ibc_relayer_addresses__.unwrap_or_default(), + native_asset_base_denomination: native_asset_base_denomination__.unwrap_or_default(), + ibc_parameters: ibc_parameters__, + allowed_fee_assets: allowed_fee_assets__.unwrap_or_default(), + fees: fees__, + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.GenesisAppState", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for IbcParameters { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.ibc_enabled { + len += 1; + } + if self.inbound_ics20_transfers_enabled { + len += 1; + } + if self.outbound_ics20_transfers_enabled { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.protocol.genesis.v1alpha1.IbcParameters", len)?; + if self.ibc_enabled { + struct_ser.serialize_field("ibcEnabled", &self.ibc_enabled)?; + } + if self.inbound_ics20_transfers_enabled { + struct_ser.serialize_field("inboundIcs20TransfersEnabled", &self.inbound_ics20_transfers_enabled)?; + } + if self.outbound_ics20_transfers_enabled { + struct_ser.serialize_field("outboundIcs20TransfersEnabled", &self.outbound_ics20_transfers_enabled)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for IbcParameters { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "ibc_enabled", + "ibcEnabled", + "inbound_ics20_transfers_enabled", + "inboundIcs20TransfersEnabled", + "outbound_ics20_transfers_enabled", + "outboundIcs20TransfersEnabled", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + IbcEnabled, + InboundIcs20TransfersEnabled, + OutboundIcs20TransfersEnabled, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "ibcEnabled" | "ibc_enabled" => Ok(GeneratedField::IbcEnabled), + "inboundIcs20TransfersEnabled" | "inbound_ics20_transfers_enabled" => Ok(GeneratedField::InboundIcs20TransfersEnabled), + "outboundIcs20TransfersEnabled" | "outbound_ics20_transfers_enabled" => Ok(GeneratedField::OutboundIcs20TransfersEnabled), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = IbcParameters; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.protocol.genesis.v1alpha1.IbcParameters") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut ibc_enabled__ = None; + let mut inbound_ics20_transfers_enabled__ = None; + let mut outbound_ics20_transfers_enabled__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::IbcEnabled => { + if ibc_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("ibcEnabled")); + } + ibc_enabled__ = Some(map_.next_value()?); + } + GeneratedField::InboundIcs20TransfersEnabled => { + if inbound_ics20_transfers_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("inboundIcs20TransfersEnabled")); + } + inbound_ics20_transfers_enabled__ = Some(map_.next_value()?); + } + GeneratedField::OutboundIcs20TransfersEnabled => { + if outbound_ics20_transfers_enabled__.is_some() { + return Err(serde::de::Error::duplicate_field("outboundIcs20TransfersEnabled")); + } + outbound_ics20_transfers_enabled__ = Some(map_.next_value()?); + } + } + } + Ok(IbcParameters { + ibc_enabled: ibc_enabled__.unwrap_or_default(), + inbound_ics20_transfers_enabled: inbound_ics20_transfers_enabled__.unwrap_or_default(), + outbound_ics20_transfers_enabled: outbound_ics20_transfers_enabled__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.protocol.genesis.v1alpha1.IbcParameters", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/mod.rs b/crates/astria-core/src/generated/mod.rs index d476b83f21..4b78c07fe0 100644 --- a/crates/astria-core/src/generated/mod.rs +++ b/crates/astria-core/src/generated/mod.rs @@ -84,6 +84,18 @@ pub mod protocol { pub mod v1alpha1; } #[path = ""] + pub mod genesis { + pub mod v1alpha1 { + include!("astria.protocol.genesis.v1alpha1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impls { + use super::*; + include!("astria.protocol.genesis.v1alpha1.serde.rs"); + } + } + } + #[path = ""] pub mod memos { pub mod v1alpha1 { include!("astria.protocol.memos.v1alpha1.rs"); diff --git a/crates/astria-core/src/lib.rs b/crates/astria-core/src/lib.rs index 29c87603ef..4fecf8dddd 100644 --- a/crates/astria-core/src/lib.rs +++ b/crates/astria-core/src/lib.rs @@ -12,7 +12,6 @@ pub mod crypto; pub mod execution; pub mod primitive; pub mod protocol; -pub mod sequencer; pub mod sequencerblock; #[cfg(feature = "brotli")] diff --git a/crates/astria-core/src/primitive/v1/u128.rs b/crates/astria-core/src/primitive/v1/u128.rs index fbfb4208b1..ee77646a27 100644 --- a/crates/astria-core/src/primitive/v1/u128.rs +++ b/crates/astria-core/src/primitive/v1/u128.rs @@ -40,6 +40,12 @@ impl From for u128 { } } +impl<'a> From<&'a u128> for Uint128 { + fn from(primitive: &'a u128) -> Self { + (*primitive).into() + } +} + #[cfg(test)] mod tests { use crate::generated::primitive::v1::Uint128; diff --git a/crates/astria-core/src/protocol/genesis/mod.rs b/crates/astria-core/src/protocol/genesis/mod.rs new file mode 100644 index 0000000000..32a5a9d4fd --- /dev/null +++ b/crates/astria-core/src/protocol/genesis/mod.rs @@ -0,0 +1 @@ +pub mod v1alpha1; diff --git a/crates/astria-core/src/snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap b/crates/astria-core/src/protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap similarity index 50% rename from crates/astria-core/src/snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap rename to crates/astria-core/src/protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap index fde7a2b1e4..faa12162a8 100644 --- a/crates/astria-core/src/snapshots/astria_core__sequencer__tests__genesis_state_is_unchanged.snap +++ b/crates/astria-core/src/protocol/genesis/snapshots/astria_core__protocol__genesis__v1alpha1__tests__genesis_state_is_unchanged.snap @@ -1,9 +1,10 @@ --- -source: crates/astria-core/src/sequencer.rs +source: crates/astria-core/src/protocol/genesis/v1alpha1.rs expression: genesis_state() --- { - "address_prefixes": { + "chainId": "astria-1", + "addressPrefixes": { "base": "astria" }, "accounts": [ @@ -11,28 +12,34 @@ expression: genesis_state() "address": { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, - "balance": 1000000000000000000 + "balance": { + "lo": "1000000000000000000" + } }, { "address": { "bech32m": "astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z" }, - "balance": 1000000000000000000 + "balance": { + "lo": "1000000000000000000" + } }, { "address": { "bech32m": "astria1vpcfutferpjtwv457r63uwr6hdm8gwr3pxt5ny" }, - "balance": 1000000000000000000 + "balance": { + "lo": "1000000000000000000" + } } ], - "authority_sudo_address": { + "authoritySudoAddress": { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, - "ibc_sudo_address": { + "ibcSudoAddress": { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, - "ibc_relayer_addresses": [ + "ibcRelayerAddresses": [ { "bech32m": "astria1rsxyjrcm255ds9euthjx6yc3vrjt9sxrm9cfgm" }, @@ -40,22 +47,36 @@ expression: genesis_state() "bech32m": "astria1xnlvg0rle2u6auane79t4p27g8hxnj36ja960z" } ], - "native_asset_base_denomination": "nria", - "ibc_params": { + "nativeAssetBaseDenomination": "nria", + "ibcParameters": { "ibcEnabled": true, "inboundIcs20TransfersEnabled": true, "outboundIcs20TransfersEnabled": true }, - "allowed_fee_assets": [ + "allowedFeeAssets": [ "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 + "transferBaseFee": { + "lo": "12" + }, + "sequenceBaseFee": { + "lo": "32" + }, + "sequenceByteCostMultiplier": { + "lo": "1" + }, + "initBridgeAccountBaseFee": { + "lo": "48" + }, + "bridgeLockByteCostMultiplier": { + "lo": "1" + }, + "bridgeSudoChangeFee": { + "lo": "24" + }, + "ics20WithdrawalBaseFee": { + "lo": "24" + } } } diff --git a/crates/astria-core/src/protocol/genesis/v1alpha1.rs b/crates/astria-core/src/protocol/genesis/v1alpha1.rs new file mode 100644 index 0000000000..2131d1fccc --- /dev/null +++ b/crates/astria-core/src/protocol/genesis/v1alpha1.rs @@ -0,0 +1,778 @@ +use std::convert::Infallible; + +pub use penumbra_ibc::params::IBCParameters; + +use crate::{ + generated::protocol::genesis::v1alpha1 as raw, + primitive::v1::{ + asset::{ + self, + denom::ParseTracePrefixedError, + ParseDenomError, + }, + Address, + AddressError, + ADDRESS_LEN, + }, + Protobuf, +}; + +/// The genesis state of Astria's Sequencer. +/// +/// Verified to only contain valid fields (right now, addresses that have the same base prefix +/// as set in `GenesisState::address_prefixes::base`). +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(try_from = "raw::GenesisAppState", into = "raw::GenesisAppState") +)] +pub struct GenesisAppState { + chain_id: String, + address_prefixes: AddressPrefixes, + accounts: Vec, + authority_sudo_address: crate::primitive::v1::Address, + ibc_sudo_address: crate::primitive::v1::Address, + ibc_relayer_addresses: Vec, + native_asset_base_denomination: asset::TracePrefixed, + ibc_parameters: IBCParameters, + allowed_fee_assets: Vec, + fees: Fees, +} + +impl GenesisAppState { + #[must_use] + pub fn address_prefixes(&self) -> &AddressPrefixes { + &self.address_prefixes + } + + #[must_use] + pub fn accounts(&self) -> &[Account] { + &self.accounts + } + + #[must_use] + pub fn authority_sudo_address(&self) -> &Address { + &self.authority_sudo_address + } + + #[must_use] + pub fn chain_id(&self) -> &str { + &self.chain_id + } + + #[must_use] + pub fn ibc_sudo_address(&self) -> &Address { + &self.ibc_sudo_address + } + + #[must_use] + pub fn ibc_relayer_addresses(&self) -> &[Address] { + &self.ibc_relayer_addresses + } + + #[must_use] + pub fn native_asset_base_denomination(&self) -> &asset::TracePrefixed { + &self.native_asset_base_denomination + } + + #[must_use] + pub fn ibc_parameters(&self) -> &IBCParameters { + &self.ibc_parameters + } + + #[must_use] + pub fn allowed_fee_assets(&self) -> &[asset::Denom] { + &self.allowed_fee_assets + } + + #[must_use] + pub fn fees(&self) -> &Fees { + &self.fees + } + + fn ensure_address_has_base_prefix( + &self, + address: &Address, + field: &str, + ) -> Result<(), Box> { + if self.address_prefixes.base != address.prefix() { + return Err(Box::new(AddressDoesNotMatchBase { + base_prefix: self.address_prefixes.base.clone(), + address: *address, + field: field.to_string(), + })); + } + Ok(()) + } + + fn ensure_all_addresses_have_base_prefix(&self) -> Result<(), Box> { + 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 Protobuf for GenesisAppState { + type Error = GenesisAppStateError; + type Raw = raw::GenesisAppState; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + address_prefixes, + accounts, + authority_sudo_address, + chain_id, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_parameters, + allowed_fee_assets, + fees, + } = raw; + let address_prefixes = address_prefixes + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("address_prefixes")) + .and_then(|aps| { + AddressPrefixes::try_from_raw_ref(aps).map_err(Self::Error::address_prefixes) + })?; + let accounts = accounts + .iter() + .map(Account::try_from_raw_ref) + .collect::, _>>() + .map_err(Self::Error::accounts)?; + + let authority_sudo_address = authority_sudo_address + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("authority_sudo_address")) + .and_then(|addr| { + Address::try_from_raw(addr).map_err(Self::Error::authority_sudo_address) + })?; + let ibc_sudo_address = ibc_sudo_address + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("ibc_sudo_address")) + .and_then(|addr| Address::try_from_raw(addr).map_err(Self::Error::ibc_sudo_address))?; + + let ibc_relayer_addresses = ibc_relayer_addresses + .iter() + .map(Address::try_from_raw) + .collect::>() + .map_err(Self::Error::ibc_relayer_addresses)?; + + let native_asset_base_denomination = native_asset_base_denomination + .parse() + .map_err(Self::Error::native_asset_base_denomination)?; + + let ibc_parameters = { + let params = ibc_parameters + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("ibc_parameters"))?; + IBCParameters::try_from_raw_ref(params).expect("conversion is infallible") + }; + + let allowed_fee_assets = allowed_fee_assets + .iter() + .map(|asset| asset.parse()) + .collect::>() + .map_err(Self::Error::allowed_fee_assets)?; + + let fees = fees + .as_ref() + .ok_or_else(|| Self::Error::field_not_set("fees")) + .and_then(|fees| Fees::try_from_raw_ref(fees).map_err(Self::Error::fees))?; + + let this = Self { + address_prefixes, + accounts, + authority_sudo_address, + chain_id: chain_id.clone(), + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_parameters, + allowed_fee_assets, + fees, + }; + this.ensure_all_addresses_have_base_prefix() + .map_err(Self::Error::address_does_not_match_base)?; + Ok(this) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + address_prefixes, + accounts, + authority_sudo_address, + chain_id, + ibc_sudo_address, + ibc_relayer_addresses, + native_asset_base_denomination, + ibc_parameters, + allowed_fee_assets, + fees, + } = self; + Self::Raw { + address_prefixes: Some(address_prefixes.to_raw()), + accounts: accounts.iter().map(Account::to_raw).collect(), + authority_sudo_address: Some(authority_sudo_address.to_raw()), + chain_id: chain_id.clone(), + ibc_sudo_address: Some(ibc_sudo_address.to_raw()), + ibc_relayer_addresses: ibc_relayer_addresses.iter().map(Address::to_raw).collect(), + native_asset_base_denomination: native_asset_base_denomination.to_string(), + ibc_parameters: Some(ibc_parameters.to_raw()), + allowed_fee_assets: allowed_fee_assets.iter().map(ToString::to_string).collect(), + fees: Some(fees.to_raw()), + } + } +} + +impl TryFrom for GenesisAppState { + type Error = ::Error; + + fn try_from(value: raw::GenesisAppState) -> Result { + Self::try_from_raw(value) + } +} + +impl From for raw::GenesisAppState { + fn from(value: GenesisAppState) -> Self { + value.into_raw() + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct GenesisAppStateError(GenesisAppStateErrorKind); + +impl GenesisAppStateError { + fn accounts(source: AccountError) -> Self { + Self(GenesisAppStateErrorKind::Accounts { + source, + }) + } + + fn address_prefixes(source: AddressPrefixesError) -> Self { + Self(GenesisAppStateErrorKind::AddressPrefixes { + source, + }) + } + + fn address_does_not_match_base(source: Box) -> Self { + Self(GenesisAppStateErrorKind::AddressDoesNotMatchBase { + source, + }) + } + + fn allowed_fee_assets(source: ParseDenomError) -> Self { + Self(GenesisAppStateErrorKind::AllowedFeeAssets { + source, + }) + } + + fn authority_sudo_address(source: AddressError) -> Self { + Self(GenesisAppStateErrorKind::AuthoritySudoAddress { + source, + }) + } + + fn fees(source: FeesError) -> Self { + Self(GenesisAppStateErrorKind::Fees { + source, + }) + } + + fn field_not_set(name: &'static str) -> Self { + Self(GenesisAppStateErrorKind::FieldNotSet { + name, + }) + } + + fn ibc_relayer_addresses(source: AddressError) -> Self { + Self(GenesisAppStateErrorKind::IbcRelayerAddresses { + source, + }) + } + + fn ibc_sudo_address(source: AddressError) -> Self { + Self(GenesisAppStateErrorKind::IbcSudoAddress { + source, + }) + } + + fn native_asset_base_denomination(source: ParseTracePrefixedError) -> Self { + Self(GenesisAppStateErrorKind::NativeAssetBaseDenomination { + source, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", GenesisAppState::full_name())] +enum GenesisAppStateErrorKind { + #[error("`accounts` field was invalid")] + Accounts { source: AccountError }, + #[error("`address_prefixes` field was invalid")] + AddressPrefixes { source: AddressPrefixesError }, + #[error("one of the provided addresses did not match the provided base prefix")] + AddressDoesNotMatchBase { + source: Box, + }, + #[error("`allowed_fee_assets` field was invalid")] + AllowedFeeAssets { source: ParseDenomError }, + #[error("`authority_sudo_address` field was invalid")] + AuthoritySudoAddress { source: AddressError }, + #[error("`fees` field was invalid")] + Fees { source: FeesError }, + #[error("`ibc_sudo_address` field was invalid")] + IbcSudoAddress { source: AddressError }, + #[error("`ibc_relayer_addresses` field was invalid")] + IbcRelayerAddresses { source: AddressError }, + #[error("field was not set: `{name}`")] + FieldNotSet { name: &'static str }, + #[error("`native_asset_base_denomination` field was invalid")] + NativeAssetBaseDenomination { source: ParseTracePrefixedError }, +} + +#[derive(Debug, thiserror::Error)] +#[error("address `{address}` at `{field}` does not have `{base_prefix}`")] +struct AddressDoesNotMatchBase { + base_prefix: String, + address: Address, + field: String, +} + +#[derive(Clone, Copy, Debug)] +pub struct Account { + pub address: Address, + pub balance: u128, +} + +impl Protobuf for Account { + type Error = AccountError; + type Raw = raw::Account; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + address, + balance, + } = raw; + let address = address + .as_ref() + .ok_or_else(|| AccountError::field_not_set("address")) + .and_then(|addr| Address::try_from_raw(addr).map_err(Self::Error::address))?; + let balance = balance + .ok_or_else(|| AccountError::field_not_set("balance")) + .map(Into::into)?; + Ok(Self { + address, + balance, + }) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + address, + balance, + } = self; + Self::Raw { + address: Some(address.to_raw()), + balance: Some((*balance).into()), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct AccountError(AccountErrorKind); + +impl AccountError { + fn address(source: AddressError) -> Self { + Self(AccountErrorKind::Address { + source, + }) + } + + fn field_not_set(name: &'static str) -> Self { + Self(AccountErrorKind::FieldNotSet { + name, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", Account::full_name())] +enum AccountErrorKind { + #[error("`address` field was invalid")] + Address { source: AddressError }, + #[error("field was not set: `{name}`")] + FieldNotSet { name: &'static str }, +} + +#[derive(Clone, Debug)] +pub struct AddressPrefixes { + pub base: String, +} + +impl Protobuf for AddressPrefixes { + type Error = AddressPrefixesError; + type Raw = raw::AddressPrefixes; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + base, + } = raw; + try_construct_dummy_address_from_prefix(base).map_err(Self::Error::base)?; + Ok(Self { + base: base.to_string(), + }) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + base, + } = self; + Self::Raw { + base: base.clone(), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct AddressPrefixesError(AddressPrefixesErrorKind); + +impl AddressPrefixesError { + fn base(source: AddressError) -> Self { + Self(AddressPrefixesErrorKind::Base { + source, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", AddressPrefixes::full_name())] +enum AddressPrefixesErrorKind { + #[error("`base` cannot be used to construct Astria addresses")] + Base { source: AddressError }, +} + +impl Protobuf for IBCParameters { + type Error = Infallible; + type Raw = raw::IbcParameters; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + Ok((*raw).into()) + } + + fn to_raw(&self) -> Self::Raw { + self.clone().into() + } +} + +impl From for raw::IbcParameters { + fn from(value: IBCParameters) -> Self { + let IBCParameters { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } = value; + Self { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } + } +} + +impl From for IBCParameters { + fn from(value: raw::IbcParameters) -> Self { + let raw::IbcParameters { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } = value; + Self { + ibc_enabled, + inbound_ics20_transfers_enabled, + outbound_ics20_transfers_enabled, + } + } +} + +#[derive(Clone, Debug)] +pub struct Fees { + pub transfer_base_fee: u128, + pub sequence_base_fee: u128, + pub sequence_byte_cost_multiplier: u128, + pub init_bridge_account_base_fee: u128, + pub bridge_lock_byte_cost_multiplier: u128, + pub bridge_sudo_change_fee: u128, + pub ics20_withdrawal_base_fee: u128, +} + +impl Protobuf for Fees { + type Error = FeesError; + type Raw = raw::Fees; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let Self::Raw { + transfer_base_fee, + sequence_base_fee, + sequence_byte_cost_multiplier, + init_bridge_account_base_fee, + bridge_lock_byte_cost_multiplier, + bridge_sudo_change_fee, + ics20_withdrawal_base_fee, + } = raw; + let transfer_base_fee = transfer_base_fee + .ok_or_else(|| Self::Error::field_not_set("transfer_base_fee"))? + .into(); + let sequence_base_fee = sequence_base_fee + .ok_or_else(|| Self::Error::field_not_set("sequence_base_fee"))? + .into(); + let sequence_byte_cost_multiplier = sequence_byte_cost_multiplier + .ok_or_else(|| Self::Error::field_not_set("sequence_byte_cost_multiplier"))? + .into(); + let init_bridge_account_base_fee = init_bridge_account_base_fee + .ok_or_else(|| Self::Error::field_not_set("init_bridge_account_base_fee"))? + .into(); + let bridge_lock_byte_cost_multiplier = bridge_lock_byte_cost_multiplier + .ok_or_else(|| Self::Error::field_not_set("bridge_lock_byte_cost_multiplier"))? + .into(); + let bridge_sudo_change_fee = bridge_sudo_change_fee + .ok_or_else(|| Self::Error::field_not_set("bridge_sudo_change_fee"))? + .into(); + let ics20_withdrawal_base_fee = ics20_withdrawal_base_fee + .ok_or_else(|| Self::Error::field_not_set("ics20_withdrawal_base_fee"))? + .into(); + Ok(Self { + transfer_base_fee, + sequence_base_fee, + sequence_byte_cost_multiplier, + init_bridge_account_base_fee, + bridge_lock_byte_cost_multiplier, + bridge_sudo_change_fee, + ics20_withdrawal_base_fee, + }) + } + + fn to_raw(&self) -> Self::Raw { + let Self { + transfer_base_fee, + sequence_base_fee, + sequence_byte_cost_multiplier, + init_bridge_account_base_fee, + bridge_lock_byte_cost_multiplier, + bridge_sudo_change_fee, + ics20_withdrawal_base_fee, + } = self; + Self::Raw { + transfer_base_fee: Some(transfer_base_fee.into()), + sequence_base_fee: Some(sequence_base_fee.into()), + sequence_byte_cost_multiplier: Some(sequence_byte_cost_multiplier.into()), + init_bridge_account_base_fee: Some(init_bridge_account_base_fee.into()), + bridge_lock_byte_cost_multiplier: Some(bridge_lock_byte_cost_multiplier.into()), + bridge_sudo_change_fee: Some(bridge_sudo_change_fee.into()), + ics20_withdrawal_base_fee: Some(ics20_withdrawal_base_fee.into()), + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct FeesError(FeesErrorKind); + +impl FeesError { + fn field_not_set(name: &'static str) -> Self { + Self(FeesErrorKind::FieldNotSet { + name, + }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("failed ensuring invariants of {}", Fees::full_name())] +enum FeesErrorKind { + #[error("field was not set: `{name}`")] + FieldNotSet { name: &'static str }, +} + +/// Constructs a dummy address from a given `prefix`, otherwise fail. +fn try_construct_dummy_address_from_prefix(prefix: &str) -> Result<(), AddressError> { + Address::builder() + .array([0u8; ADDRESS_LEN]) + .prefix(prefix) + .try_build() + .map(|_| ()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::primitive::v1::Address; + + 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 proto_genesis_state() -> raw::GenesisAppState { + raw::GenesisAppState { + accounts: vec![ + raw::Account { + address: Some(alice().to_raw()), + balance: Some(1_000_000_000_000_000_000.into()), + }, + raw::Account { + address: Some(bob().to_raw()), + balance: Some(1_000_000_000_000_000_000.into()), + }, + raw::Account { + address: Some(charlie().to_raw()), + balance: Some(1_000_000_000_000_000_000.into()), + }, + ], + address_prefixes: Some(raw::AddressPrefixes { + base: "astria".into(), + }), + authority_sudo_address: Some(alice().to_raw()), + chain_id: "astria-1".to_string(), + ibc_sudo_address: Some(alice().to_raw()), + ibc_relayer_addresses: vec![alice().to_raw(), bob().to_raw()], + native_asset_base_denomination: "nria".to_string(), + ibc_parameters: Some(raw::IbcParameters { + ibc_enabled: true, + inbound_ics20_transfers_enabled: true, + outbound_ics20_transfers_enabled: true, + }), + allowed_fee_assets: vec!["nria".into()], + fees: Some(raw::Fees { + transfer_base_fee: Some(12.into()), + sequence_base_fee: Some(32.into()), + sequence_byte_cost_multiplier: Some(1.into()), + init_bridge_account_base_fee: Some(48.into()), + bridge_lock_byte_cost_multiplier: Some(1.into()), + bridge_sudo_change_fee: Some(24.into()), + ics20_withdrawal_base_fee: Some(24.into()), + }), + } + } + + fn genesis_state() -> GenesisAppState { + proto_genesis_state().try_into().unwrap() + } + + #[test] + fn mismatched_addresses_are_caught() { + #[track_caller] + fn assert_bad_prefix(unchecked: raw::GenesisAppState, bad_field: &'static str) { + match GenesisAppState::try_from(unchecked) + .expect_err( + "converting to genesis state should have produced an error, but a valid state \ + was returned", + ) + .0 + { + GenesisAppStateErrorKind::AddressDoesNotMatchBase { + source, + } => { + let AddressDoesNotMatchBase { + base_prefix, + address, + field, + } = *source; + assert_eq!(base_prefix, ASTRIA_ADDRESS_PREFIX); + assert_eq!(address, mallory()); + assert_eq!(field, bad_field); + } + other => panic!( + "expected: `GenesisAppStateErrorKind::AddressDoesNotMatchBase\ngot: {other:?}`" + ), + }; + } + assert_bad_prefix( + raw::GenesisAppState { + authority_sudo_address: Some(mallory().to_raw()), + ..proto_genesis_state() + }, + ".authority_sudo_address", + ); + assert_bad_prefix( + raw::GenesisAppState { + ibc_sudo_address: Some(mallory().to_raw()), + ..proto_genesis_state() + }, + ".ibc_sudo_address", + ); + assert_bad_prefix( + raw::GenesisAppState { + ibc_relayer_addresses: vec![alice().to_raw(), mallory().to_raw()], + ..proto_genesis_state() + }, + ".ibc_relayer_addresses[1]", + ); + assert_bad_prefix( + raw::GenesisAppState { + accounts: vec![ + raw::Account { + address: Some(alice().to_raw()), + balance: Some(10.into()), + }, + raw::Account { + address: Some(mallory().to_raw()), + balance: Some(10.into()), + }, + ], + ..proto_genesis_state() + }, + ".accounts[1].address", + ); + } + + #[cfg(feature = "serde")] + #[test] + fn genesis_state_is_unchanged() { + insta::assert_json_snapshot!(genesis_state()); + } +} diff --git a/crates/astria-core/src/protocol/mod.rs b/crates/astria-core/src/protocol/mod.rs index 2d30299b22..e285ce4d67 100644 --- a/crates/astria-core/src/protocol/mod.rs +++ b/crates/astria-core/src/protocol/mod.rs @@ -8,6 +8,7 @@ pub mod abci; pub mod account; pub mod asset; pub mod bridge; +pub mod genesis; pub mod memos; pub mod transaction; diff --git a/crates/astria-core/src/sequencer.rs b/crates/astria-core/src/sequencer.rs deleted file mode 100644 index 4138e96b6e..0000000000 --- a/crates/astria-core/src/sequencer.rs +++ /dev/null @@ -1,390 +0,0 @@ -//! Sequencer specific types that are needed outside of it. -pub use penumbra_ibc::params::IBCParameters; - -use crate::primitive::v1::{ - asset::{ - self, - TracePrefixed, - }, - Address, -}; - -/// The genesis state of Astria's Sequencer. -/// -/// Verified to only contain valid fields (right now, addresses that have the same base prefix -/// as set in `GenesisState::address_prefixes::base`). -/// -/// *Note on the implementation:* access to all fields is through getters to uphold invariants, -/// but most returned values themselves have publicly exposed fields. This is to make it easier -/// to construct an [`UncheckedGenesisState`]. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr( - feature = "serde", - serde(try_from = "UncheckedGenesisState", into = "UncheckedGenesisState") -)] -pub struct GenesisState { - address_prefixes: AddressPrefixes, - accounts: Vec, - authority_sudo_address: Address, - ibc_sudo_address: Address, - ibc_relayer_addresses: Vec
, - native_asset_base_denomination: TracePrefixed, - ibc_params: IBCParameters, - allowed_fee_assets: Vec, - fees: Fees, -} - -impl GenesisState { - #[must_use] - pub fn address_prefixes(&self) -> &AddressPrefixes { - &self.address_prefixes - } - - #[must_use] - pub fn accounts(&self) -> &[Account] { - &self.accounts - } - - #[must_use] - pub fn authority_sudo_address(&self) -> &Address { - &self.authority_sudo_address - } - - #[must_use] - pub fn ibc_sudo_address(&self) -> &Address { - &self.ibc_sudo_address - } - - #[must_use] - pub fn ibc_relayer_addresses(&self) -> &[Address] { - &self.ibc_relayer_addresses - } - - #[must_use] - pub fn native_asset_base_denomination(&self) -> &TracePrefixed { - &self.native_asset_base_denomination - } - - #[must_use] - pub fn ibc_params(&self) -> &IBCParameters { - &self.ibc_params - } - - #[must_use] - pub fn allowed_fee_assets(&self) -> &[asset::Denom] { - &self.allowed_fee_assets - } - - #[must_use] - pub fn fees(&self) -> &Fees { - &self.fees - } -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct VerifyGenesisError(Box); - -#[derive(Debug, thiserror::Error)] -enum VerifyGenesisErrorKind { - #[error("address `{address}` at `{field}` does not have `{base_prefix}`")] - AddressDoesNotMatchBase { - base_prefix: String, - address: Address, - field: String, - }, -} - -impl From for VerifyGenesisError { - fn from(value: VerifyGenesisErrorKind) -> Self { - Self(Box::new(value)) - } -} - -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)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct UncheckedGenesisState { - pub address_prefixes: AddressPrefixes, - pub accounts: Vec, - pub authority_sudo_address: Address, - pub ibc_sudo_address: Address, - pub ibc_relayer_addresses: Vec
, - pub native_asset_base_denomination: TracePrefixed, - pub ibc_params: IBCParameters, - pub allowed_fee_assets: Vec, - pub fees: Fees, -} - -impl UncheckedGenesisState { - fn ensure_address_has_base_prefix( - &self, - address: &Address, - field: &str, - ) -> Result<(), VerifyGenesisError> { - if self.address_prefixes.base != address.prefix() { - return Err(VerifyGenesisErrorKind::AddressDoesNotMatchBase { - base_prefix: self.address_prefixes.base.clone(), - address: *address, - field: field.to_string(), - } - .into()); - } - Ok(()) - } - - 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)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Fees { - pub transfer_base_fee: u128, - pub sequence_base_fee: u128, - pub sequence_byte_cost_multiplier: u128, - pub init_bridge_account_base_fee: u128, - pub bridge_lock_byte_cost_multiplier: u128, - pub bridge_sudo_change_fee: u128, - pub ics20_withdrawal_base_fee: u128, -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct Account { - pub address: Address, - pub balance: u128, -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct AddressPrefixes { - pub base: String, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::primitive::v1::Address; - - 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, - }, - ], - 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".parse().unwrap(), - 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", - ) - .0 - { - VerifyGenesisErrorKind::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() - }, - ".ibc_relayer_addresses[1]", - ); - assert_bad_prefix( - UncheckedGenesisState { - accounts: vec![ - Account { - address: alice(), - balance: 10, - }, - Account { - address: mallory(), - balance: 10, - }, - ], - ..unchecked_genesis_state() - }, - ".accounts[1].address", - ); - } - - #[cfg(feature = "serde")] - #[test] - fn genesis_state_is_unchanged() { - insta::assert_json_snapshot!(genesis_state()); - } -} diff --git a/crates/astria-sequencer-utils/src/genesis_example.rs b/crates/astria-sequencer-utils/src/genesis_example.rs index 05d66baa07..b28083fb82 100644 --- a/crates/astria-sequencer-utils/src/genesis_example.rs +++ b/crates/astria-sequencer-utils/src/genesis_example.rs @@ -5,15 +5,15 @@ use std::{ }; use astria_core::{ + generated::protocol::genesis::v1alpha1::IbcParameters, primitive::v1::Address, - sequencer::{ + protocol::genesis::v1alpha1::{ Account, AddressPrefixes, Fees, - GenesisState, - IBCParameters, - UncheckedGenesisState, + GenesisAppState, }, + Protobuf, }; use astria_eyre::eyre::{ Result, @@ -46,47 +46,61 @@ fn charlie() -> Address { .unwrap() } -fn genesis_state() -> GenesisState { - 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, - }, - ], - address_prefixes: AddressPrefixes { - base: "astria".into(), +fn accounts() -> Vec { + vec![ + Account { + address: alice(), + balance: 1_000_000_000_000_000_000, }, - authority_sudo_address: alice(), - ibc_sudo_address: alice(), - ibc_relayer_addresses: vec![alice(), bob()], + Account { + address: bob(), + balance: 1_000_000_000_000_000_000, + }, + Account { + address: charlie(), + balance: 1_000_000_000_000_000_000, + }, + ] +} + +fn address_prefixes() -> AddressPrefixes { + AddressPrefixes { + base: "astria".into(), + } +} + +fn proto_genesis_state() -> astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + accounts: accounts().into_iter().map(Protobuf::into_raw).collect(), + address_prefixes: Some(address_prefixes().into_raw()), + authority_sudo_address: Some(alice().to_raw()), + chain_id: "test-1".into(), + ibc_sudo_address: Some(alice().to_raw()), + ibc_relayer_addresses: vec![alice().to_raw(), bob().to_raw()], native_asset_base_denomination: "nria".parse().unwrap(), - ibc_params: IBCParameters { + ibc_parameters: Some(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, - }, + fees: Some( + 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, + } + .into_raw(), + ), } - .try_into() - .unwrap() +} + +fn genesis_state() -> GenesisAppState { + GenesisAppState::try_from_raw(proto_genesis_state()).unwrap() } #[derive(clap::Args, Debug)] diff --git a/crates/astria-sequencer/src/accounts/component.rs b/crates/astria-sequencer/src/accounts/component.rs index 615cb24bbd..e08d7b357a 100644 --- a/crates/astria-sequencer/src/accounts/component.rs +++ b/crates/astria-sequencer/src/accounts/component.rs @@ -4,6 +4,7 @@ use anyhow::{ Context, Result, }; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use tendermint::abci::request::{ BeginBlock, EndBlock, @@ -21,7 +22,7 @@ pub(crate) struct AccountsComponent; #[async_trait::async_trait] impl Component for AccountsComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "AccountsComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> diff --git a/crates/astria-sequencer/src/app/benchmarks.rs b/crates/astria-sequencer/src/app/benchmarks.rs index db5448f668..b667720662 100644 --- a/crates/astria-sequencer/src/app/benchmarks.rs +++ b/crates/astria-sequencer/src/app/benchmarks.rs @@ -5,14 +5,14 @@ use std::time::Duration; -use astria_core::sequencer::{ - Account, - AddressPrefixes, - GenesisState, - UncheckedGenesisState, +use astria_core::{ + protocol::genesis::v1alpha1::{ + Account, + GenesisAppState, + }, + Protobuf, }; use cnidarium::Storage; -use penumbra_ibc::params::IBCParameters; use crate::{ app::{ @@ -25,11 +25,7 @@ use crate::{ SIGNER_COUNT, }, proposal::block_size_constraints::BlockSizeConstraints, - test_utils::{ - astria_address, - nria, - ASTRIA_PREFIX, - }, + test_utils::astria_address, }; /// The max time for any benchmark. @@ -58,23 +54,18 @@ impl Fixture { .pow(19) .saturating_add(u128::try_from(index).unwrap()), }) + .map(Protobuf::into_raw) .collect::>(); - let address_prefixes = AddressPrefixes { - base: ASTRIA_PREFIX.into(), - }; - let first_address = accounts.first().unwrap().address; - let unchecked_genesis_state = UncheckedGenesisState { - accounts, - address_prefixes, - authority_sudo_address: first_address, - ibc_sudo_address: first_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![nria().into()], - fees: test_utils::default_fees(), - }; - let genesis_state = GenesisState::try_from(unchecked_genesis_state).unwrap(); + let first_address = accounts.first().cloned().unwrap().address; + let genesis_state = GenesisAppState::try_from_raw( + astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + accounts, + authority_sudo_address: first_address.clone(), + ibc_sudo_address: first_address.clone(), + ..crate::app::test_utils::proto_genesis_state() + }, + ) + .unwrap(); let (app, storage) = test_utils::initialize_app_with_storage(Some(genesis_state), vec![]).await; diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 5300077ee8..e27feb6ec0 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -27,6 +27,7 @@ use astria_core::{ generated::protocol::transactions::v1alpha1 as raw, protocol::{ abci::AbciErrorCode, + genesis::v1alpha1::GenesisAppState, transaction::v1alpha1::{ action::ValidatorUpdate, Action, @@ -209,7 +210,7 @@ impl App { pub(crate) async fn init_chain( &mut self, storage: Storage, - genesis_state: astria_core::sequencer::GenesisState, + genesis_state: GenesisAppState, genesis_validators: Vec, chain_id: String, ) -> anyhow::Result { diff --git a/crates/astria-sequencer/src/app/test_utils.rs b/crates/astria-sequencer/src/app/test_utils.rs index fcc4223fa1..05ffb9b7e0 100644 --- a/crates/astria-sequencer/src/app/test_utils.rs +++ b/crates/astria-sequencer/src/app/test_utils.rs @@ -3,26 +3,26 @@ use std::sync::Arc; use astria_core::{ crypto::SigningKey, primitive::v1::RollupId, - protocol::transaction::v1alpha1::{ - action::{ - SequenceAction, - ValidatorUpdate, + protocol::{ + genesis::v1alpha1::{ + Account, + AddressPrefixes, + GenesisAppState, + }, + transaction::v1alpha1::{ + action::{ + SequenceAction, + ValidatorUpdate, + }, + SignedTransaction, + TransactionParams, + UnsignedTransaction, }, - SignedTransaction, - TransactionParams, - UnsignedTransaction, - }, - sequencer::{ - Account, - AddressPrefixes, - Fees, - GenesisState, - UncheckedGenesisState, }, + Protobuf, }; use bytes::Bytes; use cnidarium::Storage; -use penumbra_ibc::params::IBCParameters; use crate::{ app::App, @@ -58,7 +58,6 @@ pub(crate) fn get_bridge_signing_key() -> SigningKey { SigningKey::from(bridge_secret_bytes) } -#[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) fn default_genesis_accounts() -> Vec { vec![ Account { @@ -77,8 +76,8 @@ pub(crate) fn default_genesis_accounts() -> Vec { } #[cfg_attr(feature = "benchmark", allow(dead_code))] -pub(crate) fn default_fees() -> Fees { - Fees { +pub(crate) fn default_fees() -> astria_core::protocol::genesis::v1alpha1::Fees { + astria_core::protocol::genesis::v1alpha1::Fees { transfer_base_fee: 12, sequence_base_fee: 32, sequence_byte_cost_multiplier: 1, @@ -89,28 +88,45 @@ pub(crate) fn default_fees() -> Fees { } } -pub(crate) fn unchecked_genesis_state() -> UncheckedGenesisState { - UncheckedGenesisState { - accounts: default_genesis_accounts(), - address_prefixes: AddressPrefixes { - base: crate::test_utils::ASTRIA_PREFIX.into(), - }, - authority_sudo_address: astria_address_from_hex_string(JUDY_ADDRESS), - ibc_sudo_address: astria_address_from_hex_string(TED_ADDRESS), +pub(crate) fn address_prefixes() -> AddressPrefixes { + AddressPrefixes { + base: crate::test_utils::ASTRIA_PREFIX.into(), + } +} + +pub(crate) fn proto_genesis_state() +-> astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + use astria_core::generated::protocol::genesis::v1alpha1::{ + GenesisAppState, + IbcParameters, + }; + GenesisAppState { + address_prefixes: Some(address_prefixes().to_raw()), + accounts: default_genesis_accounts() + .into_iter() + .map(Protobuf::into_raw) + .collect(), + authority_sudo_address: Some(astria_address_from_hex_string(JUDY_ADDRESS).to_raw()), + chain_id: "test-1".to_string(), + ibc_sudo_address: Some(astria_address_from_hex_string(TED_ADDRESS).to_raw()), ibc_relayer_addresses: vec![], - native_asset_base_denomination: crate::test_utils::nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![crate::test_utils::nria().into()], - fees: default_fees(), + native_asset_base_denomination: crate::test_utils::nria().to_string(), + ibc_parameters: Some(IbcParameters { + ibc_enabled: true, + inbound_ics20_transfers_enabled: true, + outbound_ics20_transfers_enabled: true, + }), + allowed_fee_assets: vec![crate::test_utils::nria().to_string()], + fees: Some(default_fees().to_raw()), } } -pub(crate) fn genesis_state() -> GenesisState { - unchecked_genesis_state().try_into().unwrap() +pub(crate) fn genesis_state() -> GenesisAppState { + proto_genesis_state().try_into().unwrap() } pub(crate) async fn initialize_app_with_storage( - genesis_state: Option, + genesis_state: Option, genesis_validators: Vec, ) -> (App, Storage) { let storage = cnidarium::TempStorage::new() @@ -138,7 +154,7 @@ pub(crate) async fn initialize_app_with_storage( #[cfg_attr(feature = "benchmark", allow(dead_code))] pub(crate) async fn initialize_app( - genesis_state: Option, + genesis_state: Option, genesis_validators: Vec, ) -> App { let (app, _storage) = initialize_app_with_storage(genesis_state, genesis_validators).await; diff --git a/crates/astria-sequencer/src/app/tests_app.rs b/crates/astria-sequencer/src/app/tests_app.rs index 58ec904b17..aa6d9551ad 100644 --- a/crates/astria-sequencer/src/app/tests_app.rs +++ b/crates/astria-sequencer/src/app/tests_app.rs @@ -5,16 +5,18 @@ use astria_core::{ asset::TracePrefixed, RollupId, }, - protocol::transaction::v1alpha1::{ - action::{ - BridgeLockAction, - SequenceAction, - TransferAction, + protocol::{ + genesis::v1alpha1::Account, + transaction::v1alpha1::{ + action::{ + BridgeLockAction, + SequenceAction, + TransferAction, + }, + TransactionParams, + UnsignedTransaction, }, - TransactionParams, - UnsignedTransaction, }, - sequencer::Account, sequencerblock::v1alpha1::block::Deposit, }; use cnidarium::StateDelta; diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index f6065ecf0e..6909cc833d 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -16,29 +16,27 @@ use std::{ use astria_core::{ primitive::v1::RollupId, - protocol::transaction::v1alpha1::{ - action::{ - BridgeLockAction, - BridgeSudoChangeAction, - BridgeUnlockAction, - IbcRelayerChangeAction, - SequenceAction, - TransferAction, - ValidatorUpdate, + protocol::{ + genesis::v1alpha1::Account, + transaction::v1alpha1::{ + action::{ + BridgeLockAction, + BridgeSudoChangeAction, + BridgeUnlockAction, + IbcRelayerChangeAction, + SequenceAction, + TransferAction, + ValidatorUpdate, + }, + Action, + TransactionParams, + UnsignedTransaction, }, - Action, - TransactionParams, - UnsignedTransaction, - }, - sequencer::{ - Account, - AddressPrefixes, - UncheckedGenesisState, }, sequencerblock::v1alpha1::block::Deposit, + Protobuf, }; use cnidarium::StateDelta; -use penumbra_ibc::params::IBCParameters; use prost::{ bytes::Bytes, Message as _, @@ -53,12 +51,12 @@ use tendermint::{ use crate::{ app::test_utils::{ - default_fees, default_genesis_accounts, get_alice_signing_key, get_bridge_signing_key, initialize_app, initialize_app_with_storage, + proto_genesis_state, BOB_ADDRESS, CAROL_ADDRESS, }, @@ -72,26 +70,6 @@ use crate::{ }, }; -/// 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 = get_alice_signing_key(); - let alice_address = astria_address(&alice.address_bytes()); - UncheckedGenesisState { - accounts: vec![], - address_prefixes: AddressPrefixes { - base: ASTRIA_PREFIX.into(), - }, - authority_sudo_address: alice_address, - ibc_sudo_address: alice_address, - ibc_relayer_addresses: vec![], - native_asset_base_denomination: nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![nria().into()], - fees: default_fees(), - } -} - #[tokio::test] async fn app_genesis_snapshot() { let app = initialize_app(None, vec![]).await; @@ -192,15 +170,20 @@ async fn app_execute_transaction_with_every_action_snapshot() { let bridge_address = astria_address(&bridge.address_bytes()); let bob_address = astria_address_from_hex_string(BOB_ADDRESS); let carol_address = astria_address_from_hex_string(CAROL_ADDRESS); - let mut accounts = default_genesis_accounts(); - accounts.push(Account { - address: bridge_address, - balance: 1_000_000_000, - }); - let genesis_state = UncheckedGenesisState { + let accounts = { + let mut acc = default_genesis_accounts(); + acc.push(Account { + address: bridge_address, + balance: 1_000_000_000, + }); + acc.into_iter().map(Protobuf::into_raw).collect() + }; + let genesis_state = astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { accounts, - ..unchecked_genesis_state() + authority_sudo_address: Some(alice.try_address(ASTRIA_PREFIX).unwrap().to_raw()), + ibc_sudo_address: Some(alice.try_address(ASTRIA_PREFIX).unwrap().to_raw()), + ..proto_genesis_state() } .try_into() .unwrap(); diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index e88d0b5227..c433c48fcf 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -6,35 +6,39 @@ use astria_core::{ asset, RollupId, }, - protocol::transaction::v1alpha1::{ - action::{ - BridgeLockAction, - BridgeUnlockAction, - IbcRelayerChangeAction, - SequenceAction, - SudoAddressChangeAction, - TransferAction, - ValidatorUpdate, + protocol::{ + genesis::v1alpha1::GenesisAppState, + transaction::v1alpha1::{ + action::{ + BridgeLockAction, + BridgeUnlockAction, + IbcRelayerChangeAction, + SequenceAction, + SudoAddressChangeAction, + TransferAction, + ValidatorUpdate, + }, + Action, + TransactionParams, + UnsignedTransaction, }, - Action, - TransactionParams, - UnsignedTransaction, - }, - sequencer::{ - AddressPrefixes, - GenesisState, - UncheckedGenesisState, }, sequencerblock::v1alpha1::block::Deposit, + Protobuf as _, }; use bytes::Bytes; use cnidarium::StateDelta; -use penumbra_ibc::params::IBCParameters; +use super::test_utils::get_alice_signing_key; use crate::{ accounts::StateReadExt as _, app::{ - test_utils::*, + test_utils::{ + get_bridge_signing_key, + initialize_app, + BOB_ADDRESS, + CAROL_ADDRESS, + }, ActionHandler as _, }, assets::StateReadExt as _, @@ -49,6 +53,7 @@ use crate::{ astria_address, astria_address_from_hex_string, nria, + ASTRIA_PREFIX, }, transaction::{ InvalidChainId, @@ -56,27 +61,26 @@ 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 = get_alice_signing_key(); - UncheckedGenesisState { - accounts: default_genesis_accounts(), - address_prefixes: AddressPrefixes { - base: crate::test_utils::ASTRIA_PREFIX.into(), - }, - authority_sudo_address: crate::test_utils::astria_address(&alice.address_bytes()), - ibc_sudo_address: crate::test_utils::astria_address(&alice.address_bytes()), - ibc_relayer_addresses: vec![], - native_asset_base_denomination: crate::test_utils::nria(), - ibc_params: IBCParameters::default(), - allowed_fee_assets: vec![crate::test_utils::nria().into()], - fees: default_fees(), +fn proto_genesis_state() -> astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + astria_core::generated::protocol::genesis::v1alpha1::GenesisAppState { + authority_sudo_address: Some( + get_alice_signing_key() + .try_address(ASTRIA_PREFIX) + .unwrap() + .to_raw(), + ), + ibc_sudo_address: Some( + get_alice_signing_key() + .try_address(ASTRIA_PREFIX) + .unwrap() + .to_raw(), + ), + ..crate::app::test_utils::proto_genesis_state() } } -fn genesis_state() -> GenesisState { - unchecked_genesis_state().try_into().unwrap() +fn genesis_state() -> GenesisAppState { + GenesisAppState::try_from_raw(proto_genesis_state()).unwrap() } fn test_asset() -> asset::Denom { @@ -367,9 +371,10 @@ async fn app_execute_transaction_ibc_relayer_change_deletion() { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let genesis_state = UncheckedGenesisState { - ibc_relayer_addresses: vec![alice_address], - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state.ibc_relayer_addresses.push(alice_address.to_raw()); + state } .try_into() .unwrap(); @@ -393,10 +398,13 @@ async fn app_execute_transaction_ibc_relayer_change_deletion() { async fn app_execute_transaction_ibc_relayer_change_invalid() { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let genesis_state = UncheckedGenesisState { - ibc_sudo_address: astria_address(&[0; 20]), - ibc_relayer_addresses: vec![alice_address], - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state + .ibc_sudo_address + .replace(astria_address(&[0; 20]).to_raw()); + state.ibc_relayer_addresses.push(alice_address.to_raw()); + state } .try_into() .unwrap(); @@ -447,10 +455,15 @@ async fn app_execute_transaction_sudo_address_change_error() { let alice_address = astria_address(&alice.address_bytes()); let authority_sudo_address = astria_address_from_hex_string(CAROL_ADDRESS); - let genesis_state = UncheckedGenesisState { - authority_sudo_address, - ibc_sudo_address: astria_address(&[0u8; 20]), - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state + .authority_sudo_address + .replace(authority_sudo_address.to_raw()); + state + .ibc_sudo_address + .replace(astria_address(&[0u8; 20]).to_raw()); + state } .try_into() .unwrap(); @@ -509,9 +522,10 @@ async fn app_execute_transaction_fee_asset_change_removal() { let alice = get_alice_signing_key(); let alice_address = astria_address(&alice.address_bytes()); - let genesis_state = UncheckedGenesisState { - allowed_fee_assets: vec![nria().into(), test_asset()], - ..unchecked_genesis_state() + let genesis_state = { + let mut state = proto_genesis_state(); + state.allowed_fee_assets.push(test_asset().to_string()); + state } .try_into() .unwrap(); diff --git a/crates/astria-sequencer/src/bridge/component.rs b/crates/astria-sequencer/src/bridge/component.rs index cdc8f9b98b..88c105e9d0 100644 --- a/crates/astria-sequencer/src/bridge/component.rs +++ b/crates/astria-sequencer/src/bridge/component.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use anyhow::Result; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use tendermint::abci::request::{ BeginBlock, EndBlock, @@ -15,7 +16,7 @@ pub(crate) struct BridgeComponent; #[async_trait::async_trait] impl Component for BridgeComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "BridgeComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> { diff --git a/crates/astria-sequencer/src/ibc/component.rs b/crates/astria-sequencer/src/ibc/component.rs index 3a44b6865d..6f0ac0d8db 100644 --- a/crates/astria-sequencer/src/ibc/component.rs +++ b/crates/astria-sequencer/src/ibc/component.rs @@ -4,6 +4,7 @@ use anyhow::{ Context, Result, }; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use penumbra_ibc::{ component::Ibc, genesis::Content, @@ -27,14 +28,14 @@ pub(crate) struct IbcComponent; #[async_trait::async_trait] impl Component for IbcComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "IbcComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> { Ibc::init_chain( &mut state, Some(&Content { - ibc_params: app_state.ibc_params().clone(), + ibc_params: app_state.ibc_parameters().clone(), }), ) .await; diff --git a/crates/astria-sequencer/src/sequence/component.rs b/crates/astria-sequencer/src/sequence/component.rs index 84265321a5..742fb6ea1f 100644 --- a/crates/astria-sequencer/src/sequence/component.rs +++ b/crates/astria-sequencer/src/sequence/component.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use anyhow::Result; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use tendermint::abci::request::{ BeginBlock, EndBlock, @@ -15,7 +16,7 @@ pub(crate) struct SequenceComponent; #[async_trait::async_trait] impl Component for SequenceComponent { - type AppState = astria_core::sequencer::GenesisState; + type AppState = GenesisAppState; #[instrument(name = "SequenceComponent::init_chain", skip_all)] async fn init_chain(mut state: S, app_state: &Self::AppState) -> Result<()> { diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index 0049267657..f5b6c9d56a 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -2,6 +2,7 @@ use anyhow::{ bail, Context, }; +use astria_core::protocol::genesis::v1alpha1::GenesisAppState; use cnidarium::Storage; use tendermint::v0_38::abci::{ request, @@ -125,9 +126,8 @@ impl Consensus { bail!("database already initialized"); } - let genesis_state: astria_core::sequencer::GenesisState = - serde_json::from_slice(&init_chain.app_state_bytes) - .context("failed to parse app_state in genesis file")?; + let genesis_state: GenesisAppState = serde_json::from_slice(&init_chain.app_state_bytes) + .context("failed to parse genesis app state from init chain request")?; let app_hash = self .app .init_chain( @@ -216,11 +216,6 @@ mod test { TransactionParams, UnsignedTransaction, }, - sequencer::{ - Account, - AddressPrefixes, - UncheckedGenesisState, - }, }; use bytes::Bytes; use prost::Message as _; @@ -233,7 +228,6 @@ mod test { use super::*; use crate::{ - app::test_utils::default_fees, mempool::Mempool, metrics::Metrics, proposal::commitment::generate_rollup_datas_commitment, @@ -446,26 +440,22 @@ mod test { } async fn new_consensus_service(funded_key: Option) -> (Consensus, Mempool) { - let accounts = if funded_key.is_some() { - vec![Account { - address: crate::test_utils::astria_address(&funded_key.unwrap().address_bytes()), - balance: 10u128.pow(19), - }] + let accounts = if let Some(funded_key) = funded_key { + vec![ + astria_core::generated::protocol::genesis::v1alpha1::Account { + address: Some( + crate::test_utils::astria_address(&funded_key.address_bytes()).to_raw(), + ), + balance: Some(10u128.pow(19).into()), + }, + ] } else { vec![] }; - let genesis_state = UncheckedGenesisState { - accounts, - address_prefixes: AddressPrefixes { - base: crate::test_utils::ASTRIA_PREFIX.into(), - }, - authority_sudo_address: crate::test_utils::astria_address(&[0; 20]), - ibc_sudo_address: crate::test_utils::astria_address(&[0; 20]), - ibc_relayer_addresses: vec![], - native_asset_base_denomination: crate::test_utils::nria(), - ibc_params: penumbra_ibc::params::IBCParameters::default(), - allowed_fee_assets: vec!["nria".parse().unwrap()], - fees: default_fees(), + let genesis_state = { + let mut state = crate::app::test_utils::proto_genesis_state(); + state.accounts = accounts; + state } .try_into() .unwrap(); diff --git a/proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto b/proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto new file mode 100644 index 0000000000..89fa8fb3ae --- /dev/null +++ b/proto/protocolapis/astria/protocol/genesis/v1alpha1/types.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package astria.protocol.genesis.v1alpha1; + +import "astria/primitive/v1/types.proto"; + +message GenesisAppState { + string chain_id = 1; + AddressPrefixes address_prefixes = 2; + repeated Account accounts = 3; + astria.primitive.v1.Address authority_sudo_address = 4; + astria.primitive.v1.Address ibc_sudo_address = 5; + repeated astria.primitive.v1.Address ibc_relayer_addresses = 6; + string native_asset_base_denomination = 7; + IbcParameters ibc_parameters = 8; + repeated string allowed_fee_assets = 9; + Fees fees = 10; +} + +message Account { + astria.primitive.v1.Address address = 1; + astria.primitive.v1.Uint128 balance = 2; +} + +message AddressPrefixes { + string base = 1; +} + +// IBC configuration data. +message IbcParameters { + // Whether IBC (forming connections, processing IBC packets) is enabled. + bool ibc_enabled = 1; + // Whether inbound ICS-20 transfers are enabled + bool inbound_ics20_transfers_enabled = 2; + // Whether outbound ICS-20 transfers are enabled + bool outbound_ics20_transfers_enabled = 3; +} + +message Fees { + astria.primitive.v1.Uint128 transfer_base_fee = 1; + astria.primitive.v1.Uint128 sequence_base_fee = 2; + astria.primitive.v1.Uint128 sequence_byte_cost_multiplier = 3; + astria.primitive.v1.Uint128 init_bridge_account_base_fee = 4; + astria.primitive.v1.Uint128 bridge_lock_byte_cost_multiplier = 5; + astria.primitive.v1.Uint128 bridge_sudo_change_fee = 6; + astria.primitive.v1.Uint128 ics20_withdrawal_base_fee = 7; +} diff --git a/tools/protobuf-compiler/src/main.rs b/tools/protobuf-compiler/src/main.rs index 20700a0d40..5404a5392e 100644 --- a/tools/protobuf-compiler/src/main.rs +++ b/tools/protobuf-compiler/src/main.rs @@ -77,6 +77,10 @@ fn main() { "crate::generated::astria_vendored::tendermint::abci::ValidatorUpdate", ) .type_attribute(".astria.primitive.v1.Uint128", "#[derive(Copy)]") + .type_attribute( + ".astria.protocol.genesis.v1alpha1.IbcParameters", + "#[derive(Copy)]", + ) .use_arc_self(true) // override prost-types with pbjson-types .compile_well_known_types(true)