configs: validate DA subnet ids and handle empty network layouts

This commit is contained in:
andrussal 2025-12-18 14:40:49 +01:00
parent a582c00692
commit 540ef9f9c2
5 changed files with 153 additions and 79 deletions

1
Cargo.lock generated
View File

@ -7215,6 +7215,7 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"subnetworks-assignations", "subnetworks-assignations",
"thiserror 2.0.17",
"time", "time",
"tracing", "tracing",
] ]

View File

@ -41,6 +41,7 @@ num-bigint = { version = "0.4", default-features = false }
rand = { workspace = true } rand = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
subnetworks-assignations = { workspace = true } subnetworks-assignations = { workspace = true }
thiserror = { workspace = true }
time = { version = "0.3", default-features = true } time = { version = "0.3", default-features = true }
tracing = { workspace = true } tracing = { workspace = true }

View File

@ -146,25 +146,8 @@ fn create_genesis_tx(utxos: &[Utxo]) -> GenesisTx {
GenesisTx::from_tx(signed_mantle_tx).expect("Invalid genesis transaction") GenesisTx::from_tx(signed_mantle_tx).expect("Invalid genesis transaction")
} }
#[must_use] fn build_ledger_config(consensus_params: &ConsensusParams) -> nomos_ledger::Config {
pub fn create_consensus_configs( nomos_ledger::Config {
ids: &[[u8; 32]],
consensus_params: &ConsensusParams,
wallet: &WalletConfig,
) -> Vec<GeneralConsensusConfig> {
let mut leader_keys = Vec::new();
let mut blend_notes = Vec::new();
let mut da_notes = Vec::new();
let utxos = create_utxos_for_leader_and_services(
ids,
&mut leader_keys,
&mut blend_notes,
&mut da_notes,
);
let utxos = append_wallet_utxos(utxos, wallet);
let genesis_tx = create_genesis_tx(&utxos);
let ledger_config = nomos_ledger::Config {
epoch_config: EpochConfig { epoch_config: EpochConfig {
epoch_stake_distribution_stabilization: NonZero::new(3).unwrap(), epoch_stake_distribution_stabilization: NonZero::new(3).unwrap(),
epoch_period_nonce_buffer: NonZero::new(3).unwrap(), epoch_period_nonce_buffer: NonZero::new(3).unwrap(),
@ -213,7 +196,28 @@ pub fn create_consensus_configs(
}, },
}, },
}, },
}; }
}
#[must_use]
pub fn create_consensus_configs(
ids: &[[u8; 32]],
consensus_params: &ConsensusParams,
wallet: &WalletConfig,
) -> Vec<GeneralConsensusConfig> {
let mut leader_keys = Vec::new();
let mut blend_notes = Vec::new();
let mut da_notes = Vec::new();
let utxos = create_utxos_for_leader_and_services(
ids,
&mut leader_keys,
&mut blend_notes,
&mut da_notes,
);
let utxos = append_wallet_utxos(utxos, wallet);
let genesis_tx = create_genesis_tx(&utxos);
let ledger_config = build_ledger_config(consensus_params);
leader_keys leader_keys
.into_iter() .into_iter()
@ -235,7 +239,22 @@ fn create_utxos_for_leader_and_services(
blend_notes: &mut Vec<ServiceNote>, blend_notes: &mut Vec<ServiceNote>,
da_notes: &mut Vec<ServiceNote>, da_notes: &mut Vec<ServiceNote>,
) -> Vec<Utxo> { ) -> Vec<Utxo> {
let derive_key_material = |prefix: &[u8], id_bytes: &[u8]| -> [u8; 16] { let mut utxos = Vec::new();
// Assume output index which will be set by the ledger tx.
let mut output_index = 0;
// Create notes for leader, Blend and DA declarations.
for &id in ids {
output_index = push_leader_utxo(id, leader_keys, &mut utxos, output_index);
output_index = push_service_note(b"da", id, da_notes, &mut utxos, output_index);
output_index = push_service_note(b"bn", id, blend_notes, &mut utxos, output_index);
}
utxos
}
fn derive_key_material(prefix: &[u8], id_bytes: &[u8; 32]) -> [u8; 16] {
let mut sk_data = [0; 16]; let mut sk_data = [0; 16];
let prefix_len = prefix.len(); let prefix_len = prefix.len();
@ -244,62 +263,49 @@ fn create_utxos_for_leader_and_services(
sk_data[prefix_len..].copy_from_slice(&id_bytes[..remaining_len]); sk_data[prefix_len..].copy_from_slice(&id_bytes[..remaining_len]);
sk_data sk_data
}; }
let mut utxos = Vec::new(); fn push_leader_utxo(
id: [u8; 32],
// Assume output index which will be set by the ledger tx. leader_keys: &mut Vec<(ZkPublicKey, UnsecuredZkKey)>,
let mut output_index = 0; utxos: &mut Vec<Utxo>,
output_index: usize,
// Create notes for leader, Blend and DA declarations. ) -> usize {
for &id in ids { let sk_data = derive_key_material(b"ld", &id);
let sk_leader_data = derive_key_material(b"ld", &id); let sk = UnsecuredZkKey::from(BigUint::from_bytes_le(&sk_data));
let sk_leader = UnsecuredZkKey::from(BigUint::from_bytes_le(&sk_leader_data)); let pk = sk.to_public_key();
let pk_leader = sk_leader.to_public_key(); leader_keys.push((pk, sk));
leader_keys.push((pk_leader, sk_leader));
utxos.push(Utxo { utxos.push(Utxo {
note: Note::new(1_000, pk_leader), note: Note::new(1_000, pk),
tx_hash: BigUint::from(0u8).into(), tx_hash: BigUint::from(0u8).into(),
output_index: 0, output_index: 0,
}); });
output_index += 1; output_index + 1
}
let sk_da_data = derive_key_material(b"da", &id); fn push_service_note(
let sk_da = ZkKey::from(BigUint::from_bytes_le(&sk_da_data)); prefix: &[u8],
let pk_da = sk_da.to_public_key(); id: [u8; 32],
let note_da = Note::new(1, pk_da); notes: &mut Vec<ServiceNote>,
da_notes.push(ServiceNote { utxos: &mut Vec<Utxo>,
pk: pk_da, output_index: usize,
sk: sk_da, ) -> usize {
note: note_da, let sk_data = derive_key_material(prefix, &id);
let sk = ZkKey::from(BigUint::from_bytes_le(&sk_data));
let pk = sk.to_public_key();
let note = Note::new(1, pk);
notes.push(ServiceNote {
pk,
sk,
note,
output_index, output_index,
}); });
utxos.push(Utxo { utxos.push(Utxo {
note: note_da, note,
tx_hash: BigUint::from(0u8).into(), tx_hash: BigUint::from(0u8).into(),
output_index: 0, output_index: 0,
}); });
output_index += 1; output_index + 1
let sk_blend_data = derive_key_material(b"bn", &id);
let sk_blend = ZkKey::from(BigUint::from_bytes_le(&sk_blend_data));
let pk_blend = sk_blend.to_public_key();
let note_blend = Note::new(1, pk_blend);
blend_notes.push(ServiceNote {
pk: pk_blend,
sk: sk_blend,
note: note_blend,
output_index,
});
utxos.push(Utxo {
note: note_blend,
tx_hash: BigUint::from(0u8).into(),
output_index: 0,
});
output_index += 1;
}
utxos
} }
fn append_wallet_utxos(mut utxos: Vec<Utxo>, wallet: &WalletConfig) -> Vec<Utxo> { fn append_wallet_utxos(mut utxos: Vec<Utxo>, wallet: &WalletConfig) -> Vec<Utxo> {

View File

@ -18,6 +18,7 @@ use nomos_node::NomosDaMembership;
use num_bigint::BigUint; use num_bigint::BigUint;
use rand::random; use rand::random;
use subnetworks_assignations::{MembershipCreator as _, MembershipHandler as _}; use subnetworks_assignations::{MembershipCreator as _, MembershipHandler as _};
use thiserror::Error;
use tracing::warn; use tracing::warn;
use crate::secret_key_to_peer_id; use crate::secret_key_to_peer_id;
@ -168,9 +169,43 @@ pub fn create_da_configs(
da_params: &DaParams, da_params: &DaParams,
ports: &[u16], ports: &[u16],
) -> Vec<GeneralDaConfig> { ) -> Vec<GeneralDaConfig> {
try_create_da_configs(ids, da_params, ports).expect("failed to build DA configs")
}
#[derive(Debug, Error)]
pub enum DaConfigError {
#[error("DA ports length mismatch (ids={ids}, ports={ports})")]
PortsLenMismatch { ids: usize, ports: usize },
#[error(
"DA subnetwork size too large for u16 subnetwork ids (effective_subnetwork_size={effective_subnetwork_size}, max={max})"
)]
SubnetworkTooLarge {
effective_subnetwork_size: usize,
max: usize,
},
}
pub fn try_create_da_configs(
ids: &[[u8; 32]],
da_params: &DaParams,
ports: &[u16],
) -> Result<Vec<GeneralDaConfig>, DaConfigError> {
// Let the subnetwork size track the participant count so tiny local topologies // Let the subnetwork size track the participant count so tiny local topologies
// can form a membership. // can form a membership.
let effective_subnetwork_size = da_params.subnetwork_size.max(ids.len().max(1)); let effective_subnetwork_size = da_params.subnetwork_size.max(ids.len().max(1));
let max_subnetworks = u16::MAX as usize + 1;
if effective_subnetwork_size > max_subnetworks {
return Err(DaConfigError::SubnetworkTooLarge {
effective_subnetwork_size,
max: max_subnetworks,
});
}
if ports.len() < ids.len() {
return Err(DaConfigError::PortsLenMismatch {
ids: ids.len(),
ports: ports.len(),
});
}
let mut node_keys = vec![]; let mut node_keys = vec![];
let mut peer_ids = vec![]; let mut peer_ids = vec![];
let mut listening_addresses = vec![]; let mut listening_addresses = vec![];
@ -199,7 +234,7 @@ pub fn create_da_configs(
let mut assignations: HashMap<u16, HashSet<PeerId>> = HashMap::new(); let mut assignations: HashMap<u16, HashSet<PeerId>> = HashMap::new();
if peer_ids.is_empty() { if peer_ids.is_empty() {
for id in 0..effective_subnetwork_size { for id in 0..effective_subnetwork_size {
assignations.insert(u16::try_from(id).unwrap_or_default(), HashSet::new()); assignations.insert(id as u16, HashSet::new());
} }
} else { } else {
let mut sorted_peers = peer_ids.clone(); let mut sorted_peers = peer_ids.clone();
@ -214,14 +249,15 @@ pub fn create_da_configs(
members.insert(*peer); members.insert(*peer);
} }
} }
assignations.insert(u16::try_from(id).unwrap_or_default(), members); assignations.insert(id as u16, members);
} }
} }
template.init(SessionNumber::default(), assignations) template.init(SessionNumber::default(), assignations)
}; };
ids.iter() Ok(ids
.iter()
.zip(node_keys) .zip(node_keys)
.enumerate() .enumerate()
.map(|(i, (id, node_key))| { .map(|(i, (id, node_key))| {
@ -267,5 +303,31 @@ pub fn create_da_configs(
retry_commitments_limit: da_params.retry_commitments_limit, retry_commitments_limit: da_params.retry_commitments_limit,
} }
}) })
.collect() .collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_create_da_configs_rejects_subnetwork_overflow() {
let ids = vec![[1u8; 32]];
let ports = vec![12345u16];
let mut params = DaParams::default();
params.subnetwork_size = u16::MAX as usize + 2;
let err = try_create_da_configs(&ids, &params, &ports).unwrap_err();
assert!(matches!(err, DaConfigError::SubnetworkTooLarge { .. }));
}
#[test]
fn try_create_da_configs_rejects_port_mismatch() {
let ids = vec![[1u8; 32], [2u8; 32]];
let ports = vec![12345u16];
let params = DaParams::default();
let err = try_create_da_configs(&ids, &params, &ports).unwrap_err();
assert!(matches!(err, DaConfigError::PortsLenMismatch { .. }));
}
} }

View File

@ -94,6 +94,10 @@ fn initial_peers_by_network_layout(
swarm_configs: &[SwarmConfig], swarm_configs: &[SwarmConfig],
network_params: &NetworkParams, network_params: &NetworkParams,
) -> Vec<Vec<Multiaddr>> { ) -> Vec<Vec<Multiaddr>> {
if swarm_configs.is_empty() {
return Vec::new();
}
let mut all_initial_peers = vec![]; let mut all_initial_peers = vec![];
match network_params.libp2p_network_layout { match network_params.libp2p_network_layout {