diff --git a/testing-framework/configs/src/topology/configs/consensus.rs b/testing-framework/configs/src/topology/configs/consensus.rs index eca32ac..c620ea7 100644 --- a/testing-framework/configs/src/topology/configs/consensus.rs +++ b/testing-framework/configs/src/topology/configs/consensus.rs @@ -12,7 +12,7 @@ use key_management_system_service::keys::{ }; use nomos_core::{ mantle::{ - MantleTx, Note, OpProof, Utxo, + GenesisTx as GenesisTxTrait, MantleTx, Note, OpProof, Utxo, genesis_tx::GenesisTx, ledger::Tx as LedgerTx, ops::{ @@ -38,6 +38,8 @@ pub enum ConsensusConfigError { LedgerConfig { message: String }, #[error("failed to sign genesis declarations: {message}")] DeclarationSignature { message: String }, + #[error("genesis ledger is missing expected utxo note: {note}")] + MissingGenesisUtxo { note: String }, } #[derive(Clone)] @@ -215,11 +217,13 @@ pub fn create_consensus_configs( let mut blend_notes = Vec::new(); let mut sdp_notes = Vec::new(); + let leader_stake = leader_stake_amount(wallet, ids.len()); let utxos = create_utxos_for_leader_and_services( ids, &mut leader_keys, &mut blend_notes, &mut sdp_notes, + leader_stake, ); let mut utxos = append_wallet_utxos(utxos, wallet); let genesis_tx = create_genesis_tx(&mut utxos)?; @@ -241,18 +245,48 @@ pub fn create_consensus_configs( .collect()) } +fn leader_stake_amount(wallet: &WalletConfig, n_participants: usize) -> u64 { + // Minimum leader stake (legacy baseline) so small test wallets still + // have a viable leader in low-fund scenarios. + const MIN_LEADER_STAKE: u64 = 100_000; + + // Leader stake multiplier relative to average wallet allocation per validator. + // Keeps the leader stake competitive when wallet-funded UTXOs dominate total + // stake. + const LEADER_STAKE_MULTIPLIER: u64 = 10; + + let total_wallet_funds: u64 = wallet.accounts.iter().map(|account| account.value).sum(); + if total_wallet_funds == 0 { + return MIN_LEADER_STAKE; + } + + let n = n_participants.max(1) as u64; + + // Scale leader stake to stay competitive with large wallet-funded UTXOs. + // We use LEADER_STAKE_MULTIPLIER × (total_wallet_funds / n) to keep + // block production likely even when wallets dominate total stake. + let scaled = total_wallet_funds + .saturating_mul(LEADER_STAKE_MULTIPLIER) + .saturating_div(n) + .max(1); + + // Floor to preserve the prior baseline leader stake and avoid too-small values. + scaled.max(MIN_LEADER_STAKE) +} + fn create_utxos_for_leader_and_services( ids: &[[u8; 32]], leader_keys: &mut Vec<(ZkPublicKey, UnsecuredZkKey)>, blend_notes: &mut Vec, sdp_notes: &mut Vec, + leader_stake: u64, ) -> Vec { let mut utxos = Vec::new(); // Create notes for leader, Blend and DA declarations. let mut output_index = 0; for &id in ids { - output_index = push_leader_utxo(id, leader_keys, &mut utxos, output_index); + output_index = push_leader_utxo(id, leader_keys, &mut utxos, output_index, leader_stake); output_index = push_service_note(b"bn", id, blend_notes, &mut utxos, output_index); output_index = push_service_note(b"sdp", id, sdp_notes, &mut utxos, output_index); } @@ -276,13 +310,14 @@ fn push_leader_utxo( leader_keys: &mut Vec<(ZkPublicKey, UnsecuredZkKey)>, utxos: &mut Vec, output_index: usize, + leader_stake: u64, ) -> usize { let sk_data = derive_key_material(b"ld", &id); let sk = UnsecuredZkKey::from(BigUint::from_bytes_le(&sk_data)); let pk = sk.to_public_key(); leader_keys.push((pk, sk)); utxos.push(Utxo { - note: Note::new(100_000, pk), + note: Note::new(leader_stake, pk), tx_hash: BigUint::from(0u8).into(), output_index, }); @@ -427,3 +462,25 @@ fn build_genesis_tx( message: err.to_string(), }) } + +pub fn sync_utxos_with_genesis( + utxos: &mut [Utxo], + genesis_tx: &GenesisTx, +) -> Result<(), ConsensusConfigError> { + let ledger_tx = genesis_tx.mantle_tx().ledger_tx.clone(); + let ledger_tx_hash = ledger_tx.hash(); + let outputs = &ledger_tx.outputs; + + for utxo in utxos { + let output_index = outputs + .iter() + .position(|note| note == &utxo.note) + .ok_or_else(|| ConsensusConfigError::MissingGenesisUtxo { + note: format!("{:?}", utxo.note), + })?; + utxo.output_index = output_index; + utxo.tx_hash = ledger_tx_hash; + } + + Ok(()) +} diff --git a/testing-framework/configs/src/topology/configs/mod.rs b/testing-framework/configs/src/topology/configs/mod.rs index 6073bbe..8f09ae7 100644 --- a/testing-framework/configs/src/topology/configs/mod.rs +++ b/testing-framework/configs/src/topology/configs/mod.rs @@ -116,7 +116,7 @@ pub fn create_general_configs_with_blend_core_subset( collect_blend_core_providers(first_consensus, &blend_configs, n_blend_core_nodes)?; let ledger_tx = first_consensus.genesis_tx.mantle_tx().ledger_tx.clone(); let genesis_tx = create_genesis_tx_with_declarations(ledger_tx, providers)?; - apply_consensus_genesis_tx(&mut consensus_configs, &genesis_tx); + apply_consensus_genesis_tx(&mut consensus_configs, &genesis_tx)?; // Set Blend and DA keys in KMS of each node config. let kms_configs = build_kms_configs(&blend_configs); @@ -200,10 +200,13 @@ fn collect_blend_core_providers( fn apply_consensus_genesis_tx( consensus_configs: &mut [GeneralConsensusConfig], genesis_tx: &nomos_core::mantle::genesis_tx::GenesisTx, -) { +) -> Result<(), ConsensusConfigError> { for c in consensus_configs { c.genesis_tx = genesis_tx.clone(); + consensus::sync_utxos_with_genesis(&mut c.utxos, genesis_tx)?; } + + Ok(()) } fn build_kms_configs(blend_configs: &[GeneralBlendConfig]) -> Vec { diff --git a/testing-framework/core/src/topology/config.rs b/testing-framework/core/src/topology/config.rs index 4bc017f..da9b2ac 100644 --- a/testing-framework/core/src/topology/config.rs +++ b/testing-framework/core/src/topology/config.rs @@ -218,7 +218,7 @@ impl TopologyBuilder { let providers = collect_provider_infos(first_consensus, &blend_configs)?; let genesis_tx = create_consensus_genesis_tx(first_consensus, providers)?; - apply_consensus_genesis_tx(&mut consensus_configs, &genesis_tx); + apply_consensus_genesis_tx(&mut consensus_configs, &genesis_tx)?; let kms_configs = create_kms_configs( &blend_configs, @@ -307,10 +307,15 @@ fn create_consensus_genesis_tx( fn apply_consensus_genesis_tx( consensus_configs: &mut [testing_framework_config::topology::configs::consensus::GeneralConsensusConfig], genesis_tx: &nomos_core::mantle::genesis_tx::GenesisTx, -) { +) -> Result<(), TopologyBuildError> { for c in consensus_configs { c.genesis_tx = genesis_tx.clone(); + testing_framework_config::topology::configs::consensus::sync_utxos_with_genesis( + &mut c.utxos, + genesis_tx, + )?; } + Ok(()) } #[allow(clippy::too_many_arguments)] diff --git a/testing-framework/tools/cfgsync_tf/src/config/builder.rs b/testing-framework/tools/cfgsync_tf/src/config/builder.rs index 44a1f51..74638f1 100644 --- a/testing-framework/tools/cfgsync_tf/src/config/builder.rs +++ b/testing-framework/tools/cfgsync_tf/src/config/builder.rs @@ -8,7 +8,10 @@ use testing_framework_config::topology::configs::{ GeneralConfig, api::GeneralApiConfig, base::{BaseConfigError, BaseConfigs, build_base_configs}, - consensus::{ConsensusConfigError, ConsensusParams, create_genesis_tx_with_declarations}, + consensus::{ + ConsensusConfigError, ConsensusParams, create_genesis_tx_with_declarations, + sync_utxos_with_genesis, + }, network::NetworkParams, time::default_time_config, wallet::WalletConfig, @@ -131,6 +134,7 @@ pub fn try_create_node_configs( for c in &mut consensus_configs { c.genesis_tx = genesis_tx.clone(); + sync_utxos_with_genesis(&mut c.utxos, &genesis_tx)?; } let kms_configs = create_kms_configs(&blend_configs);