Skip to content

Commit

Permalink
feat(sequencer-utils): generate example genesis state (#1224)
Browse files Browse the repository at this point in the history
## Summary
Provides a `generate-genesis-state` subcommand to sequencer-utils.

## Background
We had a `test-genesis.json` file committed to the repo that was not
checked to be in-sync with the most recent version of sequencer's actual
`GenesisState`. The subcommand `generate-genesis-state` removes the need
to keep the file in sync because one can always generate one that
matches the current format.

## Changes
- remove `astria-sequencer/test-genesis-app-state.json`
- move sequencer's `GenesisState` to the new `astria-core::sequencer`
submodule. Because this config is effectively a public API, it is
justified to be outside of the `astria-sequencer` binary crate.

## Testing
It is a very thin binary which can be run by hand. The specific format
of `GenesisState` not changing is ensured by a snapshot test that was
moved from `astria-sequencer` to `astria-core`.

## Related Issues
Closes #1212

---------

Co-authored-by: noot <[email protected]>
  • Loading branch information
SuperFluffy and noot authored Jul 3, 2024
1 parent a8db883 commit e3e7548
Show file tree
Hide file tree
Showing 23 changed files with 370 additions and 177 deletions.
1 change: 1 addition & 0 deletions crates/astria-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod crypto;
pub mod execution;
pub mod primitive;
pub mod protocol;
pub mod sequencer;
pub mod sequencerblock;

#[cfg(feature = "brotli")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,90 @@
use astria_core::primitive::v1::{
//! Sequencer specific types that are needed outside of it.
pub use penumbra_ibc::params::IBCParameters;

use crate::primitive::v1::{
asset,
Address,
};
use penumbra_ibc::params::IBCParameters;
use serde::{
Deserialize,
Serialize,
};

/// The genesis state for the application.
/// 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:** The fields should not be publicly accessible to guarantee invariants. However,
/// it's easy to just go along with this for now.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(try_from = "UncheckedGenesisState", into = "UncheckedGenesisState")]
pub(crate) struct GenesisState {
pub(crate) address_prefixes: AddressPrefixes,
pub(crate) accounts: Vec<Account>,
pub(crate) authority_sudo_address: Address,
pub(crate) ibc_sudo_address: Address,
pub(crate) ibc_relayer_addresses: Vec<Address>,
pub(crate) native_asset_base_denomination: String,
pub(crate) ibc_params: IBCParameters,
pub(crate) allowed_fee_assets: Vec<asset::Denom>,
pub(crate) fees: Fees,
/// *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<Account>,
authority_sudo_address: Address,
ibc_sudo_address: Address,
ibc_relayer_addresses: Vec<Address>,
native_asset_base_denomination: String,
ibc_params: IBCParameters,
allowed_fee_assets: Vec<asset::Denom>,
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) -> &str {
&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)]
// allow: this error is only seen at chain init and never after so perf impact of too large enum
// variants is negligible
#[allow(clippy::result_large_err)]
pub(crate) enum VerifyGenesisError {
#[error(transparent)]
pub struct VerifyGenesisError(Box<VerifyGenesisErrorKind>);

#[derive(Debug, thiserror::Error)]
enum VerifyGenesisErrorKind {
#[error("address `{address}` at `{field}` does not have `{base_prefix}`")]
AddressDoesNotMatchBase {
base_prefix: String,
Expand All @@ -42,6 +93,12 @@ pub(crate) enum VerifyGenesisError {
},
}

impl From<VerifyGenesisErrorKind> for VerifyGenesisError {
fn from(value: VerifyGenesisErrorKind) -> Self {
Self(Box::new(value))
}
}

impl TryFrom<UncheckedGenesisState> for GenesisState {
type Error = VerifyGenesisError;

Expand Down Expand Up @@ -75,39 +132,37 @@ impl TryFrom<UncheckedGenesisState> for GenesisState {
}

/// The unchecked genesis state for the application.
#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct UncheckedGenesisState {
pub(crate) address_prefixes: AddressPrefixes,
pub(crate) accounts: Vec<Account>,
pub(crate) authority_sudo_address: Address,
pub(crate) ibc_sudo_address: Address,
pub(crate) ibc_relayer_addresses: Vec<Address>,
pub(crate) native_asset_base_denomination: String,
pub(crate) ibc_params: IBCParameters,
pub(crate) allowed_fee_assets: Vec<asset::Denom>,
pub(crate) fees: Fees,
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct UncheckedGenesisState {
pub address_prefixes: AddressPrefixes,
pub accounts: Vec<Account>,
pub authority_sudo_address: Address,
pub ibc_sudo_address: Address,
pub ibc_relayer_addresses: Vec<Address>,
pub native_asset_base_denomination: String,
pub ibc_params: IBCParameters,
pub allowed_fee_assets: Vec<asset::Denom>,
pub fees: Fees,
}

impl UncheckedGenesisState {
// allow: as for the enum definition itself: this only happens at init-chain and is negligible
#[allow(clippy::result_large_err)]
fn ensure_address_has_base_prefix(
&self,
address: &Address,
field: &str,
) -> Result<(), VerifyGenesisError> {
if self.address_prefixes.base != address.prefix() {
return Err(VerifyGenesisError::AddressDoesNotMatchBase {
return Err(VerifyGenesisErrorKind::AddressDoesNotMatchBase {
base_prefix: self.address_prefixes.base.clone(),
address: *address,
field: field.to_string(),
});
}
.into());
}
Ok(())
}

// allow: as for the enum definition itself: this only happens at init-chain and is negligible
#[allow(clippy::result_large_err)]
fn ensure_all_addresses_have_base_prefix(&self) -> Result<(), VerifyGenesisError> {
for (i, account) in self.accounts.iter().enumerate() {
self.ensure_address_has_base_prefix(
Expand Down Expand Up @@ -154,33 +209,35 @@ impl From<GenesisState> for UncheckedGenesisState {
}
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct Fees {
pub(crate) transfer_base_fee: u128,
pub(crate) sequence_base_fee: u128,
pub(crate) sequence_byte_cost_multiplier: u128,
pub(crate) init_bridge_account_base_fee: u128,
pub(crate) bridge_lock_byte_cost_multiplier: u128,
pub(crate) bridge_sudo_change_fee: u128,
pub(crate) ics20_withdrawal_base_fee: u128,
#[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, Deserialize, Serialize)]
pub(crate) struct Account {
pub(crate) address: Address,
pub(crate) balance: 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, Deserialize, Serialize)]
pub(crate) struct AddressPrefixes {
pub(crate) base: String,
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct AddressPrefixes {
pub base: String,
}

#[cfg(test)]
mod test {
use astria_core::primitive::v1::Address;

mod tests {
use super::*;
use crate::primitive::v1::Address;

const ASTRIA_ADDRESS_PREFIX: &str = "astria";

Expand Down Expand Up @@ -265,11 +322,14 @@ mod test {
fn mismatched_addresses_are_caught() {
#[track_caller]
fn assert_bad_prefix(unchecked: UncheckedGenesisState, bad_field: &'static str) {
match GenesisState::try_from(unchecked).expect_err(
"converting to genesis state should have produced an error, but a valid state was \
returned",
) {
VerifyGenesisError::AddressDoesNotMatchBase {
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,
Expand Down Expand Up @@ -319,6 +379,7 @@ mod test {
);
}

#[cfg(feature = "serde")]
#[test]
fn genesis_state_is_unchanged() {
insta::assert_json_snapshot!(genesis_state());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: crates/astria-sequencer/src/genesis.rs
source: crates/astria-core/src/sequencer.rs
expression: genesis_state()
---
{
Expand Down
2 changes: 1 addition & 1 deletion crates/astria-sequencer-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ rlp = "0.5.2"
serde = { workspace = true }
serde_json = { workspace = true }

astria-core = { path = "../astria-core", features = ["brotli"] }
astria-core = { path = "../astria-core", features = ["brotli", "serde"] }
astria-eyre = { path = "../astria-eyre" }
astria-merkle = { path = "../astria-merkle" }

Expand Down
28 changes: 26 additions & 2 deletions crates/astria-sequencer-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,29 @@

## General

There are two functions provided by the tool, as described below.
There are three functions provided by the tool, further described below:

1. `generate-genesis-state`
1. `copy-genesis-state`
1. `parse-blob`

### `generate-genesis-state`: create an example sequencer genesis state

This subcommand creates an example genesis state file that can be
fed into the `copy-genesis-state` command. It should be editted to
have the desired options.

#### Usage for `generate-genesis-state`

1. no arguments: it will write the example genesis to stdout.
1. `--output <PATH>`: write the example genesis to `<PATH>`.
1. `-f`: override `<PATH>` in the previous argument if another file was present.

#### Example for `generate-genesis-state`

```sh
cargo run -- generate-genesis-state -o genesis-state.json
```

### `copy-genesis-state`: JSON-encode Genesis State to a File

Expand All @@ -27,8 +49,10 @@ the path to the output file
In `crates/astria-sequencer-utils`:

```sh
# genesis-state.json would be a file created by the generate-genesis-state
# subcommand and then manually edited
cargo run -- copy-genesis-state \
--genesis-app-state-file=../astria-sequencer/test-genesis-app-state.json \
--genesis-app-state-file=genesis-state.json \
--output=$HOME/.cometbft/config/genesis.json \
--chain-id=astria
```
Expand Down
4 changes: 4 additions & 0 deletions crates/astria-sequencer-utils/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::{

use super::{
blob_parser,
genesis_example,
genesis_parser,
};

Expand All @@ -22,6 +23,9 @@ pub enum Command {
#[command(arg_required_else_help = true)]
CopyGenesisState(genesis_parser::Args),

/// Generate an example sequencer genesis state
GenerateGenesisState(genesis_example::Args),

/// Parse blob data from an arg, a file, or stdin
#[command(arg_required_else_help = true)]
ParseBlob(blob_parser::Args),
Expand Down
Loading

0 comments on commit e3e7548

Please sign in to comment.