Skip to content

Commit

Permalink
protocol upgrade (v2): Burn slashed rewards (phase RD 1) + changes to…
Browse files Browse the repository at this point in the history
… system epoch info event (#3739)

* initial changes

* add tests

* remove unused function

* add snapshot related files

* fix(node): regenerate baselines for tests

* fix(node): simplify `compute_adjusted_reward_distribution`

* fix(node): update framework manifest

* fix(node): add some checks and comments to the rewards tests

* Update crates/iota-framework/packages/iota-system/sources/validator_set.move

correction in a comment

* fix(node): regenerate baselines for tests

* add comments to reward distribution tests

* show correct burnt amount in SystemEpochInfoEventV1

* remove parentheses

* Add checks that slashed rewards are burned to tests

* get burned and minted amounts directly from match function logic

* fix mixed up burned and minted amounts.

* update protocol config for v2

* generate v2 framework snapshot

* fix(iota-protocol-config): fixed resolving protocol version for simulation tests

* update swarm-config snapshots

* generate snapshot for e2e tests

* update open-rpc

* fix(iota-indexer): tests use protocol max version instead of 1

* update framework snapshot

* fix(iota-swarm-config): test baseline

* fix(iota-e2e-tests): update snapshot for snapshot tests

* fix(iota-e2e-tests): update snapshot-tests object ids and tx digest

* fix: add comment about protocol version 2

* fix: fmt

* fix: update protocol config comment

Co-authored-by: Thibault Martinez <[email protected]>

---------

Co-authored-by: muXxer <[email protected]>
Co-authored-by: Andrew <[email protected]>
Co-authored-by: Andrew Cullen <[email protected]>
Co-authored-by: Valerii Reutov <[email protected]>
Co-authored-by: miker83z <[email protected]>
Co-authored-by: Mirko Zichichi <[email protected]>
Co-authored-by: Levente Pap <[email protected]>
Co-authored-by: Thibault Martinez <[email protected]>
  • Loading branch information
9 people authored Dec 11, 2024
1 parent 6edd6cd commit b7e7a60
Show file tree
Hide file tree
Showing 22 changed files with 1,768 additions and 662 deletions.
12 changes: 5 additions & 7 deletions crates/iota-e2e-tests/tests/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,17 @@ async fn basic_read_cmd_snapshot_tests() -> Result<(), anyhow::Error> {
let mut test_cluster = TestClusterBuilder::new().build().await;
let context = &mut test_cluster.wallet;

// These object ids and transaction digest are picked by executing this test and
// copying over some random ids from the snapshot file
let cmds = vec![
"iota client objects {ME}", // valid addr
"iota client objects 0x0000000000000000000000000000000000000000000000000000000000000000", /* empty addr */
"iota client object 0x5", // valid object
"iota client object 0x5 --bcs", // valid object BCS
// Simtest object IDs are not stable so these object IDs may or may not exist currently --
// commenting them out for now.
// "iota client object 0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee",
// // valid object "iota client object
// 0x3b5121a0603ef7ab4cb57827fceca17db3338ef2cd76126cc1523b681df27cee --bcs", // valid
// object BCS
"iota client object 0x9135cb3b5aca99a1555b742bd11ddc45fba33343be182bdc161be69da2c41be1", /* valid object */
"iota client object 0x9135cb3b5aca99a1555b742bd11ddc45fba33343be182bdc161be69da2c41be1 --bcs", /* valid object BCS */
"iota client object 0x0000000000000000000000000000000000000000000000000000000000000000", /* non-existent object */
"iota client tx-block 5zibcom3dMckjyN16ygFwr5XNa9Exi1MmY3BQs984x1N", // valid tx digest
"iota client tx-block E5Zp4QQ84PQEceSw4JRi4VTScSAQweKSgdwp9XH4aVPd", // valid tx digest
"iota client tx-block 11111111111111111111111111111111", /* non-existent tx
* digest */
];
Expand Down
925 changes: 486 additions & 439 deletions crates/iota-e2e-tests/tests/snapshots/snapshot_tests__body_fn.snap

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10 changes: 10 additions & 0 deletions crates/iota-framework-snapshot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,15 @@
"0x000000000000000000000000000000000000000000000000000000000000000b",
"0x000000000000000000000000000000000000000000000000000000000000107a"
]
},
"2": {
"git_revision": "f20be52f200b",
"package_ids": [
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x000000000000000000000000000000000000000000000000000000000000000b",
"0x000000000000000000000000000000000000000000000000000000000000107a"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,9 @@ module iota_system::iota_system_state_inner {
let storage_charge_value = storage_charge.value();
let computation_charge = computation_reward.value();

// Mints or burns tokens depending on the target reward.
// Since not all rewards are distributed in case of slashed validators,
// tokens might be minted here and burnt in the same epoch change.
let (mut total_validator_rewards, minted_tokens_amount, mut burnt_tokens_amount) = match_computation_reward_to_target_reward(
validator_target_reward,
computation_reward,
Expand All @@ -701,7 +704,7 @@ module iota_system::iota_system_state_inner {
let new_total_stake = self.validators.total_stake();

let remaining_validator_rewards_amount_after_distribution = total_validator_rewards.value();
let total_validator_rewards_distributed = total_validator_rewards_amount_before_distribution - remaining_validator_rewards_amount_after_distribution;
let total_stake_rewards_distributed = total_validator_rewards_amount_before_distribution - remaining_validator_rewards_amount_after_distribution;

self.protocol_version = next_protocol_version;

Expand Down Expand Up @@ -731,9 +734,9 @@ module iota_system::iota_system_state_inner {
storage_rebate: storage_rebate_amount,
storage_fund_balance: self.storage_fund.total_balance(),
total_gas_fees: computation_charge,
total_stake_rewards_distributed: total_validator_rewards_distributed,
total_stake_rewards_distributed,
burnt_tokens_amount,
minted_tokens_amount
minted_tokens_amount,
}
);
self.safe_mode = false;
Expand All @@ -751,24 +754,22 @@ module iota_system::iota_system_state_inner {
/// and the amount of computation fees burned in this epoch.
fun match_computation_reward_to_target_reward(
validator_target_reward: u64,
mut computation_reward: Balance<IOTA>,
mut computation_charges: Balance<IOTA>,
iota_treasury_cap: &mut iota::iota::IotaTreasuryCap,
ctx: &TxContext,
): (Balance<IOTA>, u64, u64) {
let mut burnt_tokens_amount = 0;
let mut minted_tokens_amount = 0;
if (computation_reward.value() < validator_target_reward) {
let tokens_to_mint = validator_target_reward - computation_reward.value();
let new_tokens = iota_treasury_cap.mint_balance(tokens_to_mint, ctx);
minted_tokens_amount = new_tokens.value();
computation_reward.join(new_tokens);
} else if (computation_reward.value() > validator_target_reward) {
let tokens_to_burn = computation_reward.value() - validator_target_reward;
let rewards_to_burn = computation_reward.split(tokens_to_burn);
burnt_tokens_amount = rewards_to_burn.value();
iota_treasury_cap.burn_balance(rewards_to_burn, ctx);
let burnt_tokens_amount = computation_charges.value();
let minted_tokens_amount = validator_target_reward;
if (burnt_tokens_amount < minted_tokens_amount) {
let actual_amount_to_mint = minted_tokens_amount - burnt_tokens_amount;
let balance_to_mint = iota_treasury_cap.mint_balance(actual_amount_to_mint, ctx);
computation_charges.join(balance_to_mint);
} else if (burnt_tokens_amount > minted_tokens_amount) {
let actual_amount_to_burn = burnt_tokens_amount - minted_tokens_amount;
let balance_to_burn = computation_charges.split(actual_amount_to_burn);
iota_treasury_cap.burn_balance(balance_to_burn, ctx);
};
(computation_reward, minted_tokens_amount, burnt_tokens_amount)
(computation_charges, minted_tokens_amount, burnt_tokens_amount)
}

/// Return the current epoch number. Useful for applications that need a coarse-grained concept of time,
Expand Down
107 changes: 28 additions & 79 deletions crates/iota-framework/packages/iota-system/sources/validator_set.move
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module iota_system::validator_set {
use iota_system::staking_pool::{PoolTokenExchangeRate, StakedIota, pool_id};
use iota::priority_queue as pq;
use iota::vec_map::{Self, VecMap};
use iota::vec_set::VecSet;
use iota::vec_set::{Self, VecSet};
use iota::table::{Self, Table};
use iota::event;
use iota::table_vec::{Self, TableVec};
Expand Down Expand Up @@ -341,28 +341,14 @@ module iota_system::validator_set {
// punished.
let slashed_validators = compute_slashed_validators(self, *validator_report_records);

let total_slashed_validator_voting_power = sum_voting_power_by_addresses(&self.active_validators, &slashed_validators);

// Compute the reward adjustments of slashed validators, to be taken into
// account in adjusted reward computation.
let (total_staking_reward_adjustment, individual_staking_reward_adjustments) =
compute_reward_adjustments(
get_validator_indices(&self.active_validators, &slashed_validators),
reward_slashing_rate,
&unadjusted_staking_reward_amounts,
);

// Compute the adjusted amounts of stake each validator should get given the tallying rule
// reward adjustments we computed before.
// Compute the adjusted amounts of stake each validator should get according to the tallying rule.
// `compute_adjusted_reward_distribution` must be called before `distribute_reward` and `adjust_stake_and_gas_price` to
// make sure we are using the current epoch's stake information to compute reward distribution.
let adjusted_staking_reward_amounts = compute_adjusted_reward_distribution(
&self.active_validators,
total_voting_power,
total_slashed_validator_voting_power,
unadjusted_staking_reward_amounts,
total_staking_reward_adjustment,
individual_staking_reward_adjustments,
get_validator_indices_set(&self.active_validators, &slashed_validators),
reward_slashing_rate,
);

// Distribute the rewards before adjusting stake so that we immediately start compounding
Expand Down Expand Up @@ -638,18 +624,17 @@ module iota_system::validator_set {
option::none()
}


/// Given a vector of validator addresses, return their indices in the validator set.
/// Given a vector of validator addresses, return a set of all indices of the validators.
/// Aborts if any address isn't in the given validator set.
fun get_validator_indices(validators: &vector<ValidatorV1>, validator_addresses: &vector<address>): vector<u64> {
fun get_validator_indices_set(validators: &vector<ValidatorV1>, validator_addresses: &vector<address>): VecSet<u64> {
let length = validator_addresses.length();
let mut i = 0;
let mut res = vector[];
let mut res = vec_set::empty();
while (i < length) {
let addr = validator_addresses[i];
let index_opt = find_validator(validators, addr);
assert!(index_opt.is_some(), ENotAValidator);
res.push_back(index_opt.destroy_some());
res.insert(index_opt.destroy_some());
i = i + 1;
};
res
Expand Down Expand Up @@ -941,35 +926,6 @@ module iota_system::validator_set {
}
}

/// Compute both the individual reward adjustments and total reward adjustment for staking rewards.
fun compute_reward_adjustments(
mut slashed_validator_indices: vector<u64>,
reward_slashing_rate: u64,
unadjusted_staking_reward_amounts: &vector<u64>,
): (
u64, // sum of staking reward adjustments
VecMap<u64, u64>, // mapping of individual validator's staking reward adjustment from index -> amount
) {
let mut total_staking_reward_adjustment = 0;
let mut individual_staking_reward_adjustments = vec_map::empty();

while (!slashed_validator_indices.is_empty()) {
let validator_index = slashed_validator_indices.pop_back();

// Use the slashing rate to compute the amount of staking rewards slashed from this punished validator.
let unadjusted_staking_reward = unadjusted_staking_reward_amounts[validator_index];
let staking_reward_adjustment_u128 =
unadjusted_staking_reward as u128 * (reward_slashing_rate as u128)
/ BASIS_POINT_DENOMINATOR;

// Insert into individual mapping and record into the total adjustment sum.
individual_staking_reward_adjustments.insert(validator_index, staking_reward_adjustment_u128 as u64);
total_staking_reward_adjustment = total_staking_reward_adjustment + (staking_reward_adjustment_u128 as u64);
};

(total_staking_reward_adjustment, individual_staking_reward_adjustments)
}

/// Process the validator report records of the epoch and return the addresses of the
/// non-performant validators according to the input threshold.
fun compute_slashed_validators(
Expand Down Expand Up @@ -1023,44 +979,37 @@ module iota_system::validator_set {
/// The staking rewards are shared with the stakers.
fun compute_adjusted_reward_distribution(
validators: &vector<ValidatorV1>,
total_voting_power: u64,
total_slashed_validator_voting_power: u64,
unadjusted_staking_reward_amounts: vector<u64>,
total_staking_reward_adjustment: u64,
individual_staking_reward_adjustments: VecMap<u64, u64>,
slashed_validator_indices_set: VecSet<u64>,
reward_slashing_rate: u64,
): vector<u64> {
let total_unslashed_validator_voting_power = total_voting_power - total_slashed_validator_voting_power;
let mut adjusted_staking_reward_amounts = vector[];


// Loop through each validator and adjust rewards as necessary
let length = validators.length();

let mut i = 0;
while (i < length) {
let validator = &validators[i];
// Integer divisions will truncate the results. Because of this, we expect that at the end
// there will be some reward remaining in `total_reward`.
// Use u128 to avoid multiplication overflow.
let voting_power = validator.voting_power() as u128;

// Compute adjusted staking reward.
let unadjusted_staking_reward_amount = unadjusted_staking_reward_amounts[i];
let adjusted_staking_reward_amount =
// If the validator is one of the slashed ones, then subtract the adjustment.
if (individual_staking_reward_adjustments.contains(&i)) {
let adjustment = individual_staking_reward_adjustments[&i];
unadjusted_staking_reward_amount - adjustment
} else {
// Otherwise the slashed rewards should be distributed among the unslashed
// validators so add the corresponding adjustment.
let adjustment = total_staking_reward_adjustment as u128 * voting_power
/ (total_unslashed_validator_voting_power as u128);
unadjusted_staking_reward_amount + (adjustment as u64)
};

// Check if the validator is slashed
let adjusted_staking_reward_amount = if (slashed_validator_indices_set.contains(&i)) {
// 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)
} else {
// Otherwise, unadjusted staking reward amount is assigned to the unslashed validators
unadjusted_staking_reward_amount
};

adjusted_staking_reward_amounts.push_back(adjusted_staking_reward_amount);


// Move to the next validator
i = i + 1;
};

// The sum of the adjusted staking rewards may not be equal to the total staking reward,
// because of integer division truncation and the slashing of the rewards for the slashed validators.
adjusted_staking_reward_amounts
}

Expand Down
Loading

0 comments on commit b7e7a60

Please sign in to comment.