diff --git a/Cargo.lock b/Cargo.lock index 8f1eec6842..353917502b 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", @@ -1297,6 +1311,7 @@ name = "chain-vote" version = "0.1.0" dependencies = [ "base64 0.21.5", + "bigdecimal", "cfg-if 1.0.0", "chain-core", "chain-crypto", @@ -7865,9 +7880,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", @@ -7889,9 +7904,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", @@ -7900,9 +7915,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/README.md b/src/audit/README.md index f7f3629579..8746e9421c 100644 --- a/src/audit/README.md +++ b/src/audit/README.md @@ -31,6 +31,21 @@ FRAGMENTS_STORAGE=/tmp/fund9-leader-1/persist/leader-1 ``` + +*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.1 +PRECISION=5 + +./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)** diff --git a/src/audit/src/offline/bin/main.rs b/src/audit/src/offline/bin/main.rs index 39402585d7..9e98b0a9df 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, + /// Gamma value for Quadratic scaling + #[clap(short, long)] + gamma: Option, + /// Rounding precision for arithmetic + #[clap(short, long)] + precision: Option, } fn main() -> Result<(), Box> { @@ -61,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); diff --git a/src/chain-libs/chain-impl-mockchain/Cargo.toml b/src/chain-libs/chain-impl-mockchain/Cargo.toml index 83f4b2b74d..b5acfb01b5 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 +bigdecimal = "0.4.7" + [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..62f1bbab56 100644 --- a/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs +++ b/src/chain-libs/chain-impl-mockchain/src/vote/tally.rs @@ -3,7 +3,14 @@ use crate::{ value::Value, vote::{Choice, Options}, }; +use bigdecimal::BigDecimal; +use bigdecimal::ToPrimitive; use chain_vote::EncryptedTally; +use std::num::NonZeroI64; + +use std::str::FromStr; + +use std::env; use std::fmt; use thiserror::Error; @@ -166,7 +173,28 @@ impl TallyResult { } else { let index = choice.as_byte() as usize; - self.results[index] = self.results[index].saturating_add(weight); + 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); + + 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) + .round(precision) + .to_u64() + .unwrap_or(weight.0); + + 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 6d31e20b84..1e2f57be62 100644 --- a/src/chain-libs/chain-vote/Cargo.toml +++ b/src/chain-libs/chain-vote/Cargo.toml @@ -14,6 +14,8 @@ thiserror = "1.0" cryptoxide = "^0.4.2" const_format = "0.2" base64 = "0.21.0" +bigdecimal = "0.4.7" + [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..666b9a7c1c 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,8 +12,14 @@ 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 std::env; + use rand_core::{CryptoRng, RngCore}; /// Secret key for opening vote @@ -155,6 +164,23 @@ 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); + + 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); + + 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); + for (ri, ci) in self.r.iter_mut().zip(ballot.vote().iter()) { *ri = &*ri + &(ci * weight); }