diff --git a/crates/iota-core/src/authority.rs b/crates/iota-core/src/authority.rs index 7a5c7235570..1e7ecd15d96 100644 --- a/crates/iota-core/src/authority.rs +++ b/crates/iota-core/src/authority.rs @@ -4763,6 +4763,7 @@ impl AuthorityState { gas_cost_summary: &GasCostSummary, checkpoint: CheckpointSequenceNumber, epoch_start_timestamp_ms: CheckpointTimestamp, + scores: Vec, ) -> anyhow::Result<( IotaSystemState, Option, @@ -4817,9 +4818,8 @@ impl AuthorityState { bail!("missing system packages: cannot form ChangeEpochTx"); }; - // Use ChangeEpochV3 when the feature flag is enabled and ChangeEpochV2 - // requirements are met - + // Use ChangeEpochV3 or ChangeEpochV4 when the feature flags are enabled and + // ChangeEpochV2 requirements are met if config.select_committee_from_eligible_validators() { // Get the list of eligible validators that support the target protocol version let active_validators = epoch_store.epoch_start_state().get_active_validators(); @@ -4861,18 +4861,35 @@ impl AuthorityState { } } - txns.push(EndOfEpochTransactionKind::new_change_epoch_v3( - next_epoch, - next_epoch_protocol_version, - gas_cost_summary.storage_cost, - gas_cost_summary.computation_cost, - gas_cost_summary.computation_cost_burned, - gas_cost_summary.storage_rebate, - gas_cost_summary.non_refundable_storage_fee, - epoch_start_timestamp_ms, - next_epoch_system_package_bytes, - eligible_active_validators, - )); + // Use ChangeEpochV4 when the feature flag is enabled + if config.score_based_rewards() { + txns.push(EndOfEpochTransactionKind::new_change_epoch_v4( + next_epoch, + next_epoch_protocol_version, + gas_cost_summary.storage_cost, + gas_cost_summary.computation_cost, + gas_cost_summary.computation_cost_burned, + gas_cost_summary.storage_rebate, + gas_cost_summary.non_refundable_storage_fee, + epoch_start_timestamp_ms, + next_epoch_system_package_bytes, + eligible_active_validators, + scores, + )); + } else { + txns.push(EndOfEpochTransactionKind::new_change_epoch_v3( + next_epoch, + next_epoch_protocol_version, + gas_cost_summary.storage_cost, + gas_cost_summary.computation_cost, + gas_cost_summary.computation_cost_burned, + gas_cost_summary.storage_rebate, + gas_cost_summary.non_refundable_storage_fee, + epoch_start_timestamp_ms, + next_epoch_system_package_bytes, + eligible_active_validators, + )); + } } else if config.protocol_defined_base_fee() && config.max_committee_members_count_as_option().is_some() { diff --git a/crates/iota-core/src/authority/scorer.rs b/crates/iota-core/src/authority/scorer.rs index cb931d626ac..0ce48c1fdf6 100644 --- a/crates/iota-core/src/authority/scorer.rs +++ b/crates/iota-core/src/authority/scorer.rs @@ -12,6 +12,9 @@ use iota_types::{ scoring_metrics::VersionedScoringMetrics, }; +const MAX_SCORE: u64 = 2_u64.pow(16); // Note: must be consistent with MAX_SCORE in validator_set.move in iota-framework. +const SCALE_FACTOR: u64 = 2_u64.pow(16); + /// Holds all information related to scoring of authorities in the committee. pub struct Scorer { // The current metrics counts collected by the authority, i.e., the local view of the node @@ -49,8 +52,6 @@ impl Scorer { committee_size, protocol_config, )); - - let max_score = 2_u64.pow(16); let (received_metrics, has_not_sent_report, current_scores, invalid_reports_count) = (0..committee_size) .map(|_| { @@ -60,7 +61,7 @@ impl Scorer { // Initially, none of the authorities had sent any valid report. AtomicBool::new(true), // Current scores initialized to max score. - AtomicU64::new(max_score), + AtomicU64::new(MAX_SCORE), // Invalid reports count initialized to zero. AtomicU64::new(0), ) @@ -68,26 +69,15 @@ impl Scorer { .collect(); let scale_factor = 2_u64.pow(16); let parameters = ParametersV1 { - max_score, - scale_factor, - allowances: MisbehaviorsV1 { - faulty_blocks_provable: 2, - faulty_blocks_unprovable: 1, - missing_proposals: 1000, - equivocations: 0, - }, - maximums: MisbehaviorsV1 { - faulty_blocks_provable: 10, - faulty_blocks_unprovable: 5, - missing_proposals: 5000, - equivocations: 1, - }, - weights: MisbehaviorsV1 { - faulty_blocks_provable: scale_factor * 30 / 100, - faulty_blocks_unprovable: scale_factor * 10 / 100, - missing_proposals: scale_factor * 35 / 100, - equivocations: 1, - }, + max_score: MAX_SCORE, + scale_factor: SCALE_FACTOR, + allowances: vec![1, 2, 1000, 0], + maximums: vec![5, 10, 5000, 1], + weights: vec![ + SCALE_FACTOR * 30 / 100, + SCALE_FACTOR * 10 / 100, + SCALE_FACTOR * 35 / 100, + ], }; // Assert that the allowance for major misbehaviors is 0, // maximum is 1 and weight is 1. This is because major misbehaviors should @@ -433,7 +423,7 @@ mod tests { use iota_types::messages_consensus::{MisbehaviorsV1, VersionedMisbehaviorReport}; use crate::authority::authority_per_epoch_store::scorer::{ - ParametersV1, Scorer, calculate_median_report, calculate_scores_v1, + MAX_SCORE, ParametersV1, SCALE_FACTOR, Scorer, calculate_median_report, calculate_scores_v1, }; fn mock_protocol_config(consensus_choice: ConsensusChoice) -> ProtocolConfig { @@ -681,26 +671,15 @@ mod tests { #[test] fn test_calculate_scores_v1() { let parameters = ParametersV1 { - max_score: 2_u64.pow(16), - scale_factor: 2_u64.pow(16), - allowances: MisbehaviorsV1 { - faulty_blocks_provable: 1, - faulty_blocks_unprovable: 2, - missing_proposals: 1000, - equivocations: 0, - }, - maximums: MisbehaviorsV1 { - faulty_blocks_provable: 5, - faulty_blocks_unprovable: 10, - missing_proposals: 5000, - equivocations: 1, - }, - weights: MisbehaviorsV1 { - faulty_blocks_provable: 2_u64.pow(16) * 30 / 100, - faulty_blocks_unprovable: 2_u64.pow(16) * 10 / 100, - missing_proposals: 2_u64.pow(16) * 35 / 100, - equivocations: 1, - }, + max_score: MAX_SCORE, + scale_factor: SCALE_FACTOR, + allowances: vec![1, 2, 1000, 0], + maximums: vec![5, 10, 5000, 1], + weights: vec![ + SCALE_FACTOR * 30 / 100, + SCALE_FACTOR * 10 / 100, + SCALE_FACTOR * 35 / 100, + ], }; let median_reports = MisbehaviorsV1 { diff --git a/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs b/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs index 6963f6bb26f..1a05cd4f4bf 100644 --- a/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs +++ b/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs @@ -467,7 +467,8 @@ async fn sync_end_of_epoch_checkpoint( &authority_state.epoch_store_for_testing().clone(), &GasCostSummary::new(0, 0, 0, 0, 0), *checkpoint.sequence_number(), - 0, // epoch_start_timestamp_ms + 0, // epoch_start_timestamp_ms + vec![], // scores ) .await .expect("Failed to create and execute advance epoch tx"); diff --git a/crates/iota-core/src/checkpoints/mod.rs b/crates/iota-core/src/checkpoints/mod.rs index eeae0532c4f..39cf2d7aaaa 100644 --- a/crates/iota-core/src/checkpoints/mod.rs +++ b/crates/iota-core/src/checkpoints/mod.rs @@ -1623,6 +1623,13 @@ impl CheckpointBuilder { // too frequent and not needed, since scores are not used during the epoch // (except for monitoring purposes, which does not need to be 100% exact) self.epoch_store.scorer.update_scores(); + let scores: Vec = self + .epoch_store + .scorer + .current_scores + .iter() + .map(|x| x.load(std::sync::atomic::Ordering::Relaxed)) + .collect(); let (mut effects, mut signatures): (Vec<_>, Vec<_>) = transactions.into_iter().unzip(); let epoch_rolling_gas_cost_summary = @@ -1636,6 +1643,7 @@ impl CheckpointBuilder { &mut effects, &mut signatures, sequence_number, + scores, ) .await?; @@ -1781,6 +1789,7 @@ impl CheckpointBuilder { checkpoint_effects: &mut Vec, signatures: &mut Vec>, checkpoint: CheckpointSequenceNumber, + scores: Vec, ) -> anyhow::Result<(IotaSystemState, Option)> { let (system_state, system_epoch_info_event, effects) = self .state @@ -1789,6 +1798,7 @@ impl CheckpointBuilder { epoch_total_gas_cost, checkpoint, epoch_start_timestamp_ms, + scores, ) .await?; checkpoint_effects.push(effects); diff --git a/crates/iota-e2e-tests/tests/reconfiguration_tests.rs b/crates/iota-e2e-tests/tests/reconfiguration_tests.rs index a706e8b1443..626fd3eb2e2 100644 --- a/crates/iota-e2e-tests/tests/reconfiguration_tests.rs +++ b/crates/iota-e2e-tests/tests/reconfiguration_tests.rs @@ -62,8 +62,9 @@ async fn advance_epoch_tx_test() { .create_and_execute_advance_epoch_tx( &state.epoch_store_for_testing(), &GasCostSummary::new(0, 0, 0, 0, 0), - 0, // checkpoint - 0, // epoch_start_timestamp_ms + 0, // checkpoint + 0, // epoch_start_timestamp_ms + vec![], // scores ) .await .unwrap(); diff --git a/crates/iota-framework/packages/iota-system/sources/iota_system.move b/crates/iota-framework/packages/iota-system/sources/iota_system.move index e7c4e2c144b..cbfe6e6688a 100644 --- a/crates/iota-framework/packages/iota-system/sources/iota_system.move +++ b/crates/iota-framework/packages/iota-system/sources/iota_system.move @@ -532,6 +532,7 @@ fun advance_epoch( epoch_start_timestamp_ms: u64, // Timestamp of the epoch start max_committee_members_count: u64, eligible_active_validators: vector, + scores : vector, ctx: &mut TxContext, ): Balance { let self = load_system_state_mut(wrapper); @@ -550,6 +551,7 @@ fun advance_epoch( epoch_start_timestamp_ms, max_committee_members_count, eligible_active_validators, + scores, ctx, ); @@ -765,6 +767,7 @@ public(package) fun advance_epoch_for_testing( epoch_start_timestamp_ms: u64, max_committee_members_count: u64, eligible_active_validators: vector, + scores : vector, ctx: &mut TxContext, ): Balance { let storage_charge = balance::create_for_testing(storage_charge); @@ -783,6 +786,7 @@ public(package) fun advance_epoch_for_testing( epoch_start_timestamp_ms, max_committee_members_count, eligible_active_validators, + scores, ctx, ); storage_rebate diff --git a/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move b/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move index 939ad72d6bc..3c783632746 100644 --- a/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move +++ b/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move @@ -735,6 +735,7 @@ public(package) fun advance_epoch( epoch_start_timestamp_ms: u64, // Timestamp of the epoch start max_committee_members_count: u64, eligible_active_validators: vector, + scores: vector, ctx: &mut TxContext, ): Balance { self.epoch_start_timestamp_ms = epoch_start_timestamp_ms; @@ -792,6 +793,7 @@ public(package) fun advance_epoch( self.parameters.validator_low_stake_grace_period, max_committee_members_count, eligible_active_validators, + scores, ctx, ); diff --git a/crates/iota-framework/packages/iota-system/sources/validator_set.move b/crates/iota-framework/packages/iota-system/sources/validator_set.move index d8eb6d89759..6653d985438 100644 --- a/crates/iota-framework/packages/iota-system/sources/validator_set.move +++ b/crates/iota-framework/packages/iota-system/sources/validator_set.move @@ -140,6 +140,7 @@ const ACTIVE_OR_PENDING_VALIDATOR: u8 = 2; const ANY_VALIDATOR: u8 = 3; const BASIS_POINT_DENOMINATOR: u128 = 10000; +const MAX_SCORE: u128 = 65536; // Note: must be consistent with max score used in iota-core. const MIN_STAKING_THRESHOLD: u64 = 1_000_000_000; // 1 IOTA // Errors @@ -161,6 +162,7 @@ const EValidatorSetEmpty: u64 = 13; const ENotACommitteeValidator: u64 = 14; const EInvalidStakeAmount: u64 = 15; const EInvalidEligibleValidatorIndex: u64 = 16; +const EInvalidRewardAdjustmentData: u64 = 19; const EInvalidCap: u64 = 101; @@ -457,12 +459,13 @@ public(package) fun advance_epoch( low_stake_grace_period: u64, committee_size: u64, eligible_active_validators: vector, + scores: vector, ctx: &mut TxContext, ) { let new_epoch = ctx.epoch() + 1; let total_voting_power = voting_power::total_voting_power(); - // Compute the reward distribution without taking into account the tallying rule slashing. + // Compute the reward distribution without taking into account the scores or reporting. let unadjusted_staking_reward_amounts = compute_unadjusted_reward_distribution( &self.active_validators, &self.committee_members, @@ -482,6 +485,7 @@ public(package) fun advance_epoch( unadjusted_staking_reward_amounts, get_validator_indices_set(&self.active_validators, &slashed_validators), reward_slashing_rate, + scores, ); // Distribute the rewards before adjusting stake so that we immediately start compounding @@ -506,6 +510,7 @@ public(package) fun advance_epoch( &adjusted_staking_reward_amounts, validator_report_records, &slashed_validators, + scores, ); // Collect committee validator addresses before modifying the `active_validators`. @@ -1326,15 +1331,23 @@ fun compute_adjusted_reward_distribution( unadjusted_staking_reward_amounts: vector, slashed_validator_indices_set: VecSet, reward_slashing_rate: u64, + scores: vector, ): vector { let mut adjusted_staking_reward_amounts = vector[]; // Loop through each validator and adjust rewards as necessary let length = committee_members.length(); + assert!(unadjusted_staking_reward_amounts.length() == scores.length(), EInvalidRewardAdjustmentData); + assert!(length == unadjusted_staking_reward_amounts.length(), EInvalidRewardAdjustmentData); + let mut i = 0; while (i < length) { let unadjusted_staking_reward_amount = unadjusted_staking_reward_amounts[i]; + // Calculate staking reward amount adjusted for the validator's score + let score_adjusted_staking_reward_amount = scores[i] as u128 * (unadjusted_staking_reward_amount as u128) + / MAX_SCORE; + // Check if the validator is slashed let adjusted_staking_reward_amount = if ( slashed_validator_indices_set.contains(&committee_members[i]) @@ -1342,15 +1355,14 @@ fun compute_adjusted_reward_distribution( // Use the slashing rate to compute the amount of staking rewards slashed from this punished validator. // Use u128 to avoid multiplication overflow. let staking_reward_adjustment_u128 = - ((unadjusted_staking_reward_amount as u128) * (reward_slashing_rate as u128)) / BASIS_POINT_DENOMINATOR; - unadjusted_staking_reward_amount - (staking_reward_adjustment_u128 as u64) + (score_adjusted_staking_reward_amount * (reward_slashing_rate as u128)) / BASIS_POINT_DENOMINATOR; + score_adjusted_staking_reward_amount - staking_reward_adjustment_u128 } else { // Otherwise, unadjusted staking reward amount is assigned to the unslashed validators - unadjusted_staking_reward_amount + score_adjusted_staking_reward_amount }; - adjusted_staking_reward_amounts.push_back(adjusted_staking_reward_amount); - + adjusted_staking_reward_amounts.push_back(adjusted_staking_reward_amount as u64); // Move to the next validator i = i + 1; }; @@ -1408,6 +1420,7 @@ fun emit_validator_epoch_events( pool_staking_reward_amounts: &vector, report_records: &VecMap>, slashed_validators: &vector
, + scores: vector, ) { assert!(committee_members.length() == pool_staking_reward_amounts.length()); let mut i = 0; @@ -1418,9 +1431,9 @@ fun emit_validator_epoch_events( } else { vector[] }; - let tallying_rule_global_score = if (slashed_validators.contains(&validator_address)) 0 - else 1; let mut committee_member_index = committee_members.find_index!(|c| c == i); + let tallying_rule_global_score = if (slashed_validators.contains(&validator_address) || !committee_member_index.is_some()) 0 + else scores[committee_member_index.extract()]; let pool_staking_reward = if (committee_member_index.is_some()) { // prepare event for a committee validator pool_staking_reward_amounts[committee_member_index.extract()] diff --git a/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move b/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move index 5dc13f7268a..01124259f34 100644 --- a/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move +++ b/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move @@ -190,6 +190,11 @@ public fun advance_epoch_with_reward_amounts_return_rebate_and_max_committee_mem |i| i, ); + let scores = vector::tabulate!( + system_state.committee_validator_addresses().length(), + |_| 65536u64, + ); + let storage_rebate = system_state.advance_epoch_for_testing( new_epoch, 1, @@ -203,6 +208,7 @@ public fun advance_epoch_with_reward_amounts_return_rebate_and_max_committee_mem 0, max_committee_members_count, eligible_active_validators, + scores, ctx, ); test_scenario::return_shared(system_state); @@ -285,6 +291,11 @@ public fun advance_epoch_with_reward_amounts_and_slashing_rates( |i| i, ); + let scores = vector::tabulate!( + system_state.committee_validator_addresses().length(), + |_| 65536u64, + ); + // Use the same value as the default value of max_active_validators. let max_committee_members_count = 150; @@ -301,6 +312,47 @@ public fun advance_epoch_with_reward_amounts_and_slashing_rates( 0, max_committee_members_count, eligible_active_validators, + scores, + ctx, + ); + test_utils::destroy(storage_rebate); + test_scenario::return_shared(system_state); + scenario.next_epoch(@0x0); +} + +public fun advance_epoch_with_subsidy_and_scores( + validator_subsidy: u64, + scores: vector, + scenario: &mut Scenario, +) { + scenario.next_tx(@0x0); + let new_epoch = scenario.ctx().epoch() + 1; + let mut system_state = scenario.take_shared(); + + let ctx = scenario.ctx(); + + let eligible_active_validators = vector::tabulate!( + system_state.validators().active_validators_inner().length(), + |i| i, + ); + + // Use the same value as the default value of max_active_validators. + let max_committee_members_count = 150; + + let storage_rebate = system_state.advance_epoch_for_testing( + new_epoch, + 1, + validator_subsidy * NANOS_PER_IOTA, + 0, + 0, + 0, + 0, + 0, + 10000, // 100% slashing + 0, + max_committee_members_count, + eligible_active_validators, + scores, ctx, ); test_utils::destroy(storage_rebate); diff --git a/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move b/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move index dcaef7d4cdb..d79ff2e140c 100644 --- a/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move @@ -16,6 +16,7 @@ use iota_system::governance_test_utils::{ advance_epoch_with_reward_amounts_return_rebate, advance_epoch_with_reward_amounts_and_slashing_rates, advance_epoch_with_amounts, + advance_epoch_with_subsidy_and_scores, assert_validator_total_stake_amounts, assert_validator_non_self_stake_amounts, assert_validator_self_stake_amounts, @@ -1458,6 +1459,92 @@ fun test_pool_tokens_minted() { scenario_val.end(); } +#[test] +fun test_rewards_with_scores() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + // Need to advance epoch so validator's staking starts counting. + advance_epoch(scenario); + + // Set validator scores. + let system_state = scenario.take_shared(); + let max_score = 65_536u64; + let scores = vector[ + max_score / 4, // Validator 1 + max_score / 2, // Validator 2 + (max_score * 3) / 4, // Validator 3 + max_score, // Validator 4 + ]; + test_scenario::return_shared(system_state); + + // Advance epoch with 800 IOTA of subsidy and the above scores. + advance_epoch_with_subsidy_and_scores(800, scores, scenario); + + // Check that the rewards were distributed according to the scores. + // Each pool gets +200 IOTA. + assert_validator_self_stake_amounts( + validator_addrs(), + vector[ + (100 + 50) * NANOS_PER_IOTA, // 200 * 1/4 = 50 + (200 + 100) * NANOS_PER_IOTA, // 200 * 1/2 = 100 + (300 + 150) * NANOS_PER_IOTA, // 200 * 3/4 = 150 + (400 + 200) * NANOS_PER_IOTA, // 200 * 1 = 200 + ], + scenario, + ); + + scenario_val.end(); +} + +#[test] +fun test_rewards_with_scores_and_slashing() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + // Need to advance epoch so validator's staking starts counting. + advance_epoch(scenario); + + // Set validator scores. + let system_state = scenario.take_shared(); + let max_score = 65_536u64; + let scores = vector[ + max_score / 4, // Validator 1 + max_score / 2, // Validator 2 + (max_score * 3) / 4, // Validator 3 + max_score, // Validator 4 + ]; + test_scenario::return_shared(system_state); + + // validators 2 and 4 reported by all others, so they get slashed. + report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_2, scenario); + report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_2, scenario); + report_validator(VALIDATOR_ADDR_4, VALIDATOR_ADDR_2, scenario); + report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_4, scenario); + report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_4, scenario); + report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_4, scenario); + + // Advance epoch with 800 IOTA of subsidy and the above scores. + advance_epoch_with_subsidy_and_scores(800, scores, scenario); + + // Check that the rewards were distributed according to the scores. + // Each pool gets +200 IOTA. + assert_validator_self_stake_amounts( + validator_addrs(), + vector[ + (100 + 50) * NANOS_PER_IOTA, // 200 * 1/4 = 50 + (200) * NANOS_PER_IOTA, // slashed + (300 + 150) * NANOS_PER_IOTA, // 200 * 3/4 = 150 + (400) * NANOS_PER_IOTA, // slashed + ], + scenario, + ); + + scenario_val.end(); +} + // This will set up the IOTA system state with the following validator stakes: // Validator 1 => 100 // Validator 2 => 200 diff --git a/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move b/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move index ecfddcf058c..0b4c7bc91d7 100644 --- a/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move @@ -975,6 +975,11 @@ fun advance_epoch_with_dummy_rewards( |i| i, ); + let scores = vector::tabulate!( + validator_set.committee_validator_addresses().length(), + |_| 65536u64, + ); + validator_set.advance_epoch( &mut dummy_computation_charge, &mut vec_map::empty(), @@ -984,6 +989,7 @@ fun advance_epoch_with_dummy_rewards( 0, // low_stake_grace_period committee_size, eligible_validators, + scores, scenario.ctx(), ); @@ -999,6 +1005,13 @@ fun advance_epoch_with_eligible_validators( scenario.next_epoch(@0x0); let mut dummy_computation_charge = balance::zero(); + + + let scores = vector::tabulate!( + validator_set.committee_validator_addresses().length(), + |_| 65536u64, + ); + validator_set.advance_epoch( &mut dummy_computation_charge, &mut vec_map::empty(), @@ -1008,6 +1021,7 @@ fun advance_epoch_with_eligible_validators( 0, // low_stake_grace_period committee_size, eligible_validators, + scores, scenario.ctx(), ); @@ -1031,6 +1045,11 @@ fun advance_epoch_with_low_stake_params( |i| i, ); + let scores = vector::tabulate!( + validator_set.committee_validator_addresses().length(), + |_| 65536u64, + ); + validator_set.advance_epoch( &mut dummy_computation_charge, &mut vec_map::empty(), @@ -1040,6 +1059,7 @@ fun advance_epoch_with_low_stake_params( low_stake_grace_period, committee_size, eligible_validators, + scores, scenario.ctx(), ); diff --git a/crates/iota-protocol-config/src/lib.rs b/crates/iota-protocol-config/src/lib.rs index 89d9f254cce..c26e40f88f7 100644 --- a/crates/iota-protocol-config/src/lib.rs +++ b/crates/iota-protocol-config/src/lib.rs @@ -1448,7 +1448,12 @@ impl ProtocolConfig { } pub fn score_based_rewards(&self) -> bool { - self.feature_flags.score_based_rewards + let score_based_rewards = self.feature_flags.score_based_rewards; + assert!( + !score_based_rewards || self.scorer_version.is_some(), + "score_based_rewards requires scorer_version to be set" + ); + score_based_rewards } } diff --git a/crates/iota-types/src/iota_system_state/mod.rs b/crates/iota-types/src/iota_system_state/mod.rs index fe3971a6483..5265aa62071 100644 --- a/crates/iota-types/src/iota_system_state/mod.rs +++ b/crates/iota-types/src/iota_system_state/mod.rs @@ -450,6 +450,7 @@ pub struct AdvanceEpochParams { pub epoch_start_timestamp_ms: u64, pub max_committee_members_count: u64, pub eligible_active_validators: Vec, + pub scores: Vec, } #[cfg(msim)] diff --git a/crates/iota-types/src/transaction.rs b/crates/iota-types/src/transaction.rs index 1a5bbcae833..7b00a36b49d 100644 --- a/crates/iota-types/src/transaction.rs +++ b/crates/iota-types/src/transaction.rs @@ -494,6 +494,34 @@ impl EndOfEpochTransactionKind { }) } + pub fn new_change_epoch_v4( + next_epoch: EpochId, + protocol_version: ProtocolVersion, + storage_charge: u64, + computation_charge: u64, + computation_charge_burned: u64, + storage_rebate: u64, + non_refundable_storage_fee: u64, + epoch_start_timestamp_ms: u64, + system_packages: Vec<(SequenceNumber, Vec>, Vec)>, + eligible_active_validators: Vec, + scores: Vec, + ) -> Self { + Self::ChangeEpochV4(ChangeEpochV4 { + epoch: next_epoch, + protocol_version, + storage_charge, + computation_charge, + computation_charge_burned, + storage_rebate, + non_refundable_storage_fee, + epoch_start_timestamp_ms, + system_packages, + eligible_active_validators, + scores, + }) + } + pub fn new_authenticator_state_expire( min_epoch: u64, authenticator_obj_initial_shared_version: SequenceNumber, diff --git a/iota-execution/latest/iota-adapter/src/execution_engine.rs b/iota-execution/latest/iota-adapter/src/execution_engine.rs index 910b0803f23..847e9767e50 100644 --- a/iota-execution/latest/iota-adapter/src/execution_engine.rs +++ b/iota-execution/latest/iota-adapter/src/execution_engine.rs @@ -948,6 +948,31 @@ mod checked { construct_advance_epoch_pt_impl(builder, params, call_arg_vec) } + pub fn construct_advance_epoch_pt_v4( + builder: ProgrammableTransactionBuilder, + params: &AdvanceEpochParams, + ) -> Result { + // the first three arguments to the advance_epoch function, namely + // validator_subsidy, storage_charges and computation_charges, are + // common to both v1, v2, v3 and v4 and are added in + // `construct_advance_epoch_pt_impl`. The remaining arguments are added + // here. + let call_arg_vec = vec![ + CallArg::Pure(bcs::to_bytes(¶ms.computation_charge_burned).unwrap()), /* computation_charge_burned: u64 */ + CallArg::IOTA_SYSTEM_MUT, // wrapper: &mut IotaSystemState + CallArg::Pure(bcs::to_bytes(¶ms.epoch).unwrap()), // new_epoch: u64 + CallArg::Pure(bcs::to_bytes(¶ms.next_protocol_version.as_u64()).unwrap()), /* next_protocol_version: u64 */ + CallArg::Pure(bcs::to_bytes(¶ms.storage_rebate).unwrap()), // storage_rebate: u64 + CallArg::Pure(bcs::to_bytes(¶ms.non_refundable_storage_fee).unwrap()), /* non_refundable_storage_fee: u64 */ + CallArg::Pure(bcs::to_bytes(¶ms.reward_slashing_rate).unwrap()), /* reward_slashing_rate: u64 */ + CallArg::Pure(bcs::to_bytes(¶ms.epoch_start_timestamp_ms).unwrap()), /* epoch_start_timestamp_ms: u64 */ + CallArg::Pure(bcs::to_bytes(¶ms.max_committee_members_count).unwrap()), /* max_committee_members_count: u64 */ + CallArg::Pure(bcs::to_bytes(¶ms.eligible_active_validators).unwrap()), /* eligible_active_validators: Vec */ + CallArg::Pure(bcs::to_bytes(¶ms.scores).unwrap()), // scores: Vec + ]; + construct_advance_epoch_pt_impl(builder, params, call_arg_vec) + } + /// Advances the epoch by executing a `ProgrammableTransaction`. If the /// transaction fails, it switches to safe mode and retries the epoch /// advancement in a more controlled environment. The function also @@ -1045,6 +1070,7 @@ mod checked { // separate AdvanceEpochParams struct. max_committee_members_count: 0, eligible_active_validators: vec![], + scores: vec![], }; let advance_epoch_pt = construct_advance_epoch_pt_v1(builder, ¶ms)?; advance_epoch_impl( @@ -1087,9 +1113,10 @@ mod checked { reward_slashing_rate: protocol_config.reward_slashing_rate(), epoch_start_timestamp_ms: change_epoch_v2.epoch_start_timestamp_ms, max_committee_members_count: protocol_config.max_committee_members_count(), - // AdvanceEpochV2 does not use this field, but keeping them to avoid creating a + // AdvanceEpochV2 does not use these fields, but keeping them to avoid creating a // separate AdvanceEpochParams struct. eligible_active_validators: vec![], + scores: vec![], }; let advance_epoch_pt = construct_advance_epoch_pt_v2(builder, ¶ms)?; advance_epoch_impl( @@ -1133,6 +1160,9 @@ mod checked { epoch_start_timestamp_ms: change_epoch_v3.epoch_start_timestamp_ms, max_committee_members_count: protocol_config.max_committee_members_count(), eligible_active_validators: change_epoch_v3.eligible_active_validators, + // AdvanceEpochV3 does not use these fields, but keeping them to avoid creating a + // separate AdvanceEpochParams struct. + scores: vec![], }; let advance_epoch_pt = construct_advance_epoch_pt_v3(builder, ¶ms)?; advance_epoch_impl( @@ -1163,7 +1193,6 @@ mod checked { metrics: Arc, trace_builder_opt: &mut Option, ) -> Result<(), ExecutionError> { - // To do: pass scores let params = AdvanceEpochParams { epoch: change_epoch_v4.epoch, next_protocol_version: change_epoch_v4.protocol_version, @@ -1177,8 +1206,9 @@ mod checked { epoch_start_timestamp_ms: change_epoch_v4.epoch_start_timestamp_ms, max_committee_members_count: protocol_config.max_committee_members_count(), eligible_active_validators: change_epoch_v4.eligible_active_validators, + scores: change_epoch_v4.scores, }; - let advance_epoch_pt = construct_advance_epoch_pt_v3(builder, ¶ms)?; + let advance_epoch_pt = construct_advance_epoch_pt_v4(builder, ¶ms)?; advance_epoch_impl( advance_epoch_pt, params,