use std::collections::BTreeMap;
use allocative::Allocative;
use custom_debug_derive::Debug;
use futures::future::Either;
use linera_base::{
crypto::{AccountPublicKey, CryptoError, ValidatorSecretKey},
data_types::{Blob, BlockHeight, Epoch, Round, Timestamp},
ensure,
identifiers::{AccountOwner, BlobId, ChainId},
ownership::ChainOwnership,
};
use linera_execution::ExecutionRuntimeContext;
use linera_views::{
context::Context,
map_view::MapView,
register_view::RegisterView,
views::{ClonableView, View},
ViewError,
};
use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
use rand_distr::{Distribution, WeightedAliasIndex};
use serde::{Deserialize, Serialize};
use crate::{
block::{Block, ConfirmedBlock, Timeout, ValidatedBlock},
data_types::{BlockProposal, LiteVote, OriginalProposal, ProposedBlock, Vote},
types::{TimeoutCertificate, ValidatedBlockCertificate},
ChainError,
};
#[derive(Eq, PartialEq)]
pub enum Outcome {
Accept,
Skip,
}
pub type ValidatedOrConfirmedVote<'a> = Either<&'a Vote<ValidatedBlock>, &'a Vote<ConfirmedBlock>>;
#[derive(Debug, Clone, Serialize, Deserialize, Allocative)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub enum LockingBlock {
Fast(BlockProposal),
Regular(ValidatedBlockCertificate),
}
impl LockingBlock {
pub fn round(&self) -> Round {
match self {
Self::Fast(_) => Round::Fast,
Self::Regular(certificate) => certificate.round,
}
}
pub fn chain_id(&self) -> ChainId {
match self {
Self::Fast(proposal) => proposal.content.block.chain_id,
Self::Regular(certificate) => certificate.value().chain_id(),
}
}
}
#[cfg_attr(with_graphql, derive(async_graphql::SimpleObject), graphql(complex))]
#[derive(Debug, View, ClonableView, Allocative)]
#[allocative(bound = "C")]
pub struct ChainManager<C>
where
C: Clone + Context + 'static,
{
pub ownership: RegisterView<C, ChainOwnership>,
pub seed: RegisterView<C, u64>,
#[cfg_attr(with_graphql, graphql(skip))] #[allocative(skip)]
pub distribution: RegisterView<C, Option<WeightedAliasIndex<u64>>>,
#[cfg_attr(with_graphql, graphql(skip))] #[allocative(skip)]
pub fallback_distribution: RegisterView<C, Option<WeightedAliasIndex<u64>>>,
#[cfg_attr(with_graphql, graphql(skip))]
pub signed_proposal: RegisterView<C, Option<BlockProposal>>,
#[cfg_attr(with_graphql, graphql(skip))]
pub proposed: RegisterView<C, Option<BlockProposal>>,
pub proposed_blobs: MapView<C, BlobId, Blob>,
#[cfg_attr(with_graphql, graphql(skip))]
pub locking_block: RegisterView<C, Option<LockingBlock>>,
pub locking_blobs: MapView<C, BlobId, Blob>,
#[cfg_attr(with_graphql, graphql(skip))]
pub timeout: RegisterView<C, Option<TimeoutCertificate>>,
#[cfg_attr(with_graphql, graphql(skip))]
pub confirmed_vote: RegisterView<C, Option<Vote<ConfirmedBlock>>>,
#[cfg_attr(with_graphql, graphql(skip))]
pub validated_vote: RegisterView<C, Option<Vote<ValidatedBlock>>>,
#[cfg_attr(with_graphql, graphql(skip))]
pub timeout_vote: RegisterView<C, Option<Vote<Timeout>>>,
#[cfg_attr(with_graphql, graphql(skip))]
pub fallback_vote: RegisterView<C, Option<Vote<Timeout>>>,
pub round_timeout: RegisterView<C, Option<Timestamp>>,
#[cfg_attr(with_graphql, graphql(skip))]
pub current_round: RegisterView<C, Round>,
pub fallback_owners: RegisterView<C, BTreeMap<AccountOwner, u64>>,
}
#[cfg(with_graphql)]
#[async_graphql::ComplexObject]
impl<C> ChainManager<C>
where
C: Context + Clone + 'static,
{
#[graphql(derived(name = "current_round"))]
async fn _current_round(&self) -> Round {
self.current_round()
}
}
impl<C> ChainManager<C>
where
C: Context + Clone + 'static,
{
pub fn reset<'a>(
&mut self,
ownership: ChainOwnership,
height: BlockHeight,
local_time: Timestamp,
fallback_owners: impl Iterator<Item = (AccountPublicKey, u64)> + 'a,
) -> Result<(), ChainError> {
let distribution = calculate_distribution(ownership.owners.iter());
let fallback_owners = fallback_owners
.map(|(pub_key, weight)| (AccountOwner::from(pub_key), weight))
.collect::<BTreeMap<_, _>>();
let fallback_distribution = calculate_distribution(fallback_owners.iter());
let current_round = ownership.first_round();
let round_duration = ownership.round_timeout(current_round);
let round_timeout = round_duration.map(|rd| local_time.saturating_add(rd));
self.clear();
self.seed.set(height.0);
self.ownership.set(ownership);
self.distribution.set(distribution);
self.fallback_distribution.set(fallback_distribution);
self.fallback_owners.set(fallback_owners);
self.current_round.set(current_round);
self.round_timeout.set(round_timeout);
Ok(())
}
pub fn confirmed_vote(&self) -> Option<&Vote<ConfirmedBlock>> {
self.confirmed_vote.get().as_ref()
}
pub fn validated_vote(&self) -> Option<&Vote<ValidatedBlock>> {
self.validated_vote.get().as_ref()
}
pub fn timeout_vote(&self) -> Option<&Vote<Timeout>> {
self.timeout_vote.get().as_ref()
}
pub fn fallback_vote(&self) -> Option<&Vote<Timeout>> {
self.fallback_vote.get().as_ref()
}
pub fn current_round(&self) -> Round {
*self.current_round.get()
}
pub fn check_proposed_block(&self, proposal: &BlockProposal) -> Result<Outcome, ChainError> {
let new_block = &proposal.content.block;
let new_round = proposal.content.round;
if let Some(old_proposal) = self.proposed.get() {
if old_proposal.content == proposal.content {
return Ok(Outcome::Skip); }
}
ensure!(
new_block.height < BlockHeight::MAX,
ChainError::BlockHeightOverflow
);
let current_round = self.current_round();
match new_round {
Round::Fast => {}
Round::MultiLeader(_) | Round::SingleLeader(0) => {
ensure!(
self.is_super(&proposal.owner()) || !current_round.is_fast(),
ChainError::WrongRound(current_round)
);
ensure!(
new_round >= current_round,
ChainError::InsufficientRound(new_round)
);
}
Round::SingleLeader(_) | Round::Validator(_) => {
ensure!(
new_round == current_round,
ChainError::WrongRound(current_round)
);
}
}
if let Some(vote) = self.validated_vote() {
ensure!(
new_round > vote.round,
ChainError::InsufficientRoundStrict(vote.round)
);
}
if let Some(locking_block) = self.locking_block.get() {
ensure!(
locking_block.round() < new_round,
ChainError::MustBeNewerThanLockingBlock(new_block.height, locking_block.round())
);
}
if let Some(vote) = self.confirmed_vote() {
ensure!(
match proposal.original_proposal.as_ref() {
None => false,
Some(OriginalProposal::Regular { certificate }) =>
vote.round <= certificate.round,
Some(OriginalProposal::Fast(_)) => {
vote.round.is_fast() && vote.value().matches_proposed_block(new_block)
}
},
ChainError::HasIncompatibleConfirmedVote(new_block.height, vote.round)
);
}
Ok(Outcome::Accept)
}
pub fn create_timeout_vote(
&mut self,
chain_id: ChainId,
height: BlockHeight,
round: Round,
epoch: Epoch,
key_pair: Option<&ValidatorSecretKey>,
local_time: Timestamp,
) -> Result<bool, ChainError> {
let Some(key_pair) = key_pair else {
return Ok(false); };
ensure!(
round == self.current_round(),
ChainError::WrongRound(self.current_round())
);
let Some(round_timeout) = *self.round_timeout.get() else {
return Err(ChainError::RoundDoesNotTimeOut);
};
ensure!(
local_time >= round_timeout,
ChainError::NotTimedOutYet(round_timeout)
);
if let Some(vote) = self.timeout_vote.get() {
if vote.round == round {
return Ok(false); }
}
let value = Timeout::new(chain_id, height, epoch);
self.timeout_vote
.set(Some(Vote::new(value, round, key_pair)));
Ok(true)
}
pub fn vote_fallback(
&mut self,
chain_id: ChainId,
height: BlockHeight,
epoch: Epoch,
key_pair: Option<&ValidatorSecretKey>,
) -> bool {
let Some(key_pair) = key_pair else {
return false; };
if self.fallback_vote.get().is_some() || self.current_round() >= Round::Validator(0) {
return false; }
let value = Timeout::new(chain_id, height, epoch);
let last_regular_round = Round::SingleLeader(u32::MAX);
self.fallback_vote
.set(Some(Vote::new(value, last_regular_round, key_pair)));
true
}
pub fn check_validated_block(
&self,
certificate: &ValidatedBlockCertificate,
) -> Result<Outcome, ChainError> {
let new_block = certificate.block();
let new_round = certificate.round;
if let Some(Vote { value, round, .. }) = self.confirmed_vote.get() {
if value.block() == new_block && *round == new_round {
return Ok(Outcome::Skip); }
}
if let Some(Vote { round, .. }) = self.validated_vote.get() {
ensure!(new_round >= *round, ChainError::InsufficientRound(*round))
}
if let Some(locking) = self.locking_block.get() {
ensure!(
new_round > locking.round(),
ChainError::InsufficientRoundStrict(locking.round())
);
}
Ok(Outcome::Accept)
}
pub fn create_vote(
&mut self,
proposal: BlockProposal,
block: Block,
key_pair: Option<&ValidatorSecretKey>,
local_time: Timestamp,
blobs: BTreeMap<BlobId, Blob>,
) -> Result<Option<ValidatedOrConfirmedVote>, ChainError> {
let round = proposal.content.round;
match &proposal.original_proposal {
Some(OriginalProposal::Regular { certificate }) => {
if self
.locking_block
.get()
.as_ref()
.is_none_or(|locking| locking.round() < certificate.round)
{
let value = ValidatedBlock::new(block.clone());
if let Some(certificate) = certificate.clone().with_value(value) {
self.update_locking(LockingBlock::Regular(certificate), blobs.clone())?;
}
}
}
Some(OriginalProposal::Fast(signature)) => {
if self.locking_block.get().is_none() {
let original_proposal = BlockProposal {
signature: *signature,
..proposal.clone()
};
self.update_locking(LockingBlock::Fast(original_proposal), blobs.clone())?;
}
}
None => {
if round.is_fast() && self.locking_block.get().is_none() {
self.update_locking(LockingBlock::Fast(proposal.clone()), blobs.clone())?;
}
}
}
self.update_proposed(proposal.clone(), blobs)?;
self.update_current_round(local_time);
let Some(key_pair) = key_pair else {
return Ok(None);
};
if round.is_fast() {
self.validated_vote.set(None);
let value = ConfirmedBlock::new(block);
let vote = Vote::new(value, round, key_pair);
Ok(Some(Either::Right(
self.confirmed_vote.get_mut().insert(vote),
)))
} else {
let value = ValidatedBlock::new(block);
let vote = Vote::new(value, round, key_pair);
Ok(Some(Either::Left(
self.validated_vote.get_mut().insert(vote),
)))
}
}
pub fn create_final_vote(
&mut self,
validated: ValidatedBlockCertificate,
key_pair: Option<&ValidatorSecretKey>,
local_time: Timestamp,
blobs: BTreeMap<BlobId, Blob>,
) -> Result<(), ViewError> {
let round = validated.round;
let confirmed_block = ConfirmedBlock::new(validated.inner().block().clone());
self.update_locking(LockingBlock::Regular(validated), blobs)?;
self.update_current_round(local_time);
if let Some(key_pair) = key_pair {
if self.current_round() != round {
return Ok(()); }
let vote = Vote::new(confirmed_block, round, key_pair);
self.confirmed_vote.set(Some(vote));
self.validated_vote.set(None);
}
Ok(())
}
pub async fn pending_blob(&self, blob_id: &BlobId) -> Result<Option<Blob>, ViewError> {
if let Some(blob) = self.proposed_blobs.get(blob_id).await? {
return Ok(Some(blob));
}
self.locking_blobs.get(blob_id).await
}
pub async fn pending_blobs(&self, blob_ids: &[BlobId]) -> Result<Vec<Option<Blob>>, ViewError> {
let mut blobs = self.proposed_blobs.multi_get(blob_ids).await?;
let mut missing_indices = Vec::new();
let mut missing_blob_ids = Vec::new();
for (i, (blob, blob_id)) in blobs.iter().zip(blob_ids).enumerate() {
if blob.is_none() {
missing_indices.push(i);
missing_blob_ids.push(blob_id);
}
}
let second_blobs = self.locking_blobs.multi_get(missing_blob_ids).await?;
for (blob, i) in second_blobs.into_iter().zip(missing_indices) {
blobs[i] = blob;
}
Ok(blobs)
}
fn update_current_round(&mut self, local_time: Timestamp) {
let current_round = self
.timeout
.get()
.iter()
.map(|certificate| {
self.ownership
.get()
.next_round(certificate.round)
.unwrap_or(Round::Validator(u32::MAX))
})
.chain(self.locking_block.get().as_ref().map(LockingBlock::round))
.chain(
self.proposed
.get()
.iter()
.chain(self.signed_proposal.get())
.map(|proposal| proposal.content.round),
)
.max()
.unwrap_or_default()
.max(self.ownership.get().first_round());
if current_round <= self.current_round() {
return;
}
let round_duration = self.ownership.get().round_timeout(current_round);
self.round_timeout
.set(round_duration.map(|rd| local_time.saturating_add(rd)));
self.current_round.set(current_round);
}
pub fn handle_timeout_certificate(
&mut self,
certificate: TimeoutCertificate,
local_time: Timestamp,
) {
let round = certificate.round;
if let Some(known_certificate) = self.timeout.get() {
if known_certificate.round >= round {
return;
}
}
self.timeout.set(Some(certificate));
self.update_current_round(local_time);
}
pub fn verify_owner(
&self,
proposal_owner: &AccountOwner,
proposal_round: Round,
) -> Result<bool, CryptoError> {
if self.ownership.get().super_owners.contains(proposal_owner) {
return Ok(true);
}
Ok(match proposal_round {
Round::Fast => {
false }
Round::MultiLeader(_) => {
let ownership = self.ownership.get();
ownership.open_multi_leader_rounds || ownership.owners.contains_key(proposal_owner)
}
Round::SingleLeader(r) => {
let Some(index) =
round_leader_index(r, *self.seed.get(), self.distribution.get().as_ref())
else {
return Ok(false);
};
self.ownership.get().owners.keys().nth(index) == Some(proposal_owner)
}
Round::Validator(r) => {
let Some(index) = round_leader_index(
r,
*self.seed.get(),
self.fallback_distribution.get().as_ref(),
) else {
return Ok(false);
};
self.fallback_owners.get().keys().nth(index) == Some(proposal_owner)
}
})
}
fn round_leader(&self, round: Round) -> Option<&AccountOwner> {
let ownership = self.ownership.get();
compute_round_leader(
round,
*self.seed.get(),
&ownership.owners,
self.distribution.get().as_ref(),
self.fallback_owners.get(),
self.fallback_distribution.get().as_ref(),
)
}
fn is_super(&self, owner: &AccountOwner) -> bool {
self.ownership.get().super_owners.contains(owner)
}
pub fn update_signed_proposal(
&mut self,
proposal: &BlockProposal,
local_time: Timestamp,
) -> bool {
if proposal.content.round > Round::SingleLeader(0) {
return false;
}
if let Some(old_proposal) = self.signed_proposal.get() {
if old_proposal.content.round >= proposal.content.round {
if *self.current_round.get() < old_proposal.content.round {
tracing::warn!(
chain_id = %proposal.content.block.chain_id,
current_round = ?self.current_round.get(),
proposal_round = ?old_proposal.content.round,
"Proposal round is greater than current round. Updating."
);
self.update_current_round(local_time);
return true;
}
return false;
}
}
if let Some(old_proposal) = self.proposed.get() {
if old_proposal.content.round >= proposal.content.round {
return false;
}
}
self.signed_proposal.set(Some(proposal.clone()));
self.update_current_round(local_time);
true
}
fn update_proposed(
&mut self,
proposal: BlockProposal,
blobs: BTreeMap<BlobId, Blob>,
) -> Result<(), ViewError> {
if let Some(old_proposal) = self.proposed.get() {
if old_proposal.content.round >= proposal.content.round {
return Ok(());
}
}
if let Some(old_proposal) = self.signed_proposal.get() {
if old_proposal.content.round <= proposal.content.round {
self.signed_proposal.set(None);
}
}
self.proposed.set(Some(proposal));
self.proposed_blobs.clear();
for (blob_id, blob) in blobs {
self.proposed_blobs.insert(&blob_id, blob)?;
}
Ok(())
}
fn update_locking(
&mut self,
locking: LockingBlock,
blobs: BTreeMap<BlobId, Blob>,
) -> Result<(), ViewError> {
if let Some(old_locked) = self.locking_block.get() {
if old_locked.round() >= locking.round() {
return Ok(());
}
}
self.locking_block.set(Some(locking));
self.locking_blobs.clear();
for (blob_id, blob) in blobs {
self.locking_blobs.insert(&blob_id, blob)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct ManagerSafetySnapshot {
confirmed_vote: Option<Vote<ConfirmedBlock>>,
validated_vote: Option<Vote<ValidatedBlock>>,
timeout_vote: Option<Vote<Timeout>>,
fallback_vote: Option<Vote<Timeout>>,
locking_block: Option<LockingBlock>,
locking_blobs: Vec<(BlobId, Blob)>,
}
impl ManagerSafetySnapshot {
pub async fn capture<C>(manager: &ChainManager<C>) -> Result<Self, ViewError>
where
C: Context + Clone + 'static,
{
Ok(Self {
confirmed_vote: manager.confirmed_vote.get().clone(),
validated_vote: manager.validated_vote.get().clone(),
timeout_vote: manager.timeout_vote.get().clone(),
fallback_vote: manager.fallback_vote.get().clone(),
locking_block: manager.locking_block.get().clone(),
locking_blobs: manager.locking_blobs.index_values().await?,
})
}
pub fn restore<C>(self, manager: &mut ChainManager<C>) -> Result<(), ViewError>
where
C: Context + Clone + 'static,
{
manager.confirmed_vote.set(self.confirmed_vote);
manager.validated_vote.set(self.validated_vote);
manager.timeout_vote.set(self.timeout_vote);
manager.fallback_vote.set(self.fallback_vote);
manager.locking_block.set(self.locking_block);
manager.locking_blobs.clear();
for (blob_id, blob) in self.locking_blobs {
manager.locking_blobs.insert(&blob_id, blob)?;
}
Ok(())
}
}
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub struct ChainManagerInfo {
pub ownership: ChainOwnership,
pub requested_signed_proposal: Option<Box<BlockProposal>>,
#[debug(skip_if = Option::is_none)]
pub requested_proposed: Option<Box<BlockProposal>>,
#[debug(skip_if = Option::is_none)]
pub requested_locking: Option<Box<LockingBlock>>,
#[debug(skip_if = Option::is_none)]
pub timeout: Option<Box<TimeoutCertificate>>,
#[debug(skip_if = Option::is_none)]
pub pending: Option<LiteVote>,
#[debug(skip_if = Option::is_none)]
pub timeout_vote: Option<LiteVote>,
#[debug(skip_if = Option::is_none)]
pub fallback_vote: Option<LiteVote>,
#[debug(skip_if = Option::is_none)]
pub requested_confirmed: Option<Box<ConfirmedBlock>>,
#[debug(skip_if = Option::is_none)]
pub requested_validated: Option<Box<ValidatedBlock>>,
pub current_round: Round,
#[debug(skip_if = Option::is_none)]
pub leader: Option<AccountOwner>,
#[debug(skip_if = Option::is_none)]
pub round_timeout: Option<Timestamp>,
}
impl<C> From<&ChainManager<C>> for ChainManagerInfo
where
C: Context + Clone + 'static,
{
fn from(manager: &ChainManager<C>) -> Self {
let current_round = manager.current_round();
let pending = match (manager.confirmed_vote.get(), manager.validated_vote.get()) {
(None, None) => None,
(Some(confirmed_vote), Some(validated_vote))
if validated_vote.round > confirmed_vote.round =>
{
Some(validated_vote.lite())
}
(Some(vote), _) => Some(vote.lite()),
(None, Some(vote)) => Some(vote.lite()),
};
ChainManagerInfo {
ownership: manager.ownership.get().clone(),
requested_signed_proposal: None,
requested_proposed: None,
requested_locking: None,
timeout: manager.timeout.get().clone().map(Box::new),
pending,
timeout_vote: manager.timeout_vote.get().as_ref().map(Vote::lite),
fallback_vote: manager.fallback_vote.get().as_ref().map(Vote::lite),
requested_confirmed: None,
requested_validated: None,
current_round,
leader: manager.round_leader(current_round).copied(),
round_timeout: *manager.round_timeout.get(),
}
}
}
impl ChainManagerInfo {
pub fn add_values<C>(&mut self, manager: &ChainManager<C>)
where
C: Context + Clone + 'static,
C::Extra: ExecutionRuntimeContext,
{
self.requested_signed_proposal = manager.signed_proposal.get().clone().map(Box::new);
self.requested_proposed = manager.proposed.get().clone().map(Box::new);
self.requested_locking = manager.locking_block.get().clone().map(Box::new);
self.requested_confirmed = manager
.confirmed_vote
.get()
.as_ref()
.map(|vote| Box::new(vote.value.clone()));
self.requested_validated = manager
.validated_vote
.get()
.as_ref()
.map(|vote| Box::new(vote.value.clone()));
}
pub fn should_propose(
&self,
identity: &AccountOwner,
round: Round,
seed: u64,
current_committee: &BTreeMap<AccountOwner, u64>,
) -> bool {
match round {
Round::Fast => self.ownership.super_owners.contains(identity),
Round::MultiLeader(_) => true,
Round::SingleLeader(_) | Round::Validator(_) => {
let distribution = calculate_distribution(self.ownership.owners.iter());
let fallback_distribution = calculate_distribution(current_committee.iter());
let leader = compute_round_leader(
round,
seed,
&self.ownership.owners,
distribution.as_ref(),
current_committee,
fallback_distribution.as_ref(),
);
leader == Some(identity)
}
}
}
pub fn already_handled_proposal(&self, round: Round, proposed_block: &ProposedBlock) -> bool {
self.requested_proposed.as_ref().is_some_and(|proposal| {
proposal.content.round == round && *proposed_block == proposal.content.block
})
}
pub fn has_locking_block_in_current_round(&self) -> bool {
self.requested_locking
.as_ref()
.is_some_and(|locking| locking.round() == self.current_round)
}
}
fn calculate_distribution<'a, T: 'a>(
weights: impl IntoIterator<Item = (&'a T, &'a u64)>,
) -> Option<WeightedAliasIndex<u64>> {
let weights: Vec<_> = weights.into_iter().map(|(_, weight)| *weight).collect();
if weights.is_empty() {
None
} else {
Some(WeightedAliasIndex::new(weights).ok()?)
}
}
fn compute_round_leader<'a>(
round: Round,
seed: u64,
owners: &'a BTreeMap<AccountOwner, u64>,
distribution: Option<&WeightedAliasIndex<u64>>,
fallback_owners: &'a BTreeMap<AccountOwner, u64>,
fallback_distribution: Option<&WeightedAliasIndex<u64>>,
) -> Option<&'a AccountOwner> {
match round {
Round::SingleLeader(r) => {
let index = round_leader_index(r, seed, distribution)?;
owners.keys().nth(index)
}
Round::Validator(r) => {
let index = round_leader_index(r, seed, fallback_distribution)?;
fallback_owners.keys().nth(index)
}
Round::Fast | Round::MultiLeader(_) => None,
}
}
fn round_leader_index(
round: u32,
seed: u64,
distribution: Option<&WeightedAliasIndex<u64>>,
) -> Option<usize> {
let seed = u64::from(round).rotate_left(32).wrapping_add(seed);
let mut rng = ChaCha8Rng::seed_from_u64(seed);
Some(distribution?.sample(&mut rng))
}