chore: enforce non-zero redundancy parameter for Blend cover messages (#3364)

This commit is contained in:
Antonio
2026-08-24 05:19:51 +00:00
committed by GitHub
parent 6922565198
commit e83c6d4aa0
13 changed files with 51 additions and 34 deletions
+15 -4
View File
@@ -1,12 +1,17 @@
use std::num::NonZeroU64;
use lb_blend_proofs::quota::Quota;
use lb_utils::math::NonNegativeF64;
use lb_utils::math::PositiveF64;
/// `Q_C`: the messaging allowance of a single core node for one epoch.
///
/// The spec requires the parameters to satisfy `C * ß_c > 0`, which the
/// argument types carry: `message_frequency_per_round` is positive and
/// `num_blend_layers` is non-zero, so `Q_C >= 1` for every epoch.
#[must_use]
pub fn core_quota(
rounds_per_epoch: NonZeroU64,
message_frequency_per_round: NonNegativeF64,
message_frequency_per_round: PositiveF64,
num_blend_layers: NonZeroU64,
membership_size: usize,
) -> Quota {
@@ -18,8 +23,14 @@ pub fn core_quota(
// `Q_c`: Messaging allowance that can be used by a core node during a single
// epoch. We assume `R_c` to be `0` for now, hence `Q_c = ceil(C * (ß_c
// + 0 * ß_c)) / N = ceil(C * ß_c) / N`.
(((expected_number_of_epoch_messages * num_blend_layers.get() as f64) / membership_size as f64)
.ceil() as u64)
let quota_integer = NonZeroU64::try_from(
((expected_number_of_epoch_messages * num_blend_layers.get() as f64)
/ membership_size as f64)
.ceil() as u64,
)
.expect("Core Quota cannot be zero, if `message_frequency_per_round` is greater than zero and `num_blend_layers` is non-zero.");
quota_integer
.get()
.try_into()
.expect("Core Quota must fit within the width the `PoQ` circuit allows.")
}
+4 -4
View File
@@ -142,7 +142,7 @@ mod tests {
use lb_core::sdp::{MinStake, ServiceParameters, ServiceType};
use lb_cryptarchia_engine::EpochConfig;
pub use lb_groth16::ModulusShift;
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use lb_utils::math::{NonNegativeRatio, PositiveF64};
use crate::{
config::{BlendPoWConfig, PoWConfig},
@@ -181,7 +181,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
@@ -243,7 +243,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
@@ -314,7 +314,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
+2 -2
View File
@@ -864,7 +864,7 @@ pub mod tests {
use lb_cryptarchia_engine::EpochConfig;
use lb_groth16::{AdditiveGroup as _, ModulusShift};
use lb_key_management_system_keys::keys::{Ed25519Key, Ed25519PublicKey, ZkKey, ZkSignature};
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use lb_utils::math::{NonNegativeRatio, PositiveF64};
use num_bigint::BigUint;
use rand::{RngCore as _, thread_rng};
@@ -1074,7 +1074,7 @@ pub mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: rewards::blend::RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
+2 -2
View File
@@ -642,7 +642,7 @@ mod tests {
};
use lb_groth16::{AdditiveGroup as _, Fr};
use lb_key_management_system_keys::keys::{Ed25519Key, ZkKey};
use lb_utils::math::NonNegativeF64;
use lb_utils::math::PositiveF64;
use num_bigint::BigUint;
use super::*;
@@ -658,7 +658,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: blend::RewardsParameters {
rounds_per_epoch: NonZeroU64::new(10).unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
+3 -3
View File
@@ -15,7 +15,7 @@ use lb_core::{
mantle::{Utxo, Value},
sdp::{ActivityMetadata, ProviderId, ServiceParameters},
};
use lb_utils::math::NonNegativeF64;
use lb_utils::math::PositiveF64;
use tracing::debug;
use crate::{
@@ -233,7 +233,7 @@ where
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RewardsParameters {
pub rounds_per_epoch: NonZeroU64,
pub message_frequency_per_round: NonNegativeF64,
pub message_frequency_per_round: PositiveF64,
pub num_blend_layers: NonZeroU64,
pub data_replication_factor: u64,
pub minimum_network_size: NonZeroU64,
@@ -327,7 +327,7 @@ mod tests {
) -> RewardsParameters {
RewardsParameters {
rounds_per_epoch: rounds_per_epoch.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: minimum_network_size.try_into().unwrap(),
data_replication_factor: 0,
@@ -2,7 +2,7 @@ use core::{num::NonZeroU64, time::Duration};
use lb_ledger::mantle::sdp::rewards::blend::RewardsParameters;
use lb_libp2p::protocol_name::StreamProtocol;
use lb_utils::math::NonNegativeF64;
use lb_utils::math::{NonNegativeF64, PositiveF64};
use nutype::nutype;
use serde::{Deserialize, Serialize};
@@ -127,7 +127,7 @@ pub struct SchedulerSettings {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CoverTrafficSettings {
/// `F_c`: frequency at which cover messages are generated per round.
pub message_frequency_per_round: NonNegativeF64,
pub message_frequency_per_round: PositiveF64,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
+2 -2
View File
@@ -4,7 +4,7 @@ use lb_core::blend::core_quota;
use lb_key_management_system_service::{backend::preload::KeyId, keys::UnsecuredEd25519Key};
use lb_poq::Quota;
use lb_services_utils::overwatch::{RecoveryData, StorageRecoverySettings};
use lb_utils::math::NonNegativeF64;
use lb_utils::math::PositiveF64;
use serde::{Deserialize, Serialize};
use crate::settings::TimingSettings;
@@ -103,7 +103,7 @@ pub struct SchedulerSettings {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CoverTrafficSettings {
/// `F_c`: frequency at which cover messages are generated per round.
pub message_frequency_per_round: NonNegativeF64,
pub message_frequency_per_round: PositiveF64,
}
#[cfg(test)]
+9 -3
View File
@@ -1,4 +1,4 @@
use core::time::Duration;
use core::{num::NonZeroU64, time::Duration};
use std::collections::VecDeque;
use futures::{StreamExt as _, stream::repeat};
@@ -1854,8 +1854,14 @@ async fn a_transaction_awaiting_a_pow_solution_does_not_stall_the_event_loop() {
0,
);
// No cover traffic, so every message the service sends is one this test put
// in and the count below means what it says.
settings.scheduler.cover.message_frequency_per_round = 0.0.try_into().unwrap();
// in and the count below means what it says. The frequency itself must stay
// positive (`C * ß_c > 0`), so the silence comes from the quota instead:
// `Q_c = ceil(10 rounds * 0.05 * 2 layers / 2 nodes) = ceil(0.5) = 1`, and
// the scheduler floors its cover count at `Q_c / num_blend_layers = 0`.
// Anything in `(0, 0.1]` gives the same quota, so the halfway point keeps
// float drift well clear of either rounding boundary.
settings.num_blend_layers = NonZeroU64::try_from(2).unwrap();
settings.scheduler.cover.message_frequency_per_round = 0.05.try_into().unwrap();
let (inbound_relay, inbound_message_sender) = new_stream();
let (mut blend_message_stream, _blend_message_sender) = new_stream();
@@ -516,7 +516,7 @@ mod pol_tests {
Config as SdpConfig, ServiceRewardsParameters, rewards::blend::RewardsParameters,
},
};
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use lb_utils::math::{NonNegativeRatio, PositiveF64};
use lb_wallet_service::{WalletMsg, WalletServiceSettings};
use overwatch::services::{
ServiceData,
@@ -749,7 +749,7 @@ mod pol_tests {
service_rewards_params: ServiceRewardsParameters {
blend: RewardsParameters {
rounds_per_epoch: NonZero::new(10u64).unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZero::new(3u64).unwrap(),
minimum_network_size: NonZero::new(1u64).unwrap(),
data_replication_factor: 0,
@@ -367,7 +367,7 @@ mod tests {
mantle::sdp::{ServiceRewardsParameters, rewards},
};
use lb_network_service::{NetworkService, backends::NetworkBackend, message::ChainSyncEvent};
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use lb_utils::math::{NonNegativeRatio, PositiveF64};
use overwatch::{
overwatch::OverwatchHandle,
services::{ServiceData, relay::OutboundRelay},
@@ -973,7 +973,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: rewards::blend::RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
+3 -3
View File
@@ -136,7 +136,7 @@ mod tests {
config::{BlendPoWConfig, ModulusShift, PoWConfig},
mantle::sdp::{ServiceRewardsParameters, rewards},
};
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use lb_utils::math::{NonNegativeRatio, PositiveF64};
use super::*;
@@ -177,7 +177,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: rewards::blend::RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
@@ -360,7 +360,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: rewards::blend::RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,
+3 -3
View File
@@ -24,7 +24,7 @@ use lb_node::config::{
network::deployment::Settings as NetworkDeploymentSettings,
time::deployment::Settings as TimeDeploymentSettings,
};
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use lb_utils::math::{NonNegativeRatio, PositiveF64};
use crate::{
release::ProtocolIdentity,
@@ -97,10 +97,10 @@ pub fn e2e_deployment_settings_with_genesis_block(
.expect("Normalization constant cannot be negative."),
scheduler: SchedulerSettings {
cover: CoverTrafficSettings {
message_frequency_per_round: NonNegativeF64::try_from(
message_frequency_per_round: PositiveF64::try_from(
COVER_MESSAGE_FREQUENCY_PER_ROUND,
)
.expect("Message frequency per round cannot be negative."),
.expect("Message frequency per round must be positive."),
},
delayer: MessageDelayerSettings {
maximum_release_delay_in_rounds: NonZeroU64::try_from(
+2 -2
View File
@@ -898,7 +898,7 @@ mod tests {
mantle::sdp::{ServiceRewardsParameters, rewards},
};
use lb_pol::LotteryConstants;
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use lb_utils::math::{NonNegativeRatio, PositiveF64};
use lb_utxotree::UtxoTree;
use num_bigint::BigUint;
use rpds::HashTrieSetSync;
@@ -1882,7 +1882,7 @@ mod tests {
service_rewards_params: ServiceRewardsParameters {
blend: rewards::blend::RewardsParameters {
rounds_per_epoch: epoch_length.try_into().unwrap(),
message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(),
message_frequency_per_round: PositiveF64::try_from(1.0).unwrap(),
num_blend_layers: NonZeroU64::new(3).unwrap(),
minimum_network_size: NonZeroU64::new(1).unwrap(),
data_replication_factor: 0,