snarkvm_ledger/
is_solution_limit_reached.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18/// The stake required to land one solution per epoch at various points in time.
19///
20/// Each entry represents a threshold where, starting from the given timestamp,
21/// a prover must have at least the specified amount of stake (in microcredits) to land one solution.
22///
23/// A prover with `n * stake` may land up to `n` solutions per epoch.
24///
25/// Format: `(timestamp, stake_required_per_solution)`
26pub const STAKE_REQUIREMENTS_PER_SOLUTION: [(i64, u64); 9] = [
27    (1754006399i64, 100_000_000_000u64),   /* 2025-07-31 23:59:59 UTC */
28    (1761955199i64, 250_000_000_000u64),   /* 2025-10-31 23:59:59 UTC */
29    (1769903999i64, 500_000_000_000u64),   /* 2026-01-31 23:59:59 UTC */
30    (1777593599i64, 750_000_000_000u64),   /* 2026-04-30 23:59:59 UTC */
31    (1785542399i64, 1_000_000_000_000u64), /* 2026-07-31 23:59:59 UTC */
32    (1793491199i64, 1_250_000_000_000u64), /* 2026-10-31 23:59:59 UTC */
33    (1801439999i64, 1_500_000_000_000u64), /* 2027-01-31 23:59:59 UTC */
34    (1809129599i64, 2_000_000_000_000u64), /* 2027-04-30 23:59:59 UTC */
35    (1817078399i64, 2_500_000_000_000u64), /* 2027-07-31 23:59:59 UTC */
36];
37
38/// Returns the maximum number of allowed solutions per epoch based on the provided stake and timestamp.
39pub fn maximum_allowed_solutions_per_epoch(prover_stake: u64, current_time: i64) -> u64 {
40    // If the block height is earlier than the starting enforcement, do not restrict the maximum number of solutions per epoch.
41    if current_time < STAKE_REQUIREMENTS_PER_SOLUTION.first().map(|(t, _)| *t).unwrap_or(i64::MAX) {
42        return u64::MAX;
43    }
44
45    // Find the minimum stake required for one solution per epoch.
46    let minimum_stake_per_solution_per_epoch =
47        match STAKE_REQUIREMENTS_PER_SOLUTION.binary_search_by_key(&current_time, |(t, _)| *t) {
48            // If a stake limit was found at this height, return it.
49            Ok(index) => STAKE_REQUIREMENTS_PER_SOLUTION[index].1,
50            // If the specified height was not found, determine which limit to return.
51            Err(index) => STAKE_REQUIREMENTS_PER_SOLUTION[index.saturating_sub(1)].1,
52        };
53
54    // Return the number of allowed solutions per epoch.
55    prover_stake.saturating_div(minimum_stake_per_solution_per_epoch)
56}
57
58impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
59    /// Returns the number of remaining solutions a prover can submit in the current epoch.
60    pub fn num_remaining_solutions(&self, prover_address: &Address<N>, additional_solutions_in_block: u64) -> u64 {
61        // Fetch the prover's stake.
62        let prover_stake = self.get_bonded_amount(prover_address).unwrap_or(0);
63
64        // Determine the maximum number of solutions allowed based on this prover's stake.
65        let maximum_allowed_solutions = maximum_allowed_solutions_per_epoch(prover_stake, self.latest_timestamp());
66
67        // Fetch the number of solutions the prover has earned rewards for in the current epoch.
68        let prover_num_solutions_in_epoch = *self.epoch_provers_cache.read().get(prover_address).unwrap_or(&0);
69
70        // Calculate the total number of solutions the prover has submitted in the current epoch including the current block.
71        let num_solutions = (prover_num_solutions_in_epoch as u64).saturating_add(additional_solutions_in_block);
72
73        // Return the number of remaining solutions.
74        maximum_allowed_solutions.saturating_sub(num_solutions)
75    }
76
77    /// Returns `true` if the given prover address has reached their solution limit for the current epoch.
78    pub fn is_solution_limit_reached(&self, prover_address: &Address<N>, additional_solutions_in_block: u64) -> bool {
79        // Calculate the number of remaining solutions for the prover.
80        let num_remaining_solutions = self.num_remaining_solutions(prover_address, additional_solutions_in_block);
81
82        // If the number of remaining solutions is zero, the limit is reached.
83        num_remaining_solutions == 0
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    const ITERATIONS: u64 = 100;
92
93    #[test]
94    fn test_solution_limit_per_epoch() {
95        let mut rng = TestRng::default();
96
97        for _ in 0..ITERATIONS {
98            for window in STAKE_REQUIREMENTS_PER_SOLUTION.windows(2) {
99                let (prev_time, stake_per_solution) = window[0];
100                let (next_time, _) = window[1];
101
102                // Choose a time strictly between the steps.
103                let timestamp = rng.gen_range(prev_time..next_time);
104                // Generate a random prover stake.
105                let prover_stake: u64 = rng.gen();
106                let expected_num_solutions = prover_stake / stake_per_solution;
107
108                assert_eq!(maximum_allowed_solutions_per_epoch(prover_stake, timestamp), expected_num_solutions,);
109            }
110        }
111    }
112
113    #[test]
114    fn test_solution_limit_before_enforcement() {
115        let mut rng = TestRng::default();
116
117        // Fetch the first timestamp from the table.
118        let first_timestamp = STAKE_REQUIREMENTS_PER_SOLUTION.first().unwrap().0;
119        let time_before_first = first_timestamp - 1;
120
121        // Check that before enforcement, the number of solutions is unrestricted even without prover stake.
122        let prover_stake = 0;
123        assert_eq!(maximum_allowed_solutions_per_epoch(prover_stake, time_before_first), u64::MAX);
124
125        // Check that before enforcement, the number of solutions is unrestricted for any prover stake.
126        for _ in 0..ITERATIONS {
127            assert_eq!(maximum_allowed_solutions_per_epoch(rng.gen(), rng.gen_range(0..time_before_first)), u64::MAX);
128        }
129    }
130
131    #[test]
132    fn test_solution_limit_after_final_timestamp() {
133        let mut rng = TestRng::default();
134        let (last_timestamp, stake_per_solution) = *STAKE_REQUIREMENTS_PER_SOLUTION.last().unwrap();
135
136        // Check that all timestamps after the last one are treated as the last one.
137        for _ in 0..ITERATIONS {
138            let prover_stake: u64 = rng.gen();
139            let time_after_last = rng.gen_range(last_timestamp..i64::MAX);
140            let expected_num_solutions = prover_stake / stake_per_solution;
141
142            assert_eq!(maximum_allowed_solutions_per_epoch(prover_stake, time_after_last), expected_num_solutions);
143        }
144    }
145
146    #[test]
147    fn test_solution_limit_exact_timestamps() {
148        let mut rng = TestRng::default();
149        // Check that the maximum allowed solutions per epoch is correct for each timestamp in the table.
150        for &(timestamp, stake_per_solution) in STAKE_REQUIREMENTS_PER_SOLUTION.iter() {
151            let expected_num_solutions = rng.gen_range(1..=100);
152            let prover_stake = expected_num_solutions * stake_per_solution;
153
154            assert_eq!(maximum_allowed_solutions_per_epoch(prover_stake, timestamp), expected_num_solutions,);
155        }
156    }
157}