/ node / bft / ledger-service / src / mock.rs
mock.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaOS 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 crate::{LedgerService, fmt_id};
 17  use alphavm::{
 18      ledger::{
 19          Block,
 20          PendingBlock,
 21          Transaction,
 22          committee::Committee,
 23          narwhal::{BatchCertificate, Data, Subdag, Transmission, TransmissionID},
 24          puzzle::{Solution, SolutionID},
 25      },
 26      prelude::{Address, ConsensusVersion, Field, Network, Result, Zero, bail, consensus_config_value, ensure},
 27  };
 28  
 29  use indexmap::IndexMap;
 30  #[cfg(feature = "locktick")]
 31  use locktick::parking_lot::Mutex;
 32  #[cfg(not(feature = "locktick"))]
 33  use parking_lot::Mutex;
 34  use std::{collections::BTreeMap, ops::Range};
 35  use tracing::*;
 36  
 37  /// A mock ledger service that always returns `false`.
 38  #[derive(Debug)]
 39  pub struct MockLedgerService<N: Network> {
 40      committee: Committee<N>,
 41      height_to_round_and_hash: Mutex<BTreeMap<u32, (u64, N::BlockHash)>>,
 42  }
 43  
 44  impl<N: Network> MockLedgerService<N> {
 45      /// Initializes a new mock ledger service.
 46      pub fn new(committee: Committee<N>) -> Self {
 47          Self { committee, height_to_round_and_hash: Default::default() }
 48      }
 49  
 50      /// Initializes a new mock ledger service at the specified height.
 51      pub fn new_at_height(committee: Committee<N>, height: u32) -> Self {
 52          let mut height_to_hash = BTreeMap::new();
 53          for i in 0..=height {
 54              height_to_hash.insert(i, (i as u64 * 2, Field::<N>::from_u32(i).into()));
 55          }
 56          Self { committee, height_to_round_and_hash: Mutex::new(height_to_hash) }
 57      }
 58  }
 59  
 60  #[async_trait]
 61  impl<N: Network> LedgerService<N> for MockLedgerService<N> {
 62      /// Returns the latest round in the ledger.
 63      fn latest_round(&self) -> u64 {
 64          *self.height_to_round_and_hash.lock().keys().last().unwrap_or(&0) as u64
 65      }
 66  
 67      /// Returns the latest block height in the canonical ledger.
 68      fn latest_block_height(&self) -> u32 {
 69          self.height_to_round_and_hash.lock().last_key_value().map(|(height, _)| *height).unwrap_or(0)
 70      }
 71  
 72      /// Returns the latest block in the ledger.
 73      fn latest_block(&self) -> Block<N> {
 74          unreachable!("MockLedgerService does not support latest_block")
 75      }
 76  
 77      /// Returns the latest restrictions ID in the ledger.
 78      fn latest_restrictions_id(&self) -> Field<N> {
 79          Field::zero()
 80      }
 81  
 82      /// Returns the latest cached leader and its associated round.
 83      fn latest_leader(&self) -> Option<(u64, Address<N>)> {
 84          None
 85      }
 86  
 87      /// Updates the latest cached leader and its associated round.
 88      fn update_latest_leader(&self, _round: u64, _leader: Address<N>) {}
 89  
 90      /// Returns `true` if the given block height exists in the canonical ledger.
 91      fn contains_block_height(&self, height: u32) -> bool {
 92          self.height_to_round_and_hash.lock().contains_key(&height)
 93      }
 94  
 95      /// Returns the canonical block height for the given block hash, if it exists.
 96      fn get_block_height(&self, hash: &N::BlockHash) -> Result<u32> {
 97          match self
 98              .height_to_round_and_hash
 99              .lock()
100              .iter()
101              .find_map(|(height, (_, h))| if h == hash { Some(*height) } else { None })
102          {
103              Some(height) => Ok(height),
104              None => bail!("Missing block {hash}"),
105          }
106      }
107  
108      /// Returns the canonical block hash for the given block height, if it exists.
109      fn get_block_hash(&self, height: u32) -> Result<N::BlockHash> {
110          match self.height_to_round_and_hash.lock().get(&height).cloned() {
111              Some((_, hash)) => Ok(hash),
112              None => bail!("Missing block {height}"),
113          }
114      }
115  
116      /// Returns the block round for the given block height, if it exists.
117      fn get_block_round(&self, height: u32) -> Result<u64> {
118          match self
119              .height_to_round_and_hash
120              .lock()
121              .iter()
122              .find_map(|(h, (round, _))| if *h == height { Some(*round) } else { None })
123          {
124              Some(round) => Ok(round),
125              None => bail!("Missing block {height}"),
126          }
127      }
128  
129      /// Returns the block for the given block height.
130      fn get_block(&self, _height: u32) -> Result<Block<N>> {
131          unreachable!("MockLedgerService does not support get_block")
132      }
133  
134      /// Returns the blocks in the given block range.
135      /// The range is inclusive of the start and exclusive of the end.
136      fn get_blocks(&self, _heights: Range<u32>) -> Result<Vec<Block<N>>> {
137          unreachable!("MockLedgerService does not support get_blocks")
138      }
139  
140      /// Returns the solution for the given solution ID.
141      fn get_solution(&self, _solution_id: &SolutionID<N>) -> Result<Solution<N>> {
142          unreachable!("MockLedgerService does not support get_solution")
143      }
144  
145      /// Returns the unconfirmed transaction for the given transaction ID.
146      fn get_unconfirmed_transaction(&self, _transaction_id: N::TransactionID) -> Result<Transaction<N>> {
147          unreachable!("MockLedgerService does not support get_unconfirmed_transaction")
148      }
149  
150      /// Returns the batch certificate for the given batch certificate ID.
151      fn get_batch_certificate(&self, _certificate_id: &Field<N>) -> Result<BatchCertificate<N>> {
152          unreachable!("MockLedgerService does not support get_batch_certificate")
153      }
154  
155      /// Returns the current committee.
156      fn current_committee(&self) -> Result<Committee<N>> {
157          Ok(self.committee.clone())
158      }
159  
160      /// Returns the committee for the given round.
161      fn get_committee_for_round(&self, _round: u64) -> Result<Committee<N>> {
162          Ok(self.committee.clone())
163      }
164  
165      /// Returns the committee lookback for the given round.
166      fn get_committee_lookback_for_round(&self, _round: u64) -> Result<Committee<N>> {
167          Ok(self.committee.clone())
168      }
169  
170      /// Returns `false` for all queries.
171      fn contains_certificate(&self, certificate_id: &Field<N>) -> Result<bool> {
172          trace!("[MockLedgerService] Contains certificate ID {} - false", fmt_id(certificate_id));
173          Ok(false)
174      }
175  
176      /// Returns `false` for all queries.
177      fn contains_transmission(&self, transmission_id: &TransmissionID<N>) -> Result<bool> {
178          trace!(
179              "[MockLedgerService] Contains transmission ID {}.{} - false",
180              fmt_id(transmission_id),
181              fmt_id(transmission_id.checksum().unwrap_or_default())
182          );
183          Ok(false)
184      }
185  
186      /// Ensures that the given transmission is not a fee and matches the given transmission ID.
187      fn ensure_transmission_is_well_formed(
188          &self,
189          transmission_id: TransmissionID<N>,
190          _transmission: &mut Transmission<N>,
191      ) -> Result<()> {
192          trace!(
193              "[MockLedgerService] Ensure transmission ID matches {}.{} - Ok",
194              fmt_id(transmission_id),
195              fmt_id(transmission_id.checksum().unwrap_or_default())
196          );
197          Ok(())
198      }
199  
200      /// Checks the given solution is well-formed.
201      async fn check_solution_basic(&self, solution_id: SolutionID<N>, _solution: Data<Solution<N>>) -> Result<()> {
202          trace!("[MockLedgerService] Check solution basic {:?} - Ok", fmt_id(solution_id));
203          Ok(())
204      }
205  
206      /// Checks the given transaction is well-formed and unique.
207      async fn check_transaction_basic(
208          &self,
209          transaction_id: N::TransactionID,
210          _transaction: Transaction<N>,
211      ) -> Result<()> {
212          trace!("[MockLedgerService] Check transaction basic {:?} - Ok", fmt_id(transaction_id));
213          Ok(())
214      }
215  
216      fn check_block_subdag(&self, _block: Block<N>, _prefix: &[PendingBlock<N>]) -> Result<PendingBlock<N>> {
217          unimplemented!();
218      }
219  
220      fn check_block_content(&self, _block: PendingBlock<N>) -> Result<Block<N>> {
221          unimplemented!();
222      }
223  
224      /// Checks the given block is valid next block.
225      fn check_next_block(&self, _block: &Block<N>) -> Result<()> {
226          Ok(())
227      }
228  
229      /// Returns a candidate for the next block in the ledger, using a committed subdag and its transmissions.
230      #[cfg(feature = "ledger-write")]
231      fn prepare_advance_to_next_quorum_block(
232          &self,
233          _subdag: Subdag<N>,
234          _transmissions: IndexMap<TransmissionID<N>, Transmission<N>>,
235      ) -> Result<Block<N>> {
236          unreachable!("MockLedgerService does not support prepare_advance_to_next_quorum_block")
237      }
238  
239      /// Adds the given block as the next block in the ledger.
240      #[cfg(feature = "ledger-write")]
241      fn advance_to_next_block(&self, block: &Block<N>) -> Result<()> {
242          ensure!(
243              block.height() == self.latest_block_height() + 1,
244              "Tried to advance to block {} from block {}",
245              block.height(),
246              self.latest_block_height()
247          );
248          self.height_to_round_and_hash.lock().insert(block.height(), (block.round(), block.hash()));
249          Ok(())
250      }
251  
252      /// Returns the spent cost for a transaction in microcredits.
253      fn transaction_spend_in_microcredits(
254          &self,
255          _transaction: &Transaction<N>,
256          consensus_version: ConsensusVersion,
257      ) -> Result<u64> {
258          let height = N::CONSENSUS_HEIGHT(consensus_version).unwrap();
259          Ok(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap())
260      }
261  }