/ ledger / src / find.rs
find.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphavm library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  use super::*;
 20  
 21  use alphavm_utilities::flatten_error;
 22  
 23  impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
 24      /// Returns the block height that contains the given `state root`.
 25      pub fn find_block_height_from_state_root(&self, state_root: N::StateRoot) -> Result<Option<u32>> {
 26          self.vm.block_store().find_block_height_from_state_root(state_root)
 27      }
 28  
 29      /// Returns the block hash that contains the given `transaction ID`.
 30      pub fn find_block_hash(&self, transaction_id: &N::TransactionID) -> Result<Option<N::BlockHash>> {
 31          self.vm.block_store().find_block_hash(transaction_id)
 32      }
 33  
 34      /// Returns the block height that contains the given `solution ID`.
 35      pub fn find_block_height_from_solution_id(&self, solution_id: &SolutionID<N>) -> Result<Option<u32>> {
 36          self.vm.block_store().find_block_height_from_solution_id(solution_id)
 37      }
 38  
 39      /// Returns the latest transaction ID that contains the given `program ID`.
 40      pub fn find_latest_transaction_id_from_program_id(
 41          &self,
 42          program_id: &ProgramID<N>,
 43      ) -> Result<Option<N::TransactionID>> {
 44          self.vm.transaction_store().find_latest_transaction_id_from_program_id(program_id)
 45      }
 46  
 47      /// Returns the transaction ID that contains the given `program ID` and `edition`.
 48      pub fn find_transaction_id_from_program_id_and_edition(
 49          &self,
 50          program_id: &ProgramID<N>,
 51          edition: u16,
 52      ) -> Result<Option<N::TransactionID>> {
 53          self.vm.transaction_store().find_transaction_id_from_program_id_and_edition(program_id, edition)
 54      }
 55  
 56      /// Returns the transaction ID that contains the given `transition ID`.
 57      pub fn find_transaction_id_from_transition_id(
 58          &self,
 59          transition_id: &N::TransitionID,
 60      ) -> Result<Option<N::TransactionID>> {
 61          self.vm.transaction_store().find_transaction_id_from_transition_id(transition_id)
 62      }
 63  
 64      /// Returns the transition ID that contains the given `input ID` or `output ID`.
 65      pub fn find_transition_id(&self, id: &Field<N>) -> Result<N::TransitionID> {
 66          self.vm.transition_store().find_transition_id(id)
 67      }
 68  
 69      /// Returns the record ciphertexts that belong to the given view key.
 70      #[allow(clippy::type_complexity)]
 71      pub fn find_record_ciphertexts<'a>(
 72          &'a self,
 73          view_key: &'a ViewKey<N>,
 74          filter: RecordsFilter<N>,
 75      ) -> Result<impl 'a + Iterator<Item = (Field<N>, Cow<'a, Record<N, Ciphertext<N>>>)>> {
 76          // Derive the x-coordinate of the address corresponding to the given view key.
 77          let address_x_coordinate = view_key.to_address().to_x_coordinate();
 78          // Derive the `sk_tag` from the graph key.
 79          let sk_tag = match GraphKey::try_from(view_key) {
 80              Ok(graph_key) => graph_key.sk_tag(),
 81              Err(e) => bail!("Failed to derive the graph key from the view key: {e}"),
 82          };
 83  
 84          Ok(self.records().flat_map(move |cow| {
 85              // Retrieve the commitment and record.
 86              let (commitment, record) = match cow {
 87                  (Cow::Borrowed(commitment), record) => (*commitment, record),
 88                  (Cow::Owned(commitment), record) => (commitment, record),
 89              };
 90  
 91              // Check ownership before determining whether to decrypt the record.
 92              if !record.is_owner_with_address_x_coordinate(view_key, &address_x_coordinate) {
 93                  return None;
 94              }
 95  
 96              // Determine whether to decrypt this record (or not), based on the filter.
 97              let commitment = match filter {
 98                  RecordsFilter::All => Ok(Some(commitment)),
 99                  RecordsFilter::Spent => Record::<N, Plaintext<N>>::tag(sk_tag, commitment).and_then(|tag| {
100                      // Determine if the record is spent.
101                      self.contains_tag(&tag).map(|is_spent| match is_spent {
102                          true => Some(commitment),
103                          false => None,
104                      })
105                  }),
106                  RecordsFilter::Unspent => Record::<N, Plaintext<N>>::tag(sk_tag, commitment).and_then(|tag| {
107                      // Determine if the record is spent.
108                      self.contains_tag(&tag).map(|is_spent| match is_spent {
109                          true => None,
110                          false => Some(commitment),
111                      })
112                  }),
113                  RecordsFilter::SlowSpent(private_key) => {
114                      Record::<N, Plaintext<N>>::serial_number(private_key, commitment).and_then(|serial_number| {
115                          // Determine if the record is spent.
116                          self.contains_serial_number(&serial_number).map(|is_spent| match is_spent {
117                              true => Some(commitment),
118                              false => None,
119                          })
120                      })
121                  }
122                  RecordsFilter::SlowUnspent(private_key) => {
123                      Record::<N, Plaintext<N>>::serial_number(private_key, commitment).and_then(|serial_number| {
124                          // Determine if the record is spent.
125                          self.contains_serial_number(&serial_number).map(|is_spent| match is_spent {
126                              true => None,
127                              false => Some(commitment),
128                          })
129                      })
130                  }
131              };
132  
133              match commitment {
134                  Ok(Some(commitment)) => Some((commitment, record)),
135                  Ok(None) => None,
136                  Err(err) => {
137                      warn!("{}", &flatten_error(err.context("Failed to process 'find_record_ciphertexts({filter:?})'")));
138                      None
139                  }
140              }
141          }))
142      }
143  
144      /// Returns the records that belong to the given view key.
145      #[allow(clippy::type_complexity)]
146      pub fn find_records<'a>(
147          &'a self,
148          view_key: &'a ViewKey<N>,
149          filter: RecordsFilter<N>,
150      ) -> Result<impl 'a + Iterator<Item = (Field<N>, Record<N, Plaintext<N>>)>> {
151          self.find_record_ciphertexts(view_key, filter).map(|iter| {
152              iter.flat_map(|(commitment, record)| match record.decrypt(view_key) {
153                  Ok(record) => Some((commitment, record)),
154                  Err(err) => {
155                      warn!("{}", &flatten_error(err.context("Failed to decrypt record")));
156                      None
157                  }
158              })
159          })
160      }
161  }