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_asset::Value;
14use penumbra_sdk_num::Amount;
15use penumbra_sdk_proto::{
16 state::future::DomainFuture, DomainType, StateReadProto, StateWriteProto,
17};
18use std::pin::Pin;
19use tendermint::PublicKey;
20use tracing::instrument;
21
22#[async_trait]
23pub trait ValidatorDataRead: StateRead {
24 async fn get_validator_info(
25 &self,
26 identity_key: &IdentityKey,
27 ) -> Result<Option<validator::Info>> {
28 let validator = self.get_validator_definition(identity_key).await?;
29 let status = self.get_validator_status(identity_key).await?;
30 let rate_data = self.get_validator_rate(identity_key).await?;
31
32 match (validator, status, rate_data) {
33 (Some(validator), Some(status), Some(rate_data)) => Ok(Some(validator::Info {
34 validator,
35 status,
36 rate_data,
37 })),
38 _ => Ok(None),
39 }
40 }
41
42 fn get_validator_state(
43 &self,
44 identity_key: &IdentityKey,
45 ) -> DomainFuture<validator::State, Self::GetRawFut> {
46 self.get(&state_key::validators::state::by_id(identity_key))
47 }
48
49 async fn get_validator_bonding_state(
50 &self,
51 identity_key: &IdentityKey,
52 ) -> Option<validator::BondingState> {
53 self.get(&state_key::validators::pool::bonding_state::by_id(
54 identity_key,
55 ))
56 .await
57 .expect("no deserialization error expected")
58 }
59
60 async fn get_validator_pool_size(&self, identity_key: &IdentityKey) -> Option<Amount> {
62 self.get(&state_key::validators::pool::balance::by_id(identity_key))
63 .await
64 .expect("no deserialization error expected")
65 }
66
67 async fn get_validator_status(
69 &self,
70 identity_key: &IdentityKey,
71 ) -> Result<Option<validator::Status>> {
72 let bonding_state = self.get_validator_bonding_state(identity_key).await;
73 let state = self.get_validator_state(identity_key).await?;
74 let power = self.get_validator_power(identity_key).await?;
75 let identity_key = identity_key.clone();
76 match (state, power, bonding_state) {
77 (Some(state), Some(voting_power), Some(bonding_state)) => Ok(Some(validator::Status {
78 identity_key,
79 state,
80 voting_power,
81 bonding_state,
82 })),
83 _ => Ok(None),
84 }
85 }
86
87 fn get_validator_rate(
88 &self,
89 identity_key: &IdentityKey,
90 ) -> Pin<Box<dyn Future<Output = Result<Option<RateData>>> + Send + 'static>> {
91 self.get(&state_key::validators::rate::current_by_id(identity_key))
92 .boxed()
93 }
94
95 async fn get_prev_validator_rate(&self, identity_key: &IdentityKey) -> Option<RateData> {
96 self.get(&state_key::validators::rate::previous_by_id(identity_key))
97 .await
98 .expect("no deserialization error expected")
99 }
100
101 fn get_validator_power(
102 &self,
103 validator: &IdentityKey,
104 ) -> DomainFuture<Amount, Self::GetRawFut> {
105 self.get(&state_key::validators::power::by_id(validator))
106 }
107
108 async fn get_last_disabled_height(&self, identity_key: &IdentityKey) -> Option<u64> {
111 self.nonverifiable_get_raw(
112 state_key::validators::last_disabled::by_id(identity_key).as_bytes(),
113 )
114 .await
115 .expect("no deserialization error expected")
116 .map(|bytes| u64::from_be_bytes(bytes.try_into().expect("we only write 8 bytes")))
117 }
118
119 async fn get_validator_definition(
120 &self,
121 identity_key: &IdentityKey,
122 ) -> Result<Option<Validator>> {
123 self.get(&state_key::validators::definitions::by_id(identity_key))
124 .await
125 }
126
127 fn get_validator_uptime(
128 &self,
129 identity_key: &IdentityKey,
130 ) -> DomainFuture<Uptime, Self::GetRawFut> {
131 let key = state_key::validators::uptime::by_id(identity_key);
132 self.nonverifiable_get(key.as_bytes())
133 }
134
135 async fn lookup_identity_key_by_consensus_key(&self, ck: &PublicKey) -> Option<IdentityKey> {
136 self.get(&state_key::validators::lookup_by::consensus_key(ck))
137 .await
138 .expect("no deserialization error")
139 }
140
141 async fn lookup_consensus_key_by_comet_address(&self, address: &[u8; 20]) -> Option<PublicKey> {
142 self.get(&state_key::validators::lookup_by::cometbft_address(address))
143 .await
144 .expect("no deserialization error")
145 }
146
147 async fn get_validator_definition_by_consensus_key(
150 &self,
151 ck: &PublicKey,
152 ) -> Result<Option<Validator>> {
153 if let Some(identity_key) = self.lookup_identity_key_by_consensus_key(ck).await {
154 self.get_validator_definition(&identity_key).await
155 } else {
156 return Ok(None);
157 }
158 }
159
160 async fn get_validator_definition_by_cometbft_address(
161 &self,
162 address: &[u8; 20],
163 ) -> Result<Option<Validator>> {
164 if let Some(consensus_key) = self.lookup_consensus_key_by_comet_address(address).await {
165 self.get_validator_definition_by_consensus_key(&consensus_key)
166 .await
167 } else {
168 return Ok(None);
169 }
170 }
171
172 async fn compute_unbonding_height(
180 &self,
181 id: &IdentityKey,
182 start_height: u64,
183 ) -> Result<Option<u64>> {
184 let Some(val_bonding_state) = self.get_validator_bonding_state(id).await else {
185 anyhow::bail!(
186 "validator bonding state not tracked (validator_identity={})",
187 id
188 )
189 };
190
191 let min_block_delay = self.get_stake_params().await?.unbonding_delay;
192 let upper_bound_height = start_height.saturating_add(min_block_delay);
193
194 let unbonding_height = match val_bonding_state {
195 Bonded => Some(upper_bound_height),
197 Unbonding { unbonds_at_height } => {
199 if unbonds_at_height > start_height {
200 Some(unbonds_at_height.min(upper_bound_height))
206 } else {
207 None
213 }
214 }
215 Unbonded => None,
217 };
218
219 Ok(unbonding_height)
220 }
221
222 fn fetch_validator_consensus_key(
226 &self,
227 identity_key: &IdentityKey,
228 ) -> Pin<Box<dyn Future<Output = Result<Option<PublicKey>>> + Send + 'static>> {
229 use futures::TryFutureExt;
230 self.get(&state_key::validators::definitions::by_id(identity_key))
231 .map_ok(|opt: Option<Validator>| opt.map(|v: Validator| v.consensus_key))
232 .boxed()
233 }
234}
235
236impl<T: StateRead + ?Sized> ValidatorDataRead for T {}
237
238#[async_trait]
239pub trait ValidatorPoolDeposit: StateWrite {
240 async fn deposit_to_validator_pool(
248 &mut self,
249 validator_ik: &IdentityKey,
250 unbonded_deposit: Amount,
251 ) -> Option<Value> {
252 let state_path = state_key::validators::pool::balance::by_id(validator_ik);
253 let old_supply = self
254 .get(&state_path)
255 .await
256 .expect("no deserialization error expected")
257 .unwrap_or(Amount::zero());
258
259 tracing::debug!(validator_identity = %validator_ik, ?unbonded_deposit, ?old_supply, "depositing into validator pool");
260
261 let new_supply = match old_supply.checked_add(&unbonded_deposit) {
263 Some(new_supply) => new_supply,
264 None => {
265 tracing::warn!(
266 validator_identity = %validator_ik,
267 ?unbonded_deposit,
268 ?old_supply,
269 "deposit failed: overflow"
270 );
271 return None;
272 }
273 };
274
275 let bonded_deposit = if let Some(rate) = self
277 .get_validator_rate(validator_ik)
278 .await
279 .expect("no deserialization error expected")
280 {
281 use penumbra_sdk_sct::component::clock::EpochRead;
282 let current_epoch = self.get_current_epoch().await.expect("epoch is always set");
283 rate.build_delegate(current_epoch, unbonded_deposit)
284 .delegation_value()
285 } else {
286 return None;
288 };
289
290 self.put(state_path, new_supply);
292
293 Some(bonded_deposit)
294 }
295}
296
297impl<T: StateWrite + ?Sized> ValidatorPoolDeposit for T {}
298
299#[async_trait]
300pub(crate) trait ValidatorDataWrite: StateWrite {
301 fn set_validator_uptime(&mut self, identity_key: &IdentityKey, uptime: Uptime) {
302 self.nonverifiable_put_raw(
303 state_key::validators::uptime::by_id(identity_key)
304 .as_bytes()
305 .to_vec(),
306 uptime.encode_to_vec(),
307 );
308 }
309
310 fn set_validator_bonding_state(
311 &mut self,
312 identity_key: &IdentityKey,
313 state: validator::BondingState,
314 ) {
315 tracing::debug!(?state, validator_identity = %identity_key, "set bonding state for validator");
316 self.put(
317 state_key::validators::pool::bonding_state::by_id(identity_key),
318 state.clone(),
319 );
320 self.record_proto(
321 event::EventValidatorBondingStateChange {
322 identity_key: *identity_key,
323 bonding_state: state,
324 }
325 .to_proto(),
326 );
327 }
328
329 #[instrument(skip(self))]
330 fn set_validator_power(
331 &mut self,
332 identity_key: &IdentityKey,
333 voting_power: Amount,
334 ) -> Result<()> {
335 tracing::debug!(validator_identity = ?identity_key, ?voting_power, "setting validator power");
336 if voting_power.value() > MAX_VOTING_POWER {
337 anyhow::bail!("voting power exceeds maximum")
338 }
339 self.put(
340 state_key::validators::power::by_id(identity_key),
341 voting_power,
342 );
343 self.record_proto(
344 event::EventValidatorVotingPowerChange {
345 identity_key: *identity_key,
346 voting_power,
347 }
348 .to_proto(),
349 );
350
351 Ok(())
352 }
353
354 #[instrument(skip(self))]
355 fn set_initial_validator_state(
356 &mut self,
357 id: &IdentityKey,
358 initial_state: State,
359 ) -> Result<()> {
360 tracing::debug!(validator_identity = %id, ?initial_state, "setting initial validator state");
361 if !matches!(initial_state, State::Active | State::Defined) {
362 anyhow::bail!("invalid initial validator state");
363 }
364
365 self.put(state_key::validators::state::by_id(id), initial_state);
366 self.record_proto(
367 event::EventValidatorStateChange {
368 identity_key: *id,
369 state: initial_state,
370 }
371 .to_proto(),
372 );
373 Ok(())
374 }
375
376 #[instrument(skip(self))]
377 fn set_validator_rate_data(&mut self, identity_key: &IdentityKey, rate_data: RateData) {
378 tracing::debug!("setting validator rate data");
379 self.put(
380 state_key::validators::rate::current_by_id(identity_key),
381 rate_data.clone(),
382 );
383 self.record_proto(
384 event::EventRateDataChange {
385 identity_key: *identity_key,
386 rate_data,
387 }
388 .to_proto(),
389 );
390 }
391
392 #[instrument(skip(self))]
393 fn set_prev_validator_rate(&mut self, identity_key: &IdentityKey, rate_data: RateData) {
395 let path = state_key::validators::rate::previous_by_id(identity_key);
396 self.put(path, rate_data)
397 }
398
399 #[instrument(skip(self))]
400 fn set_last_disabled_height(&mut self, identity_key: &IdentityKey, height: u64) {
404 self.nonverifiable_put_raw(
405 state_key::validators::last_disabled::by_id(identity_key)
406 .as_bytes()
407 .to_vec(),
408 height.to_be_bytes().to_vec(),
409 );
410 }
411}
412
413impl<T: StateWrite + ?Sized> ValidatorDataWrite for T {}
414
415#[async_trait]
416pub(crate) trait ValidatorPoolTracker: StateWrite {
417 fn set_validator_pool_size(&mut self, identity_key: &IdentityKey, amount: Amount) {
419 self.put(
420 state_key::validators::pool::balance::by_id(identity_key),
421 amount,
422 );
423 }
424
425 async fn increase_validator_pool_size(
428 &mut self,
429 identity_key: &IdentityKey,
430 add: Amount,
431 ) -> Option<Amount> {
432 let state_path = state_key::validators::pool::balance::by_id(identity_key);
433 let old_supply = self
434 .get(&state_path)
435 .await
436 .expect("no deserialization error expected")
437 .unwrap_or(Amount::zero());
438
439 tracing::debug!(validator_identity = %identity_key, ?add, ?old_supply, "expanding validator pool size");
440
441 if let Some(new_supply) = old_supply.checked_add(&add) {
442 self.put(state_path, new_supply);
443 Some(new_supply)
444 } else {
445 None
446 }
447 }
448
449 async fn decrease_validator_pool_size(
452 &mut self,
453 identity_key: &IdentityKey,
454 sub: Amount,
455 ) -> Option<Amount> {
456 let state_path = state_key::validators::pool::balance::by_id(identity_key);
457 let old_supply = self
458 .get(&state_path)
459 .await
460 .expect("no deserialization error expected")
461 .unwrap_or(Amount::zero());
462
463 tracing::debug!(validator_identity = %identity_key, ?sub, ?old_supply, "contracting validator pool size");
464
465 if let Some(new_supply) = old_supply.checked_sub(&sub) {
466 self.put(state_path, new_supply);
467 Some(new_supply)
468 } else {
469 None
470 }
471 }
472}
473
474impl<T: StateWrite + ?Sized> ValidatorPoolTracker for T {}