mirror of
https://github.com/logos-co/nomos-node.git
synced 2026-08-27 09:31:10 +00:00
feat(pol): derive t0/t1_constant from f (#2155)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
asset_content_type: application/gzip
|
||||
|
||||
Generated
+40
-1
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -30,7 +30,8 @@ impl TryFrom<Inputs> 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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ impl From<Inputs> 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()
|
||||
}
|
||||
|
||||
@@ -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<const INPUTS: usize>() -> PoQInputs<INPUTS> {
|
||||
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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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); _],
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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<u32>,
|
||||
base_period_length: NonZero<u64>,
|
||||
/// sufficient time measured in slots to measure the density of block
|
||||
/// production with enough statistical significance.
|
||||
s_gen: NonZero<u64>,
|
||||
/// `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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(serde::Deserialize)]
|
||||
struct RawConfig {
|
||||
security_param: NonZero<u32>,
|
||||
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<u32>,
|
||||
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<u32>,
|
||||
active_slot_coefficient: f64,
|
||||
) -> NonZero<u64> {
|
||||
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<u32>,
|
||||
active_slot_coefficient: f64,
|
||||
) -> NonZero<u64> {
|
||||
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<u64> {
|
||||
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<u64> {
|
||||
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());
|
||||
|
||||
@@ -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"),
|
||||
)
|
||||
}
|
||||
|
||||
+22
-3
@@ -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]);
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<u64> {
|
||||
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 {
|
||||
|
||||
@@ -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::<Value>();
|
||||
.sum::<Value>()
|
||||
.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::<UtxoTree>();
|
||||
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(),
|
||||
|
||||
@@ -717,6 +717,8 @@ mod tests {
|
||||
nonce: ZkHash::ZERO,
|
||||
utxos: UtxoTree::default(),
|
||||
total_stake: 100,
|
||||
lottery_0: Fr::ZERO,
|
||||
lottery_1: Fr::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<NodeId> From<Membership<NodeId>> for PublicInfo<NodeId> {
|
||||
fn from(value: Membership<NodeId>) -> 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BackendSettings>(
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<EpochState> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -223,7 +223,7 @@ impl Cryptarchia {
|
||||
Self {
|
||||
consensus: <lb_cryptarchia_engine::Cryptarchia<_>>::from_lib(
|
||||
lib_id,
|
||||
ledger_config.consensus_config,
|
||||
ledger_config.consensus_config.clone(),
|
||||
state,
|
||||
lib_slot,
|
||||
lib_length,
|
||||
|
||||
@@ -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<u32> = 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<u32> = 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(
|
||||
[(
|
||||
|
||||
@@ -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<HeaderId>,
|
||||
) -> 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<HeaderId> {
|
||||
fn new_cryptarchia(
|
||||
lib: HeaderId,
|
||||
slot_activation_coeff: NonNegativeRatio,
|
||||
) -> lb_cryptarchia_engine::Cryptarchia<HeaderId> {
|
||||
<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,
|
||||
|
||||
@@ -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" }
|
||||
|
||||
+1
-10
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<u64> 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 _;
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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<PolChainInputsData> 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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
+233
-55
@@ -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<BigUint> = LazyLock::new(|| {
|
||||
BigUint::from_str_radix(
|
||||
"30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001",
|
||||
@@ -25,52 +17,238 @@ pub static P: LazyLock<BigUint> = 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<BigUint> = 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<BigUint> = 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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<PoQChainInputsData> for PoQChainInputs {
|
||||
core_root,
|
||||
pol_ledger_aged,
|
||||
pol_epoch_nonce,
|
||||
total_stake,
|
||||
lottery_0,
|
||||
lottery_1,
|
||||
}: PoQChainInputsData,
|
||||
) -> Result<Self, Self::Error> {
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PoQVerifierInputData> 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<PoQVerifierInputData> 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()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,8 @@ pub fn verify(proof: &PoQProof, public_inputs: PoQVerifierInput) -> Result<bool,
|
||||
mod tests {
|
||||
use std::str::FromStr as _;
|
||||
|
||||
use lb_pol::LotteryConstants;
|
||||
use lb_utils::math::NonNegativeRatio;
|
||||
use num_bigint::BigUint;
|
||||
|
||||
use super::*;
|
||||
@@ -217,6 +219,9 @@ mod tests {
|
||||
]
|
||||
.map(|(value, selector)| (BigUint::from_str(value).unwrap().into(), selector)),
|
||||
};
|
||||
let (lottery_0, lottery_1) =
|
||||
LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap()))
|
||||
.compute_lottery_values(5000);
|
||||
let chain_data = PoQChainInputsData {
|
||||
session: 150,
|
||||
core_root: BigUint::from_str(
|
||||
@@ -234,7 +239,8 @@ mod tests {
|
||||
)
|
||||
.unwrap()
|
||||
.into(),
|
||||
total_stake: 5000,
|
||||
lottery_0,
|
||||
lottery_1,
|
||||
};
|
||||
let common_data = PoQCommonInputsData {
|
||||
core_quota: 15,
|
||||
@@ -266,7 +272,8 @@ mod tests {
|
||||
pol_epoch_nonce: chain_data.pol_epoch_nonce,
|
||||
pol_ledger_aged: chain_data.pol_ledger_aged,
|
||||
session: chain_data.session,
|
||||
total_stake: chain_data.total_stake,
|
||||
lottery_0: chain_data.lottery_0,
|
||||
lottery_1: chain_data.lottery_1,
|
||||
};
|
||||
assert!(verify(&proof, recomputed_verify_inputs.into()).unwrap());
|
||||
}
|
||||
@@ -274,6 +281,9 @@ mod tests {
|
||||
#[expect(clippy::too_many_lines, reason = "For the sake of the test let it be")]
|
||||
#[test]
|
||||
fn test_leader_full_flow() {
|
||||
let (lottery_0, lottery_1) =
|
||||
LotteryConstants::new(NonNegativeRatio::new(1, 10.try_into().unwrap()))
|
||||
.compute_lottery_values(5000);
|
||||
let chain_data = PoQChainInputsData {
|
||||
session: 150,
|
||||
core_root: BigUint::from_str(
|
||||
@@ -291,7 +301,8 @@ mod tests {
|
||||
)
|
||||
.unwrap()
|
||||
.into(),
|
||||
total_stake: 5000,
|
||||
lottery_0,
|
||||
lottery_1,
|
||||
};
|
||||
let common_data = PoQCommonInputsData {
|
||||
core_quota: 15,
|
||||
@@ -469,7 +480,8 @@ mod tests {
|
||||
pol_epoch_nonce: chain_data.pol_epoch_nonce,
|
||||
pol_ledger_aged: chain_data.pol_ledger_aged,
|
||||
session: chain_data.session,
|
||||
total_stake: chain_data.total_stake,
|
||||
lottery_0: chain_data.lottery_0,
|
||||
lottery_1: chain_data.lottery_1,
|
||||
};
|
||||
assert!(verify(&proof, recomputed_verify_inputs.into()).unwrap());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user