snarkvm_ledger/
find.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 block height that contains the given `state root`.
20    pub fn find_block_height_from_state_root(&self, state_root: N::StateRoot) -> Result<Option<u32>> {
21        self.vm.block_store().find_block_height_from_state_root(state_root)
22    }
23
24    /// Returns the block hash that contains the given `transaction ID`.
25    pub fn find_block_hash(&self, transaction_id: &N::TransactionID) -> Result<Option<N::BlockHash>> {
26        self.vm.block_store().find_block_hash(transaction_id)
27    }
28
29    /// Returns the block height that contains the given `solution ID`.
30    pub fn find_block_height_from_solution_id(&self, solution_id: &SolutionID<N>) -> Result<Option<u32>> {
31        self.vm.block_store().find_block_height_from_solution_id(solution_id)
32    }
33
34    /// Returns the latest transaction ID that contains the given `program ID`.
35    pub fn find_latest_transaction_id_from_program_id(
36        &self,
37        program_id: &ProgramID<N>,
38    ) -> Result<Option<N::TransactionID>> {
39        self.vm.transaction_store().find_latest_transaction_id_from_program_id(program_id)
40    }
41
42    /// Returns the transaction ID that contains the given `program ID` and `edition`.
43    pub fn find_transaction_id_from_program_id_and_edition(
44        &self,
45        program_id: &ProgramID<N>,
46        edition: u16,
47    ) -> Result<Option<N::TransactionID>> {
48        self.vm.transaction_store().find_transaction_id_from_program_id_and_edition(program_id, edition)
49    }
50
51    /// Returns the transaction ID that contains the given `transition ID`.
52    pub fn find_transaction_id_from_transition_id(
53        &self,
54        transition_id: &N::TransitionID,
55    ) -> Result<Option<N::TransactionID>> {
56        self.vm.transaction_store().find_transaction_id_from_transition_id(transition_id)
57    }
58
59    /// Returns the transition ID that contains the given `input ID` or `output ID`.
60    pub fn find_transition_id(&self, id: &Field<N>) -> Result<N::TransitionID> {
61        self.vm.transition_store().find_transition_id(id)
62    }
63
64    /// Returns the record ciphertexts that belong to the given view key.
65    #[allow(clippy::type_complexity)]
66    pub fn find_record_ciphertexts<'a>(
67        &'a self,
68        view_key: &'a ViewKey<N>,
69        filter: RecordsFilter<N>,
70    ) -> Result<impl 'a + Iterator<Item = (Field<N>, Cow<'a, Record<N, Ciphertext<N>>>)>> {
71        // Derive the x-coordinate of the address corresponding to the given view key.
72        let address_x_coordinate = view_key.to_address().to_x_coordinate();
73        // Derive the `sk_tag` from the graph key.
74        let sk_tag = match GraphKey::try_from(view_key) {
75            Ok(graph_key) => graph_key.sk_tag(),
76            Err(e) => bail!("Failed to derive the graph key from the view key: {e}"),
77        };
78
79        Ok(self.records().flat_map(move |cow| {
80            // Retrieve the commitment and record.
81            let (commitment, record) = match cow {
82                (Cow::Borrowed(commitment), record) => (*commitment, record),
83                (Cow::Owned(commitment), record) => (commitment, record),
84            };
85
86            // Check ownership before determining whether to decrypt the record.
87            if !record.is_owner_with_address_x_coordinate(view_key, &address_x_coordinate) {
88                return None;
89            }
90
91            // Determine whether to decrypt this record (or not), based on the filter.
92            let commitment = match filter {
93                RecordsFilter::All => Ok(Some(commitment)),
94                RecordsFilter::Spent => Record::<N, Plaintext<N>>::tag(sk_tag, commitment).and_then(|tag| {
95                    // Determine if the record is spent.
96                    self.contains_tag(&tag).map(|is_spent| match is_spent {
97                        true => Some(commitment),
98                        false => None,
99                    })
100                }),
101                RecordsFilter::Unspent => Record::<N, Plaintext<N>>::tag(sk_tag, commitment).and_then(|tag| {
102                    // Determine if the record is spent.
103                    self.contains_tag(&tag).map(|is_spent| match is_spent {
104                        true => None,
105                        false => Some(commitment),
106                    })
107                }),
108                RecordsFilter::SlowSpent(private_key) => {
109                    Record::<N, Plaintext<N>>::serial_number(private_key, commitment).and_then(|serial_number| {
110                        // Determine if the record is spent.
111                        self.contains_serial_number(&serial_number).map(|is_spent| match is_spent {
112                            true => Some(commitment),
113                            false => None,
114                        })
115                    })
116                }
117                RecordsFilter::SlowUnspent(private_key) => {
118                    Record::<N, Plaintext<N>>::serial_number(private_key, commitment).and_then(|serial_number| {
119                        // Determine if the record is spent.
120                        self.contains_serial_number(&serial_number).map(|is_spent| match is_spent {
121                            true => None,
122                            false => Some(commitment),
123                        })
124                    })
125                }
126            };
127
128            match commitment {
129                Ok(Some(commitment)) => Some((commitment, record)),
130                Ok(None) => None,
131                Err(e) => {
132                    warn!("Failed to process 'find_record_ciphertexts({:?})': {e}", filter);
133                    None
134                }
135            }
136        }))
137    }
138
139    /// Returns the records that belong to the given view key.
140    #[allow(clippy::type_complexity)]
141    pub fn find_records<'a>(
142        &'a self,
143        view_key: &'a ViewKey<N>,
144        filter: RecordsFilter<N>,
145    ) -> Result<impl 'a + Iterator<Item = (Field<N>, Record<N, Plaintext<N>>)>> {
146        self.find_record_ciphertexts(view_key, filter).map(|iter| {
147            iter.flat_map(|(commitment, record)| match record.decrypt(view_key) {
148                Ok(record) => Some((commitment, record)),
149                Err(e) => {
150                    warn!("Failed to decrypt the record: {e}");
151                    None
152                }
153            })
154        })
155    }
156}