1use std::{
2 collections::{BTreeMap, BTreeSet},
3 str::FromStr,
4};
5
6use anyhow::{Context, Result};
7use async_trait::async_trait;
8use cnidarium::{StateRead, StateWrite};
9use futures::StreamExt;
10use ibc_types::core::client::ClientId;
11use penumbra_sdk_asset::{asset, Value, STAKING_TOKEN_DENOM};
12use penumbra_sdk_ibc::component::ClientStateReadExt as _;
13use penumbra_sdk_ibc::component::ClientStateWriteExt as _;
14use penumbra_sdk_num::Amount;
15use penumbra_sdk_proto::{StateReadProto, StateWriteProto};
16use penumbra_sdk_sct::{
17 component::{clock::EpochRead, tree::SctRead},
18 Nullifier,
19};
20use penumbra_sdk_shielded_pool::component::AssetRegistryRead;
21use penumbra_sdk_stake::{
22 component::{validator_handler::ValidatorDataRead, ConsensusIndexRead},
23 DelegationToken, GovernanceKey, IdentityKey,
24};
25use penumbra_sdk_tct as tct;
26use tokio::task::JoinSet;
27use tracing::instrument;
28
29use penumbra_sdk_stake::{rate::RateData, validator};
30
31use crate::{
32 change::ParameterChange,
33 params::GovernanceParameters,
34 proposal::{Proposal, ProposalPayload},
35 proposal_state::State as ProposalState,
36 state_key::persistent_flags,
37 validator_vote::action::ValidatorVoteReason,
38 vote::Vote,
39};
40use crate::{state_key, tally::Tally};
41
42#[async_trait]
43pub trait StateReadExt: StateRead + penumbra_sdk_stake::StateReadExt {
44 async fn is_pre_upgrade_height(&self) -> Result<bool> {
48 let Some(next_upgrade_height) = self
49 .nonverifiable_get_raw(state_key::upgrades::next_upgrade().as_bytes())
50 .await?
51 else {
52 return Ok(false);
53 };
54
55 let next_upgrade_height = u64::from_be_bytes(next_upgrade_height.as_slice().try_into()?);
56
57 let current_height = self.get_block_height().await?;
58 Ok(current_height.saturating_add(1) == next_upgrade_height)
59 }
60
61 async fn get_governance_params(&self) -> Result<GovernanceParameters> {
63 self.get(state_key::governance_params())
64 .await?
65 .ok_or_else(|| anyhow::anyhow!("Missing GovernanceParameters"))
66 }
67
68 async fn next_proposal_id(&self) -> Result<u64> {
70 Ok(self
71 .get_proto::<u64>(state_key::next_proposal_id())
72 .await?
73 .unwrap_or_default())
74 }
75
76 async fn proposal_definition(&self, proposal_id: u64) -> Result<Option<Proposal>> {
78 Ok(self
79 .get(&state_key::proposal_definition(proposal_id))
80 .await?)
81 }
82
83 async fn proposal_payload(&self, proposal_id: u64) -> Result<Option<ProposalPayload>> {
85 Ok(self
86 .get(&state_key::proposal_definition(proposal_id))
87 .await?
88 .map(|p: Proposal| p.payload))
89 }
90
91 async fn proposal_deposit_amount(&self, proposal_id: u64) -> Result<Option<Amount>> {
93 self.get(&state_key::proposal_deposit_amount(proposal_id))
94 .await
95 }
96
97 async fn proposal_state(&self, proposal_id: u64) -> Result<Option<ProposalState>> {
99 Ok(self
100 .get::<ProposalState>(&state_key::proposal_state(proposal_id))
101 .await?)
102 }
103
104 async fn unfinished_proposals(&self) -> Result<BTreeSet<u64>> {
106 let prefix = state_key::all_unfinished_proposals();
107 let mut stream = self.prefix_proto(prefix);
108 let mut proposals = BTreeSet::new();
109 while let Some((key, ())) = stream.next().await.transpose()? {
110 let proposal_id = u64::from_str(
111 key.rsplit('/')
112 .next()
113 .context("invalid key for unfinished proposal")?,
114 )?;
115 proposals.insert(proposal_id);
116 }
117 Ok(proposals)
118 }
119
120 async fn validator_vote(
122 &self,
123 proposal_id: u64,
124 identity_key: IdentityKey,
125 ) -> Result<Option<Vote>> {
126 Ok(self
127 .get::<Vote>(&state_key::validator_vote(proposal_id, identity_key))
128 .await?)
129 }
130
131 async fn proposal_voting_start(&self, proposal_id: u64) -> Result<Option<u64>> {
133 Ok(self
134 .get_proto::<u64>(&state_key::proposal_voting_start(proposal_id))
135 .await?)
136 }
137
138 async fn proposal_voting_end(&self, proposal_id: u64) -> Result<Option<u64>> {
140 Ok(self
141 .get_proto::<u64>(&state_key::proposal_voting_end(proposal_id))
142 .await?)
143 }
144
145 async fn proposal_voting_start_position(
147 &self,
148 proposal_id: u64,
149 ) -> Result<Option<tct::Position>> {
150 Ok(self
151 .get_proto::<u64>(&state_key::proposal_voting_start_position(proposal_id))
152 .await?
153 .map(Into::into))
154 }
155
156 async fn total_voting_power_at_proposal_start(&self, proposal_id: u64) -> Result<u64> {
158 Ok(self
159 .validator_voting_power_at_proposal_start(proposal_id)
160 .await?
161 .values()
162 .copied()
163 .sum())
164 }
165
166 async fn check_nullifier_unvoted_for_proposal(
168 &self,
169 proposal_id: u64,
170 nullifier: &Nullifier,
171 ) -> Result<()> {
172 if let Some(height) = self
173 .get_proto::<u64>(&state_key::voted_nullifier_lookup_for_proposal(
174 proposal_id,
175 nullifier,
176 ))
177 .await?
178 {
179 anyhow::bail!(
181 "nullifier {nullifier} was already used for voting on proposal {proposal_id} at height {height}",
182 );
183 }
184
185 Ok(())
186 }
187
188 async fn rate_data_at_proposal_start(
190 &self,
191 proposal_id: u64,
192 identity_key: IdentityKey,
193 ) -> Result<Option<RateData>> {
194 self.get(&state_key::rate_data_at_proposal_start(
195 proposal_id,
196 identity_key,
197 ))
198 .await
199 }
200
201 async fn check_proposal_votable(&self, proposal_id: u64) -> Result<()> {
203 if let Some(proposal_state) = self.proposal_state(proposal_id).await? {
204 use crate::proposal_state::State::*;
205 match proposal_state {
206 Voting => {
207 }
209 Withdrawn { .. } => {
210 anyhow::bail!("proposal {} has already been withdrawn", proposal_id)
211 }
212 Finished { .. } | Claimed { .. } => {
213 anyhow::bail!("voting on proposal {} has already concluded", proposal_id)
214 }
215 }
216 } else {
217 anyhow::bail!("proposal {} does not exist", proposal_id);
218 }
219
220 Ok(())
221 }
222
223 async fn check_proposal_started_at_position(
225 &self,
226 proposal_id: u64,
227 claimed_position: tct::Position,
228 ) -> Result<()> {
229 if let Some(position) = self.proposal_voting_start_position(proposal_id).await? {
230 if position != claimed_position {
231 anyhow::bail!(
232 "proposal {} was not started at claimed start position of {:?}",
233 proposal_id,
234 claimed_position
235 );
236 }
237 } else {
238 anyhow::bail!("proposal {} does not exist", proposal_id);
239 }
240
241 Ok(())
242 }
243
244 async fn check_nullifier_unspent_before_start_block_height(
246 &self,
247 proposal_id: u64,
248 nullifier: &Nullifier,
249 ) -> Result<()> {
250 let Some(start_height) = self.proposal_voting_start(proposal_id).await? else {
251 anyhow::bail!("proposal {} does not exist", proposal_id);
252 };
253
254 if let Some(spend_info) = self.spend_info(*nullifier).await? {
255 if spend_info.spend_height < start_height {
256 anyhow::bail!(
257 "nullifier {} was already spent at block height {} before proposal started at block height {}",
258 nullifier,
259 spend_info.spend_height,
260 start_height
261 );
262 }
263 }
264
265 Ok(())
266 }
267
268 async fn validator_by_delegation_asset(&self, asset_id: asset::Id) -> Result<IdentityKey> {
270 let Some(denom_metadata) = self.denom_metadata_by_asset(&asset_id).await else {
272 anyhow::bail!("asset ID {} does not correspond to a known denom", asset_id);
273 };
274
275 let validator_identity = DelegationToken::try_from(denom_metadata)?.validator();
278
279 Ok(validator_identity)
280 }
281
282 async fn check_unbonded_amount_correct_exchange_for_proposal(
285 &self,
286 proposal_id: u64,
287 value: &Value,
288 unbonded_amount: &Amount,
289 ) -> Result<()> {
290 let validator_identity = self.validator_by_delegation_asset(value.asset_id).await?;
291
292 let Some(rate_data) = self
294 .rate_data_at_proposal_start(proposal_id, validator_identity)
295 .await?
296 else {
297 anyhow::bail!(
298 "validator {} was not active at the start of proposal {}",
299 validator_identity,
300 proposal_id
301 );
302 };
303
304 if rate_data.unbonded_amount(value.amount).value() != unbonded_amount.value() {
306 anyhow::bail!(
307 "unbonded amount {}{} does not correspond to {} staked delegation tokens for validator {} using the exchange rate at the start of proposal {}",
308 unbonded_amount,
309 *STAKING_TOKEN_DENOM,
310 value.amount,
311 validator_identity,
312 proposal_id,
313 );
314 }
315
316 Ok(())
317 }
318
319 async fn check_height_in_future_of_voting_end(&self, height: u64) -> Result<()> {
320 let block_height = self.get_block_height().await?;
321 let voting_blocks = self.get_governance_params().await?.proposal_voting_blocks;
322 let voting_end_height = block_height + voting_blocks;
323
324 if height < voting_end_height {
325 anyhow::bail!(
326 "effective height {} is less than the block height {} for the end of the voting period",
327 height,
328 voting_end_height
329 );
330 }
331 Ok(())
332 }
333
334 async fn check_validator_has_not_voted(
336 &self,
337 proposal_id: u64,
338 identity_key: &IdentityKey,
339 ) -> Result<()> {
340 if let Some(_vote) = self.validator_vote(proposal_id, *identity_key).await? {
341 anyhow::bail!(
342 "validator {} has already voted on proposal {}",
343 identity_key,
344 proposal_id
345 );
346 }
347
348 Ok(())
349 }
350
351 async fn check_governance_key_matches_validator(
353 &self,
354 identity_key: &IdentityKey,
355 governance_key: &GovernanceKey,
356 ) -> Result<()> {
357 if let Some(validator) = self.get_validator_definition(identity_key).await? {
358 if validator.governance_key != *governance_key {
359 anyhow::bail!(
360 "governance key {} does not match validator {}",
361 governance_key,
362 identity_key
363 );
364 }
365 } else {
366 anyhow::bail!("validator {} does not exist", identity_key);
367 }
368
369 Ok(())
370 }
371
372 async fn check_proposal_claimable(&self, proposal_id: u64) -> Result<()> {
374 if let Some(proposal_state) = self.proposal_state(proposal_id).await? {
375 match proposal_state {
376 ProposalState::Voting => {
377 anyhow::bail!("proposal {} is still voting", proposal_id)
378 }
379 ProposalState::Withdrawn { .. } => {
380 anyhow::bail!(
381 "proposal {} has been withdrawn but voting has not concluded",
382 proposal_id
383 )
384 }
385 ProposalState::Finished { .. } => {
386 }
388 ProposalState::Claimed { .. } => {
389 anyhow::bail!(
390 "the deposit for proposal {} has already been claimed",
391 proposal_id
392 )
393 }
394 }
395 } else {
396 anyhow::bail!("proposal {} does not exist", proposal_id);
397 }
398
399 Ok(())
400 }
401
402 async fn check_proposal_claim_valid_deposit(
404 &self,
405 proposal_id: u64,
406 claim_deposit_amount: Amount,
407 ) -> Result<()> {
408 if let Some(proposal_deposit_amount) = self.proposal_deposit_amount(proposal_id).await? {
409 if claim_deposit_amount != proposal_deposit_amount {
410 anyhow::bail!(
411 "proposal deposit claim for {}{} does not match proposal deposit of {}{}",
412 claim_deposit_amount,
413 *STAKING_TOKEN_DENOM,
414 proposal_deposit_amount,
415 *STAKING_TOKEN_DENOM,
416 );
417 }
418 } else {
419 anyhow::bail!("proposal {} does not exist", proposal_id);
420 }
421
422 Ok(())
423 }
424
425 async fn specific_validator_voting_power_at_proposal_start(
427 &self,
428 proposal_id: u64,
429 identity_key: IdentityKey,
430 ) -> Result<u64> {
431 self.get_proto(&state_key::voting_power_at_proposal_start(
432 proposal_id,
433 identity_key,
434 ))
435 .await
436 .map(Option::unwrap_or_default)
437 }
438
439 async fn validator_voting_power_at_proposal_start(
441 &self,
442 proposal_id: u64,
443 ) -> Result<BTreeMap<IdentityKey, u64>> {
444 let mut powers = BTreeMap::new();
445
446 let prefix = state_key::all_voting_power_at_proposal_start(proposal_id);
447 let mut stream = self.prefix_proto(&prefix);
448
449 while let Some((key, power)) = stream.next().await.transpose()? {
450 let identity_key = key
451 .rsplit('/')
452 .next()
453 .ok_or_else(|| {
454 anyhow::anyhow!(
455 "incorrect key format for validator voting power at proposal start"
456 )
457 })?
458 .parse()?;
459 powers.insert(identity_key, power);
460 }
461
462 Ok(powers)
463 }
464
465 async fn check_validator_active_at_proposal_start(
467 &self,
468 proposal_id: u64,
469 identity_key: &IdentityKey,
470 ) -> Result<()> {
471 if self
472 .get_proto::<u64>(&state_key::voting_power_at_proposal_start(
473 proposal_id,
474 *identity_key,
475 ))
476 .await?
477 .is_none()
478 {
479 anyhow::bail!(
480 "validator {} was not active at the start of proposal {}",
481 identity_key,
482 proposal_id
483 );
484 }
485
486 Ok(())
487 }
488
489 async fn validator_votes(&self, proposal_id: u64) -> Result<BTreeMap<IdentityKey, Vote>> {
491 let mut votes = BTreeMap::new();
492
493 let prefix = state_key::all_validator_votes_for_proposal(proposal_id);
494 let mut stream = self.prefix(&prefix);
495
496 while let Some((key, vote)) = stream.next().await.transpose()? {
497 let identity_key = key
498 .rsplit('/')
499 .next()
500 .ok_or_else(|| anyhow::anyhow!("incorrect key format for validator vote"))?
501 .parse()?;
502 votes.insert(identity_key, vote);
503 }
504
505 Ok(votes)
506 }
507
508 async fn tallied_delegator_votes(
511 &self,
512 proposal_id: u64,
513 ) -> Result<BTreeMap<IdentityKey, Tally>> {
514 let mut tallies = BTreeMap::new();
515
516 let prefix = state_key::all_tallied_delegator_votes_for_proposal(proposal_id);
517 let mut stream = self.prefix(&prefix);
518
519 while let Some((key, tally)) = stream.next().await.transpose()? {
520 let identity_key = key
521 .rsplit('/')
522 .next()
523 .ok_or_else(|| anyhow::anyhow!("incorrect key format for delegator vote tally"))?
524 .parse()?;
525 tallies.insert(identity_key, tally);
526 }
527
528 Ok(tallies)
529 }
530
531 async fn current_tally(&self, proposal_id: u64) -> Result<Tally> {
534 let validator_powers = self
535 .validator_voting_power_at_proposal_start(proposal_id)
536 .await?;
537 let mut validator_votes = self.validator_votes(proposal_id).await?;
538 let mut delegator_tallies = self.tallied_delegator_votes(proposal_id).await?;
539
540 let mut tally = Tally::default();
542 for (validator, power) in validator_powers.into_iter() {
543 let delegator_tally = delegator_tallies.remove(&validator).unwrap_or_default();
544 if let Some(vote) = validator_votes.remove(&validator) {
545 let effective_power = power.saturating_sub(delegator_tally.total());
550 tally += (vote, effective_power).into();
551 }
552 tally += delegator_tally;
554 }
555
556 assert!(
557 validator_votes.is_empty(),
558 "no inactive validator should have voted"
559 );
560 assert!(
561 delegator_tallies.is_empty(),
562 "no delegator should have been able to vote for an inactive validator"
563 );
564
565 Ok(tally)
566 }
567
568 async fn param_changes_for_height(&self, height: u64) -> Result<Option<ParameterChange>> {
570 self.get(&state_key::param_changes_for_height(height)).await
571 }
572
573 fn proposal_started(&self) -> bool {
575 self.object_get::<()>(state_key::proposal_started())
576 .is_some()
577 }
578
579 async fn is_chain_halted(&self) -> bool {
580 self.nonverifiable_get_proto(state_key::persistent_flags::halt_bit().as_bytes())
581 .await
582 .expect("no deserialization errors")
583 .unwrap_or_default()
584 }
585}
586
587impl<T: StateRead + penumbra_sdk_stake::StateReadExt + ?Sized> StateReadExt for T {}
588
589#[async_trait]
590pub trait StateWriteExt: StateWrite + penumbra_sdk_ibc::component::ConnectionStateWriteExt {
591 fn put_governance_params(&mut self, params: GovernanceParameters) {
593 self.put(state_key::governance_params().into(), params)
595 }
596
597 fn init_proposal_counter(&mut self) {
599 self.put_proto(state_key::next_proposal_id().to_string(), 0);
600 }
601
602 async fn new_proposal(&mut self, proposal: &Proposal) -> Result<u64> {
604 let proposal_id = self.next_proposal_id().await?;
605 if proposal_id != proposal.id {
606 anyhow::bail!(
607 "proposal id {} does not match next proposal id {}",
608 proposal.id,
609 proposal_id
610 );
611 }
612
613 let mut js = JoinSet::new();
615 let mut validator_identity_stream = self.consensus_set_stream()?;
616 while let Some(identity_key) = validator_identity_stream.next().await {
617 let identity_key = identity_key?;
618
619 let state = self.get_validator_state(&identity_key);
620 let rate_data = self.get_validator_rate(&identity_key);
621 let power: penumbra_sdk_proto::state::future::DomainFuture<
622 Amount,
623 <Self as StateRead>::GetRawFut,
624 > = self.get_validator_power(&identity_key);
625 js.spawn(async move {
626 let state = state
627 .await?
628 .expect("every known validator must have a recorded state");
629 let per_validator = if state == validator::State::Active {
631 let rate_data = rate_data
632 .await?
633 .expect("every known validator must have a recorded current rate");
634 let power = power
635 .await?
636 .expect("every known validator must have a recorded current power");
637 Some((identity_key, rate_data, power))
638 } else {
639 None
640 };
641 anyhow::Ok(per_validator)
643 });
644 }
645 while let Some(per_validator) = js.join_next().await.transpose()? {
648 if let Some((identity_key, rate_data, power)) = per_validator? {
649 self.put(
650 state_key::rate_data_at_proposal_start(proposal_id, identity_key),
651 rate_data,
652 );
653 self.put(
654 state_key::voting_power_at_proposal_start(proposal_id, identity_key),
655 power,
656 )
657 }
658 }
659
660 self.put_proto(state_key::next_proposal_id().to_owned(), proposal_id + 1);
662
663 self.put(
665 state_key::proposal_definition(proposal_id),
666 proposal.clone(),
667 );
668
669 Ok(proposal_id)
671 }
672
673 async fn mark_nullifier_voted(&mut self, proposal_id: u64, nullifier: &Nullifier) {
675 self.put_proto(
676 state_key::voted_nullifier_lookup_for_proposal(proposal_id, nullifier),
677 self.get_block_height()
678 .await
679 .expect("block height should be set"),
680 );
681 }
682
683 fn mark_proposal_started(&mut self) {
685 self.object_put(state_key::proposal_started(), ());
686 }
687
688 fn put_deposit_amount(&mut self, proposal_id: u64, amount: Amount) {
690 self.put(state_key::proposal_deposit_amount(proposal_id), amount);
691 }
692
693 fn put_proposal_state(&mut self, proposal_id: u64, state: ProposalState) {
695 self.put(state_key::proposal_state(proposal_id), state.clone());
697
698 match &state {
699 ProposalState::Voting | ProposalState::Withdrawn { .. } => {
700 self.put_proto(state_key::unfinished_proposal(proposal_id), ());
703 }
704 ProposalState::Finished { .. } | ProposalState::Claimed { .. } => {
705 self.delete(state_key::unfinished_proposal(proposal_id));
708 }
709 }
710 }
711
712 fn cast_validator_vote(
714 &mut self,
715 proposal_id: u64,
716 identity_key: IdentityKey,
717 vote: Vote,
718 reason: ValidatorVoteReason,
719 ) {
720 self.put(state_key::validator_vote(proposal_id, identity_key), vote);
722 self.put(
724 state_key::validator_vote_reason(proposal_id, identity_key),
725 reason,
726 );
727 }
728
729 fn put_proposal_voting_start(&mut self, proposal_id: u64, end_block: u64) {
731 self.put_proto(state_key::proposal_voting_start(proposal_id), end_block);
732 }
733
734 fn put_proposal_voting_end(&mut self, proposal_id: u64, end_block: u64) {
736 self.put_proto(state_key::proposal_voting_end(proposal_id), end_block);
737 }
738
739 fn put_proposal_voting_start_position(
741 &mut self,
742 proposal_id: u64,
743 start_position: tct::Position,
744 ) {
745 self.put_proto(
746 state_key::proposal_voting_start_position(proposal_id),
747 u64::from(start_position),
748 );
749 }
750
751 async fn mark_nullifier_voted_on_proposal(&mut self, proposal_id: u64, nullifier: &Nullifier) {
753 self.put_proto(
754 state_key::voted_nullifier_lookup_for_proposal(proposal_id, nullifier),
755 self.get_block_height()
756 .await
757 .expect("block height should be set"),
758 );
759 }
760
761 async fn cast_delegator_vote(
763 &mut self,
764 proposal_id: u64,
765 identity_key: IdentityKey,
766 vote: Vote,
767 nullifier: &Nullifier,
768 unbonded_amount: Amount,
769 ) -> Result<()> {
770 let power = unbonded_amount.value() as u64;
772 let tally: Tally = (vote, power).into();
773
774 self.put(
776 state_key::untallied_delegator_vote(proposal_id, identity_key, nullifier),
777 tally,
778 );
779
780 Ok(())
781 }
782
783 #[instrument(skip(self))]
785 async fn tally_delegator_votes(&mut self, just_for_proposal: Option<u64>) -> Result<()> {
786 let prefix = if let Some(proposal_id) = just_for_proposal {
788 state_key::all_untallied_delegator_votes_for_proposal(proposal_id)
789 } else {
790 state_key::all_untallied_delegator_votes().to_string()
791 };
792 let mut prefix_stream = self.prefix(&prefix);
793
794 let mut keys_to_delete = vec![];
797 let mut new_tallies: BTreeMap<u64, BTreeMap<IdentityKey, Tally>> = BTreeMap::new();
798
799 while let Some((key, tally)) = prefix_stream.next().await.transpose()? {
800 let mut reverse_path_elements = key.rsplit('/');
802 reverse_path_elements.next(); let identity_key = reverse_path_elements
804 .next()
805 .ok_or_else(|| {
806 anyhow::anyhow!("unexpected key format for untallied delegator vote")
807 })?
808 .parse()?;
809 let proposal_id = reverse_path_elements
810 .next()
811 .ok_or_else(|| {
812 anyhow::anyhow!("unexpected key format for untallied delegator vote")
813 })?
814 .parse()?;
815
816 let mut current_tally = self
818 .get::<Tally>(&state_key::tallied_delegator_votes(
819 proposal_id,
820 identity_key,
821 ))
822 .await?
823 .unwrap_or_default();
824
825 current_tally += tally;
827
828 new_tallies
830 .entry(proposal_id)
831 .or_default()
832 .insert(identity_key, current_tally);
833
834 keys_to_delete.push(key);
836 }
837
838 drop(prefix_stream);
840
841 for key in keys_to_delete {
843 self.delete(key);
844 }
845
846 for (proposal_id, new_tallies_for_proposal) in new_tallies {
848 for (identity_key, tally) in new_tallies_for_proposal {
849 tracing::debug!(
850 proposal_id,
851 identity_key = %identity_key,
852 yes = %tally.yes(),
853 no = %tally.no(),
854 abstain = %tally.abstain(),
855 "tallying delegator votes"
856 );
857 self.put(
858 state_key::tallied_delegator_votes(proposal_id, identity_key),
859 tally,
860 );
861 }
862 }
863
864 Ok(())
865 }
866
867 #[instrument(skip(self))]
868 async fn enact_proposal(
869 &mut self,
870 proposal_id: u64,
871 payload: &ProposalPayload,
872 ) -> Result<Result<()>> {
874 match payload {
875 ProposalPayload::Signaling { .. } => {
876 tracing::info!("signaling proposal passed, nothing to do");
878 }
879 ProposalPayload::Emergency { halt_chain } => {
880 if *halt_chain {
882 tracing::info!("emergency proposal passed calling for immediate chain halt");
885 self.signal_halt();
886 }
887 }
888 ProposalPayload::ParameterChange(change) => {
889 let current_height = self.get_block_height().await?;
890 let change_height = current_height + 1;
892 tracing::info!(
893 change_height,
894 ?change,
895 "parameter change proposal passed, scheduling for next block"
896 );
897 self.put(
901 state_key::param_changes_for_height(change_height),
902 change.clone(),
903 );
904 }
905 ProposalPayload::CommunityPoolSpend {
906 transaction_plan: _,
907 } => {
908 self.deliver_community_pool_transaction(proposal_id).await?;
911 }
912 ProposalPayload::UpgradePlan { height } => {
913 tracing::info!(target_height = height, "upgrade plan proposal passed");
914 self.signal_upgrade(*height).await?;
915 }
916 ProposalPayload::FreezeIbcClient { client_id } => {
917 let client_id = &ClientId::from_str(client_id)
918 .map_err(|e| tonic::Status::aborted(format!("invalid client id: {e}")))?;
919 let client_state = self.get_client_state(client_id).await?;
920
921 let frozen_client =
922 client_state.with_frozen_height(ibc_types::core::client::Height {
923 revision_number: 0,
924 revision_height: 1,
925 });
926 self.put_client(client_id, frozen_client);
927 }
928 ProposalPayload::UnfreezeIbcClient { client_id } => {
929 let client_id = &ClientId::from_str(client_id)
930 .map_err(|e| tonic::Status::aborted(format!("invalid client id: {e}")))?;
931 let client_state = self.get_client_state(client_id).await?;
932
933 let unfrozen_client = client_state.unfrozen();
934 self.put_client(client_id, unfrozen_client);
935 }
936 }
937 Ok(Ok(()))
938 }
939
940 async fn deliver_community_pool_transaction(&mut self, proposal: u64) -> Result<()> {
941 let delivery_height = self.get_block_height().await? + 1;
943
944 tracing::info!(%proposal, %delivery_height, "scheduling Community Pool transaction for delivery at next block");
945
946 self.put_proto(
947 state_key::deliver_single_community_pool_transaction_at_height(
948 delivery_height,
949 proposal,
950 ),
951 proposal,
952 );
953 Ok(())
954 }
955
956 async fn signal_upgrade(&mut self, height: u64) -> Result<()> {
961 self.nonverifiable_put_raw(
962 state_key::upgrades::next_upgrade().into(),
963 height.to_be_bytes().to_vec(),
964 );
965 Ok(())
966 }
967
968 fn signal_halt(&mut self) {
972 self.nonverifiable_put_proto(persistent_flags::halt_bit().as_bytes().to_vec(), true);
973 }
974
975 fn ready_to_start(&mut self) {
978 self.nonverifiable_put_proto(persistent_flags::halt_bit().as_bytes().to_vec(), false);
979 }
980}
981
982impl<T: StateWrite + StateReadExt + ?Sized> StateWriteExt for T {}