1use super::{EpochChangeEvent, RoundChangeEvent, ValidatorCreator, ValidatorOwnerBadgeData};
2use crate::blueprints::consensus_manager::VALIDATOR_ROLE;
3use crate::errors::ApplicationError;
4use crate::errors::RuntimeError;
5use crate::internal_prelude::*;
6use radix_engine_interface::api::field_api::LockFlags;
7use radix_engine_interface::api::object_api::ModuleId;
8use radix_engine_interface::api::{
9 AttachedModuleId, CollectionIndex, FieldValue, SystemApi, ACTOR_STATE_SELF,
10};
11use radix_engine_interface::blueprints::consensus_manager::*;
12use radix_engine_interface::blueprints::package::BlueprintDefinitionInit;
13use radix_engine_interface::blueprints::resource::*;
14use radix_engine_interface::object_modules::metadata::UncheckedUrl;
15use radix_engine_interface::{metadata_init, mint_roles, rule};
16use radix_native_sdk::modules::metadata::Metadata;
17use radix_native_sdk::modules::role_assignment::RoleAssignment;
18use radix_native_sdk::resource::NativeVault;
19use radix_native_sdk::resource::{NativeBucket, ResourceManager};
20use radix_native_sdk::runtime::Runtime;
21
22const MILLIS_IN_SECOND: i64 = 1000;
23const SECONDS_IN_MINUTE: i64 = 60;
24const MILLIS_IN_MINUTE: i64 = MILLIS_IN_SECOND * SECONDS_IN_MINUTE;
25
26#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
27pub struct ConsensusManagerConfigSubstate {
28 pub config: ConsensusManagerConfig,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
32pub struct ConsensusManagerSubstate {
33 pub started: bool,
35 pub epoch: Epoch,
37 pub effective_epoch_start_milli: i64,
43 pub actual_epoch_start_milli: i64,
46 pub round: Round,
48 pub current_leader: Option<ValidatorIndex>,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, ScryptoSbor)]
54pub struct Validator {
55 pub key: Secp256k1PublicKey,
56 pub stake: Decimal,
57}
58
59#[derive(Debug, PartialEq, Eq, ScryptoSbor)]
60pub struct ValidatorRewardsSubstate {
61 pub proposer_rewards: IndexMap<ValidatorIndex, Decimal>,
62 pub rewards_vault: Vault,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
66pub struct CurrentValidatorSetSubstate {
67 pub validator_set: ActiveValidatorSet,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
71#[sbor(transparent)]
72pub struct ActiveValidatorSet {
73 pub validators_by_stake_desc: IndexMap<ComponentAddress, Validator>,
75}
76
77impl ActiveValidatorSet {
78 pub fn get_by_index(&self, index: ValidatorIndex) -> Option<(&ComponentAddress, &Validator)> {
79 self.validators_by_stake_desc.get_index(index as usize)
80 }
81
82 pub fn get_by_address(&self, address: &ComponentAddress) -> Option<&Validator> {
83 self.validators_by_stake_desc.get(address)
84 }
85
86 pub fn get_by_public_key(
88 &self,
89 public_key: &Secp256k1PublicKey,
90 ) -> Option<(&ComponentAddress, &Validator)> {
91 self.validators_by_stake_desc
92 .iter()
93 .find(|(_, validator)| &validator.key == public_key)
94 }
95
96 pub fn total_active_stake_xrd(&self) -> Result<Decimal, RuntimeError> {
98 let mut sum = Decimal::ZERO;
99 for v in self
100 .validators_by_stake_desc
101 .iter()
102 .map(|(_, validator)| validator.stake)
103 {
104 sum = sum.checked_add(v).ok_or(RuntimeError::ApplicationError(
105 ApplicationError::ConsensusManagerError(
106 ConsensusManagerError::UnexpectedDecimalComputationError,
107 ),
108 ))?;
109 }
110 Ok(sum)
111 }
112
113 pub fn validator_count(&self) -> usize {
114 self.validators_by_stake_desc.len()
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
119#[sbor(transparent)]
120pub struct ProposerMilliTimestampSubstate {
121 pub epoch_milli: i64,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
128#[sbor(transparent)]
129pub struct ProposerMinuteTimestampSubstate {
130 pub epoch_minute: i32,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
137pub struct CurrentProposalStatisticSubstate {
138 pub validator_statistics: Vec<ProposalStatistic>,
141}
142
143impl CurrentProposalStatisticSubstate {
144 pub fn get_mut_proposal_statistic(
146 &mut self,
147 validator_index: ValidatorIndex,
148 ) -> Result<&mut ProposalStatistic, RuntimeError> {
149 let validator_count = self.validator_statistics.len();
150 self.validator_statistics
151 .get_mut(validator_index as usize)
152 .ok_or_else(|| {
153 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
154 ConsensusManagerError::InvalidValidatorIndex {
155 index: validator_index,
156 count: validator_count,
157 },
158 ))
159 })
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Default, ScryptoSbor)]
164pub struct ProposalStatistic {
165 pub made: u64,
167 pub missed: u64,
169}
170
171impl ProposalStatistic {
172 pub fn success_ratio(&self) -> Result<Decimal, RuntimeError> {
176 let total = self.made + self.missed;
177 if total == 0 {
178 return Ok(Decimal::one());
179 }
180 Ok(Decimal::from(self.made)
181 .checked_div(total)
182 .ok_or(RuntimeError::ApplicationError(
183 ApplicationError::ConsensusManagerError(
184 ConsensusManagerError::UnexpectedDecimalComputationError,
185 ),
186 ))?)
187 }
188}
189
190#[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor)]
191pub enum ConsensusManagerError {
192 InvalidRoundUpdate {
193 from: Round,
194 to: Round,
195 },
196 InvalidProposerTimestampUpdate {
197 from_millis: i64,
198 to_millis: i64,
199 },
200 InconsistentGapRounds {
201 gap_rounds: usize,
202 progressed_rounds: u64,
203 },
204 InvalidValidatorIndex {
205 index: ValidatorIndex,
206 count: usize,
207 },
208 AlreadyStarted,
209 NotXrd,
210 UnexpectedDecimalComputationError,
211 EpochMathOverflow,
212 InvalidConsensusTime(i64),
213 ExceededValidatorCount {
214 current: u32,
215 max: u32,
216 },
217}
218
219declare_native_blueprint_state! {
220 blueprint_ident: ConsensusManager,
221 blueprint_snake_case: consensus_manager,
222 features: {
223 },
224 fields: {
225 config: {
226 ident: Configuration,
227 field_type: {
228 kind: StaticSingleVersioned,
229 },
230 condition: Condition::Always,
231 },
232 state: {
233 ident: State,
234 field_type: {
235 kind: StaticSingleVersioned,
236 },
237 condition: Condition::Always,
238 },
239 validator_rewards: {
240 ident: ValidatorRewards,
241 field_type: {
242 kind: StaticSingleVersioned,
243 },
244 condition: Condition::Always,
245 },
246 current_validator_set: {
247 ident: CurrentValidatorSet,
248 field_type: {
249 kind: StaticSingleVersioned,
250 },
251 condition: Condition::Always,
252 },
253 current_proposal_statistic: {
254 ident: CurrentProposalStatistic,
255 field_type: {
256 kind: StaticSingleVersioned,
257 },
258 condition: Condition::Always,
259 },
260 proposer_minute_timestamp: {
261 ident: ProposerMinuteTimestamp,
262 field_type: {
263 kind: StaticSingleVersioned,
264 },
265 condition: Condition::Always,
266 },
267 proposer_milli_timestamp: {
268 ident: ProposerMilliTimestamp,
269 field_type: {
270 kind: StaticSingleVersioned,
271 },
272 condition: Condition::Always,
273 },
274 },
275 collections: {
276 registered_validators_by_stake: SortedIndex {
277 entry_ident: RegisteredValidatorByStake,
278 key_type: {
279 kind: Static,
280 content_type: ComponentAddress,
281 },
282 full_key_content: {
283 full_content_type: ValidatorByStakeKey,
284 sort_prefix_property_name: inverse_stake_sort_prefix,
285 },
286 value_type: {
287 kind: StaticSingleVersioned,
288 },
289 allow_ownership: false,
290 },
291 }
292}
293
294#[derive(Debug, Clone, ScryptoSbor)]
295pub struct ValidatorByStakeKey {
296 pub divided_stake: u16,
297 pub validator_address: ComponentAddress,
298}
299
300impl SortedIndexKeyContentSource<ConsensusManagerRegisteredValidatorByStakeKeyPayload>
301 for ValidatorByStakeKey
302{
303 fn sort_key(&self) -> u16 {
304 u16::MAX - self.divided_stake
305 }
306
307 fn into_content(
308 self,
309 ) -> <ConsensusManagerRegisteredValidatorByStakeKeyPayload as SortedIndexKeyPayload>::Content
310 {
311 self.validator_address
312 }
313}
314
315impl SortedIndexKeyFullContent<ConsensusManagerRegisteredValidatorByStakeKeyPayload>
316 for ValidatorByStakeKey
317{
318 fn from_sort_key_and_content(sort_key: u16, validator_address: ComponentAddress) -> Self {
319 Self {
320 divided_stake: u16::MAX - sort_key,
321 validator_address,
322 }
323 }
324
325 fn as_content(&self) -> &ComponentAddress {
326 &self.validator_address
327 }
328}
329
330pub type ConsensusManagerConfigurationV1 = ConsensusManagerConfigSubstate;
331pub type ConsensusManagerStateV1 = ConsensusManagerSubstate;
332pub type ConsensusManagerValidatorRewardsV1 = ValidatorRewardsSubstate;
333pub type ConsensusManagerCurrentValidatorSetV1 = CurrentValidatorSetSubstate;
334pub type ConsensusManagerCurrentProposalStatisticV1 = CurrentProposalStatisticSubstate;
335pub type ConsensusManagerProposerMinuteTimestampV1 = ProposerMinuteTimestampSubstate;
336pub type ConsensusManagerProposerMilliTimestampV1 = ProposerMilliTimestampSubstate;
337pub type ConsensusManagerRegisteredValidatorByStakeV1 = Validator;
338
339pub const CONSENSUS_MANAGER_REGISTERED_VALIDATORS_BY_STAKE_INDEX: CollectionIndex = 0u8;
340
341pub struct ConsensusManagerBlueprint;
342
343impl ConsensusManagerBlueprint {
344 pub fn definition() -> BlueprintDefinitionInit {
345 let mut aggregator = TypeAggregator::<ScryptoCustomTypeKind>::new();
346
347 let feature_set = ConsensusManagerFeatureSet::all_features();
348 let state = ConsensusManagerStateSchemaInit::create_schema_init(&mut aggregator);
349
350 let mut functions = index_map_new();
351 functions.insert(
352 CONSENSUS_MANAGER_CREATE_IDENT.to_string(),
353 FunctionSchemaInit {
354 receiver: None,
355 input: TypeRef::Static(
356 aggregator.add_child_type_and_descendents::<ConsensusManagerCreateInput>(),
357 ),
358 output: TypeRef::Static(
359 aggregator.add_child_type_and_descendents::<ConsensusManagerCreateOutput>(),
360 ),
361 export: CONSENSUS_MANAGER_CREATE_IDENT.to_string(),
362 },
363 );
364 functions.insert(
365 CONSENSUS_MANAGER_GET_CURRENT_EPOCH_IDENT.to_string(),
366 FunctionSchemaInit {
367 receiver: Some(ReceiverInfo::normal_ref()),
368 input: TypeRef::Static(
369 aggregator
370 .add_child_type_and_descendents::<ConsensusManagerGetCurrentEpochInput>(),
371 ),
372 output: TypeRef::Static(
373 aggregator
374 .add_child_type_and_descendents::<ConsensusManagerGetCurrentEpochOutput>(),
375 ),
376 export: CONSENSUS_MANAGER_GET_CURRENT_EPOCH_IDENT.to_string(),
377 },
378 );
379 functions.insert(
380 CONSENSUS_MANAGER_START_IDENT.to_string(),
381 FunctionSchemaInit {
382 receiver: Some(ReceiverInfo::normal_ref_mut()),
383 input: TypeRef::Static(
384 aggregator.add_child_type_and_descendents::<ConsensusManagerStartInput>(),
385 ),
386 output: TypeRef::Static(
387 aggregator.add_child_type_and_descendents::<ConsensusManagerStartOutput>(),
388 ),
389 export: CONSENSUS_MANAGER_START_IDENT.to_string(),
390 },
391 );
392 functions.insert(
393 CONSENSUS_MANAGER_GET_CURRENT_TIME_IDENT.to_string(),
394 FunctionSchemaInit {
395 receiver: Some(ReceiverInfo::normal_ref()),
396 input: TypeRef::Static(
397 aggregator
398 .add_child_type_and_descendents::<ConsensusManagerGetCurrentTimeInputV1>(),
399 ),
400 output: TypeRef::Static(
401 aggregator
402 .add_child_type_and_descendents::<ConsensusManagerGetCurrentTimeOutput>(),
403 ),
404 export: CONSENSUS_MANAGER_GET_CURRENT_TIME_IDENT.to_string(),
405 },
406 );
407 functions.insert(
408 CONSENSUS_MANAGER_COMPARE_CURRENT_TIME_IDENT.to_string(),
409 FunctionSchemaInit {
410 receiver: Some(ReceiverInfo::normal_ref()),
411 input: TypeRef::Static(
412 aggregator
413 .add_child_type_and_descendents::<ConsensusManagerCompareCurrentTimeInputV1>(
414 ),
415 ),
416 output: TypeRef::Static(
417 aggregator
418 .add_child_type_and_descendents::<ConsensusManagerCompareCurrentTimeOutput>(
419 ),
420 ),
421 export: CONSENSUS_MANAGER_COMPARE_CURRENT_TIME_IDENT.to_string(),
422 },
423 );
424 functions.insert(
425 CONSENSUS_MANAGER_NEXT_ROUND_IDENT.to_string(),
426 FunctionSchemaInit {
427 receiver: Some(ReceiverInfo::normal_ref_mut()),
428 input: TypeRef::Static(
429 aggregator.add_child_type_and_descendents::<ConsensusManagerNextRoundInput>(),
430 ),
431 output: TypeRef::Static(
432 aggregator.add_child_type_and_descendents::<ConsensusManagerNextRoundOutput>(),
433 ),
434 export: CONSENSUS_MANAGER_NEXT_ROUND_IDENT.to_string(),
435 },
436 );
437 functions.insert(
438 CONSENSUS_MANAGER_CREATE_VALIDATOR_IDENT.to_string(),
439 FunctionSchemaInit {
440 receiver: Some(ReceiverInfo::normal_ref_mut()),
441 input: TypeRef::Static(
442 aggregator
443 .add_child_type_and_descendents::<ConsensusManagerCreateValidatorInput>(),
444 ),
445 output: TypeRef::Static(
446 aggregator
447 .add_child_type_and_descendents::<ConsensusManagerCreateValidatorOutput>(),
448 ),
449 export: CONSENSUS_MANAGER_CREATE_VALIDATOR_IDENT.to_string(),
450 },
451 );
452
453 let event_schema = event_schema! {
454 aggregator,
455 [
456 RoundChangeEvent,
457 EpochChangeEvent
458 ]
459 };
460
461 let consensus_manager_schema = generate_full_schema(aggregator);
462
463 BlueprintDefinitionInit {
464 blueprint_type: BlueprintType::default(),
465 is_transient: false,
466 feature_set,
467 dependencies: indexset!(
468 XRD.into(),
469 PACKAGE_OF_DIRECT_CALLER_RESOURCE.into(),
470 SYSTEM_EXECUTION_RESOURCE.into(),
471 VALIDATOR_OWNER_BADGE.into(),
472 ),
473 schema: BlueprintSchemaInit {
474 generics: vec![],
475 schema: consensus_manager_schema,
476 state,
477 events: event_schema,
478 types: BlueprintTypeSchemaInit::default(),
479 functions: BlueprintFunctionsSchemaInit { functions },
480 hooks: BlueprintHooksInit::default(),
481 },
482
483 royalty_config: PackageRoyaltyConfig::default(),
484 auth_config: AuthConfig {
485 function_auth: FunctionAuth::AccessRules(indexmap!(
486 CONSENSUS_MANAGER_CREATE_IDENT.to_string() => rule!(require(system_execution(SystemExecution::Protocol))),
487 )),
488 method_auth: MethodAuthTemplate::StaticRoleDefinition(roles_template!(
489 roles {
490 VALIDATOR_ROLE;
491 },
492 methods {
493 CONSENSUS_MANAGER_START_IDENT => []; CONSENSUS_MANAGER_NEXT_ROUND_IDENT => [VALIDATOR_ROLE];
495
496 CONSENSUS_MANAGER_GET_CURRENT_EPOCH_IDENT => MethodAccessibility::Public;
497 CONSENSUS_MANAGER_GET_CURRENT_TIME_IDENT => MethodAccessibility::Public;
498 CONSENSUS_MANAGER_COMPARE_CURRENT_TIME_IDENT => MethodAccessibility::Public;
499 CONSENSUS_MANAGER_CREATE_VALIDATOR_IDENT => MethodAccessibility::Public;
500 }
501 )),
502 },
503 }
504 }
505
506 pub(crate) fn create<Y: SystemApi<RuntimeError>>(
507 validator_token_address_reservation: GlobalAddressReservation,
508 consensus_manager_address_reservation: GlobalAddressReservation,
509 genesis_epoch: Epoch,
510 initial_config: ConsensusManagerConfig,
511 initial_time_milli: i64,
512 initial_current_leader: Option<ValidatorIndex>,
513 api: &mut Y,
514 ) -> Result<(), RuntimeError> {
515 if initial_config.max_validators > ValidatorIndex::MAX as u32 {
516 return Err(RuntimeError::ApplicationError(
517 ApplicationError::ConsensusManagerError(
518 ConsensusManagerError::ExceededValidatorCount {
519 current: initial_config.max_validators,
520 max: ValidatorIndex::MAX as u32,
521 },
522 ),
523 ));
524 }
525
526 {
527 let global_id =
529 NonFungibleGlobalId::package_of_direct_caller_badge(CONSENSUS_MANAGER_PACKAGE);
530 let consensus_manager_address =
531 api.get_reservation_address(consensus_manager_address_reservation.0.as_node_id())?;
532
533 ResourceManager::new_non_fungible::<ValidatorOwnerBadgeData, _, _, _>(
534 OwnerRole::Fixed(rule!(require(global_caller(consensus_manager_address)))),
535 NonFungibleIdType::Bytes,
536 true,
537 NonFungibleResourceRoles {
538 mint_roles: mint_roles! {
539 minter => rule!(require(global_id));
540 minter_updater => rule!(deny_all);
541 },
542 ..Default::default()
543 },
544 metadata_init! {
545 "name" => "Validator Owner Badges".to_owned(), locked;
546 "description" => "Badges created by the Radix system that provide individual control over the validator components created for validator node-runners.".to_owned(), locked;
547 "tags" => vec!["badge".to_owned(), "validator".to_owned()], locked;
548 "icon_url" => UncheckedUrl::of("https://assets.radixdlt.com/icons/icon-validator_owner_badge.png".to_owned()), locked;
549 },
550 Some(validator_token_address_reservation),
551 api,
552 )?;
553 };
554
555 let consensus_manager_id = {
556 let config = ConsensusManagerConfigSubstate {
557 config: initial_config,
558 };
559 let consensus_manager = ConsensusManagerSubstate {
560 started: false,
561 epoch: genesis_epoch,
562 actual_epoch_start_milli: initial_time_milli,
563 effective_epoch_start_milli: initial_time_milli,
564 round: Round::zero(),
565 current_leader: initial_current_leader,
566 };
567 let validator_rewards = ValidatorRewardsSubstate {
568 proposer_rewards: index_map_new(),
569 rewards_vault: Vault::create(XRD, api)?,
570 };
571 let current_validator_set = CurrentValidatorSetSubstate {
572 validator_set: ActiveValidatorSet {
573 validators_by_stake_desc: index_map_new(),
574 },
575 };
576 let current_proposal_statistic = CurrentProposalStatisticSubstate {
577 validator_statistics: Vec::new(),
578 };
579 let minute_timestamp = ProposerMinuteTimestampSubstate {
580 epoch_minute: Self::milli_to_minute(initial_time_milli).ok_or(
581 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
582 ConsensusManagerError::InvalidConsensusTime(initial_time_milli),
583 )),
584 )?,
585 };
586 let milli_timestamp = ProposerMilliTimestampSubstate {
587 epoch_milli: initial_time_milli,
588 };
589
590 api.new_simple_object(
591 CONSENSUS_MANAGER_BLUEPRINT,
592 indexmap! {
593 ConsensusManagerField::Configuration.field_index() => FieldValue::immutable(&ConsensusManagerConfigurationFieldPayload::from_content_source(config)),
594 ConsensusManagerField::State.field_index() => FieldValue::new(&ConsensusManagerStateFieldPayload::from_content_source(consensus_manager)),
595 ConsensusManagerField::ValidatorRewards.field_index() => FieldValue::new(&ConsensusManagerValidatorRewardsFieldPayload::from_content_source(validator_rewards)),
596 ConsensusManagerField::CurrentValidatorSet.field_index() => FieldValue::new(&ConsensusManagerCurrentValidatorSetFieldPayload::from_content_source(current_validator_set)),
597 ConsensusManagerField::CurrentProposalStatistic.field_index() => FieldValue::new(&ConsensusManagerCurrentProposalStatisticFieldPayload::from_content_source(current_proposal_statistic)),
598 ConsensusManagerField::ProposerMinuteTimestamp.field_index() => FieldValue::new(&ConsensusManagerProposerMinuteTimestampFieldPayload::from_content_source(minute_timestamp)),
599 ConsensusManagerField::ProposerMilliTimestamp.field_index() => FieldValue::new(&ConsensusManagerProposerMilliTimestampFieldPayload::from_content_source(milli_timestamp)),
600 },
601 )?
602 };
603
604 let role_definitions = roles2! {
605 VALIDATOR_ROLE => rule!(require(system_execution(SystemExecution::Validator)));
606 };
607
608 let roles = indexmap!(ModuleId::Main => role_definitions);
609 let role_assignment = RoleAssignment::create(OwnerRole::None, roles, api)?.0;
610 let metadata = Metadata::create_with_data(
611 metadata_init! {
612 "name" => "Consensus Manager".to_owned(), locked;
613 "description" => "A component that keeps track of various consensus related concepts such as the epoch, round, current validator set, and so on.".to_owned(), locked;
614 },
615 api,
616 )?;
617
618 api.globalize(
619 consensus_manager_id,
620 indexmap!(
621 AttachedModuleId::RoleAssignment => role_assignment.0,
622 AttachedModuleId::Metadata => metadata.0,
623 ),
624 Some(consensus_manager_address_reservation),
625 )?;
626
627 Ok(())
628 }
629
630 pub(crate) fn get_current_epoch<Y: SystemApi<RuntimeError>>(
631 api: &mut Y,
632 ) -> Result<Epoch, RuntimeError> {
633 let handle = api.actor_open_field(
634 ACTOR_STATE_SELF,
635 ConsensusManagerField::State.into(),
636 LockFlags::read_only(),
637 )?;
638
639 let consensus_manager = api
640 .field_read_typed::<ConsensusManagerStateFieldPayload>(handle)?
641 .fully_update_and_into_latest_version();
642
643 Ok(consensus_manager.epoch)
644 }
645
646 pub(crate) fn start<Y: SystemApi<RuntimeError>>(api: &mut Y) -> Result<(), RuntimeError> {
647 let config_substate = {
648 let config_handle = api.actor_open_field(
649 ACTOR_STATE_SELF,
650 ConsensusManagerField::Configuration.into(),
651 LockFlags::read_only(),
652 )?;
653 let config_substate = api
654 .field_read_typed::<ConsensusManagerConfigurationFieldPayload>(config_handle)?
655 .fully_update_and_into_latest_version();
656 api.field_close(config_handle)?;
657 config_substate
658 };
659
660 let manager_handle = api.actor_open_field(
661 ACTOR_STATE_SELF,
662 ConsensusManagerField::State.into(),
663 LockFlags::MUTABLE,
664 )?;
665 let mut manager_substate = api
666 .field_read_typed::<ConsensusManagerStateFieldPayload>(manager_handle)?
667 .fully_update_and_into_latest_version();
668
669 if manager_substate.started {
670 return Err(RuntimeError::ApplicationError(
671 ApplicationError::ConsensusManagerError(ConsensusManagerError::AlreadyStarted),
672 ));
673 }
674 let post_genesis_epoch =
675 manager_substate
676 .epoch
677 .next()
678 .ok_or(RuntimeError::ApplicationError(
679 ApplicationError::ConsensusManagerError(
680 ConsensusManagerError::EpochMathOverflow,
681 ),
682 ))?;
683
684 Self::epoch_change(post_genesis_epoch, &config_substate.config, api)?;
685 manager_substate.started = true;
686 manager_substate.epoch = post_genesis_epoch;
687 manager_substate.round = Round::zero();
688
689 api.field_write_typed(
690 manager_handle,
691 &ConsensusManagerStateFieldPayload::from_content_source(manager_substate),
692 )?;
693 api.field_close(manager_handle)?;
694
695 Ok(())
696 }
697
698 pub(crate) fn get_current_time_v1<Y: SystemApi<RuntimeError>>(
699 precision: TimePrecisionV1,
700 api: &mut Y,
701 ) -> Result<Instant, RuntimeError> {
702 match precision {
703 TimePrecisionV1::Minute => {
704 let handle = api.actor_open_field(
705 ACTOR_STATE_SELF,
706 ConsensusManagerField::ProposerMinuteTimestamp.into(),
707 LockFlags::read_only(),
708 )?;
709 let proposer_minute_timestamp = api
710 .field_read_typed::<ConsensusManagerProposerMinuteTimestampFieldPayload>(
711 handle,
712 )?
713 .fully_update_and_into_latest_version();
714 api.field_close(handle)?;
715
716 Ok(Self::epoch_minute_to_instant(
717 proposer_minute_timestamp.epoch_minute,
718 ))
719 }
720 }
721 }
722
723 pub(crate) fn get_current_time_v2<Y: SystemApi<RuntimeError>>(
724 precision: TimePrecisionV2,
725 api: &mut Y,
726 ) -> Result<Instant, RuntimeError> {
727 match precision {
728 TimePrecisionV2::Minute => {
729 let handle = api.actor_open_field(
730 ACTOR_STATE_SELF,
731 ConsensusManagerField::ProposerMinuteTimestamp.into(),
732 LockFlags::read_only(),
733 )?;
734 let proposer_minute_timestamp = api
735 .field_read_typed::<ConsensusManagerProposerMinuteTimestampFieldPayload>(
736 handle,
737 )?
738 .fully_update_and_into_latest_version();
739 api.field_close(handle)?;
740
741 Ok(Self::epoch_minute_to_instant(
742 proposer_minute_timestamp.epoch_minute,
743 ))
744 }
745 TimePrecisionV2::Second => {
746 let handle = api.actor_open_field(
747 ACTOR_STATE_SELF,
748 ConsensusManagerField::ProposerMilliTimestamp.into(),
749 LockFlags::read_only(),
750 )?;
751 let proposer_milli_timestamp = api
752 .field_read_typed::<ConsensusManagerProposerMilliTimestampFieldPayload>(handle)?
753 .fully_update_and_into_latest_version();
754 api.field_close(handle)?;
755
756 Ok(Self::epoch_milli_to_instant(
757 proposer_milli_timestamp.epoch_milli,
758 ))
759 }
760 }
761 }
762
763 pub(crate) fn compare_current_time_v1<Y: SystemApi<RuntimeError>>(
764 other_arbitrary_precision_instant: Instant,
765 precision: TimePrecisionV1,
766 operator: TimeComparisonOperator,
767 api: &mut Y,
768 ) -> Result<bool, RuntimeError> {
769 match precision {
770 TimePrecisionV1::Minute => {
771 let other_epoch_minute = other_arbitrary_precision_instant
772 .seconds_since_unix_epoch
773 .checked_mul(MILLIS_IN_SECOND)
774 .and_then(|result| Self::milli_to_minute(result))
775 .unwrap_or_else(|| {
776 if other_arbitrary_precision_instant
778 .seconds_since_unix_epoch
779 .is_negative()
780 {
781 i32::MIN
782 } else {
783 i32::MAX
784 }
785 });
786
787 let handle = api.actor_open_field(
788 ACTOR_STATE_SELF,
789 ConsensusManagerField::ProposerMinuteTimestamp.into(),
790 LockFlags::read_only(),
791 )?;
792 let proposer_minute_timestamp = api
793 .field_read_typed::<ConsensusManagerProposerMinuteTimestampFieldPayload>(
794 handle,
795 )?
796 .fully_update_and_into_latest_version();
797 api.field_close(handle)?;
798
799 let proposer_instant =
801 Self::epoch_minute_to_instant(proposer_minute_timestamp.epoch_minute);
802 let other_instant = Self::epoch_minute_to_instant(other_epoch_minute);
803 let result = proposer_instant.compare(other_instant, operator);
804 Ok(result)
805 }
806 }
807 }
808
809 pub(crate) fn compare_current_time_v2<Y: SystemApi<RuntimeError>>(
810 other_arbitrary_precision_instant: Instant,
811 precision: TimePrecisionV2,
812 operator: TimeComparisonOperator,
813 api: &mut Y,
814 ) -> Result<bool, RuntimeError> {
815 match precision {
816 TimePrecisionV2::Minute => {
817 let other_epoch_minute = other_arbitrary_precision_instant
818 .seconds_since_unix_epoch
819 .checked_mul(MILLIS_IN_SECOND)
820 .and_then(|result| Self::milli_to_minute(result))
821 .unwrap_or_else(|| {
822 if other_arbitrary_precision_instant
824 .seconds_since_unix_epoch
825 .is_negative()
826 {
827 i32::MIN
828 } else {
829 i32::MAX
830 }
831 });
832
833 let handle = api.actor_open_field(
834 ACTOR_STATE_SELF,
835 ConsensusManagerField::ProposerMinuteTimestamp.into(),
836 LockFlags::read_only(),
837 )?;
838 let proposer_minute_timestamp = api
839 .field_read_typed::<ConsensusManagerProposerMinuteTimestampFieldPayload>(
840 handle,
841 )?
842 .fully_update_and_into_latest_version();
843 api.field_close(handle)?;
844
845 let proposer_instant =
847 Self::epoch_minute_to_instant(proposer_minute_timestamp.epoch_minute);
848 let other_instant = Self::epoch_minute_to_instant(other_epoch_minute);
849 let result = proposer_instant.compare(other_instant, operator);
850 Ok(result)
851 }
852
853 TimePrecisionV2::Second => {
854 let other_epoch_second = other_arbitrary_precision_instant.seconds_since_unix_epoch;
855
856 let handle = api.actor_open_field(
857 ACTOR_STATE_SELF,
858 ConsensusManagerField::ProposerMilliTimestamp.into(),
859 LockFlags::read_only(),
860 )?;
861 let proposer_milli_timestamp = api
862 .field_read_typed::<ConsensusManagerProposerMilliTimestampFieldPayload>(handle)?
863 .fully_update_and_into_latest_version();
864 api.field_close(handle)?;
865
866 let proposer_instant =
868 Self::epoch_milli_to_instant(proposer_milli_timestamp.epoch_milli);
869 let other_instant = Instant::new(other_epoch_second);
870 let result = proposer_instant.compare(other_instant, operator);
871 Ok(result)
872 }
873 }
874 }
875
876 fn epoch_minute_to_instant(epoch_minute: i32) -> Instant {
877 Instant::new(epoch_minute as i64 * SECONDS_IN_MINUTE)
878 }
879
880 fn epoch_milli_to_instant(epoch_milli: i64) -> Instant {
881 Instant::new(epoch_milli / MILLIS_IN_SECOND)
882 }
883
884 fn milli_to_minute(epoch_milli: i64) -> Option<i32> {
885 i32::try_from(epoch_milli / MILLIS_IN_MINUTE).ok() }
887
888 pub(crate) fn next_round<Y: SystemApi<RuntimeError>>(
889 round: Round,
890 proposer_timestamp_milli: i64,
891 proposal_history: LeaderProposalHistory,
892 api: &mut Y,
893 ) -> Result<(), RuntimeError> {
894 Self::check_non_decreasing_and_update_timestamps(proposer_timestamp_milli, api)?;
895
896 let config_handle = api.actor_open_field(
897 ACTOR_STATE_SELF,
898 ConsensusManagerField::Configuration.into(),
899 LockFlags::read_only(),
900 )?;
901 let config_substate = api
902 .field_read_typed::<ConsensusManagerConfigurationFieldPayload>(config_handle)?
903 .fully_update_and_into_latest_version();
904 api.field_close(config_handle)?;
905
906 let manager_handle = api.actor_open_field(
907 ACTOR_STATE_SELF,
908 ConsensusManagerField::State.into(),
909 LockFlags::MUTABLE,
910 )?;
911 let mut manager_substate = api
912 .field_read_typed::<ConsensusManagerStateFieldPayload>(manager_handle)?
913 .fully_update_and_into_latest_version();
914
915 let progressed_rounds = Round::calculate_progress(manager_substate.round, round)
916 .ok_or_else(|| {
917 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
918 ConsensusManagerError::InvalidRoundUpdate {
919 from: manager_substate.round,
920 to: round,
921 },
922 ))
923 })?;
924
925 let current_leader = proposal_history.current_leader;
926 Self::update_proposal_statistics(progressed_rounds, proposal_history, api)?;
927
928 let config = &config_substate.config;
929 let should_epoch_change = config.epoch_change_condition.should_epoch_change(
930 manager_substate.effective_epoch_start_milli,
931 proposer_timestamp_milli,
932 round,
933 );
934 match should_epoch_change {
935 EpochChangeOutcome::NoChange => {
936 Runtime::emit_event(api, RoundChangeEvent { round })?;
937 manager_substate.round = round;
938 }
939 EpochChangeOutcome::Change {
940 next_epoch_effective_start_millis: next_epoch_effective_start,
941 } => {
942 let next_epoch =
943 manager_substate
944 .epoch
945 .next()
946 .ok_or(RuntimeError::ApplicationError(
947 ApplicationError::ConsensusManagerError(
948 ConsensusManagerError::EpochMathOverflow,
949 ),
950 ))?;
951 Self::epoch_change(next_epoch, config, api)?;
952 manager_substate.epoch = next_epoch;
953 manager_substate.round = Round::zero();
954 manager_substate.actual_epoch_start_milli = proposer_timestamp_milli;
955 manager_substate.effective_epoch_start_milli = next_epoch_effective_start;
956 }
957 }
958 manager_substate.current_leader = Some(current_leader);
959
960 api.field_write_typed(
961 manager_handle,
962 &ConsensusManagerStateFieldPayload::from_content_source(manager_substate),
963 )?;
964 api.field_close(manager_handle)?;
965
966 Ok(())
967 }
968
969 fn get_validator_xrd_cost<Y: SystemApi<RuntimeError>>(
970 api: &mut Y,
971 ) -> Result<Option<Decimal>, RuntimeError> {
972 let manager_handle = api.actor_open_field(
973 ACTOR_STATE_SELF,
974 ConsensusManagerField::State.field_index(),
975 LockFlags::read_only(),
976 )?;
977 let manager_substate =
978 api.field_read_typed::<ConsensusManagerStateFieldPayload>(manager_handle)?;
979 let manager_substate = manager_substate.fully_update_and_into_latest_version();
980
981 let validator_creation_xrd_cost = if manager_substate.started {
982 let config_handle = api.actor_open_field(
983 ACTOR_STATE_SELF,
984 ConsensusManagerField::Configuration.into(),
985 LockFlags::read_only(),
986 )?;
987 let manager_config: ConsensusManagerConfigurationFieldPayload =
988 api.field_read_typed(config_handle)?;
989 api.field_close(config_handle)?;
990
991 let validator_creation_xrd_cost = manager_config
992 .fully_update_and_into_latest_version()
993 .config
994 .validator_creation_usd_cost
995 .checked_mul(api.usd_price()?)
996 .ok_or(RuntimeError::ApplicationError(
997 ApplicationError::ConsensusManagerError(
998 ConsensusManagerError::UnexpectedDecimalComputationError,
999 ),
1000 ))?;
1001 Some(validator_creation_xrd_cost)
1002 } else {
1003 None
1004 };
1005
1006 api.field_close(manager_handle)?;
1007
1008 Ok(validator_creation_xrd_cost)
1009 }
1010
1011 pub(crate) fn create_validator<Y: SystemApi<RuntimeError>>(
1012 key: Secp256k1PublicKey,
1013 fee_factor: Decimal,
1014 xrd_payment: Bucket,
1015 api: &mut Y,
1016 ) -> Result<(ComponentAddress, Bucket, Bucket), RuntimeError> {
1017 if !xrd_payment.resource_address(api)?.eq(&XRD) {
1018 return Err(RuntimeError::ApplicationError(
1019 ApplicationError::ConsensusManagerError(ConsensusManagerError::NotXrd),
1020 ));
1021 }
1022
1023 let validator_xrd_cost = Self::get_validator_xrd_cost(api)?;
1024 if let Some(xrd_cost) = validator_xrd_cost {
1025 let xrd_paid = xrd_payment.take(xrd_cost, api)?;
1026 xrd_paid.burn(api)?;
1027 }
1028
1029 let (validator_address, owner_token_bucket) =
1030 ValidatorCreator::create(key, false, fee_factor, api)?;
1031
1032 Ok((validator_address, owner_token_bucket, xrd_payment))
1033 }
1034
1035 fn check_non_decreasing_and_update_timestamps<Y: SystemApi<RuntimeError>>(
1036 current_time_ms: i64,
1037 api: &mut Y,
1038 ) -> Result<(), RuntimeError> {
1039 let handle = api.actor_open_field(
1040 ACTOR_STATE_SELF,
1041 ConsensusManagerField::ProposerMilliTimestamp.into(),
1042 LockFlags::MUTABLE,
1043 )?;
1044 let exact_time_substate: ConsensusManagerProposerMilliTimestampFieldPayload =
1045 api.field_read_typed(handle)?;
1046 let mut exact_time_substate = exact_time_substate.fully_update_and_into_latest_version();
1047 let previous_timestamp = exact_time_substate.epoch_milli;
1048 if current_time_ms < previous_timestamp {
1049 return Err(RuntimeError::ApplicationError(
1050 ApplicationError::ConsensusManagerError(
1051 ConsensusManagerError::InvalidProposerTimestampUpdate {
1052 from_millis: previous_timestamp,
1053 to_millis: current_time_ms,
1054 },
1055 ),
1056 ));
1057 } else if current_time_ms > previous_timestamp {
1058 exact_time_substate.epoch_milli = current_time_ms;
1059 api.field_write_typed(
1060 handle,
1061 &ConsensusManagerProposerMilliTimestampFieldPayload::from_content_source(
1062 exact_time_substate,
1063 ),
1064 )?;
1065 }
1066 api.field_close(handle)?;
1067
1068 let new_rounded_value = Self::milli_to_minute(current_time_ms).ok_or(
1069 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
1070 ConsensusManagerError::InvalidConsensusTime(current_time_ms),
1071 )),
1072 )?;
1073 let handle = api.actor_open_field(
1074 ACTOR_STATE_SELF,
1075 ConsensusManagerField::ProposerMinuteTimestamp.into(),
1076 LockFlags::MUTABLE,
1077 )?;
1078 let rounded_timestamp_substate: ConsensusManagerProposerMinuteTimestampFieldPayload =
1079 api.field_read_typed(handle)?;
1080 let mut rounded_timestamp_substate =
1081 rounded_timestamp_substate.fully_update_and_into_latest_version();
1082 let previous_rounded_value = rounded_timestamp_substate.epoch_minute;
1083 if new_rounded_value > previous_rounded_value {
1084 rounded_timestamp_substate.epoch_minute = new_rounded_value;
1085 api.field_write_typed(
1086 handle,
1087 &ConsensusManagerProposerMinuteTimestampFieldPayload::from_content_source(
1088 rounded_timestamp_substate,
1089 ),
1090 )?;
1091 }
1092 api.field_close(handle)?;
1093
1094 Ok(())
1095 }
1096
1097 fn update_proposal_statistics<Y: SystemApi<RuntimeError>>(
1098 progressed_rounds: u64,
1099 proposal_history: LeaderProposalHistory,
1100 api: &mut Y,
1101 ) -> Result<(), RuntimeError> {
1102 if proposal_history.gap_round_leaders.len() as u64 != progressed_rounds - 1 {
1103 return Err(RuntimeError::ApplicationError(
1104 ApplicationError::ConsensusManagerError(
1105 ConsensusManagerError::InconsistentGapRounds {
1106 gap_rounds: proposal_history.gap_round_leaders.len(),
1107 progressed_rounds,
1108 },
1109 ),
1110 ));
1111 }
1112
1113 let statistic_handle = api.actor_open_field(
1114 ACTOR_STATE_SELF,
1115 ConsensusManagerField::CurrentProposalStatistic.into(),
1116 LockFlags::MUTABLE,
1117 )?;
1118 let statistic: ConsensusManagerCurrentProposalStatisticFieldPayload =
1119 api.field_read_typed(statistic_handle)?;
1120 let mut statistic = statistic.fully_update_and_into_latest_version();
1121 for gap_round_leader in proposal_history.gap_round_leaders {
1122 let gap_round_statistic = statistic.get_mut_proposal_statistic(gap_round_leader)?;
1123 gap_round_statistic.missed += 1;
1124 }
1125 let current_round_statistic =
1126 statistic.get_mut_proposal_statistic(proposal_history.current_leader)?;
1127 if proposal_history.is_fallback {
1128 current_round_statistic.missed += 1;
1129 } else {
1130 current_round_statistic.made += 1;
1131 }
1132 api.field_write_typed(
1133 statistic_handle,
1134 &ConsensusManagerCurrentProposalStatisticFieldPayload::from_content_source(statistic),
1135 )?;
1136 api.field_close(statistic_handle)?;
1137
1138 Ok(())
1139 }
1140
1141 fn epoch_change<Y: SystemApi<RuntimeError>>(
1142 next_epoch: Epoch,
1143 config: &ConsensusManagerConfig,
1144 api: &mut Y,
1145 ) -> Result<(), RuntimeError> {
1146 let validator_set_handle = api.actor_open_field(
1148 ACTOR_STATE_SELF,
1149 ConsensusManagerField::CurrentValidatorSet.into(),
1150 LockFlags::MUTABLE,
1151 )?;
1152 let validator_set_substate: ConsensusManagerCurrentValidatorSetFieldPayload =
1153 api.field_read_typed(validator_set_handle)?;
1154 let mut validator_set_substate =
1155 validator_set_substate.fully_update_and_into_latest_version();
1156 let previous_validator_set = validator_set_substate.validator_set;
1157
1158 let statistic_handle = api.actor_open_field(
1160 ACTOR_STATE_SELF,
1161 ConsensusManagerField::CurrentProposalStatistic.into(),
1162 LockFlags::MUTABLE,
1163 )?;
1164 let statistic_substate: ConsensusManagerCurrentProposalStatisticFieldPayload =
1165 api.field_read_typed(statistic_handle)?;
1166 let mut statistic_substate = statistic_substate.fully_update_and_into_latest_version();
1167 let previous_statistics = statistic_substate.validator_statistics;
1168
1169 let rewards_handle = api.actor_open_field(
1171 ACTOR_STATE_SELF,
1172 ConsensusManagerField::ValidatorRewards.into(),
1173 LockFlags::MUTABLE,
1174 )?;
1175 let mut rewards_substate = api
1176 .field_read_typed::<ConsensusManagerValidatorRewardsFieldPayload>(rewards_handle)?
1177 .fully_update_and_into_latest_version();
1178
1179 Self::apply_validator_emissions_and_rewards(
1181 previous_validator_set,
1182 previous_statistics,
1183 config,
1184 &mut rewards_substate,
1185 next_epoch.previous().ok_or(RuntimeError::ApplicationError(
1186 ApplicationError::ConsensusManagerError(ConsensusManagerError::EpochMathOverflow),
1187 ))?,
1188 api,
1189 )?;
1190
1191 let num_validators_to_read_from_store =
1200 config.max_validators + (config.max_validators / 10) + 10;
1201
1202 let mut top_registered_validators: Vec<(
1203 ComponentAddress,
1204 ConsensusManagerRegisteredValidatorByStakeEntryPayload,
1205 )> = api.actor_sorted_index_scan_typed(
1206 ACTOR_STATE_SELF,
1207 ConsensusManagerCollection::RegisteredValidatorByStakeSortedIndex.collection_index(),
1208 num_validators_to_read_from_store,
1209 )?;
1210
1211 top_registered_validators.sort_by(|(_, validator_1), (_, validator_2)| {
1215 let validator1 = validator_1.as_unique_version();
1216 let validator2 = validator_2.as_unique_version();
1217 validator1.stake.cmp(&validator2.stake).reverse()
1218 });
1219
1220 let next_active_validator_set = ActiveValidatorSet {
1221 validators_by_stake_desc: top_registered_validators
1222 .into_iter()
1223 .take(config.max_validators as usize)
1224 .map(|(component_address, validator)| {
1225 (
1226 component_address,
1227 validator.fully_update_and_into_latest_version(),
1228 )
1229 })
1230 .collect(),
1231 };
1232
1233 let mut next_validator_set_total_stake = Decimal::zero();
1234 let mut significant_protocol_update_readiness: IndexMap<String, Decimal> = index_map_new();
1235 for (validator_address, validator) in
1236 next_active_validator_set.validators_by_stake_desc.iter()
1237 {
1238 next_validator_set_total_stake = next_validator_set_total_stake
1239 .checked_add(validator.stake)
1240 .ok_or(RuntimeError::ApplicationError(
1241 ApplicationError::ConsensusManagerError(
1242 ConsensusManagerError::UnexpectedDecimalComputationError,
1243 ),
1244 ))?;
1245 let rtn = api.call_method(
1246 validator_address.as_node_id(),
1247 VALIDATOR_GET_PROTOCOL_UPDATE_READINESS_IDENT,
1248 scrypto_encode(&ValidatorGetProtocolUpdateReadinessInput {}).unwrap(),
1249 )?;
1250 if let Some(protocol_update_readiness) = scrypto_decode::<Option<String>>(&rtn).unwrap()
1251 {
1252 let entry = significant_protocol_update_readiness
1253 .entry(protocol_update_readiness)
1254 .or_insert(Decimal::zero());
1255 *entry =
1256 entry
1257 .checked_add(validator.stake)
1258 .ok_or(RuntimeError::ApplicationError(
1259 ApplicationError::ConsensusManagerError(
1260 ConsensusManagerError::UnexpectedDecimalComputationError,
1261 ),
1262 ))?;
1263 }
1264 }
1265
1266 let significant_protocol_update_readiness_stake_threshold = next_validator_set_total_stake
1269 .checked_mul(dec!("0.1"))
1270 .ok_or(RuntimeError::ApplicationError(
1271 ApplicationError::ConsensusManagerError(
1272 ConsensusManagerError::UnexpectedDecimalComputationError,
1273 ),
1274 ))?;
1275 significant_protocol_update_readiness.retain(|_, stake_signalled| {
1276 *stake_signalled >= significant_protocol_update_readiness_stake_threshold
1277 });
1278
1279 Runtime::emit_event(
1281 api,
1282 EpochChangeEvent {
1283 epoch: next_epoch,
1284 validator_set: next_active_validator_set.clone(),
1285 significant_protocol_update_readiness,
1286 },
1287 )?;
1288
1289 api.field_write_typed(
1291 rewards_handle,
1292 &ConsensusManagerValidatorRewardsFieldPayload::from_content_source(rewards_substate),
1293 )?;
1294 api.field_close(rewards_handle)?;
1295
1296 statistic_substate.validator_statistics = (0..next_active_validator_set.validator_count())
1298 .map(|_index| ProposalStatistic::default())
1299 .collect();
1300 api.field_write_typed(
1301 statistic_handle,
1302 &ConsensusManagerCurrentProposalStatisticFieldPayload::from_content_source(
1303 statistic_substate,
1304 ),
1305 )?;
1306 api.field_close(statistic_handle)?;
1307
1308 validator_set_substate.validator_set = next_active_validator_set;
1310 api.field_write_typed(
1311 validator_set_handle,
1312 &ConsensusManagerCurrentValidatorSetFieldPayload::from_content_source(
1313 validator_set_substate,
1314 ),
1315 )?;
1316 api.field_close(validator_set_handle)?;
1317
1318 Ok(())
1319 }
1320
1321 fn apply_validator_emissions_and_rewards<Y: SystemApi<RuntimeError>>(
1324 validator_set: ActiveValidatorSet,
1325 validator_statistics: Vec<ProposalStatistic>,
1326 config: &ConsensusManagerConfig,
1327 validator_rewards: &mut ValidatorRewardsSubstate,
1328 epoch: Epoch, api: &mut Y,
1330 ) -> Result<(), RuntimeError> {
1331 let mut stake_sum_xrd = Decimal::ZERO;
1332
1333 let mut validator_infos: IndexMap<ValidatorIndex, ValidatorInfo> = index_map_new();
1334 for (index, (address, validator)) in validator_set
1335 .validators_by_stake_desc
1336 .into_iter()
1337 .enumerate()
1338 {
1339 if let Some(info) = ValidatorInfo::create_if_applicable(
1340 address,
1341 validator.stake,
1342 validator_statistics[index].clone(),
1343 config.min_validator_reliability,
1344 )? {
1345 stake_sum_xrd = stake_sum_xrd.checked_add(info.stake_xrd).ok_or(
1346 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
1347 ConsensusManagerError::UnexpectedDecimalComputationError,
1348 )),
1349 )?;
1350
1351 validator_infos.insert(
1352 TryInto::<ValidatorIndex>::try_into(index)
1353 .expect("Validator index exceeds the range of u8"),
1355 info,
1356 );
1357 } else {
1358 }
1360 }
1361 if validator_infos.is_empty() {
1362 return Ok(());
1363 }
1364
1365 let emission_per_staked_xrd = config
1372 .total_emission_xrd_per_epoch
1373 .checked_div(stake_sum_xrd)
1374 .ok_or(RuntimeError::ApplicationError(
1375 ApplicationError::ConsensusManagerError(
1376 ConsensusManagerError::UnexpectedDecimalComputationError,
1377 ),
1378 ))?;
1379 let effective_total_emission_xrd = {
1380 let mut sum = Decimal::ZERO;
1381
1382 for v in validator_infos.values() {
1383 let emission = v
1384 .effective_stake_xrd
1385 .checked_mul(emission_per_staked_xrd)
1386 .ok_or(RuntimeError::ApplicationError(
1387 ApplicationError::ConsensusManagerError(
1388 ConsensusManagerError::UnexpectedDecimalComputationError,
1389 ),
1390 ))?;
1391 sum = sum
1392 .checked_add(emission)
1393 .ok_or(RuntimeError::ApplicationError(
1394 ApplicationError::ConsensusManagerError(
1395 ConsensusManagerError::UnexpectedDecimalComputationError,
1396 ),
1397 ))?;
1398 }
1399 sum
1400 };
1401
1402 let total_emission_xrd_bucket =
1403 ResourceManager(XRD).mint_fungible(effective_total_emission_xrd, api)?;
1404
1405 for validator_info in validator_infos.values() {
1406 let emission_xrd_bucket = total_emission_xrd_bucket.take(
1407 validator_info
1408 .effective_stake_xrd
1409 .checked_mul(emission_per_staked_xrd)
1410 .ok_or(RuntimeError::ApplicationError(
1411 ApplicationError::ConsensusManagerError(
1412 ConsensusManagerError::UnexpectedDecimalComputationError,
1413 ),
1414 ))?,
1415 api,
1416 )?;
1417 api.call_method(
1418 validator_info.address.as_node_id(),
1419 VALIDATOR_APPLY_EMISSION_IDENT,
1420 scrypto_encode(&ValidatorApplyEmissionInput {
1421 xrd_bucket: emission_xrd_bucket.into(),
1422 epoch,
1423 proposals_made: validator_info.proposal_statistic.made,
1424 proposals_missed: validator_info.proposal_statistic.missed,
1425 })
1426 .unwrap(),
1427 )?;
1428 }
1429 total_emission_xrd_bucket.drop_empty(api)?;
1430
1431 let mut total_effective_stake = Decimal::ZERO;
1435 let mut total_claimable_proposer_rewards = Decimal::ZERO;
1436
1437 for (index, validator_info) in &validator_infos {
1440 total_effective_stake = total_effective_stake
1441 .checked_add(validator_info.effective_stake_xrd)
1442 .ok_or(RuntimeError::ApplicationError(
1443 ApplicationError::ConsensusManagerError(
1444 ConsensusManagerError::UnexpectedDecimalComputationError,
1445 ),
1446 ))?;
1447 total_claimable_proposer_rewards = total_claimable_proposer_rewards
1448 .checked_add(
1449 validator_rewards
1450 .proposer_rewards
1451 .get(index)
1452 .cloned()
1453 .unwrap_or_default(),
1454 )
1455 .ok_or(RuntimeError::ApplicationError(
1456 ApplicationError::ConsensusManagerError(
1457 ConsensusManagerError::UnexpectedDecimalComputationError,
1458 ),
1459 ))?;
1460 }
1461
1462 let total_claimable_validator_set_rewards = validator_rewards
1463 .rewards_vault
1464 .amount(api)?
1465 .checked_sub(total_claimable_proposer_rewards)
1466 .ok_or(RuntimeError::ApplicationError(
1467 ApplicationError::ConsensusManagerError(
1468 ConsensusManagerError::UnexpectedDecimalComputationError,
1469 ),
1470 ))?;
1471 let reward_per_effective_stake = if total_effective_stake.is_zero() {
1472 Decimal::ZERO
1475 } else {
1476 total_claimable_validator_set_rewards
1477 .checked_div(total_effective_stake)
1478 .ok_or(RuntimeError::ApplicationError(
1479 ApplicationError::ConsensusManagerError(
1480 ConsensusManagerError::UnexpectedDecimalComputationError,
1481 ),
1482 ))?
1483 };
1484
1485 for (index, validator_info) in validator_infos {
1486 let as_proposer = validator_rewards
1487 .proposer_rewards
1488 .swap_remove(&index)
1489 .unwrap_or_default();
1490 let as_member_of_validator_set = validator_info
1491 .effective_stake_xrd
1492 .checked_mul(reward_per_effective_stake)
1493 .ok_or(RuntimeError::ApplicationError(
1494 ApplicationError::ConsensusManagerError(
1495 ConsensusManagerError::UnexpectedDecimalComputationError,
1496 ),
1497 ))?;
1498 let total_rewards = as_proposer.checked_add(as_member_of_validator_set).ok_or(
1499 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
1500 ConsensusManagerError::UnexpectedDecimalComputationError,
1501 )),
1502 )?;
1503 if total_rewards.is_zero() {
1504 continue;
1505 }
1506
1507 let xrd_bucket = validator_rewards.rewards_vault.take(total_rewards, api)?;
1510
1511 api.call_method(
1512 validator_info.address.as_node_id(),
1513 VALIDATOR_APPLY_REWARD_IDENT,
1514 scrypto_encode(&ValidatorApplyRewardInput { xrd_bucket, epoch }).unwrap(),
1515 )?;
1516 }
1517
1518 validator_rewards.proposer_rewards.clear();
1521
1522 Ok(())
1523 }
1524}
1525
1526#[derive(Debug)]
1527struct ValidatorInfo {
1528 pub address: ComponentAddress,
1529 pub stake_xrd: Decimal,
1530 pub effective_stake_xrd: Decimal,
1531 pub proposal_statistic: ProposalStatistic, }
1533
1534impl ValidatorInfo {
1535 fn create_if_applicable(
1536 address: ComponentAddress,
1537 stake_xrd: Decimal,
1538 proposal_statistic: ProposalStatistic,
1539 min_required_reliability: Decimal,
1540 ) -> Result<Option<Self>, RuntimeError> {
1541 if stake_xrd.is_positive() {
1542 let reliability_factor = Self::to_reliability_factor(
1543 proposal_statistic.success_ratio()?,
1544 min_required_reliability,
1545 )?;
1546 let effective_stake_xrd =
1547 stake_xrd
1548 .checked_mul(reliability_factor)
1549 .ok_or(RuntimeError::ApplicationError(
1550 ApplicationError::ConsensusManagerError(
1551 ConsensusManagerError::UnexpectedDecimalComputationError,
1552 ),
1553 ))?;
1554 Ok(Some(Self {
1555 address,
1556 stake_xrd,
1557 proposal_statistic,
1558 effective_stake_xrd,
1559 }))
1560 } else {
1561 Ok(None)
1562 }
1563 }
1564
1565 fn to_reliability_factor(
1569 reliability: Decimal,
1570 min_required_reliability: Decimal,
1571 ) -> Result<Decimal, RuntimeError> {
1572 let reliability_reserve = reliability.checked_sub(min_required_reliability).ok_or(
1573 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
1574 ConsensusManagerError::UnexpectedDecimalComputationError,
1575 )),
1576 )?;
1577 if reliability_reserve.is_negative() {
1578 return Ok(Decimal::zero());
1579 }
1580 let max_allowed_unreliability =
1581 Decimal::one().checked_sub(min_required_reliability).ok_or(
1582 RuntimeError::ApplicationError(ApplicationError::ConsensusManagerError(
1583 ConsensusManagerError::UnexpectedDecimalComputationError,
1584 )),
1585 )?;
1586 if max_allowed_unreliability.is_zero() {
1587 if reliability == Decimal::one() {
1589 return Ok(Decimal::one());
1590 } else {
1591 return Ok(Decimal::zero());
1592 }
1593 }
1594 Ok(reliability_reserve
1595 .checked_div(max_allowed_unreliability)
1596 .ok_or(RuntimeError::ApplicationError(
1597 ApplicationError::ConsensusManagerError(
1598 ConsensusManagerError::UnexpectedDecimalComputationError,
1599 ),
1600 ))?)
1601 }
1602}
1603
1604#[cfg(test)]
1605mod tests {
1606 use super::*;
1607
1608 #[test]
1609 fn test_to_reliability_factor() {
1610 let min_required_reliability = dec!("0.8");
1611 assert_eq!(
1612 ValidatorInfo::to_reliability_factor(dec!("0"), min_required_reliability),
1613 Ok(dec!("0"))
1614 );
1615 assert_eq!(
1616 ValidatorInfo::to_reliability_factor(dec!("0.4"), min_required_reliability),
1617 Ok(dec!("0"))
1618 );
1619
1620 assert_eq!(
1622 ValidatorInfo::to_reliability_factor(dec!("0.8"), min_required_reliability),
1623 Ok(dec!("0"))
1624 );
1625 assert_eq!(
1626 ValidatorInfo::to_reliability_factor(dec!("0.9"), min_required_reliability),
1627 Ok(dec!("0.5"))
1628 );
1629 assert_eq!(
1630 ValidatorInfo::to_reliability_factor(dec!("0.95"), min_required_reliability),
1631 Ok(dec!("0.75"))
1632 );
1633 assert_eq!(
1634 ValidatorInfo::to_reliability_factor(dec!("1"), min_required_reliability),
1635 Ok(dec!("1"))
1636 );
1637 }
1638}