1use crate::{
2 component::{
3 action_handler::ActionHandler, validator_handler::ValidatorDataRead,
4 validator_handler::ValidatorManager,
5 },
6 rate::RateData,
7 validator,
8};
9use anyhow::{ensure, Context, Result};
10use async_trait::async_trait;
11use cnidarium::StateWrite;
12use decaf377_rdsa::VerificationKey;
13use penumbra_sdk_proto::DomainType;
1415#[async_trait]
16impl ActionHandler for validator::Definition {
17type CheckStatelessContext = ();
18async fn check_stateless(&self, _context: ()) -> Result<()> {
19// First, we check that the validator website/name/description does not
20 // exceed 70, 140, and 280 characters respectively. We use guard statements
21 // so that clients can display actionable error messages.
22if self.validator.website.len() > 70 {
23anyhow::bail!("validator website field must be less than 70 characters")
24 }
2526if self.validator.name.len() > 140 {
27anyhow::bail!("validator name must be less than 140 characters")
28 }
2930if self.validator.description.len() > 280 {
31anyhow::bail!("validator description must be less than 280 characters")
32 }
3334if self.validator.funding_streams.len() > 8 {
35anyhow::bail!("validators can declare at most 8 funding streams")
36 }
3738// This prevents an attacker who compromises a validator identity signing key from locking
39 // the validator in an enabled state permanently, instead making it so that the original
40 // operator always has the option of disabling the validator permanently, regardless of what
41 // the attacker does. This reduces the incentive to steal compromise validator signing keys,
42 // because it reduces the expected payoff of such a compromise.
43if self.validator.sequence_number == u32::MAX && self.validator.enabled {
44anyhow::bail!("validators must be disabled when their lifetime is over")
45 }
4647// Then, we check the signature:
48let definition_bytes = self.validator.encode_to_vec();
49 VerificationKey::try_from(self.validator.identity_key.0)
50 .and_then(|vk| vk.verify(&definition_bytes, &self.auth_sig))
51 .context("validator definition signature failed to verify")?;
5253let total_funding_bps = self
54.validator
55 .funding_streams
56 .iter()
57 .map(|fs| fs.rate_bps() as u64)
58 .sum::<u64>();
5960if total_funding_bps > 10_000 {
61anyhow::bail!(
62"validator defined {} bps of funding streams, greater than 10000bps (= 100%)",
63 total_funding_bps
64 );
65 }
6667Ok(())
68 }
6970async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
71// These checks all formerly happened in the `check_stateful` method,
72 // if profiling shows that they cause a bottleneck we could (CAREFULLY)
73 // move some of them back.
74let new_validator = &self.validator;
7576// Check that the sequence numbers of the updated validators is correct...
77 // Check whether we are redefining an existing validator.
78let prev_definition = state
79 .get_validator_definition(&new_validator.identity_key)
80 .await?;
8182if let Some(prev_validator) = &prev_definition {
83// Ensure that the highest existing sequence number is less than
84 // the new sequence number.
85 // Ensure that the sequence number keeps increasing.
86let old_seq = prev_validator.sequence_number;
87let new_seq = new_validator.sequence_number;
88ensure!(
89 new_seq > old_seq,
90"definition sequence number must increase (given {}, but previous definition sequence number is {})",
91 new_seq,
92 old_seq,
93 );
94 }
9596// Check if the consensus key is known, and if so, that it is by the
97 // validator that declares it in this definition.
98if let Some(ck_owner) = state
99 .get_validator_definition_by_consensus_key(&new_validator.consensus_key)
100 .await?
101{
102// If we detect that the new definition tries to squat someone else's
103 // consensus key, we MUST reject this definition:
104 //
105 // 1. It prevents someone from declaring an (app-level) validator that
106 // "piggybacks" on the actual behavior of someone else's validator.
107 //
108 // 2. If we submit a validator update to CometBFT that
109 // includes duplicate consensus keys, CometBFT gets confused
110 // and hangs.
111ensure!(
112 ck_owner.identity_key == new_validator.identity_key,
113"consensus key {:?} is already in use by validator {}",
114 new_validator.consensus_key,
115 ck_owner.identity_key,
116 );
117 }
118119/* ------------ execution ----------- */
120 // If the validator is already defined, we update the definition.
121 // Otherwise, we add the new validator and "prime" its state.
122if prev_definition.is_some() {
123 state
124 .update_validator_definition(new_validator.clone())
125 .await
126.context(
127"should be able to update validator during validator definition execution",
128 )?;
129 } else {
130let validator_key = new_validator.identity_key;
131132// The validator starts with a reward rate of 0 and an exchange rate
133 // of 1, expressed in bps^2 (i.e. 1_0000_0000 is 1.0).
134let initial_rate_data = RateData {
135 identity_key: validator_key,
136 validator_reward_rate: 0u128.into(),
137 validator_exchange_rate: 1_0000_0000u128.into(),
138 };
139140 state
141 .add_validator(new_validator.clone(), initial_rate_data)
142 .await
143.context("should be able to add validator during validator definition execution")?;
144 }
145146Ok(())
147 }
148}