diff --git a/.github/workflows/end-to-end-with-cucumber.yml b/.github/workflows/end-to-end-with-cucumber.yml index 7ffa0760a..3d20ec857 100644 --- a/.github/workflows/end-to-end-with-cucumber.yml +++ b/.github/workflows/end-to-end-with-cucumber.yml @@ -70,16 +70,12 @@ jobs: with: tool: cargo-nextest - # Build release binary for logos-blockchain-node (so runtimes can be reduced) with required test features + # Build release binary for logos-blockchain-node (so runtimes can be reduced) - name: Build binaries with required features uses: actions-rs/cargo@9e120dd99b0fbad1c065f686657e914e76bd7b72 with: command: build - # Note: Trying without the `high-active-slot-coefficient` feature flag for now, as locally tests seem to - # converge faster without it, especially with release mode binaries. We can re-enable it later if - # needed. args: --locked --release --all - # args: --locked --all --features high-active-slot-coefficient env: CARGO_INCREMENTAL: 0 # Disable incremental compilation diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index c21d58574..9c86277ef 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -77,7 +77,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build node binary - run: cargo build -p logos-blockchain-node --features high-active-slot-coefficient --release --target ${{ matrix.platform.target_triple }} + run: cargo build -p logos-blockchain-node --release --target ${{ matrix.platform.target_triple }} - name: Tar node binary run: tar -czf logos-blockchain-node-${{ matrix.platform.os }}-${{ matrix.platform.arch }}-${{ needs.configure-version.outputs.version }}.tar.gz -C target/${{ matrix.platform.target_triple }}/release logos-blockchain-node @@ -230,4 +230,4 @@ jobs: upload_url: ${{ env.UPLOAD_URL }} asset_path: ${{ env.DOWNLOAD_DIRECTORY }}/${{ env.LB_CIRCUIT_NAME }} asset_name: ${{ env.LB_CIRCUIT_NAME }} - asset_content_type: application/gzip \ No newline at end of file + asset_content_type: application/gzip diff --git a/Cargo.lock b/Cargo.lock index ebd1a2d71..d275c769f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,40 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" +[[package]] +name = "astro-float" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96034cc871c05bb65ad7fb77e6a8bebf45d8b055ed0311769e2f83a1d373c1ec" +dependencies = [ + "astro-float-macro", + "astro-float-num", +] + +[[package]] +name = "astro-float-macro" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cfe0f6df5a74fb25b9e713470ad77e091f35f798730673c8772f26ed438963" +dependencies = [ + "astro-float-num", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "astro-float-num" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86887daca11d02e0b04f37a9cb81888aae881397fb48ff66494e356aea97554a" +dependencies = [ + "itertools 0.10.5", + "lazy_static", + "rand 0.8.5", + "serde", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -4092,7 +4126,9 @@ dependencies = [ "logos-blockchain-core 0.1.0", "logos-blockchain-groth16 0.1.0", "logos-blockchain-key-management-system-keys 0.1.0", + "logos-blockchain-pol 0.1.0", "logos-blockchain-poq 0.1.0", + "logos-blockchain-utils 0.1.0", "num-bigint", "serde", "thiserror 1.0.69", @@ -4647,6 +4683,7 @@ name = "logos-blockchain-cryptarchia-engine" version = "0.1.0" dependencies = [ "cfg_eval", + "logos-blockchain-pol 0.1.0", "logos-blockchain-utils 0.1.0", "serde", "serde_with", @@ -5284,9 +5321,11 @@ dependencies = [ name = "logos-blockchain-pol" version = "0.1.0" dependencies = [ + "astro-float", "logos-blockchain-circuits-prover 0.1.0", "logos-blockchain-circuits-utils 0.1.0", "logos-blockchain-groth16 0.1.0", + "logos-blockchain-utils 0.1.0", "logos-blockchain-witness-generator 0.1.0", "num-bigint", "num-traits", @@ -5320,6 +5359,7 @@ dependencies = [ "logos-blockchain-circuits-utils 0.1.0", "logos-blockchain-groth16 0.1.0", "logos-blockchain-pol 0.1.0", + "logos-blockchain-utils 0.1.0", "logos-blockchain-witness-generator 0.1.0", "num-bigint", "serde", @@ -5506,7 +5546,6 @@ dependencies = [ "logos-blockchain-libp2p 0.1.0", "logos-blockchain-network-service 0.1.0", "logos-blockchain-node 0.1.0", - "logos-blockchain-pol 0.1.0", "logos-blockchain-tx-service 0.1.0", "logos-blockchain-utils 0.1.0", "logos-blockchain-zone-sdk", diff --git a/README.md b/README.md index e181c11d4..9145ca832 100644 --- a/README.md +++ b/README.md @@ -245,12 +245,6 @@ target/debug/logos-blockchain-node nodes/node/standalone-node-config.yaml Node stores its state inside the `store` directory. If there are any issues when restarting the node, please try removing `store` directory. -If you would like to test a node with a more frequent blocks per slot, please usea `--features high-active-slot-coefficient` feature flag when building: -```bash -cargo build --all-features --all-targets --features high-active-slot-coefficient -``` - - #### Running Logos Blockchain Node with integration test To run the node programatically, one can use `local_testnet_one_node` integration test. diff --git a/blend/message/src/crypto/proofs.rs b/blend/message/src/crypto/proofs.rs index 8a3eaab08..d831fbaf0 100644 --- a/blend/message/src/crypto/proofs.rs +++ b/blend/message/src/crypto/proofs.rs @@ -30,6 +30,7 @@ pub struct PoQVerificationInputsMinusSigningKey { impl Default for PoQVerificationInputsMinusSigningKey { fn default() -> Self { use lb_core::crypto::ZkHash; + use lb_groth16::{Field as _, Fr}; Self { session: 1, @@ -41,7 +42,8 @@ impl Default for PoQVerificationInputsMinusSigningKey { pol_ledger_aged: ZkHash::default(), pol_epoch_nonce: ZkHash::default(), message_quota: 1, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, } } diff --git a/blend/network/src/core/with_core/behaviour/tests/utils.rs b/blend/network/src/core/with_core/behaviour/tests/utils.rs index c359c819f..fd2d90389 100644 --- a/blend/network/src/core/with_core/behaviour/tests/utils.rs +++ b/blend/network/src/core/with_core/behaviour/tests/utils.rs @@ -13,7 +13,7 @@ use lb_blend_message::{ use lb_blend_proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs}; use lb_blend_scheduling::membership::{Membership, Node}; use lb_core::{crypto::ZkHash, sdp::SessionNumber}; -use lb_groth16::Field as _; +use lb_groth16::{Field as _, Fr}; use lb_key_management_system_keys::keys::{Ed25519PublicKey, UnsecuredEd25519Key}; use lb_libp2p::{NetworkBehaviour, SwarmEvent}; use libp2p::{ @@ -182,7 +182,8 @@ pub fn default_poq_verification_inputs_for_session( pol_ledger_aged: ZkHash::ZERO, pol_epoch_nonce: ZkHash::ZERO, message_quota: 0, - total_stake: 0, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, } } diff --git a/blend/proofs/Cargo.toml b/blend/proofs/Cargo.toml index 4fc2bf8ef..82b525e69 100644 --- a/blend/proofs/Cargo.toml +++ b/blend/proofs/Cargo.toml @@ -17,7 +17,9 @@ ed25519-dalek = { workspace = true } generic-array = { default-features = false, version = "1.2" } lb-blend-crypto = { workspace = true } lb-groth16 = { workspace = true } +lb-pol = { workspace = true } lb-poq = { workspace = true } +lb-utils = { workspace = true } num-bigint = { default-features = false, version = "0.4" } serde = { workspace = true } thiserror = "1" diff --git a/blend/proofs/src/quota/fixtures.rs b/blend/proofs/src/quota/fixtures.rs index 6f6ec7512..ef313b39b 100644 --- a/blend/proofs/src/quota/fixtures.rs +++ b/blend/proofs/src/quota/fixtures.rs @@ -1,3 +1,5 @@ +use lb_pol::LotteryConstants; +use lb_utils::math::NonNegativeRatio; use num_bigint::BigUint; use crate::quota::{ @@ -19,6 +21,9 @@ pub fn valid_proof_of_core_quota_inputs( signing_key: Ed25519PublicKey, core_quota: u64, ) -> (PublicInputs, ProofOfCoreQuotaInputs) { + let (lottery_0, lottery_1) = + LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap())) + .compute_lottery_values(1); let public_inputs = PublicInputs { signing_key, core: CoreInputs { @@ -35,7 +40,8 @@ pub fn valid_proof_of_core_quota_inputs( pol_epoch_nonce: BigUint::from(1u64).into(), pol_ledger_aged: BigUint::from(1u64).into(), message_quota: 1, - total_stake: 1, + lottery_0, + lottery_1, }, }; @@ -134,11 +140,17 @@ pub fn valid_proof_of_core_quota_inputs( // Reference values taken from the tests within the `poq` crate. #[must_use] -#[expect(clippy::too_many_lines, reason = "This fixture is simply too long.")] +#[expect( + clippy::too_many_lines, + reason = "Fixture function. Only used for tests." +)] pub fn valid_proof_of_leadership_quota_inputs( signing_key: Ed25519PublicKey, leader_quota: u64, ) -> (PublicInputs, ProofOfLeadershipQuotaInputs) { + let (lottery_0, lottery_1) = + LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap())) + .compute_lottery_values(1); let public_inputs = PublicInputs { signing_key, leader: LeaderInputs { @@ -153,7 +165,8 @@ pub fn valid_proof_of_leadership_quota_inputs( .unwrap() .into(), message_quota: leader_quota, - total_stake: 1, + lottery_0, + lottery_1, }, // Not relevant for leadership quota proofs session: 1, diff --git a/blend/proofs/src/quota/inputs/prove/mod.rs b/blend/proofs/src/quota/inputs/prove/mod.rs index 71c9a85c6..7b7f4c1ab 100644 --- a/blend/proofs/src/quota/inputs/prove/mod.rs +++ b/blend/proofs/src/quota/inputs/prove/mod.rs @@ -30,7 +30,8 @@ impl TryFrom for PoQWitnessInputs { pol_epoch_nonce: value.public.leader.pol_epoch_nonce, pol_ledger_aged: value.public.leader.pol_ledger_aged, session: value.public.session, - total_stake: value.public.leader.total_stake, + lottery_0: value.public.leader.lottery_0, + lottery_1: value.public.leader.lottery_1, }; let common_input_data = PoQCommonInputsData { core_quota: value.public.core.quota, diff --git a/blend/proofs/src/quota/inputs/prove/public.rs b/blend/proofs/src/quota/inputs/prove/public.rs index aed3f7913..b3a889a25 100644 --- a/blend/proofs/src/quota/inputs/prove/public.rs +++ b/blend/proofs/src/quota/inputs/prove/public.rs @@ -1,3 +1,4 @@ +use lb_groth16::Fr; use serde::{Deserialize, Serialize}; use crate::{ZkHash, quota::Ed25519PublicKey}; @@ -41,5 +42,8 @@ pub struct LeaderInputs { #[serde(with = "lb_groth16::serde::serde_fr")] pub pol_epoch_nonce: ZkHash, pub message_quota: u64, - pub total_stake: u64, + #[serde(with = "lb_groth16::serde::serde_fr")] + pub lottery_0: Fr, + #[serde(with = "lb_groth16::serde::serde_fr")] + pub lottery_1: Fr, } diff --git a/blend/proofs/src/quota/inputs/verify.rs b/blend/proofs/src/quota/inputs/verify.rs index ecf03734c..83964342e 100644 --- a/blend/proofs/src/quota/inputs/verify.rs +++ b/blend/proofs/src/quota/inputs/verify.rs @@ -46,7 +46,8 @@ impl From for PoQVerifierInput { pol_epoch_nonce: value.prove_inputs.leader.pol_epoch_nonce, pol_ledger_aged: value.prove_inputs.leader.pol_ledger_aged, session: value.prove_inputs.session, - total_stake: value.prove_inputs.leader.total_stake, + lottery_0: value.prove_inputs.leader.lottery_0, + lottery_1: value.prove_inputs.leader.lottery_1, } .into() } diff --git a/blend/proofs/src/quota/tests.rs b/blend/proofs/src/quota/tests.rs index 00005824d..50204df7a 100644 --- a/blend/proofs/src/quota/tests.rs +++ b/blend/proofs/src/quota/tests.rs @@ -1,6 +1,6 @@ use const_hex::FromHex as _; use lb_blend_crypto::{ZkHash, merkle::MerkleTree}; -use lb_groth16::{Field as _, fr_from_bytes_unchecked}; +use lb_groth16::{Field as _, Fr, fr_from_bytes_unchecked}; use lb_key_management_system_keys::keys::UnsecuredZkKey; use crate::{ @@ -130,7 +130,8 @@ fn generate_inputs() -> PoQInputs { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }; let session = 1; let signing_key = Ed25519PublicKey::from_bytes(&[10; ED25519_PUBLIC_KEY_SIZE]).unwrap(); diff --git a/blend/scheduling/src/message_blend/crypto/core_and_leader/send.rs b/blend/scheduling/src/message_blend/crypto/core_and_leader/send.rs index 68b06aa8c..373225e52 100644 --- a/blend/scheduling/src/message_blend/crypto/core_and_leader/send.rs +++ b/blend/scheduling/src/message_blend/crypto/core_and_leader/send.rs @@ -196,7 +196,7 @@ mod test { public::{CoreInputs, LeaderInputs}, }; use lb_core::crypto::ZkHash; - use lb_groth16::Field as _; + use lb_groth16::{Field as _, Fr}; use lb_key_management_system_keys::keys::{ED25519_PUBLIC_KEY_SIZE, Ed25519PublicKey}; use multiaddr::{Multiaddr, PeerId}; @@ -235,7 +235,8 @@ mod test { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, }, MockCorePoQGenerator, @@ -245,7 +246,8 @@ mod test { pol_ledger_aged: ZkHash::ONE, pol_epoch_nonce: ZkHash::ONE, message_quota: 2, - total_stake: 2, + lottery_0: Fr::ONE, + lottery_1: Fr::ONE, }; processor.rotate_epoch(new_leader_inputs); @@ -282,7 +284,8 @@ mod test { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, }, MockCorePoQGenerator, diff --git a/blend/scheduling/src/message_blend/crypto/core_and_leader/send_and_receive.rs b/blend/scheduling/src/message_blend/crypto/core_and_leader/send_and_receive.rs index d97f4639e..897a86b29 100644 --- a/blend/scheduling/src/message_blend/crypto/core_and_leader/send_and_receive.rs +++ b/blend/scheduling/src/message_blend/crypto/core_and_leader/send_and_receive.rs @@ -140,7 +140,7 @@ mod test { use lb_blend_message::crypto::proofs::PoQVerificationInputsMinusSigningKey; use lb_blend_proofs::quota::inputs::prove::public::{CoreInputs, LeaderInputs}; use lb_core::crypto::ZkHash; - use lb_groth16::Field as _; + use lb_groth16::{Field as _, Fr}; use lb_key_management_system_keys::keys::{ED25519_PUBLIC_KEY_SIZE, Ed25519PublicKey}; use multiaddr::{Multiaddr, PeerId}; @@ -183,7 +183,8 @@ mod test { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, }, MockCorePoQGenerator, @@ -193,7 +194,8 @@ mod test { pol_ledger_aged: ZkHash::ONE, pol_epoch_nonce: ZkHash::ONE, message_quota: 2, - total_stake: 2, + lottery_0: Fr::ONE, + lottery_1: Fr::ONE, }; processor.rotate_epoch(new_leader_inputs); @@ -205,7 +207,8 @@ mod test { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }) ); assert_eq!( diff --git a/blend/scheduling/src/message_blend/crypto/leader/send.rs b/blend/scheduling/src/message_blend/crypto/leader/send.rs index 118a71dfb..3e18e2acf 100644 --- a/blend/scheduling/src/message_blend/crypto/leader/send.rs +++ b/blend/scheduling/src/message_blend/crypto/leader/send.rs @@ -146,7 +146,7 @@ mod test { public::{CoreInputs, LeaderInputs}, }; use lb_core::crypto::ZkHash; - use lb_groth16::Field as _; + use lb_groth16::{Field as _, Fr}; use lb_key_management_system_keys::keys::{ED25519_PUBLIC_KEY_SIZE, Ed25519PublicKey}; use libp2p::{Multiaddr, PeerId}; @@ -177,7 +177,8 @@ mod test { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, }, ProofOfLeadershipQuotaInputs { @@ -194,7 +195,8 @@ mod test { pol_ledger_aged: ZkHash::ONE, pol_epoch_nonce: ZkHash::ONE, message_quota: 2, - total_stake: 2, + lottery_0: Fr::ONE, + lottery_1: Fr::ONE, }; let new_private_inputs = ProofOfLeadershipQuotaInputs { aged_path_and_selectors: [(ZkHash::ONE, true); _], diff --git a/consensus/cryptarchia-engine/Cargo.toml b/consensus/cryptarchia-engine/Cargo.toml index 5ed4b5a78..f81a7a87a 100644 --- a/consensus/cryptarchia-engine/Cargo.toml +++ b/consensus/cryptarchia-engine/Cargo.toml @@ -18,7 +18,8 @@ workspace = true [dependencies] cfg_eval = { optional = true, version = "0.1" } -lb-utils = { features = ["serde", "time"], workspace = true } +lb-pol = { workspace = true } +lb-utils = { workspace = true } serde = { optional = true, workspace = true } serde_with = { features = ["macros"], optional = true, workspace = true } thiserror = "1" @@ -27,5 +28,5 @@ tokio = { default-features = false, features = ["time"], optional = true, v tracing = { workspace = true } [features] -serde = ["dep:cfg_eval", "dep:serde", "dep:serde_with"] +serde = ["dep:cfg_eval", "dep:serde", "dep:serde_with", "lb-utils/serde", "lb-utils/time"] tokio = ["dep:tokio"] diff --git a/consensus/cryptarchia-engine/src/config.rs b/consensus/cryptarchia-engine/src/config.rs index 0cbeb2196..6b50deba6 100644 --- a/consensus/cryptarchia-engine/src/config.rs +++ b/consensus/cryptarchia-engine/src/config.rs @@ -1,37 +1,60 @@ use std::num::NonZero; -use lb_utils::math::NonNegativeF64; +use lb_pol::LotteryConstants; +use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[derive(Clone, Debug, PartialEq)] pub struct Config { - // The k parameter in the Common Prefix property. - // Blocks deeper than k are generally considered stable and forks deeper than that - // trigger the additional fork selection rule, which is however only expected to be used - // during bootstrapping. + /// The `k` parameter in the Common Prefix property. + /// Blocks deeper than k are generally considered stable and forks deeper + /// than that trigger the additional fork selection rule, which is + /// however only expected to be used during bootstrapping. security_param: NonZero, - base_period_length: NonZero, - /// sufficient time measured in slots to measure the density of block - /// production with enough statistical significance. - s_gen: NonZero, + /// `f`, the rate of occupied slots + slot_activation_coeff: NonNegativeRatio, stake_inference_learning_rate: NonNegativeF64, + /// Lottery approximation constants computed from `slot_activation_coeff` + #[cfg_attr(feature = "serde", serde(skip))] + lottery_constants: LotteryConstants, +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Config { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(serde::Deserialize)] + struct RawConfig { + security_param: NonZero, + slot_activation_coeff: NonNegativeRatio, + stake_inference_learning_rate: NonNegativeF64, + } + + let raw = RawConfig::deserialize(deserializer)?; + + Ok(Self { + security_param: raw.security_param, + slot_activation_coeff: raw.slot_activation_coeff, + stake_inference_learning_rate: raw.stake_inference_learning_rate, + lottery_constants: LotteryConstants::new(raw.slot_activation_coeff), + }) + } } impl Config { #[must_use] - pub const fn new( + pub fn new( security_param: NonZero, - active_slot_coefficient: f64, + slot_activation_coeff: NonNegativeRatio, stake_inference_learning_rate: NonNegativeF64, ) -> Self { Self { security_param, - base_period_length: Self::compute_base_period_length( - security_param, - active_slot_coefficient, - ), - s_gen: Self::compute_s_gen(security_param, active_slot_coefficient), + slot_activation_coeff, stake_inference_learning_rate, + lottery_constants: LotteryConstants::new(slot_activation_coeff), } } @@ -41,28 +64,22 @@ impl Config { } #[must_use] - const fn compute_base_period_length( - security_param: NonZero, - active_slot_coefficient: f64, - ) -> NonZero { - NonZero::new(((security_param.get() as f64) / active_slot_coefficient).floor() as u64) - .expect("base_period_length with proper configuration should never be zero") + pub const fn slot_activation_coeff(&self) -> NonNegativeRatio { + self.slot_activation_coeff } #[must_use] - const fn compute_s_gen( - security_param: NonZero, - active_slot_coefficient: f64, - ) -> NonZero { - NonZero::new( - ((security_param.get() as f64) / (4.0 * active_slot_coefficient)).floor() as u64, - ) - .expect("s_gen with proper configuration should never be zero") + pub const fn lottery_constants(&self) -> &LotteryConstants { + &self.lottery_constants } #[must_use] pub const fn base_period_length(&self) -> NonZero { - self.base_period_length + NonZero::new( + ((self.security_param.get() as f64) / self.slot_activation_coeff.as_f64()).floor() + as u64, + ) + .expect("base_period_length with proper configuration should never be zero") } #[must_use] @@ -70,9 +87,15 @@ impl Config { self.stake_inference_learning_rate.get() } + /// sufficient time measured in slots to measure the density of block + /// production with enough statistical significance. #[must_use] pub const fn s_gen(&self) -> NonZero { - self.s_gen + NonZero::new( + ((self.security_param.get() as f64) / (4.0 * self.slot_activation_coeff.as_f64())) + .floor() as u64, + ) + .expect("s_gen with proper configuration should never be zero") } } @@ -84,7 +107,11 @@ mod tests { #[test] fn test_config() { - let config = Config::new(NonZero::new(10).unwrap(), 0.2, 0.1.try_into().unwrap()); + let config = Config::new( + NonZero::new(10).unwrap(), + NonNegativeRatio::new(1, 5.try_into().unwrap()), + 0.1.try_into().unwrap(), + ); assert_eq!(config.security_param(), NonZero::new(10).unwrap()); assert_eq!(config.base_period_length(), NonZero::new(50).unwrap()); assert_eq!(config.s_gen(), NonZero::new(12).unwrap()); diff --git a/consensus/cryptarchia-engine/src/lib.rs b/consensus/cryptarchia-engine/src/lib.rs index f0817d7df..3cc460fb4 100644 --- a/consensus/cryptarchia-engine/src/lib.rs +++ b/consensus/cryptarchia-engine/src/lib.rs @@ -582,6 +582,10 @@ where let pruned_blocks = new.update_lib(); (new, pruned_blocks) } + + pub const fn config(&self) -> &Config { + &self.config + } } /// The output of applying a new block to [`Cryptarchia`] @@ -699,6 +703,8 @@ pub mod tests { num::NonZero, }; + use lb_utils::math::NonNegativeRatio; + use super::{Cryptarchia, Error, Slot, maxvalid_bg}; use crate::{Config, ReorgedBlocks, State, UpdatedCryptarchia}; @@ -711,7 +717,7 @@ pub mod tests { pub fn config_with(security_param: u32) -> Config { Config::new( NonZero::new(security_param).unwrap(), - 0.1, + NonNegativeRatio::new(1, 10.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ) } diff --git a/core/src/block/mod.rs b/core/src/block/mod.rs index 0aebb30de..be06d7adb 100644 --- a/core/src/block/mod.rs +++ b/core/src/block/mod.rs @@ -220,6 +220,8 @@ mod tests { use lb_groth16::Fr; use lb_key_management_system_keys::keys::UnsecuredZkKey; + use lb_pol::LotteryConstants; + use lb_utils::math::NonNegativeRatio; use lb_utxotree::UtxoTree; use num_bigint::BigUint; @@ -244,12 +246,22 @@ mod tests { let utxo_tree_root = utxo_tree.root(); let utxo_merkle_path = utxo_tree.path(&utxo.id()).expect("note must exist in tree"); + let (lottery_0, lottery_1) = + LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap())) + .compute_lottery_values(1000); + // We grind the nonce here to find a winning PoL let public_inputs = { let mut nonce = 0; while nonce < 1000 { - let inputs = - LeaderPublic::new(utxo_tree_root, utxo_tree_root, Fr::from(nonce), 0, 1000); + let inputs = LeaderPublic::new( + utxo_tree_root, + utxo_tree_root, + Fr::from(nonce), + 0, + lottery_0, + lottery_1, + ); if inputs.check_winning(utxo.note.value, *utxo.id().as_fr(), *leader_sk.as_fr()) { break; @@ -257,7 +269,14 @@ mod tests { nonce += 1; } - LeaderPublic::new(utxo_tree_root, utxo_tree_root, Fr::from(nonce), 0, 1000) + LeaderPublic::new( + utxo_tree_root, + utxo_tree_root, + Fr::from(nonce), + 0, + lottery_0, + lottery_1, + ) }; let signing_key = Ed25519Key::from_bytes(&[0; 32]); diff --git a/core/src/proofs/leader_proof.rs b/core/src/proofs/leader_proof.rs index fff1038a6..b7fee7289 100644 --- a/core/src/proofs/leader_proof.rs +++ b/core/src/proofs/leader_proof.rs @@ -5,7 +5,6 @@ use lb_groth16::{Fr, fr_from_bytes, serde::serde_fr}; use lb_key_management_system_keys::keys::ZkPublicKey; use lb_poseidon2::{Digest as _, Poseidon2Bn254Hasher}; use lb_utxotree::MerklePath; -use num_bigint::BigUint; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -95,7 +94,8 @@ impl LeaderProof for Groth16LeaderProof { public_inputs.epoch_nonce, public_inputs.aged_root, public_inputs.latest_root, - public_inputs.total_stake, + public_inputs.lottery_0, + public_inputs.lottery_1, leader_pk, ), ) @@ -128,7 +128,10 @@ pub struct LeaderPublic { pub slot: u64, #[serde(with = "serde_fr")] pub epoch_nonce: Fr, - pub total_stake: u64, + #[serde(with = "serde_fr")] + pub lottery_0: Fr, + #[serde(with = "serde_fr")] + pub lottery_1: Fr, #[serde(with = "serde_fr")] pub aged_root: Fr, #[serde(with = "serde_fr")] @@ -155,12 +158,14 @@ impl LeaderPublic { latest_root: Fr, epoch_nonce: Fr, slot: u64, - total_stake: u64, + lottery_0: Fr, + lottery_1: Fr, ) -> Self { Self { slot, epoch_nonce, - total_stake, + lottery_0, + lottery_1, aged_root, latest_root, } @@ -168,20 +173,12 @@ impl LeaderPublic { #[must_use] pub fn check_winning(&self, value: u64, note_id: Fr, sk: Fr) -> bool { - let (t0, t1) = self.scaled_phi_approx(); let threshold = - Self::phi_approx(&Fr::from(value), &(Fr::from(t0), Fr::from(t1))).into_bigint(); + Self::phi_approx(&Fr::from(value), &(self.lottery_0, self.lottery_1)).into_bigint(); let ticket = Self::ticket(note_id, sk, self.epoch_nonce, Fr::from(self.slot)).into_bigint(); ticket < threshold } - fn scaled_phi_approx(&self) -> (BigUint, BigUint) { - let t0 = &*lb_pol::T0_CONSTANT / &BigUint::from(self.total_stake); - let total_stake_sq = &BigUint::from(self.total_stake) * &BigUint::from(self.total_stake); - let t1 = &*lb_pol::P - (&*lb_pol::T1_CONSTANT / &total_stake_sq); - (t0, t1) - } - fn phi_approx(stake: &Fr, approx: &(Fr, Fr)) -> Fr { // stake * (t0 + t1 * stake) *stake * (approx.0 + (approx.1 * *stake)) @@ -216,7 +213,8 @@ impl LeaderPrivate { let chain = lb_pol::PolChainInputsData { slot_number: public.slot, epoch_nonce: public.epoch_nonce, - total_stake: public.total_stake, + lottery_0: public.lottery_0, + lottery_1: public.lottery_1, aged_root: public.aged_root, latest_root: public.latest_root, leader_pk, @@ -287,6 +285,8 @@ fn ed25519_pk_to_fr_tuple(pk: &Ed25519PublicKey) -> (Fr, Fr) { mod tests { use std::str::FromStr as _; + use lb_pol::LotteryConstants; + use lb_utils::math::NonNegativeRatio; use rand::RngCore as _; use super::*; @@ -334,14 +334,16 @@ mod tests { ); } - fn rand_inputs() -> (LeaderPublic, Fr, Fr) { + fn rand_inputs(lottery_constants: &LotteryConstants) -> (LeaderPublic, Fr, Fr) { + let (lottery_0, lottery_1) = lottery_constants.compute_lottery_values(1); let mut rng = rand::thread_rng(); let public = LeaderPublic::new( Fr::ZERO, Fr::ZERO, Fr::ZERO, rng.next_u64(), - 1, // total stake + lottery_0, + lottery_1, ); let note = Fr::from(rng.next_u64()); // note value let sk = Fr::from(rng.next_u64()); // secret key @@ -376,9 +378,12 @@ mod tests { #[test] fn test_check_winning() { + let slot_activation_coeff = NonNegativeRatio::new(1, 10.try_into().unwrap()); + let constants = LotteryConstants::new(slot_activation_coeff); + // winning rate of all the stake should be ~ active slot coeff - check_prob(lb_pol::slot_activation_coefficient(), || { - let (public, note_id, sk) = rand_inputs(); + check_prob(slot_activation_coeff.as_f64(), || { + let (public, note_id, sk) = rand_inputs(&constants); public.check_winning(1, note_id, sk) }); } diff --git a/ledger/src/config.rs b/ledger/src/config.rs index ed8fcabfe..91af76ac4 100644 --- a/ledger/src/config.rs +++ b/ledger/src/config.rs @@ -1,6 +1,7 @@ use std::num::{NonZero, NonZeroU64}; use lb_cryptarchia_engine::{Epoch, Slot}; +use lb_pol::LotteryConstants; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, PartialEq)] @@ -11,6 +12,11 @@ pub struct Config { } impl Config { + #[must_use] + pub const fn lottery_constants(&self) -> &LotteryConstants { + self.consensus_config.lottery_constants() + } + #[must_use] pub const fn base_period_length(&self) -> NonZero { self.consensus_config.base_period_length() @@ -58,7 +64,7 @@ mod tests { use lb_core::sdp::{MinStake, ServiceParameters, ServiceType}; use lb_cryptarchia_engine::EpochConfig; - use lb_utils::math::NonNegativeF64; + use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use crate::mantle::sdp::{ServiceRewardsParameters, rewards::blend::RewardsParameters}; @@ -72,7 +78,7 @@ mod tests { }, consensus_config: lb_cryptarchia_engine::Config::new( NonZero::new(5).unwrap(), - 0.5, + NonNegativeRatio::new(1, 2.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ), sdp_config: crate::mantle::sdp::Config { @@ -122,7 +128,7 @@ mod tests { }, consensus_config: lb_cryptarchia_engine::Config::new( NonZero::new(5).unwrap(), - 0.5, + NonNegativeRatio::new(1, 2.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ), sdp_config: crate::mantle::sdp::Config { diff --git a/ledger/src/cryptarchia/mod.rs b/ledger/src/cryptarchia/mod.rs index 9629e971e..1f95e11a0 100644 --- a/ledger/src/cryptarchia/mod.rs +++ b/ledger/src/cryptarchia/mod.rs @@ -12,7 +12,6 @@ use lb_core::{ use lb_cryptarchia_engine::{Epoch, Slot}; use lb_groth16::{Fr, fr_from_bytes}; use lb_key_management_system_keys::keys::ZkPublicKey; -use lb_pol::slot_activation_coefficient; use lb_utxotree::MerklePath; use crate::cryptarchia::{ @@ -27,17 +26,22 @@ use crate::mantle::sdp::locked_notes::LockedNotes; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Eq, PartialEq)] pub struct EpochState { - // The epoch this snapshot is for + /// The epoch this snapshot is for pub epoch: Epoch, - // value of the ledger nonce after 'epoch_period_nonce_buffer' slots from the beginning of the - // epoch + /// value of the ledger nonce after `epoch_period_nonce_buffer` slots from + /// the beginning of the epoch #[cfg_attr(feature = "serde", serde(with = "lb_groth16::serde::serde_fr"))] pub nonce: Fr, - // stake distribution snapshot taken at the beginning of the epoch - // (in practice, this is equivalent to the utxos the are spendable at the beginning of the - // epoch) + /// stake distribution snapshot taken at the beginning of the epoch + /// (in practice, this is equivalent to the utxos the are spendable at the + /// beginning of the epoch) pub utxos: UtxoTree, pub total_stake: Value, + /// Lottery values computed based on `total_stake` + #[cfg_attr(feature = "serde", serde(with = "lb_groth16::serde::serde_fr"))] + pub lottery_0: Fr, + #[cfg_attr(feature = "serde", serde(with = "lb_groth16::serde::serde_fr"))] + pub lottery_1: Fr, } impl EpochState { @@ -60,6 +64,8 @@ impl EpochState { nonce, utxos, total_stake: self.total_stake, + lottery_0: self.lottery_0, + lottery_1: self.lottery_1, } } @@ -78,6 +84,11 @@ impl EpochState { self.total_stake } + #[must_use] + pub const fn lottery_values(&self) -> (Fr, Fr) { + (self.lottery_0, self.lottery_1) + } + #[must_use] pub fn utxo_merkle_root(&self) -> Fr { self.utxos.root() @@ -131,6 +142,9 @@ impl LedgerState { self.epoch_state.total_stake, block_density_inference.current_block_density(), ); + let (lottery_0, lottery_1) = config + .lottery_constants() + .compute_lottery_values(total_stake); let current_epoch = config.epoch(self.slot); let new_epoch = config.epoch(slot); @@ -169,6 +183,8 @@ impl LedgerState { nonce: self.nonce, utxos: self.utxos.clone(), total_stake, + lottery_0, + lottery_1, }; Ok(Self { slot, @@ -193,12 +209,16 @@ impl LedgerState { nonce: self.nonce, utxos: self.utxos.clone(), total_stake, + lottery_0, + lottery_1, }; let next_epoch_state = EpochState { epoch: new_epoch + 1, nonce: self.nonce, utxos: self.utxos.clone(), total_stake, + lottery_0, + lottery_1, }; Ok(Self { slot, @@ -225,7 +245,8 @@ impl LedgerState { self.latest_utxos().root(), self.epoch_state.nonce, slot.into(), - self.epoch_state.total_stake, + self.epoch_state.lottery_0, + self.epoch_state.lottery_1, ); if !proof.verify(&public_inputs) { return Err(LedgerError::InvalidProof); @@ -365,11 +386,17 @@ impl LedgerState { total_stake ); + let (lottery_0, lottery_1) = config + .lottery_constants() + .compute_lottery_values(total_stake); + Some(EpochState { epoch: requested_epoch, nonce: self.nonce, utxos: self.utxos.clone(), total_stake, + lottery_0, + lottery_1, }) } else { // Requested epoch is in the past @@ -404,11 +431,15 @@ impl LedgerState { .utxos() .iter() .map(|(_, (utxo, _))| utxo.note.value) - .sum::(); + .sum::() + .max(1); // TODO: Change total_stake to NonZeroU64: https://github.com/logos-blockchain/logos-blockchain/issues/2166 + let (lottery_0, lottery_1) = config + .lottery_constants() + .compute_lottery_values(total_stake); let slot: Slot = 0.into(); let stake_inference = Arc::new(StakeInference::new( config.consensus_config.stake_inference_learning_rate(), - slot_activation_coefficient(), + config.consensus_config.slot_activation_coeff().as_f64(), config.consensus_config.security_param().get().into(), )); let block_density = BlockDensity::new(stake_inference.period(), slot); @@ -421,12 +452,16 @@ impl LedgerState { nonce, utxos: utxos.clone(), total_stake, + lottery_0, + lottery_1, }, epoch_state: EpochState { epoch: 0.into(), nonce, utxos, total_stake, + lottery_0, + lottery_1, }, block_density, stake_inference, @@ -463,7 +498,7 @@ pub mod tests { use lb_cryptarchia_engine::EpochConfig; use lb_groth16::Field as _; use lb_key_management_system_keys::keys::{Ed25519PublicKey, ZkKey}; - use lb_utils::math::NonNegativeF64; + use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use num_bigint::BigUint; use rand::{RngCore as _, thread_rng}; @@ -579,7 +614,8 @@ pub mod tests { }, ledger_state.epoch_state.nonce, slot.into(), - ledger_state.epoch_state.total_stake, + ledger_state.epoch_state.lottery_0, + ledger_state.epoch_state.lottery_1, ), leader_key: Ed25519PublicKey::from_bytes(&[0u8; 32]).unwrap(), voucher_cm: VoucherCm::default(), @@ -608,7 +644,7 @@ pub mod tests { }, consensus_config: lb_cryptarchia_engine::Config::new( NonZero::new(1).unwrap(), - 0.1, + NonNegativeRatio::new(1, 10.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ), sdp_config: crate::mantle::sdp::Config { @@ -635,13 +671,16 @@ pub mod tests { pub fn genesis_state(utxos: &[Utxo]) -> LedgerState { let config = config(); let total_stake = utxos.iter().map(|u| u.note.value).sum(); + let (lottery_0, lottery_1) = config + .lottery_constants() + .compute_lottery_values(total_stake); let utxos = utxos .iter() .map(|utxo| (utxo.id(), *utxo)) .collect::(); let stake_inference = Arc::new(StakeInference::new( config.consensus_config.stake_inference_learning_rate(), - slot_activation_coefficient(), + config.consensus_config.slot_activation_coeff().as_f64(), config.consensus_config.security_param().get().into(), )); let block_density_inference = BlockDensity::new(stake_inference.period(), 0.into()); @@ -654,12 +693,16 @@ pub mod tests { nonce: Fr::ZERO, utxos: utxos.clone(), total_stake, + lottery_0, + lottery_1, }, epoch_state: EpochState { epoch: 0.into(), nonce: Fr::ZERO, utxos, total_stake, + lottery_0, + lottery_1, }, stake_inference, block_density: block_density_inference, @@ -848,7 +891,8 @@ pub mod tests { latest_root: ledger_state.latest_utxos().root(), epoch_nonce: ledger_state.epoch_state.nonce, slot: slot.into(), - total_stake: ledger_state.epoch_state.total_stake, + lottery_0: ledger_state.epoch_state.lottery_0, + lottery_1: ledger_state.epoch_state.lottery_1, }, leader_key: Ed25519PublicKey::from_bytes(&[0u8; 32]).unwrap(), voucher_cm: VoucherCm::default(), @@ -872,7 +916,8 @@ pub mod tests { latest_root: BigUint::from(1u8).into(), // Invalid latest root epoch_nonce: ledger_state.epoch_state.nonce, slot: slot.into(), - total_stake: ledger_state.epoch_state.total_stake, + lottery_0: ledger_state.epoch_state.lottery_0, + lottery_1: ledger_state.epoch_state.lottery_1, }, leader_key: Ed25519PublicKey::from_bytes(&[0u8; 32]).unwrap(), voucher_cm: VoucherCm::default(), diff --git a/ledger/src/mantle/sdp/mod.rs b/ledger/src/mantle/sdp/mod.rs index 7e3e3b6d2..80a2e30f7 100644 --- a/ledger/src/mantle/sdp/mod.rs +++ b/ledger/src/mantle/sdp/mod.rs @@ -717,6 +717,8 @@ mod tests { nonce: ZkHash::ZERO, utxos: UtxoTree::default(), total_stake: 100, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, } } diff --git a/ledger/src/mantle/sdp/rewards/blend/mod.rs b/ledger/src/mantle/sdp/rewards/blend/mod.rs index 3b04713c2..d988302b0 100644 --- a/ledger/src/mantle/sdp/rewards/blend/mod.rs +++ b/ledger/src/mantle/sdp/rewards/blend/mod.rs @@ -268,7 +268,8 @@ impl RewardsParameters { pol_ledger_aged: epoch_state.utxos.root(), pol_epoch_nonce: epoch_state.nonce, message_quota, - total_stake: epoch_state.total_stake, + lottery_0: epoch_state.lottery_0, + lottery_1: epoch_state.lottery_1, } } } diff --git a/ledger/src/mantle/sdp/rewards/test_utils.rs b/ledger/src/mantle/sdp/rewards/test_utils.rs index 2247072f0..b13a064b7 100644 --- a/ledger/src/mantle/sdp/rewards/test_utils.rs +++ b/ledger/src/mantle/sdp/rewards/test_utils.rs @@ -2,7 +2,7 @@ use lb_core::{ crypto::ZkHash, sdp::{Declaration, DeclarationId, ProviderId, ServiceParameters, ServiceType, SessionNumber}, }; -use lb_groth16::Fr; +use lb_groth16::{Field as _, Fr}; use lb_key_management_system_keys::keys::{Ed25519Key, ZkPublicKey}; use num_bigint::BigUint; @@ -61,5 +61,7 @@ pub fn dummy_epoch_state_with(epoch: u32, nonce: u64) -> EpochState { nonce: ZkHash::from(BigUint::from(nonce)), utxos: UtxoTree::default(), total_stake: 0, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, } } diff --git a/nodes/node/binary/Cargo.toml b/nodes/node/binary/Cargo.toml index 82d9082a2..fb5a5454a 100644 --- a/nodes/node/binary/Cargo.toml +++ b/nodes/node/binary/Cargo.toml @@ -85,10 +85,9 @@ tower-http = { default-features = false, features = ["cors", "limit", "timeout", rand = { workspace = true } [features] -config-gen = ["lb-key-management-system-service/unsafe"] -default = ["config-gen", "tracing"] -high-active-slot-coefficient = ["lb-pol/high-active-slot-coefficient"] -instrumentation = [] -profiling = ["lb-http-api-common/profiling", "lb-tracing-service/profiling"] -testing = ["lb-key-management-system-service/unsafe"] -tracing = [] +config-gen = ["lb-key-management-system-service/unsafe"] +default = ["config-gen", "tracing"] +instrumentation = [] +profiling = ["lb-http-api-common/profiling", "lb-tracing-service/profiling"] +testing = ["lb-key-management-system-service/unsafe"] +tracing = [] diff --git a/nodes/node/binary/src/config/cryptarchia/deployment.rs b/nodes/node/binary/src/config/cryptarchia/deployment.rs index 018f067bf..d53487110 100644 --- a/nodes/node/binary/src/config/cryptarchia/deployment.rs +++ b/nodes/node/binary/src/config/cryptarchia/deployment.rs @@ -7,14 +7,14 @@ use lb_core::{ sdp::{MinStake, ServiceType}, }; use lb_cryptarchia_engine::Config as ConsensusConfig; -use lb_pol::slot_activation_coefficient; -use lb_utils::math::NonNegativeF64; +use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Settings { pub epoch_config: EpochConfig, pub security_param: NonZeroU32, + pub slot_activation_coeff: NonNegativeRatio, pub learning_rate: NonNegativeF64, pub sdp_config: SdpConfig, pub gossipsub_protocol: String, @@ -37,10 +37,10 @@ pub struct EpochConfig { impl Settings { #[must_use] - pub const fn consensus_config(&self) -> ConsensusConfig { + pub fn consensus_config(&self) -> ConsensusConfig { ConsensusConfig::new( self.security_param, - slot_activation_coefficient(), + self.slot_activation_coeff, self.learning_rate, ) } diff --git a/nodes/node/binary/src/config/deployment/devnet.rs b/nodes/node/binary/src/config/deployment/devnet.rs index 9539747d0..fdf675e9a 100644 --- a/nodes/node/binary/src/config/deployment/devnet.rs +++ b/nodes/node/binary/src/config/deployment/devnet.rs @@ -34,6 +34,9 @@ cryptarchia: epoch_period_nonce_buffer: 3 epoch_period_nonce_stabilization: 4 security_param: 20 + slot_activation_coeff: + numerator: 1 + denominator: 10 learning_rate: 0.1 sdp_config: service_params: diff --git a/nodes/node/standalone-deployment-config.yaml b/nodes/node/standalone-deployment-config.yaml index a8529998b..c58b06949 100644 --- a/nodes/node/standalone-deployment-config.yaml +++ b/nodes/node/standalone-deployment-config.yaml @@ -31,6 +31,9 @@ cryptarchia: epoch_period_nonce_buffer: 3 epoch_period_nonce_stabilization: 4 security_param: 20 + slot_activation_coeff: + numerator: 1 + denominator: 10 learning_rate: 0.1 sdp_config: service_params: diff --git a/services/blend/src/core/backends/mod.rs b/services/blend/src/core/backends/mod.rs index 5829c1dc2..cab05c085 100644 --- a/services/blend/src/core/backends/mod.rs +++ b/services/blend/src/core/backends/mod.rs @@ -38,14 +38,15 @@ where { fn default() -> Self { use lb_core::crypto::ZkHash; - use lb_groth16::Field as _; + use lb_groth16::{Field as _, Fr}; Self { epoch: LeaderInputs { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, session: SessionInfo { membership: Membership::new_without_local(&[]), @@ -63,14 +64,15 @@ where impl From> for PublicInfo { fn from(value: Membership) -> Self { use lb_core::crypto::ZkHash; - use lb_groth16::Field as _; + use lb_groth16::{Field as _, Fr}; Self { epoch: LeaderInputs { message_quota: 1, pol_epoch_nonce: ZkHash::ZERO, pol_ledger_aged: ZkHash::ZERO, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, session: SessionInfo { membership: value, diff --git a/services/blend/src/core/mod.rs b/services/blend/src/core/mod.rs index f87017d1a..7ebc1b69f 100644 --- a/services/blend/src/core/mod.rs +++ b/services/blend/src/core/mod.rs @@ -609,7 +609,8 @@ where LeaderInputsMinusQuota { pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }, remaining_clock_stream, ) = async { @@ -637,7 +638,8 @@ where pol_ledger_aged, pol_epoch_nonce, message_quota: blend_config.session_leadership_quota(), - total_stake, + lottery_0, + lottery_1, }, session: SessionInfo { membership: current_membership_info.public.membership.clone(), @@ -1814,13 +1816,15 @@ where EpochEvent::NewEpoch(LeaderInputsMinusQuota { pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }) => { let new_leader_inputs = LeaderInputs { message_quota: settings.session_leadership_quota(), pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }; let new_public_info = PublicInfo { epoch: new_leader_inputs, @@ -1841,13 +1845,15 @@ where EpochEvent::NewEpochAndOldEpochTransitionExpired(LeaderInputsMinusQuota { pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }) => { let new_leader_inputs = LeaderInputs { message_quota: settings.session_leadership_quota(), pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }; let new_public_inputs = PublicInfo { epoch: new_leader_inputs, diff --git a/services/blend/src/core/processor.rs b/services/blend/src/core/processor.rs index 5f110db84..ec6482d36 100644 --- a/services/blend/src/core/processor.rs +++ b/services/blend/src/core/processor.rs @@ -241,6 +241,7 @@ mod tests { scheduling::message_blend::crypto::SessionCryptographicProcessorSettings, }; use lb_core::crypto::ZkHash; + use lb_groth16::Fr; use lb_key_management_system_service::keys::{Ed25519PublicKey, UnsecuredEd25519Key}; use crate::{ @@ -264,7 +265,8 @@ mod tests { pol_ledger_aged: ZkHash::ZERO, pol_epoch_nonce: ZkHash::ZERO, message_quota: 1, - total_stake: 1, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, } } diff --git a/services/blend/src/core/tests/utils.rs b/services/blend/src/core/tests/utils.rs index 38af8fedc..c3ef753df 100644 --- a/services/blend/src/core/tests/utils.rs +++ b/services/blend/src/core/tests/utils.rs @@ -34,7 +34,7 @@ use lb_blend::{ }, }; use lb_core::{crypto::ZkHash, sdp::SessionNumber}; -use lb_groth16::Field as _; +use lb_groth16::{Field as _, Fr}; use lb_key_management_system_service::keys::{Ed25519PublicKey, UnsecuredEd25519Key}; use lb_network_service::{NetworkService, backends::NetworkBackend}; use lb_poq::CorePathAndSelectors; @@ -339,7 +339,8 @@ pub fn new_public_info( pol_ledger_aged: ZkHash::ZERO, pol_epoch_nonce: ZkHash::ZERO, message_quota: settings.session_leadership_quota(), - total_stake: 10, + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, }, } } diff --git a/services/blend/src/edge/mod.rs b/services/blend/src/edge/mod.rs index 1cd347dce..c33ce423b 100644 --- a/services/blend/src/edge/mod.rs +++ b/services/blend/src/edge/mod.rs @@ -363,7 +363,8 @@ where let EpochEvent::NewEpoch(LeaderInputsMinusQuota { pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }) = epoch_handler .tick(slot_tick) .await @@ -376,7 +377,8 @@ where message_quota: settings.session_leadership_quota(), pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }, remaining_clock_stream, ) @@ -573,17 +575,20 @@ where EpochEvent::NewEpoch(LeaderInputsMinusQuota { pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }) | EpochEvent::NewEpochAndOldEpochTransitionExpired(LeaderInputsMinusQuota { pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }) => LeaderInputs { message_quota: settings.session_leadership_quota(), pol_epoch_nonce, pol_ledger_aged, - total_stake, + lottery_0, + lottery_1, }, // We don't handle the epoch transitions in edge node. EpochEvent::OldEpochTransitionPeriodExpired => current_public_inputs.leader, diff --git a/services/blend/src/epoch_info.rs b/services/blend/src/epoch_info.rs index 2863e328a..7a7dcead2 100644 --- a/services/blend/src/epoch_info.rs +++ b/services/blend/src/epoch_info.rs @@ -6,6 +6,7 @@ use lb_blend::proofs::quota::inputs::prove::private::ProofOfLeadershipQuotaInput use lb_chain_service::api::{CryptarchiaServiceApi, CryptarchiaServiceData}; use lb_core::crypto::ZkHash; use lb_cryptarchia_engine::{Epoch, Slot}; +use lb_groth16::Fr; use lb_ledger::EpochState; use lb_time_service::SlotTick; use overwatch::overwatch::OverwatchHandle; @@ -59,22 +60,25 @@ where pub struct LeaderInputsMinusQuota { pub pol_ledger_aged: ZkHash, pub pol_epoch_nonce: ZkHash, - pub total_stake: u64, + pub lottery_0: Fr, + pub lottery_1: Fr, } impl From for LeaderInputsMinusQuota { fn from( EpochState { nonce, - total_stake, utxos, + lottery_0, + lottery_1, .. }: EpochState, ) -> Self { Self { pol_epoch_nonce: nonce, pol_ledger_aged: utxos.root(), - total_stake, + lottery_0, + lottery_1, } } } diff --git a/services/blend/src/test_utils/epoch.rs b/services/blend/src/test_utils/epoch.rs index b8c02c1c1..2b1496a76 100644 --- a/services/blend/src/test_utils/epoch.rs +++ b/services/blend/src/test_utils/epoch.rs @@ -3,7 +3,7 @@ use futures::{Stream, future::ready, stream::once}; use lb_blend::proofs::quota::inputs::prove::private::ProofOfLeadershipQuotaInputs; use lb_chain_service::Slot; use lb_core::crypto::ZkHash; -use lb_groth16::Field as _; +use lb_groth16::{Field as _, Fr}; use lb_ledger::EpochState; use overwatch::overwatch::OverwatchHandle; @@ -17,6 +17,8 @@ pub fn default_epoch_state() -> EpochState { nonce: ZkHash::ZERO, total_stake: 1_000, utxos: UtxoTree::new(), + lottery_0: Fr::ZERO, + lottery_1: Fr::ZERO, } } diff --git a/services/chain/chain-leader/src/leadership.rs b/services/chain/chain-leader/src/leadership.rs index 665d90ec5..9f3b172e2 100644 --- a/services/chain/chain-leader/src/leadership.rs +++ b/services/chain/chain-leader/src/leadership.rs @@ -156,7 +156,8 @@ fn public_inputs_for_slot( latest_tree.root(), epoch_state.nonce, slot.into(), - epoch_state.total_stake(), + epoch_state.lottery_0, + epoch_state.lottery_1, ) } @@ -342,7 +343,7 @@ mod pol_tests { use lb_ledger::mantle::sdp::{ Config as SdpConfig, ServiceRewardsParameters, rewards::blend::RewardsParameters, }; - use lb_utils::math::NonNegativeF64; + use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use lb_wallet_service::{WalletMsg, WalletServiceSettings, api::WalletServiceData}; use overwatch::services::{ ServiceData, @@ -357,12 +358,13 @@ mod pol_tests { /// verified successfully. #[tokio::test] async fn test_build_proof_for() { + let config = test_config(); + // Create secret key and leader let kms = DummyKms; let key_id = KeyId::from("0"); let sk = UnsecuredZkKey::new(Fr::from(0u64)); let pk = sk.to_public_key(); - let config = test_config(); // Create a UTXO let utxo = Tx::new(vec![], vec![Note::new(1000u64, pk)]) @@ -374,11 +376,17 @@ mod pol_tests { let latest_tree = UtxoTree::new().insert(utxo.id(), utxo).0; // Create EpochState + let total_stake = utxo.note.value; + let (lottery_0, lottery_1) = config + .lottery_constants() + .compute_lottery_values(total_stake); let epoch_state = EpochState { epoch: 1.into(), nonce: Fr::from(999u64), utxos: aged_tree.clone(), - total_stake: utxo.note.value, + total_stake, + lottery_0, + lottery_1, }; // Create notifier channel (not used in this test) @@ -408,7 +416,8 @@ mod pol_tests { latest_tree.root(), epoch_state.nonce, winning_slot.into(), - utxo.note.value, + epoch_state.lottery_0, + epoch_state.lottery_1, ); assert!( proof.verify(&public_inputs), @@ -453,7 +462,7 @@ mod pol_tests { }, consensus_config: lb_cryptarchia_engine::Config::new( NonZero::new(5).unwrap(), - 0.05, + NonNegativeRatio::new(1, 10.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ), sdp_config: SdpConfig { diff --git a/services/chain/chain-network/src/bootstrap/ibd.rs b/services/chain/chain-network/src/bootstrap/ibd.rs index 5e77e357e..95ad54c66 100644 --- a/services/chain/chain-network/src/bootstrap/ibd.rs +++ b/services/chain/chain-network/src/bootstrap/ibd.rs @@ -423,7 +423,7 @@ mod tests { mantle::sdp::{ServiceRewardsParameters, rewards}, }; use lb_network_service::{NetworkService, backends::NetworkBackend, message::ChainSyncEvent}; - use lb_utils::math::NonNegativeF64; + use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use overwatch::{ overwatch::OverwatchHandle, services::{ServiceData, relay::OutboundRelay}, @@ -1117,7 +1117,7 @@ mod tests { }, consensus_config: lb_cryptarchia_engine::Config::new( NonZero::new(1).unwrap(), - 0.1, + NonNegativeRatio::new(1, 10.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ), sdp_config: lb_ledger::mantle::sdp::Config { diff --git a/services/chain/chain-service/src/lib.rs b/services/chain/chain-service/src/lib.rs index 184e1b87a..14b072714 100644 --- a/services/chain/chain-service/src/lib.rs +++ b/services/chain/chain-service/src/lib.rs @@ -223,7 +223,7 @@ impl Cryptarchia { Self { consensus: >::from_lib( lib_id, - ledger_config.consensus_config, + ledger_config.consensus_config.clone(), state, lib_slot, lib_length, diff --git a/services/chain/chain-service/src/states.rs b/services/chain/chain-service/src/states.rs index 360798962..fb980eb4c 100644 --- a/services/chain/chain-service/src/states.rs +++ b/services/chain/chain-service/src/states.rs @@ -113,7 +113,7 @@ mod tests { use lb_core::sdp::{MinStake, ServiceParameters, ServiceType}; use lb_cryptarchia_engine::State::Bootstrapping; use lb_ledger::mantle::sdp::{ServiceRewardsParameters, rewards}; - use lb_utils::math::NonNegativeF64; + use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use super::*; @@ -125,7 +125,7 @@ mod tests { let security_param: NonZero = 2.try_into().unwrap(); let cryptarchia_engine_config = lb_cryptarchia_engine::Config::new( security_param, - 0.1, + NonNegativeRatio::new(1, 10.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ); let ledger_config = lb_ledger::Config { @@ -134,7 +134,7 @@ mod tests { epoch_period_nonce_buffer: 1.try_into().unwrap(), epoch_period_nonce_stabilization: 1.try_into().unwrap(), }, - consensus_config: cryptarchia_engine_config, + consensus_config: cryptarchia_engine_config.clone(), sdp_config: lb_ledger::mantle::sdp::Config { service_params: Arc::new( [( @@ -259,7 +259,7 @@ mod tests { let security_param: NonZero = 2.try_into().unwrap(); let cryptarchia_engine_config = lb_cryptarchia_engine::Config::new( security_param, - 0.1, + NonNegativeRatio::new(1, 10.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ); let ledger_config = lb_ledger::Config { @@ -268,7 +268,7 @@ mod tests { epoch_period_nonce_buffer: 1.try_into().unwrap(), epoch_period_nonce_stabilization: 1.try_into().unwrap(), }, - consensus_config: cryptarchia_engine_config, + consensus_config: cryptarchia_engine_config.clone(), sdp_config: lb_ledger::mantle::sdp::Config { service_params: Arc::new( [( diff --git a/services/chain/chain-service/src/sync/block_provider.rs b/services/chain/chain-service/src/sync/block_provider.rs index e41e3eaa2..aac58026c 100644 --- a/services/chain/chain-service/src/sync/block_provider.rs +++ b/services/chain/chain-service/src/sync/block_provider.rs @@ -568,6 +568,7 @@ mod tests { StorageService, backends::rocksdb::{RocksBackend, RocksBackendSettings}, }; + use lb_utils::math::NonNegativeRatio; use lb_utxotree::UtxoTree; use num_bigint::BigUint; use overwatch::{derive_services, overwatch::OverwatchRunner}; @@ -697,8 +698,11 @@ mod tests { impl TestEnv { async fn new() -> Self { let (service, storage_relay) = Self::setup_storage().await; - let cryptarchia = Self::new_cryptarchia(HeaderId::from([0; 32])); - let proof = Self::make_test_proof(); + let cryptarchia = Self::new_cryptarchia( + HeaderId::from([0; 32]), + NonNegativeRatio::new(1, 10.try_into().unwrap()), + ); + let proof = Self::make_test_proof(&cryptarchia); let provider = BlockProvider::new(storage_relay.clone()); Self { @@ -795,7 +799,10 @@ mod tests { for (i, (block, header_id, prev_header, slot)) in blocks.iter().enumerate() { if i == 0 { - self.cryptarchia = Self::new_cryptarchia(*header_id); + self.cryptarchia = Self::new_cryptarchia( + *header_id, + self.cryptarchia.config().slot_activation_coeff(), + ); } if i > 0 { @@ -946,7 +953,9 @@ mod tests { ); } - fn make_test_proof() -> lb_core::proofs::leader_proof::Groth16LeaderProof { + fn make_test_proof( + cryptarchia: &lb_cryptarchia_engine::Cryptarchia, + ) -> lb_core::proofs::leader_proof::Groth16LeaderProof { let leader_sk = UnsecuredZkKey::zero(); let utxo = Utxo { tx_hash: Fr::from(BigUint::from(1u8)).into(), @@ -956,13 +965,23 @@ mod tests { let utxo_tree = UtxoTree::<_, _, ZkHasher>::new().insert(utxo.id(), utxo).0; let utxo_tree_root = utxo_tree.root(); let utxo_merkle_path = utxo_tree.path(&utxo.id()).expect("note must exist in tree"); + let (lottery_0, lottery_1) = cryptarchia + .config() + .lottery_constants() + .compute_lottery_values(1000); // We grind the nonce here to find a winning PoL let public_inputs = { let mut nonce = 0; while nonce < 1000 { - let inputs = - LeaderPublic::new(utxo_tree_root, utxo_tree_root, Fr::from(nonce), 0, 1000); + let inputs = LeaderPublic::new( + utxo_tree_root, + utxo_tree_root, + Fr::from(nonce), + 0, + lottery_0, + lottery_1, + ); if inputs.check_winning(utxo.note.value, *utxo.id().as_fr(), *leader_sk.as_fr()) { @@ -971,7 +990,14 @@ mod tests { nonce += 1; } - LeaderPublic::new(utxo_tree_root, utxo_tree_root, Fr::from(nonce), 0, 1000) + LeaderPublic::new( + utxo_tree_root, + utxo_tree_root, + Fr::from(nonce), + 0, + lottery_0, + lottery_1, + ) }; let private_inputs = LeaderPrivate::new( @@ -990,12 +1016,15 @@ mod tests { .expect("Proof generation should succeed") } - fn new_cryptarchia(lib: HeaderId) -> lb_cryptarchia_engine::Cryptarchia { + fn new_cryptarchia( + lib: HeaderId, + slot_activation_coeff: NonNegativeRatio, + ) -> lb_cryptarchia_engine::Cryptarchia { >::from_lib( lib, Config::new( NonZero::new(1).unwrap(), - 0.1, + slot_activation_coeff, 1f64.try_into().expect("1 > 0"), ), lb_cryptarchia_engine::State::Bootstrapping, diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 823119076..1b823dabb 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -32,7 +32,6 @@ lb-key-management-system-service = { workspace = true } lb-libp2p = { workspace = true } lb-network-service = { features = ["libp2p"], workspace = true } lb-node = { default-features = false, features = ["testing"], workspace = true } -lb-pol = { features = ["high-active-slot-coefficient"], workspace = true } lb-tx-service = { features = ["libp2p", "mock"], workspace = true } lb-utils = { workspace = true } logos-blockchain-zone-sdk = { path = "../zone-sdk" } diff --git a/tests/README.md b/tests/README.md index af3d58e02..c6dbdc928 100644 --- a/tests/README.md +++ b/tests/README.md @@ -18,11 +18,7 @@ Ensure that the following are installed on your system: Integration tests involving nodes run the binaries directly by spawning. Ensure the binaries are built and available in your `target/debug` or `target/release` directory. You can build the project using: -`cargo build --features high-active-slot-coefficient` or `cargo build --release --features high-active-slot-coefficient` - -**Important:** -Make sure to build the node binaries in advance with the `high-active-slot-coefficient` feature enabled, which increases the lottery winning rate and results in more frequent block production. -Integration tests rely on these pre-built binaries and will not build them automatically. +`cargo build` or `cargo build --release` ## Setup and Usage (using Docker) @@ -86,11 +82,6 @@ $env:USE_RELEASE_BINARIES="1"; cargo test --test test_cryptarchia_happy_path two ``` -**Important:** -Make sure to build the node binaries in advance with the `high-active-slot-coefficient` feature enabled, which increases the lottery winning rate and results in more frequent block production. -Integration tests rely on these pre-built binaries and will not build them automatically. - - ### 2. Run Tests with Debug Feature Flag To execute the test suite with the debug feature flag, use the following command: diff --git a/tests/src/common/time.rs b/tests/src/common/time.rs index c964b1325..dff833eea 100644 --- a/tests/src/common/time.rs +++ b/tests/src/common/time.rs @@ -4,7 +4,6 @@ use std::{ }; use lb_node::config::{blend, deployment::DeploymentSettings}; -use lb_pol::slot_activation_coefficient; /// Calculates the maximum time required for `num_blocks` blocks to be proposed /// and fully propagated across the network. @@ -20,7 +19,7 @@ pub fn max_block_propagation_time( let proposal_interval = deployment .time .slot_duration - .div_f64(slot_activation_coefficient()); + .div_f64(deployment.cryptarchia.slot_activation_coeff.as_f64()); let blend_latency = max_blend_latency_per_block(blend_network_size, &deployment.blend); diff --git a/tests/src/tests/cryptarchia/bootstrap.rs b/tests/src/tests/cryptarchia/bootstrap.rs index 327d790b8..15cbb796b 100644 --- a/tests/src/tests/cryptarchia/bootstrap.rs +++ b/tests/src/tests/cryptarchia/bootstrap.rs @@ -5,7 +5,6 @@ use std::{ use futures::stream::{self, StreamExt as _}; use lb_libp2p::PeerId; -use lb_pol::slot_activation_coefficient; use logos_blockchain_tests::{ common::{ sync::{wait_for_validators_mode, wait_for_validators_mode_and_height}, @@ -133,7 +132,7 @@ async fn test_ibd_behind_nodes() { // So, calculate an acceptable height margin for safe comparison. let height_margin = acceptable_height_margin( config.deployment.time.slot_duration, - slot_activation_coefficient(), + config.deployment.cryptarchia.slot_activation_coeff.as_f64(), height_check_timestamp.elapsed(), ); diff --git a/tests/src/topology/configs/deployment.rs b/tests/src/topology/configs/deployment.rs index cc35dc7ef..4a9ae083a 100644 --- a/tests/src/topology/configs/deployment.rs +++ b/tests/src/topology/configs/deployment.rs @@ -20,7 +20,7 @@ use lb_node::config::{ network::deployment::Settings as NetworkDeploymentSettings, time::deployment::Settings as TimeDeploymentSettings, }; -use lb_utils::math::NonNegativeF64; +use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use time::OffsetDateTime; use crate::topology::configs::time::{CONSENSUS_SLOT_TIME_VAR, DEFAULT_SLOT_TIME_IN_SECS}; @@ -98,6 +98,7 @@ pub fn e2e_deployment_settings_with_genesis_tx(genesis_tx: GenesisTx) -> Deploym // nodes have some time to sync before deciding on the // longest chain. security_param: NonZero::new(10).unwrap(), + slot_activation_coeff: NonNegativeRatio::new(1, 10.try_into().unwrap()), epoch_config: EpochConfig { epoch_stake_distribution_stabilization: NonZero::new(3).unwrap(), epoch_period_nonce_buffer: NonZero::new(3).unwrap(), diff --git a/utils/src/math.rs b/utils/src/math.rs index 8f3ce75c9..26526a200 100644 --- a/utils/src/math.rs +++ b/utils/src/math.rs @@ -1,4 +1,5 @@ use core::ops::{Deref, DerefMut}; +use std::num::NonZeroU32; #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] @@ -252,6 +253,28 @@ impl TryFrom for F64Ge1 { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct NonNegativeRatio { + pub numerator: u32, + pub denominator: NonZeroU32, +} + +impl NonNegativeRatio { + #[must_use] + pub const fn new(numerator: u32, denominator: NonZeroU32) -> Self { + Self { + numerator, + denominator, + } + } + + #[must_use] + pub const fn as_f64(&self) -> f64 { + self.numerator as f64 / self.denominator.get() as f64 + } +} + #[cfg(feature = "serde")] mod serde { use serde::Deserialize as _; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 67c2e775f..1f2ffdff4 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -331,7 +331,7 @@ mod tests { }; use lb_cryptarchia_engine::EpochConfig; use lb_ledger::mantle::sdp::{ServiceRewardsParameters, rewards}; - use lb_utils::math::NonNegativeF64; + use lb_utils::math::{NonNegativeF64, NonNegativeRatio}; use num_bigint::BigUint; use super::*; @@ -682,7 +682,7 @@ mod tests { }, consensus_config: lb_cryptarchia_engine::Config::new( NonZero::new(1).unwrap(), - 0.1, + NonNegativeRatio::new(1, 10.try_into().unwrap()), 1f64.try_into().expect("1 > 0"), ), sdp_config: lb_ledger::mantle::sdp::Config { diff --git a/zk/proofs/pol/Cargo.toml b/zk/proofs/pol/Cargo.toml index 44efe94d8..c960a2c1f 100644 --- a/zk/proofs/pol/Cargo.toml +++ b/zk/proofs/pol/Cargo.toml @@ -10,9 +10,11 @@ repository = { workspace = true } version = { workspace = true } [dependencies] +astro-float = "0.9" lb-circuits-prover = { workspace = true } lb-circuits-utils = { workspace = true } lb-groth16 = { features = ["deser"], workspace = true } +lb-utils = { workspace = true } lb-witness-generator = { workspace = true } num-bigint = "0.4" num-traits = "0.2.19" @@ -28,6 +30,5 @@ lb-circuits-utils = { workspace = true } workspace = true [features] -build-verification-key = [] -default = ["build-verification-key"] -high-active-slot-coefficient = [] +build-verification-key = [] +default = ["build-verification-key"] diff --git a/zk/proofs/pol/src/chain_inputs.rs b/zk/proofs/pol/src/chain_inputs.rs index ac425271c..657bbd1d1 100644 --- a/zk/proofs/pol/src/chain_inputs.rs +++ b/zk/proofs/pol/src/chain_inputs.rs @@ -1,8 +1,6 @@ use lb_groth16::{Fr, Groth16Input, Groth16InputDeser}; use serde::{Deserialize, Serialize}; -use crate::lottery::compute_lottery_values; - /// Public inputs of the POL cirmcom circuit with circom specific types. #[derive(Copy, Clone, Debug)] pub struct PolChainInputs { @@ -21,7 +19,8 @@ pub struct PolChainInputs { pub struct PolChainInputsData { pub slot_number: u64, pub epoch_nonce: Fr, - pub total_stake: u64, + pub lottery_0: Fr, + pub lottery_1: Fr, pub aged_root: Fr, pub latest_root: Fr, pub leader_pk: (Fr, Fr), @@ -105,21 +104,18 @@ impl From for PolChainInputs { PolChainInputsData { slot_number, epoch_nonce, - total_stake, + lottery_0, + lottery_1, aged_root, latest_root, leader_pk: (pk1, pk2), }: PolChainInputsData, ) -> Self { - let slot_number = Fr::from(slot_number); - - let (lottery_0, lottery_1) = compute_lottery_values(total_stake); - Self { - slot_number: Groth16Input::new(slot_number), + slot_number: Groth16Input::new(slot_number.into()), epoch_nonce: Groth16Input::new(epoch_nonce), - lottery_0: Groth16Input::new(lottery_0.into()), - lottery_1: Groth16Input::new(lottery_1.into()), + lottery_0: Groth16Input::new(lottery_0), + lottery_1: Groth16Input::new(lottery_1), aged_root: aged_root.into(), latest_root: latest_root.into(), leader_pk1: pk1.into(), diff --git a/zk/proofs/pol/src/inputs.rs b/zk/proofs/pol/src/inputs.rs index 3144c14c4..ed4d681cd 100644 --- a/zk/proofs/pol/src/inputs.rs +++ b/zk/proofs/pol/src/inputs.rs @@ -139,22 +139,23 @@ impl PolVerifierInput { } #[must_use] + #[expect(clippy::too_many_arguments, reason = "all needed")] pub fn new( entropy_contribution: Fr, slot_number: u64, epoch_nonce: Fr, aged_root: Fr, latest_root: Fr, - total_stake: u64, + lottery_0: Fr, + lottery_1: Fr, leader_pk: (Fr, Fr), ) -> Self { - let (lottery_0, lottery_1) = crate::lottery::compute_lottery_values(total_stake); Self { entropy_contribution: entropy_contribution.into(), slot_number: Fr::from(slot_number).into(), epoch_nonce: epoch_nonce.into(), - lottery_0: Fr::from(lottery_0).into(), - lottery_1: Fr::from(lottery_1).into(), + lottery_0: lottery_0.into(), + lottery_1: lottery_1.into(), aged_root: aged_root.into(), latest_root: latest_root.into(), leader_pk1: leader_pk.0.into(), diff --git a/zk/proofs/pol/src/lib.rs b/zk/proofs/pol/src/lib.rs index 8c062c034..4b087bcdf 100644 --- a/zk/proofs/pol/src/lib.rs +++ b/zk/proofs/pol/src/lib.rs @@ -46,9 +46,7 @@ use tracing::error; pub use wallet_inputs::{PolWalletInputs, PolWalletInputsData}; pub use witness::Witness; -pub use crate::lottery::{ - P, T0_CONSTANT, T1_CONSTANT, compute_lottery_values, slot_activation_coefficient, -}; +pub use crate::lottery::{LotteryConstants, P}; use crate::{inputs::PolVerifierInputJson, proving_key::POL_PROVING_KEY_PATH}; pub type PoLProof = CompressedGroth16Proof; @@ -145,6 +143,7 @@ mod tests { use std::str::FromStr as _; use lb_groth16::Fr; + use lb_utils::math::NonNegativeRatio; use num_bigint::BigUint; use super::*; @@ -152,10 +151,15 @@ mod tests { #[expect(clippy::too_many_lines, reason = "For the sake of the test let it be")] #[test] fn test_full_flow() { + let (lottery_0, lottery_1) = + LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap())) + .compute_lottery_values(5000); + let chain_data = PolChainInputsData { slot_number: 135, epoch_nonce: Fr::from(510u64), - total_stake: 5000, + lottery_0, + lottery_1, aged_root: BigUint::from_str( "16524395010779500501330992017298834046369952285388149958144954382059764408785", ) diff --git a/zk/proofs/pol/src/lottery.rs b/zk/proofs/pol/src/lottery.rs index b5bae2fa4..5b179d6b4 100644 --- a/zk/proofs/pol/src/lottery.rs +++ b/zk/proofs/pol/src/lottery.rs @@ -1,22 +1,14 @@ -use std::{ - ops::{Deref as _, Div as _}, - sync::LazyLock, -}; +use std::sync::LazyLock; +use astro_float::{BigFloat, Consts, Radix, RoundingMode, Sign}; +use lb_groth16::Fr; +use lb_utils::math::NonNegativeRatio; use num_bigint::BigUint; use num_traits::{CheckedSub as _, Num as _}; -#[must_use] -pub const fn slot_activation_coefficient() -> f64 { - #[cfg(not(feature = "high-active-slot-coefficient"))] - { - 1f64 / 30f64 - } - #[cfg(feature = "high-active-slot-coefficient")] - 0.1f64 -} - -/// From [Proof of Leadership spec](https://www.notion.so/nomos-tech/Proof-of-Leadership-Specification-21c261aa09df819ba5b6d95d0fe3066d?source=copy_link#256261aa09df800fbc88e5aae5ea7e06) +/// The BN254 scalar field order, +/// +/// The value is defined in [Proof of Leadership spec](https://nomos-tech.notion.site/v1-1-Proof-of-Leadership-Specification-2e9261aa09df80058244c902defc6da2#2e9261aa09df807abe78c815e7a31809) pub static P: LazyLock = LazyLock::new(|| { BigUint::from_str_radix( "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", @@ -25,52 +17,238 @@ pub static P: LazyLock = LazyLock::new(|| { .expect("P constant should parse") }); -/// From [Proof of Leadership spec](https://www.notion.so/nomos-tech/Proof-of-Leadership-Specification-21c261aa09df819ba5b6d95d0fe3066d?source=copy_link#256261aa09df800fbc88e5aae5ea7e06) -pub static T0_CONSTANT: LazyLock = LazyLock::new(|| { - #[cfg(not(feature = "high-active-slot-coefficient"))] - { - BigUint::from_str_radix( - "1a3fb997fd58374772808c13d1c2ddacb5ab3ea77413f86fd6e0d3d978e5438", - 16, +/// Lottery approximation constants used for computing t₀ and t₁. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct LotteryConstants { + pub t0_constant: BigUint, + pub t1_constant: BigUint, +} + +impl LotteryConstants { + const PRECISION: usize = 512; + const ROUNDING_MODE: RoundingMode = RoundingMode::ToEven; + + /// Computes the lottery approximation constants with 512 bits of precision. + /// + /// Formulas: + /// - t₀_constant = floor(p * (-ln(1-f))) + /// - t₁_constant = floor(p * ln²(1-f) / 2) + /// + /// Where `p` is the BN254 scalar field order and `f` is the slot activation + /// coefficient. + /// + /// The calculations are defined in the [Proof of Leadership spec](https://nomos-tech.notion.site/v1-1-Proof-of-Leadership-Specification-2e9261aa09df80058244c902defc6da2#2e9261aa09df807abe78c815e7a31809). + #[expect(clippy::doc_markdown, reason = "math formulas")] + #[must_use] + pub fn new(f: NonNegativeRatio) -> Self { + let mut cc = Consts::new().expect("memory allocation should succeed"); + + let p = Self::p_as_bigfloat(&mut cc); + + // f = f_numerator / f_denominator (exact rational arithmetic) + let f_num = BigFloat::from_u32(f.numerator, Self::PRECISION); + let f_den = BigFloat::from_u32(f.denominator.get(), Self::PRECISION); + let f = f_num.div(&f_den, Self::PRECISION, Self::ROUNDING_MODE); + + // -ln(1-f) + let one = BigFloat::from_u32(1, Self::PRECISION); + let one_minus_f = one.sub(&f, Self::PRECISION, Self::ROUNDING_MODE); + let ln_one_minus_f = one_minus_f.ln(Self::PRECISION, Self::ROUNDING_MODE, &mut cc); + let neg_ln = ln_one_minus_f.neg(); + + // t₀_constant = floor(p * (-ln(1-f))) + let t0_constant = Self::floor_bigfloat( + &p.mul(&neg_ln, Self::PRECISION, Self::ROUNDING_MODE), + &mut cc, + ); + + // ln²(1-f) + let ln_sq = ln_one_minus_f.mul(&ln_one_minus_f, Self::PRECISION, Self::ROUNDING_MODE); + + // p * ln²(1-f) + let t1_float = p.mul(&ln_sq, Self::PRECISION, Self::ROUNDING_MODE); + + // t₁_constant = floor(p * ln²(1-f) / 2) + let two = BigFloat::from_u32(2, Self::PRECISION); + let t1_constant = Self::floor_bigfloat( + &t1_float.div(&two, Self::PRECISION, Self::ROUNDING_MODE), + &mut cc, + ); + + Self { + t0_constant, + t1_constant, + } + } + + /// Convert [`P`] (BN254 field order) to [`BigFloat`]. + fn p_as_bigfloat(cc: &mut Consts) -> BigFloat { + // Use decimal to avoid any hex 'e' vs exponent ambiguity + BigFloat::parse( + P.to_str_radix(10).as_str(), + Radix::Dec, + Self::PRECISION, + Self::ROUNDING_MODE, + cc, ) - .expect("Constant should parse") + } + + /// Floor a non-negative integer-valued [`BigFloat`] to [`BigUint`] + fn floor_bigfloat(val: &BigFloat, cc: &mut Consts) -> BigUint { + // Floor the value first + let floored = val.floor(); + if floored.is_zero() { + return BigUint::ZERO; + } + + // Since astro-float doesn't provide direct BigUint conversion, + // we extract hex digits and reconstruct the BigUint. + // + // - digits: mantissa as hex digits (0-15 each) + // - exp: num of digits before decimal points + let (sign, digits, exp) = floored + .convert_to_radix(Radix::Hex, RoundingMode::None, cc) + .expect("floored BigFloat should convert to hex"); + assert_eq!(sign, Sign::Pos, "floored BigFloat should be positive"); + + // Negative exponent means value < 1, which floors to 0 + let exp = exp.max(0) as usize; + + // Build BigUint by processing each hex digit left-to-right. + // Each hex digit represents 4 bits, so we shift left by 4 + // and add the digit value (or 0 if beyond mantissa length). + let mut result = BigUint::ZERO; + for i in 0..exp { + result <<= 4; + if i < digits.len() { + result += u64::from(digits[i]); + } + } + result } - #[cfg(feature = "high-active-slot-coefficient")] - { - BigUint::from_str_radix( - "5193d04d01fb16d1b9c55677fc83950d1cf88207f4a5756431fde6db9ab1768", - 16, - ) - .expect("Constant should parse") + + /// Computes the lottery values t₀ and t₁ for a given total stake. + pub fn compute_lottery_values(&self, total_stake: u64) -> (Fr, Fr) { + let total_stake = BigUint::from(total_stake); + + let lottery_0 = &self.t0_constant / &total_stake; + let lottery_1 = P + .checked_sub(&(&self.t1_constant / &total_stake.pow(2))) + .expect("(T1 / (S^2)) must be less than P"); + (lottery_0.into(), lottery_1.into()) } -}); +} + +#[cfg(test)] +mod tests { + use super::*; -/// From [Proof of Leadership spec](https://www.notion.so/nomos-tech/Proof-of-Leadership-Specification-21c261aa09df819ba5b6d95d0fe3066d?source=copy_link#256261aa09df800fbc88e5aae5ea7e06) -pub static T1_CONSTANT: LazyLock = LazyLock::new(|| { - #[cfg(not(feature = "high-active-slot-coefficient"))] - { - BigUint::from_str_radix( - "71e790b41991052e30c93934b5612412e7958837bac8b1c524c24d84cc7d0", - 16, - ) - .expect("Constant should parse") + #[test] + fn test_compute_lottery_constants() { + let constants = LotteryConstants::new(NonNegativeRatio::new(1, 30.try_into().unwrap())); + assert_eq!( + constants.t0_constant, + BigUint::from_str_radix( + "1a3fb997fd5838f2a1585ee090a95c88129ab25cc4d2e2d28f1a95f81d85465", + 16, + ) + .unwrap(), + ); + assert_eq!( + constants.t1_constant, + BigUint::from_str_radix( + "71e790b4199113a9a00298d823c5716ddac764a110a45fe3b770bbb3e8a57", + 16, + ) + .unwrap() + ); + + let constants = LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap())); + assert_eq!( + constants.t0_constant, + BigUint::from_str_radix( + "5193d04d01fb16d1b9c55677fc83950d1cf88207f4a5756431fde6db9ab1768", + 16, + ) + .unwrap(), + ); + assert_eq!( + constants.t1_constant, + BigUint::from_str_radix( + "44c2a290c72d4dc7d6e514a4b9683cc6e7c15e64f0f59482ec7d3f5906784a", + 16, + ) + .unwrap() + ); } - #[cfg(feature = "high-active-slot-coefficient")] - { - BigUint::from_str_radix( - "44c2a290c72d4dc7d6e514a4b9683cc6e7c15e64f0f59482ec7d3f5906784a", - 16, - ) - .expect("Constant should parse") + + #[test] + fn test_compute_lottery_values() { + let constants = LotteryConstants::new(NonNegativeRatio::new(1, 30.try_into().unwrap())); + let (lottery_0, lottery_1) = constants.compute_lottery_values(1000); + + assert_eq!( + lottery_0, + BigUint::from_str_radix( + "6b83fe55f9383508b9bbe2d335e8e78d9c133ce0554b4f251b0ca3b6be8c", + 16, + ) + .unwrap() + .into(), + ); + assert_eq!( + lottery_1, + BigUint::from_str_radix( + "30644e7269c19af80558c2b75767747a6fa9f2beb0e87df2e51121184e5e6c17", + 16, + ) + .unwrap() + .into(), + ); } -}); -pub fn compute_lottery_values(total_stake: u64) -> (BigUint, BigUint) { - let total_stake = BigUint::from(total_stake); + #[test] + fn floor_bigfloat() { + let mut cc = Consts::new().unwrap(); - let lottery_0 = T0_CONSTANT.deref().div(total_stake.clone()); - let lottery_1 = P - .checked_sub(&T1_CONSTANT.deref().div(total_stake.pow(2))) - .expect("(T1 / (S^2)) must be less than P"); - (lottery_0, lottery_1) + assert_eq!( + LotteryConstants::floor_bigfloat( + &BigFloat::from_u32(0, LotteryConstants::PRECISION), + &mut cc + ), + BigUint::ZERO + ); + assert_eq!( + LotteryConstants::floor_bigfloat( + &BigFloat::from_u32(42, LotteryConstants::PRECISION), + &mut cc + ), + BigUint::from(42u32) + ); + assert_eq!( + LotteryConstants::floor_bigfloat( + &make_bigfloat("123456789012345678901234567890", &mut cc), + &mut cc + ), + BigUint::parse_bytes(b"123456789012345678901234567890", 10).unwrap() + ); + assert_eq!( + LotteryConstants::floor_bigfloat(&make_bigfloat("99.999", &mut cc), &mut cc), + BigUint::from(99u32) + ); + assert_eq!( + LotteryConstants::floor_bigfloat(&make_bigfloat("0.999", &mut cc), &mut cc), + BigUint::ZERO + ); + } + + fn make_bigfloat(val: &str, cc: &mut Consts) -> BigFloat { + BigFloat::parse( + val, + Radix::Dec, + LotteryConstants::PRECISION, + RoundingMode::None, + cc, + ) + } } diff --git a/zk/proofs/poq/Cargo.toml b/zk/proofs/poq/Cargo.toml index 077cb5f1d..2855d9501 100644 --- a/zk/proofs/poq/Cargo.toml +++ b/zk/proofs/poq/Cargo.toml @@ -21,6 +21,9 @@ serde_json = { version = "1.0" } thiserror = "2.0" tracing = { default-features = false, workspace = true } +[dev-dependencies] +lb-utils = { workspace = true } + [build-dependencies] lb-circuits-utils = { workspace = true } diff --git a/zk/proofs/poq/src/chain_inputs.rs b/zk/proofs/poq/src/chain_inputs.rs index a183a6162..348030139 100644 --- a/zk/proofs/poq/src/chain_inputs.rs +++ b/zk/proofs/poq/src/chain_inputs.rs @@ -1,5 +1,5 @@ use lb_groth16::{Fr, Groth16Input, Groth16InputDeser}; -use lb_pol::{P, compute_lottery_values}; +use lb_pol::P; use num_bigint::BigUint; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,7 +20,8 @@ pub struct PoQChainInputsData { pub core_root: Fr, pub pol_ledger_aged: Fr, pub pol_epoch_nonce: Fr, - pub total_stake: u64, + pub lottery_0: Fr, + pub lottery_1: Fr, } #[derive(Deserialize, Serialize)] @@ -98,23 +99,21 @@ impl TryFrom for PoQChainInputs { core_root, pol_ledger_aged, pol_epoch_nonce, - total_stake, + lottery_0, + lottery_1, }: PoQChainInputsData, ) -> Result { let session = BigUint::from(session); if session > *P { return Err(PoQInputsFromDataError::SessionGreaterThanP); } - - let (lottery_0, lottery_1) = compute_lottery_values(total_stake); - Ok(Self { session: Groth16Input::new(session.into()), core_root: core_root.into(), pol_ledger_aged: pol_ledger_aged.into(), pol_epoch_nonce: pol_epoch_nonce.into(), - pol_t0: Groth16Input::new(lottery_0.into()), - pol_t1: Groth16Input::new(lottery_1.into()), + pol_t0: Groth16Input::new(lottery_0), + pol_t1: Groth16Input::new(lottery_1), }) } } diff --git a/zk/proofs/poq/src/inputs.rs b/zk/proofs/poq/src/inputs.rs index c524ed753..a36be2d9e 100644 --- a/zk/proofs/poq/src/inputs.rs +++ b/zk/proofs/poq/src/inputs.rs @@ -1,5 +1,4 @@ use lb_groth16::{Field as _, Fr, Groth16Input, Groth16InputDeser}; -use lb_pol::compute_lottery_values; use serde::{Deserialize, Serialize}; use crate::{ @@ -106,7 +105,8 @@ pub struct PoQVerifierInputData { pub k_part_one: Fr, pub k_part_two: Fr, pub pol_epoch_nonce: Fr, - pub total_stake: u64, + pub lottery_0: Fr, + pub lottery_1: Fr, pub pol_ledger_aged: Fr, } @@ -164,8 +164,6 @@ impl PoQVerifierInput { impl From for PoQVerifierInput { fn from(value: PoQVerifierInputData) -> Self { - let (lottery_0, lottery_1) = compute_lottery_values(value.total_stake); - Self { core_quota: Groth16Input::new(value.core_quota.into()), core_root: value.core_root.into(), @@ -175,8 +173,8 @@ impl From for PoQVerifierInput { leader_quota: Groth16Input::new(value.leader_quota.into()), pol_epoch_nonce: value.pol_epoch_nonce.into(), pol_ledger_aged: value.pol_ledger_aged.into(), - pol_t0: Groth16Input::new(lottery_0.into()), - pol_t1: Groth16Input::new(lottery_1.into()), + pol_t0: Groth16Input::new(value.lottery_0), + pol_t1: Groth16Input::new(value.lottery_1), session: Groth16Input::new(value.session.into()), } } diff --git a/zk/proofs/poq/src/lib.rs b/zk/proofs/poq/src/lib.rs index 0bb213665..309eef7b4 100644 --- a/zk/proofs/poq/src/lib.rs +++ b/zk/proofs/poq/src/lib.rs @@ -120,6 +120,8 @@ pub fn verify(proof: &PoQProof, public_inputs: PoQVerifierInput) -> Result