Skip to main content

forest/blocks/
election_proof.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::blocks::VRFProof;
5use crate::shim::clock::BLOCKS_PER_EPOCH;
6use crate::utils::encoding::blake2b_256;
7use fvm_ipld_encoding::tuple::*;
8use get_size2::GetSize;
9use num::{
10    BigInt, Integer,
11    bigint::{ParseBigIntError, Sign},
12};
13use std::sync::LazyLock;
14
15const PRECISION: u64 = 256;
16const MAX_WIN_COUNT: i64 = 3 * BLOCKS_PER_EPOCH as i64;
17
18fn parse(strings: &[&str]) -> Result<Vec<BigInt>, ParseBigIntError> {
19    strings
20        .iter()
21        .map(|s| {
22            let i: BigInt = s.parse()?;
23            // << 256 (Q.0 to Q.256), >> 128 to transform integer params to coefficients
24            Ok(i << (PRECISION - 128))
25        })
26        .collect()
27}
28
29static EXP_NUM_COEF: LazyLock<Vec<BigInt>> = LazyLock::new(|| {
30    parse(&[
31        "-648770010757830093818553637600",
32        "67469480939593786226847644286976",
33        "-3197587544499098424029388939001856",
34        "89244641121992890118377641805348864",
35        "-1579656163641440567800982336819953664",
36        "17685496037279256458459817590917169152",
37        "-115682590513835356866803355398940131328",
38        "340282366920938463463374607431768211456",
39    ])
40    .unwrap()
41});
42static EXP_DENO_COEF: LazyLock<Vec<BigInt>> = LazyLock::new(|| {
43    parse(&[
44        "1225524182432722209606361",
45        "114095592300906098243859450",
46        "5665570424063336070530214243",
47        "194450132448609991765137938448",
48        "5068267641632683791026134915072",
49        "104716890604972796896895427629056",
50        "1748338658439454459487681798864896",
51        "23704654329841312470660182937960448",
52        "259380097567996910282699886670381056",
53        "2250336698853390384720606936038375424",
54        "14978272436876548034486263159246028800",
55        "72144088983913131323343765784380833792",
56        "224599776407103106596571252037123047424",
57        "340282366920938463463374607431768211456",
58    ])
59    .unwrap()
60});
61
62/// `expneg` accepts x in Q.256 format and computes e^-x.
63/// It is most precise within [0, 1.725) range, where error is less than
64/// 3.4e-30. Over the [0, 5) range its error is less than 4.6e-15.
65/// Output is in Q.256 format.
66fn expneg(x: &BigInt) -> BigInt {
67    let num = poly_val(&EXP_NUM_COEF, x);
68    let deno = poly_val(&EXP_DENO_COEF, x);
69
70    (num << PRECISION).div_floor(&deno)
71}
72
73/// `poly_val` evaluates a polynomial given by coefficients `p` in Q.256 format
74/// at point `x` in Q.256 format. Output is in Q.256.
75/// Coefficients should be ordered from the highest order coefficient to the
76/// lowest.
77fn poly_val(poly: &[BigInt], x: &BigInt) -> BigInt {
78    let mut poly_iter = poly.iter();
79    let mut res = poly_iter.next().cloned().unwrap_or_default();
80
81    for coeff in poly_iter {
82        res = ((res * x) >> PRECISION) + coeff;
83    }
84    res
85}
86
87/// computes lambda in Q.256
88#[inline]
89fn lambda(power: &BigInt, total_power: &BigInt) -> BigInt {
90    ((power * BLOCKS_PER_EPOCH) << PRECISION).div_floor(total_power)
91}
92
93/// Poisson inverted `CDF`
94/// lambda is in Q.256 format
95struct Poiss {
96    lam: BigInt,
97    pmf: BigInt,
98    icdf: BigInt,
99
100    k: u64,
101}
102
103impl Poiss {
104    fn new(lambda: BigInt) -> Self {
105        // pmf(k) = (lambda^k)*(e^lambda) / k!
106        // k = 0 here, so it simplifies to just e^-lambda
107        let pmf = expneg(&lambda);
108
109        let icdf = (BigInt::from(1) << PRECISION) - &pmf;
110
111        Self {
112            lam: lambda,
113            pmf,
114            icdf,
115            k: 0,
116        }
117    }
118    fn next(&mut self) -> &BigInt {
119        // pmf(k) = (lambda^k)*(e^lambda) / k!
120        // so pmf(k) = pmf(k-1) * lambda / k
121        self.k += 1;
122
123        // calculate pmf for k
124        self.pmf = self.pmf.div_floor(&BigInt::from(self.k));
125        self.pmf = (&self.pmf * &self.lam) >> PRECISION;
126
127        // calculate output
128        // icdf(k) = icdf(k-1) - pmf(k)
129        self.icdf -= &self.pmf;
130        &self.icdf
131    }
132}
133
134/// Proofs generated by a miner which determines the reward they earn.
135/// This is generated from hashing a partial ticket and using the hash to
136/// generate a value.
137#[derive(
138    Clone,
139    Debug,
140    PartialEq,
141    PartialOrd,
142    Eq,
143    Default,
144    Ord,
145    Serialize_tuple,
146    Deserialize_tuple,
147    Hash,
148    GetSize,
149)]
150pub struct ElectionProof {
151    pub win_count: i64,
152    pub vrfproof: VRFProof,
153}
154
155impl ElectionProof {
156    /// Uses `VRFProof` to compute number of wins.
157    /// The algorithm is based on Algorand's Sortition with Binomial
158    /// distribution replaced by Poisson distribution.
159    pub fn compute_win_count(&self, power: &BigInt, total_power: &BigInt) -> i64 {
160        let h = blake2b_256(self.vrfproof.as_bytes());
161        let lhs = BigInt::from_bytes_be(Sign::Plus, &h);
162
163        // We are calculating upside-down CDF of Poisson distribution with
164        // rate λ=power*E/totalPower
165        // Steps:
166        //  1. calculate λ=power*E/totalPower
167        //  2. calculate elam = exp(-λ)
168        //  3. Check how many times we win:
169        //    j = 0
170        //    pmf = elam
171        //    rhs = 1 - pmf
172        //    for h(vrf) < rhs: j++; pmf = pmf * lam / j; rhs = rhs - pmf
173
174        let lam = lambda(power, total_power);
175        let mut p = Poiss::new(lam);
176        let mut rhs = &p.icdf;
177
178        let mut j: i64 = 0;
179        while &lhs < rhs && j < MAX_WIN_COUNT {
180            rhs = p.next();
181            j += 1;
182        }
183
184        j
185    }
186}
187
188#[cfg(test)]
189impl quickcheck::Arbitrary for ElectionProof {
190    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
191        let fmt_str = format!("===={}=====", u64::arbitrary(g));
192        let vrfproof = VRFProof::new(fmt_str.into_bytes());
193        Self {
194            win_count: i64::arbitrary(g),
195            vrfproof,
196        }
197    }
198}