namada_apps_lib/config/genesis/
chain.rs

1use std::collections::BTreeMap;
2use std::path::Path;
3use std::str::FromStr;
4
5use eyre::eyre;
6use namada_macros::BorshDeserializer;
7#[cfg(feature = "migrations")]
8use namada_migrations::*;
9use namada_sdk::address::{
10    Address, EstablishedAddress, EstablishedAddressGen, InternalAddress,
11};
12use namada_sdk::borsh::{BorshDeserialize, BorshSerialize, BorshSerializeExt};
13use namada_sdk::chain::{ChainId, ChainIdPrefix};
14use namada_sdk::eth_bridge::EthereumBridgeParams;
15use namada_sdk::governance::pgf::parameters::PgfParameters;
16use namada_sdk::hash::Hash;
17use namada_sdk::ibc::parameters::{IbcParameters, IbcTokenRateLimits};
18use namada_sdk::key::{RefTo, common};
19use namada_sdk::parameters::EpochDuration;
20use namada_sdk::time::{DateTimeUtc, DurationNanos, Rfc3339String};
21use namada_sdk::token::Amount;
22use namada_sdk::wallet::store::AddressVpType;
23use namada_sdk::wallet::{Wallet, pre_genesis};
24use serde::{Deserialize, Serialize};
25use sha2::{Digest, Sha256};
26
27use super::utils::{read_toml, write_toml};
28use super::{templates, transactions};
29use crate::config::genesis::templates::Validated;
30use crate::config::utils::{set_ip, set_port};
31use crate::config::{Config, TendermintMode};
32use crate::tendermint::node::Id as TendermintNodeId;
33use crate::tendermint_config::net::Address as TendermintAddress;
34use crate::tendermint_node::id_from_pk;
35use crate::wallet::{Alias, CliWalletUtils};
36use crate::wasm_loader;
37
38pub const METADATA_FILE_NAME: &str = "chain.toml";
39
40/// Derive established addresses from seed data.
41pub trait DeriveEstablishedAddress {
42    /// Arbitrary data to hash the seed data with.
43    const SALT: &'static str;
44
45    /// Derive an established address.
46    fn derive_established_address(&self) -> EstablishedAddress
47    where
48        Self: BorshSerialize,
49    {
50        let mut hasher = Sha256::new();
51        hasher.update(Self::SALT.as_bytes());
52        hasher.update(self.serialize_to_vec());
53        let digest = hasher.finalize();
54        let digest_ref: &[u8; 32] = digest.as_ref();
55        EstablishedAddress::from(*digest_ref)
56    }
57
58    /// Derive an address.
59    #[inline]
60    fn derive_address(&self) -> Address
61    where
62        Self: BorshSerialize,
63    {
64        Address::Established(self.derive_established_address())
65    }
66}
67
68impl Finalized {
69    /// Write all genesis and the chain metadata TOML files to the given
70    /// directory.
71    pub fn write_toml_files(&self, output_dir: &Path) -> eyre::Result<()> {
72        let vps_file = output_dir.join(templates::VPS_FILE_NAME);
73        let tokens_file = output_dir.join(templates::TOKENS_FILE_NAME);
74        let balances_file = output_dir.join(templates::BALANCES_FILE_NAME);
75        let parameters_file = output_dir.join(templates::PARAMETERS_FILE_NAME);
76        let transactions_file =
77            output_dir.join(templates::TRANSACTIONS_FILE_NAME);
78        let metadata_file = output_dir.join(METADATA_FILE_NAME);
79
80        write_toml(&self.vps, &vps_file, "Validity predicates")?;
81        write_toml(&self.tokens, &tokens_file, "Tokens")?;
82        write_toml(&self.balances, &balances_file, "Balances")?;
83        write_toml(&self.parameters, &parameters_file, "Parameters")?;
84        write_toml(&self.transactions, &transactions_file, "Transactions")?;
85        write_toml(&self.metadata, &metadata_file, "Chain metadata")?;
86        Ok(())
87    }
88
89    /// Attempt to read the address of the native token.
90    pub fn read_native_token(input_dir: &Path) -> eyre::Result<Address> {
91        let tokens_file = input_dir.join(templates::TOKENS_FILE_NAME);
92        let parameters_file = input_dir.join(templates::PARAMETERS_FILE_NAME);
93
94        let mut tokens: FinalizedTokens = read_toml(&tokens_file, "Tokens")?;
95        let parameters: FinalizedParameters =
96            read_toml(&parameters_file, "Parameters")?;
97
98        let alias = &parameters.parameters.native_token;
99
100        Ok(tokens
101            .token
102            .remove(alias)
103            .expect("The native token must exist")
104            .address)
105    }
106
107    /// Try to read all genesis and the chain metadata TOML files from the given
108    /// directory.
109    ///
110    /// The consistency of the files is checked with [`Finalized::is_valid`].
111    pub fn read_toml_files(input_dir: &Path) -> eyre::Result<Self> {
112        let vps_file = input_dir.join(templates::VPS_FILE_NAME);
113        let tokens_file = input_dir.join(templates::TOKENS_FILE_NAME);
114        let balances_file = input_dir.join(templates::BALANCES_FILE_NAME);
115        let parameters_file = input_dir.join(templates::PARAMETERS_FILE_NAME);
116        let transactions_file =
117            input_dir.join(templates::TRANSACTIONS_FILE_NAME);
118        let metadata_file = input_dir.join(METADATA_FILE_NAME);
119
120        let vps = read_toml(&vps_file, "Validity predicates")?;
121        let tokens = read_toml(&tokens_file, "Tokens")?;
122        let balances = read_toml(&balances_file, "Balances")?;
123        let parameters = read_toml(&parameters_file, "Parameters")?;
124        let transactions = read_toml(&transactions_file, "Transactions")?;
125        let metadata = read_toml(&metadata_file, "Chain metadata")?;
126        let genesis = Self {
127            vps,
128            tokens,
129            balances,
130            parameters,
131            transactions,
132            metadata,
133        };
134
135        if !genesis.is_valid() {
136            return Err(eyre!("Invalid genesis files"));
137        }
138
139        Ok(genesis)
140    }
141
142    /// Find the address of the configured native token
143    pub fn get_native_token(&self) -> &Address {
144        let alias = &self.parameters.parameters.native_token;
145        &self
146            .tokens
147            .token
148            .get(alias)
149            .expect("The native token must exist")
150            .address
151    }
152
153    /// Derive Namada wallet from genesis
154    pub fn derive_wallet(
155        &self,
156        base_dir: &Path,
157        pre_genesis_wallet: Option<Wallet<CliWalletUtils>>,
158        validator: Option<(Alias, pre_genesis::ValidatorWallet)>,
159    ) -> Wallet<CliWalletUtils> {
160        let mut wallet = crate::wallet::load_or_new(base_dir);
161        for (alias, config) in &self.tokens.token {
162            wallet.insert_address(
163                alias.normalize(),
164                config.address.clone(),
165                false,
166            );
167            wallet.add_vp_type_to_address(
168                AddressVpType::Token,
169                config.address.clone(),
170            );
171        }
172        if let Some(pre_genesis_wallet) = pre_genesis_wallet {
173            wallet.extend(pre_genesis_wallet);
174        }
175        if let Some((alias, validator_wallet)) = validator {
176            let tendermint_pk = validator_wallet.tendermint_node_key.ref_to();
177            let address = self
178                .transactions
179                .find_validator(&tendermint_pk)
180                .map(|tx| Address::Established(tx.tx.data.address.raw.clone()))
181                .expect("Validator alias not found in genesis transactions.");
182            wallet.extend_from_pre_genesis_validator(
183                address.clone(),
184                alias.clone(),
185                validator_wallet,
186            );
187        }
188
189        // Add some internal addresses to the wallet
190        for int_add in &[
191            InternalAddress::PoS,
192            InternalAddress::Masp,
193            InternalAddress::Ibc,
194            InternalAddress::EthBridge,
195            InternalAddress::EthBridgePool,
196            InternalAddress::Governance,
197            InternalAddress::Pgf,
198        ] {
199            wallet.insert_address(
200                int_add.to_string().to_lowercase(),
201                Address::Internal(int_add.clone()),
202                false,
203            );
204        }
205
206        wallet
207    }
208
209    /// Derive Namada configuration from genesis
210    pub fn derive_config(
211        &self,
212        base_dir: &Path,
213        node_mode: TendermintMode,
214        tendermint_pk: Option<&common::PublicKey>,
215        allow_duplicate_ip: bool,
216        add_persistent_peers: bool,
217    ) -> Config {
218        if node_mode != TendermintMode::Validator && tendermint_pk.is_some() {
219            println!(
220                "Warning: Validator alias used to derive config, but node \
221                 mode is not validator, it is {node_mode:?}!"
222            );
223        }
224        let mut config =
225            Config::new(base_dir, self.metadata.chain_id.clone(), node_mode);
226
227        // Derive persistent peers from genesis
228        let persistent_peers =
229            self.derive_persistent_peers(add_persistent_peers);
230        // If `tendermint_pk` is given, find its net_address
231        let validator_net_and_tm_address =
232            if let Some(tendermint_pk) = tendermint_pk {
233                self.transactions.find_validator(tendermint_pk).map(
234                    |validator_tx| {
235                        (
236                            validator_tx.tx.data.net_address,
237                            validator_tx.derive_tendermint_address(),
238                        )
239                    },
240                )
241            } else {
242                None
243            };
244        // Check if the validators are localhost to automatically turn off
245        // Tendermint P2P address book strict mode to allow it
246        let is_localhost = persistent_peers.iter().all(|peer| match peer {
247            TendermintAddress::Tcp {
248                peer_id: _,
249                host,
250                port: _,
251            } => matches!(host.as_str(), "127.0.0.1" | "localhost"),
252            TendermintAddress::Unix { path: _ } => false,
253        });
254
255        // Configure the ledger
256        config.ledger.genesis_time = self.metadata.genesis_time.clone();
257
258        // Add a ledger P2P persistent peers
259        config.ledger.cometbft.p2p.persistent_peers = persistent_peers;
260        config.ledger.cometbft.consensus.timeout_commit =
261            self.metadata.consensus_timeout_commit.into();
262        config.ledger.cometbft.p2p.allow_duplicate_ip = allow_duplicate_ip;
263        config.ledger.cometbft.p2p.addr_book_strict = !is_localhost;
264
265        if let Some((net_address, tm_address)) = validator_net_and_tm_address {
266            // Take out address of self from the P2P persistent peers
267            config.ledger.cometbft.p2p.persistent_peers = config.ledger.cometbft.p2p.persistent_peers.iter()
268                        .filter_map(|peer|
269                            // we do not add the validator in its own persistent peer list
270                            if peer != &tm_address  {
271                                Some(peer.to_owned())
272                            } else {
273                                None
274                            })
275                        .collect();
276
277            let first_port = net_address.port();
278            if !is_localhost {
279                set_ip(&mut config.ledger.cometbft.p2p.laddr, "0.0.0.0");
280            }
281            set_port(&mut config.ledger.cometbft.p2p.laddr, first_port);
282            if !is_localhost {
283                set_ip(&mut config.ledger.cometbft.rpc.laddr, "0.0.0.0");
284            }
285            set_port(
286                &mut config.ledger.cometbft.rpc.laddr,
287                first_port.checked_add(1).expect("Port must not overflow"),
288            );
289            set_port(
290                &mut config.ledger.cometbft.proxy_app,
291                first_port.checked_add(2).expect("Port must not overflow"),
292            );
293
294            // Validator node should turned off peer exchange reactor
295            config.ledger.cometbft.p2p.pex = false;
296        }
297
298        config
299    }
300
301    /// Derive persistent peers from genesis validators
302    fn derive_persistent_peers(
303        &self,
304        add_persistent_peers: bool,
305    ) -> Vec<TendermintAddress> {
306        add_persistent_peers.then(|| {
307            self.transactions
308                .validator_account
309                .as_ref()
310                .map(|txs| {
311                    txs.iter()
312                        .map(FinalizedValidatorAccountTx::derive_tendermint_address)
313                        .collect()
314                })
315                .unwrap_or_default()
316        })
317        .unwrap_or_default()
318    }
319
320    /// Get the chain parameters set in genesis
321    pub fn get_chain_parameters(
322        &self,
323        wasm_dir: impl AsRef<Path>,
324    ) -> namada_sdk::parameters::Parameters {
325        let templates::ChainParams {
326            min_num_of_blocks,
327            max_proposal_bytes,
328            vp_allowlist,
329            tx_allowlist,
330            implicit_vp,
331            epochs_per_year,
332            masp_epoch_multiplier,
333            masp_fee_payment_gas_limit,
334            gas_scale,
335            max_block_gas,
336            minimum_gas_price,
337            max_tx_bytes,
338            is_native_token_transferable,
339            ..
340        } = self.parameters.parameters.clone();
341
342        let implicit_vp_filename = &self
343            .vps
344            .wasm
345            .get(&implicit_vp)
346            .expect("Implicit VP must be present")
347            .filename;
348
349        let implicit_vp_code_hash =
350            wasm_loader::read_wasm(&wasm_dir, implicit_vp_filename)
351                .ok()
352                .map(Hash::sha256);
353
354        let epy_i64 = i64::try_from(epochs_per_year)
355            .expect("`epochs_per_year` must not exceed `i64::MAX`");
356        #[allow(clippy::arithmetic_side_effects)]
357        let min_duration: i64 = 60 * 60 * 24 * 365 / epy_i64;
358        let epoch_duration = EpochDuration {
359            min_num_of_blocks,
360            min_duration: namada_sdk::time::Duration::seconds(min_duration)
361                .into(),
362        };
363        let vp_allowlist = vp_allowlist.unwrap_or_default();
364        let tx_allowlist = tx_allowlist.unwrap_or_default();
365
366        namada_sdk::parameters::Parameters {
367            max_tx_bytes,
368            epoch_duration,
369            vp_allowlist,
370            tx_allowlist,
371            implicit_vp_code_hash,
372            epochs_per_year,
373            masp_epoch_multiplier,
374            max_proposal_bytes,
375            masp_fee_payment_gas_limit,
376            gas_scale,
377            max_block_gas,
378            minimum_gas_price: minimum_gas_price
379                .iter()
380                .map(|(token, amt)| {
381                    (
382                        self.tokens.token.get(token).cloned().unwrap().address,
383                        amt.amount(),
384                    )
385                })
386                .collect(),
387            is_native_token_transferable,
388        }
389    }
390
391    pub fn get_pos_params(
392        &self,
393    ) -> namada_sdk::proof_of_stake::parameters::PosParams {
394        let templates::PosParams {
395            max_validator_slots,
396            pipeline_len,
397            unbonding_len,
398            tm_votes_per_token,
399            block_proposer_reward,
400            block_vote_reward,
401            max_inflation_rate,
402            target_staked_ratio,
403            duplicate_vote_min_slash_rate,
404            light_client_attack_min_slash_rate,
405            cubic_slashing_window_length,
406            validator_stake_threshold,
407            liveness_window_check,
408            liveness_threshold,
409            rewards_gain_p,
410            rewards_gain_d,
411        } = self.parameters.pos_params.clone();
412
413        namada_sdk::proof_of_stake::parameters::PosParams {
414            owned: namada_sdk::proof_of_stake::parameters::OwnedPosParams {
415                max_validator_slots,
416                pipeline_len,
417                unbonding_len,
418                tm_votes_per_token,
419                block_proposer_reward,
420                block_vote_reward,
421                max_inflation_rate,
422                target_staked_ratio,
423                duplicate_vote_min_slash_rate,
424                light_client_attack_min_slash_rate,
425                cubic_slashing_window_length,
426                validator_stake_threshold,
427                liveness_window_check,
428                liveness_threshold,
429                rewards_gain_p,
430                rewards_gain_d,
431            },
432            max_proposal_period: self.parameters.gov_params.max_proposal_period,
433        }
434    }
435
436    pub fn get_gov_params(
437        &self,
438    ) -> namada_sdk::governance::parameters::GovernanceParameters {
439        let templates::GovernanceParams {
440            min_proposal_fund,
441            max_proposal_code_size,
442            min_proposal_voting_period,
443            max_proposal_period,
444            max_proposal_content_size,
445            min_proposal_grace_epochs,
446            max_proposal_latency,
447        } = self.parameters.gov_params.clone();
448        namada_sdk::governance::parameters::GovernanceParameters {
449            min_proposal_fund: Amount::native_whole(min_proposal_fund),
450            max_proposal_code_size,
451            max_proposal_period,
452            max_proposal_content_size,
453            min_proposal_grace_epochs,
454            min_proposal_voting_period,
455            max_proposal_latency,
456        }
457    }
458
459    pub fn get_pgf_params(&self) -> PgfParameters {
460        self.parameters.pgf_params.clone()
461    }
462
463    pub fn get_eth_bridge_params(&self) -> Option<EthereumBridgeParams> {
464        if let Some(templates::EthBridgeParams {
465            eth_start_height,
466            min_confirmations,
467            contracts,
468            erc20_whitelist,
469        }) = self.parameters.eth_bridge_params.clone()
470        {
471            Some(EthereumBridgeParams {
472                eth_start_height,
473                min_confirmations,
474                erc20_whitelist,
475                contracts,
476            })
477        } else {
478            None
479        }
480    }
481
482    pub fn get_ibc_params(&self) -> IbcParameters {
483        let templates::IbcParams {
484            default_mint_limit,
485            default_per_epoch_throughput_limit,
486        } = self.parameters.ibc_params.clone();
487        IbcParameters {
488            default_rate_limits: IbcTokenRateLimits {
489                mint_limit: default_mint_limit,
490                throughput_per_epoch_limit: default_per_epoch_throughput_limit,
491            },
492        }
493    }
494
495    pub fn get_token_address(&self, alias: &Alias) -> Option<&Address> {
496        self.tokens.token.get(alias).map(|token| &token.address)
497    }
498
499    // Validate the chain ID against the genesis contents
500    pub fn is_valid(&self) -> bool {
501        let Self {
502            vps,
503            tokens,
504            balances,
505            parameters,
506            transactions,
507            metadata,
508        } = self.clone();
509        let Metadata {
510            chain_id,
511            genesis_time,
512            consensus_timeout_commit,
513            address_gen,
514        } = metadata.clone();
515
516        let Some(chain_id_prefix) = chain_id.prefix() else {
517            tracing::warn!(
518                "Invalid Chain ID \"{chain_id}\" - unable to find a prefix"
519            );
520            return false;
521        };
522        let metadata = Metadata {
523            chain_id: chain_id_prefix.clone(),
524            genesis_time,
525            consensus_timeout_commit,
526            address_gen,
527        };
528        let to_finalize = ToFinalize {
529            vps,
530            tokens,
531            balances,
532            parameters,
533            transactions,
534            metadata,
535        };
536        let derived_chain_id = derive_chain_id(chain_id_prefix, &to_finalize);
537        let is_valid = derived_chain_id == chain_id;
538        if !is_valid {
539            tracing::warn!(
540                "Invalid chain ID. This indicates that something in the \
541                 genesis files might have been modified."
542            );
543        }
544        is_valid
545    }
546}
547
548/// Create the [`Finalized`] chain configuration. Derives the chain ID from the
549/// genesis bytes and assigns addresses to tokens and transactions that
550/// initialize established accounts.
551///
552/// Invariant: The output must deterministic. For the same input this function
553/// must return the same output.
554pub fn finalize(
555    templates: templates::All<Validated>,
556    chain_id_prefix: ChainIdPrefix,
557    genesis_time: DateTimeUtc,
558    consensus_timeout_commit: crate::tendermint::Timeout,
559) -> Finalized {
560    let genesis_time: Rfc3339String = genesis_time.into();
561    let consensus_timeout_commit: DurationNanos =
562        consensus_timeout_commit.into();
563
564    // Derive seed for address generator
565    let genesis_to_gen_address = GenesisToGenAddresses {
566        templates,
567        metadata: Metadata {
568            chain_id: chain_id_prefix.clone(),
569            genesis_time,
570            consensus_timeout_commit,
571            address_gen: None,
572        },
573    };
574    let genesis_bytes = genesis_to_gen_address.serialize_to_vec();
575    let addr_gen = established_address_gen(&genesis_bytes);
576
577    // Generate addresses
578    let templates::All {
579        vps,
580        tokens,
581        balances,
582        parameters,
583        transactions,
584    } = genesis_to_gen_address.templates;
585    let tokens = FinalizedTokens::finalize_from(tokens);
586    let transactions = FinalizedTransactions::finalize_from(transactions);
587    let parameters = FinalizedParameters::finalize_from(parameters);
588
589    // Store the last state of the address generator in the metadata
590    let mut metadata = genesis_to_gen_address.metadata;
591    metadata.address_gen = Some(addr_gen);
592
593    // Derive chain ID
594    let to_finalize = ToFinalize {
595        metadata,
596        vps,
597        tokens,
598        balances,
599        parameters,
600        transactions,
601    };
602    let chain_id = derive_chain_id(chain_id_prefix, &to_finalize);
603
604    // Construct the `Finalized` chain
605    let ToFinalize {
606        vps,
607        tokens,
608        balances,
609        parameters,
610        transactions,
611        metadata,
612    } = to_finalize;
613    let Metadata {
614        chain_id: _,
615        genesis_time,
616        consensus_timeout_commit,
617        address_gen,
618    } = metadata;
619    let metadata = Metadata {
620        chain_id,
621        genesis_time,
622        consensus_timeout_commit,
623        address_gen,
624    };
625    Finalized {
626        metadata,
627        vps,
628        tokens,
629        balances,
630        parameters,
631        transactions,
632    }
633}
634
635/// Derive a chain ID from genesis contents
636pub fn derive_chain_id(
637    chain_id_prefix: ChainIdPrefix,
638    to_finalize: &ToFinalize,
639) -> ChainId {
640    let to_finalize_bytes = to_finalize.serialize_to_vec();
641    ChainId::from_genesis(chain_id_prefix, to_finalize_bytes)
642}
643
644/// Chain genesis config to be finalized. This struct is used to derive the
645/// chain ID to construct a [`Finalized`] chain genesis config.
646#[derive(
647    Clone, Debug, Deserialize, Serialize, BorshDeserialize, BorshSerialize,
648)]
649pub struct GenesisToGenAddresses {
650    /// Filled-in templates
651    pub templates: templates::All<Validated>,
652    /// Chain metadata
653    pub metadata: Metadata<ChainIdPrefix>,
654}
655
656/// Chain genesis config to be finalized. This struct is used to derive the
657/// chain ID to construct a [`Finalized`] chain genesis config.
658pub type ToFinalize = Chain<ChainIdPrefix>;
659
660/// Chain genesis config.
661pub type Finalized = Chain<ChainId>;
662
663/// Use bytes as a deterministic seed for address generator.
664fn established_address_gen(bytes: &[u8]) -> EstablishedAddressGen {
665    let mut hasher = Sha256::new();
666    hasher.update(bytes);
667    // hex of the first 40 chars of the hash
668    let hash = format!("{:.width$X}", hasher.finalize(), width = 40);
669    EstablishedAddressGen::new(hash)
670}
671
672/// Chain genesis config with generic chain ID.
673#[derive(
674    Clone,
675    Debug,
676    Deserialize,
677    Serialize,
678    BorshDeserialize,
679    BorshSerialize,
680    PartialEq,
681    Eq,
682)]
683pub struct Chain<ID> {
684    pub vps: templates::ValidityPredicates,
685    pub tokens: FinalizedTokens,
686    pub balances: templates::DenominatedBalances,
687    pub parameters: FinalizedParameters,
688    pub transactions: FinalizedTransactions,
689    /// Chain metadata
690    pub metadata: Metadata<ID>,
691}
692
693#[derive(
694    Clone,
695    Debug,
696    Deserialize,
697    Serialize,
698    BorshDeserialize,
699    BorshDeserializer,
700    BorshSerialize,
701    PartialEq,
702    Eq,
703)]
704pub struct FinalizedTokens {
705    pub token: BTreeMap<Alias, FinalizedTokenConfig>,
706}
707
708impl FinalizedTokens {
709    fn finalize_from(tokens: templates::Tokens) -> FinalizedTokens {
710        let templates::Tokens { token } = tokens;
711        let token = token
712            .into_iter()
713            .map(|(key, config)| {
714                let address = Address::Established(
715                    (&key, &config).derive_established_address(),
716                );
717                (key, FinalizedTokenConfig { address, config })
718            })
719            .collect();
720        FinalizedTokens { token }
721    }
722}
723
724#[derive(
725    Clone,
726    Debug,
727    Deserialize,
728    Serialize,
729    BorshDeserialize,
730    BorshDeserializer,
731    BorshSerialize,
732    PartialEq,
733    Eq,
734)]
735pub struct FinalizedTokenConfig {
736    pub address: Address,
737    #[serde(flatten)]
738    pub config: templates::TokenConfig,
739}
740
741impl DeriveEstablishedAddress for (&Alias, &templates::TokenConfig) {
742    const SALT: &'static str = "token-config";
743}
744
745#[derive(
746    Clone,
747    Debug,
748    Default,
749    Deserialize,
750    Serialize,
751    BorshDeserialize,
752    BorshDeserializer,
753    BorshSerialize,
754    PartialEq,
755    Eq,
756)]
757pub struct FinalizedTransactions {
758    pub established_account: Option<Vec<FinalizedEstablishedAccountTx>>,
759    pub validator_account: Option<Vec<FinalizedValidatorAccountTx>>,
760    pub bond: Option<Vec<transactions::BondTx<Validated>>>,
761}
762
763impl FinalizedTransactions {
764    fn finalize_from(
765        transactions: transactions::Transactions<Validated>,
766    ) -> FinalizedTransactions {
767        let transactions::Transactions {
768            established_account,
769            validator_account,
770            bond,
771        } = transactions;
772        let established_account = established_account.map(|txs| {
773            txs.into_iter()
774                .map(|tx| FinalizedEstablishedAccountTx {
775                    address: tx.derive_address(),
776                    tx,
777                })
778                .collect()
779        });
780        let validator_account = validator_account.map(|txs| {
781            txs.into_iter()
782                .map(|tx| FinalizedValidatorAccountTx { tx })
783                .collect()
784        });
785        FinalizedTransactions {
786            established_account,
787            validator_account,
788            bond,
789        }
790    }
791
792    fn find_validator(
793        &self,
794        tendermint_pk: &common::PublicKey,
795    ) -> Option<&FinalizedValidatorAccountTx> {
796        let validator_accounts = self.validator_account.as_ref()?;
797        validator_accounts
798            .iter()
799            .find(|tx| &tx.tx.data.tendermint_node_key.pk.raw == tendermint_pk)
800    }
801}
802
803#[derive(
804    Clone,
805    Debug,
806    Deserialize,
807    Serialize,
808    BorshDeserialize,
809    BorshDeserializer,
810    BorshSerialize,
811    PartialEq,
812    Eq,
813)]
814pub struct FinalizedParameters {
815    pub parameters: templates::ChainParams<Validated>,
816    pub pos_params: templates::PosParams,
817    pub gov_params: templates::GovernanceParams,
818    pub pgf_params: PgfParameters,
819    pub eth_bridge_params: Option<templates::EthBridgeParams>,
820    pub ibc_params: templates::IbcParams,
821}
822
823impl FinalizedParameters {
824    fn finalize_from(
825        templates::Parameters {
826            parameters,
827            pos_params,
828            gov_params,
829            pgf_params,
830            eth_bridge_params,
831            ibc_params,
832        }: templates::Parameters<Validated>,
833    ) -> Self {
834        let finalized_pgf_params = PgfParameters {
835            stewards: pgf_params.stewards,
836            pgf_inflation_rate: pgf_params.pgf_inflation_rate,
837            stewards_inflation_rate: pgf_params.stewards_inflation_rate,
838            maximum_number_of_stewards: pgf_params.maximum_number_of_stewards,
839        };
840        Self {
841            parameters,
842            pos_params,
843            gov_params,
844            pgf_params: finalized_pgf_params,
845            eth_bridge_params,
846            ibc_params,
847        }
848    }
849}
850
851#[derive(
852    Clone,
853    Debug,
854    Deserialize,
855    Serialize,
856    BorshSerialize,
857    BorshDeserialize,
858    BorshDeserializer,
859    PartialEq,
860    Eq,
861)]
862pub struct FinalizedEstablishedAccountTx {
863    pub address: Address,
864    #[serde(flatten)]
865    pub tx: transactions::EstablishedAccountTx,
866}
867
868#[derive(
869    Clone,
870    Debug,
871    Deserialize,
872    Serialize,
873    BorshSerialize,
874    BorshDeserialize,
875    BorshDeserializer,
876    PartialEq,
877    Eq,
878)]
879pub struct FinalizedValidatorAccountTx {
880    #[serde(flatten)]
881    pub tx: transactions::SignedValidatorAccountTx,
882}
883
884impl FinalizedValidatorAccountTx {
885    pub fn derive_tendermint_address(&self) -> TendermintAddress {
886        // Derive the node ID from the node key
887        let node_id: TendermintNodeId =
888            id_from_pk(&self.tx.data.tendermint_node_key.pk.raw);
889
890        // Build the list of persistent peers from the validators' node IDs
891        TendermintAddress::from_str(&format!(
892            "{}@{}",
893            node_id, self.tx.data.net_address,
894        ))
895        .expect("Validator address must be valid")
896    }
897}
898
899/// Chain metadata
900#[derive(
901    Clone,
902    Debug,
903    Deserialize,
904    Serialize,
905    BorshDeserialize,
906    BorshSerialize,
907    PartialEq,
908    Eq,
909)]
910pub struct Metadata<ID> {
911    /// Chain ID in [`Finalized`] or chain ID prefix in
912    /// [`GenesisToGenAddresses`] and [`ToFinalize`].
913    pub chain_id: ID,
914    // Genesis timestamp
915    pub genesis_time: Rfc3339String,
916    /// The Tendermint consensus timeout_commit configuration
917    pub consensus_timeout_commit: DurationNanos,
918    /// This generator should be used to initialize the ledger for the
919    /// next address that will be generated on chain.
920    ///
921    /// The value is expected to always be `None` in [`GenesisToGenAddresses`]
922    /// and `Some` in [`ToFinalize`] and [`Finalized`].
923    pub address_gen: Option<EstablishedAddressGen>,
924}
925
926#[cfg(test)]
927mod test {
928    use std::path::PathBuf;
929
930    use super::*;
931    use crate::time::test_utils::GENESIS_TIME;
932
933    /// Test that the [`finalize`] returns deterministic output with the same
934    /// chain ID for the same input.
935    #[test]
936    fn test_finalize_is_deterministic() {
937        // Load the localnet templates
938        let templates_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
939            .parent()
940            .unwrap()
941            .parent()
942            .unwrap()
943            .join("genesis/localnet");
944        let templates = templates::load_and_validate(&templates_dir).unwrap();
945
946        let chain_id_prefix: ChainIdPrefix =
947            FromStr::from_str("test-prefix").unwrap();
948
949        let genesis_time = DateTimeUtc::from_str(GENESIS_TIME).unwrap();
950
951        let consensus_timeout_commit =
952            crate::tendermint::Timeout::from_str("1s").unwrap();
953
954        let finalized_0 = finalize(
955            templates.clone(),
956            chain_id_prefix.clone(),
957            genesis_time,
958            consensus_timeout_commit,
959        );
960
961        let finalized_1 = finalize(
962            templates,
963            chain_id_prefix,
964            genesis_time,
965            consensus_timeout_commit,
966        );
967
968        pretty_assertions::assert_eq!(finalized_0, finalized_1);
969    }
970}