Skip to content

Instantly share code, notes, and snippets.

@erwanor
Created September 25, 2023 20:36
Show Gist options
  • Save erwanor/23a73454f1ee6f703af90d6c778ad1d2 to your computer and use it in GitHub Desktop.
Save erwanor/23a73454f1ee6f703af90d6c778ad1d2 to your computer and use it in GitHub Desktop.
/// Materializes the entire current validator set as a Tendermint update.
///
/// This re-defines all validators every time, to simplify the code compared to
/// trying to track delta updates.
#[instrument(skip(self))]
async fn build_tendermint_validator_updates(&mut self) -> Result<()> {
let current_consensus_keys: CurrentConsensusKeys = self
.get(state_key::current_consensus_keys())
.await?
.expect("current consensus keys must be present");
let current_consensus_keys = current_consensus_keys
.consensus_keys
.into_iter()
.collect::<BTreeSet<_>>();
let mut voting_power_by_consensus_key = BTreeMap::<PublicKey, u64>::new();
// First, build a mapping of consensus key to voting power for all known validators.
// Using a JoinSet, run each validator's state queries concurrently.
let mut js = JoinSet::new();
for v in self.validator_identity_list().await?.iter() {
let state = self.validator_state(v);
let power = self.validator_power(v);
let consensus_key = self.validator_consensus_key(v);
js.spawn(async move {
let state = state
.await?
.expect("every known validator must have a recorded state");
// Compute the effective power of this validator; this is the
// validator power, clamped to zero for all non-Active validators.
let effective_power = if state == validator::State::Active {
power
.await?
.expect("every known validator must have a recorded power")
} else {
0
};
let consensus_key = consensus_key
.await?
.expect("every known validator must have a recorded consensus key");
anyhow::Ok((consensus_key, effective_power))
});
}
// Now collect the computed results into the lookup table.
while let Some(pair) = js.join_next().await.transpose()? {
let (consensus_key, effective_power) = pair?;
voting_power_by_consensus_key.insert(consensus_key, effective_power);
}
// Next, filter that mapping to exclude any zero-power validators, UNLESS they
// were already known to Tendermint.
voting_power_by_consensus_key.retain(|consensus_key, voting_power| {
*voting_power > 0 || current_consensus_keys.contains(consensus_key)
});
// Finally, tell tendermint to delete any known consensus keys not otherwise updated
for ck in current_consensus_keys.iter() {
voting_power_by_consensus_key.entry(*ck).or_insert(0);
}
// Save the validator updates to send to Tendermint.
let tendermint_validator_updates = voting_power_by_consensus_key
.iter()
.map(|(ck, power)| {
Ok(Update {
pub_key: *ck,
power: (*power)
.try_into()
.context("should be able to convert u64 into validator Power")?,
})
})
.collect::<Result<Vec<_>>>()?;
self.put_tendermint_validator_updates(tendermint_validator_updates);
// Record the new consensus keys we will have told tendermint about.
let updated_consensus_keys = CurrentConsensusKeys {
consensus_keys: voting_power_by_consensus_key
.iter()
.filter_map(|(ck, power)| if *power != 0 { Some(*ck) } else { None })
.collect(),
};
tracing::debug!(?updated_consensus_keys);
self.put(
state_key::current_consensus_keys().to_owned(),
updated_consensus_keys,
);
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment