Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check consistency of genesis files with chain ID #3843

Merged
merged 9 commits into from
Sep 25, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Let user customize the pre-genesis chain-id via environment variable
([\#3833](https://github.com/anoma/namada/pull/3833))
3 changes: 3 additions & 0 deletions .changelog/unreleased/improvements/3843-check-genesis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Validate a chain ID of genesis on ABCI InitChain request
prior to applying it to ensure it's not been tampered with.
([\#3843](https://github.com/anoma/namada/pull/3843))
4 changes: 3 additions & 1 deletion .github/workflows/scripts/e2e.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@
"e2e::wallet_tests::wallet_encrypted_key_cmds": 1,
"e2e::wallet_tests::wallet_encrypted_key_cmds_env_var": 1,
"e2e::wallet_tests::wallet_unencrypted_key_cmds": 1,
"e2e::ledger_tests::masp_txs_and_queries": 82
"e2e::ledger_tests::masp_txs_and_queries": 82,
"e2e::ledger_tests::test_genesis_chain_id_change": 35,
"e2e::ledger_tests::test_genesis_manipulation": 103
}
73 changes: 69 additions & 4 deletions crates/apps_lib/src/config/genesis/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::str::FromStr;

use borsh::{BorshDeserialize, BorshSerialize};
use borsh_ext::BorshSerializeExt;
use eyre::eyre;
use namada_macros::BorshDeserializer;
#[cfg(feature = "migrations")]
use namada_migrations::*;
Expand Down Expand Up @@ -106,6 +107,8 @@ impl Finalized {

/// Try to read all genesis and the chain metadata TOML files from the given
/// directory.
///
/// The consistency of the files is checked with [`Finalized::is_valid`].
pub fn read_toml_files(input_dir: &Path) -> eyre::Result<Self> {
let vps_file = input_dir.join(templates::VPS_FILE_NAME);
let tokens_file = input_dir.join(templates::TOKENS_FILE_NAME);
Expand All @@ -121,14 +124,20 @@ impl Finalized {
let parameters = read_toml(&parameters_file, "Parameters")?;
let transactions = read_toml(&transactions_file, "Transactions")?;
let metadata = read_toml(&metadata_file, "Chain metadata")?;
Ok(Self {
let genesis = Self {
vps,
tokens,
balances,
parameters,
transactions,
metadata,
})
};

if !genesis.is_valid() {
return Err(eyre!("Invalid genesis files"));
}

Ok(genesis)
}

/// Find the address of the configured native token
Expand Down Expand Up @@ -485,6 +494,54 @@ impl Finalized {
pub fn get_token_address(&self, alias: &Alias) -> Option<&Address> {
self.tokens.token.get(alias).map(|token| &token.address)
}

// Validate the chain ID against the genesis contents
pub fn is_valid(&self) -> bool {
let Self {
vps,
tokens,
balances,
parameters,
transactions,
metadata,
} = self.clone();
let Metadata {
chain_id,
genesis_time,
consensus_timeout_commit,
address_gen,
} = metadata.clone();

let Some(chain_id_prefix) = chain_id.prefix() else {
tracing::warn!(
"Invalid Chain ID \"{chain_id}\" - unable to find a prefix"
);
return false;
};
let metadata = Metadata {
chain_id: chain_id_prefix.clone(),
genesis_time,
consensus_timeout_commit,
address_gen,
};
let to_finalize = ToFinalize {
vps,
tokens,
balances,
parameters,
transactions,
metadata,
};
let derived_chain_id = derive_chain_id(chain_id_prefix, &to_finalize);
let is_valid = derived_chain_id == chain_id;
if !is_valid {
tracing::warn!(
"Invalid chain ID. This indicates that something in the \
genesis files might have been modified."
);
}
is_valid
}
}

/// Create the [`Finalized`] chain configuration. Derives the chain ID from the
Expand Down Expand Up @@ -541,8 +598,7 @@ pub fn finalize(
parameters,
transactions,
};
let to_finalize_bytes = to_finalize.serialize_to_vec();
let chain_id = ChainId::from_genesis(chain_id_prefix, to_finalize_bytes);
let chain_id = derive_chain_id(chain_id_prefix, &to_finalize);

// Construct the `Finalized` chain
let ToFinalize {
Expand Down Expand Up @@ -575,6 +631,15 @@ pub fn finalize(
}
}

/// Derive a chain ID from genesis contents
pub fn derive_chain_id(
chain_id_prefix: ChainIdPrefix,
to_finalize: &ToFinalize,
) -> ChainId {
let to_finalize_bytes = to_finalize.serialize_to_vec();
ChainId::from_genesis(chain_id_prefix, to_finalize_bytes)
}

/// Chain genesis config to be finalized. This struct is used to derive the
/// chain ID to construct a [`Finalized`] chain genesis config.
#[derive(
Expand Down
13 changes: 12 additions & 1 deletion crates/apps_lib/src/config/genesis/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Genesis transactions

use std::collections::{BTreeMap, BTreeSet};
use std::env;
use std::fmt::Debug;
use std::net::SocketAddr;

Expand Down Expand Up @@ -46,6 +47,7 @@ use crate::config::genesis::{utils, GenesisAddress};
use crate::wallet::{CliWalletUtils, WalletTransport};

/// Dummy chain id used to sign [`Tx`] objects at pre-genesis.
pub const NAMADA_GENESIS_TX_ENV_VAR: &str = "NAMADA_GENESIS_TX_CHAIN_ID";
const NAMADA_GENESIS_TX_CHAIN_ID: &str = "namada-genesis";

/// Helper trait to fetch tx data to sign.
Expand All @@ -62,6 +64,15 @@ pub trait TxToSign {
fn get_owner(&self) -> GenesisAddress;
}

/// Get the genesis chain ID from "NAMADA_GENESIS_TX_CHAIN_ID" env var, if set,
/// or the default
pub fn genesis_chain_id() -> ChainId {
ChainId(
env::var(NAMADA_GENESIS_TX_ENV_VAR)
.unwrap_or_else(|_| NAMADA_GENESIS_TX_CHAIN_ID.to_string()),
)
}

/// Return a dummy set of tx arguments to sign with the
/// hardware wallet.
fn get_tx_args(use_device: bool) -> TxArgs {
Expand Down Expand Up @@ -107,7 +118,7 @@ fn pre_genesis_tx_timestamp() -> DateTimeUtc {
/// Return a ready to sign genesis [`Tx`].
fn get_tx_to_sign(tag: impl AsRef<str>, data: impl BorshSerialize) -> Tx {
let mut tx = Tx::from_type(TxType::Raw);
tx.header.chain_id = ChainId(NAMADA_GENESIS_TX_CHAIN_ID.to_string());
tx.header.chain_id = genesis_chain_id();
tx.header.timestamp = pre_genesis_tx_timestamp();
tx.set_code(Code {
salt: [0; 8],
Expand Down
7 changes: 7 additions & 0 deletions crates/core/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ impl ChainId {
}
errors
}

/// Find the prefix of a valid ChainId.
pub fn prefix(&self) -> Option<ChainIdPrefix> {
let ChainId(chain_id) = self;
let (prefix, _) = chain_id.rsplit_once(CHAIN_ID_PREFIX_SEP)?;
Some(ChainIdPrefix(prefix.to_string()))
}
}

/// Height of a block, i.e. the level. The `default` is the
Expand Down
4 changes: 2 additions & 2 deletions crates/node/src/shell/init_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ where
let genesis = {
let chain_dir = self.base_dir.join(chain_id);
genesis::chain::Finalized::read_toml_files(&chain_dir)
.expect("Missing genesis files")
.expect("Missing or invalid genesis files")
};
#[cfg(any(test, fuzzing, feature = "benches"))]
let genesis = {
let chain_dir = self.base_dir.join(chain_id);
if chain_dir.join(genesis::chain::METADATA_FILE_NAME).exists() {
genesis::chain::Finalized::read_toml_files(&chain_dir)
.expect("Missing genesis files")
.expect("Missing or invalid genesis files")
} else {
genesis::make_dev_genesis(num_validators, &chain_dir)
}
Expand Down
5 changes: 4 additions & 1 deletion crates/node/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ pub fn test_genesis(args: TestGenesis, global_args: args::Global) {
check_can_sign,
} = args;

let templates = genesis::templates::load_and_validate(&path).unwrap();
let Some(templates) = genesis::templates::load_and_validate(&path) else {
eprintln!("Unable to load the genesis templates");
cli::safe_exit(1);
};
let genesis = genesis::chain::finalize(
templates,
FromStr::from_str("namada-dryrun").unwrap(),
Expand Down
Loading