snarkvm_ledger/
advance.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
18impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
19    /// Returns a candidate for the next block in the ledger, using a committed subdag and its transmissions.
20    pub fn prepare_advance_to_next_quorum_block<R: Rng + CryptoRng>(
21        &self,
22        subdag: Subdag<N>,
23        transmissions: IndexMap<TransmissionID<N>, Transmission<N>>,
24        rng: &mut R,
25    ) -> Result<Block<N>> {
26        // Retrieve the latest block as the previous block (for the next block).
27        let previous_block = self.latest_block();
28
29        // Decouple the transmissions into ratifications, solutions, and transactions.
30        let (ratifications, solutions, transactions) = decouple_transmissions(transmissions.into_iter())?;
31        // Currently, we do not support ratifications from the memory pool.
32        ensure!(ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
33        // Construct the block template.
34        let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) =
35            self.construct_block_template(&previous_block, Some(&subdag), ratifications, solutions, transactions, rng)?;
36
37        // Construct the new quorum block.
38        Block::new_quorum(
39            previous_block.hash(),
40            header,
41            subdag,
42            ratifications,
43            solutions,
44            aborted_solution_ids,
45            transactions,
46            aborted_transaction_ids,
47        )
48    }
49
50    /// Returns a candidate for the next block in the ledger.
51    pub fn prepare_advance_to_next_beacon_block<R: Rng + CryptoRng>(
52        &self,
53        private_key: &PrivateKey<N>,
54        candidate_ratifications: Vec<Ratify<N>>,
55        candidate_solutions: Vec<Solution<N>>,
56        candidate_transactions: Vec<Transaction<N>>,
57        rng: &mut R,
58    ) -> Result<Block<N>> {
59        // Currently, we do not support ratifications from the memory pool.
60        ensure!(candidate_ratifications.is_empty(), "Ratifications are currently unsupported from the memory pool");
61
62        // Retrieve the latest block as the previous block (for the next block).
63        let previous_block = self.latest_block();
64
65        // Construct the block template.
66        let (header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids) = self
67            .construct_block_template(
68                &previous_block,
69                None,
70                candidate_ratifications,
71                candidate_solutions,
72                candidate_transactions,
73                rng,
74            )?;
75
76        // Construct the new beacon block.
77        Block::new_beacon(
78            private_key,
79            previous_block.hash(),
80            header,
81            ratifications,
82            solutions,
83            aborted_solution_ids,
84            transactions,
85            aborted_transaction_ids,
86            rng,
87        )
88    }
89
90    /// Adds the given block as the next block in the ledger.
91    pub fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> {
92        // Acquire the write lock on the current block.
93        let mut current_block = self.current_block.write();
94        // Check again for any possible race conditions.
95        if current_block.is_genesis() {
96            // current block is initialized as the genesis block, but the ledger will
97            // also advance to it on startup.
98            ensure!(
99                current_block.height() == block.height() || current_block.height() + 1 == block.height(),
100                "The given block is not the direct successor of the latest block"
101            );
102        } else {
103            ensure!(
104                current_block.height() + 1 == block.height(),
105                "The given block is not the direct successor of the latest block"
106            );
107        }
108        // Update the VM.
109        self.vm.add_next_block(block)?;
110        // Update the current block.
111        *current_block = block.clone();
112        // Drop the write lock on the current block.
113        drop(current_block);
114
115        // Update the cached committee from storage.
116        if let Ok(current_committee) = self.vm.finalize_store().committee_store().current_committee() {
117            *self.current_committee.write() = Some(current_committee);
118        }
119
120        // If the block is the start of a new epoch, or the epoch hash has not been set,
121        // update the current epoch hash and clear the epoch prover cache.
122        if block.height() % N::NUM_BLOCKS_PER_EPOCH == 0 || self.current_epoch_hash.read().is_none() {
123            // Update and log the current epoch hash.
124            match self.get_epoch_hash(block.height()).ok() {
125                Some(epoch_hash) => {
126                    trace!("Updating the current epoch hash at block {} to '{epoch_hash}'", block.height());
127                    *self.current_epoch_hash.write() = Some(epoch_hash);
128                }
129                None => {
130                    error!("Failed to update the current epoch hash at block {}", block.height());
131                }
132            }
133            // Clear the epoch provers cache.
134            self.epoch_provers_cache.write().clear();
135        } else {
136            // If the block is not part of a new epoch, add the new provers to the epoch prover cache.
137            if let Some(solutions) = block.solutions().as_deref() {
138                let mut epoch_provers_cache = self.epoch_provers_cache.write();
139                for (_, s) in solutions.iter() {
140                    let _ = *epoch_provers_cache.entry(s.address()).and_modify(|e| *e += 1).or_insert(1);
141                }
142            }
143        }
144
145        Ok(())
146    }
147}
148
149/// Splits candidate solutions into a collection of accepted ones and aborted ones.
150pub fn split_candidate_solutions<T, F>(
151    mut candidate_solutions: Vec<T>,
152    max_solutions: usize,
153    mut verification_fn: F,
154) -> (Vec<T>, Vec<T>)
155where
156    T: Sized + Copy,
157    F: FnMut(&mut T) -> bool,
158{
159    // Separate the candidate solutions into valid and aborted solutions.
160    let mut valid_candidate_solutions = Vec::with_capacity(max_solutions);
161    let mut aborted_candidate_solutions = Vec::new();
162    // Reverse the candidate solutions in order to be able to chunk them more efficiently.
163    candidate_solutions.reverse();
164    // Verify the candidate solutions in chunks. This is done so that we can potentially
165    // perform these operations in parallel while keeping the end result deterministic.
166    let chunk_size = 16;
167    while !candidate_solutions.is_empty() {
168        // Check if the collection of valid solutions is full.
169        if valid_candidate_solutions.len() >= max_solutions {
170            // If that's the case, mark the rest of the candidates as aborted.
171            aborted_candidate_solutions.extend(candidate_solutions.into_iter().rev());
172            break;
173        }
174
175        // Split off a chunk of the candidate solutions.
176        let mut candidates_chunk = if candidate_solutions.len() > chunk_size {
177            candidate_solutions.split_off(candidate_solutions.len() - chunk_size)
178        } else {
179            std::mem::take(&mut candidate_solutions)
180        };
181
182        // Verify the solutions in the chunk.
183        let verification_results = candidates_chunk.iter_mut().rev().map(|solution| {
184            let verified = verification_fn(solution);
185            (solution, verified)
186        });
187
188        // Process the results of the verification.
189        for (solution, is_valid) in verification_results.into_iter() {
190            if is_valid && valid_candidate_solutions.len() < max_solutions {
191                valid_candidate_solutions.push(*solution);
192            } else {
193                aborted_candidate_solutions.push(*solution);
194            }
195        }
196    }
197
198    // The `aborted_candidate_solutions` can contain both verified and unverified solutions.
199    // When `check_solution_mut` is used as `verification_fn`, these aborted solutions
200    // may include both mutated and un-mutated variants. This occurs because the verification
201    // check is skipped once the `max_solutions` limit is reached.
202    //
203    // This approach is SAFE because currently, only the `solutionID` of aborted solutions is stored.
204    // However, if full aborted solutions need to be stored in the future, this logic will need to be revisited.
205    (valid_candidate_solutions, aborted_candidate_solutions)
206}
207
208impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
209    /// Constructs a block template for the next block in the ledger.
210    #[allow(clippy::type_complexity)]
211    fn construct_block_template<R: Rng + CryptoRng>(
212        &self,
213        previous_block: &Block<N>,
214        subdag: Option<&Subdag<N>>,
215        candidate_ratifications: Vec<Ratify<N>>,
216        candidate_solutions: Vec<Solution<N>>,
217        candidate_transactions: Vec<Transaction<N>>,
218        rng: &mut R,
219    ) -> Result<(Header<N>, Ratifications<N>, Solutions<N>, Vec<SolutionID<N>>, Transactions<N>, Vec<N::TransactionID>)>
220    {
221        // Construct the solutions.
222        let (solutions, aborted_solutions, solutions_root, combined_proof_target) = match candidate_solutions.is_empty()
223        {
224            true => (None, vec![], Field::<N>::zero(), 0u128),
225            false => {
226                // Retrieve the latest epoch hash.
227                let latest_epoch_hash = self.latest_epoch_hash()?;
228                // Retrieve the latest proof target.
229                let latest_proof_target = self.latest_proof_target();
230                // Separate the candidate solutions into valid and aborted solutions.
231                let mut accepted_solutions: IndexMap<Address<N>, u64> = IndexMap::new();
232                let (valid_candidate_solutions, aborted_candidate_solutions) =
233                    split_candidate_solutions(candidate_solutions, N::MAX_SOLUTIONS, |solution| {
234                        let prover_address = solution.address();
235                        let num_accepted_solutions = accepted_solutions.get(&prover_address).copied().unwrap_or(0);
236                        // Check if the prover has reached their solution limit.
237                        if self.is_solution_limit_reached(&prover_address, num_accepted_solutions) {
238                            return false;
239                        }
240                        // Check if the solution is valid and update the number of accepted solutions.
241                        match self.puzzle().check_solution_mut(solution, latest_epoch_hash, latest_proof_target) {
242                            // Increment the number of accepted solutions for the prover.
243                            Ok(()) => {
244                                *accepted_solutions.entry(prover_address).or_insert(0) += 1;
245                                true
246                            }
247                            // The solution is invalid, so we do not increment the number of accepted solutions.
248                            Err(_) => false,
249                        }
250                    });
251
252                // Check if there are any valid solutions.
253                match valid_candidate_solutions.is_empty() {
254                    true => (None, aborted_candidate_solutions, Field::<N>::zero(), 0u128),
255                    false => {
256                        // Construct the solutions.
257                        let solutions = PuzzleSolutions::new(valid_candidate_solutions)?;
258                        // Compute the solutions root.
259                        let solutions_root = solutions.to_accumulator_point()?;
260                        // Compute the combined proof target.
261                        let combined_proof_target = self.puzzle().get_combined_proof_target(&solutions)?;
262                        // Output the solutions, solutions root, and combined proof target.
263                        (Some(solutions), aborted_candidate_solutions, solutions_root, combined_proof_target)
264                    }
265                }
266            }
267        };
268        // Prepare the solutions.
269        let solutions = Solutions::from(solutions);
270
271        // Construct the aborted solution IDs.
272        let aborted_solution_ids = aborted_solutions.iter().map(Solution::id).collect::<Vec<_>>();
273
274        // Retrieve the latest state root.
275        let latest_state_root = self.latest_state_root();
276        // Retrieve the latest cumulative proof target.
277        let latest_cumulative_proof_target = previous_block.cumulative_proof_target();
278        // Retrieve the latest coinbase target.
279        let latest_coinbase_target = previous_block.coinbase_target();
280        // Retrieve the latest cumulative weight.
281        let latest_cumulative_weight = previous_block.cumulative_weight();
282        // Retrieve the last coinbase target.
283        let last_coinbase_target = previous_block.last_coinbase_target();
284        // Retrieve the last coinbase timestamp.
285        let last_coinbase_timestamp = previous_block.last_coinbase_timestamp();
286
287        // Compute the next round number.
288        let next_round = match subdag {
289            Some(subdag) => subdag.anchor_round(),
290            None => previous_block.round().saturating_add(1),
291        };
292        // Compute the next height.
293        let next_height = previous_block.height().saturating_add(1);
294        // Determine the timestamp for the next block.
295        let next_timestamp = match subdag {
296            Some(subdag) => {
297                // Retrieve the previous committee lookback.
298                let previous_committee_lookback = {
299                    // Calculate the penultimate round, which is the round before the anchor round.
300                    let penultimate_round = subdag.anchor_round().saturating_sub(1);
301                    // Output the committee lookback for the penultimate round.
302                    self.get_committee_lookback_for_round(penultimate_round)?
303                        .ok_or(anyhow!("Failed to fetch committee lookback for round {penultimate_round}"))?
304                };
305                // Return the timestamp for the given committee lookback.
306                subdag.timestamp(&previous_committee_lookback)
307            }
308            None => OffsetDateTime::now_utc().unix_timestamp(),
309        };
310
311        // Calculate the next coinbase targets and timestamps.
312        let (
313            next_coinbase_target,
314            next_proof_target,
315            next_cumulative_proof_target,
316            next_cumulative_weight,
317            next_last_coinbase_target,
318            next_last_coinbase_timestamp,
319        ) = to_next_targets::<N>(
320            latest_cumulative_proof_target,
321            combined_proof_target,
322            latest_coinbase_target,
323            latest_cumulative_weight,
324            last_coinbase_target,
325            last_coinbase_timestamp,
326            next_timestamp,
327        )?;
328
329        // Calculate the coinbase reward.
330        let coinbase_reward = coinbase_reward::<N>(
331            next_height,
332            next_timestamp,
333            N::GENESIS_TIMESTAMP,
334            N::STARTING_SUPPLY,
335            N::ANCHOR_TIME,
336            N::ANCHOR_HEIGHT,
337            N::BLOCK_TIME,
338            combined_proof_target,
339            u64::try_from(latest_cumulative_proof_target)?,
340            latest_coinbase_target,
341        )?;
342
343        // Construct the finalize state.
344        let state = FinalizeGlobalState::new::<N>(
345            next_round,
346            next_height,
347            next_cumulative_weight,
348            next_cumulative_proof_target,
349            previous_block.hash(),
350        )?;
351        // Speculate over the ratifications, solutions, and transactions.
352        let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = self.vm.speculate(
353            state,
354            next_timestamp.saturating_sub(previous_block.timestamp()),
355            Some(coinbase_reward),
356            candidate_ratifications,
357            &solutions,
358            candidate_transactions.iter(),
359            rng,
360        )?;
361
362        // Compute the ratifications root.
363        let ratifications_root = ratifications.to_ratifications_root()?;
364
365        // Construct the subdag root.
366        let subdag_root = match subdag {
367            Some(subdag) => subdag.to_subdag_root()?,
368            None => Field::zero(),
369        };
370
371        // Construct the metadata.
372        let metadata = Metadata::new(
373            N::ID,
374            next_round,
375            next_height,
376            next_cumulative_weight,
377            next_cumulative_proof_target,
378            next_coinbase_target,
379            next_proof_target,
380            next_last_coinbase_target,
381            next_last_coinbase_timestamp,
382            next_timestamp,
383        )?;
384
385        // Construct the header.
386        let header = Header::from(
387            latest_state_root,
388            transactions.to_transactions_root()?,
389            transactions.to_finalize_root(ratified_finalize_operations)?,
390            ratifications_root,
391            solutions_root,
392            subdag_root,
393            metadata,
394        )?;
395
396        // Return the block template.
397        Ok((header, ratifications, solutions, aborted_solution_ids, transactions, aborted_transaction_ids))
398    }
399}