From 2b4965f54b75fa45e5378b979d8f9b4a65ccb0ca Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 15 Jan 2025 10:23:10 +0000 Subject: [PATCH 01/22] feat(quadratic voting): alternative --- Cargo.lock | 40 +++++++++---------- .../chain-impl-mockchain/Cargo.toml | 2 + .../chain-impl-mockchain/src/vote/tally.rs | 7 +++- src/chain-libs/chain-vote/Cargo.toml | 3 ++ src/chain-libs/chain-vote/src/tally.rs | 11 ++++- 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f1eec6842..58756bf061 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1216,6 +1216,7 @@ dependencies = [ "hex", "imhamt", "lazy_static", + "num-integer", "proptest", "quickcheck", "quickcheck_macros", @@ -1303,12 +1304,14 @@ dependencies = [ "const_format", "criterion", "cryptoxide 0.4.4", + "num-integer", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", "rayon", "smoke", "thiserror", + "tracing", ] [[package]] @@ -4023,9 +4026,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -4739,25 +4742,24 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", - "num-rational 0.4.1", + "num-rational 0.4.2", "num-traits", ] [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", "serde", @@ -4765,9 +4767,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", "serde", @@ -4786,19 +4788,18 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -4818,11 +4819,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -4831,9 +4831,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", diff --git a/src/chain-libs/chain-impl-mockchain/Cargo.toml b/src/chain-libs/chain-impl-mockchain/Cargo.toml index 83f4b2b74d..4523268cf1 100644 --- a/src/chain-libs/chain-impl-mockchain/Cargo.toml +++ b/src/chain-libs/chain-impl-mockchain/Cargo.toml @@ -32,6 +32,8 @@ criterion = { version = "0.3.0", optional = true } rand = "0.8" cryptoxide = "0.4" tracing.workspace = true +num-integer = "0.1" + [features] property-test-api = [ diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 7d6a1c2f24..32c601ee5e 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -4,6 +4,7 @@ use crate::{ vote::{Choice, Options}, }; use chain_vote::EncryptedTally; +use num_integer::Roots; use std::fmt; use thiserror::Error; @@ -166,7 +167,11 @@ impl TallyResult { } else { let index = choice.as_byte() as usize; - self.results[index] = self.results[index].saturating_add(weight); + // Returns the truncated principal square root of an integer – ⌊√x⌋ + // This is solving for r in r² = x, rounding toward zero. The result will satisfy r² ≤ x < (r+1)² + let weight_gammad = weight.0.sqrt(); + + self.results[index] = self.results[index].saturating_add(Weight(weight_gammad)); Ok(()) } diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 6d31e20b84..17b6628f4e 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -14,6 +14,9 @@ thiserror = "1.0" cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" +tracing = "0.1" +num-integer = "0.1" + [dev-dependencies] rand_chacha = "0.3" diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index d28543be17..644342266d 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -11,6 +11,8 @@ use crate::{ use base64::{engine::general_purpose, Engine as _}; use cryptoxide::blake2b::Blake2b; use cryptoxide::digest::Digest; +use num_integer::Roots; + use rand_core::{CryptoRng, RngCore}; /// Secret key for opening vote @@ -155,10 +157,15 @@ impl EncryptedTally { pub fn add(&mut self, ballot: &Ballot, weight: u64) { assert_eq!(ballot.vote().len(), self.r.len()); assert_eq!(ballot.fingerprint(), &self.fingerprint); + + // Returns the truncated principal square root of an integer – ⌊√x⌋ + // This is solving for r in r² = x, rounding toward zero. The result will satisfy r² ≤ x < (r+1)² + let weight_gammad = weight.sqrt(); + for (ri, ci) in self.r.iter_mut().zip(ballot.vote().iter()) { - *ri = &*ri + &(ci * weight); + *ri = &*ri + &(ci * weight_gammad); } - self.max_stake += weight; + self.max_stake += weight_gammad; } /// Given a single committee member's `secret_key`, returns a partial decryption of From a13cc3e291d29c27d525f6366d8ed82d3fdf63ef Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 13:44:52 +0000 Subject: [PATCH 02/22] refactor(gamma): big decimal --- Cargo.lock | 54 ++++++++++++------- src/audit/README.md | 11 ++++ src/audit/src/offline/bin/main.rs | 17 ++++++ .../chain-impl-mockchain/Cargo.toml | 2 +- .../chain-impl-mockchain/src/vote/tally.rs | 27 ++++++++-- src/chain-libs/chain-vote/Cargo.toml | 3 +- src/chain-libs/chain-vote/src/tally.rs | 26 ++++++--- 7 files changed, 108 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58756bf061..b09ccbd898 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,6 +691,19 @@ dependencies = [ "serde", ] +[[package]] +name = "bigdecimal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "bincode" version = "1.3.3" @@ -1203,6 +1216,7 @@ dependencies = [ name = "chain-impl-mockchain" version = "0.1.0" dependencies = [ + "bigdecimal", "cardano-legacy-address", "chain-addr", "chain-core", @@ -1216,7 +1230,6 @@ dependencies = [ "hex", "imhamt", "lazy_static", - "num-integer", "proptest", "quickcheck", "quickcheck_macros", @@ -1298,13 +1311,13 @@ name = "chain-vote" version = "0.1.0" dependencies = [ "base64 0.21.5", + "bigdecimal", "cfg-if 1.0.0", "chain-core", "chain-crypto", "const_format", "criterion", "cryptoxide 0.4.4", - "num-integer", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -4026,9 +4039,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" @@ -4742,24 +4755,25 @@ dependencies = [ [[package]] name = "num" -version = "0.4.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", - "num-rational 0.4.2", + "num-rational 0.4.1", "num-traits", ] [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ + "autocfg", "num-integer", "num-traits", "serde", @@ -4767,9 +4781,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.6" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ "num-traits", "serde", @@ -4788,18 +4802,19 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ + "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", @@ -4819,10 +4834,11 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ + "autocfg", "num-bigint", "num-integer", "num-traits", @@ -4831,9 +4847,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.19" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", diff --git a/src/audit/README.md b/src/audit/README.md index f7f3629579..ef1457f384 100644 --- a/src/audit/README.md +++ b/src/audit/README.md @@ -31,6 +31,17 @@ FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 ``` +```bash + +OFFICIAL_RESULTS=/tmp/activevoteplans.json +BLOCK0=/tmp/fund9-leader-1/artifacts/block0.bin +FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 +QUADRATIC_SCALE=0.1 + +./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --official-results $OFFICIAL_RESULTS --quadratic_scaling $QUADRATIC_SCALE + +``` + This will create three files: - *ledger_after_tally.json* **(decrypted ledger state after tally)** *should match official results!* - *ledger_before_tally.json* **(encrypted ledger state before tally)** diff --git a/src/audit/src/offline/bin/main.rs b/src/audit/src/offline/bin/main.rs index 39402585d7..ff4401faf5 100644 --- a/src/audit/src/offline/bin/main.rs +++ b/src/audit/src/offline/bin/main.rs @@ -19,6 +19,7 @@ use lib::offline::{ use chain_core::packer::Codec; use color_eyre::{eyre::Context, Report}; +use std::env; use std::{error::Error, path::PathBuf}; /// @@ -36,6 +37,12 @@ pub struct Args { /// cross reference official results #[clap(short, long)] official_results: Option, + /// Quadratic scaling + #[clap(short, long)] + gamma: Option, + /// Quadratic scaling + #[clap(short, long)] + precision: Option, } fn main() -> Result<(), Box> { @@ -95,6 +102,16 @@ fn main() -> Result<(), Box> { // use tally tool to validate decrypted results let shares_and_results = extract_decryption_shares_and_results(all_fragments); + if let Some(gamma) = args.gamma { + const SCALE_FACTOR: &str = "QUADRATIC_VOTING_SCALING_FACTOR"; + std::env::set_var(SCALE_FACTOR, gamma); + } + + if let Some(precision) = args.precision { + const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; + std::env::set_var(PRECISION, precision); + } + // Compare decrypted tallies with official results if provided if let Some(official_results) = args.official_results { // official catalyst results in json format diff --git a/src/chain-libs/chain-impl-mockchain/Cargo.toml b/src/chain-libs/chain-impl-mockchain/Cargo.toml index 4523268cf1..b5acfb01b5 100644 --- a/src/chain-libs/chain-impl-mockchain/Cargo.toml +++ b/src/chain-libs/chain-impl-mockchain/Cargo.toml @@ -32,7 +32,7 @@ criterion = { version = "0.3.0", optional = true } rand = "0.8" cryptoxide = "0.4" tracing.workspace = true -num-integer = "0.1" +bigdecimal = "0.4.7" [features] diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 32c601ee5e..b703797272 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -3,8 +3,14 @@ use crate::{ value::Value, vote::{Choice, Options}, }; +use bigdecimal::BigDecimal; +use bigdecimal::ToPrimitive; use chain_vote::EncryptedTally; -use num_integer::Roots; +use std::num::NonZeroI64; + +use std::str::FromStr; + +use std::env; use std::fmt; use thiserror::Error; @@ -167,11 +173,22 @@ impl TallyResult { } else { let index = choice.as_byte() as usize; - // Returns the truncated principal square root of an integer – ⌊√x⌋ - // This is solving for r in r² = x, rounding toward zero. The result will satisfy r² ≤ x < (r+1)² - let weight_gammad = weight.0.sqrt(); + const SCALE_FACTOR: &str = "QUADRATIC_VOTING_SCALING_FACTOR"; + const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; + + let scaling_factor = env::var(SCALE_FACTOR).unwrap_or(1.to_string()); + let precision = + i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); + + let gamma = BigDecimal::from_str(&scaling_factor).unwrap_or(BigDecimal::from(1)); + let stake = BigDecimal::from(weight.0); + + let weight = (gamma * stake) + .round(precision) + .to_u64() + .unwrap_or(weight.0); - self.results[index] = self.results[index].saturating_add(Weight(weight_gammad)); + self.results[index] = self.results[index].saturating_add(Weight(weight)); Ok(()) } diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 17b6628f4e..4dfbba1836 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -15,7 +15,8 @@ cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" tracing = "0.1" -num-integer = "0.1" +bigdecimal = "0.4.7" + [dev-dependencies] diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index 644342266d..f6e254e8cc 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -1,5 +1,8 @@ +use std::num::NonZeroI64; use std::num::NonZeroU64; +use std::str::FromStr; + use crate::GroupElement; use crate::{ committee::*, @@ -9,9 +12,13 @@ use crate::{ TallyOptimizationTable, }; use base64::{engine::general_purpose, Engine as _}; + +use bigdecimal::BigDecimal; +use bigdecimal::ToPrimitive; use cryptoxide::blake2b::Blake2b; use cryptoxide::digest::Digest; -use num_integer::Roots; + +use std::env; use rand_core::{CryptoRng, RngCore}; @@ -158,14 +165,21 @@ impl EncryptedTally { assert_eq!(ballot.vote().len(), self.r.len()); assert_eq!(ballot.fingerprint(), &self.fingerprint); - // Returns the truncated principal square root of an integer – ⌊√x⌋ - // This is solving for r in r² = x, rounding toward zero. The result will satisfy r² ≤ x < (r+1)² - let weight_gammad = weight.sqrt(); + const SCALE_FACTOR: &str = "QUADRATIC_VOTING_SCALING_FACTOR"; + const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; + + let scaling_factor = env::var(SCALE_FACTOR).unwrap_or(1.to_string()); + let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); + + let gamma = BigDecimal::from_str(&scaling_factor).unwrap_or(BigDecimal::from(1)); + let stake = BigDecimal::from(weight); + + let weight = (gamma * stake).round(precision).to_u64().unwrap_or(weight); for (ri, ci) in self.r.iter_mut().zip(ballot.vote().iter()) { - *ri = &*ri + &(ci * weight_gammad); + *ri = &*ri + &(ci * weight); } - self.max_stake += weight_gammad; + self.max_stake += weight; } /// Given a single committee member's `secret_key`, returns a partial decryption of From 038fdb8839711210177e5e2726ad31d92d4c33f1 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 13:51:41 +0000 Subject: [PATCH 03/22] refactor(gamma): big decimal --- src/audit/README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/audit/README.md b/src/audit/README.md index ef1457f384..8746e9421c 100644 --- a/src/audit/README.md +++ b/src/audit/README.md @@ -31,14 +31,18 @@ FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 ``` + +*Generate encrypted tally with gamma scaling* + + ```bash -OFFICIAL_RESULTS=/tmp/activevoteplans.json BLOCK0=/tmp/fund9-leader-1/artifacts/block0.bin FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 -QUADRATIC_SCALE=0.1 +GAMMA=0.1 +PRECISION=5 -./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --official-results $OFFICIAL_RESULTS --quadratic_scaling $QUADRATIC_SCALE +./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA --precision $PRECISION ``` From eabf187ebb1e74c23eedc0af4ba98452e687e773 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 13:52:53 +0000 Subject: [PATCH 04/22] refactor(gamma): big decimal --- Cargo.lock | 1 - src/chain-libs/chain-vote/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b09ccbd898..4c85dc6f23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1324,7 +1324,6 @@ dependencies = [ "rayon", "smoke", "thiserror", - "tracing", ] [[package]] diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 4dfbba1836..7e94af1184 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -14,7 +14,6 @@ thiserror = "1.0" cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" -tracing = "0.1" bigdecimal = "0.4.7" From 72e6c8f0da52d6d09f3606d3fbaa12499e5192ab Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 18:45:41 +0000 Subject: [PATCH 05/22] refactor(gamma): big decimal --- src/audit/src/offline/bin/main.rs | 8 ++++---- src/chain-libs/chain-impl-mockchain/src/vote/tally.rs | 6 +++--- src/chain-libs/chain-vote/src/tally.rs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/audit/src/offline/bin/main.rs b/src/audit/src/offline/bin/main.rs index ff4401faf5..97fdec1cc5 100644 --- a/src/audit/src/offline/bin/main.rs +++ b/src/audit/src/offline/bin/main.rs @@ -37,10 +37,10 @@ pub struct Args { /// cross reference official results #[clap(short, long)] official_results: Option, - /// Quadratic scaling + /// Gamma value for Quadratic scaling #[clap(short, long)] gamma: Option, - /// Quadratic scaling + /// Rounding precision for arithmetic #[clap(short, long)] precision: Option, } @@ -103,8 +103,8 @@ fn main() -> Result<(), Box> { let shares_and_results = extract_decryption_shares_and_results(all_fragments); if let Some(gamma) = args.gamma { - const SCALE_FACTOR: &str = "QUADRATIC_VOTING_SCALING_FACTOR"; - std::env::set_var(SCALE_FACTOR, gamma); + const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; + std::env::set_var(GAMMA, gamma); } if let Some(precision) = args.precision { diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index b703797272..bf9bcb60f0 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -173,14 +173,14 @@ impl TallyResult { } else { let index = choice.as_byte() as usize; - const SCALE_FACTOR: &str = "QUADRATIC_VOTING_SCALING_FACTOR"; + const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; - let scaling_factor = env::var(SCALE_FACTOR).unwrap_or(1.to_string()); + let gamma = env::var(GAMMA).unwrap_or(1.to_string()); let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - let gamma = BigDecimal::from_str(&scaling_factor).unwrap_or(BigDecimal::from(1)); + let gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); let stake = BigDecimal::from(weight.0); let weight = (gamma * stake) diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index f6e254e8cc..3fd8265145 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -165,13 +165,13 @@ impl EncryptedTally { assert_eq!(ballot.vote().len(), self.r.len()); assert_eq!(ballot.fingerprint(), &self.fingerprint); - const SCALE_FACTOR: &str = "QUADRATIC_VOTING_SCALING_FACTOR"; + const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; - let scaling_factor = env::var(SCALE_FACTOR).unwrap_or(1.to_string()); + let gamma = env::var(GAMMA).unwrap_or(1.to_string()); let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - let gamma = BigDecimal::from_str(&scaling_factor).unwrap_or(BigDecimal::from(1)); + let gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); let stake = BigDecimal::from(weight); let weight = (gamma * stake).round(precision).to_u64().unwrap_or(weight); From 12163056d309f0fb00359eaff9124e89dc572b56 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 18:51:57 +0000 Subject: [PATCH 06/22] refactor(gamma): big decimal --- src/chain-libs/chain-impl-mockchain/src/vote/tally.rs | 1 + src/chain-libs/chain-vote/src/tally.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index bf9bcb60f0..9a0b4c99c2 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -176,6 +176,7 @@ impl TallyResult { const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; + // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. let gamma = env::var(GAMMA).unwrap_or(1.to_string()); let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index 3fd8265145..ae6ed35c5a 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -168,6 +168,7 @@ impl EncryptedTally { const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; + // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. let gamma = env::var(GAMMA).unwrap_or(1.to_string()); let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); From 39aee7d0fbb12808694b78a7016f982d174b02c4 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 20:28:40 +0000 Subject: [PATCH 07/22] refactor(gamma): big decimal --- Cargo.lock | 13 ++++++------ src/audit/src/offline/bin/main.rs | 20 +++++++++---------- .../chain-impl-mockchain/src/vote/tally.rs | 7 ++++++- src/chain-libs/chain-vote/Cargo.toml | 2 +- src/chain-libs/chain-vote/src/tally.rs | 7 ++++++- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c85dc6f23..6755eda571 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1324,6 +1324,7 @@ dependencies = [ "rayon", "smoke", "thiserror", + "tracing", ] [[package]] @@ -7880,9 +7881,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -7904,9 +7905,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -7915,9 +7916,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", diff --git a/src/audit/src/offline/bin/main.rs b/src/audit/src/offline/bin/main.rs index 97fdec1cc5..9e98b0a9df 100644 --- a/src/audit/src/offline/bin/main.rs +++ b/src/audit/src/offline/bin/main.rs @@ -68,6 +68,16 @@ fn main() -> Result<(), Box> { info!("Audit Tool."); info!("Starting Offline Tally"); + if let Some(gamma) = args.gamma { + const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; + std::env::set_var(GAMMA, gamma); + } + + if let Some(precision) = args.precision { + const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; + std::env::set_var(PRECISION, precision); + } + // Load and replay fund fragments from storage let storage_path = PathBuf::from(args.fragments); @@ -102,16 +112,6 @@ fn main() -> Result<(), Box> { // use tally tool to validate decrypted results let shares_and_results = extract_decryption_shares_and_results(all_fragments); - if let Some(gamma) = args.gamma { - const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; - std::env::set_var(GAMMA, gamma); - } - - if let Some(precision) = args.precision { - const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; - std::env::set_var(PRECISION, precision); - } - // Compare decrypted tallies with official results if provided if let Some(official_results) = args.official_results { // official catalyst results in json format diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 9a0b4c99c2..62f1bbab56 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -178,10 +178,15 @@ impl TallyResult { // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. let gamma = env::var(GAMMA).unwrap_or(1.to_string()); + let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - let gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); + let mut gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); + // Gamma must be between 0 and 1, anything else is treated as bad input; defaulting gamma to 1. + if gamma < BigDecimal::from(0) || gamma > BigDecimal::from(1) { + gamma = BigDecimal::from(1); + } let stake = BigDecimal::from(weight.0); let weight = (gamma * stake) diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 7e94af1184..06e5e01fc3 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -15,7 +15,7 @@ cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" bigdecimal = "0.4.7" - +tracing = "0.1.41" [dev-dependencies] diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index ae6ed35c5a..b5d13ad817 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -2,6 +2,7 @@ use std::num::NonZeroI64; use std::num::NonZeroU64; use std::str::FromStr; +use tracing::info; use crate::GroupElement; use crate::{ @@ -172,7 +173,11 @@ impl EncryptedTally { let gamma = env::var(GAMMA).unwrap_or(1.to_string()); let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - let gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); + let mut gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); + // Gamma must be between 0 and 1, anything else is treated as bad input; defaulting gamma to 1. + if gamma < BigDecimal::from(0) || gamma > BigDecimal::from(1) { + gamma = BigDecimal::from(1); + } let stake = BigDecimal::from(weight); let weight = (gamma * stake).round(precision).to_u64().unwrap_or(weight); From 7dac2e114377e899074decd9a903b345e5d5bcf7 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 20:29:30 +0000 Subject: [PATCH 08/22] refactor(gamma): big decimal --- Cargo.lock | 1 - src/chain-libs/chain-vote/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6755eda571..353917502b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1324,7 +1324,6 @@ dependencies = [ "rayon", "smoke", "thiserror", - "tracing", ] [[package]] diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 06e5e01fc3..1e2f57be62 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -15,7 +15,6 @@ cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" bigdecimal = "0.4.7" -tracing = "0.1.41" [dev-dependencies] From 375e6c1da1b58989a8869acf3a62dcbe70c86454 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 20:31:30 +0000 Subject: [PATCH 09/22] refactor(gamma): big decimal --- src/chain-libs/chain-vote/src/tally.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index b5d13ad817..666b9a7c1c 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -2,7 +2,6 @@ use std::num::NonZeroI64; use std::num::NonZeroU64; use std::str::FromStr; -use tracing::info; use crate::GroupElement; use crate::{ From d3c221a3b694f745897582c3e48efc03cae758c3 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 20 Jan 2025 21:54:00 +0000 Subject: [PATCH 10/22] refactor(big rational): fraction to denom and numer --- Cargo.lock | 36 +++++++++---------- .../chain-impl-mockchain/Cargo.toml | 1 + .../chain-impl-mockchain/src/vote/tally.rs | 16 +++++---- src/chain-libs/chain-vote/Cargo.toml | 2 ++ src/chain-libs/chain-vote/src/tally.rs | 23 ++++++++---- 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 353917502b..cc4bb5a9e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1230,6 +1230,7 @@ dependencies = [ "hex", "imhamt", "lazy_static", + "num", "proptest", "quickcheck", "quickcheck_macros", @@ -1318,12 +1319,14 @@ dependencies = [ "const_format", "criterion", "cryptoxide 0.4.4", + "num", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", "rayon", "smoke", "thiserror", + "tracing", ] [[package]] @@ -4754,25 +4757,24 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", - "num-rational 0.4.1", + "num-rational 0.4.2", "num-traits", ] [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", "serde", @@ -4780,9 +4782,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", "serde", @@ -4801,19 +4803,18 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -4833,11 +4834,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -4846,9 +4846,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", diff --git a/src/chain-libs/chain-impl-mockchain/Cargo.toml b/src/chain-libs/chain-impl-mockchain/Cargo.toml index b5acfb01b5..c75ceaaa80 100644 --- a/src/chain-libs/chain-impl-mockchain/Cargo.toml +++ b/src/chain-libs/chain-impl-mockchain/Cargo.toml @@ -33,6 +33,7 @@ rand = "0.8" cryptoxide = "0.4" tracing.workspace = true bigdecimal = "0.4.7" +num = "0.4.3" [features] diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 62f1bbab56..60288505aa 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -6,6 +6,8 @@ use crate::{ use bigdecimal::BigDecimal; use bigdecimal::ToPrimitive; use chain_vote::EncryptedTally; +use num::BigRational; +use num::{BigInt, FromPrimitive}; use std::num::NonZeroI64; use std::str::FromStr; @@ -177,19 +179,19 @@ impl TallyResult { const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. - let gamma = env::var(GAMMA).unwrap_or(1.to_string()); + let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - let mut gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); - // Gamma must be between 0 and 1, anything else is treated as bad input; defaulting gamma to 1. - if gamma < BigDecimal::from(0) || gamma > BigDecimal::from(1) { - gamma = BigDecimal::from(1); - } + let gamma = + BigRational::from_f64(gamma).unwrap_or(BigRational::from_integer(BigInt::from(1))); + let denom = BigDecimal::from_bigint(gamma.denom().clone(), precision); + let numer = BigDecimal::from_bigint(gamma.numer().clone(), precision); + let stake = BigDecimal::from(weight.0); - let weight = (gamma * stake) + let weight = ((stake.clone() * numer.clone()) / denom) .round(precision) .to_u64() .unwrap_or(weight.0); diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 1e2f57be62..d5baca7013 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -15,6 +15,8 @@ cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" bigdecimal = "0.4.7" +num = "0.4.3" +tracing = "*" [dev-dependencies] diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index 666b9a7c1c..ec146b10ce 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -12,6 +12,10 @@ use crate::{ TallyOptimizationTable, }; use base64::{engine::general_purpose, Engine as _}; +use num::BigInt; +use num::BigRational; +use num::FromPrimitive; +use tracing::info; use bigdecimal::BigDecimal; use bigdecimal::ToPrimitive; @@ -169,17 +173,22 @@ impl EncryptedTally { const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. - let gamma = env::var(GAMMA).unwrap_or(1.to_string()); + let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - let mut gamma = BigDecimal::from_str(&gamma).unwrap_or(BigDecimal::from(1)); - // Gamma must be between 0 and 1, anything else is treated as bad input; defaulting gamma to 1. - if gamma < BigDecimal::from(0) || gamma > BigDecimal::from(1) { - gamma = BigDecimal::from(1); - } + let gamma = + BigRational::from_f64(gamma).unwrap_or(BigRational::from_integer(BigInt::from(1))); + let denom = BigDecimal::from_bigint(gamma.denom().clone(), precision); + let numer = BigDecimal::from_bigint(gamma.numer().clone(), precision); + let stake = BigDecimal::from(weight); - let weight = (gamma * stake).round(precision).to_u64().unwrap_or(weight); + let weight = ((stake.clone() * numer.clone()) / denom) + .round(precision) + .to_u64() + .unwrap_or(weight); + + info!("stake before {:?} stake after {:?}", stake.to_u64(), weight); for (ri, ci) in self.r.iter_mut().zip(ballot.vote().iter()) { *ri = &*ri + &(ci * weight); From bccc2fc9d04a9e37112c31c7284fb94fccf2c04e Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 21 Jan 2025 12:04:22 +0000 Subject: [PATCH 11/22] ignore(fractional exponent with arbitrary precision) --- Cargo.lock | 36 ++++++++++++++---- src/audit/README.md | 5 +-- src/audit/src/offline/bin/main.rs | 8 ---- .../chain-impl-mockchain/Cargo.toml | 3 +- .../chain-impl-mockchain/src/vote/tally.rs | 36 +++++++++++------- src/chain-libs/chain-vote/Cargo.toml | 3 +- src/chain-libs/chain-vote/src/tally.rs | 38 ++++++++++++------- 7 files changed, 81 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc4bb5a9e0..9cfeab27ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -693,17 +693,24 @@ dependencies = [ [[package]] name = "bigdecimal" -version = "0.4.7" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" +checksum = "d1e50562e37200edf7c6c43e54a08e64a5553bfb59d9c297d5572512aa517256" dependencies = [ - "autocfg", - "libm", - "num-bigint", + "num-bigint 0.3.3", "num-integer", "num-traits", ] +[[package]] +name = "bigdecimalmath" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a09dec65c0538edbd13f2284cdaed57e138521d060d5de1a56c688873a057090" +dependencies = [ + "bigdecimal", +] + [[package]] name = "bincode" version = "1.3.3" @@ -946,7 +953,7 @@ dependencies = [ "js-sys", "linked-hash-map", "noop_proc_macro", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "rand 0.8.5", "rand_os", @@ -1217,6 +1224,7 @@ name = "chain-impl-mockchain" version = "0.1.0" dependencies = [ "bigdecimal", + "bigdecimalmath", "cardano-legacy-address", "chain-addr", "chain-core", @@ -1313,6 +1321,7 @@ version = "0.1.0" dependencies = [ "base64 0.21.5", "bigdecimal", + "bigdecimalmath", "cfg-if 1.0.0", "chain-core", "chain-crypto", @@ -4761,7 +4770,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-complex", "num-integer", "num-iter", @@ -4769,6 +4778,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4838,7 +4858,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "serde", diff --git a/src/audit/README.md b/src/audit/README.md index 8746e9421c..88db9b35d3 100644 --- a/src/audit/README.md +++ b/src/audit/README.md @@ -39,11 +39,10 @@ FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 BLOCK0=/tmp/fund9-leader-1/artifacts/block0.bin FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 -GAMMA=0.1 -PRECISION=5 +GAMMA=0.5 -./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA --precision $PRECISION +./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA ``` This will create three files: diff --git a/src/audit/src/offline/bin/main.rs b/src/audit/src/offline/bin/main.rs index 9e98b0a9df..a5824bf819 100644 --- a/src/audit/src/offline/bin/main.rs +++ b/src/audit/src/offline/bin/main.rs @@ -40,9 +40,6 @@ pub struct Args { /// Gamma value for Quadratic scaling #[clap(short, long)] gamma: Option, - /// Rounding precision for arithmetic - #[clap(short, long)] - precision: Option, } fn main() -> Result<(), Box> { @@ -73,11 +70,6 @@ fn main() -> Result<(), Box> { std::env::set_var(GAMMA, gamma); } - if let Some(precision) = args.precision { - const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; - std::env::set_var(PRECISION, precision); - } - // Load and replay fund fragments from storage let storage_path = PathBuf::from(args.fragments); diff --git a/src/chain-libs/chain-impl-mockchain/Cargo.toml b/src/chain-libs/chain-impl-mockchain/Cargo.toml index c75ceaaa80..84837d6ed5 100644 --- a/src/chain-libs/chain-impl-mockchain/Cargo.toml +++ b/src/chain-libs/chain-impl-mockchain/Cargo.toml @@ -32,7 +32,8 @@ criterion = { version = "0.3.0", optional = true } rand = "0.8" cryptoxide = "0.4" tracing.workspace = true -bigdecimal = "0.4.7" +bigdecimal = "~0.2" +bigdecimalmath = "0.1.0" num = "0.4.3" diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 60288505aa..967a7cb300 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -4,11 +4,13 @@ use crate::{ vote::{Choice, Options}, }; use bigdecimal::BigDecimal; + use bigdecimal::ToPrimitive; +use bigdecimalmath::root; use chain_vote::EncryptedTally; -use num::BigRational; -use num::{BigInt, FromPrimitive}; -use std::num::NonZeroI64; + +use num::FromPrimitive; +use num::Rational32; use std::str::FromStr; @@ -176,23 +178,24 @@ impl TallyResult { let index = choice.as_byte() as usize; const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; - const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); - let precision = - i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); + let gamma = Rational32::from_f64(gamma).unwrap_or(Rational32::from_integer(1)); + let denom = isize::try_from(*gamma.denom()).unwrap(); + let numer = gamma.numer(); - let gamma = - BigRational::from_f64(gamma).unwrap_or(BigRational::from_integer(BigInt::from(1))); - let denom = BigDecimal::from_bigint(gamma.denom().clone(), precision); - let numer = BigDecimal::from_bigint(gamma.numer().clone(), precision); + // stake + let base = BigDecimal::from(weight.0); - let stake = BigDecimal::from(weight.0); + // m-th power, then take the n-th root: where x^(m/n) + // mth power equals x^m where m in the numerator + let mth_power = self.pow_fractional_exponent(&base, *numer); - let weight = ((stake.clone() * numer.clone()) / denom) - .round(precision) + // nth root + let weight = root(denom, mth_power) + .unwrap_or(base.clone()) .to_u64() .unwrap_or(weight.0); @@ -201,6 +204,13 @@ impl TallyResult { Ok(()) } } + + fn pow_fractional_exponent(&self, x: &BigDecimal, n: i32) -> BigDecimal { + let (bigint, scale) = x.as_bigint_and_exponent(); + let new_scale = scale * n as i64; + + BigDecimal::new(bigint.pow(n as u32), new_scale) + } } impl From for Weight { diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index d5baca7013..9ec06db92a 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -14,7 +14,8 @@ thiserror = "1.0" cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" -bigdecimal = "0.4.7" +bigdecimal = "~0.2" +bigdecimalmath = "0.1.0" num = "0.4.3" tracing = "*" diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index ec146b10ce..30218a433b 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -1,6 +1,7 @@ -use std::num::NonZeroI64; use std::num::NonZeroU64; +use bigdecimal::BigDecimal; +use bigdecimalmath::root; use std::str::FromStr; use crate::GroupElement; @@ -12,12 +13,11 @@ use crate::{ TallyOptimizationTable, }; use base64::{engine::general_purpose, Engine as _}; -use num::BigInt; -use num::BigRational; + use num::FromPrimitive; +use num::Rational32; use tracing::info; -use bigdecimal::BigDecimal; use bigdecimal::ToPrimitive; use cryptoxide::blake2b::Blake2b; use cryptoxide::digest::Digest; @@ -170,25 +170,28 @@ impl EncryptedTally { assert_eq!(ballot.fingerprint(), &self.fingerprint); const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; - const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); - let precision = i64::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - let gamma = - BigRational::from_f64(gamma).unwrap_or(BigRational::from_integer(BigInt::from(1))); - let denom = BigDecimal::from_bigint(gamma.denom().clone(), precision); - let numer = BigDecimal::from_bigint(gamma.numer().clone(), precision); + let gamma = Rational32::from_f64(gamma).unwrap_or(Rational32::from_integer(1)); + let denom = isize::try_from(*gamma.denom()).unwrap(); + let numer = gamma.numer(); + + // stake + let base = BigDecimal::from(weight); - let stake = BigDecimal::from(weight); + // m-th power, then take the n-th root: where x^(m/n) + // mth power equals x^m where m in the numerator + let mth_power = self.pow_fractional_exponent(&base, *numer); - let weight = ((stake.clone() * numer.clone()) / denom) - .round(precision) + // nth root + let weight = root(denom, mth_power) + .unwrap_or(base.clone()) .to_u64() .unwrap_or(weight); - info!("stake before {:?} stake after {:?}", stake.to_u64(), weight); + info!("stake before {:?} stake after {:?}", base.to_u64(), weight); for (ri, ci) in self.r.iter_mut().zip(ballot.vote().iter()) { *ri = &*ri + &(ci * weight); @@ -196,6 +199,13 @@ impl EncryptedTally { self.max_stake += weight; } + fn pow_fractional_exponent(&self, x: &BigDecimal, n: i32) -> BigDecimal { + let (bigint, scale) = x.as_bigint_and_exponent(); + let new_scale = scale * n as i64; + + BigDecimal::new(bigint.pow(n as u32), new_scale) + } + /// Given a single committee member's `secret_key`, returns a partial decryption of /// the `EncryptedTally` pub fn partial_decrypt( From cb98492df7ab2ed5be45393fefed3a26689d12fd Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 21 Jan 2025 12:35:54 +0000 Subject: [PATCH 12/22] feat(rug): fractional powers without roots --- Cargo.lock | 144 +++++++++++++----- .../chain-impl-mockchain/Cargo.toml | 3 +- .../chain-impl-mockchain/src/vote/tally.rs | 39 +++-- src/chain-libs/chain-vote/Cargo.toml | 3 +- src/chain-libs/chain-vote/src/tally.rs | 46 +++--- 5 files changed, 147 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cfeab27ed..b6dea45908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,6 +582,12 @@ dependencies = [ "tower-service", ] +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backtrace" version = "0.3.69" @@ -691,26 +697,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bigdecimal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e50562e37200edf7c6c43e54a08e64a5553bfb59d9c297d5572512aa517256" -dependencies = [ - "num-bigint 0.3.3", - "num-integer", - "num-traits", -] - -[[package]] -name = "bigdecimalmath" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a09dec65c0538edbd13f2284cdaed57e138521d060d5de1a56c688873a057090" -dependencies = [ - "bigdecimal", -] - [[package]] name = "bincode" version = "1.3.3" @@ -953,7 +939,7 @@ dependencies = [ "js-sys", "linked-hash-map", "noop_proc_macro", - "num-bigint 0.4.6", + "num-bigint", "num-integer", "rand 0.8.5", "rand_os", @@ -1223,8 +1209,6 @@ dependencies = [ name = "chain-impl-mockchain" version = "0.1.0" dependencies = [ - "bigdecimal", - "bigdecimalmath", "cardano-legacy-address", "chain-addr", "chain-core", @@ -1246,6 +1230,7 @@ dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", "rayon", + "rug", "serde", "serde_json", "sparse-array", @@ -1320,8 +1305,6 @@ name = "chain-vote" version = "0.1.0" dependencies = [ "base64 0.21.5", - "bigdecimal", - "bigdecimalmath", "cfg-if 1.0.0", "chain-core", "chain-crypto", @@ -1333,6 +1316,7 @@ dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", "rayon", + "rug", "smoke", "thiserror", "tracing", @@ -2826,6 +2810,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0205cd82059bc63b63cf516d714352a30c44f2c74da9961dfda2617ae6b5918" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "goblin" version = "0.5.4" @@ -4770,7 +4764,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "num-bigint 0.4.6", + "num-bigint", "num-complex", "num-integer", "num-iter", @@ -4778,17 +4772,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -4858,7 +4841,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "num-bigint 0.4.6", + "num-bigint", "num-integer", "num-traits", "serde", @@ -6322,6 +6305,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rug" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ae2c1089ec0575193eb9222881310cc1ed8bce3646ef8b81b44b518595b79d" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + [[package]] name = "rust_decimal" version = "1.33.1" @@ -9292,6 +9287,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -9322,6 +9326,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -9334,6 +9354,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -9346,6 +9372,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -9358,6 +9390,18 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -9370,6 +9414,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -9382,6 +9432,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -9394,6 +9450,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9406,6 +9468,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.5.19" diff --git a/src/chain-libs/chain-impl-mockchain/Cargo.toml b/src/chain-libs/chain-impl-mockchain/Cargo.toml index 84837d6ed5..bbe60b1aca 100644 --- a/src/chain-libs/chain-impl-mockchain/Cargo.toml +++ b/src/chain-libs/chain-impl-mockchain/Cargo.toml @@ -32,8 +32,7 @@ criterion = { version = "0.3.0", optional = true } rand = "0.8" cryptoxide = "0.4" tracing.workspace = true -bigdecimal = "~0.2" -bigdecimalmath = "0.1.0" +rug = "1.26.1" num = "0.4.3" diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 967a7cb300..5a313a5d49 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -3,14 +3,14 @@ use crate::{ value::Value, vote::{Choice, Options}, }; -use bigdecimal::BigDecimal; -use bigdecimal::ToPrimitive; -use bigdecimalmath::root; use chain_vote::EncryptedTally; - +use core::cmp::Ordering; use num::FromPrimitive; + use num::Rational32; +use rug::Integer; +use rug::{float::Round, ops::Pow, Float, Rational}; use std::str::FromStr; @@ -178,24 +178,28 @@ impl TallyResult { let index = choice.as_byte() as usize; const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; + const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); + let precision = + u32::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); + let gamma = Rational32::from_f64(gamma).unwrap_or(Rational32::from_integer(1)); - let denom = isize::try_from(*gamma.denom()).unwrap(); + let denom = gamma.denom(); let numer = gamma.numer(); - // stake - let base = BigDecimal::from(weight.0); - - // m-th power, then take the n-th root: where x^(m/n) - // mth power equals x^m where m in the numerator - let mth_power = self.pow_fractional_exponent(&base, *numer); + let stake = Float::with_val(precision, weight.0); + // r = gamma in rational form + let exp_r = Rational::from((*numer, *denom)); + let exp_f = Float::with_val(precision, &exp_r); + let p = stake.clone().pow(&exp_f); - // nth root - let weight = root(denom, mth_power) - .unwrap_or(base.clone()) + let weight = p + .to_integer_round(Round::Down) + .unwrap_or((Integer::from(weight.0), Ordering::Less)) + .0 .to_u64() .unwrap_or(weight.0); @@ -204,13 +208,6 @@ impl TallyResult { Ok(()) } } - - fn pow_fractional_exponent(&self, x: &BigDecimal, n: i32) -> BigDecimal { - let (bigint, scale) = x.as_bigint_and_exponent(); - let new_scale = scale * n as i64; - - BigDecimal::new(bigint.pow(n as u32), new_scale) - } } impl From for Weight { diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 9ec06db92a..776320fd38 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -14,8 +14,7 @@ thiserror = "1.0" cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" -bigdecimal = "~0.2" -bigdecimalmath = "0.1.0" +rug = "1.26.1" num = "0.4.3" tracing = "*" diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index 30218a433b..16bd05cdff 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -1,7 +1,9 @@ use std::num::NonZeroU64; -use bigdecimal::BigDecimal; -use bigdecimalmath::root; +use core::cmp::Ordering; +use num::FromPrimitive; +use rug::{float::Round, ops::Pow, Float, Rational}; + use std::str::FromStr; use crate::GroupElement; @@ -13,12 +15,11 @@ use crate::{ TallyOptimizationTable, }; use base64::{engine::general_purpose, Engine as _}; - -use num::FromPrimitive; use num::Rational32; +use rug::Integer; + use tracing::info; -use bigdecimal::ToPrimitive; use cryptoxide::blake2b::Blake2b; use cryptoxide::digest::Digest; @@ -170,42 +171,37 @@ impl EncryptedTally { assert_eq!(ballot.fingerprint(), &self.fingerprint); const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; + const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); + let precision = u32::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); + let gamma = Rational32::from_f64(gamma).unwrap_or(Rational32::from_integer(1)); - let denom = isize::try_from(*gamma.denom()).unwrap(); + let denom = gamma.denom(); let numer = gamma.numer(); - // stake - let base = BigDecimal::from(weight); - - // m-th power, then take the n-th root: where x^(m/n) - // mth power equals x^m where m in the numerator - let mth_power = self.pow_fractional_exponent(&base, *numer); - - // nth root - let weight = root(denom, mth_power) - .unwrap_or(base.clone()) + let stake = Float::with_val(precision, weight); + // r = gamma in rational form + let exp_r = Rational::from((*numer, *denom)); + let exp_f = Float::with_val(precision, &exp_r); + let p = stake.clone().pow(&exp_f); + info!("{} to the power of {} is: {}", stake, exp_f, p); + + let weight = p + .to_integer_round(Round::Down) + .unwrap_or((Integer::from(weight), Ordering::Less)) + .0 .to_u64() .unwrap_or(weight); - info!("stake before {:?} stake after {:?}", base.to_u64(), weight); - for (ri, ci) in self.r.iter_mut().zip(ballot.vote().iter()) { *ri = &*ri + &(ci * weight); } self.max_stake += weight; } - fn pow_fractional_exponent(&self, x: &BigDecimal, n: i32) -> BigDecimal { - let (bigint, scale) = x.as_bigint_and_exponent(); - let new_scale = scale * n as i64; - - BigDecimal::new(bigint.pow(n as u32), new_scale) - } - /// Given a single committee member's `secret_key`, returns a partial decryption of /// the `EncryptedTally` pub fn partial_decrypt( From 5c45c45cf03633c56aa19502bc3722c6ace6f015 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 21 Jan 2025 12:58:24 +0000 Subject: [PATCH 13/22] feat(rug): fractional powers without roots --- src/audit/src/offline/bin/main.rs | 8 ++++++++ .../chain-impl-mockchain/src/vote/tally.rs | 12 +++++++----- src/chain-libs/chain-vote/src/tally.rs | 17 +++++++++++------ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/audit/src/offline/bin/main.rs b/src/audit/src/offline/bin/main.rs index a5824bf819..9e98b0a9df 100644 --- a/src/audit/src/offline/bin/main.rs +++ b/src/audit/src/offline/bin/main.rs @@ -40,6 +40,9 @@ pub struct Args { /// Gamma value for Quadratic scaling #[clap(short, long)] gamma: Option, + /// Rounding precision for arithmetic + #[clap(short, long)] + precision: Option, } fn main() -> Result<(), Box> { @@ -70,6 +73,11 @@ fn main() -> Result<(), Box> { std::env::set_var(GAMMA, gamma); } + if let Some(precision) = args.precision { + const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; + std::env::set_var(PRECISION, precision); + } + // Load and replay fund fragments from storage let storage_path = PathBuf::from(args.fragments); diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 5a313a5d49..208c02cd2f 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -191,12 +191,14 @@ impl TallyResult { let numer = gamma.numer(); let stake = Float::with_val(precision, weight.0); - // r = gamma in rational form - let exp_r = Rational::from((*numer, *denom)); - let exp_f = Float::with_val(precision, &exp_r); - let p = stake.clone().pow(&exp_f); - let weight = p + // rational = gamma in rational form i.e fraction + // 0.5 = 1/2 + let gamma = Float::with_val(precision, &Rational::from((*numer, *denom))); + + let stake_with_gamma_scaling = stake.clone().pow(&gamma); + + let weight = stake_with_gamma_scaling .to_integer_round(Round::Down) .unwrap_or((Integer::from(weight.0), Ordering::Less)) .0 diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index 16bd05cdff..c164e9eec3 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -183,13 +183,18 @@ impl EncryptedTally { let numer = gamma.numer(); let stake = Float::with_val(precision, weight); - // r = gamma in rational form - let exp_r = Rational::from((*numer, *denom)); - let exp_f = Float::with_val(precision, &exp_r); - let p = stake.clone().pow(&exp_f); - info!("{} to the power of {} is: {}", stake, exp_f, p); - let weight = p + // rational = gamma in rational form i.e fraction + // 0.5 = 1/2 + let gamma = Float::with_val(precision, &Rational::from((*numer, *denom))); + + let stake_with_gamma_scaling = stake.clone().pow(&gamma); + info!( + "{} to the power of {} is: {}", + stake, gamma, stake_with_gamma_scaling + ); + + let weight = stake_with_gamma_scaling .to_integer_round(Round::Down) .unwrap_or((Integer::from(weight), Ordering::Less)) .0 From 491490ca84c759e0dbd473df8c8156d7cd6e9fcb Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 21 Jan 2025 13:23:46 +0000 Subject: [PATCH 14/22] feat(rug): fractional powers without roots --- Cargo.lock | 1 - .../chain-impl-mockchain/src/vote/tally.rs | 6 +++++- src/chain-libs/chain-vote/Cargo.toml | 2 +- src/chain-libs/chain-vote/src/tally.rs | 12 +++++------- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6dea45908..baa9220a5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1319,7 +1319,6 @@ dependencies = [ "rug", "smoke", "thiserror", - "tracing", ] [[package]] diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 208c02cd2f..5cc80bc4d8 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -181,7 +181,11 @@ impl TallyResult { const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. - let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); + let mut gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); + // Gamma must be between 0 and 1, anything else is treated as bad input; defaulting gamma to 1. + if gamma < 0.0 || gamma > 1.0 { + gamma = 1.0; + } let precision = u32::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); diff --git a/src/chain-libs/chain-vote/Cargo.toml b/src/chain-libs/chain-vote/Cargo.toml index 776320fd38..aa5dc95200 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -16,7 +16,7 @@ const_format = "0.2" base64 = "0.21.0" rug = "1.26.1" num = "0.4.3" -tracing = "*" + [dev-dependencies] diff --git a/src/chain-libs/chain-vote/src/tally.rs b/src/chain-libs/chain-vote/src/tally.rs index c164e9eec3..020347f8ec 100644 --- a/src/chain-libs/chain-vote/src/tally.rs +++ b/src/chain-libs/chain-vote/src/tally.rs @@ -18,8 +18,6 @@ use base64::{engine::general_purpose, Engine as _}; use num::Rational32; use rug::Integer; -use tracing::info; - use cryptoxide::blake2b::Blake2b; use cryptoxide::digest::Digest; @@ -174,7 +172,11 @@ impl EncryptedTally { const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. - let gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); + let mut gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); + // Gamma must be between 0 and 1, anything else is treated as bad input; defaulting gamma to 1. + if gamma < 0.0 || gamma > 1.0 { + gamma = 1.0; + } let precision = u32::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); @@ -189,10 +191,6 @@ impl EncryptedTally { let gamma = Float::with_val(precision, &Rational::from((*numer, *denom))); let stake_with_gamma_scaling = stake.clone().pow(&gamma); - info!( - "{} to the power of {} is: {}", - stake, gamma, stake_with_gamma_scaling - ); let weight = stake_with_gamma_scaling .to_integer_round(Round::Down) From cbe894bc6a43767b63fa232f439e87626da50a33 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 21 Jan 2025 13:25:29 +0000 Subject: [PATCH 15/22] feat(rug): fractional powers without roots --- src/audit/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audit/README.md b/src/audit/README.md index 88db9b35d3..c350463bbd 100644 --- a/src/audit/README.md +++ b/src/audit/README.md @@ -40,9 +40,10 @@ FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 BLOCK0=/tmp/fund9-leader-1/artifacts/block0.bin FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 GAMMA=0.5 +PRECISION=5 -./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA +./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA --precision $PRECISION ``` This will create three files: From 01367e367d4ab19c2bab62479edb7be088db946d Mon Sep 17 00:00:00 2001 From: cong-or Date: Thu, 6 Feb 2025 14:30:11 +0000 Subject: [PATCH 16/22] refactor(nearest): rounding strategy --- src/chain-libs/chain-impl-mockchain/src/vote/tally.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 5cc80bc4d8..6e4d9f5b26 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -203,7 +203,7 @@ impl TallyResult { let stake_with_gamma_scaling = stake.clone().pow(&gamma); let weight = stake_with_gamma_scaling - .to_integer_round(Round::Down) + .to_integer_round(Round::Nearest) .unwrap_or((Integer::from(weight.0), Ordering::Less)) .0 .to_u64() From 82ec649c3ae9d848b76a85a8e989a90acd2f3742 Mon Sep 17 00:00:00 2001 From: cong-or Date: Fri, 14 Feb 2025 10:59:37 +0000 Subject: [PATCH 17/22] feat(apply gamma fractional exponent to vp ): quadratic voting --- .../scripts/python/proposers_rewards.py | 219 ++++++++++++------ 1 file changed, 153 insertions(+), 66 deletions(-) diff --git a/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py b/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py index a3dacc0d2e..778d729149 100755 --- a/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py +++ b/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py @@ -1,5 +1,16 @@ # coding: utf-8 -from typing import Dict, Optional, List, Tuple, Generator, TextIO, Union, Any, Set, Mapping +from typing import ( + Dict, + Optional, + List, + Tuple, + Generator, + TextIO, + Union, + Any, + Set, + Mapping, +) import sys import asyncio @@ -21,6 +32,7 @@ from rich import print from asyncio import run as aiorun from copy import deepcopy +from fractions import Fraction # VIT servicing station models @@ -34,6 +46,7 @@ NOT_FUNDED_APPROVAL_THRESHOLD = "Not Funded - Approval Threshold" LOVELACE_FACTOR = 1000000 + class Challenge(pydantic.BaseModel): id: int challenge_type: str @@ -43,6 +56,7 @@ class Challenge(pydantic.BaseModel): fund_id: int challenge_url: str + class Proposal(pydantic.BaseModel): internal_id: int proposal_id: str @@ -57,12 +71,13 @@ class Proposal(pydantic.BaseModel): challenge_id: int challenge_type: str challenge: Challenge - + @pydantic.computed_field @property def ideascale_url(self) -> str: return f"https://cardano.ideascale.com/c/idea/{self.proposal_id}" + class Author(pydantic.BaseModel): """Represents an author.""" @@ -71,8 +86,10 @@ class Author(pydantic.BaseModel): email: str user_name: str = pydantic.Field(alias="userName") + # Ideascale models + class IdeascaleProposal(pydantic.BaseModel): id: int title: str @@ -91,8 +108,10 @@ def assign_authors_if_any(cls, values): values["authors"] = authors return values + # Jormungandr models + class Options(pydantic.BaseModel): start: int end: int @@ -170,6 +189,7 @@ class Result(pydantic.BaseModel): votes_cast: int vote_result: Optional[int] = None + class Winner(pydantic.BaseModel): internal_id: int proposal_id: str @@ -187,16 +207,18 @@ def dict(self, **kwargs): # Override std dict to list all authors in different columns output = super().dict(**kwargs) _output = {} - for k,v in output.items(): - if k == 'authors': + for k, v in output.items(): + if k == "authors": for idx, author in enumerate(v): - _output[f"{k}_{idx}"] = author['email'] + _output[f"{k}_{idx}"] = author["email"] else: _output[k] = v return _output + # Ideascale interface + class JsonHttpClient: """HTTP Client for JSON APIs.""" @@ -222,6 +244,7 @@ async def get(self, path: str, headers: Mapping[str, str] = {}): else: raise GetFailed(r.status, r.reason, content) + class GetFailed(Exception): """Raised when a request fails.""" @@ -229,10 +252,13 @@ def __init__(self, status, reason, content): """Initialize a new instance of GetFailed.""" super().__init__(f"{status} {reason}\n{content})") + class IdeascaleImporter: """Interface with IdeaScale API.""" - def __init__(self, api_key: str, api_url: str = "https://temp-cardano-sandbox.ideascale.com"): + def __init__( + self, api_key: str, api_url: str = "https://temp-cardano-sandbox.ideascale.com" + ): """Initialize entities.""" self.api_key = api_key self.api_url = api_url @@ -240,7 +266,7 @@ def __init__(self, api_key: str, api_url: str = "https://temp-cardano-sandbox.id self.N_WORKERS = 3 self.proposals: List[IdeascaleProposal] = [] - + async def import_proposals(self, stage_ids: List[int], page_size: int = 50): """Get all ideas from the stage with the given id. @@ -264,7 +290,9 @@ async def worker(d: WorkerData, stage_id: int): p = d.page d.page += 1 - res = await self._get(f"/a/rest/v1/stages/{stage_id}/ideas/{p}/{page_size}") + res = await self._get( + f"/a/rest/v1/stages/{stage_id}/ideas/{p}/{page_size}" + ) res_proposals: List[IdeascaleProposal] = [] for i in res: @@ -275,20 +303,24 @@ async def worker(d: WorkerData, stage_id: int): if len(res_proposals) < page_size: d.done = True + d = {} - for stage_id in stage_ids: + for stage_id in stage_ids: print(f"Start proposal requests for stage: {stage_id}") d = WorkerData(stage_id) - worker_tasks = [asyncio.create_task(worker(d, stage_id)) for _ in range(self.N_WORKERS)] + worker_tasks = [ + asyncio.create_task(worker(d, stage_id)) for _ in range(self.N_WORKERS) + ] for task in worker_tasks: await task self.proposals.extend(d.proposals) - + async def _get(self, path: str): """Execute a GET request.""" headers = {"api_token": self.api_key} return await self.inner.get(path, headers) + # File loaders @@ -297,9 +329,11 @@ def load_json_from_file(file_path: str) -> Dict: return json.load(f) -def get_proposals_from_file(proposals_file_path: str, challenges: Dict[int, Challenge]) -> Dict[str, Proposal]: +def get_proposals_from_file( + proposals_file_path: str, challenges: Dict[int, Challenge] +) -> Dict[str, Proposal]: proposals: Generator[Proposal, None, None] = ( - Proposal(**proposal_data, challenge=challenges[proposal_data['challenge_id']]) + Proposal(**proposal_data, challenge=challenges[proposal_data["challenge_id"]]) for proposal_data in load_json_from_file(proposals_file_path) ) proposals_dict = {proposal.chain_proposal_id: proposal for proposal in proposals} @@ -411,8 +445,7 @@ def load_block0_data(block0_path: str) -> Dict[str, Any]: # Checkers -class SanityException(Exception): - ... +class SanityException(Exception): ... def sanity_check_data( @@ -436,10 +469,12 @@ def sanity_check_data( # Analyse and compute needed data + class WinnerSelectionRule(enum.Enum): YES_ONLY: str = "yes_only" YES_NO_DIFF: str = "yes_no_diff" + def extract_choices_votes(proposal: Proposal, voteplan_proposal: ProposalStatus): yes_index = int(proposal.chain_vote_options["yes"]) no_index = int(proposal.chain_vote_options["no"]) @@ -454,16 +489,22 @@ def calc_approval_threshold( voteplan_proposal: ProposalStatus, total_stake_threshold: float, winner_selection_rule: WinnerSelectionRule, - relative_threshold: float + relative_threshold: float, ) -> Tuple[int, bool]: - yes_result, second_choice_result = extract_choices_votes(proposal, voteplan_proposal) - pass_relative_threshold = ((yes_result - second_choice_result) / (yes_result + second_choice_result)) >= float(relative_threshold) + yes_result, second_choice_result = extract_choices_votes( + proposal, voteplan_proposal + ) + pass_relative_threshold = ( + (yes_result - second_choice_result) / (yes_result + second_choice_result) + ) >= float(relative_threshold) if winner_selection_rule == WinnerSelectionRule.YES_ONLY: vote_result = yes_result pass_total_threshold = yes_result >= float(total_stake_threshold) elif winner_selection_rule == WinnerSelectionRule.YES_NO_DIFF: vote_result = yes_result - second_choice_result - pass_total_threshold = (yes_result + second_choice_result) >= float(total_stake_threshold) + pass_total_threshold = (yes_result + second_choice_result) >= float( + total_stake_threshold + ) threshold_rules = pass_total_threshold and pass_relative_threshold return vote_result, threshold_rules @@ -473,7 +514,7 @@ def calc_vote_value_and_threshold_success( voteplan_proposals: Dict[str, ProposalStatus], total_stake_threshold: float, winner_selection_rule: WinnerSelectionRule, - relative_threshold: float + relative_threshold: float, ) -> Dict[str, Tuple[int, bool]]: full_ids = set(proposals.keys()) result = { @@ -482,7 +523,7 @@ def calc_vote_value_and_threshold_success( voteplan_proposals[proposal_id], total_stake_threshold, winner_selection_rule, - relative_threshold + relative_threshold, ) for proposal_id in full_ids } @@ -495,10 +536,14 @@ def calc_results( funds: float, total_stake_threshold: float, winner_selection_rule: WinnerSelectionRule, - relative_threshold: float + relative_threshold: float, ) -> List[Result]: success_results = calc_vote_value_and_threshold_success( - proposals, voteplan_proposals, total_stake_threshold, winner_selection_rule, relative_threshold + proposals, + voteplan_proposals, + total_stake_threshold, + winner_selection_rule, + relative_threshold, ) sorted_ids = sorted( success_results.keys(), key=lambda x: success_results[x][0], reverse=True @@ -509,7 +554,9 @@ def calc_results( proposal = proposals[proposal_id] voteplan_proposal = voteplan_proposals[proposal_id] vote_result, threshold_success = success_results[proposal_id] - yes_result, second_choice_result = extract_choices_votes(proposal, voteplan_proposal) + yes_result, second_choice_result = extract_choices_votes( + proposal, voteplan_proposal + ) funded = all( (threshold_success, depletion > 0, depletion >= proposal.proposal_funds) ) @@ -541,7 +588,7 @@ def calc_results( ideascale_url=proposal.ideascale_url, challenge_id=proposal.challenge.id, challenge_title=proposal.challenge.title, - votes_cast=voteplan_proposal.votes_cast + votes_cast=voteplan_proposal.votes_cast, ) if winner_selection_rule == WinnerSelectionRule.YES_ONLY: @@ -595,59 +642,72 @@ def calculate_total_stake_from_block0_configuration( if fund["address"] not in [key for key in committee_keys] ) + def extract_relevant_choice(x, winner_selection_rule): if winner_selection_rule == WinnerSelectionRule.YES_ONLY: return x.yes elif winner_selection_rule == WinnerSelectionRule.YES_NO_DIFF: return x.vote_result -def calc_leftovers(results, remaining_funds, excluded_categories, winner_selection_rule): - leftovers_candidates = sorted([ - result - for result in deepcopy(results) - if ( - result.status == NOT_FUNDED and - result.meets_threshold == YES and - result.challenge_id not in excluded_categories - ) - ], key=lambda x: extract_relevant_choice(x, winner_selection_rule), reverse=True) + +def calc_leftovers( + results, remaining_funds, excluded_categories, winner_selection_rule +): + leftovers_candidates = sorted( + [ + result + for result in deepcopy(results) + if ( + result.status == NOT_FUNDED + and result.meets_threshold == YES + and result.challenge_id not in excluded_categories + ) + ], + key=lambda x: extract_relevant_choice(x, winner_selection_rule), + reverse=True, + ) depletion = remaining_funds for candidate in leftovers_candidates: funded = depletion >= candidate.requested_funds - not_funded_reason = ( - "" - if funded - else NOT_FUNDED_OVER_BUDGET - ) + not_funded_reason = "" if funded else NOT_FUNDED_OVER_BUDGET if funded: depletion -= candidate.requested_funds candidate.status = FUNDED if funded else NOT_FUNDED candidate.fund_depletion = depletion candidate.not_funded_reason = not_funded_reason - + return leftovers_candidates, depletion + def pick_milestones_qty(winner, limits, qty): idx = next((i for i, l in enumerate(limits) if winner.requested_funds > l), None) return qty[idx] -def generate_winners(results, fund_prefix, milestones_limit, milestones_qty, _ideascale_proposals): + +def generate_winners( + results, fund_prefix, milestones_limit, milestones_qty, _ideascale_proposals +): ideascale_proposals = {p.id: p for p in _ideascale_proposals} winners = [] - _winners = sorted([r for r in results if r.status == FUNDED], key=lambda r: r.proposal.lower()) + _winners = sorted( + [r for r in results if r.status == FUNDED], key=lambda r: r.proposal.lower() + ) for idx, _winner in enumerate(_winners): winner = Winner( **_winner.dict(), proposal_title=_winner.proposal, project_id=fund_prefix + idx, - milestone_qty=pick_milestones_qty(_winner, milestones_limit, milestones_qty) + milestone_qty=pick_milestones_qty( + _winner, milestones_limit, milestones_qty + ), ) if winner.internal_id in ideascale_proposals.keys(): winner.authors = ideascale_proposals[winner.internal_id].authors winners.append(winner) return winners + # Output results @@ -666,24 +726,27 @@ def output_json(results: List[Result], f: TextIO): # CLI + class OutputFormat(enum.Enum): CSV: str = "csv" JSON: str = "json" -def build_path_for_challenge(file_path: str, challenge_name: str, output_format: OutputFormat) -> str: +def build_path_for_challenge( + file_path: str, challenge_name: str, output_format: OutputFormat +) -> str: path, suffix = os.path.splitext(file_path) - suffix = 'json' if (output_format == OutputFormat.JSON) else 'csv' + suffix = "json" if (output_format == OutputFormat.JSON) else "csv" return f"{path}_{challenge_name}.{suffix}" -def save_results(output_path: str, title: str, output_format: OutputFormat, results: List[Results]): +def save_results( + output_path: str, title: str, output_format: OutputFormat, results: List[Results] +): challenge_output_file_path = build_path_for_challenge( output_path, - re.sub( - r"(?u)[^-\w.]", "", title.replace(" ", "_").replace(":", "_") - ), - output_format + re.sub(r"(?u)[^-\w.]", "", title.replace(" ", "_").replace(":", "_")), + output_format, ) with open( @@ -698,16 +761,17 @@ def save_results(output_path: str, title: str, output_format: OutputFormat, resu def calculate_rewards( output_file: str = typer.Option(...), block0_path: str = typer.Option(...), + gamma: str = typer.Option(...), # quadratic voting total_stake_threshold: float = typer.Option( 0.01, help=""" This value indicates the minimum percentage of voting needed by projects to be eligible for funding. Voting choices considered for this depends by the winner rule. - """ + """, ), relative_threshold: float = typer.Option( 0, - help="This value indicates the relative threshold between Yes/No votes needed by projects to be eligible for funding." + help="This value indicates the relative threshold between Yes/No votes needed by projects to be eligible for funding.", ), output_format: OutputFormat = typer.Option("csv", help="Output format"), winner_selection_rule: WinnerSelectionRule = typer.Option( @@ -717,7 +781,7 @@ def calculate_rewards( Possible choices are: - `yes_only` Fuzzy threshold voting: only YES votes are considered for ranking. Only YES votes are considered to calculate thresholds. - `yes_no_diff` Fuzzy threshold voting: YES/NO difference is considered for ranking. Sum of YES/NO is considered to calculate thresholds. - """ + """, ), proposals_path: Optional[str] = typer.Option(None), excluded_proposals_path: Optional[str] = typer.Option(None), @@ -725,21 +789,26 @@ def calculate_rewards( challenges_path: Optional[str] = typer.Option(None), vit_station_url: str = typer.Option("https://servicing-station.vit.iohk.io"), committee_keys_path: Optional[str] = typer.Option(None), - fund_prefix: int = typer.Option(1100001, help="This number will be used to assign progressively project ids to winners."), + fund_prefix: int = typer.Option( + 1100001, + help="This number will be used to assign progressively project ids to winners.", + ), leftovers_excluded_categories: List[int] = typer.Option( [], - help="List of categories IDs that are not considered in leftovers winners calculation." + help="List of categories IDs that are not considered in leftovers winners calculation.", ), milestones_limit: List[int] = typer.Option( [0, 75000, 150000, 300000], - help="Map of budgets to assign number of milestones. Lenght must coincide with `milestones_qty` parameter." + help="Map of budgets to assign number of milestones. Lenght must coincide with `milestones_qty` parameter.", ), milestones_qty: List[int] = typer.Option( [3, 4, 5, 6], - help="Map of milestones qty to assign number of milestones. Lenght must coincide with `milestones_limit` parameter." + help="Map of milestones qty to assign number of milestones. Lenght must coincide with `milestones_limit` parameter.", ), ideascale_api_key: str = typer.Option(None, help="IdeaScale API key"), - ideascale_api_url: str = typer.Option("https://temp-cardano-sandbox.ideascale.com", help="IdeaScale API url"), + ideascale_api_url: str = typer.Option( + "https://temp-cardano-sandbox.ideascale.com", help="IdeaScale API url" + ), stage_ids: List[int] = typer.Option([], help="Stage IDs"), ): """ @@ -788,7 +857,13 @@ def calculate_rewards( block0_config, committee_keys ) # minimum amount of stake needed for a proposal to be accepted + print(f"\nTotal stake before gamma applied {total_stake}") + print(f"Gamma as fractional exponent: {gamma}") + + expo = Fraction(gamma) # gamma as fractional exponent + total_stake = Fraction(total_stake) ** expo total_stake_approval_threshold = float(total_stake_threshold) * float(total_stake) + print(f"Total stake after gamma applied {total_stake}\n") total_remaining_funds = 0 @@ -804,19 +879,24 @@ def calculate_rewards( challenge.rewards_total, total_stake_approval_threshold, winner_selection_rule, - relative_threshold + relative_threshold, ) total_remaining_funds += remaining_funds all_results += results save_results(output_file, challenge.title, output_format, results) - - leftover_results, final_remaining_funds = calc_leftovers(all_results, total_remaining_funds, leftovers_excluded_categories, winner_selection_rule) - save_results(output_file, 'leftovers', output_format, leftover_results) + + leftover_results, final_remaining_funds = calc_leftovers( + all_results, + total_remaining_funds, + leftovers_excluded_categories, + winner_selection_rule, + ) + save_results(output_file, "leftovers", output_format, leftover_results) ideascale_proposals = [] - if (ideascale_api_key): + if ideascale_api_key: ideascale = IdeascaleImporter(ideascale_api_key, ideascale_api_url) async def _get_proposals(): @@ -827,8 +907,14 @@ async def _get_proposals(): milestones_limit.reverse() milestones_qty.reverse() - winners = generate_winners(all_results + leftover_results, fund_prefix, milestones_limit, milestones_qty, ideascale_proposals) - save_results(output_file, 'winners', output_format, winners) + winners = generate_winners( + all_results + leftover_results, + fund_prefix, + milestones_limit, + milestones_qty, + ideascale_proposals, + ) + save_results(output_file, "winners", output_format, winners) print("[bold green]Winners generated.[/bold green]") print(f"Total Stake: {total_stake}") @@ -837,5 +923,6 @@ async def _get_proposals(): print(f"Unallocated budget: {final_remaining_funds}") print(f"Funded projects: {len(winners)}") + if __name__ == "__main__": typer.run(calculate_rewards) From 981d20968c61f11475b15d1b0f9b3838db8fe1d5 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 23 Feb 2025 20:36:54 +0000 Subject: [PATCH 18/22] revert(scaling for encrypted tally only): tally fragment --- .../chain-impl-mockchain/src/vote/tally.rs | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs index 6e4d9f5b26..7d6a1c2f24 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -3,18 +3,7 @@ use crate::{ value::Value, vote::{Choice, Options}, }; - use chain_vote::EncryptedTally; -use core::cmp::Ordering; -use num::FromPrimitive; - -use num::Rational32; -use rug::Integer; -use rug::{float::Round, ops::Pow, Float, Rational}; - -use std::str::FromStr; - -use std::env; use std::fmt; use thiserror::Error; @@ -177,39 +166,7 @@ impl TallyResult { } else { let index = choice.as_byte() as usize; - const GAMMA: &str = "QUADRATIC_VOTING_GAMMA"; - const PRECISION: &str = "QUADRATIC_VOTING_PRECISION"; - - // Apply quadratic scaling if gamma value specified in env var. Else gamma is 1 and has no effect. - let mut gamma = f64::from_str(&env::var(GAMMA).unwrap_or(1.0.to_string())).unwrap(); - // Gamma must be between 0 and 1, anything else is treated as bad input; defaulting gamma to 1. - if gamma < 0.0 || gamma > 1.0 { - gamma = 1.0; - } - - let precision = - u32::from_str(&env::var(PRECISION).unwrap_or(1.to_string())).unwrap_or(1); - - let gamma = Rational32::from_f64(gamma).unwrap_or(Rational32::from_integer(1)); - let denom = gamma.denom(); - let numer = gamma.numer(); - - let stake = Float::with_val(precision, weight.0); - - // rational = gamma in rational form i.e fraction - // 0.5 = 1/2 - let gamma = Float::with_val(precision, &Rational::from((*numer, *denom))); - - let stake_with_gamma_scaling = stake.clone().pow(&gamma); - - let weight = stake_with_gamma_scaling - .to_integer_round(Round::Nearest) - .unwrap_or((Integer::from(weight.0), Ordering::Less)) - .0 - .to_u64() - .unwrap_or(weight.0); - - self.results[index] = self.results[index].saturating_add(Weight(weight)); + self.results[index] = self.results[index].saturating_add(weight); Ok(()) } From f527c11930231cf05c95825bae404b45ce03bedf Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 14 Jul 2025 10:29:26 +0100 Subject: [PATCH 19/22] Update README.md --- src/audit/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audit/README.md b/src/audit/README.md index c350463bbd..0b77523edd 100644 --- a/src/audit/README.md +++ b/src/audit/README.md @@ -40,7 +40,7 @@ FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 BLOCK0=/tmp/fund9-leader-1/artifacts/block0.bin FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 GAMMA=0.5 -PRECISION=5 +PRECISION=100 ./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA --precision $PRECISION @@ -54,4 +54,4 @@ This will create three files: [*See here for next steps of audit process*](src/tally/README.md) ### Find my vote -[*See here for instructions on how to find your voting history*](src/find/README.md) \ No newline at end of file +[*See here for instructions on how to find your voting history*](src/find/README.md) From 6d3aecd9b00cb164515f2ce0706e1a10d399e3c8 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 14 Jul 2025 15:14:16 +0100 Subject: [PATCH 20/22] Update README.md --- src/audit/README.md | 92 +++++++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/src/audit/README.md b/src/audit/README.md index 0b77523edd..939c100216 100644 --- a/src/audit/README.md +++ b/src/audit/README.md @@ -1,57 +1,93 @@ -# Audit Tooling: +# Audit Tooling -## Offline audit +Independent verification tools for Catalyst voting results. These tools allow anyone to audit and verify the integrity of voting outcomes without needing to trust centralized authorities. -### Download Fund State -Download historical fund state from [*here*](https://github.com/input-output-hk/catalyst-core) in order to replay and audit the voting event. +## Quick Start -The official published results can be found in this file in the form of **activevoteplans.json**. +### 1. Download Fund State -**activevoteplans.json** = FINAL RESULTS. +Download historical fund state from [here](https://github.com/input-output-hk/catalyst-core) to replay and audit the voting event. -If you would like to re-generate **activevoteplans.json** yourself, via a live node and historical fragments - [*see here for instructions*](./balance/README.md) +The official published results are found in **activevoteplans.json**. -If not, you can begin the audit with the following steps. +**activevoteplans.json** = FINAL RESULTS. -*Example usage:* +### 2. Build the Audit Tool -``` +```bash cargo build --release -p audit -``` +``` -*Cross reference offline tallies with published catalyst tallies.* +### 3. Run Offline Audit -```bash +#### Option A: Cross-reference with Official Results + +Compare your audit results with published Catalyst tallies: +```bash OFFICIAL_RESULTS=/tmp/activevoteplans.json BLOCK0=/tmp/fund9-leader-1/artifacts/block0.bin FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 ./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --official-results $OFFICIAL_RESULTS - ``` - -*Generate encrypted tally with gamma scaling* - +#### Option B: Generate Encrypted Tally with Gamma Scaling ```bash - BLOCK0=/tmp/fund9-leader-1/artifacts/block0.bin FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 GAMMA=0.5 -PRECISION=100 - +PRECISION=5 -./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA --precision $PRECISION +./target/release/offline --fragments $FRAGMENTS_STORAGE --block0 $BLOCK0 --gamma $GAMMA --precision $PRECISION ``` -This will create three files: -- *ledger_after_tally.json* **(decrypted ledger state after tally)** *should match official results!* -- *ledger_before_tally.json* **(encrypted ledger state before tally)** -- *decryption_shares.json* **(decryption shares for each proposal)** +### 4. Generated Files + +The offline audit creates three critical files: -[*See here for next steps of audit process*](src/tally/README.md) +- **ledger_after_tally.json** - Decrypted ledger state after tally *(should match official results!)* +- **ledger_before_tally.json** - Encrypted ledger state before tally +- **decryption_shares.json** - Decryption shares for each proposal -### Find my vote -[*See here for instructions on how to find your voting history*](src/find/README.md) +--- + +## ➡️ **NEXT STEP: Verify Individual Proposals** + +**🔍 [Complete the audit process - Verify specific proposal results →](src/tally/README.md)** + +After generating the audit files above, the next crucial step is to **independently verify individual proposal results** using cryptographic proof. This step: + +- ✅ **Validates each proposal's decryption** was performed correctly +- ✅ **Provides mathematical proof** of result integrity +- ✅ **Requires no trust** in election officials or committee members +- ✅ **Can be run by anyone** using publicly available data + +**Why this step is essential:** +- The offline audit gives you the raw encrypted data +- The tally verification proves the decryption was legitimate +- Together, they provide complete end-to-end verification + +--- + +## Additional Tools + +### Find Your Vote +[See instructions on how to find your voting history →](src/find/README.md) + +### Regenerate Results from Live Node +If you want to regenerate **activevoteplans.json** yourself via a live node and historical fragments: +[See instructions here →](./balance/README.md) + +## Overview + +This audit tooling provides: + +1. **Offline Verification** - Replay voting events from blockchain data +2. **Cryptographic Proof** - Mathematically verify decryption integrity +3. **Individual Vote Tracking** - Find and verify your specific votes +4. **Complete Transparency** - No black boxes or trusted components + +The audit process is designed to be completely independent and reproducible, ensuring that anyone can verify Catalyst voting results without needing to trust any centralized authority. +``` From a5d4c599de7fe506269c144282223ad55f193d15 Mon Sep 17 00:00:00 2001 From: cong-or Date: Mon, 14 Jul 2025 15:17:07 +0100 Subject: [PATCH 21/22] Update README.md --- src/audit/src/tally/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/audit/src/tally/README.md b/src/audit/src/tally/README.md index 6f13ede580..c3a8707036 100644 --- a/src/audit/src/tally/README.md +++ b/src/audit/src/tally/README.md @@ -42,6 +42,34 @@ SHARES_ALICE='WDDMb68A6JCVR5UdhDtl7QYrQHSMOFqg44lHcmtB/Q3IfSoqusq+obtC/JJOtDYWad #### Public use: Validate results #### `decrypt_tally_from_shares(pub_keys, encrypted_tally, decrypt_shares) -> tallyResultPlaintext` +**Public Verification: Independently Validate Voting Results** +This function allows anyone to independently verify that encrypted voting results have been correctly decrypted without requiring access to any private keys or secret information. It uses publicly available data to cryptographically prove the integrity of the decryption process. +**What it does:** +- Takes encrypted tallies, decryption shares, and public keys as input +- Validates that the decryption shares are legitimate and correspond to the correct public keys +- Combines the shares to reproduce the plaintext voting results +- Provides cryptographic proof that the decryption was performed correctly + +**Why it's important:** +- **Transparency**: Anyone can verify results using only publicly available information +- **Trust**: No need to trust election officials or committee members +- **Auditability**: Results can be independently verified by multiple parties +- **Security**: The verification process is cryptographically sound and tamper-proof + +**Who can use it:** +- Voters wanting to verify their voting event results +- Independent auditors and researchers +- Media organizations and watchdog groups +- Anyone interested in election integrity + +**What makes it "public":** +- No secret keys or private information required +- All necessary data is publicly available after the election +- The verification process is transparent and reproducible +- Results are deterministic - anyone running the same verification will get identical results + +This verification method ensures that the democratic process remains transparent and that election results can be independently validated by the community. + ```bash SHARES_ALICE='0DDHBs4TnabGQjvhIiQP2S53mTThxqilR+ogY8MpIRV6PdDb+5NWVZYQvAQQZUIe8e/rzeZjGX5QkCpd84b/CyvrivhD4u7zvhccz6zSgOfLx3EVjY9PXBXOhPYkrUoE0DDHBs4TnabGQjvhIiQP2S53mTThxqilR+ogY8MpIRXOp9W5weElw0uZSyz4oCkRMKiRv2L1kfrOuNLOXtobBnWorfj2FLdBb2jZ5Cb0tqYvMKj+WLTTs2hrohjlSC0D' From 92751a234be7333d222f82df1d78017b5e5b6638 Mon Sep 17 00:00:00 2001 From: Lucio Baglione Date: Mon, 6 Oct 2025 14:56:27 +0200 Subject: [PATCH 22/22] feat: Apply gamma to single voter to calculate total stake threshold | NPG-000 (#768) Apply gamma to single voter to calculate total stake threshold. --- .../scripts/python/proposers_rewards.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py b/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py index 778d729149..4c9fcb3c21 100755 --- a/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py +++ b/src/catalyst-toolbox/catalyst-toolbox/scripts/python/proposers_rewards.py @@ -631,13 +631,13 @@ def filter_excluded_proposals( def calculate_total_stake_from_block0_configuration( - block0_config: Dict[str, Dict], committee_keys: List[str] + block0_config: Dict[str, Dict], committee_keys: List[str], gamma: Fraction ): funds = ( initial["fund"] for initial in block0_config["initial"] if "fund" in initial ) return sum( - fund["value"] + fund["value"] ** gamma for fund in itertools.chain.from_iterable(funds) if fund["address"] not in [key for key in committee_keys] ) @@ -761,7 +761,12 @@ def save_results( def calculate_rewards( output_file: str = typer.Option(...), block0_path: str = typer.Option(...), - gamma: str = typer.Option(...), # quadratic voting + gamma: str = typer.Option( + "1", + help=""" + The gamma value applied for the calculation of the total stake threshold. It is applied to every single voting value before the sum is executed. + """ + ), total_stake_threshold: float = typer.Option( 0.01, help=""" @@ -770,7 +775,7 @@ def calculate_rewards( """, ), relative_threshold: float = typer.Option( - 0, + -1, help="This value indicates the relative threshold between Yes/No votes needed by projects to be eligible for funding.", ), output_format: OutputFormat = typer.Option("csv", help="Output format"), @@ -853,15 +858,18 @@ def calculate_rewards( committee_keys = ( load_json_from_file(committee_keys_path) if committee_keys_path else [] ) - total_stake = calculate_total_stake_from_block0_configuration( - block0_config, committee_keys - ) + # minimum amount of stake needed for a proposal to be accepted - print(f"\nTotal stake before gamma applied {total_stake}") + _total_stake = calculate_total_stake_from_block0_configuration( + block0_config, committee_keys, Fraction(1) + ) + print(f"\nTotal stake before gamma applied {_total_stake}") print(f"Gamma as fractional exponent: {gamma}") - expo = Fraction(gamma) # gamma as fractional exponent - total_stake = Fraction(total_stake) ** expo + total_stake = calculate_total_stake_from_block0_configuration( + block0_config, committee_keys, Fraction(gamma) + ) + total_stake_approval_threshold = float(total_stake_threshold) * float(total_stake) print(f"Total stake after gamma applied {total_stake}\n")