/ synthesizer / src / vm / helpers / committee.rs
committee.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the deltavm 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  #![allow(clippy::redundant_closure)]
 17  
 18  use deltavm_ledger_committee::Committee;
 19  use console::{
 20      account::Address,
 21      network::Network,
 22      prelude::{cfg_into_iter, cfg_iter, cfg_reduce},
 23      program::{Identifier, Literal, Plaintext, Value},
 24      types::{Boolean, U8, U64},
 25  };
 26  
 27  use anyhow::{Result, bail, ensure};
 28  use indexmap::{IndexMap, indexmap};
 29  use std::str::FromStr;
 30  
 31  #[cfg(not(feature = "serial"))]
 32  use rayon::prelude::*;
 33  
 34  /// Returns the committee given the committee map from finalize storage.
 35  pub fn committee_and_delegated_maps_into_committee<N: Network>(
 36      starting_round: u64,
 37      committee_map: Vec<(Plaintext<N>, Value<N>)>,
 38      delegated_map: Vec<(Plaintext<N>, Value<N>)>,
 39  ) -> Result<Committee<N>> {
 40      // Prepare the identifiers.
 41      let is_open_identifier: Identifier<N> = Identifier::from_str("is_open")?;
 42      let commission_identifier: Identifier<N> = Identifier::from_str("commission")?;
 43  
 44      // Extract the committee members.
 45      let committee_members: IndexMap<Address<N>, (u64, bool, u8)> = committee_map
 46          .iter()
 47          .map(|(key, value)| {
 48              // Extract the address from the key.
 49              let address = match key {
 50                  Plaintext::Literal(Literal::Address(address), _) => address,
 51                  _ => bail!("Invalid committee key (missing address) - {key}"),
 52              };
 53  
 54              // Extract the committee state from the value.
 55              let (is_open, commission) = match value {
 56                  Value::Plaintext(Plaintext::Struct(state, _)) => {
 57                      // Extract the is_open flag from the value.
 58                      let is_open = match state.get(&is_open_identifier) {
 59                          Some(Plaintext::Literal(Literal::Boolean(is_open), _)) => **is_open,
 60                          _ => bail!("Invalid committee state (missing boolean) - {value}"),
 61                      };
 62                      // Extract the commission from the value.
 63                      let commission = match state.get(&commission_identifier) {
 64                          Some(Plaintext::Literal(Literal::U8(commission), _)) => **commission,
 65                          _ => bail!("Invalid committee state (missing commission) - {value}"),
 66                      };
 67                      // Return the committee state.
 68                      (is_open, commission)
 69                  }
 70                  _ => bail!("Invalid committee value (missing struct) - {value}"),
 71              };
 72  
 73              // Extract the microcredits for the address from the delegated map.
 74              let Some(microcredits) = delegated_map.iter().find_map(|(delegated_key, delegated_value)| {
 75                  // Retrieve the delegated address.
 76                  let delegated_address = match delegated_key {
 77                      Plaintext::Literal(Literal::Address(address), _) => Some(address),
 78                      _ => None,
 79                  };
 80                  // Check if the address matches.
 81                  match delegated_address == Some(address) {
 82                      // Extract the microcredits from the value.
 83                      true => match delegated_value {
 84                          Value::Plaintext(Plaintext::Literal(Literal::U64(microcredits), _)) => Some(**microcredits),
 85                          _ => None,
 86                      },
 87                      false => None,
 88                  }
 89              }) else {
 90                  bail!("Missing microcredits for committee member - {address}");
 91              };
 92  
 93              // Return the committee member.
 94              Ok((*address, (microcredits, is_open, commission)))
 95          })
 96          .collect::<Result<IndexMap<_, _>>>()?;
 97  
 98      // Return the committee.
 99      Committee::new(starting_round, committee_members)
100  }
101  
102  /// Returns the stakers given the bonded map from finalize storage.
103  pub fn bonded_map_into_stakers<N: Network>(
104      bonded_map: Vec<(Plaintext<N>, Value<N>)>,
105  ) -> Result<IndexMap<Address<N>, (Address<N>, u64)>> {
106      // Prepare the identifiers.
107      let validator_identifier = Identifier::from_str("validator")?;
108      let microcredits_identifier = Identifier::from_str("microcredits")?;
109  
110      // Convert the given key and value into a staker entry.
111      let convert = |key, value| {
112          // Extract the staker from the key.
113          let address = match key {
114              Plaintext::Literal(Literal::Address(address), _) => address,
115              _ => bail!("Invalid bonded key (missing staker) - {key}"),
116          };
117          // Extract the bonded state from the value.
118          match &value {
119              Value::Plaintext(Plaintext::Struct(state, _)) => {
120                  // Extract the validator from the value.
121                  let validator = match state.get(&validator_identifier) {
122                      Some(Plaintext::Literal(Literal::Address(validator), _)) => *validator,
123                      _ => bail!("Invalid bonded state (missing validator) - {value}"),
124                  };
125                  // Extract the microcredits from the value.
126                  let microcredits = match state.get(&microcredits_identifier) {
127                      Some(Plaintext::Literal(Literal::U64(microcredits), _)) => **microcredits,
128                      _ => bail!("Invalid bonded state (missing microcredits) - {value}"),
129                  };
130                  // Return the bonded state.
131                  Ok((address, (validator, microcredits)))
132              }
133              _ => bail!("Invalid bonded value (missing struct) - {value}"),
134          }
135      };
136  
137      // Convert the bonded map into stakers.
138      bonded_map.into_iter().map(|(key, value)| convert(key, value)).collect::<Result<IndexMap<_, _>>>()
139  }
140  
141  /// Checks that the given committee from committee storage matches the given stakers.
142  pub fn ensure_stakers_matches<N: Network>(
143      committee: &Committee<N>,
144      stakers: &IndexMap<Address<N>, (Address<N>, u64)>,
145  ) -> Result<()> {
146      // Construct the validator map.
147      let validator_map: IndexMap<_, _> = cfg_reduce!(
148          cfg_into_iter!(stakers)
149              .map(|(_, (validator, microcredits))| {
150                  if committee.members().contains_key(validator) {
151                      Some(indexmap! {*validator => *microcredits})
152                  } else {
153                      None
154                  }
155              })
156              .flatten(),
157          || IndexMap::new(),
158          |mut acc, e| {
159              for (validator, microcredits) in e {
160                  let entry: &mut u64 = acc.entry(validator).or_default();
161                  *entry = entry.saturating_add(microcredits);
162              }
163              acc
164          }
165      );
166  
167      // Compute the total microcredits.
168      let total_microcredits =
169          cfg_reduce!(cfg_iter!(validator_map).map(|(_, microcredits)| *microcredits), || 0u64, |a, b| {
170              // Add the staker's microcredits to the total microcredits.
171              a.saturating_add(b)
172          });
173  
174      // Ensure the committee and committee map match.
175      ensure!(committee.members().len() == validator_map.len(), "Committee and validator map length do not match");
176      // Ensure the total microcredits match.
177      ensure!(committee.total_stake() == total_microcredits, "Committee and validator map total stake do not match");
178  
179      // Iterate over the committee and ensure the committee and validators match.
180      for (validator, (microcredits, _, _)) in committee.members() {
181          let candidate_microcredits = validator_map.get(validator);
182          ensure!(candidate_microcredits.is_some(), "A validator is missing in finalize storage");
183          ensure!(
184              *microcredits == *candidate_microcredits.unwrap(),
185              "Committee contains an incorrect 'microcredits' amount from stakers"
186          );
187      }
188  
189      Ok(())
190  }
191  
192  /// Returns the next committee, given the current committee and stakers.
193  pub fn to_next_committee<N: Network>(
194      current_committee: &Committee<N>,
195      next_round: u64,
196      next_delegated: &IndexMap<Address<N>, u64>,
197  ) -> Result<Committee<N>> {
198      // Return the next committee.
199      Committee::new(
200          next_round,
201          cfg_iter!(next_delegated)
202              .flat_map(|(delegatee, microcredits)| {
203                  let Some((_, is_open, commission)) = current_committee.members().get(delegatee) else {
204                      // Do nothing, as the delegatee is not part of the committee.
205                      return None;
206                  };
207                  Some((*delegatee, (*microcredits, *is_open, *commission)))
208              })
209              .collect(),
210      )
211  }
212  
213  pub fn to_next_delegated<N: Network>(
214      next_stakers: &IndexMap<Address<N>, (Address<N>, u64)>,
215  ) -> IndexMap<Address<N>, u64> {
216      // Construct the delegated map.
217      let delegated_map: IndexMap<Address<N>, u64> = cfg_reduce!(
218          cfg_into_iter!(next_stakers).map(|(_, (delegatee, microcredits))| indexmap! {*delegatee => *microcredits}),
219          || IndexMap::new(),
220          |mut acc, e| {
221              for (delegatee, microcredits) in e {
222                  let entry: &mut u64 = acc.entry(delegatee).or_default();
223                  *entry = entry.saturating_add(microcredits);
224              }
225              acc
226          }
227      );
228  
229      delegated_map
230  }
231  
232  /// Returns the committee map, bonded map, and delegated map, given the committee and stakers.
233  pub fn to_next_committee_bonded_delegated_map<N: Network>(
234      next_committee: &Committee<N>,
235      next_stakers: &IndexMap<Address<N>, (Address<N>, u64)>,
236      next_delegated: &IndexMap<Address<N>, u64>,
237  ) -> (Vec<(Plaintext<N>, Value<N>)>, Vec<(Plaintext<N>, Value<N>)>, Vec<(Plaintext<N>, Value<N>)>) {
238      // Prepare the identifiers.
239      let validator_identifier = Identifier::from_str("validator").expect("Failed to parse 'validator'");
240      let microcredits_identifier = Identifier::from_str("microcredits").expect("Failed to parse 'microcredits'");
241      let is_open_identifier = Identifier::from_str("is_open").expect("Failed to parse 'is_open'");
242      let commission_identifier = Identifier::from_str("commission").expect("Failed to parse 'commission'");
243  
244      // Construct the committee map.
245      let committee_map = cfg_iter!(next_committee.members())
246          .map(|(validator, (_, is_open, commission))| {
247              // Construct the committee state.
248              let committee_state = indexmap! {
249                  is_open_identifier => Plaintext::from(Literal::Boolean(Boolean::new(*is_open))),
250                  commission_identifier => Plaintext::from(Literal::U8(U8::new(*commission))),
251              };
252              // Return the committee state.
253              (
254                  Plaintext::from(Literal::Address(*validator)),
255                  Value::Plaintext(Plaintext::Struct(committee_state, Default::default())),
256              )
257          })
258          .collect::<Vec<_>>();
259  
260      // Construct the bonded map.
261      let bonded_map = cfg_iter!(next_stakers)
262          .map(|(staker, (validator, microcredits))| {
263              // Construct the bonded state.
264              let bonded_state = indexmap! {
265                  validator_identifier => Plaintext::from(Literal::Address(*validator)),
266                  microcredits_identifier => Plaintext::from(Literal::U64(U64::new(*microcredits))),
267              };
268              // Return the bonded state.
269              (
270                  Plaintext::from(Literal::Address(*staker)),
271                  Value::Plaintext(Plaintext::Struct(bonded_state, Default::default())),
272              )
273          })
274          .collect::<Vec<_>>();
275  
276      // Construct the delegated map.
277      let delegated_map = cfg_iter!(next_delegated)
278          .map(|(delegatee, microcredits)| {
279              (
280                  Plaintext::from(Literal::Address(*delegatee)),
281                  Value::Plaintext(Plaintext::Literal(Literal::U64(U64::new(*microcredits)), Default::default())),
282              )
283          })
284          .collect::<Vec<_>>();
285  
286      (committee_map, bonded_map, delegated_map)
287  }
288  
289  /// Returns the withdraw map, given the withdrawal addresses.
290  pub fn to_next_withdraw_map<N: Network>(
291      withdrawal_addresses: &IndexMap<Address<N>, Address<N>>,
292  ) -> Vec<(Plaintext<N>, Value<N>)> {
293      cfg_iter!(withdrawal_addresses)
294          .map(|(staker, withdraw_address)| {
295              (
296                  Plaintext::from(Literal::Address(*staker)),
297                  Value::Plaintext(Plaintext::Literal(Literal::Address(*withdraw_address), Default::default())),
298              )
299          })
300          .collect::<Vec<_>>()
301  }
302  
303  #[cfg(test)]
304  pub(crate) mod test_helpers {
305      use super::*;
306      use crate::vm::TestRng;
307      use deltavm_ledger_committee::{MIN_DELEGATOR_STAKE, MIN_VALIDATOR_STAKE};
308  
309      use rand::{CryptoRng, Rng};
310  
311      /// Returns the stakers, given the map of `(validator, (microcredits, is_open, commission))` entries.
312      /// This method simulates the existence of delegators for the members.
313      pub(crate) fn to_stakers<N: Network, R: Rng + CryptoRng>(
314          members: &IndexMap<Address<N>, (u64, bool, u8)>,
315          rng: &mut R,
316      ) -> IndexMap<Address<N>, (Address<N>, u64)> {
317          members
318              .into_iter()
319              .flat_map(|(validator, (microcredits, _, _))| {
320                  // Keep a tally of the remaining microcredits.
321                  let remaining_microcredits = microcredits.saturating_sub(MIN_VALIDATOR_STAKE);
322                  // Set the staker amount to `MIN_DELEGATOR_STAKE` microcredits.
323                  let staker_amount = MIN_DELEGATOR_STAKE;
324                  // Determine the number of iterations.
325                  let num_iterations = (remaining_microcredits / staker_amount).saturating_sub(1);
326  
327                  // Construct the map of stakers.
328                  let rngs = (0..num_iterations).map(|_| TestRng::from_seed(rng.r#gen())).collect::<Vec<_>>();
329                  let mut stakers: IndexMap<_, _> = cfg_into_iter!(rngs)
330                      .map(|mut rng| {
331                          // Sample a random staker.
332                          let staker = Address::<N>::new(rng.r#gen());
333                          // Output the staker.
334                          (staker, (*validator, staker_amount))
335                      })
336                      .collect();
337  
338                  // Insert the validator.
339                  stakers.insert(*validator, (*validator, MIN_VALIDATOR_STAKE));
340  
341                  // Insert the last staker.
342                  let final_amount = remaining_microcredits.saturating_sub(num_iterations * staker_amount);
343                  if final_amount > 0 {
344                      let staker = Address::<N>::new(rng.r#gen());
345                      stakers.insert(staker, (*validator, final_amount));
346                  }
347                  // Return the stakers.
348                  stakers
349              })
350              .collect()
351      }
352  
353      /// Returns the validator delegation totals, given the map of `(validator, (microcredits, is_open, commission))` entries.
354      /// This method simulates the existence of delegators for the members.
355      pub(crate) fn to_delegations<N: Network>(
356          members: &IndexMap<Address<N>, (u64, bool, u8)>,
357      ) -> IndexMap<Address<N>, u64> {
358          members.into_iter().map(|(validator, (microcredits, _, _))| (*validator, *microcredits)).collect()
359      }
360  
361      /// Returns the withdrawal addresses, given the stakers.
362      /// This method simulates the existence of unique withdrawal addresses for the stakers.
363      pub(crate) fn to_withdraw_addresses<N: Network, R: Rng + CryptoRng>(
364          stakers: &IndexMap<Address<N>, (Address<N>, u64)>,
365          rng: &mut R,
366      ) -> IndexMap<Address<N>, Address<N>> {
367          stakers
368              .into_iter()
369              .map(|(staker, _)| {
370                  // Sample a random withdraw address.
371                  let withdraw_address = Address::<N>::new(rng.r#gen());
372                  // Return the withdraw address.
373                  (*staker, withdraw_address)
374              })
375              .collect()
376      }
377  }
378  
379  #[cfg(test)]
380  mod tests {
381      use super::*;
382      use console::prelude::TestRng;
383  
384      #[allow(unused_imports)]
385      use rayon::prelude::*;
386      use std::str::FromStr;
387  
388      /// Returns the committee map, given the map of `(validator, (microcredits, is_open, commission))` entries.
389      fn to_committee_map<N: Network>(members: &IndexMap<Address<N>, (u64, bool, u8)>) -> Vec<(Plaintext<N>, Value<N>)> {
390          members
391              .par_iter()
392              .map(|(validator, (_, is_open, commission))| {
393                  let is_open = Boolean::<N>::new(*is_open);
394                  let commission = U8::<N>::new(*commission);
395                  (
396                      Plaintext::from(Literal::Address(*validator)),
397                      Value::from_str(&format!("{{ is_open: {is_open}, commission: {commission} }}")).unwrap(),
398                  )
399              })
400              .collect()
401      }
402  
403      /// Returns the delegated map, given the map of `(validator, (microcredits, is_open, commission))` entries.
404      fn to_delegated_map<N: Network>(members: &IndexMap<Address<N>, (u64, bool, u8)>) -> Vec<(Plaintext<N>, Value<N>)> {
405          members
406              .par_iter()
407              .map(|(validator, (microcredits, _, _))| {
408                  (
409                      Plaintext::from(Literal::Address(*validator)),
410                      Value::Plaintext(Plaintext::Literal(Literal::U64(U64::new(*microcredits)), Default::default())),
411                  )
412              })
413              .collect()
414      }
415  
416      /// Returns the bonded map, given the staker, validator and microcredits.
417      fn to_bonded_map<N: Network>(stakers: &IndexMap<Address<N>, (Address<N>, u64)>) -> Vec<(Plaintext<N>, Value<N>)> {
418          // Prepare the identifiers.
419          let validator_identifier = Identifier::from_str("validator").expect("Failed to parse 'validator'");
420          let microcredits_identifier = Identifier::from_str("microcredits").expect("Failed to parse 'microcredits'");
421  
422          stakers
423              .par_iter()
424              .map(|(staker, (validator, microcredits))| {
425                  // Construct the bonded state.
426                  let bonded_state = indexmap! {
427                      validator_identifier => Plaintext::from(Literal::Address(*validator)),
428                      microcredits_identifier => Plaintext::from(Literal::U64(U64::new(*microcredits))),
429                  };
430                  // Return the bonded state.
431                  (
432                      Plaintext::from(Literal::Address(*staker)),
433                      Value::Plaintext(Plaintext::Struct(bonded_state, Default::default())),
434                  )
435              })
436              .collect()
437      }
438  
439      /// Returns the withdrawal addresses given the withdraw map from finalize storage.
440      pub fn withdraw_map_to_withdrawal_addresses<N: Network>(
441          withdraw_map: Vec<(Plaintext<N>, Value<N>)>,
442      ) -> Result<IndexMap<Address<N>, Address<N>>> {
443          // Convert the given key and value into a staker entry.
444          let convert = |key, value| {
445              // Extract the staker from the key.
446              let staker = match key {
447                  Plaintext::Literal(Literal::Address(address), _) => address,
448                  _ => bail!("Invalid withdraw key (missing staker) - {key}"),
449              };
450  
451              // Extract the withdrawal address from the value.
452              let withdrawal_address = match value {
453                  Value::Plaintext(Plaintext::Literal(Literal::Address(address), _)) => address,
454                  _ => bail!("Invalid withdraw value (missing address) - {key}"),
455              };
456  
457              Ok((staker, withdrawal_address))
458          };
459  
460          // Convert the withdraw map into withdrawal addresses.
461          withdraw_map.into_iter().map(|(key, value)| convert(key, value)).collect::<Result<IndexMap<_, _>>>()
462      }
463  
464      #[test]
465      fn test_committee_and_delegated_maps_into_committee() {
466          let rng = &mut TestRng::default();
467  
468          // Sample a committee.
469          let committee = deltavm_ledger_committee::test_helpers::sample_committee_for_round_and_size(1, 25, rng);
470  
471          // Initialize the committee map.
472          let committee_map = to_committee_map(committee.members());
473  
474          // Initialize the delegated map.
475          let delegated_map = to_delegated_map(committee.members());
476  
477          // Start a timer.
478          let timer = std::time::Instant::now();
479          // Convert the committee map into a committee.
480          let candidate_committee =
481              committee_and_delegated_maps_into_committee(committee.starting_round(), committee_map, delegated_map)
482                  .unwrap();
483          println!("committee_and_delegated_maps_into_committee: {}ms", timer.elapsed().as_millis());
484          assert_eq!(candidate_committee, committee);
485      }
486  
487      #[test]
488      fn test_bonded_map_into_stakers() {
489          let rng = &mut TestRng::default();
490  
491          // Sample a committee.
492          let committee = deltavm_ledger_committee::test_helpers::sample_committee_for_round_and_size(1, 25, rng);
493          // Convert the committee into stakers.
494          let expected_stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng);
495          // Initialize the bonded map.
496          let bonded_map = to_bonded_map(&expected_stakers);
497  
498          // Start a timer.
499          let timer = std::time::Instant::now();
500          // Convert the bonded map into stakers.
501          let candidate_stakers = bonded_map_into_stakers(bonded_map).unwrap();
502          println!("bonded_map_into_stakers: {}ms", timer.elapsed().as_millis());
503          assert_eq!(candidate_stakers.len(), expected_stakers.len());
504          assert_eq!(candidate_stakers, expected_stakers);
505      }
506  
507      #[test]
508      fn test_ensure_stakers_matches() {
509          let rng = &mut TestRng::default();
510  
511          // Sample a committee.
512          let committee = deltavm_ledger_committee::test_helpers::sample_committee_for_round_and_size(1, 25, rng);
513          // Convert the committee into stakers.
514          let stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng);
515  
516          // Start a timer.
517          let timer = std::time::Instant::now();
518          // Ensure the stakers matches.
519          let result = ensure_stakers_matches(&committee, &stakers);
520          println!("ensure_stakers_matches: {}ms", timer.elapsed().as_millis());
521          assert!(result.is_ok());
522      }
523  
524      #[test]
525      fn test_to_next_committee() {
526          let rng = &mut TestRng::default();
527  
528          // Sample a committee.
529          let committee = deltavm_ledger_committee::test_helpers::sample_committee_for_round_and_size(1, 25, rng);
530          // Convert the committee into stakers.
531          let _stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng);
532          // Convert the committee into delegations.
533          let delegations = crate::committee::test_helpers::to_delegations(committee.members());
534  
535          // Start a timer.
536          let timer = std::time::Instant::now();
537          // Ensure the next committee matches the current committee.
538          // Note: We can perform this check, in this specific case only, because we did not apply staking rewards.
539          let next_committee = to_next_committee(&committee, committee.starting_round() + 1, &delegations).unwrap();
540          println!("to_next_committee: {}ms", timer.elapsed().as_millis());
541          assert_eq!(committee.starting_round() + 1, next_committee.starting_round());
542          assert_eq!(committee.members(), next_committee.members());
543      }
544  
545      #[test]
546      fn test_to_next_committee_bonded_delegated_map() {
547          let rng = &mut TestRng::default();
548  
549          // Sample a committee.
550          let committee = deltavm_ledger_committee::test_helpers::sample_committee(rng);
551          // Convert the committee into stakers.
552          let stakers: IndexMap<Address<console::network::MainnetV0>, (Address<console::network::MainnetV0>, u64)> =
553              crate::committee::test_helpers::to_stakers(committee.members(), rng);
554          // Convert the committee into delegations.
555          let delegations = crate::committee::test_helpers::to_delegations(committee.members());
556  
557          // Start a timer.
558          let timer = std::time::Instant::now();
559          // Ensure the next committee matches the current committee.
560          // Note: We can perform this check, in this specific case only, because we did not apply staking rewards.
561          let (committee_map, bonded_map, _) = to_next_committee_bonded_delegated_map(&committee, &stakers, &delegations);
562          println!("to_next_committee_bonded_delegated_map: {}ms", timer.elapsed().as_millis());
563          assert_eq!(committee_map, to_committee_map(committee.members()));
564          assert_eq!(bonded_map, to_bonded_map(&stakers));
565      }
566  
567      #[test]
568      fn test_to_withdraw_map() {
569          let rng = &mut TestRng::default();
570  
571          // Sample a committee.
572          let committee = deltavm_ledger_committee::test_helpers::sample_committee(rng);
573          // Convert the committee into stakers.
574          let stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng);
575  
576          // Construct the withdraw addresses.
577          let withdrawal_addresses = crate::committee::test_helpers::to_withdraw_addresses(&stakers, rng);
578  
579          // Start a timer.
580          let timer = std::time::Instant::now();
581          // Ensure the withdrawal map is correct.
582          let withdrawal_map = to_next_withdraw_map(&withdrawal_addresses);
583          println!("to_next_withdraw_map: {}ms", timer.elapsed().as_millis());
584          assert_eq!(withdrawal_addresses, withdraw_map_to_withdrawal_addresses(withdrawal_map).unwrap());
585      }
586  }