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