/ synthesizer / process / src / tests / test_random.rs
test_random.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 crate::Process;
 17  use alphavm_ledger_committee::{MIN_DELEGATOR_STAKE, MIN_VALIDATOR_SELF_STAKE, MIN_VALIDATOR_STAKE};
 18  #[cfg(feature = "rocks")]
 19  use alphavm_ledger_store::{FinalizeMode, FinalizeStorage, FinalizeStore, atomic_finalize};
 20  #[cfg(not(feature = "rocks"))]
 21  use alphavm_ledger_store::{
 22      FinalizeMode,
 23      FinalizeStorage,
 24      FinalizeStore,
 25      atomic_finalize,
 26      helpers::memory::FinalizeMemory,
 27  };
 28  use console::{
 29      account::{Address, PrivateKey},
 30      network::{MainnetV0, prelude::*},
 31  };
 32  use rand::seq::IteratorRandom;
 33  
 34  use indexmap::{IndexMap, IndexSet};
 35  
 36  use super::test_utils::*;
 37  
 38  /// Returns `value` with probability 0.2, and a uniform value in the closed interval
 39  /// [0, 1.2 * value] with probability 0.8.
 40  fn random_below_with_blowup(value: u64, rng: &mut impl Rng) -> u64 {
 41      const BLOWUP: u64 = 20;
 42      let upper_bound = value + value / BLOWUP;
 43  
 44      match rng.gen_range(0..100) {
 45          0..=79 => rng.gen_range(0..=upper_bound),
 46          _ => value,
 47      }
 48  }
 49  
 50  /// If `default` is `Some(value)` this function returns `value` with probability
 51  /// 0.8 and a uniformly random value in `values` with probability 0.2. If
 52  /// `default` is `None`, it returns a uniformly random value from `values`.
 53  fn random_with_default<'a, T>(
 54      values: impl Iterator<Item = &'a T>,
 55      default: Option<&'a T>,
 56      rng: &mut impl Rng,
 57  ) -> &'a T {
 58      match rng.gen_range(0..100) {
 59          0..=79 => default.unwrap_or(values.choose(rng).unwrap()),
 60          _ => values.choose(rng).unwrap(),
 61      }
 62  }
 63  
 64  /// A bonding or unbonding operation.
 65  #[derive(Clone)]
 66  enum Operation {
 67      /// Bond funds as a validator.
 68      BondValidator {
 69          private_key: PrivateKey<MainnetV0>,
 70          withdrawal_address: Address<MainnetV0>,
 71          amount: u64,
 72          commission: u8,
 73      },
 74      /// Bond funds as a delegator.
 75      BondPublic {
 76          private_key: PrivateKey<MainnetV0>,
 77          validator_address: Address<MainnetV0>,
 78          withdrawal_address: Address<MainnetV0>,
 79          amount: u64,
 80      },
 81      /// Unbond funds.
 82      UnbondPublic { withdrawal_private_key: PrivateKey<MainnetV0>, staker_address: Address<MainnetV0>, amount: u64 },
 83  }
 84  
 85  impl Debug for Operation {
 86      fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
 87          match self {
 88              Operation::BondValidator { private_key, amount, .. } => {
 89                  let validator_address = Address::<MainnetV0>::try_from(private_key).unwrap().to_string();
 90                  write!(f, "BondValidator {{ {}, {amount} }}", &validator_address[..12])
 91              }
 92              Operation::BondPublic { private_key, validator_address, amount, .. } => {
 93                  let delegator_address = Address::<MainnetV0>::try_from(private_key).unwrap().to_string();
 94                  let validator_address = validator_address.to_string();
 95                  write!(f, "BondPublic {{ {}, {}, {amount} }}", &delegator_address[..12], &validator_address[..12])
 96              }
 97              Operation::UnbondPublic { staker_address, amount, .. } => {
 98                  let staker_address = staker_address.to_string();
 99                  write!(f, "UnbondPublic {{ {}, {amount} }}", &staker_address[..12])
100              }
101          }
102      }
103  }
104  
105  impl Operation {
106      /// Returns a random operation. Note that the operation may not be valid in
107      /// the sense that it is not guaranteed to execute successfully.
108      pub fn random(state: &State, rng: &mut impl Rng) -> Self {
109          match rng.gen_range(0..3) {
110              0 => Operation::random_bond_validator(state, rng),
111              1 => Operation::random_bond_public(state, rng),
112              _ => Operation::random_unbond_public(state, rng),
113          }
114      }
115  
116      /// Returns a random `bond_validator` operation.
117      fn random_bond_validator(state: &State, rng: &mut impl Rng) -> Self {
118          let validator = state.stakers().choose(rng).unwrap();
119          let account_balance = state.account_balance(validator.address());
120  
121          Operation::BondValidator {
122              private_key: *validator.private_key(),
123              withdrawal_address: *random_with_default(state.stakers(), Some(validator), rng).withdrawal_address(),
124              amount: random_below_with_blowup(account_balance, rng),
125              commission: random_below_with_blowup(100, rng) as u8,
126          }
127      }
128  
129      /// Returns a random `bond_public` operation.
130      fn random_bond_public(state: &State, rng: &mut impl Rng) -> Self {
131          let delegator = state.stakers().choose(rng).unwrap();
132          let validator_addresses = state.stakers().map(|staker| staker.address());
133          let default_address = state.bonded_to(delegator.address());
134          let account_balance = state.account_balance(delegator.address());
135  
136          Operation::BondPublic {
137              private_key: *delegator.private_key(),
138              validator_address: *random_with_default(validator_addresses, default_address.as_ref(), rng),
139              withdrawal_address: *random_with_default(state.stakers(), Some(delegator), rng).withdrawal_address(),
140              amount: random_below_with_blowup(account_balance, rng),
141          }
142      }
143  
144      /// Returns a random `unbond_public` operation.
145      fn random_unbond_public(state: &State, rng: &mut impl Rng) -> Self {
146          let staker = state.stakers().choose(rng).unwrap();
147          let withdrawal_private_key = match state.bonded_to(staker.address()) {
148              Some(validator_address) => {
149                  let validator = state.staker_with_address(&validator_address).unwrap();
150                  [*staker.withdrawal_private_key(), *validator.withdrawal_private_key()].into_iter().choose(rng).unwrap()
151              }
152              None => *state.stakers().choose(rng).unwrap().withdrawal_private_key(),
153          };
154          let account_balance = state.account_balance(staker.address());
155  
156          Operation::UnbondPublic {
157              withdrawal_private_key,
158              staker_address: *staker.address(),
159              amount: random_below_with_blowup(account_balance, rng),
160          }
161      }
162  }
163  
164  /// A staker in the network with `(address, amount, withdrawal_private_key, withdrawal_address)`.
165  #[derive(Copy, Clone, Debug)]
166  struct Staker(PrivateKey<MainnetV0>, Address<MainnetV0>, PrivateKey<MainnetV0>, Address<MainnetV0>, u64);
167  
168  impl Staker {
169      fn private_key(&self) -> &PrivateKey<MainnetV0> {
170          &self.0
171      }
172  
173      fn address(&self) -> &Address<MainnetV0> {
174          &self.1
175      }
176  
177      fn withdrawal_private_key(&self) -> &PrivateKey<MainnetV0> {
178          &self.2
179      }
180  
181      fn withdrawal_address(&self) -> &Address<MainnetV0> {
182          &self.3
183      }
184  
185      fn initial_balance(&self) -> u64 {
186          self.4
187      }
188  }
189  
190  #[derive(Default)]
191  struct State {
192      /// Maps staker addresses to stakers in the system.
193      stakers: IndexMap<Address<MainnetV0>, Staker>,
194      /// Maps addresses to registered commissions.
195      commissions: IndexMap<Address<MainnetV0>, u8>,
196      /// Maps stakers to the corresponding validator.
197      bonded_to: IndexMap<Address<MainnetV0>, Address<MainnetV0>>,
198      /// Contains the bonded amount for each staker.
199      bonded_amounts: IndexMap<Address<MainnetV0>, u64>,
200      /// Contains the account balance for each staker.
201      account_balances: IndexMap<Address<MainnetV0>, u64>,
202      /// Contains the unbonding amount for each staker.
203      unbonding_amounts: IndexMap<Address<MainnetV0>, u64>,
204      /// Contains each staker who have had funds delegated to them at some point.
205      /// (This is used to mimic the behavior of the `delegated_state` function.)
206      has_delegated_state: IndexSet<Address<MainnetV0>>,
207      // Contains the withdrawal address registered by each staker. (Note that
208      // this may be different from the withdrawal address on the staker.)
209      withdrawal_addresses: IndexMap<Address<MainnetV0>, Address<MainnetV0>>,
210  }
211  
212  impl State {
213      /// Returns a new [`State`] with `num_stakers` stakers.
214      pub fn new<F: FinalizeStorage<MainnetV0>>(
215          num_stakers: u32,
216          store: &FinalizeStore<MainnetV0, F>,
217          rng: &mut TestRng,
218      ) -> Self {
219          let (validators, _) = initialize_stakers(store, num_stakers, 0, rng).unwrap();
220          let stakers: IndexMap<Address<MainnetV0>, Staker> = validators
221              .into_iter()
222              .map(|(private_key, (address, initial_balance, withdrawal_private_key, withdrawal_address))| {
223                  (address, Staker(private_key, address, withdrawal_private_key, withdrawal_address, initial_balance))
224              })
225              .collect();
226          let account_balances = stakers.iter().map(|(address, staker)| (*address, staker.initial_balance())).collect();
227          Self { stakers, account_balances, ..Default::default() }
228      }
229  
230      /// An iterator over the set of potential stakers.
231      fn stakers(&self) -> impl Iterator<Item = &Staker> {
232          self.stakers.iter().map(|(_, staker)| staker)
233      }
234  
235      /// Returns `true` if the address is in the committee.
236      fn in_committee(&self, address: &Address<MainnetV0>) -> bool {
237          self.delegated_amount(address) > MIN_VALIDATOR_STAKE
238              && self.bonded_amount(address) > MIN_VALIDATOR_SELF_STAKE
239              && self.bonded_to(address) == Some(*address)
240      }
241  
242      /// Returns `true` if the address has bonded funds.
243      fn is_bonded(&self, address: &Address<MainnetV0>) -> bool {
244          self.bonded_to.contains_key(address)
245      }
246  
247      /// Returns `true` if the address is unbonding.
248      fn is_unbonding(&self, address: &Address<MainnetV0>) -> bool {
249          self.unbonding_amounts.contains_key(address)
250      }
251  
252      /// Returns `Some(validator_address)` if the given address has bonded funds
253      /// to a validator, or `None` otherwise.
254      fn bonded_to(&self, address: &Address<MainnetV0>) -> Option<Address<MainnetV0>> {
255          self.bonded_to.get(address).copied()
256      }
257  
258      /// Returns the amount (in microcredits) that the address has bonded.
259      fn bonded_amount(&self, address: &Address<MainnetV0>) -> u64 {
260          self.bonded_amounts.get(address).copied().unwrap_or_default()
261      }
262  
263      /// Returns `Some((validator_address, amount))` if the given address has
264      /// bonded `amount` microcredits to `validator_address`.
265      fn bond_state(&self, address: &Address<MainnetV0>) -> Option<(Address<MainnetV0>, u64)> {
266          self.bonded_to(address).zip(Some(self.bonded_amount(address)))
267      }
268  
269      /// Returns the account balance (amount of available unbonded funds) of the
270      /// given address.
271      fn account_balance(&self, address: &Address<MainnetV0>) -> u64 {
272          self.account_balances.get(address).copied().unwrap_or_default()
273      }
274  
275      /// Returns `Some((amount, height))` if the given address is unbonding
276      /// `amount` microcredits.
277      fn unbond_state(&self, address: &Address<MainnetV0>) -> Option<(u64, u32)> {
278          self.unbonding_amounts.get(address).copied().zip(Some(10360))
279      }
280  
281      /// Returns the total amount of funds bonded to the given validator address.
282      fn delegated_amount(&self, validator_address: &Address<MainnetV0>) -> u64 {
283          self.stakers()
284              .filter_map(|staker| {
285                  if self.bonded_to(staker.address()) == Some(*validator_address) {
286                      Some(self.bonded_amount(staker.address()))
287                  } else {
288                      None
289                  }
290              })
291              .sum()
292      }
293  
294      /// Returns the total amount of funds bonded to the given validator address
295      /// if it is non-zero, or `None`.
296      fn delegated_state(&self, validator_address: &Address<MainnetV0>) -> Option<u64> {
297          if self.has_delegated_state.contains(validator_address) {
298              Some(self.delegated_amount(validator_address))
299          } else {
300              None
301          }
302      }
303  
304      /// Returns the staker with the given address.
305      fn staker_with_address(&self, address: &Address<MainnetV0>) -> Option<Staker> {
306          self.stakers.get(address).copied()
307      }
308  
309      /// Returns the staker with the given private key.
310      fn staker_with_private_key(&self, private_key: &PrivateKey<MainnetV0>) -> Option<Staker> {
311          Address::try_from(private_key).ok().and_then(|address| self.stakers.get(&address)).copied()
312      }
313  
314      /// Returns `Some(withdrawal_address)` if the address has registered a
315      /// withdrawal address, or `None` if no withdrawal address is registered.
316      fn withdrawal_address(&self, address: &Address<MainnetV0>) -> Option<Address<MainnetV0>> {
317          self.withdrawal_addresses.get(address).copied()
318      }
319  
320      /// Returns `Some(commission)` if the address has registered a commission,
321      /// or `None` otherwise.
322      fn commision(&self, address: &Address<MainnetV0>) -> Option<u8> {
323          self.commissions.get(address).copied()
324      }
325  
326      /// Rust reference implementation of the `bond_validator` validation.
327      fn check_bond_validator(
328          &self,
329          private_key: &PrivateKey<MainnetV0>,
330          withdrawal_address: &Address<MainnetV0>,
331          amount: u64,
332          commission: u8,
333      ) -> Result<()> {
334          let validator = self.staker_with_private_key(private_key).unwrap();
335  
336          // Amount is at least 1 credit.
337          ensure!(amount >= 1_000_000u64);
338          // Commision is at most 100%.
339          ensure!(commission <= 100u8);
340          // Validator has enough funds.
341          ensure!(amount <= self.account_balance(validator.address()));
342  
343          if self.is_bonded(validator.address()) {
344              // The validator is not bonded to someone else.
345              ensure!(self.bonded_to(validator.address()) == Some(*validator.address()));
346              // The withdrawal address is not updated.
347              ensure!(self.withdrawal_address(validator.address()) == Some(*withdrawal_address));
348              // The commission is not updated.
349              ensure!(self.commision(validator.address()) == Some(commission));
350          } else {
351              // The initial bonded amount is at least 100 credits.
352              ensure!(amount >= MIN_VALIDATOR_SELF_STAKE);
353              // The total delegated amount is at least 10 000 000 credits.
354              ensure!(self.delegated_amount(validator.address()) + amount >= MIN_VALIDATOR_STAKE);
355              // The validator is not unbonding.
356              ensure!(!self.is_unbonding(validator.address()));
357              // The withdrawal address is not updated.
358              ensure!(
359                  self.withdrawal_address(validator.address()).is_none()
360                      || self.withdrawal_address(validator.address()) == Some(*withdrawal_address)
361              );
362          }
363          Ok(())
364      }
365  
366      /// Rust reference implementation of the `bond_public` validation.
367      fn check_bond_public(
368          &self,
369          private_key: &PrivateKey<MainnetV0>,
370          validator_address: &Address<MainnetV0>,
371          withdrawal_address: &Address<MainnetV0>,
372          amount: u64,
373      ) -> Result<()> {
374          let delegator = self.staker_with_private_key(private_key).unwrap();
375  
376          // Amount is at least 1 credit.
377          ensure!(amount >= 1_000_000u64);
378          // Delegator has enough funds.
379          ensure!(amount <= self.account_balance(delegator.address()));
380          // The delegator is different from the validator.
381          ensure!(delegator.address() != validator_address);
382          // The validator is not unbonding.
383          ensure!(!self.is_unbonding(validator_address));
384  
385          if self.is_bonded(delegator.address()) {
386              // The delegator is not bonded to someone else.
387              ensure!(self.bonded_to(delegator.address()) == Some(*validator_address));
388              // The withdrawal address is not updated.
389              ensure!(self.withdrawal_address(delegator.address()) == Some(*withdrawal_address));
390              // TODO: We don't currently track the `is_open` flag or the total number of delegators.
391          } else {
392              // The initial bonded amount is at least 10 000 000 credits.
393              ensure!(amount >= MIN_DELEGATOR_STAKE);
394              // The withdrawal address is not updated.
395              ensure!(
396                  self.withdrawal_address(delegator.address()).is_none()
397                      || self.withdrawal_address(delegator.address()) == Some(*withdrawal_address)
398              );
399          }
400          Ok(())
401      }
402  
403      /// Rust reference implementation of the `unbond_public` validation.
404      fn check_unbond_public(
405          &self,
406          withdrawal_private_key: &PrivateKey<MainnetV0>,
407          staker_address: &Address<MainnetV0>,
408          amount: u64,
409      ) -> Result<()> {
410          let staker = self.staker_with_address(staker_address).unwrap();
411          let validator_address = self.bonded_to(staker_address).ok_or(anyhow!("staker is not bonded"))?;
412          let validator = self.staker_with_address(&validator_address).unwrap();
413          let withdrawal_address = Address::try_from(withdrawal_private_key).unwrap();
414  
415          // The caller is the withdrawal address of the staker or the corresponding validator.
416          ensure!(
417              self.withdrawal_address(staker.address()) == Some(withdrawal_address)
418                  || self.withdrawal_address(validator.address()) == Some(withdrawal_address)
419          );
420          // The staker has bonded enough funds.
421          ensure!(amount <= self.bonded_amount(staker_address));
422  
423          Ok(())
424      }
425  
426      /// Rust reference implementation of the `bond_validator` state update.
427      fn bond_validator(
428          &mut self,
429          private_key: &PrivateKey<MainnetV0>,
430          withdrawal_address: &Address<MainnetV0>,
431          amount: u64,
432          commission: u8,
433      ) {
434          let validator = self.staker_with_private_key(private_key).unwrap();
435          self.commissions.insert(*validator.address(), commission);
436          self.withdrawal_addresses.insert(*validator.address(), *withdrawal_address);
437          self.bonded_to.insert(*validator.address(), *validator.address());
438          *self.bonded_amounts.entry(*validator.address()).or_default() += amount;
439          self.account_balances[validator.address()] -= amount;
440          self.has_delegated_state.insert(*validator.address());
441      }
442  
443      /// Rust reference implementation of the `bond_public` state update.
444      fn bond_public(
445          &mut self,
446          private_key: &PrivateKey<MainnetV0>,
447          validator_address: &Address<MainnetV0>,
448          withdrawal_address: &Address<MainnetV0>,
449          amount: u64,
450      ) {
451          let delegator = self.staker_with_private_key(private_key).unwrap();
452          self.bonded_to.insert(*delegator.address(), *validator_address);
453          *self.bonded_amounts.entry(*delegator.address()).or_default() += amount;
454          self.account_balances[delegator.address()] -= amount;
455          self.withdrawal_addresses.insert(*delegator.address(), *withdrawal_address);
456          self.has_delegated_state.insert(*validator_address);
457      }
458  
459      /// Rust reference implementation of the `unbond_public` state update.
460      fn unbond_public(
461          &mut self,
462          withdrawal_private_key: &PrivateKey<MainnetV0>,
463          staker_address: &Address<MainnetV0>,
464          amount: u64,
465          _height: u64,
466      ) {
467          let staker = self.staker_with_address(staker_address).unwrap();
468          let validator_address = self.bonded_to(staker_address).unwrap();
469          let validator = self.staker_with_address(&validator_address).unwrap();
470          let withdrawal_address = Address::try_from(withdrawal_private_key).unwrap();
471          let validator_in_committee = self.in_committee(&validator_address);
472  
473          self.bonded_amounts[staker.address()] -= amount;
474          *self.unbonding_amounts.entry(*staker.address()).or_default() += amount;
475  
476          if *staker_address != validator_address {
477              // # Unbonding a delegator (staker != validator)
478              //
479              // - If the delegator stake falls below the minimum delegator stake,
480              //   the delegators entire stake is unbonded.
481              // - If the validator unbonds the delegator, the delegators entire
482              //   stake is unbonded.
483              //
484              // - If the validator is in the committee *and* the total delegated
485              //   stake falls below the minimum validator stake, the validators
486              //   entire stake is unbonded.
487              //
488              // TODO: Can the `validator_in_committee` check be removed?
489              if self.bonded_amount(staker.address()) < MIN_DELEGATOR_STAKE
490                  || self.withdrawal_address(&validator_address) == Some(withdrawal_address)
491              {
492                  // The resulting stake is less than the minimum stake, *or* the
493                  // unbond operation is initiated by the validator.
494                  *self.unbonding_amounts.entry(*staker.address()).or_default() +=
495                      self.bonded_amounts.swap_remove(staker.address()).unwrap();
496                  self.bonded_to.swap_remove(staker.address());
497              }
498              if validator_in_committee && self.delegated_amount(validator.address()) < MIN_VALIDATOR_STAKE {
499                  // The validator is in the committee *and* the total delegated
500                  // stake falls below the minimum validator stake.
501                  *self.unbonding_amounts.entry(*validator.address()).or_default() +=
502                      self.bonded_amounts.swap_remove(validator.address()).unwrap();
503                  self.bonded_to.swap_remove(validator.address());
504                  self.commissions.swap_remove(validator.address());
505              }
506          } else {
507              // # Unbonding a validator (staker == validator)
508              //
509              // - If the validator stake falls below the minimum self-stake, the
510              //   validators entire stake is unbonded.
511              // - If the total delegated stake falls below the minim validator
512              //   stake, the validators entire stake is unbonded.
513              if self.bonded_amount(staker.address()) < MIN_VALIDATOR_SELF_STAKE
514                  || self.delegated_amount(staker.address()) < MIN_VALIDATOR_STAKE
515              {
516                  *self.unbonding_amounts.entry(*staker.address()).or_default() +=
517                      self.bonded_amounts.swap_remove(staker.address()).unwrap();
518                  self.bonded_to.swap_remove(staker.address());
519                  self.commissions.swap_remove(staker.address());
520              }
521          }
522      }
523  
524      /// Executes the given operation and updates the store or returns an error if
525      /// the execution failed.
526      pub fn execute_operation(&mut self, op: &Operation) -> Result<()> {
527          match op {
528              Operation::BondValidator { private_key, withdrawal_address, amount, commission } => {
529                  self.check_bond_validator(private_key, withdrawal_address, *amount, *commission)?;
530                  self.bond_validator(private_key, withdrawal_address, *amount, *commission);
531              }
532              Operation::BondPublic { private_key, validator_address, withdrawal_address, amount } => {
533                  self.check_bond_public(private_key, validator_address, withdrawal_address, *amount)?;
534                  self.bond_public(private_key, validator_address, withdrawal_address, *amount);
535              }
536              Operation::UnbondPublic { withdrawal_private_key, staker_address, amount } => {
537                  self.check_unbond_public(withdrawal_private_key, staker_address, *amount)?;
538                  self.unbond_public(withdrawal_private_key, staker_address, *amount, 10_000);
539              }
540          }
541          Ok(())
542      }
543  }
544  
545  /// Executes the given operation and updates the store or returns an error if
546  /// the execution failed.
547  fn execute_operation<F: FinalizeStorage<MainnetV0>>(
548      process: &Process<MainnetV0>,
549      store: &FinalizeStore<MainnetV0, F>,
550      op: &Operation,
551      rng: &mut TestRng,
552  ) -> Result<()> {
553      match op {
554          Operation::BondValidator { private_key, withdrawal_address, amount, commission } => {
555              bond_validator(process, store, private_key, withdrawal_address, *amount, *commission, rng)?;
556          }
557          Operation::BondPublic { private_key, validator_address, withdrawal_address, amount } => {
558              bond_public(process, store, private_key, validator_address, withdrawal_address, *amount, rng)?;
559          }
560          Operation::UnbondPublic { withdrawal_private_key, staker_address, amount } => {
561              unbond_public(process, store, withdrawal_private_key, staker_address, *amount, 10_000, rng)?;
562          }
563      }
564      Ok(())
565  }
566  
567  /// Returns `Ok(())` if the given store is consistent with the current state.
568  fn validate_state<F: FinalizeStorage<MainnetV0>>(state: &State, store: &FinalizeStore<MainnetV0, F>) -> Result<()> {
569      for staker in state.stakers() {
570          // Account balances match.
571          ensure!(account_balance(store, staker.address()).unwrap() == state.account_balance(staker.address()));
572          // Bonded amounts and corresponding validator addresses match.
573          ensure!(bond_state(store, staker.address()).unwrap() == state.bond_state(staker.address()));
574          // Unbonding amounts match.
575          ensure!(unbond_state(store, staker.address()).unwrap() == state.unbond_state(staker.address()));
576          // Delegated amounts match.
577          ensure!(delegated_state(store, staker.address()).unwrap() == state.delegated_state(staker.address()));
578      }
579      Ok(())
580  }
581  
582  fn print_operations(ops: &[Operation]) {
583      eprintln!("[💸]: Operations:");
584      for (i, op) in ops.iter().enumerate() {
585          eprintln!("      [{i}]: {op:?}");
586      }
587  }
588  
589  #[ignore] // Ignoring as it causes CI timeouts
590  #[test]
591  fn test_random_operations() {
592      loop {
593          let mut rng = TestRng::default();
594          let process = Process::<MainnetV0>::load().unwrap();
595          let (store, _) = sample_finalize_store!();
596          let mut state = State::new(8, &store, &mut rng);
597  
598          let mut executed_ops = Vec::new();
599          test_atomic_finalize!(store, FinalizeMode::RealRun, {
600              while executed_ops.len() < 64 {
601                  let op = Operation::random(&state, &mut rng);
602                  let expected_result = state.execute_operation(&op);
603                  let actual_result = execute_operation(&process, &store, &op, &mut rng);
604  
605                  if actual_result.is_ok() != expected_result.is_ok() {
606                      eprintln!("[💥]: State divergence detected.");
607                      eprintln!("      Expected result: {expected_result:?}");
608                      eprintln!("      Actual result:   {actual_result:?}");
609                      executed_ops.push(op);
610                      print_operations(&executed_ops);
611                      panic!();
612                  }
613                  if let Err(error) = validate_state(&state, &store) {
614                      eprintln!("[💥]: State divergence detected.");
615                      eprintln!("      Error: {error:?}");
616                      executed_ops.push(op);
617                      print_operations(&executed_ops);
618                      panic!();
619                  }
620                  if actual_result.is_ok() {
621                      executed_ops.push(op);
622                  }
623              }
624              Ok(())
625          })
626          .unwrap();
627      }
628  }