penumbra_sdk_stake/component/validator_handler/
validator_store.rs1use crate::{
2 component::{StateReadExt as _, MAX_VOTING_POWER},
3 event,
4 rate::RateData,
5 state_key,
6 validator::{self, BondingState::*, State, Validator},
7 IdentityKey, Uptime,
8};
9use anyhow::Result;
10use async_trait::async_trait;
11use cnidarium::{StateRead, StateWrite};
12use futures::{Future, FutureExt};
13use penumbra_sdk_num::Amount;
14use penumbra_sdk_proto::{
15 state::future::DomainFuture, DomainType, StateReadProto, StateWriteProto,
16};
17use std::pin::Pin;
18use tendermint::PublicKey;
19use tracing::instrument;
20
21#[async_trait]
22pub trait ValidatorDataRead: StateRead {
23 async fn get_validator_info(
24 &self,
25 identity_key: &IdentityKey,
26 ) -> Result<Option<validator::Info>> {
27 let validator = self.get_validator_definition(identity_key).await?;
28 let status = self.get_validator_status(identity_key).await?;
29 let rate_data = self.get_validator_rate(identity_key).await?;
30
31 match (validator, status, rate_data) {
32 (Some(validator), Some(status), Some(rate_data)) => Ok(Some(validator::Info {
33 validator,
34 status,
35 rate_data,
36 })),
37 _ => Ok(None),
38 }
39 }
40
41 fn get_validator_state(
42 &self,
43 identity_key: &IdentityKey,
44 ) -> DomainFuture<validator::State, Self::GetRawFut> {
45 self.get(&state_key::validators::state::by_id(identity_key))
46 }
47
48 async fn get_validator_bonding_state(
49 &self,
50 identity_key: &IdentityKey,
51 ) -> Option<validator::BondingState> {
52 self.get(&state_key::validators::pool::bonding_state::by_id(
53 identity_key,
54 ))
55 .await
56 .expect("no deserialization error expected")
57 }
58
59 async fn get_validator_pool_size(&self, identity_key: &IdentityKey) -> Option<Amount> {
61 self.get(&state_key::validators::pool::balance::by_id(identity_key))
62 .await
63 .expect("no deserialization error expected")
64 }
65
66 async fn get_validator_status(
68 &self,
69 identity_key: &IdentityKey,
70 ) -> Result<Option<validator::Status>> {
71 let bonding_state = self.get_validator_bonding_state(identity_key).await;
72 let state = self.get_validator_state(identity_key).await?;
73 let power = self.get_validator_power(identity_key).await?;
74 let identity_key = identity_key.clone();
75 match (state, power, bonding_state) {
76 (Some(state), Some(voting_power), Some(bonding_state)) => Ok(Some(validator::Status {
77 identity_key,
78 state,
79 voting_power,
80 bonding_state,
81 })),
82 _ => Ok(None),
83 }
84 }
85
86 fn get_validator_rate(
87 &self,
88 identity_key: &IdentityKey,
89 ) -> Pin<Box<dyn Future<Output = Result<Option<RateData>>> + Send + 'static>> {
90 self.get(&state_key::validators::rate::current_by_id(identity_key))
91 .boxed()
92 }
93
94 async fn get_prev_validator_rate(&self, identity_key: &IdentityKey) -> Option<RateData> {
95 self.get(&state_key::validators::rate::previous_by_id(identity_key))
96 .await
97 .expect("no deserialization error expected")
98 }
99
100 fn get_validator_power(
101 &self,
102 validator: &IdentityKey,
103 ) -> DomainFuture<Amount, Self::GetRawFut> {
104 self.get(&state_key::validators::power::by_id(validator))
105 }
106
107 async fn get_last_disabled_height(&self, identity_key: &IdentityKey) -> Option<u64> {
110 self.nonverifiable_get_raw(
111 state_key::validators::last_disabled::by_id(identity_key).as_bytes(),
112 )
113 .await
114 .expect("no deserialization error expected")
115 .map(|bytes| u64::from_be_bytes(bytes.try_into().expect("we only write 8 bytes")))
116 }
117
118 async fn get_validator_definition(
119 &self,
120 identity_key: &IdentityKey,
121 ) -> Result<Option<Validator>> {
122 self.get(&state_key::validators::definitions::by_id(identity_key))
123 .await
124 }
125
126 fn get_validator_uptime(
127 &self,
128 identity_key: &IdentityKey,
129 ) -> DomainFuture<Uptime, Self::GetRawFut> {
130 let key = state_key::validators::uptime::by_id(identity_key);
131 self.nonverifiable_get(key.as_bytes())
132 }
133
134 async fn lookup_identity_key_by_consensus_key(&self, ck: &PublicKey) -> Option<IdentityKey> {
135 self.get(&state_key::validators::lookup_by::consensus_key(ck))
136 .await
137 .expect("no deserialization error")
138 }
139
140 async fn lookup_consensus_key_by_comet_address(&self, address: &[u8; 20]) -> Option<PublicKey> {
141 self.get(&state_key::validators::lookup_by::cometbft_address(address))
142 .await
143 .expect("no deserialization error")
144 }
145
146 async fn get_validator_definition_by_consensus_key(
149 &self,
150 ck: &PublicKey,
151 ) -> Result<Option<Validator>> {
152 if let Some(identity_key) = self.lookup_identity_key_by_consensus_key(ck).await {
153 self.get_validator_definition(&identity_key).await
154 } else {
155 return Ok(None);
156 }
157 }
158
159 async fn get_validator_definition_by_cometbft_address(
160 &self,
161 address: &[u8; 20],
162 ) -> Result<Option<Validator>> {
163 if let Some(consensus_key) = self.lookup_consensus_key_by_comet_address(address).await {
164 self.get_validator_definition_by_consensus_key(&consensus_key)
165 .await
166 } else {
167 return Ok(None);
168 }
169 }
170
171 async fn compute_unbonding_height(
179 &self,
180 id: &IdentityKey,
181 start_height: u64,
182 ) -> Result<Option<u64>> {
183 let Some(val_bonding_state) = self.get_validator_bonding_state(id).await else {
184 anyhow::bail!(
185 "validator bonding state not tracked (validator_identity={})",
186 id
187 )
188 };
189
190 let min_block_delay = self.get_stake_params().await?.unbonding_delay;
191 let upper_bound_height = start_height.saturating_add(min_block_delay);
192
193 let unbonding_height = match val_bonding_state {
194 Bonded => Some(upper_bound_height),
196 Unbonding { unbonds_at_height } => {
198 if unbonds_at_height > start_height {
199 Some(unbonds_at_height.min(upper_bound_height))
205 } else {
206 None
212 }
213 }
214 Unbonded => None,
216 };
217
218 Ok(unbonding_height)
219 }
220
221 fn fetch_validator_consensus_key(
225 &self,
226 identity_key: &IdentityKey,
227 ) -> Pin<Box<dyn Future<Output = Result<Option<PublicKey>>> + Send + 'static>> {
228 use futures::TryFutureExt;
229 self.get(&state_key::validators::definitions::by_id(identity_key))
230 .map_ok(|opt: Option<Validator>| opt.map(|v: Validator| v.consensus_key))
231 .boxed()
232 }
233}
234
235impl<T: StateRead + ?Sized> ValidatorDataRead for T {}
236
237#[async_trait]
238pub(crate) trait ValidatorDataWrite: StateWrite {
239 fn set_validator_uptime(&mut self, identity_key: &IdentityKey, uptime: Uptime) {
240 self.nonverifiable_put_raw(
241 state_key::validators::uptime::by_id(identity_key)
242 .as_bytes()
243 .to_vec(),
244 uptime.encode_to_vec(),
245 );
246 }
247
248 fn set_validator_bonding_state(
249 &mut self,
250 identity_key: &IdentityKey,
251 state: validator::BondingState,
252 ) {
253 tracing::debug!(?state, validator_identity = %identity_key, "set bonding state for validator");
254 self.put(
255 state_key::validators::pool::bonding_state::by_id(identity_key),
256 state.clone(),
257 );
258 self.record_proto(
259 event::EventValidatorBondingStateChange {
260 identity_key: *identity_key,
261 bonding_state: state,
262 }
263 .to_proto(),
264 );
265 }
266
267 #[instrument(skip(self))]
268 fn set_validator_power(
269 &mut self,
270 identity_key: &IdentityKey,
271 voting_power: Amount,
272 ) -> Result<()> {
273 tracing::debug!(validator_identity = ?identity_key, ?voting_power, "setting validator power");
274 if voting_power.value() > MAX_VOTING_POWER {
275 anyhow::bail!("voting power exceeds maximum")
276 }
277 self.put(
278 state_key::validators::power::by_id(identity_key),
279 voting_power,
280 );
281 self.record_proto(
282 event::EventValidatorVotingPowerChange {
283 identity_key: *identity_key,
284 voting_power,
285 }
286 .to_proto(),
287 );
288
289 Ok(())
290 }
291
292 #[instrument(skip(self))]
293 fn set_initial_validator_state(
294 &mut self,
295 id: &IdentityKey,
296 initial_state: State,
297 ) -> Result<()> {
298 tracing::debug!(validator_identity = %id, ?initial_state, "setting initial validator state");
299 if !matches!(initial_state, State::Active | State::Defined) {
300 anyhow::bail!("invalid initial validator state");
301 }
302
303 self.put(state_key::validators::state::by_id(id), initial_state);
304 self.record_proto(
305 event::EventValidatorStateChange {
306 identity_key: *id,
307 state: initial_state,
308 }
309 .to_proto(),
310 );
311 Ok(())
312 }
313
314 #[instrument(skip(self))]
315 fn set_validator_rate_data(&mut self, identity_key: &IdentityKey, rate_data: RateData) {
316 tracing::debug!("setting validator rate data");
317 self.put(
318 state_key::validators::rate::current_by_id(identity_key),
319 rate_data.clone(),
320 );
321 self.record_proto(
322 event::EventRateDataChange {
323 identity_key: *identity_key,
324 rate_data,
325 }
326 .to_proto(),
327 );
328 }
329
330 #[instrument(skip(self))]
331 fn set_prev_validator_rate(&mut self, identity_key: &IdentityKey, rate_data: RateData) {
333 let path = state_key::validators::rate::previous_by_id(identity_key);
334 self.put(path, rate_data)
335 }
336
337 #[instrument(skip(self))]
338 fn set_last_disabled_height(&mut self, identity_key: &IdentityKey, height: u64) {
342 self.nonverifiable_put_raw(
343 state_key::validators::last_disabled::by_id(identity_key)
344 .as_bytes()
345 .to_vec(),
346 height.to_be_bytes().to_vec(),
347 );
348 }
349}
350
351impl<T: StateWrite + ?Sized> ValidatorDataWrite for T {}
352
353#[async_trait]
354pub(crate) trait ValidatorPoolTracker: StateWrite {
355 fn set_validator_pool_size(&mut self, identity_key: &IdentityKey, amount: Amount) {
357 self.put(
358 state_key::validators::pool::balance::by_id(identity_key),
359 amount,
360 );
361 }
362
363 async fn increase_validator_pool_size(
366 &mut self,
367 identity_key: &IdentityKey,
368 add: Amount,
369 ) -> Option<Amount> {
370 let state_path = state_key::validators::pool::balance::by_id(identity_key);
371 let old_supply = self
372 .get(&state_path)
373 .await
374 .expect("no deserialization error expected")
375 .unwrap_or(Amount::zero());
376
377 tracing::debug!(validator_identity = %identity_key, ?add, ?old_supply, "expanding validator pool size");
378
379 if let Some(new_supply) = old_supply.checked_add(&add) {
380 self.put(state_path, new_supply);
381 Some(new_supply)
382 } else {
383 None
384 }
385 }
386
387 async fn decrease_validator_pool_size(
390 &mut self,
391 identity_key: &IdentityKey,
392 sub: Amount,
393 ) -> Option<Amount> {
394 let state_path = state_key::validators::pool::balance::by_id(identity_key);
395 let old_supply = self
396 .get(&state_path)
397 .await
398 .expect("no deserialization error expected")
399 .unwrap_or(Amount::zero());
400
401 tracing::debug!(validator_identity = %identity_key, ?sub, ?old_supply, "contracting validator pool size");
402
403 if let Some(new_supply) = old_supply.checked_sub(&sub) {
404 self.put(state_path, new_supply);
405 Some(new_supply)
406 } else {
407 None
408 }
409 }
410}
411
412impl<T: StateWrite + ?Sized> ValidatorPoolTracker for T {}