snarkvm_ledger/
get.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 the committee for the given `block height`.
20    pub fn get_committee(&self, block_height: u32) -> Result<Option<Committee<N>>> {
21        self.vm.finalize_store().committee_store().get_committee(block_height)
22    }
23
24    /// Returns the committee for the given `round`.
25    pub fn get_committee_for_round(&self, round: u64) -> Result<Option<Committee<N>>> {
26        // Check if the committee is already in the cache.
27        if let Some(committee) = self.committee_cache.lock().get(&round) {
28            return Ok(Some(committee.clone()));
29        }
30
31        match self.vm.finalize_store().committee_store().get_committee_for_round(round)? {
32            // Return the committee if it exists.
33            Some(committee) => {
34                // Insert the committee into the cache.
35                self.committee_cache.lock().push(round, committee.clone());
36                // Return the committee.
37                Ok(Some(committee))
38            }
39            // Return the current committee if the round is equivalent.
40            None => {
41                // Retrieve the current committee.
42                let current_committee = self.latest_committee()?;
43                // Return the current committee if the round is equivalent.
44                match current_committee.starting_round() == round {
45                    true => Ok(Some(current_committee)),
46                    false => Ok(None),
47                }
48            }
49        }
50    }
51
52    /// Returns the committee lookback for the given round.
53    pub fn get_committee_lookback_for_round(&self, round: u64) -> Result<Option<Committee<N>>> {
54        // Get the round number for the previous committee. Note, we subtract 2 from odd rounds,
55        // because committees are updated in even rounds.
56        let previous_round = match round % 2 == 0 {
57            true => round.saturating_sub(1),
58            false => round.saturating_sub(2),
59        };
60
61        // Get the committee lookback round.
62        let committee_lookback_round = previous_round.saturating_sub(Committee::<N>::COMMITTEE_LOOKBACK_RANGE);
63
64        // Retrieve the committee for the committee lookback round.
65        self.get_committee_for_round(committee_lookback_round)
66    }
67
68    /// Returns the state root that contains the given `block height`.
69    pub fn get_state_root(&self, block_height: u32) -> Result<Option<N::StateRoot>> {
70        self.vm.block_store().get_state_root(block_height)
71    }
72
73    /// Returns a state path for the given commitment.
74    pub fn get_state_path_for_commitment(&self, commitment: &Field<N>) -> Result<StatePath<N>> {
75        self.vm.block_store().get_state_path_for_commitment(commitment)
76    }
77
78    /// Returns a list of state paths for the given list of `commitment`s.
79    pub fn get_state_paths_for_commitments(&self, commitments: &[Field<N>]) -> Result<Vec<StatePath<N>>> {
80        self.vm.block_store().get_state_paths_for_commitments(commitments)
81    }
82
83    /// Returns the epoch hash for the given block height.
84    pub fn get_epoch_hash(&self, block_height: u32) -> Result<N::BlockHash> {
85        // Compute the epoch number from the current block height.
86        let epoch_number = block_height.saturating_div(N::NUM_BLOCKS_PER_EPOCH);
87        // Compute the epoch starting height (a multiple of `NUM_BLOCKS_PER_EPOCH`).
88        let epoch_starting_height = epoch_number.saturating_mul(N::NUM_BLOCKS_PER_EPOCH);
89        // Retrieve the epoch hash, defined as the 'previous block hash' from the epoch starting height.
90        let epoch_hash = self.get_previous_hash(epoch_starting_height)?;
91        // Construct the epoch hash.
92        Ok(epoch_hash)
93    }
94
95    /// Returns the block for the given block height.
96    pub fn get_block(&self, height: u32) -> Result<Block<N>> {
97        match self.try_get_block(height)? {
98            Some(block) => Ok(block),
99            None => bail!("Block {height} does not exist in storage"),
100        }
101    }
102
103    /// Returns the block for the given block height.
104    ///
105    /// This behaves the same as [`Self::get_block`], except that a missing block will cause the function to
106    /// return `Ok(None)` instead of an error.
107    pub fn try_get_block(&self, height: u32) -> Result<Option<Block<N>>> {
108        if height == 0 {
109            return Ok(Some(self.genesis_block.clone()));
110        }
111
112        match self.vm.block_store().get_block_hash(height)? {
113            Some(hash) => self.vm.block_store().get_block(&hash),
114            None => Ok(None),
115        }
116    }
117
118    /// Returns the blocks in the given block range.
119    /// The range is inclusive of the start and exclusive of the end.
120    pub fn get_blocks(&self, heights: Range<u32>) -> Result<Vec<Block<N>>> {
121        cfg_into_iter!(heights).map(|height| self.get_block(height)).collect()
122    }
123
124    /// Returns the block for the given block hash.
125    pub fn get_block_by_hash(&self, block_hash: &N::BlockHash) -> Result<Block<N>> {
126        match self.try_get_block_by_hash(block_hash)? {
127            Some(block) => Ok(block),
128            None => bail!("Block '{block_hash}' does not exist in storage"),
129        }
130    }
131
132    /// Returns the block for the given block hash.
133    ///
134    /// This behaves the same as [`Self::get_block_by_hash`], except that a missing block will cause the function to
135    /// return `Ok(None)` instead of an error.
136    pub fn try_get_block_by_hash(&self, block_hash: &N::BlockHash) -> Result<Option<Block<N>>> {
137        self.vm.block_store().get_block(block_hash)
138    }
139
140    /// Returns the block height for the given block hash.
141    pub fn get_height(&self, block_hash: &N::BlockHash) -> Result<u32> {
142        match self.vm.block_store().get_block_height(block_hash)? {
143            Some(height) => Ok(height),
144            None => bail!("Missing block height for block '{block_hash}'"),
145        }
146    }
147
148    /// Returns the block hash for the given block height.
149    pub fn get_hash(&self, height: u32) -> Result<N::BlockHash> {
150        // If the height is 0, return the genesis block hash.
151        if height == 0 {
152            return Ok(self.genesis_block.hash());
153        }
154        match self.vm.block_store().get_block_hash(height)? {
155            Some(block_hash) => Ok(block_hash),
156            None => bail!("Missing block hash for block {height}"),
157        }
158    }
159
160    /// Returns the previous block hash for the given block height.
161    pub fn get_previous_hash(&self, height: u32) -> Result<N::BlockHash> {
162        // If the height is 0, return the default block hash.
163        if height == 0 {
164            return Ok(N::BlockHash::default());
165        }
166        match self.vm.block_store().get_previous_block_hash(height)? {
167            Some(previous_hash) => Ok(previous_hash),
168            None => bail!("Missing previous block hash for block {height}"),
169        }
170    }
171
172    /// Returns the block header for the given block height.
173    pub fn get_header(&self, height: u32) -> Result<Header<N>> {
174        // If the height is 0, return the genesis block header.
175        if height == 0 {
176            return Ok(*self.genesis_block.header());
177        }
178        // Retrieve the block hash.
179        let block_hash = match self.vm.block_store().get_block_hash(height)? {
180            Some(block_hash) => block_hash,
181            None => bail!("Block {height} does not exist in storage"),
182        };
183        // Retrieve the block header.
184        match self.vm.block_store().get_block_header(&block_hash)? {
185            Some(header) => Ok(header),
186            None => bail!("Missing block header for block {height}"),
187        }
188    }
189
190    /// Returns the block transactions for the given block height.
191    pub fn get_transactions(&self, height: u32) -> Result<Transactions<N>> {
192        // If the height is 0, return the genesis block transactions.
193        if height == 0 {
194            return Ok(self.genesis_block.transactions().clone());
195        }
196        // Retrieve the block hash.
197        let Some(block_hash) = self.vm.block_store().get_block_hash(height)? else {
198            bail!("Block {height} does not exist in storage");
199        };
200        // Retrieve the block transaction.
201        match self.vm.block_store().get_block_transactions(&block_hash)? {
202            Some(transactions) => Ok(transactions),
203            None => bail!("Missing block transactions for block {height}"),
204        }
205    }
206
207    /// Returns the aborted transaction IDs for the given block height.
208    pub fn get_aborted_transaction_ids(&self, height: u32) -> Result<Vec<N::TransactionID>> {
209        // If the height is 0, return the genesis block aborted transaction IDs.
210        if height == 0 {
211            return Ok(self.genesis_block.aborted_transaction_ids().clone());
212        }
213        // Retrieve the block hash.
214        let Some(block_hash) = self.vm.block_store().get_block_hash(height)? else {
215            bail!("Block {height} does not exist in storage");
216        };
217        // Retrieve the aborted transaction IDs.
218        match self.vm.block_store().get_block_aborted_transaction_ids(&block_hash)? {
219            Some(aborted_transaction_ids) => Ok(aborted_transaction_ids),
220            None => bail!("Missing aborted transaction IDs for block {height}"),
221        }
222    }
223
224    /// Returns the transaction for the given transaction ID.
225    pub fn get_transaction(&self, transaction_id: N::TransactionID) -> Result<Transaction<N>> {
226        // Retrieve the transaction.
227        match self.vm.block_store().get_transaction(&transaction_id)? {
228            Some(transaction) => Ok(transaction),
229            None => bail!("Missing transaction for ID {transaction_id}"),
230        }
231    }
232
233    /// Returns the confirmed transaction for the given transaction ID.
234    pub fn get_confirmed_transaction(&self, transaction_id: N::TransactionID) -> Result<ConfirmedTransaction<N>> {
235        // Retrieve the confirmed transaction.
236        match self.vm.block_store().get_confirmed_transaction(&transaction_id)? {
237            Some(confirmed_transaction) => Ok(confirmed_transaction),
238            None => bail!("Missing confirmed transaction for ID {transaction_id}"),
239        }
240    }
241
242    /// Returns the unconfirmed transaction for the given `transaction ID`.
243    pub fn get_unconfirmed_transaction(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> {
244        // Retrieve the unconfirmed transaction.
245        match self.vm.block_store().get_unconfirmed_transaction(transaction_id)? {
246            Some(unconfirmed_transaction) => Ok(unconfirmed_transaction),
247            None => bail!("Missing unconfirmed transaction for ID {transaction_id}"),
248        }
249    }
250
251    /// Returns the latest edition for the given `program ID`.
252    pub fn get_latest_edition_for_program(&self, program_id: &ProgramID<N>) -> Result<u16> {
253        match self.vm.block_store().get_latest_edition_for_program(program_id)? {
254            Some(edition) => Ok(edition),
255            None => bail!("Missing latest edition for program ID {program_id}"),
256        }
257    }
258
259    /// Returns the latest program for the given `program ID`.
260    pub fn get_program(&self, program_id: ProgramID<N>) -> Result<Program<N>> {
261        match self.vm.block_store().get_latest_program(&program_id)? {
262            Some(program) => Ok(program),
263            None => bail!("Missing program for ID {program_id}"),
264        }
265    }
266
267    /// Returns the program for the given `program ID` and `edition`.
268    pub fn get_program_for_edition(&self, program_id: ProgramID<N>, edition: u16) -> Result<Program<N>> {
269        match self.try_get_program_for_edition(&program_id, edition)? {
270            Some(program) => Ok(program),
271            None => bail!("Missing program for ID {program_id} and edition {edition}"),
272        }
273    }
274
275    /// Returns the program for the given `program ID` and `edition`,
276    /// or `None` if no program of this ID and edition exists.
277    pub fn try_get_program_for_edition(&self, program_id: &ProgramID<N>, edition: u16) -> Result<Option<Program<N>>> {
278        self.vm.block_store().get_program_for_edition(program_id, edition)
279    }
280
281    /// Returns the block solutions for the given block height.
282    pub fn get_solutions(&self, height: u32) -> Result<Solutions<N>> {
283        // If the height is 0, return the genesis block solutions.
284        if height == 0 {
285            return Ok(self.genesis_block.solutions().clone());
286        }
287        // Retrieve the block hash.
288        let block_hash = match self.vm.block_store().get_block_hash(height)? {
289            Some(block_hash) => block_hash,
290            None => bail!("Block {height} does not exist in storage"),
291        };
292        // Retrieve the block solutions.
293        self.vm.block_store().get_block_solutions(&block_hash)
294    }
295
296    /// Returns the solution for the given solution ID.
297    pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Result<Solution<N>> {
298        self.vm.block_store().get_solution(solution_id)
299    }
300
301    /// Returns the block authority for the given block height.
302    pub fn get_authority(&self, height: u32) -> Result<Authority<N>> {
303        // If the height is 0, return the genesis block authority.
304        if height == 0 {
305            return Ok(self.genesis_block.authority().clone());
306        }
307        // Retrieve the block hash.
308        let block_hash = match self.vm.block_store().get_block_hash(height)? {
309            Some(block_hash) => block_hash,
310            None => bail!("Block {height} does not exist in storage"),
311        };
312        // Retrieve the block authority.
313        match self.vm.block_store().get_block_authority(&block_hash)? {
314            Some(authority) => Ok(authority),
315            None => bail!("Missing authority for block {height}"),
316        }
317    }
318
319    /// Returns the batch certificate for the given `certificate ID`.
320    pub fn get_batch_certificate(&self, certificate_id: &Field<N>) -> Result<Option<BatchCertificate<N>>> {
321        self.vm.block_store().get_batch_certificate(certificate_id)
322    }
323
324    /// Returns the delegators for the given validator.
325    pub fn get_delegators_for_validator(&self, validator: &Address<N>) -> Result<Vec<Address<N>>> {
326        // Construct the credits.aleo program ID.
327        let credits_program_id = ProgramID::from_str("credits.aleo")?;
328        // Construct the bonded mapping name.
329        let bonded_mapping = Identifier::from_str("bonded")?;
330        // Construct the bonded mapping key name.
331        let bonded_mapping_key = Identifier::from_str("validator")?;
332        // Get the credits.aleo bonded mapping.
333        let bonded = self.vm.finalize_store().get_mapping_confirmed(credits_program_id, bonded_mapping)?;
334        // Select the delegators for the given validator.
335        cfg_into_iter!(bonded)
336            .filter_map(|(bonded_address, bond_state)| {
337                let Plaintext::Literal(Literal::Address(bonded_address), _) = bonded_address else {
338                    return Some(Err(anyhow!("Invalid delegator in finalize storage.")));
339                };
340                let Value::Plaintext(Plaintext::Struct(bond_state, _)) = bond_state else {
341                    return Some(Err(anyhow!("Invalid bond_state in finalize storage.")));
342                };
343                let Some(mapping_validator) = bond_state.get(&bonded_mapping_key) else {
344                    return Some(Err(anyhow!("Invalid bond_state validator in finalize storage.")));
345                };
346                let Plaintext::Literal(Literal::Address(mapping_validator), _) = mapping_validator else {
347                    return Some(Err(anyhow!("Invalid validator in finalize storage.")));
348                };
349                // Select bonded addresses which:
350                // 1. are bonded to the right validator.
351                // 2. are not themselves the validator.
352                (mapping_validator == validator && bonded_address != *validator).then_some(Ok(bonded_address))
353            })
354            .collect::<Result<_>>()
355    }
356
357    /// Returns the amount of microcredits that the given address has bonded.
358    pub fn get_bonded_amount(&self, address: &Address<N>) -> Result<u64> {
359        // Construct the credits.aleo program ID.
360        let credits_program_id = ProgramID::from_str("credits.aleo")?;
361        // Construct the bonded mapping name.
362        let bonded_mapping = Identifier::from_str("bonded")?;
363        // Construct the bonded mapping key name.
364        let bonded_mapping_key = Plaintext::from(Literal::Address(*address));
365        // Construct the bond_state microcredits key.
366        let microcredits_key = Identifier::from_str("microcredits")?;
367        // Get the bond state for the given staker.
368        let bond_state =
369            self.vm.finalize_store().get_value_confirmed(credits_program_id, bonded_mapping, &bonded_mapping_key)?;
370        // Find the microcredits in the bond state.
371        match bond_state {
372            Some(Value::Plaintext(Plaintext::Struct(bond_state, _))) => match bond_state.get(&microcredits_key) {
373                Some(Plaintext::Literal(Literal::U64(amount), _)) => Ok(**amount),
374                _ => bail!("Expected 'microcredits' as a u64 in bond_state struct."),
375            },
376            // If the address is not bonded, then return 0.
377            None => Ok(0),
378            _ => bail!("Invalid bond_state in finalize storage."),
379        }
380    }
381}