Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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