diff --git a/framework/cached-packages/src/libra_framework_sdk_builder.rs b/framework/cached-packages/src/libra_framework_sdk_builder.rs index 852bab104..41a38d8b2 100644 --- a/framework/cached-packages/src/libra_framework_sdk_builder.rs +++ b/framework/cached-packages/src/libra_framework_sdk_builder.rs @@ -484,29 +484,9 @@ pub enum EntryFunctionCall { fullnode_addresses: Vec, }, - /// Unlock from active delegation, it's moved to pending_inactive if locked_until_secs < current_time or - /// directly inactive if it's not from an active validator. - /// This can only called by the operator of the validator/staking pool. - StakeJoinValidatorSet { - pool_address: AccountAddress, - }, - - /// Similar to unlock_with_cap but will use ownership capability from the signing account. - /// Unlock `amount` from the active stake. Only possible if the lockup has expired. - /// Request to have `pool_address` leave the validator set. The validator is only actually removed from the set when - /// the next epoch starts. - /// The last validator in the set cannot leave. This is an edge case that should never happen as long as the network - /// is still operational. - /// - /// Can only be called by the operator of the validator/staking pool. - StakeLeaveValidatorSet { - pool_address: AccountAddress, - }, - - /// Add `amount` of coins from the `account` owning the StakePool. /// Rotate the consensus key of the validator, it'll take effect in next epoch. StakeRotateConsensusKey { - pool_address: AccountAddress, + validator_address: AccountAddress, new_consensus_pubkey: Vec, proof_of_possession: Vec, }, @@ -518,7 +498,7 @@ pub enum EntryFunctionCall { /// Update the network and full node addresses of the validator. This only takes effect in the next epoch. StakeUpdateNetworkAndFullnodeAddresses { - pool_address: AccountAddress, + validator_address: AccountAddress, new_network_addresses: Vec, new_fullnode_addresses: Vec, }, @@ -847,22 +827,22 @@ impl EntryFunctionCall { network_addresses, fullnode_addresses, ), - StakeJoinValidatorSet { pool_address } => stake_join_validator_set(pool_address), - StakeLeaveValidatorSet { pool_address } => stake_leave_validator_set(pool_address), StakeRotateConsensusKey { - pool_address, + validator_address, new_consensus_pubkey, proof_of_possession, - } => { - stake_rotate_consensus_key(pool_address, new_consensus_pubkey, proof_of_possession) - } + } => stake_rotate_consensus_key( + validator_address, + new_consensus_pubkey, + proof_of_possession, + ), StakeSetOperator { new_operator } => stake_set_operator(new_operator), StakeUpdateNetworkAndFullnodeAddresses { - pool_address, + validator_address, new_network_addresses, new_fullnode_addresses, } => stake_update_network_and_fullnode_addresses( - pool_address, + validator_address, new_network_addresses, new_fullnode_addresses, ), @@ -2204,51 +2184,9 @@ pub fn stake_initialize_validator( )) } -/// Unlock from active delegation, it's moved to pending_inactive if locked_until_secs < current_time or -/// directly inactive if it's not from an active validator. -/// This can only called by the operator of the validator/staking pool. -pub fn stake_join_validator_set(pool_address: AccountAddress) -> TransactionPayload { - TransactionPayload::EntryFunction(EntryFunction::new( - ModuleId::new( - AccountAddress::new([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, - ]), - ident_str!("stake").to_owned(), - ), - ident_str!("join_validator_set").to_owned(), - vec![], - vec![bcs::to_bytes(&pool_address).unwrap()], - )) -} - -/// Similar to unlock_with_cap but will use ownership capability from the signing account. -/// Unlock `amount` from the active stake. Only possible if the lockup has expired. -/// Request to have `pool_address` leave the validator set. The validator is only actually removed from the set when -/// the next epoch starts. -/// The last validator in the set cannot leave. This is an edge case that should never happen as long as the network -/// is still operational. -/// -/// Can only be called by the operator of the validator/staking pool. -pub fn stake_leave_validator_set(pool_address: AccountAddress) -> TransactionPayload { - TransactionPayload::EntryFunction(EntryFunction::new( - ModuleId::new( - AccountAddress::new([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, - ]), - ident_str!("stake").to_owned(), - ), - ident_str!("leave_validator_set").to_owned(), - vec![], - vec![bcs::to_bytes(&pool_address).unwrap()], - )) -} - -/// Add `amount` of coins from the `account` owning the StakePool. /// Rotate the consensus key of the validator, it'll take effect in next epoch. pub fn stake_rotate_consensus_key( - pool_address: AccountAddress, + validator_address: AccountAddress, new_consensus_pubkey: Vec, proof_of_possession: Vec, ) -> TransactionPayload { @@ -2263,7 +2201,7 @@ pub fn stake_rotate_consensus_key( ident_str!("rotate_consensus_key").to_owned(), vec![], vec![ - bcs::to_bytes(&pool_address).unwrap(), + bcs::to_bytes(&validator_address).unwrap(), bcs::to_bytes(&new_consensus_pubkey).unwrap(), bcs::to_bytes(&proof_of_possession).unwrap(), ], @@ -2288,7 +2226,7 @@ pub fn stake_set_operator(new_operator: AccountAddress) -> TransactionPayload { /// Update the network and full node addresses of the validator. This only takes effect in the next epoch. pub fn stake_update_network_and_fullnode_addresses( - pool_address: AccountAddress, + validator_address: AccountAddress, new_network_addresses: Vec, new_fullnode_addresses: Vec, ) -> TransactionPayload { @@ -2303,7 +2241,7 @@ pub fn stake_update_network_and_fullnode_addresses( ident_str!("update_network_and_fullnode_addresses").to_owned(), vec![], vec![ - bcs::to_bytes(&pool_address).unwrap(), + bcs::to_bytes(&validator_address).unwrap(), bcs::to_bytes(&new_network_addresses).unwrap(), bcs::to_bytes(&new_fullnode_addresses).unwrap(), ], @@ -3170,30 +3108,10 @@ mod decoder { } } - pub fn stake_join_validator_set(payload: &TransactionPayload) -> Option { - if let TransactionPayload::EntryFunction(script) = payload { - Some(EntryFunctionCall::StakeJoinValidatorSet { - pool_address: bcs::from_bytes(script.args().get(0)?).ok()?, - }) - } else { - None - } - } - - pub fn stake_leave_validator_set(payload: &TransactionPayload) -> Option { - if let TransactionPayload::EntryFunction(script) = payload { - Some(EntryFunctionCall::StakeLeaveValidatorSet { - pool_address: bcs::from_bytes(script.args().get(0)?).ok()?, - }) - } else { - None - } - } - pub fn stake_rotate_consensus_key(payload: &TransactionPayload) -> Option { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::StakeRotateConsensusKey { - pool_address: bcs::from_bytes(script.args().get(0)?).ok()?, + validator_address: bcs::from_bytes(script.args().get(0)?).ok()?, new_consensus_pubkey: bcs::from_bytes(script.args().get(1)?).ok()?, proof_of_possession: bcs::from_bytes(script.args().get(2)?).ok()?, }) @@ -3217,7 +3135,7 @@ mod decoder { ) -> Option { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::StakeUpdateNetworkAndFullnodeAddresses { - pool_address: bcs::from_bytes(script.args().get(0)?).ok()?, + validator_address: bcs::from_bytes(script.args().get(0)?).ok()?, new_network_addresses: bcs::from_bytes(script.args().get(1)?).ok()?, new_fullnode_addresses: bcs::from_bytes(script.args().get(2)?).ok()?, }) @@ -3545,14 +3463,6 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy(@diem_framework); - let stake_pool_res = global(stake_pool); + // let stake_pool_res = global(stake_pool); // aborts_if !exists(@diem_framework); - aborts_if !exists(stake_pool); - aborts_if global(stake_pool).delegated_voter != proposer_address; + // aborts_if !exists(stake_pool); + // aborts_if global(stake_pool).delegated_voter != proposer_address; include AbortsIfNotGovernanceConfig; let current_time = timestamp::now_seconds(); let proposal_expiration = current_time + governance_config.voting_duration_secs; - aborts_if stake_pool_res.locked_until_secs < proposal_expiration; + // aborts_if stake_pool_res.locked_until_secs < proposal_expiration; aborts_if !exists(@diem_framework); // let allow_validator_set_change = global(@diem_framework).allow_validator_set_change; // aborts_if !allow_validator_set_change && !exists(@diem_framework); diff --git a/framework/libra-framework/sources/modified_source/genesis.move b/framework/libra-framework/sources/modified_source/genesis.move index 18477d001..56bc75370 100644 --- a/framework/libra-framework/sources/modified_source/genesis.move +++ b/framework/libra-framework/sources/modified_source/genesis.move @@ -6,7 +6,6 @@ module diem_framework::genesis { use diem_framework::account; use diem_framework::aggregator_factory; - // use diem_framework::diem_coin::{Self, GasCoin}; use diem_framework::diem_governance; use diem_framework::block; use diem_framework::chain_id; @@ -127,16 +126,6 @@ module diem_framework::genesis { execution_config::set(&diem_framework_account, execution_config); version::initialize(&diem_framework_account, initial_version); stake::initialize(&diem_framework_account); - // staking_config::initialize( - // &diem_framework_account, - // minimum_stake, - // maximum_stake, - // recurring_lockup_duration_secs, - // allow_validator_set_change, - // rewards_rate, - // rewards_rate_denominator, - // voting_power_increase_limit, - // ); storage_gas::initialize(&diem_framework_account); gas_schedule::initialize(&diem_framework_account, gas_schedule); @@ -152,8 +141,6 @@ module diem_framework::genesis { //////// 0L //////// validator_universe::initialize(&diem_framework_account); - //TODO!: genesis seats - proof_of_fee::init_genesis_baseline_reward(&diem_framework_account); slow_wallet::initialize(&diem_framework_account); tower_state::initialize(&diem_framework_account); @@ -264,32 +251,10 @@ module diem_framework::genesis { let account = account::create_account(account_address); // coin::register(&account); coin::register(&account); - - // NO COINS MINTED AT GENESIS, NO PREMINE FOR TEST OR PROD. - // diem_coin::mint(diem_framework, account_address, balance); - // gas_coin::mint(diem_framework, account_address, TESTNET_GENESIS_BOOTSTRAP_COIN); account } } - // // This creates an funds an account if it doesn't exist. - // /// If it exists, it just returns the signer. - // fun depr_ol_create_account(root: &signer, account_address: address): signer { - // // assert!(!account::exists_at(account_address), error::already_exists(EDUPLICATE_ACCOUNT)); - // if (!account::exists_at(account_address)) { - // ol_account::create_account(root, account_address); - // // gas_coin::mint_to(root, account_address, 10000); - - // }; - // // NOTE: after the inital genesis set up, the validators can rotate their auth keys. - // // let new_signer = account::create_account(account_address); - // // coin::register(&new_signer); - // // mint genesis bootstrap coins - - - // create_signer(account_address) - // } - fun create_initialize_validators_with_commission( diem_framework: &signer, _depr_use_staking_contract: bool, @@ -321,39 +286,14 @@ module diem_framework::genesis { }); musical_chairs::initialize(diem_framework, num_validators); - stake::on_new_epoch(); + // TODO: maybe consolidate reconfig methods in one place + // for smoke tests + // stake::maybe_reconfigure(diem_framework, + // validator_universe::get_eligible_validators()); + stake::on_new_epoch() } - /// Sets up the initial validator set for the network. - /// The validator "owner" accounts, and their authentication - /// Addresses (and keys) are encoded in the `owners` - /// Each validator signs consensus messages with the private key corresponding to the Ed25519 - /// public key in `consensus_pubkeys`. - /// Finally, each validator must specify the network address - /// (see types/src/network_address/mod.rs) for itself and its full nodes. - /// - /// Network address fields are a vector per account, where each entry is a vector of addresses - /// encoded in a single BCS byte array. - // fun ol_create_initialize_validators(diem_framework: &signer, validators: vector) { - // let i = 0; - // let num_validators = vector::length(&validators); - - // // let validators_with_commission = vector::empty(); - - // while (i < num_validators) { - // ol_create_validator_accounts(diem_framework, vector::borrow(&validators, i)); - - // i = i + 1; - // }; - - // musical_chairs::initialize(diem_framework, num_validators); - - // stake::on_new_epoch(); - - // // create_initialize_validators_with_commission(diem_framework, false, validators_with_commission); - // } - fun create_initialize_validators(diem_framework: &signer, validators: vector) { let i = 0; let num_validators = vector::length(&validators); @@ -374,48 +314,6 @@ module diem_framework::genesis { create_initialize_validators_with_commission(diem_framework, false, validators_with_commission); } - // fun depr_ol_create_validator_accounts( - // diem_framework: &signer, - // validator: &ValidatorConfiguration, - // // _use_staking_contract: bool, - // ) { // NOTE: after the accounts are created, the migration will restore the previous authentication keys. - // let owner = &ol_create_account(diem_framework, validator.owner_address); - // // TODO: we probably don't need either of these accounts. - // ol_create_account(diem_framework, validator.operator_address); - // ol_create_account(diem_framework, validator.voter_address); - - // // Initialize the stake pool and join the validator set. - // // let pool_address = if (use_staking_contract) { - - // // staking_contract::create_staking_contract( - // // owner, - // // validator.operator_address, - // // validator.voter_address, - // // validator.stake_amount, - // // commission_config.commission_percentage, - // // x"", - // // ); - // // staking_contract::stake_pool_address(validator.owner_address, validator.operator_address) - // // } else - // let pool_address = { - - - // stake::initialize_stake_owner( - // owner, - // validator.stake_amount, - // validator.operator_address, - // validator.voter_address, - // ); - - // validator.owner_address - // }; - - - // // if (commission_config.join_during_genesis) { // TODO: remove this check - // initialize_validator(pool_address, validator); - // // }; - // } - fun register_one_genesis_validator( diem_framework: &signer, commission_config: &ValidatorConfigurationWithCommission, @@ -425,11 +323,6 @@ module diem_framework::genesis { let owner = &create_account(diem_framework, validator.owner_address, validator.stake_amount); - // account: &signer, - // consensus_pubkey: vector, - // proof_of_possession: vector, - // network_addresses: vector, - // fullnode_addresses: vector, validator_universe::register_validator( owner, validator.consensus_pubkey, @@ -438,73 +331,7 @@ module diem_framework::genesis { validator.full_node_network_addresses, ); - stake::join_validator_set_internal(owner, validator.owner_address); - } - - fun test_create_validator_accounts( - diem_framework: &signer, - commission_config: &ValidatorConfigurationWithCommission, - _use_staking_contract: bool, - ) { - let validator = &commission_config.validator_config; - - let owner = &create_account(diem_framework, validator.owner_address, validator.stake_amount); - // TODO: we probably don't need either of these accounts. - create_account(diem_framework, validator.operator_address, 0); - create_account(diem_framework, validator.voter_address, 0); - - // Initialize the stake pool and join the validator set. - // let pool_address = if (use_staking_contract) { - - // staking_contract::create_staking_contract( - // owner, - // validator.operator_address, - // validator.voter_address, - // validator.stake_amount, - // commission_config.commission_percentage, - // x"", - // ); - // staking_contract::stake_pool_address(validator.owner_address, validator.operator_address) - // } else - let pool_address = { - - - stake::initialize_stake_owner( - owner, - validator.stake_amount, - validator.operator_address, - validator.voter_address, - ); - - validator.owner_address - }; - - - if (commission_config.join_during_genesis) { // TODO: remove this check - initialize_validator(pool_address, validator); - validator_universe::genesis_helper_add_validator(diem_framework, owner) - }; - } - - fun initialize_validator(pool_address: address, validator: &ValidatorConfiguration) { - let operator = &create_signer(validator.operator_address); - - stake::rotate_consensus_key( - operator, - pool_address, - validator.consensus_pubkey, - validator.proof_of_possession, - ); - - stake::update_network_and_fullnode_addresses( - operator, - pool_address, - validator.network_addresses, - validator.full_node_network_addresses, - ); - - stake::join_validator_set_internal(operator, pool_address); - + stake::join_validator_set(owner, validator.owner_address); } /// The last step of genesis. diff --git a/framework/libra-framework/sources/modified_source/reconfiguration.move b/framework/libra-framework/sources/modified_source/reconfiguration.move index a3055113f..bc158f1d8 100644 --- a/framework/libra-framework/sources/modified_source/reconfiguration.move +++ b/framework/libra-framework/sources/modified_source/reconfiguration.move @@ -106,7 +106,7 @@ module diem_framework::reconfiguration { /// Signal validators to start using new configuration. Must be called from friend config modules. public(friend) fun reconfigure() acquires Configuration { // Do not do anything if genesis has not finished. - if (chain_status::is_genesis() || timestamp::now_microseconds() == 0 || !reconfiguration_enabled()) { + if (chain_status::is_genesis() || timestamp::now_microseconds() == 0) { return }; @@ -119,7 +119,7 @@ module diem_framework::reconfiguration { // This is OK because: // - The time changes in every non-empty block // - A block automatically ends after a transaction that emits a reconfiguration event, which is guaranteed by - // VM spec that all transactions comming after a reconfiguration transaction will be returned as Retry + // VM spec that all transactions coming after a reconfiguration transaction will be returned as Retry // status. // - Each transaction must emit at most one reconfiguration event // diff --git a/framework/libra-framework/sources/modified_source/stake.depr b/framework/libra-framework/sources/modified_source/stake.depr new file mode 100644 index 000000000..59deec5ab --- /dev/null +++ b/framework/libra-framework/sources/modified_source/stake.depr @@ -0,0 +1,1804 @@ +/** + * Validator lifecycle: + * 1. Prepare a validator node set up and call stake::initialize_validator + * 2. Once ready to deposit stake (or have funds assigned by a staking service in exchange for ownership capability), + * call stake::add_stake (or *_with_cap versions if called from the staking service) + * 3. Call stake::join_validator_set (or _with_cap version) to join the active validator set. Changes are effective in + * the next epoch. + * 4. Validate and gain rewards. The stake will automatically be locked up for a fixed duration (set by governance) and + * automatically renewed at expiration. + * 5. At any point, if the validator operator wants to update the consensus key or network/fullnode addresses, they can + * call stake::rotate_consensus_key and stake::update_network_and_fullnode_addresses. Similar to changes to stake, the + * changes to consensus key/network/fullnode addresses are only effective in the next epoch. + * 6. Validator can request to unlock their stake at any time. However, their stake will only become withdrawable when + * their current lockup expires. This can be at most as long as the fixed lockup duration. + * 7. After exiting, the validator can either explicitly leave the validator set by calling stake::leave_validator_set + * or if their stake drops below the min required, they would get removed at the end of the epoch. + * 8. Validator can always rejoin the validator set by going through steps 2-3 again. + * 9. An owner can always switch operators by calling stake::set_operator. + * 10. An owner can always switch designated voter by calling stake::set_designated_voter. +*/ +module diem_framework::stake_old { + use std::error; + use std::option::{Self, Option}; + use std::signer; + use std::vector; + use diem_std::bls12381; + use diem_std::table::Table; + use diem_std::comparator; + + use diem_framework::gas_coin::GasCoin; + use diem_framework::account; + use diem_framework::coin::{Self, Coin, MintCapability}; + use diem_framework::event::{Self, EventHandle}; + use diem_framework::timestamp; + use diem_framework::system_addresses; + use diem_framework::chain_status; + + use ol_framework::slow_wallet; + use ol_framework::testnet; + // use ol_framework::ol_account; + + // use diem_std::debug::print; + + friend diem_framework::block; + friend diem_framework::genesis; + friend diem_framework::reconfiguration; + friend diem_framework::transaction_fee; + + //////// 0L /////// + friend diem_framework::epoch_boundary; + friend ol_framework::rewards; + + /// Validator Config not published. + const EVALIDATOR_CONFIG: u64 = 1; + /// Not enough stake to join validator set. + const ESTAKE_TOO_LOW: u64 = 2; + /// Too much stake to join validator set. + const ESTAKE_TOO_HIGH: u64 = 3; + /// Account is already a validator or pending validator. + const EALREADY_ACTIVE_VALIDATOR: u64 = 4; + /// Account is not a validator. + const ENOT_VALIDATOR: u64 = 5; + /// Can't remove last validator. + const ELAST_VALIDATOR: u64 = 6; + /// Total stake exceeds maximum allowed. + const ESTAKE_EXCEEDS_MAX: u64 = 7; + /// Account is already registered as a validator candidate. + const EALREADY_REGISTERED: u64 = 8; + /// Account does not have the right operator capability. + const ENOT_OPERATOR: u64 = 9; + /// Validators cannot join or leave post genesis on this test network. + const ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED: u64 = 10; + /// Invalid consensus public key + const EINVALID_PUBLIC_KEY: u64 = 11; + /// Validator set exceeds the limit + const EVALIDATOR_SET_TOO_LARGE: u64 = 12; + /// Voting power increase has exceeded the limit for this current epoch. + const EVOTING_POWER_INCREASE_EXCEEDS_LIMIT: u64 = 13; + /// Stake pool does not exist at the provided pool address. + const ESTAKE_POOL_DOES_NOT_EXIST: u64 = 14; + /// Owner capability does not exist at the provided account. + const EOWNER_CAP_NOT_FOUND: u64 = 15; + /// An account cannot own more than one owner capability. + const EOWNER_CAP_ALREADY_EXISTS: u64 = 16; + /// Validator is not defined in the ACL of entities allowed to be validators + const EINELIGIBLE_VALIDATOR: u64 = 17; + /// Cannot update stake pool's lockup to earlier than current lockup. + const EINVALID_LOCKUP: u64 = 18; + /// Table to store collected transaction fees for each validator already exists. + const EFEES_TABLE_ALREADY_EXISTS: u64 = 19; + + /// Validator status enum. We can switch to proper enum later once Move supports it. + const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1; + const VALIDATOR_STATUS_ACTIVE: u64 = 2; + const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3; + const VALIDATOR_STATUS_INACTIVE: u64 = 4; + + /// Limit the maximum size to u16::max, it's the current limit of the bitvec + /// https://github.com/diem-labs/diem-core/blob/main/crates/diem-bitvec/src/lib.rs#L20 + const MAX_VALIDATOR_SET_SIZE: u64 = 65536; + + /// Limit the maximum value of `rewards_rate` in order to avoid any arithmetic overflow. + const MAX_REWARDS_RATE: u64 = 1000000; + + const MAX_U64: u128 = 18446744073709551615; + + /// Capability that represents ownership and can be used to control the validator and the associated stake pool. + /// Having this be separate from the signer for the account that the validator resources are hosted at allows + /// modules to have control over a validator. + struct OwnerCapability has key, store { + pool_address: address, + } + + /// Each validator has a separate StakePool resource and can provide a stake. + /// Changes in stake for an active validator: + /// 1. If a validator calls add_stake, the newly added stake is moved to pending_active. + /// 2. If validator calls unlock, their stake is moved to pending_inactive. + /// 2. When the next epoch starts, any pending_inactive stake is moved to inactive and can be withdrawn. + /// Any pending_active stake is moved to active and adds to the validator's voting power. + /// + /// Changes in stake for an inactive validator: + /// 1. If a validator calls add_stake, the newly added stake is moved directly to active. + /// 2. If validator calls unlock, their stake is moved directly to inactive. + /// 3. When the next epoch starts, the validator can be activated if their active stake is more than the minimum. + struct StakePool has key { + // active stake + active: Coin, + // inactive stake, can be withdrawn + inactive: Coin, + // pending activation for next epoch + pending_active: Coin, + // pending deactivation for next epoch + pending_inactive: Coin, + locked_until_secs: u64, + // Track the current operator of the validator node. + // This allows the operator to be different from the original account and allow for separation of + // the validator operations and ownership. + // Only the account holding OwnerCapability of the staking pool can update this. + operator_address: address, + + // Track the current vote delegator of the staking pool. + // Only the account holding OwnerCapability of the staking pool can update this. + delegated_voter: address, + + // The events emitted for the entire StakePool's lifecycle. + initialize_validator_events: EventHandle, + set_operator_events: EventHandle, + add_stake_events: EventHandle, + reactivate_stake_events: EventHandle, + rotate_consensus_key_events: EventHandle, + update_network_and_fullnode_addresses_events: EventHandle, + increase_lockup_events: EventHandle, + join_validator_set_events: EventHandle, + distribute_rewards_events: EventHandle, + unlock_stake_events: EventHandle, + withdraw_stake_events: EventHandle, + leave_validator_set_events: EventHandle, + } + + /// Validator info stored in validator address. + struct ValidatorConfig has key, copy, store, drop { + consensus_pubkey: vector, + network_addresses: vector, + // to make it compatible with previous definition, remove later + fullnode_addresses: vector, + // Index in the active set if the validator corresponding to this stake pool is active. + validator_index: u64, + } + + /// Consensus information per validator, stored in ValidatorSet. + struct ValidatorInfo has copy, store, drop { + addr: address, + voting_power: u64, + config: ValidatorConfig, + } + + /// Full ValidatorSet, stored in @diem_framework. + /// 1. join_validator_set adds to pending_active queue. + /// 2. leave_valdiator_set moves from active to pending_inactive queue. + /// 3. on_new_epoch processes two pending queues and refresh ValidatorInfo from the owner's address. + struct ValidatorSet has key { + consensus_scheme: u8, + // Active validators for the current epoch. + active_validators: vector, + // Pending validators to leave in next epoch (still active). + pending_inactive: vector, + // Pending validators to join in next epoch. + pending_active: vector, + // Current total voting power. + total_voting_power: u128, + // Total voting power waiting to join in the next epoch. + total_joining_power: u128, + } + + /// GasCoin capabilities, set during genesis and stored in @CoreResource account. + /// This allows the Stake module to mint rewards to stakers. + struct GasCoinCapabilities has key { + mint_cap: MintCapability, + } + + struct IndividualValidatorPerformance has store, drop { + successful_proposals: u64, + failed_proposals: u64, + } + + struct ValidatorPerformance has key { + validators: vector, + } + + struct RegisterValidatorCandidateEvent has drop, store { + pool_address: address, + } + + struct SetOperatorEvent has drop, store { + pool_address: address, + old_operator: address, + new_operator: address, + } + + struct AddStakeEvent has drop, store { + pool_address: address, + amount_added: u64, + } + + struct ReactivateStakeEvent has drop, store { + pool_address: address, + amount: u64, + } + + struct RotateConsensusKeyEvent has drop, store { + pool_address: address, + old_consensus_pubkey: vector, + new_consensus_pubkey: vector, + } + + struct UpdateNetworkAndFullnodeAddressesEvent has drop, store { + pool_address: address, + old_network_addresses: vector, + new_network_addresses: vector, + old_fullnode_addresses: vector, + new_fullnode_addresses: vector, + } + + struct IncreaseLockupEvent has drop, store { + pool_address: address, + old_locked_until_secs: u64, + new_locked_until_secs: u64, + } + + struct JoinValidatorSetEvent has drop, store { + pool_address: address, + } + + struct DistributeRewardsEvent has drop, store { + pool_address: address, + rewards_amount: u64, + } + + struct UnlockStakeEvent has drop, store { + pool_address: address, + amount_unlocked: u64, + } + + struct WithdrawStakeEvent has drop, store { + pool_address: address, + amount_withdrawn: u64, + } + + struct LeaveValidatorSetEvent has drop, store { + pool_address: address, + } + + /// Stores transaction fees assigned to validators. All fees are distributed to validators + /// at the end of the epoch. + struct ValidatorFees has key { + fees_table: Table>, + } + + // /// Initializes the resource storing information about collected transaction fees per validator. + // /// Used by `transaction_fee.move` to initialize fee collection and distribution. + // public(friend) fun initialize_validator_fees(diem_framework: &signer) { + // system_addresses::assert_diem_framework(diem_framework); + // assert!( + // !exists(@diem_framework), + // error::already_exists(EFEES_TABLE_ALREADY_EXISTS) + // ); + // move_to(diem_framework, ValidatorFees { fees_table: table::new() }); + // } + + // /// Stores the transaction fee collected to the specified validator address. + // public(friend) fun add_transaction_fee(validator_addr: address, fee: Coin) acquires ValidatorFees { + // let fees_table = &mut borrow_global_mut(@diem_framework).fees_table; + // if (table::contains(fees_table, validator_addr)) { + // let collected_fee = table::borrow_mut(fees_table, validator_addr); + // coin::merge(collected_fee, fee); + // } else { + // table::add(fees_table, validator_addr, fee); + // } + // } + + // #[view] + // /// Return the lockup expiration of the stake pool at `pool_address`. + // /// This will throw an error if there's no stake pool at `pool_address`. + // public fun get_lockup_secs(pool_address: address): u64 acquires StakePool { + // assert_stake_pool_exists(pool_address); + // borrow_global(pool_address).locked_until_secs + // } + + // #[view] + // /// Return the remaining lockup of the stake pool at `pool_address`. + // /// This will throw an error if there's no stake pool at `pool_address`. + // public fun get_remaining_lockup_secs(pool_address: address): u64 acquires StakePool { + // assert_stake_pool_exists(pool_address); + // let lockup_time = borrow_global(pool_address).locked_until_secs; + // if (lockup_time <= timestamp::now_seconds()) { + // 0 + // } else { + // lockup_time - timestamp::now_seconds() + // } + // } + + // #[view] + // /// Return the different stake amounts for `pool_address` (whether the validator is active or not). + // /// The returned amounts are for (active, inactive, pending_active, pending_inactive) stake respectively. + // public fun get_stake(pool_address: address): (u64, u64, u64, u64) acquires StakePool { + // assert_stake_pool_exists(pool_address); + // let stake_pool = borrow_global(pool_address); + // ( + // coin::value(&stake_pool.active), + // coin::value(&stake_pool.inactive), + // coin::value(&stake_pool.pending_active), + // coin::value(&stake_pool.pending_inactive), + // ) + // } + + #[view] + /// Returns the list of active validators + public fun get_current_validators(): vector
acquires ValidatorSet { + let validator_set = borrow_global(@diem_framework); + let addresses = vector::empty
(); + let i = 0; + while (i < vector::length(&validator_set.active_validators)) { + let v_info = vector::borrow(&validator_set.active_validators, i); + vector::push_back(&mut addresses, v_info.addr); + i = i + 1; + }; + addresses + } + + #[view] + /// helper to check if this address is a current one. + public fun is_current_val(addr: address):bool acquires ValidatorSet { + let vals = get_current_validators(); + vector::contains(&vals, &addr) + } + + #[view] + /// Returns the validator's state. + public fun get_validator_state(pool_address: address): u64 acquires ValidatorSet { + let validator_set = borrow_global(@diem_framework); + if (option::is_some(&find_validator(&validator_set.pending_active, pool_address))) { + VALIDATOR_STATUS_PENDING_ACTIVE + } else if (option::is_some(&find_validator(&validator_set.active_validators, pool_address))) { + VALIDATOR_STATUS_ACTIVE + } else if (option::is_some(&find_validator(&validator_set.pending_inactive, pool_address))) { + VALIDATOR_STATUS_PENDING_INACTIVE + } else { + VALIDATOR_STATUS_INACTIVE + } + } + + + //////// 0L //////// + // TODO: v7 + #[view] + /// Returns the validator's state. + public fun is_valid(pool_address: address): bool acquires ValidatorSet { + get_validator_state(pool_address) < VALIDATOR_STATUS_INACTIVE + } + + + #[view] + /// Return the voting power of the validator in the current epoch. + /// This is the same as the validator's total active and pending_inactive stake. + public fun get_current_epoch_voting_power(pool_address: address): u64 acquires StakePool, ValidatorSet { + assert_stake_pool_exists(pool_address); + let validator_state = get_validator_state(pool_address); + // Both active and pending inactive validators can still vote in the current epoch. + if (validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE) { + let active_stake = coin::value(&borrow_global(pool_address).active); + let pending_inactive_stake = coin::value(&borrow_global(pool_address).pending_inactive); + active_stake + pending_inactive_stake + } else { + 0 + } + } + + // #[view] + // /// Return the delegated voter of the validator at `pool_address`. + // public fun get_delegated_voter(pool_address: address): address acquires StakePool { + // assert_stake_pool_exists(pool_address); + // borrow_global(pool_address).delegated_voter + // } + + #[view] + /// Return the operator of the validator at `pool_address`. + public fun get_operator(pool_address: address): address acquires StakePool { + assert_stake_pool_exists(pool_address); + borrow_global(pool_address).operator_address + } + + /// Return the pool address in `owner_cap`. + public fun get_owned_pool_address(owner_cap: &OwnerCapability): address { + owner_cap.pool_address + } + + #[view] + /// Return the validator index for `pool_address`. + public fun get_validator_index(pool_address: address): u64 acquires ValidatorConfig { + assert_stake_pool_exists(pool_address); + borrow_global(pool_address).validator_index + } + + #[view] + /// Return the number of successful and failed proposals for the proposal at the given validator index. + public fun get_current_epoch_proposal_counts(validator_index: u64): (u64, u64) acquires ValidatorPerformance { + let validator_performances = &borrow_global(@diem_framework).validators; + let validator_performance = vector::borrow(validator_performances, validator_index); + (validator_performance.successful_proposals, validator_performance.failed_proposals) + } + + #[view] + /// Return the validator's config. + public fun get_validator_config(pool_address: address): (vector, vector, vector) acquires ValidatorConfig { + assert_stake_pool_exists(pool_address); + let validator_config = borrow_global(pool_address); + (validator_config.consensus_pubkey, validator_config.network_addresses, validator_config.fullnode_addresses) + } + + #[view] + public fun stake_pool_exists(addr: address): bool { + exists(addr) + } + + /// Initialize validator set to the core resource account. + public(friend) fun initialize(diem_framework: &signer) { + system_addresses::assert_diem_framework(diem_framework); + + move_to(diem_framework, ValidatorSet { + consensus_scheme: 0, + active_validators: vector::empty(), + pending_active: vector::empty(), + pending_inactive: vector::empty(), + total_voting_power: 0, + total_joining_power: 0, + }); + + move_to(diem_framework, ValidatorPerformance { + validators: vector::empty(), + }); + } + + /// This is only called during Genesis, which is where MintCapability can be created. + /// Beyond genesis, no one can create GasCoin mint/burn capabilities. + + // TODO: v7 - remove this + // public(friend) fun store_diem_coin_mint_cap(diem_framework: &signer, mint_cap: MintCapability) { + // system_addresses::assert_diem_framework(diem_framework); + // move_to(diem_framework, GasCoinCapabilities { mint_cap }) + // } + + /// Allow on chain governance to remove validators from the validator set. + // TODO: v7 - remove this + + public fun remove_validators( + diem_framework: &signer, + validators: &vector
, + ) acquires ValidatorSet { + system_addresses::assert_diem_framework(diem_framework); + + let validator_set = borrow_global_mut(@diem_framework); + let active_validators = &mut validator_set.active_validators; + let pending_inactive = &mut validator_set.pending_inactive; + let len = vector::length(validators); + let i = 0; + // Remove each validator from the validator set. + while ({ + spec { + invariant spec_validators_are_initialized(active_validators); + invariant spec_validator_indices_are_valid(active_validators); + invariant spec_validators_are_initialized(pending_inactive); + invariant spec_validator_indices_are_valid(pending_inactive); + }; + i < len + }) { + let validator = *vector::borrow(validators, i); + let validator_index = find_validator(active_validators, validator); + if (option::is_some(&validator_index)) { + let validator_info = vector::swap_remove(active_validators, *option::borrow(&validator_index)); + vector::push_back(pending_inactive, validator_info); + }; + i = i + 1; + }; + } + + /// Initialize the validator account and give ownership to the signing account + /// except it leaves the ValidatorConfig to be set by another entity. + /// Note: this triggers setting the operator and owner, set it to the account's address + /// to set later. + public entry fun initialize_stake_owner( + owner: &signer, + initial_stake_amount: u64, + operator: address, + _voter: address, + ) acquires AllowedValidators, OwnerCapability, StakePool { + initialize_owner(owner); + move_to(owner, ValidatorConfig { + consensus_pubkey: vector::empty(), + network_addresses: vector::empty(), + fullnode_addresses: vector::empty(), + validator_index: 0, + }); + + if (initial_stake_amount > 0) { + // add_stake(owner, initial_stake_amount); + }; + + let account_address = signer::address_of(owner); + if (account_address != operator) { + set_operator(owner, operator) + }; + // if (account_address != voter) { + // set_delegated_voter(owner, voter) + // }; + } + + /// Initialize the validator account and give ownership to the signing account. + public entry fun initialize_validator( + account: &signer, + consensus_pubkey: vector, + proof_of_possession: vector, + network_addresses: vector, + fullnode_addresses: vector, + ) acquires AllowedValidators { + // Checks the public key has a valid proof-of-possession to prevent rogue-key attacks. + let pubkey_from_pop = &mut bls12381::public_key_from_bytes_with_pop( + consensus_pubkey, + &bls12381::proof_of_possession_from_bytes(proof_of_possession) + ); + assert!(option::is_some(pubkey_from_pop), error::invalid_argument(EINVALID_PUBLIC_KEY)); + + initialize_owner(account); + + move_to(account, ValidatorConfig { + consensus_pubkey, + network_addresses, + fullnode_addresses, + validator_index: 0, + }); + + slow_wallet::set_slow(account); + } + + fun initialize_owner(owner: &signer) acquires AllowedValidators { + let owner_address = signer::address_of(owner); + assert!(is_allowed(owner_address), error::not_found(EINELIGIBLE_VALIDATOR)); + assert!(!stake_pool_exists(owner_address), error::already_exists(EALREADY_REGISTERED)); + + move_to(owner, StakePool { + active: coin::zero(), + pending_active: coin::zero(), + pending_inactive: coin::zero(), + inactive: coin::zero(), + locked_until_secs: 0, + operator_address: owner_address, + delegated_voter: owner_address, + // Events. + initialize_validator_events: account::new_event_handle(owner), + set_operator_events: account::new_event_handle(owner), + add_stake_events: account::new_event_handle(owner), + reactivate_stake_events: account::new_event_handle(owner), + rotate_consensus_key_events: account::new_event_handle(owner), + update_network_and_fullnode_addresses_events: account::new_event_handle(owner), + increase_lockup_events: account::new_event_handle(owner), + join_validator_set_events: account::new_event_handle(owner), + distribute_rewards_events: account::new_event_handle(owner), + unlock_stake_events: account::new_event_handle(owner), + withdraw_stake_events: account::new_event_handle(owner), + leave_validator_set_events: account::new_event_handle(owner), + }); + + // move_to(owner, OwnerCapability { pool_address: owner_address }); + } + + // /// Extract and return owner capability from the signing account. + // public fun extract_owner_cap(owner: &signer): OwnerCapability acquires OwnerCapability { + // let owner_address = signer::address_of(owner); + // assert_owner_cap_exists(owner_address); + // move_from(owner_address) + // } + + // /// Deposit `owner_cap` into `account`. This requires `account` to not already have owernship of another + // /// staking pool. + // public fun deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) { + // assert!(!exists(signer::address_of(owner)), error::not_found(EOWNER_CAP_ALREADY_EXISTS)); + // move_to(owner, owner_cap); + // } + + // /// Destroy `owner_cap`. + // public fun destroy_owner_cap(owner_cap: OwnerCapability) { + // let OwnerCapability { pool_address: _ } = owner_cap; + // } + + /// Allows an owner to change the operator of the stake pool. + public entry fun set_operator(owner: &signer, new_operator: address) acquires OwnerCapability, StakePool { + let owner_address = signer::address_of(owner); + assert_owner_cap_exists(owner_address); + let ownership_cap = borrow_global(owner_address); + set_operator_with_cap(ownership_cap, new_operator); + } + + /// Allows an account with ownership capability to change the operator of the stake pool. + public fun set_operator_with_cap(owner_cap: &OwnerCapability, new_operator: address) acquires StakePool { + let pool_address = owner_cap.pool_address; + assert_stake_pool_exists(pool_address); + let stake_pool = borrow_global_mut(pool_address); + let old_operator = stake_pool.operator_address; + stake_pool.operator_address = new_operator; + + event::emit_event( + &mut stake_pool.set_operator_events, + SetOperatorEvent { + pool_address, + old_operator, + new_operator, + }, + ); + } + + // /// Allows an owner to change the delegated voter of the stake pool. + // public entry fun set_delegated_voter(owner: &signer, new_voter: address) acquires OwnerCapability, StakePool { + // let owner_address = signer::address_of(owner); + // assert_owner_cap_exists(owner_address); + // let ownership_cap = borrow_global(owner_address); + // set_delegated_voter_with_cap(ownership_cap, new_voter); + // } + + // /// Allows an owner to change the delegated voter of the stake pool. + // public fun set_delegated_voter_with_cap(owner_cap: &OwnerCapability, new_voter: address) acquires StakePool { + // let pool_address = owner_cap.pool_address; + // assert_stake_pool_exists(pool_address); + // let stake_pool = borrow_global_mut(pool_address); + // stake_pool.delegated_voter = new_voter; + // } + + /// Add `amount` of coins from the `account` owning the StakePool. + + // TODO: v7 - remove this + // public entry fun add_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool, ValidatorSet { + // let owner_address = signer::address_of(owner); + // assert_owner_cap_exists(owner_address); + // let ownership_cap = borrow_global(owner_address); + // add_stake_with_cap(ownership_cap, coin::withdraw(owner, amount)); + // } + + // TODO: v7 - remove this + + // /// Add `coins` into `pool_address`. this requires the corresponding `owner_cap` to be passed in. + // public fun add_stake_with_cap(owner_cap: &OwnerCapability, coins: Coin) acquires StakePool, ValidatorSet { + // let pool_address = owner_cap.pool_address; + // assert_stake_pool_exists(pool_address); + + // let amount = coin::value(&coins); + // if (amount == 0) { + // coin::destroy_zero(coins); + // return + // }; + + // // Only track and validate voting power increase for active and pending_active validator. + // // Pending_inactive validator will be removed from the validator set in the next epoch. + // // Inactive validator's total stake will be tracked when they join the validator set. + // let validator_set = borrow_global_mut(@diem_framework); + // // Search directly rather using get_validator_state to save on unnecessary loops. + // if (option::is_some(&find_validator(&validator_set.active_validators, pool_address)) || + // option::is_some(&find_validator(&validator_set.pending_active, pool_address))) { + // update_voting_power_increase(amount); + // }; + + // // Add to pending_active if it's a current validator because the stake is not counted until the next epoch. + // // Otherwise, the delegation can be added to active directly as the validator is also activated in the epoch. + // let stake_pool = borrow_global_mut(pool_address); + // if (is_current_epoch_validator(pool_address)) { + // coin::merge(&mut stake_pool.pending_active, coins); + // } else { + // coin::merge(&mut stake_pool.active, coins); + // }; + + // // let (_, maximum_stake) = staking_config::get_required_stake(&staking_config::get()); + // let maximum_stake = 1000; + + // let voting_power = get_next_epoch_voting_power(stake_pool); + + // assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_EXCEEDS_MAX)); + + // event::emit_event( + // &mut stake_pool.add_stake_events, + // AddStakeEvent { + // pool_address, + // amount_added: amount, + // }, + // ); + // } + + // /// Move `amount` of coins from pending_inactive to active. + // public entry fun reactivate_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool { + // let owner_address = signer::address_of(owner); + // assert_owner_cap_exists(owner_address); + // let ownership_cap = borrow_global(owner_address); + // reactivate_stake_with_cap(ownership_cap, amount); + // } + + // public fun reactivate_stake_with_cap(owner_cap: &OwnerCapability, amount: u64) acquires StakePool { + // let pool_address = owner_cap.pool_address; + // assert_stake_pool_exists(pool_address); + + // // Cap the amount to reactivate by the amount in pending_inactive. + // let stake_pool = borrow_global_mut(pool_address); + // let total_pending_inactive = coin::value(&stake_pool.pending_inactive); + // amount = min(amount, total_pending_inactive); + + // // Since this does not count as a voting power change (pending inactive still counts as voting power in the + // // current epoch), stake can be immediately moved from pending inactive to active. + // // We also don't need to check voting power increase as there's none. + // let reactivated_coins = coin::extract(&mut stake_pool.pending_inactive, amount); + // coin::merge(&mut stake_pool.active, reactivated_coins); + + // event::emit_event( + // &mut stake_pool.reactivate_stake_events, + // ReactivateStakeEvent { + // pool_address, + // amount, + // }, + // ); + // } + + /// Rotate the consensus key of the validator, it'll take effect in next epoch. + public entry fun rotate_consensus_key( + operator: &signer, + pool_address: address, + new_consensus_pubkey: vector, + proof_of_possession: vector, + ) acquires StakePool, ValidatorConfig { + assert_stake_pool_exists(pool_address); + let stake_pool = borrow_global_mut(pool_address); + assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); + + assert!(exists(pool_address), error::not_found(EVALIDATOR_CONFIG)); + let validator_info = borrow_global_mut(pool_address); + let old_consensus_pubkey = validator_info.consensus_pubkey; + // Checks the public key has a valid proof-of-possession to prevent rogue-key attacks. + let pubkey_from_pop = &mut bls12381::public_key_from_bytes_with_pop( + new_consensus_pubkey, + &bls12381::proof_of_possession_from_bytes(proof_of_possession) + ); + assert!(option::is_some(pubkey_from_pop), error::invalid_argument(EINVALID_PUBLIC_KEY)); + validator_info.consensus_pubkey = new_consensus_pubkey; + + event::emit_event( + &mut stake_pool.rotate_consensus_key_events, + RotateConsensusKeyEvent { + pool_address, + old_consensus_pubkey, + new_consensus_pubkey, + }, + ); + } + + /// Update the network and full node addresses of the validator. This only takes effect in the next epoch. + public entry fun update_network_and_fullnode_addresses( + operator: &signer, + pool_address: address, + new_network_addresses: vector, + new_fullnode_addresses: vector, + ) acquires StakePool, ValidatorConfig { + assert_stake_pool_exists(pool_address); + let stake_pool = borrow_global_mut(pool_address); + assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); + + assert!(exists(pool_address), error::not_found(EVALIDATOR_CONFIG)); + let validator_info = borrow_global_mut(pool_address); + let old_network_addresses = validator_info.network_addresses; + validator_info.network_addresses = new_network_addresses; + let old_fullnode_addresses = validator_info.fullnode_addresses; + validator_info.fullnode_addresses = new_fullnode_addresses; + + event::emit_event( + &mut stake_pool.update_network_and_fullnode_addresses_events, + UpdateNetworkAndFullnodeAddressesEvent { + pool_address, + old_network_addresses, + new_network_addresses, + old_fullnode_addresses, + new_fullnode_addresses, + }, + ); + } + + // /// Similar to increase_lockup_with_cap but will use ownership capability from the signing account. + // public entry fun increase_lockup(owner: &signer) acquires OwnerCapability, { + // let owner_address = signer::address_of(owner); + // assert_owner_cap_exists(owner_address); + // let _ownership_cap = borrow_global(owner_address); + // // increase_lockup_with_cap(ownership_cap); + // } + + /// Unlock from active delegation, it's moved to pending_inactive if locked_until_secs < current_time or + /// directly inactive if it's not from an active validator. + // public fun increase_lockup_with_cap(owner_cap: &OwnerCapability) acquires StakePool { + // let pool_address = owner_cap.pool_address; + // assert_stake_pool_exists(pool_address); + // let config = staking_config::get(); + + // let stake_pool = borrow_global_mut(pool_address); + // let old_locked_until_secs = stake_pool.locked_until_secs; + // let new_locked_until_secs = timestamp::now_seconds() + staking_config::get_recurring_lockup_duration(&config); + // assert!(old_locked_until_secs < new_locked_until_secs, error::invalid_argument(EINVALID_LOCKUP)); + // stake_pool.locked_until_secs = new_locked_until_secs; + + // event::emit_event( + // &mut stake_pool.increase_lockup_events, + // IncreaseLockupEvent { + // pool_address, + // old_locked_until_secs, + // new_locked_until_secs, + // }, + // ); + // } + + /// This can only called by the operator of the validator/staking pool. + public entry fun join_validator_set( + operator: &signer, + pool_address: address + ) acquires StakePool, ValidatorConfig, ValidatorSet { + // assert!( + // staking_config::get_allow_validator_set_change(&staking_config::get()), + // error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED), + // ); + + join_validator_set_internal(operator, pool_address); + } + + /// Request to have `pool_address` join the validator set. Can only be called after calling `initialize_validator`. + /// If the validator has the required stake (more than minimum and less than maximum allowed), they will be + /// added to the pending_active queue. All validators in this queue will be added to the active set when the next + /// epoch starts (eligibility will be rechecked). + /// + /// This internal version can only be called by the Genesis module during Genesis. + public(friend) fun join_validator_set_internal( + operator: &signer, + pool_address: address + ) acquires StakePool, ValidatorConfig, ValidatorSet { + + assert_stake_pool_exists(pool_address); + let stake_pool = borrow_global_mut(pool_address); + assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); + + assert!( + get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE, + error::invalid_state(EALREADY_ACTIVE_VALIDATOR), + ); + + // let config = staking_config::get(); + // let (minimum_stake, maximum_stake) = staking_config::get_required_stake(&config); + let minimum_stake = 1; + let maximum_stake = 100; + let voting_power = 1; // voting power is always 1 in Libra + assert!(voting_power >= minimum_stake, error::invalid_argument(ESTAKE_TOO_LOW)); + assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_TOO_HIGH)); + // Track and validate voting power increase. + update_voting_power_increase(voting_power); + + + // Add validator to pending_active, to be activated in the next epoch. + let validator_config = borrow_global_mut(pool_address); + assert!(!vector::is_empty(&validator_config.consensus_pubkey), error::invalid_argument(EINVALID_PUBLIC_KEY)); + + // Validate the current validator set size has not exceeded the limit. + let validator_set = borrow_global_mut(@diem_framework); + vector::push_back(&mut validator_set.pending_active, generate_validator_info(pool_address, stake_pool, *validator_config)); + + let validator_set_size = vector::length(&validator_set.active_validators) + vector::length(&validator_set.pending_active); + assert!(validator_set_size <= MAX_VALIDATOR_SET_SIZE, error::invalid_argument(EVALIDATOR_SET_TOO_LARGE)); + + event::emit_event( + &mut stake_pool.join_validator_set_events, + JoinValidatorSetEvent { pool_address }, + ); + } + + /// Similar to unlock_with_cap but will use ownership capability from the signing account. + // public entry fun unlock(owner: &signer, amount: u64) acquires OwnerCapability, StakePool { + // let owner_address = signer::address_of(owner); + // assert_owner_cap_exists(owner_address); + // let ownership_cap = borrow_global(owner_address); + // unlock_with_cap(amount, ownership_cap); + // } + + /// Unlock `amount` from the active stake. Only possible if the lockup has expired. + // public fun unlock_with_cap(amount: u64, owner_cap: &OwnerCapability) acquires StakePool { + // // Short-circuit if amount to unlock is 0 so we don't emit events. + // if (amount == 0) { + // return + // }; + + // // Unlocked coins are moved to pending_inactive. When the current lockup cycle expires, they will be moved into + // // inactive in the earliest possible epoch transition. + // let pool_address = owner_cap.pool_address; + // assert_stake_pool_exists(pool_address); + // let stake_pool = borrow_global_mut(pool_address); + // // Cap amount to unlock by maximum active stake. + // let amount = min(amount, coin::value(&stake_pool.active)); + // let unlocked_stake = coin::extract(&mut stake_pool.active, amount); + // coin::merge(&mut stake_pool.pending_inactive, unlocked_stake); + + // event::emit_event( + // &mut stake_pool.unlock_stake_events, + // UnlockStakeEvent { + // pool_address, + // amount_unlocked: amount, + // }, + // ); + // } + + // /// Withdraw from `account`'s inactive stake. + // public entry fun withdraw( + // owner: &signer, + // withdraw_amount: u64 + // ) acquires OwnerCapability, StakePool, ValidatorSet { + // let owner_address = signer::address_of(owner); + // assert_owner_cap_exists(owner_address); + // let ownership_cap = borrow_global(owner_address); + // let coins = withdraw_with_cap(ownership_cap, withdraw_amount); + // coin::deposit(owner_address, coins); + // } + + // /// Withdraw from `pool_address`'s inactive stake with the corresponding `owner_cap`. + // public fun withdraw_with_cap( + // owner_cap: &OwnerCapability, + // withdraw_amount: u64 + // ): Coin acquires StakePool, ValidatorSet { + // let pool_address = owner_cap.pool_address; + // assert_stake_pool_exists(pool_address); + // let stake_pool = borrow_global_mut(pool_address); + // // There's an edge case where a validator unlocks their stake and leaves the validator set before + // // the stake is fully unlocked (the current lockup cycle has not expired yet). + // // This can leave their stake stuck in pending_inactive even after the current lockup cycle expires. + // if (get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE && + // timestamp::now_seconds() >= stake_pool.locked_until_secs) { + // let pending_inactive_stake = coin::extract_all(&mut stake_pool.pending_inactive); + // coin::merge(&mut stake_pool.inactive, pending_inactive_stake); + // }; + + // // Cap withdraw amount by total ianctive coins. + // withdraw_amount = min(withdraw_amount, coin::value(&stake_pool.inactive)); + // if (withdraw_amount == 0) return coin::zero(); + + // event::emit_event( + // &mut stake_pool.withdraw_stake_events, + // WithdrawStakeEvent { + // pool_address, + // amount_withdrawn: withdraw_amount, + // }, + // ); + + // coin::extract(&mut stake_pool.inactive, withdraw_amount) + // } + + /// Request to have `pool_address` leave the validator set. The validator is only actually removed from the set when + /// the next epoch starts. + /// The last validator in the set cannot leave. This is an edge case that should never happen as long as the network + /// is still operational. + /// + /// Can only be called by the operator of the validator/staking pool. + + // TODO: V7 change this + public entry fun leave_validator_set( + operator: &signer, + pool_address: address + ) acquires StakePool, ValidatorSet { + // let config = staking_config::get(); + // assert!( + // staking_config::get_allow_validator_set_change(&config), + // error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED), + // ); + + assert_stake_pool_exists(pool_address); + let stake_pool = borrow_global_mut(pool_address); + // Account has to be the operator. + assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); + + let validator_set = borrow_global_mut(@diem_framework); + // If the validator is still pending_active, directly kick the validator out. + let maybe_pending_active_index = find_validator(&validator_set.pending_active, pool_address); + if (option::is_some(&maybe_pending_active_index)) { + vector::swap_remove( + &mut validator_set.pending_active, option::extract(&mut maybe_pending_active_index)); + + // Decrease the voting power increase as the pending validator's voting power was added when they requested + // to join. Now that they changed their mind, their voting power should not affect the joining limit of this + // epoch. + let validator_stake = 1; // NOTE: voting power is always 1 in Libra + // total_joining_power should be larger than validator_stake but just in case there has been a small + // rounding error somewhere that can lead to an underflow, we still want to allow this transaction to + // succeed. + if (validator_set.total_joining_power > validator_stake) { + validator_set.total_joining_power = validator_set.total_joining_power - validator_stake; + } else { + validator_set.total_joining_power = 0; + }; + } else { + // Validate that the validator is already part of the validator set. + let maybe_active_index = find_validator(&validator_set.active_validators, pool_address); + assert!(option::is_some(&maybe_active_index), error::invalid_state(ENOT_VALIDATOR)); + let validator_info = vector::swap_remove( + &mut validator_set.active_validators, option::extract(&mut maybe_active_index)); + assert!(vector::length(&validator_set.active_validators) > 0, error::invalid_state(ELAST_VALIDATOR)); + vector::push_back(&mut validator_set.pending_inactive, validator_info); + + event::emit_event( + &mut stake_pool.leave_validator_set_events, + LeaveValidatorSetEvent { + pool_address, + }, + ); + }; + } + + /// Returns true if the current validator can still vote in the current epoch. + /// This includes validators that requested to leave but are still in the pending_inactive queue and will be removed + /// when the epoch starts. + public fun is_current_epoch_validator(pool_address: address): bool acquires ValidatorSet { + assert_stake_pool_exists(pool_address); + let validator_state = get_validator_state(pool_address); + validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE + } + + /// Update the validator performance (proposal statistics). This is only called by block::prologue(). + /// This function cannot abort. + public(friend) fun update_performance_statistics(proposer_index: Option, failed_proposer_indices: vector) acquires ValidatorPerformance { + // Validator set cannot change until the end of the epoch, so the validator index in arguments should + // match with those of the validators in ValidatorPerformance resource. + let validator_perf = borrow_global_mut(@diem_framework); + let validator_len = vector::length(&validator_perf.validators); + + // proposer_index is an option because it can be missing (for NilBlocks) + if (option::is_some(&proposer_index)) { + let cur_proposer_index = option::extract(&mut proposer_index); + // Here, and in all other vector::borrow, skip any validator indices that are out of bounds, + // this ensures that this function doesn't abort if there are out of bounds errors. + if (cur_proposer_index < validator_len) { + let validator = vector::borrow_mut(&mut validator_perf.validators, cur_proposer_index); + spec { + assume validator.successful_proposals + 1 <= MAX_U64; + }; + validator.successful_proposals = validator.successful_proposals + 1; + }; + }; + + let f = 0; + let f_len = vector::length(&failed_proposer_indices); + while ({ + spec { + invariant len(validator_perf.validators) == validator_len; + }; + f < f_len + }) { + let validator_index = *vector::borrow(&failed_proposer_indices, f); + if (validator_index < validator_len) { + let validator = vector::borrow_mut(&mut validator_perf.validators, validator_index); + spec { + assume validator.failed_proposals + 1 <= MAX_U64; + }; + validator.failed_proposals = validator.failed_proposals + 1; + }; + f = f + 1; + }; + } + + /// Triggers at epoch boundary. This function shouldn't abort. + /// + /// 1. Distribute transaction fees and rewards to stake pools of active and pending inactive validators (requested + /// to leave but not yet removed). + /// 2. Officially move pending active stake to active and move pending inactive stake to inactive. + /// The staking pool's voting power in this new epoch will be updated to the total active stake. + /// 3. Add pending active validators to the active set if they satisfy requirements so they can vote and remove + /// pending inactive validators so they no longer can vote. + /// 4. The validator's voting power in the validator set is updated to be the corresponding staking pool's voting + /// power. + public(friend) fun on_new_epoch() acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + let validator_set = borrow_global_mut(@diem_framework); + // let config = staking_config::get(); + let validator_perf = borrow_global_mut(@diem_framework); + + // Process pending stake and distribute transaction fees and rewards for each currently active validator. + // let i = 0; + // let len = vector::length(&validator_set.active_validators); + // while (i < len) { + // let validator = vector::borrow(&validator_set.active_validators, i); + // update_stake_pool(validator_perf, validator.addr); + // i = i + 1; + // }; + + // Activate currently pending_active validators. + append(&mut validator_set.active_validators, &mut validator_set.pending_active); + + // Officially deactivate all pending_inactive validators. They will now no longer receive rewards. + validator_set.pending_inactive = vector::empty(); + + // Update active validator set so that network address/public key change takes effect. + // Moreover, recalculate the total voting power, and deactivate the validator whose + // voting power is less than the minimum required stake. + let next_epoch_validators = vector::empty(); + // let (minimum_stake, _) = staking_config::get_required_stake(&config); + let minimum_stake = 0; + + + let vlen = vector::length(&validator_set.active_validators); + let total_voting_power = 0; + let i = 0; + while ({ + spec { + invariant spec_validators_are_initialized(next_epoch_validators); + }; + i < vlen + }) { + let old_validator_info = vector::borrow_mut(&mut validator_set.active_validators, i); + let pool_address = old_validator_info.addr; + let validator_config = borrow_global_mut(pool_address); + let stake_pool = borrow_global_mut(pool_address); + let new_validator_info = generate_validator_info(pool_address, stake_pool, *validator_config); + + // A validator needs at least the min stake required to join the validator set. + if (new_validator_info.voting_power >= minimum_stake) { + spec { + assume total_voting_power + new_validator_info.voting_power <= MAX_U128; + }; + total_voting_power = total_voting_power + (new_validator_info.voting_power as u128); + vector::push_back(&mut next_epoch_validators, new_validator_info); + }; + i = i + 1; + }; + + + validator_set.active_validators = next_epoch_validators; + validator_set.total_voting_power = total_voting_power; + + + validator_set.total_joining_power = 0; + + // Update validator indices, reset performance scores, and renew lockups. + validator_perf.validators = vector::empty(); + // let recurring_lockup_duration_secs = 0; + //staking_config::get_recurring_lockup_duration(&config); + + + let vlen = vector::length(&validator_set.active_validators); + let validator_index = 0; + while ({ + spec { + invariant spec_validators_are_initialized(validator_set.active_validators); + invariant len(validator_set.pending_active) == 0; + invariant len(validator_set.pending_inactive) == 0; + invariant 0 <= validator_index && validator_index <= vlen; + invariant vlen == len(validator_set.active_validators); + invariant forall i in 0..validator_index: + global(validator_set.active_validators[i].addr).validator_index < validator_index; + invariant len(validator_perf.validators) == validator_index; + }; + validator_index < vlen + }) { + + // Update validator index. + let validator_info = vector::borrow_mut(&mut validator_set.active_validators, validator_index); + validator_info.config.validator_index = validator_index; + let validator_config = borrow_global_mut(validator_info.addr); + validator_config.validator_index = validator_index; + + // reset performance scores. + vector::push_back(&mut validator_perf.validators, IndividualValidatorPerformance { + successful_proposals: 0, + failed_proposals: 0, + }); + + // // Automatically renew a validator's lockup for validators that will still be in the validator set in the + // // next epoch. + // let stake_pool = borrow_global_mut(validator_info.addr); + // if (stake_pool.locked_until_secs <= timestamp::now_seconds()) { + // spec { + // assume timestamp::spec_now_seconds() + recurring_lockup_duration_secs <= MAX_U64; + // }; + // stake_pool.locked_until_secs = + // timestamp::now_seconds() + recurring_lockup_duration_secs; + // }; + + validator_index = validator_index + 1; + }; + + // if (features::periodical_reward_rate_decrease_enabled()) { + // // Update rewards rate after reward distribution. + // staking_config::calculate_and_save_latest_epoch_rewards_rate(); + // }; + } + + /// DANGER: belt and suspenders critical mutations + /// Called on epoch boundary to reconfigure + /// No change may happen due to failover rules. + /// Returns instrumentation for audits: if what the validator set was, which validators qualified after failover rules, a list of validators which had missing configs and were excluded, if the new list sucessfully matches the actual validators after reconfiguration(actual_validator_set, qualified_on_failover, missing_configs, success) + public(friend) fun maybe_reconfigure(root: &signer, proposed_validators: vector
): (vector
, vector
, bool) acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + + + // NOTE: ol does not use the pending, and pending inactive lists. + // DANGER: critical mutation: belt and suspenders + + // we attempt to change the validator set + // it is no guaranteed that it will change + // we check our "failover rules" here + // which establishes minimum amount of vals for a viable network + + //////// RECONFIGURE //////// + + let (list_info, _voting_power, missing_configs) = make_validator_set_config(&proposed_validators); + // mutations happen in private function + bulk_set_next_validators(root, list_info); + + let validator_set = borrow_global_mut(@diem_framework); + let validator_perf = borrow_global_mut(@diem_framework); + + let validator_set_sanity = vector::empty
(); + + + ///// UPDATE VALIDATOR PERFORMANCE + + validator_perf.validators = vector::empty(); + + + let vlen = vector::length(&proposed_validators); + let validator_index = 0; + while ({ + spec { + invariant spec_validators_are_initialized(validator_set.active_validators); + invariant len(validator_set.pending_active) == 0; + invariant len(validator_set.pending_inactive) == 0; + invariant 0 <= validator_index && validator_index <= vlen; + invariant vlen == len(validator_set.active_validators); + invariant forall i in 0..validator_index: + global(validator_set.active_validators[i].addr).validator_index < validator_index; + invariant len(validator_perf.validators) == validator_index; + }; + validator_index < vlen + }) { + + // Update validator index. + let validator_info = vector::borrow_mut(&mut validator_set.active_validators, validator_index); + validator_info.config.validator_index = validator_index; + let validator_config = borrow_global_mut(validator_info.addr); + + vector::push_back(&mut validator_set_sanity, validator_info.addr); + validator_config.validator_index = validator_index; + + // reset performance scores. + vector::push_back(&mut validator_perf.validators, IndividualValidatorPerformance { + successful_proposals: 0, + failed_proposals: 0, + }); + + validator_index = validator_index + 1; + }; + + let finally_the_validators = get_current_validators(); + let success_sanity = comparator::is_equal( + &comparator::compare(&proposed_validators, &validator_set_sanity) + ); + let success_current = comparator::is_equal( + &comparator::compare(&proposed_validators, &finally_the_validators) + ); + + (finally_the_validators, missing_configs, success_sanity && success_current) + } + + #[test_only] + public fun test_make_val_cfg(list: &vector
): (vector, u128, vector
) acquires StakePool, ValidatorConfig { + make_validator_set_config(list) + } + + /// Make the active valiators list + /// returns: the list of validators, and the total voting power (1 per validator), and also the list of validators that did not have valid configs + fun make_validator_set_config(list: &vector
): (vector, u128, vector
) acquires StakePool, ValidatorConfig { + + let next_epoch_validators = vector::empty(); + let vlen = vector::length(list); + + let missing_configs = vector::empty(); + + let total_voting_power = 0; + let i = 0; + while ({ + spec { + invariant spec_validators_are_initialized(next_epoch_validators); + }; + i < vlen + }) { + // let old_validator_info = vector::borrow_mut(&mut validator_set.active_validators, i); + + let pool_address = *vector::borrow(list, i); + + + if (!exists(pool_address)) { + vector::push_back(&mut missing_configs, pool_address); + i = i + 1; + continue + }; // belt and suspenders + + let validator_config = borrow_global_mut(pool_address); + + if (!exists(pool_address)) { + vector::push_back(&mut missing_configs, pool_address); + i = i + 1; + continue + }; // belt and suspenders + let stake_pool = borrow_global_mut(pool_address); + + + let new_validator_info = generate_validator_info(pool_address, stake_pool, *validator_config); + + // all validators have same weight + total_voting_power = total_voting_power + 1; + + vector::push_back(&mut next_epoch_validators, new_validator_info); + // A validator needs at least the min stake required to join the validator set. + + // spec { + // assume total_voting_power + 1<= MAX_U128; + // }; + + i = i + 1; + }; + + (next_epoch_validators, total_voting_power, missing_configs) + } + + #[test_only] + public fun test_set_next_vals(root: &signer, list: vector) acquires ValidatorSet { + system_addresses::assert_ol(root); + bulk_set_next_validators(root, list); + } + + /// sets the global state for the next epoch validators + /// belt and suspenders, private function is authorized. Test functions also authorized. + fun bulk_set_next_validators(root: &signer, list: vector) acquires ValidatorSet { + system_addresses::assert_ol(root); + + let validator_set = borrow_global_mut(@diem_framework); + validator_set.active_validators = list; + validator_set.total_voting_power = (vector::length(&list) as u128); + } + + //////// Failover Rules //////// + // If the cardinality of validator_set in the next epoch is less than 4, + // if we are failing to qualify anyone. Pick top 1/2 of outgoing compliant validator set + // by proposals. They are probably online. + public fun check_failover_rules(proposed: vector
, performant: vector
): vector
acquires ValidatorSet { + + let min_f = 3; + let current_vals = get_current_validators(); + // check if this is not test. Failover doesn't apply here + if (testnet::is_testnet()) { + if (vector::length(&proposed) == 0) { + return current_vals + }; + return proposed + }; + + let is_performant_below_f_4 = vector::length(&performant) <= ( 2 * (min_f+1) + 1); + + let is_proposed_below_f_3 = vector::length(&proposed) <= ( 2 * min_f + 1); + + // hail mary. + // there may be something wrong with performance metrics or evaluation + // do nothing + if (is_proposed_below_f_3 && is_performant_below_f_4) { + return current_vals + }; + + // happy case, not near failure + if (!is_proposed_below_f_3 && !is_performant_below_f_4) return proposed; + // the proposed validators per the algo is to low. + // and the performant validators from previous epoch are at a healthy + // number, just failover to the performant validators + if (is_proposed_below_f_3 && !is_performant_below_f_4) { + return performant + }; + + // The proposed number of validators is not near failure + // but the previous epoch's performing nodes is below healthy + // pick whichever one has the most number + if (!is_proposed_below_f_3 && is_performant_below_f_4) { + if (vector::length(&performant) > vector::length(&proposed)) { + return performant + }; + // vector::for_each(performant, |v| { + // if (!vector::contains(&proposed, &v)) { + // vector::push_back(&mut proposed, v); + // } + // }); + return proposed + }; + + // this is unreachable but as a backstop for dev fingers + current_vals + } + + /// Bubble sort the validators by their proposal counts. + public fun get_sorted_vals_by_props(n: u64): vector
acquires ValidatorSet, ValidatorConfig, ValidatorPerformance { + let eligible_validators = get_current_validators(); + let length = vector::length
(&eligible_validators); + + // vector to store each address's node_weight + let weights = vector::empty(); + let filtered_vals = vector::empty
(); + let k = 0; + while (k < length) { + // TODO: Ensure that this address is an active validator + + let cur_address = *vector::borrow
(&eligible_validators, k); + + let idx = get_validator_index(cur_address); + let (proposed, failed) = get_current_epoch_proposal_counts(idx); + let delta = 0; + if (proposed > failed) { + delta = proposed - failed; + }; + + vector::push_back(&mut weights, delta); + vector::push_back
(&mut filtered_vals, cur_address); + k = k + 1; + }; + + // Sorting the accounts vector based on value (weights). + // Bubble sort algorithm + let len_filtered = vector::length
(&filtered_vals); + if (len_filtered < 2) return filtered_vals; + let i = 0; + while (i < len_filtered){ + let j = 0; + while(j < len_filtered-i-1){ + + let value_j = *(vector::borrow(&weights, j)); + + let value_jp1 = *(vector::borrow(&weights, j+1)); + if(value_j > value_jp1){ + + vector::swap(&mut weights, j, j+1); + + vector::swap
(&mut filtered_vals, j, j+1); + }; + j = j + 1; + + }; + i = i + 1; + + }; + + + // Reverse to have sorted order - high to low. + vector::reverse
(&mut filtered_vals); + + // Return the top n validators + let final = vector::empty
(); + let m = 0; + while ( m < n) { + vector::push_back(&mut final, *vector::borrow(&filtered_vals, m)); + m = m + 1; + }; + + return final + + } + + // TODO: this is deprecated + + /// Update individual validator's stake pool + /// 1. distribute transaction fees to active/pending_inactive delegations + /// 2. distribute rewards to active/pending_inactive delegations + /// 3. process pending_active, pending_inactive correspondingly + /// This function shouldn't abort. + fun update_stake_pool( + validator_perf: &ValidatorPerformance, + pool_address: address, + // _staking_config: &StakingConfig, + ) acquires StakePool, ValidatorConfig { + let stake_pool = borrow_global_mut(pool_address); + let validator_config = borrow_global(pool_address); + let cur_validator_perf = vector::borrow(&validator_perf.validators, validator_config.validator_index); + let _num_successful_proposals = cur_validator_perf.successful_proposals; + spec { + // The following addition should not overflow because `num_total_proposals` cannot be larger than 86400, + // the maximum number of proposals in a day (1 proposal per second). + assume cur_validator_perf.successful_proposals + cur_validator_perf.failed_proposals <= MAX_U64; + }; + let _num_total_proposals = cur_validator_perf.successful_proposals + cur_validator_perf.failed_proposals; + + // let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(staking_config); + + + let _rewards_rate = 1; + let _rewards_rate_denominator = 1; + + // let rewards_amount = distribute_rewards( + // &mut stake_pool.active, + // num_successful_proposals, + // num_total_proposals, + // rewards_rate, + // rewards_rate_denominator + // ); + + // // Pending active stake can now be active. + // coin::merge(&mut stake_pool.active, coin::extract_all(&mut stake_pool.pending_active)); + + // // Additionally, distribute transaction fees. + // if (features::collect_and_distribute_gas_fees()) { + // let fees_table = &mut borrow_global_mut(@diem_framework).fees_table; + // if (table::contains(fees_table, pool_address)) { + // let coin = table::remove(fees_table, pool_address); + // coin::merge(&mut stake_pool.active, coin); + // }; + // }; + + // // Pending inactive stake is only fully unlocked and moved into inactive if the current lockup cycle has expired + // let current_lockup_expiration = stake_pool.locked_until_secs; + // if (timestamp::now_seconds() >= current_lockup_expiration) { + // coin::merge( + // &mut stake_pool.inactive, + // coin::extract_all(&mut stake_pool.pending_inactive), + // ); + // }; + + event::emit_event( + &mut stake_pool.distribute_rewards_events, + DistributeRewardsEvent { + pool_address, + rewards_amount: 0, + }, + ); + } + + //////// 0L //////// + // since rewards are handled externally to stake.move we need an api to emit the event + public(friend) fun emit_distribute_reward(root: &signer, pool_address: address, rewards_amount: u64) acquires StakePool { + system_addresses::assert_ol(root); + let stake_pool = borrow_global_mut(pool_address); + event::emit_event( + &mut stake_pool.distribute_rewards_events, + DistributeRewardsEvent { + pool_address, + rewards_amount, + }, + ); + } + + // /// Calculate the rewards amount. + // // TODO: v7 - remove this + // fun calculate_rewards_amount( + // stake_amount: u64, + // num_successful_proposals: u64, + // num_total_proposals: u64, + // rewards_rate: u64, + // rewards_rate_denominator: u64, + // ): u64 { + + // 0 + // } + + + + + /// Mint rewards corresponding to current epoch's `stake` and `num_successful_votes`. + + // // TODO: v7 - change this + // fun distribute_rewards( + // stake: &mut Coin, + // _num_successful_proposals: u64, + // _num_total_proposals: u64, + // _rewards_rate: u64, + // _rewards_rate_denominator: u64, + // ): u64 acquires GasCoinCapabilities { + // // let _stake_amount = coin::value(stake); + // let rewards_amount = 0; + // if (rewards_amount > 0) { + // let mint_cap = &borrow_global(@diem_framework).mint_cap; + // let rewards = coin::mint(rewards_amount, mint_cap); + // ol_account::merge_coins(stake, rewards); + // }; + // rewards_amount + // } + + fun append(v1: &mut vector, v2: &mut vector) { + while (!vector::is_empty(v2)) { + vector::push_back(v1, vector::pop_back(v2)); + } + } + + fun find_validator(v: &vector, addr: address): Option { + let i = 0; + let len = vector::length(v); + while ({ + spec { + invariant !(exists j in 0..i: v[j].addr == addr); + }; + i < len + }) { + if (vector::borrow(v, i).addr == addr) { + return option::some(i) + }; + i = i + 1; + }; + option::none() + } + + fun generate_validator_info(addr: address, stake_pool: &StakePool, config: ValidatorConfig): ValidatorInfo { + let voting_power = get_next_epoch_voting_power(stake_pool); + ValidatorInfo { + addr, + voting_power, + config, + } + } + + /// Returns validator's next epoch voting power, including pending_active, active, and pending_inactive stake. + + // TODO: v7 - remove this + + fun get_next_epoch_voting_power(_stake_pool: &StakePool): u64 { + // let value_pending_active = coin::value(&stake_pool.pending_active); + // let value_active = coin::value(&stake_pool.active); + // let value_pending_inactive = coin::value(&stake_pool.pending_inactive); + // spec { + // assume value_pending_active + value_active + value_pending_inactive <= MAX_U64; + // }; + // // value_pending_active + value_active + value_pending_inactive; + 1 + } + + fun update_voting_power_increase(increase_amount: u64) acquires ValidatorSet { + let validator_set = borrow_global_mut(@diem_framework); + // let voting_power_increase_limit = + // (staking_config::get_voting_power_increase_limit(&staking_config::get()) as u128); + let voting_power_increase_limit = 1000; + validator_set.total_joining_power = validator_set.total_joining_power + (increase_amount as u128); + + // Only validator voting power increase if the current validator set's voting power > 0. + if (validator_set.total_voting_power > 0) { + assert!( + validator_set.total_joining_power <= validator_set.total_voting_power * voting_power_increase_limit / 100, + error::invalid_argument(EVOTING_POWER_INCREASE_EXCEEDS_LIMIT), + ); + } + } + + fun assert_stake_pool_exists(pool_address: address) { + assert!(stake_pool_exists(pool_address), error::invalid_argument(ESTAKE_POOL_DOES_NOT_EXIST)); + } + + //////////// TESTNET //////////// + + /// This provides an ACL for Testnet purposes. In testnet, everyone is a whale, a whale can be a validator. + /// This allows a testnet to bring additional entities into the validator set without compromising the + /// security of the testnet. This will NOT be enabled in Mainnet. + struct AllowedValidators has key { + accounts: vector
, + } + + public fun configure_allowed_validators(diem_framework: &signer, accounts: vector
) acquires AllowedValidators { + let diem_framework_address = signer::address_of(diem_framework); + system_addresses::assert_diem_framework(diem_framework); + if (!exists(diem_framework_address)) { + move_to(diem_framework, AllowedValidators { accounts }); + } else { + let allowed = borrow_global_mut(diem_framework_address); + allowed.accounts = accounts; + } + } + + fun is_allowed(account: address): bool acquires AllowedValidators { + if (!exists(@diem_framework)) { + true + } else { + let allowed = borrow_global(@diem_framework); + vector::contains(&allowed.accounts, &account) + } + } + // TODO: v7 - remove this + + fun assert_owner_cap_exists(owner: address) { + assert!(exists(owner), error::not_found(EOWNER_CAP_NOT_FOUND)); + } + + + #[test_only] + const EPOCH_DURATION: u64 = 60; + + #[test_only] + public fun mock_performance(vm: &signer, addr: address, success: u64, fail: u64) acquires ValidatorConfig, ValidatorPerformance { + system_addresses::assert_ol(vm); + + let idx = get_validator_index(addr); + + let perf = borrow_global_mut(@diem_framework); + + let p = vector::borrow_mut(&mut perf.validators, idx); + p.successful_proposals = success; + p.failed_proposals = fail; + } + + #[test_only] + public fun generate_identity(): (bls12381::SecretKey, bls12381::PublicKey, bls12381::ProofOfPossession) { + let (sk, pkpop) = bls12381::generate_keys(); + let pop = bls12381::generate_proof_of_possession(&sk); + let unvalidated_pk = bls12381::public_key_with_pop_to_normal(&pkpop); + (sk, unvalidated_pk, pop) + } + + #[test_only] + public fun end_epoch() acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + // Set the number of blocks to 1, to give out rewards to non-failing validators. + set_validator_perf_at_least_one_block(); + timestamp::fast_forward_seconds(EPOCH_DURATION); + on_new_epoch(); + } + + + + + #[test_only] + public fun set_validator_perf_at_least_one_block() acquires ValidatorPerformance { + let validator_perf = borrow_global_mut(@diem_framework); + let len = vector::length(&validator_perf.validators); + let i = 0; + while (i < len) { + let validator = vector::borrow_mut(&mut validator_perf.validators, i); + if (validator.successful_proposals + validator.failed_proposals < 1) { + validator.successful_proposals = 1; + }; + i = i + 1; + }; + } + + #[test_only] + public fun initialize_test_validator( + root: &signer, + public_key: &bls12381::PublicKey, + proof_of_possession: &bls12381::ProofOfPossession, + validator: &signer, + _amount: u64, + should_join_validator_set: bool, + should_end_epoch: bool, + ) acquires AllowedValidators, StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + use ol_framework::ol_account; + system_addresses::assert_ol(root); + let validator_address = signer::address_of(validator); + if (!account::exists_at(signer::address_of(validator))) { + // account::create_account_for_test(validator_address); + ol_account::create_account(root, validator_address); + }; + + let pk_bytes = bls12381::public_key_to_bytes(public_key); + let pop_bytes = bls12381::proof_of_possession_to_bytes(proof_of_possession); + initialize_validator(validator, pk_bytes, pop_bytes, vector::empty(), vector::empty()); + + // if (amount > 0) { + // mint_and_add_stake(validator, amount); + // }; + + if (should_join_validator_set) { + join_validator_set(validator, validator_address); + }; + if (should_end_epoch) { + end_epoch(); + }; + } + + #[test_only] + /// One step setup for tests + public fun quick_init(root: &signer, val_sig: &signer) acquires ValidatorPerformance, ValidatorSet, StakePool, ValidatorConfig, AllowedValidators { + system_addresses::assert_ol(root); + let (_sk, pk, pop) = generate_identity(); + initialize_test_validator(root, &pk, &pop, val_sig, 100, true, true); + } + + #[test_only] + public fun get_reward_event_guid(val: address): u64 acquires StakePool{ + let sp = borrow_global(val); + event::counter(&sp.distribute_rewards_events) + } +} diff --git a/framework/libra-framework/sources/modified_source/stake.move b/framework/libra-framework/sources/modified_source/stake.move index 964ae8d9e..e73105ad8 100644 --- a/framework/libra-framework/sources/modified_source/stake.move +++ b/framework/libra-framework/sources/modified_source/stake.move @@ -1,23 +1,4 @@ -/** - * Validator lifecycle: - * 1. Prepare a validator node set up and call stake::initialize_validator - * 2. Once ready to deposit stake (or have funds assigned by a staking service in exchange for ownership capability), - * call stake::add_stake (or *_with_cap versions if called from the staking service) - * 3. Call stake::join_validator_set (or _with_cap version) to join the active validator set. Changes are effective in - * the next epoch. - * 4. Validate and gain rewards. The stake will automatically be locked up for a fixed duration (set by governance) and - * automatically renewed at expiration. - * 5. At any point, if the validator operator wants to update the consensus key or network/fullnode addresses, they can - * call stake::rotate_consensus_key and stake::update_network_and_fullnode_addresses. Similar to changes to stake, the - * changes to consensus key/network/fullnode addresses are only effective in the next epoch. - * 6. Validator can request to unlock their stake at any time. However, their stake will only become withdrawable when - * their current lockup expires. This can be at most as long as the fixed lockup duration. - * 7. After exiting, the validator can either explicitly leave the validator set by calling stake::leave_validator_set - * or if their stake drops below the min required, they would get removed at the end of the epoch. - * 8. Validator can always rejoin the validator set by going through steps 2-3 again. - * 9. An owner can always switch operators by calling stake::set_operator. - * 10. An owner can always switch designated voter by calling stake::set_designated_voter. -*/ + module diem_framework::stake { use std::error; use std::option::{Self, Option}; @@ -26,18 +7,13 @@ module diem_framework::stake { use diem_std::bls12381; use diem_std::table::Table; use diem_std::comparator; - use diem_framework::gas_coin::LibraCoin as GasCoin; use diem_framework::account; - use diem_framework::coin::{Self, Coin, MintCapability}; + use diem_framework::coin::{Coin}; use diem_framework::event::{Self, EventHandle}; - use diem_framework::timestamp; use diem_framework::system_addresses; - use diem_framework::chain_status; - use ol_framework::slow_wallet; use ol_framework::testnet; - // use ol_framework::ol_account; // use diem_std::debug::print; @@ -52,24 +28,10 @@ module diem_framework::stake { /// Validator Config not published. const EVALIDATOR_CONFIG: u64 = 1; - /// Not enough stake to join validator set. - const ESTAKE_TOO_LOW: u64 = 2; - /// Too much stake to join validator set. - const ESTAKE_TOO_HIGH: u64 = 3; - /// Account is already a validator or pending validator. - const EALREADY_ACTIVE_VALIDATOR: u64 = 4; - /// Account is not a validator. - const ENOT_VALIDATOR: u64 = 5; - /// Can't remove last validator. - const ELAST_VALIDATOR: u64 = 6; - /// Total stake exceeds maximum allowed. - const ESTAKE_EXCEEDS_MAX: u64 = 7; /// Account is already registered as a validator candidate. const EALREADY_REGISTERED: u64 = 8; /// Account does not have the right operator capability. const ENOT_OPERATOR: u64 = 9; - /// Validators cannot join or leave post genesis on this test network. - const ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED: u64 = 10; /// Invalid consensus public key const EINVALID_PUBLIC_KEY: u64 = 11; /// Validator set exceeds the limit @@ -80,19 +42,16 @@ module diem_framework::stake { const ESTAKE_POOL_DOES_NOT_EXIST: u64 = 14; /// Owner capability does not exist at the provided account. const EOWNER_CAP_NOT_FOUND: u64 = 15; - /// An account cannot own more than one owner capability. - const EOWNER_CAP_ALREADY_EXISTS: u64 = 16; + // const EOWNER_CAP_ALREADY_EXISTS: u64 = 16; /// Validator is not defined in the ACL of entities allowed to be validators const EINELIGIBLE_VALIDATOR: u64 = 17; - /// Cannot update stake pool's lockup to earlier than current lockup. - const EINVALID_LOCKUP: u64 = 18; /// Table to store collected transaction fees for each validator already exists. const EFEES_TABLE_ALREADY_EXISTS: u64 = 19; /// Validator status enum. We can switch to proper enum later once Move supports it. - const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1; + // const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1; const VALIDATOR_STATUS_ACTIVE: u64 = 2; - const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3; + // const VALIDATOR_STATUS_PENDING_INACTIVE: u64 = 3; const VALIDATOR_STATUS_INACTIVE: u64 = 4; /// Limit the maximum size to u16::max, it's the current limit of the bitvec @@ -108,10 +67,10 @@ module diem_framework::stake { /// Having this be separate from the signer for the account that the validator resources are hosted at allows /// modules to have control over a validator. struct OwnerCapability has key, store { - pool_address: address, + validator_address: address, } - /// Each validator has a separate StakePool resource and can provide a stake. + /// Each validator has a separate ValidatorState resource and can provide a stake. /// Changes in stake for an active validator: /// 1. If a validator calls add_stake, the newly added stake is moved to pending_active. /// 2. If validator calls unlock, their stake is moved to pending_inactive. @@ -122,38 +81,17 @@ module diem_framework::stake { /// 1. If a validator calls add_stake, the newly added stake is moved directly to active. /// 2. If validator calls unlock, their stake is moved directly to inactive. /// 3. When the next epoch starts, the validator can be activated if their active stake is more than the minimum. - struct StakePool has key { - // active stake - active: Coin, - // inactive stake, can be withdrawn - inactive: Coin, - // pending activation for next epoch - pending_active: Coin, - // pending deactivation for next epoch - pending_inactive: Coin, - locked_until_secs: u64, - // Track the current operator of the validator node. - // This allows the operator to be different from the original account and allow for separation of - // the validator operations and ownership. + struct ValidatorState has key { // Only the account holding OwnerCapability of the staking pool can update this. operator_address: address, - // Track the current vote delegator of the staking pool. - // Only the account holding OwnerCapability of the staking pool can update this. - delegated_voter: address, - - // The events emitted for the entire StakePool's lifecycle. + // The events emitted for the entire ValidatorState's lifecycle. initialize_validator_events: EventHandle, set_operator_events: EventHandle, - add_stake_events: EventHandle, - reactivate_stake_events: EventHandle, rotate_consensus_key_events: EventHandle, update_network_and_fullnode_addresses_events: EventHandle, - increase_lockup_events: EventHandle, join_validator_set_events: EventHandle, distribute_rewards_events: EventHandle, - unlock_stake_events: EventHandle, - withdraw_stake_events: EventHandle, leave_validator_set_events: EventHandle, } @@ -174,9 +112,10 @@ module diem_framework::stake { config: ValidatorConfig, } + // NOTE: 0L things might break with diem-platform if this changes /// Full ValidatorSet, stored in @diem_framework. /// 1. join_validator_set adds to pending_active queue. - /// 2. leave_valdiator_set moves from active to pending_inactive queue. + /// 2. leave_validator_set moves from active to pending_inactive queue. /// 3. on_new_epoch processes two pending queues and refresh ValidatorInfo from the owner's address. struct ValidatorSet has key { consensus_scheme: u8, @@ -192,12 +131,6 @@ module diem_framework::stake { total_joining_power: u128, } - /// GasCoin capabilities, set during genesis and stored in @CoreResource account. - /// This allows the Stake module to mint rewards to stakers. - struct GasCoinCapabilities has key { - mint_cap: MintCapability, - } - struct IndividualValidatorPerformance has store, drop { successful_proposals: u64, failed_proposals: u64, @@ -208,66 +141,41 @@ module diem_framework::stake { } struct RegisterValidatorCandidateEvent has drop, store { - pool_address: address, + validator_address: address, } struct SetOperatorEvent has drop, store { - pool_address: address, + validator_address: address, old_operator: address, new_operator: address, } - struct AddStakeEvent has drop, store { - pool_address: address, - amount_added: u64, - } - - struct ReactivateStakeEvent has drop, store { - pool_address: address, - amount: u64, - } - struct RotateConsensusKeyEvent has drop, store { - pool_address: address, + validator_address: address, old_consensus_pubkey: vector, new_consensus_pubkey: vector, } struct UpdateNetworkAndFullnodeAddressesEvent has drop, store { - pool_address: address, + validator_address: address, old_network_addresses: vector, new_network_addresses: vector, old_fullnode_addresses: vector, new_fullnode_addresses: vector, } - struct IncreaseLockupEvent has drop, store { - pool_address: address, - old_locked_until_secs: u64, - new_locked_until_secs: u64, - } - struct JoinValidatorSetEvent has drop, store { - pool_address: address, + validator_address: address, } struct DistributeRewardsEvent has drop, store { - pool_address: address, + validator_address: address, rewards_amount: u64, } - struct UnlockStakeEvent has drop, store { - pool_address: address, - amount_unlocked: u64, - } - - struct WithdrawStakeEvent has drop, store { - pool_address: address, - amount_withdrawn: u64, - } struct LeaveValidatorSetEvent has drop, store { - pool_address: address, + validator_address: address, } /// Stores transaction fees assigned to validators. All fees are distributed to validators @@ -276,62 +184,6 @@ module diem_framework::stake { fees_table: Table>, } - // /// Initializes the resource storing information about collected transaction fees per validator. - // /// Used by `transaction_fee.move` to initialize fee collection and distribution. - // public(friend) fun initialize_validator_fees(diem_framework: &signer) { - // system_addresses::assert_diem_framework(diem_framework); - // assert!( - // !exists(@diem_framework), - // error::already_exists(EFEES_TABLE_ALREADY_EXISTS) - // ); - // move_to(diem_framework, ValidatorFees { fees_table: table::new() }); - // } - - // /// Stores the transaction fee collected to the specified validator address. - // public(friend) fun add_transaction_fee(validator_addr: address, fee: Coin) acquires ValidatorFees { - // let fees_table = &mut borrow_global_mut(@diem_framework).fees_table; - // if (table::contains(fees_table, validator_addr)) { - // let collected_fee = table::borrow_mut(fees_table, validator_addr); - // coin::merge(collected_fee, fee); - // } else { - // table::add(fees_table, validator_addr, fee); - // } - // } - - // #[view] - // /// Return the lockup expiration of the stake pool at `pool_address`. - // /// This will throw an error if there's no stake pool at `pool_address`. - // public fun get_lockup_secs(pool_address: address): u64 acquires StakePool { - // assert_stake_pool_exists(pool_address); - // borrow_global(pool_address).locked_until_secs - // } - - // #[view] - // /// Return the remaining lockup of the stake pool at `pool_address`. - // /// This will throw an error if there's no stake pool at `pool_address`. - // public fun get_remaining_lockup_secs(pool_address: address): u64 acquires StakePool { - // assert_stake_pool_exists(pool_address); - // let lockup_time = borrow_global(pool_address).locked_until_secs; - // if (lockup_time <= timestamp::now_seconds()) { - // 0 - // } else { - // lockup_time - timestamp::now_seconds() - // } - // } - - // #[view] - // /// Return the different stake amounts for `pool_address` (whether the validator is active or not). - // /// The returned amounts are for (active, inactive, pending_active, pending_inactive) stake respectively. - // public fun get_stake(pool_address: address): (u64, u64, u64, u64) acquires StakePool { - // assert_stake_pool_exists(pool_address); - // let stake_pool = borrow_global(pool_address); - // ( - // coin::value(&stake_pool.active), - // coin::value(&stake_pool.inactive), - // coin::value(&stake_pool.pending_active), - // coin::value(&stake_pool.pending_inactive), - // ) - // } #[view] /// Returns the list of active validators @@ -356,14 +208,10 @@ module diem_framework::stake { #[view] /// Returns the validator's state. - public fun get_validator_state(pool_address: address): u64 acquires ValidatorSet { + public fun get_validator_state(validator_address: address): u64 acquires ValidatorSet { let validator_set = borrow_global(@diem_framework); - if (option::is_some(&find_validator(&validator_set.pending_active, pool_address))) { - VALIDATOR_STATUS_PENDING_ACTIVE - } else if (option::is_some(&find_validator(&validator_set.active_validators, pool_address))) { + if (option::is_some(&find_validator(&validator_set.active_validators, validator_address))) { VALIDATOR_STATUS_ACTIVE - } else if (option::is_some(&find_validator(&validator_set.pending_inactive, pool_address))) { - VALIDATOR_STATUS_PENDING_INACTIVE } else { VALIDATOR_STATUS_INACTIVE } @@ -374,51 +222,28 @@ module diem_framework::stake { // TODO: v7 #[view] /// Returns the validator's state. - public fun is_valid(pool_address: address): bool acquires ValidatorSet { - get_validator_state(pool_address) < VALIDATOR_STATUS_INACTIVE + public fun is_valid(addr: address): bool acquires ValidatorSet{ + get_validator_state(addr) == VALIDATOR_STATUS_ACTIVE } #[view] - /// Return the voting power of the validator in the current epoch. - /// This is the same as the validator's total active and pending_inactive stake. - public fun get_current_epoch_voting_power(pool_address: address): u64 acquires StakePool, ValidatorSet { - assert_stake_pool_exists(pool_address); - let validator_state = get_validator_state(pool_address); - // Both active and pending inactive validators can still vote in the current epoch. - if (validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE) { - let active_stake = coin::value(&borrow_global(pool_address).active); - let pending_inactive_stake = coin::value(&borrow_global(pool_address).pending_inactive); - active_stake + pending_inactive_stake - } else { - 0 - } - } - - // #[view] - // /// Return the delegated voter of the validator at `pool_address`. - // public fun get_delegated_voter(pool_address: address): address acquires StakePool { - // assert_stake_pool_exists(pool_address); - // borrow_global(pool_address).delegated_voter - // } - - #[view] - /// Return the operator of the validator at `pool_address`. - public fun get_operator(pool_address: address): address acquires StakePool { - assert_stake_pool_exists(pool_address); - borrow_global(pool_address).operator_address + /// Return the operator of the validator at `validator_address`. + public fun get_operator(validator_address: address): address acquires ValidatorState { + assert_stake_pool_exists(validator_address); + borrow_global(validator_address).operator_address } /// Return the pool address in `owner_cap`. public fun get_owned_pool_address(owner_cap: &OwnerCapability): address { - owner_cap.pool_address + owner_cap.validator_address } #[view] - /// Return the validator index for `pool_address`. - public fun get_validator_index(pool_address: address): u64 acquires ValidatorConfig { - assert_stake_pool_exists(pool_address); - borrow_global(pool_address).validator_index + /// Return the validator index for `validator_address`. + public fun get_validator_index(validator_address: address): u64 acquires ValidatorConfig { + assert_stake_pool_exists(validator_address); + borrow_global(validator_address).validator_index } #[view] @@ -431,15 +256,15 @@ module diem_framework::stake { #[view] /// Return the validator's config. - public fun get_validator_config(pool_address: address): (vector, vector, vector) acquires ValidatorConfig { - assert_stake_pool_exists(pool_address); - let validator_config = borrow_global(pool_address); + public fun get_validator_config(validator_address: address): (vector, vector, vector) acquires ValidatorConfig { + assert_stake_pool_exists(validator_address); + let validator_config = borrow_global(validator_address); (validator_config.consensus_pubkey, validator_config.network_addresses, validator_config.fullnode_addresses) } #[view] public fun stake_pool_exists(addr: address): bool { - exists(addr) + exists(addr) } /// Initialize validator set to the core resource account. @@ -451,8 +276,8 @@ module diem_framework::stake { active_validators: vector::empty(), pending_active: vector::empty(), pending_inactive: vector::empty(), - total_voting_power: 0, - total_joining_power: 0, + total_voting_power: 1, + total_joining_power: 1, }); move_to(diem_framework, ValidatorPerformance { @@ -460,49 +285,6 @@ module diem_framework::stake { }); } - /// This is only called during Genesis, which is where MintCapability can be created. - /// Beyond genesis, no one can create GasCoin mint/burn capabilities. - - // TODO: v7 - remove this - // public(friend) fun store_diem_coin_mint_cap(diem_framework: &signer, mint_cap: MintCapability) { - // system_addresses::assert_diem_framework(diem_framework); - // move_to(diem_framework, GasCoinCapabilities { mint_cap }) - // } - - /// Allow on chain governance to remove validators from the validator set. - // TODO: v7 - remove this - - public fun remove_validators( - diem_framework: &signer, - validators: &vector
, - ) acquires ValidatorSet { - system_addresses::assert_diem_framework(diem_framework); - - let validator_set = borrow_global_mut(@diem_framework); - let active_validators = &mut validator_set.active_validators; - let pending_inactive = &mut validator_set.pending_inactive; - let len = vector::length(validators); - let i = 0; - // Remove each validator from the validator set. - while ({ - spec { - invariant spec_validators_are_initialized(active_validators); - invariant spec_validator_indices_are_valid(active_validators); - invariant spec_validators_are_initialized(pending_inactive); - invariant spec_validator_indices_are_valid(pending_inactive); - }; - i < len - }) { - let validator = *vector::borrow(validators, i); - let validator_index = find_validator(active_validators, validator); - if (option::is_some(&validator_index)) { - let validator_info = vector::swap_remove(active_validators, *option::borrow(&validator_index)); - vector::push_back(pending_inactive, validator_info); - }; - i = i + 1; - }; - } - /// Initialize the validator account and give ownership to the signing account /// except it leaves the ValidatorConfig to be set by another entity. /// Note: this triggers setting the operator and owner, set it to the account's address @@ -512,7 +294,7 @@ module diem_framework::stake { initial_stake_amount: u64, operator: address, _voter: address, - ) acquires AllowedValidators, OwnerCapability, StakePool { + ) acquires AllowedValidators, OwnerCapability, ValidatorState { initialize_owner(owner); move_to(owner, ValidatorConfig { consensus_pubkey: vector::empty(), @@ -566,53 +348,23 @@ module diem_framework::stake { assert!(is_allowed(owner_address), error::not_found(EINELIGIBLE_VALIDATOR)); assert!(!stake_pool_exists(owner_address), error::already_exists(EALREADY_REGISTERED)); - move_to(owner, StakePool { - active: coin::zero(), - pending_active: coin::zero(), - pending_inactive: coin::zero(), - inactive: coin::zero(), - locked_until_secs: 0, + move_to(owner, ValidatorState { operator_address: owner_address, - delegated_voter: owner_address, // Events. initialize_validator_events: account::new_event_handle(owner), set_operator_events: account::new_event_handle(owner), - add_stake_events: account::new_event_handle(owner), - reactivate_stake_events: account::new_event_handle(owner), + rotate_consensus_key_events: account::new_event_handle(owner), update_network_and_fullnode_addresses_events: account::new_event_handle(owner), - increase_lockup_events: account::new_event_handle(owner), join_validator_set_events: account::new_event_handle(owner), distribute_rewards_events: account::new_event_handle(owner), - unlock_stake_events: account::new_event_handle(owner), - withdraw_stake_events: account::new_event_handle(owner), leave_validator_set_events: account::new_event_handle(owner), }); - // move_to(owner, OwnerCapability { pool_address: owner_address }); } - // /// Extract and return owner capability from the signing account. - // public fun extract_owner_cap(owner: &signer): OwnerCapability acquires OwnerCapability { - // let owner_address = signer::address_of(owner); - // assert_owner_cap_exists(owner_address); - // move_from(owner_address) - // } - - // /// Deposit `owner_cap` into `account`. This requires `account` to not already have owernship of another - // /// staking pool. - // public fun deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) { - // assert!(!exists(signer::address_of(owner)), error::not_found(EOWNER_CAP_ALREADY_EXISTS)); - // move_to(owner, owner_cap); - // } - - // /// Destroy `owner_cap`. - // public fun destroy_owner_cap(owner_cap: OwnerCapability) { - // let OwnerCapability { pool_address: _ } = owner_cap; - // } - /// Allows an owner to change the operator of the stake pool. - public entry fun set_operator(owner: &signer, new_operator: address) acquires OwnerCapability, StakePool { + public entry fun set_operator(owner: &signer, new_operator: address) acquires OwnerCapability, ValidatorState { let owner_address = signer::address_of(owner); assert_owner_cap_exists(owner_address); let ownership_cap = borrow_global(owner_address); @@ -620,142 +372,37 @@ module diem_framework::stake { } /// Allows an account with ownership capability to change the operator of the stake pool. - public fun set_operator_with_cap(owner_cap: &OwnerCapability, new_operator: address) acquires StakePool { - let pool_address = owner_cap.pool_address; - assert_stake_pool_exists(pool_address); - let stake_pool = borrow_global_mut(pool_address); + public fun set_operator_with_cap(owner_cap: &OwnerCapability, new_operator: address) acquires ValidatorState { + let validator_address = owner_cap.validator_address; + assert_stake_pool_exists(validator_address); + let stake_pool = borrow_global_mut(validator_address); let old_operator = stake_pool.operator_address; stake_pool.operator_address = new_operator; event::emit_event( &mut stake_pool.set_operator_events, SetOperatorEvent { - pool_address, + validator_address, old_operator, new_operator, }, ); } - // /// Allows an owner to change the delegated voter of the stake pool. - // public entry fun set_delegated_voter(owner: &signer, new_voter: address) acquires OwnerCapability, StakePool { - // let owner_address = signer::address_of(owner); - // assert_owner_cap_exists(owner_address); - // let ownership_cap = borrow_global(owner_address); - // set_delegated_voter_with_cap(ownership_cap, new_voter); - // } - - // /// Allows an owner to change the delegated voter of the stake pool. - // public fun set_delegated_voter_with_cap(owner_cap: &OwnerCapability, new_voter: address) acquires StakePool { - // let pool_address = owner_cap.pool_address; - // assert_stake_pool_exists(pool_address); - // let stake_pool = borrow_global_mut(pool_address); - // stake_pool.delegated_voter = new_voter; - // } - - /// Add `amount` of coins from the `account` owning the StakePool. - - // TODO: v7 - remove this - // public entry fun add_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool, ValidatorSet { - // let owner_address = signer::address_of(owner); - // assert_owner_cap_exists(owner_address); - // let ownership_cap = borrow_global(owner_address); - // add_stake_with_cap(ownership_cap, coin::withdraw(owner, amount)); - // } - - // TODO: v7 - remove this - - // /// Add `coins` into `pool_address`. this requires the corresponding `owner_cap` to be passed in. - // public fun add_stake_with_cap(owner_cap: &OwnerCapability, coins: Coin) acquires StakePool, ValidatorSet { - // let pool_address = owner_cap.pool_address; - // assert_stake_pool_exists(pool_address); - - // let amount = coin::value(&coins); - // if (amount == 0) { - // coin::destroy_zero(coins); - // return - // }; - - // // Only track and validate voting power increase for active and pending_active validator. - // // Pending_inactive validator will be removed from the validator set in the next epoch. - // // Inactive validator's total stake will be tracked when they join the validator set. - // let validator_set = borrow_global_mut(@diem_framework); - // // Search directly rather using get_validator_state to save on unnecessary loops. - // if (option::is_some(&find_validator(&validator_set.active_validators, pool_address)) || - // option::is_some(&find_validator(&validator_set.pending_active, pool_address))) { - // update_voting_power_increase(amount); - // }; - - // // Add to pending_active if it's a current validator because the stake is not counted until the next epoch. - // // Otherwise, the delegation can be added to active directly as the validator is also activated in the epoch. - // let stake_pool = borrow_global_mut(pool_address); - // if (is_current_epoch_validator(pool_address)) { - // coin::merge(&mut stake_pool.pending_active, coins); - // } else { - // coin::merge(&mut stake_pool.active, coins); - // }; - - // // let (_, maximum_stake) = staking_config::get_required_stake(&staking_config::get()); - // let maximum_stake = 1000; - - // let voting_power = get_next_epoch_voting_power(stake_pool); - - // assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_EXCEEDS_MAX)); - - // event::emit_event( - // &mut stake_pool.add_stake_events, - // AddStakeEvent { - // pool_address, - // amount_added: amount, - // }, - // ); - // } - - // /// Move `amount` of coins from pending_inactive to active. - // public entry fun reactivate_stake(owner: &signer, amount: u64) acquires OwnerCapability, StakePool { - // let owner_address = signer::address_of(owner); - // assert_owner_cap_exists(owner_address); - // let ownership_cap = borrow_global(owner_address); - // reactivate_stake_with_cap(ownership_cap, amount); - // } - - // public fun reactivate_stake_with_cap(owner_cap: &OwnerCapability, amount: u64) acquires StakePool { - // let pool_address = owner_cap.pool_address; - // assert_stake_pool_exists(pool_address); - - // // Cap the amount to reactivate by the amount in pending_inactive. - // let stake_pool = borrow_global_mut(pool_address); - // let total_pending_inactive = coin::value(&stake_pool.pending_inactive); - // amount = min(amount, total_pending_inactive); - - // // Since this does not count as a voting power change (pending inactive still counts as voting power in the - // // current epoch), stake can be immediately moved from pending inactive to active. - // // We also don't need to check voting power increase as there's none. - // let reactivated_coins = coin::extract(&mut stake_pool.pending_inactive, amount); - // coin::merge(&mut stake_pool.active, reactivated_coins); - - // event::emit_event( - // &mut stake_pool.reactivate_stake_events, - // ReactivateStakeEvent { - // pool_address, - // amount, - // }, - // ); - // } /// Rotate the consensus key of the validator, it'll take effect in next epoch. public entry fun rotate_consensus_key( operator: &signer, - pool_address: address, + validator_address: address, new_consensus_pubkey: vector, proof_of_possession: vector, - ) acquires StakePool, ValidatorConfig { - assert_stake_pool_exists(pool_address); - let stake_pool = borrow_global_mut(pool_address); + ) acquires ValidatorState, ValidatorConfig { + assert_stake_pool_exists(validator_address); + let stake_pool = borrow_global_mut(validator_address); assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); - assert!(exists(pool_address), error::not_found(EVALIDATOR_CONFIG)); - let validator_info = borrow_global_mut(pool_address); + assert!(exists(validator_address), error::not_found(EVALIDATOR_CONFIG)); + let validator_info = borrow_global_mut(validator_address); let old_consensus_pubkey = validator_info.consensus_pubkey; // Checks the public key has a valid proof-of-possession to prevent rogue-key attacks. let pubkey_from_pop = &mut bls12381::public_key_from_bytes_with_pop( @@ -768,7 +415,7 @@ module diem_framework::stake { event::emit_event( &mut stake_pool.rotate_consensus_key_events, RotateConsensusKeyEvent { - pool_address, + validator_address, old_consensus_pubkey, new_consensus_pubkey, }, @@ -778,16 +425,16 @@ module diem_framework::stake { /// Update the network and full node addresses of the validator. This only takes effect in the next epoch. public entry fun update_network_and_fullnode_addresses( operator: &signer, - pool_address: address, + validator_address: address, new_network_addresses: vector, new_fullnode_addresses: vector, - ) acquires StakePool, ValidatorConfig { - assert_stake_pool_exists(pool_address); - let stake_pool = borrow_global_mut(pool_address); + ) acquires ValidatorState, ValidatorConfig { + assert_stake_pool_exists(validator_address); + let stake_pool = borrow_global_mut(validator_address); assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); - assert!(exists(pool_address), error::not_found(EVALIDATOR_CONFIG)); - let validator_info = borrow_global_mut(pool_address); + assert!(exists(validator_address), error::not_found(EVALIDATOR_CONFIG)); + let validator_info = borrow_global_mut(validator_address); let old_network_addresses = validator_info.network_addresses; validator_info.network_addresses = new_network_addresses; let old_fullnode_addresses = validator_info.fullnode_addresses; @@ -796,7 +443,7 @@ module diem_framework::stake { event::emit_event( &mut stake_pool.update_network_and_fullnode_addresses_events, UpdateNetworkAndFullnodeAddressesEvent { - pool_address, + validator_address, old_network_addresses, new_network_addresses, old_fullnode_addresses, @@ -805,243 +452,63 @@ module diem_framework::stake { ); } - // /// Similar to increase_lockup_with_cap but will use ownership capability from the signing account. - // public entry fun increase_lockup(owner: &signer) acquires OwnerCapability, { - // let owner_address = signer::address_of(owner); - // assert_owner_cap_exists(owner_address); - // let _ownership_cap = borrow_global(owner_address); - // // increase_lockup_with_cap(ownership_cap); - // } - - /// Unlock from active delegation, it's moved to pending_inactive if locked_until_secs < current_time or - /// directly inactive if it's not from an active validator. - // public fun increase_lockup_with_cap(owner_cap: &OwnerCapability) acquires StakePool { - // let pool_address = owner_cap.pool_address; - // assert_stake_pool_exists(pool_address); - // let config = staking_config::get(); - - // let stake_pool = borrow_global_mut(pool_address); - // let old_locked_until_secs = stake_pool.locked_until_secs; - // let new_locked_until_secs = timestamp::now_seconds() + staking_config::get_recurring_lockup_duration(&config); - // assert!(old_locked_until_secs < new_locked_until_secs, error::invalid_argument(EINVALID_LOCKUP)); - // stake_pool.locked_until_secs = new_locked_until_secs; - - // event::emit_event( - // &mut stake_pool.increase_lockup_events, - // IncreaseLockupEvent { - // pool_address, - // old_locked_until_secs, - // new_locked_until_secs, - // }, - // ); - // } - - /// This can only called by the operator of the validator/staking pool. - public entry fun join_validator_set( - operator: &signer, - pool_address: address - ) acquires StakePool, ValidatorConfig, ValidatorSet { - // assert!( - // staking_config::get_allow_validator_set_change(&staking_config::get()), - // error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED), - // ); - - join_validator_set_internal(operator, pool_address); - } - /// Request to have `pool_address` join the validator set. Can only be called after calling `initialize_validator`. + // #[test_only] + /// Request to have `validator_address` join the validator set. Can only be called after calling `initialize_validator`. /// If the validator has the required stake (more than minimum and less than maximum allowed), they will be /// added to the pending_active queue. All validators in this queue will be added to the active set when the next /// epoch starts (eligibility will be rechecked). /// - /// This internal version can only be called by the Genesis module during Genesis. - public(friend) fun join_validator_set_internal( + /// This internal version can only be called by the Genesis module during + /// Genesis. + public(friend) fun join_validator_set( operator: &signer, - pool_address: address - ) acquires StakePool, ValidatorConfig, ValidatorSet { + validator_address: address + ) acquires ValidatorState, ValidatorConfig, ValidatorSet { - assert_stake_pool_exists(pool_address); - let stake_pool = borrow_global_mut(pool_address); + assert_stake_pool_exists(validator_address); + let stake_pool = borrow_global_mut(validator_address); assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); - assert!( - get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE, - error::invalid_state(EALREADY_ACTIVE_VALIDATOR), - ); + // assert!( + // get_validator_state(validator_address) == VALIDATOR_STATUS_INACTIVE, + // error::invalid_state(EALREADY_ACTIVE_VALIDATOR), + // ); // let config = staking_config::get(); // let (minimum_stake, maximum_stake) = staking_config::get_required_stake(&config); - let minimum_stake = 1; - let maximum_stake = 100; - let voting_power = 1; // voting power is always 1 in Libra - assert!(voting_power >= minimum_stake, error::invalid_argument(ESTAKE_TOO_LOW)); - assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_TOO_HIGH)); + // let minimum_stake = 1; + // let maximum_stake = 100; + // let voting_power = 1; // voting power is always 1 in Libra + // assert!(voting_power >= minimum_stake, error::invalid_argument(ESTAKE_TOO_LOW)); + // assert!(voting_power <= maximum_stake, error::invalid_argument(ESTAKE_TOO_HIGH)); // Track and validate voting power increase. - update_voting_power_increase(voting_power); + // update_voting_power_increase(voting_power); // Add validator to pending_active, to be activated in the next epoch. - let validator_config = borrow_global_mut(pool_address); + let validator_config = borrow_global_mut(validator_address); assert!(!vector::is_empty(&validator_config.consensus_pubkey), error::invalid_argument(EINVALID_PUBLIC_KEY)); // Validate the current validator set size has not exceeded the limit. let validator_set = borrow_global_mut(@diem_framework); - vector::push_back(&mut validator_set.pending_active, generate_validator_info(pool_address, stake_pool, *validator_config)); + vector::push_back(&mut validator_set.active_validators, generate_validator_info(validator_address, *validator_config)); - let validator_set_size = vector::length(&validator_set.active_validators) + vector::length(&validator_set.pending_active); + let validator_set_size = vector::length(&validator_set.active_validators); assert!(validator_set_size <= MAX_VALIDATOR_SET_SIZE, error::invalid_argument(EVALIDATOR_SET_TOO_LARGE)); event::emit_event( &mut stake_pool.join_validator_set_events, - JoinValidatorSetEvent { pool_address }, + JoinValidatorSetEvent { validator_address }, ); } - /// Similar to unlock_with_cap but will use ownership capability from the signing account. - // public entry fun unlock(owner: &signer, amount: u64) acquires OwnerCapability, StakePool { - // let owner_address = signer::address_of(owner); - // assert_owner_cap_exists(owner_address); - // let ownership_cap = borrow_global(owner_address); - // unlock_with_cap(amount, ownership_cap); - // } - - /// Unlock `amount` from the active stake. Only possible if the lockup has expired. - // public fun unlock_with_cap(amount: u64, owner_cap: &OwnerCapability) acquires StakePool { - // // Short-circuit if amount to unlock is 0 so we don't emit events. - // if (amount == 0) { - // return - // }; - - // // Unlocked coins are moved to pending_inactive. When the current lockup cycle expires, they will be moved into - // // inactive in the earliest possible epoch transition. - // let pool_address = owner_cap.pool_address; - // assert_stake_pool_exists(pool_address); - // let stake_pool = borrow_global_mut(pool_address); - // // Cap amount to unlock by maximum active stake. - // let amount = min(amount, coin::value(&stake_pool.active)); - // let unlocked_stake = coin::extract(&mut stake_pool.active, amount); - // coin::merge(&mut stake_pool.pending_inactive, unlocked_stake); - - // event::emit_event( - // &mut stake_pool.unlock_stake_events, - // UnlockStakeEvent { - // pool_address, - // amount_unlocked: amount, - // }, - // ); - // } - - // /// Withdraw from `account`'s inactive stake. - // public entry fun withdraw( - // owner: &signer, - // withdraw_amount: u64 - // ) acquires OwnerCapability, StakePool, ValidatorSet { - // let owner_address = signer::address_of(owner); - // assert_owner_cap_exists(owner_address); - // let ownership_cap = borrow_global(owner_address); - // let coins = withdraw_with_cap(ownership_cap, withdraw_amount); - // coin::deposit(owner_address, coins); - // } - - // /// Withdraw from `pool_address`'s inactive stake with the corresponding `owner_cap`. - // public fun withdraw_with_cap( - // owner_cap: &OwnerCapability, - // withdraw_amount: u64 - // ): Coin acquires StakePool, ValidatorSet { - // let pool_address = owner_cap.pool_address; - // assert_stake_pool_exists(pool_address); - // let stake_pool = borrow_global_mut(pool_address); - // // There's an edge case where a validator unlocks their stake and leaves the validator set before - // // the stake is fully unlocked (the current lockup cycle has not expired yet). - // // This can leave their stake stuck in pending_inactive even after the current lockup cycle expires. - // if (get_validator_state(pool_address) == VALIDATOR_STATUS_INACTIVE && - // timestamp::now_seconds() >= stake_pool.locked_until_secs) { - // let pending_inactive_stake = coin::extract_all(&mut stake_pool.pending_inactive); - // coin::merge(&mut stake_pool.inactive, pending_inactive_stake); - // }; - - // // Cap withdraw amount by total ianctive coins. - // withdraw_amount = min(withdraw_amount, coin::value(&stake_pool.inactive)); - // if (withdraw_amount == 0) return coin::zero(); - - // event::emit_event( - // &mut stake_pool.withdraw_stake_events, - // WithdrawStakeEvent { - // pool_address, - // amount_withdrawn: withdraw_amount, - // }, - // ); - - // coin::extract(&mut stake_pool.inactive, withdraw_amount) - // } - - /// Request to have `pool_address` leave the validator set. The validator is only actually removed from the set when - /// the next epoch starts. - /// The last validator in the set cannot leave. This is an edge case that should never happen as long as the network - /// is still operational. - /// - /// Can only be called by the operator of the validator/staking pool. - - // TODO: V7 change this - public entry fun leave_validator_set( - operator: &signer, - pool_address: address - ) acquires StakePool, ValidatorSet { - // let config = staking_config::get(); - // assert!( - // staking_config::get_allow_validator_set_change(&config), - // error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED), - // ); - - assert_stake_pool_exists(pool_address); - let stake_pool = borrow_global_mut(pool_address); - // Account has to be the operator. - assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); - - let validator_set = borrow_global_mut(@diem_framework); - // If the validator is still pending_active, directly kick the validator out. - let maybe_pending_active_index = find_validator(&validator_set.pending_active, pool_address); - if (option::is_some(&maybe_pending_active_index)) { - vector::swap_remove( - &mut validator_set.pending_active, option::extract(&mut maybe_pending_active_index)); - - // Decrease the voting power increase as the pending validator's voting power was added when they requested - // to join. Now that they changed their mind, their voting power should not affect the joining limit of this - // epoch. - let validator_stake = 1; // NOTE: voting power is always 1 in Libra - // total_joining_power should be larger than validator_stake but just in case there has been a small - // rounding error somewhere that can lead to an underflow, we still want to allow this transaction to - // succeed. - if (validator_set.total_joining_power > validator_stake) { - validator_set.total_joining_power = validator_set.total_joining_power - validator_stake; - } else { - validator_set.total_joining_power = 0; - }; - } else { - // Validate that the validator is already part of the validator set. - let maybe_active_index = find_validator(&validator_set.active_validators, pool_address); - assert!(option::is_some(&maybe_active_index), error::invalid_state(ENOT_VALIDATOR)); - let validator_info = vector::swap_remove( - &mut validator_set.active_validators, option::extract(&mut maybe_active_index)); - assert!(vector::length(&validator_set.active_validators) > 0, error::invalid_state(ELAST_VALIDATOR)); - vector::push_back(&mut validator_set.pending_inactive, validator_info); - - event::emit_event( - &mut stake_pool.leave_validator_set_events, - LeaveValidatorSetEvent { - pool_address, - }, - ); - }; - } /// Returns true if the current validator can still vote in the current epoch. /// This includes validators that requested to leave but are still in the pending_inactive queue and will be removed /// when the epoch starts. - public fun is_current_epoch_validator(pool_address: address): bool acquires ValidatorSet { - assert_stake_pool_exists(pool_address); - let validator_state = get_validator_state(pool_address); - validator_state == VALIDATOR_STATUS_ACTIVE || validator_state == VALIDATOR_STATUS_PENDING_INACTIVE + public fun is_current_epoch_validator(addr: address): bool acquires ValidatorSet{ + get_validator_state(addr) == VALIDATOR_STATUS_ACTIVE } /// Update the validator performance (proposal statistics). This is only called by block::prologue(). @@ -1095,26 +562,12 @@ module diem_framework::stake { /// 3. Add pending active validators to the active set if they satisfy requirements so they can vote and remove /// pending inactive validators so they no longer can vote. /// 4. The validator's voting power in the validator set is updated to be the corresponding staking pool's voting - /// power. - public(friend) fun on_new_epoch() acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + // / power. + public(friend) fun on_new_epoch() acquires ValidatorConfig, ValidatorPerformance, ValidatorSet { let validator_set = borrow_global_mut(@diem_framework); // let config = staking_config::get(); let validator_perf = borrow_global_mut(@diem_framework); - // Process pending stake and distribute transaction fees and rewards for each currently active validator. - // let i = 0; - // let len = vector::length(&validator_set.active_validators); - // while (i < len) { - // let validator = vector::borrow(&validator_set.active_validators, i); - // update_stake_pool(validator_perf, validator.addr); - // i = i + 1; - // }; - - // Activate currently pending_active validators. - append(&mut validator_set.active_validators, &mut validator_set.pending_active); - - // Officially deactivate all pending_inactive validators. They will now no longer receive rewards. - validator_set.pending_inactive = vector::empty(); // Update active validator set so that network address/public key change takes effect. // Moreover, recalculate the total voting power, and deactivate the validator whose @@ -1134,10 +587,10 @@ module diem_framework::stake { i < vlen }) { let old_validator_info = vector::borrow_mut(&mut validator_set.active_validators, i); - let pool_address = old_validator_info.addr; - let validator_config = borrow_global_mut(pool_address); - let stake_pool = borrow_global_mut(pool_address); - let new_validator_info = generate_validator_info(pool_address, stake_pool, *validator_config); + let validator_address = old_validator_info.addr; + let validator_config = borrow_global_mut(validator_address); + + let new_validator_info = generate_validator_info(validator_address, *validator_config); // A validator needs at least the min stake required to join the validator set. if (new_validator_info.voting_power >= minimum_stake) { @@ -1155,7 +608,7 @@ module diem_framework::stake { validator_set.total_voting_power = total_voting_power; - validator_set.total_joining_power = 0; + // validator_set.total_joining_power = 0; // Update validator indices, reset performance scores, and renew lockups. validator_perf.validators = vector::empty(); @@ -1168,8 +621,8 @@ module diem_framework::stake { while ({ spec { invariant spec_validators_are_initialized(validator_set.active_validators); - invariant len(validator_set.pending_active) == 0; - invariant len(validator_set.pending_inactive) == 0; + // invariant len(validator_set.pending_active) == 0; + // invariant len(validator_set.pending_inactive) == 0; invariant 0 <= validator_index && validator_index <= vlen; invariant vlen == len(validator_set.active_validators); invariant forall i in 0..validator_index: @@ -1191,31 +644,19 @@ module diem_framework::stake { failed_proposals: 0, }); - // // Automatically renew a validator's lockup for validators that will still be in the validator set in the - // // next epoch. - // let stake_pool = borrow_global_mut(validator_info.addr); - // if (stake_pool.locked_until_secs <= timestamp::now_seconds()) { - // spec { - // assume timestamp::spec_now_seconds() + recurring_lockup_duration_secs <= MAX_U64; - // }; - // stake_pool.locked_until_secs = - // timestamp::now_seconds() + recurring_lockup_duration_secs; - // }; validator_index = validator_index + 1; }; - // if (features::periodical_reward_rate_decrease_enabled()) { - // // Update rewards rate after reward distribution. - // staking_config::calculate_and_save_latest_epoch_rewards_rate(); - // }; } /// DANGER: belt and suspenders critical mutations /// Called on epoch boundary to reconfigure /// No change may happen due to failover rules. /// Returns instrumentation for audits: if what the validator set was, which validators qualified after failover rules, a list of validators which had missing configs and were excluded, if the new list sucessfully matches the actual validators after reconfiguration(actual_validator_set, qualified_on_failover, missing_configs, success) - public(friend) fun maybe_reconfigure(root: &signer, proposed_validators: vector
): (vector
, vector
, bool) acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + public(friend) fun maybe_reconfigure(root: &signer, proposed_validators: + vector
): (vector
, vector
, bool) acquires + ValidatorConfig, ValidatorPerformance, ValidatorSet { // NOTE: ol does not use the pending, and pending inactive lists. @@ -1248,8 +689,8 @@ module diem_framework::stake { while ({ spec { invariant spec_validators_are_initialized(validator_set.active_validators); - invariant len(validator_set.pending_active) == 0; - invariant len(validator_set.pending_inactive) == 0; + // invariant len(validator_set.pending_active) == 0; + // invariant len(validator_set.pending_inactive) == 0; invariant 0 <= validator_index && validator_index <= vlen; invariant vlen == len(validator_set.active_validators); invariant forall i in 0..validator_index: @@ -1288,13 +729,13 @@ module diem_framework::stake { } #[test_only] - public fun test_make_val_cfg(list: &vector
): (vector, u128, vector
) acquires StakePool, ValidatorConfig { + public fun test_make_val_cfg(list: &vector
): (vector, u128, vector
) acquires ValidatorConfig { make_validator_set_config(list) } - /// Make the active valiators list + /// Make the active validators list /// returns: the list of validators, and the total voting power (1 per validator), and also the list of validators that did not have valid configs - fun make_validator_set_config(list: &vector
): (vector, u128, vector
) acquires StakePool, ValidatorConfig { + fun make_validator_set_config(list: &vector
): (vector, u128, vector
) acquires ValidatorConfig { let next_epoch_validators = vector::empty(); let vlen = vector::length(list); @@ -1311,26 +752,25 @@ module diem_framework::stake { }) { // let old_validator_info = vector::borrow_mut(&mut validator_set.active_validators, i); - let pool_address = *vector::borrow(list, i); + let validator_address = *vector::borrow(list, i); - if (!exists(pool_address)) { - vector::push_back(&mut missing_configs, pool_address); + if (!exists(validator_address)) { + vector::push_back(&mut missing_configs, validator_address); i = i + 1; continue }; // belt and suspenders - let validator_config = borrow_global_mut(pool_address); + let validator_config = borrow_global_mut(validator_address); - if (!exists(pool_address)) { - vector::push_back(&mut missing_configs, pool_address); + if (!exists(validator_address)) { + vector::push_back(&mut missing_configs, validator_address); i = i + 1; continue }; // belt and suspenders - let stake_pool = borrow_global_mut(pool_address); - let new_validator_info = generate_validator_info(pool_address, stake_pool, *validator_config); + let new_validator_info = generate_validator_info(validator_address, *validator_config); // all validators have same weight total_voting_power = total_voting_power + 1; @@ -1338,6 +778,7 @@ module diem_framework::stake { vector::push_back(&mut next_epoch_validators, new_validator_info); // A validator needs at least the min stake required to join the validator set. + // TODO: check this // spec { // assume total_voting_power + 1<= MAX_U128; // }; @@ -1486,129 +927,20 @@ module diem_framework::stake { } - // TODO: this is deprecated - - /// Update individual validator's stake pool - /// 1. distribute transaction fees to active/pending_inactive delegations - /// 2. distribute rewards to active/pending_inactive delegations - /// 3. process pending_active, pending_inactive correspondingly - /// This function shouldn't abort. - fun update_stake_pool( - validator_perf: &ValidatorPerformance, - pool_address: address, - // _staking_config: &StakingConfig, - ) acquires StakePool, ValidatorConfig { - let stake_pool = borrow_global_mut(pool_address); - let validator_config = borrow_global(pool_address); - let cur_validator_perf = vector::borrow(&validator_perf.validators, validator_config.validator_index); - let _num_successful_proposals = cur_validator_perf.successful_proposals; - spec { - // The following addition should not overflow because `num_total_proposals` cannot be larger than 86400, - // the maximum number of proposals in a day (1 proposal per second). - assume cur_validator_perf.successful_proposals + cur_validator_perf.failed_proposals <= MAX_U64; - }; - let _num_total_proposals = cur_validator_perf.successful_proposals + cur_validator_perf.failed_proposals; - - // let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(staking_config); - - - let _rewards_rate = 1; - let _rewards_rate_denominator = 1; - - // let rewards_amount = distribute_rewards( - // &mut stake_pool.active, - // num_successful_proposals, - // num_total_proposals, - // rewards_rate, - // rewards_rate_denominator - // ); - - // // Pending active stake can now be active. - // coin::merge(&mut stake_pool.active, coin::extract_all(&mut stake_pool.pending_active)); - - // // Additionally, distribute transaction fees. - // if (features::collect_and_distribute_gas_fees()) { - // let fees_table = &mut borrow_global_mut(@diem_framework).fees_table; - // if (table::contains(fees_table, pool_address)) { - // let coin = table::remove(fees_table, pool_address); - // coin::merge(&mut stake_pool.active, coin); - // }; - // }; - - // // Pending inactive stake is only fully unlocked and moved into inactive if the current lockup cycle has expired - // let current_lockup_expiration = stake_pool.locked_until_secs; - // if (timestamp::now_seconds() >= current_lockup_expiration) { - // coin::merge( - // &mut stake_pool.inactive, - // coin::extract_all(&mut stake_pool.pending_inactive), - // ); - // }; - - event::emit_event( - &mut stake_pool.distribute_rewards_events, - DistributeRewardsEvent { - pool_address, - rewards_amount: 0, - }, - ); - } - //////// 0L //////// // since rewards are handled externally to stake.move we need an api to emit the event - public(friend) fun emit_distribute_reward(root: &signer, pool_address: address, rewards_amount: u64) acquires StakePool { + public(friend) fun emit_distribute_reward(root: &signer, validator_address: address, rewards_amount: u64) acquires ValidatorState { system_addresses::assert_ol(root); - let stake_pool = borrow_global_mut(pool_address); + let stake_pool = borrow_global_mut(validator_address); event::emit_event( &mut stake_pool.distribute_rewards_events, DistributeRewardsEvent { - pool_address, + validator_address, rewards_amount, }, ); } - // /// Calculate the rewards amount. - // // TODO: v7 - remove this - // fun calculate_rewards_amount( - // stake_amount: u64, - // num_successful_proposals: u64, - // num_total_proposals: u64, - // rewards_rate: u64, - // rewards_rate_denominator: u64, - // ): u64 { - - // 0 - // } - - - - - /// Mint rewards corresponding to current epoch's `stake` and `num_successful_votes`. - - // // TODO: v7 - change this - // fun distribute_rewards( - // stake: &mut Coin, - // _num_successful_proposals: u64, - // _num_total_proposals: u64, - // _rewards_rate: u64, - // _rewards_rate_denominator: u64, - // ): u64 acquires GasCoinCapabilities { - // // let _stake_amount = coin::value(stake); - // let rewards_amount = 0; - // if (rewards_amount > 0) { - // let mint_cap = &borrow_global(@diem_framework).mint_cap; - // let rewards = coin::mint(rewards_amount, mint_cap); - // ol_account::merge_coins(stake, rewards); - // }; - // rewards_amount - // } - - fun append(v1: &mut vector, v2: &mut vector) { - while (!vector::is_empty(v2)) { - vector::push_back(v1, vector::pop_back(v2)); - } - } - fun find_validator(v: &vector, addr: address): Option { let i = 0; let len = vector::length(v); @@ -1626,48 +958,17 @@ module diem_framework::stake { option::none() } - fun generate_validator_info(addr: address, stake_pool: &StakePool, config: ValidatorConfig): ValidatorInfo { - let voting_power = get_next_epoch_voting_power(stake_pool); + fun generate_validator_info(addr: address, config: ValidatorConfig): ValidatorInfo { ValidatorInfo { addr, - voting_power, + voting_power: 1, config, } } - /// Returns validator's next epoch voting power, including pending_active, active, and pending_inactive stake. - - // TODO: v7 - remove this - fun get_next_epoch_voting_power(_stake_pool: &StakePool): u64 { - // let value_pending_active = coin::value(&stake_pool.pending_active); - // let value_active = coin::value(&stake_pool.active); - // let value_pending_inactive = coin::value(&stake_pool.pending_inactive); - // spec { - // assume value_pending_active + value_active + value_pending_inactive <= MAX_U64; - // }; - // // value_pending_active + value_active + value_pending_inactive; - 1 - } - - fun update_voting_power_increase(increase_amount: u64) acquires ValidatorSet { - let validator_set = borrow_global_mut(@diem_framework); - // let voting_power_increase_limit = - // (staking_config::get_voting_power_increase_limit(&staking_config::get()) as u128); - let voting_power_increase_limit = 1000; - validator_set.total_joining_power = validator_set.total_joining_power + (increase_amount as u128); - - // Only validator voting power increase if the current validator set's voting power > 0. - if (validator_set.total_voting_power > 0) { - assert!( - validator_set.total_joining_power <= validator_set.total_voting_power * voting_power_increase_limit / 100, - error::invalid_argument(EVOTING_POWER_INCREASE_EXCEEDS_LIMIT), - ); - } - } - - fun assert_stake_pool_exists(pool_address: address) { - assert!(stake_pool_exists(pool_address), error::invalid_argument(ESTAKE_POOL_DOES_NOT_EXIST)); + fun assert_stake_pool_exists(validator_address: address) { + assert!(stake_pool_exists(validator_address), error::invalid_argument(ESTAKE_POOL_DOES_NOT_EXIST)); } //////////// TESTNET //////////// @@ -1730,7 +1031,7 @@ module diem_framework::stake { } #[test_only] - public fun end_epoch() acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + public fun end_epoch() acquires ValidatorPerformance, ValidatorConfig, ValidatorSet { // Set the number of blocks to 1, to give out rewards to non-failing validators. set_validator_perf_at_least_one_block(); timestamp::fast_forward_seconds(EPOCH_DURATION); @@ -1739,6 +1040,11 @@ module diem_framework::stake { + #[test_only] + public fun test_reconfigure(root: &signer, vals: vector
) acquires + ValidatorConfig, ValidatorPerformance, ValidatorSet { + maybe_reconfigure(root, vals); + } #[test_only] public fun set_validator_perf_at_least_one_block() acquires ValidatorPerformance { @@ -1763,7 +1069,8 @@ module diem_framework::stake { _amount: u64, should_join_validator_set: bool, should_end_epoch: bool, - ) acquires AllowedValidators, StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet { + ) acquires AllowedValidators, ValidatorState, ValidatorConfig, ValidatorSet, + ValidatorPerformance { use ol_framework::ol_account; system_addresses::assert_ol(root); let validator_address = signer::address_of(validator); @@ -1790,15 +1097,17 @@ module diem_framework::stake { #[test_only] /// One step setup for tests - public fun quick_init(root: &signer, val_sig: &signer) acquires ValidatorPerformance, ValidatorSet, StakePool, ValidatorConfig, AllowedValidators { + public fun quick_init(root: &signer, val_sig: &signer) acquires + ValidatorSet, ValidatorState, ValidatorConfig, AllowedValidators, + ValidatorPerformance { system_addresses::assert_ol(root); let (_sk, pk, pop) = generate_identity(); initialize_test_validator(root, &pk, &pop, val_sig, 100, true, true); } #[test_only] - public fun get_reward_event_guid(val: address): u64 acquires StakePool{ - let sp = borrow_global(val); + public fun get_reward_event_guid(val: address): u64 acquires ValidatorState{ + let sp = borrow_global(val); event::counter(&sp.distribute_rewards_events) } } diff --git a/framework/libra-framework/sources/modified_source/stake.spec.depr b/framework/libra-framework/sources/modified_source/stake.spec.depr new file mode 100644 index 000000000..3fe8ea3a8 --- /dev/null +++ b/framework/libra-framework/sources/modified_source/stake.spec.depr @@ -0,0 +1,355 @@ +spec diem_framework::stake_old { + // ----------------- + // Global invariants + // ----------------- + + spec module { + // The validator set should satisfy its desired invariant. + invariant [suspendable] exists(@diem_framework) ==> validator_set_is_valid(); + // After genesis, `DiemCoinCapabilities`, `ValidatorPerformance` and `ValidatorSet` exist. + // invariant [suspendable] chain_status::is_operating() ==> exists(@diem_framework); + invariant [suspendable] chain_status::is_operating() ==> exists(@diem_framework); + invariant [suspendable] chain_status::is_operating() ==> exists(@diem_framework); + } + + // A desired invariant for the validator set. + spec fun validator_set_is_valid(): bool { + let validator_set = global(@diem_framework); + spec_validators_are_initialized(validator_set.active_validators) && + spec_validators_are_initialized(validator_set.pending_inactive) && + spec_validators_are_initialized(validator_set.pending_active) && + spec_validator_indices_are_valid(validator_set.active_validators) && + spec_validator_indices_are_valid(validator_set.pending_inactive) + } + + + // ----------------------- + // Function specifications + // ----------------------- + + // `Validator` is initialized once. + spec initialize(diem_framework: &signer) { + let diem_addr = signer::address_of(diem_framework); + aborts_if !system_addresses::is_diem_framework_address(diem_addr); + aborts_if exists(diem_addr); + aborts_if exists(diem_addr); + } + + // spec extract_owner_cap(owner: &signer): OwnerCapability { + // let owner_address = signer::address_of(owner); + // aborts_if !exists(owner_address); + // } + + // spec deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) { + // let owner_address = signer::address_of(owner); + // aborts_if exists(owner_address); + // } + + // spec unlock_with_cap(amount: u64, owner_cap: &OwnerCapability) { + // let pool_address = owner_cap.pool_address; + // let pre_stake_pool = global(pool_address); + // let post stake_pool = global(pool_address); + // modifies global(pool_address); + // let min_amount = diem_std::math64::min(amount,pre_stake_pool.active.value); + + // ensures stake_pool.pending_inactive.value == pre_stake_pool.pending_inactive.value + min_amount; + // } + + // Only active validator can update locked_until_secs. + // spec increase_lockup_with_cap(owner_cap: &OwnerCapability) { + // let config = global(@diem_framework); + // let pool_address = owner_cap.pool_address; + // let pre_stake_pool = global(pool_address); + // let post stake_pool = global(pool_address); + // let now_seconds = timestamp::spec_now_seconds(); + // let lockup = config.recurring_lockup_duration_secs; + // modifies global(pool_address); + + // aborts_if !exists(pool_address); + // aborts_if pre_stake_pool.locked_until_secs >= lockup + now_seconds; + // aborts_if lockup + now_seconds > MAX_U64; + // aborts_if !exists(@diem_framework); + // aborts_if !exists(@diem_framework); + + // ensures stake_pool.locked_until_secs == lockup + now_seconds; + // } + + spec update_network_and_fullnode_addresses( + operator: &signer, + pool_address: address, + new_network_addresses: vector, + new_fullnode_addresses: vector, + ) { + let pre_stake_pool = global(pool_address); + let post validator_info = global(pool_address); + modifies global(pool_address); + + // Only the true operator address can update the network and full node addresses of the validator. + aborts_if !exists(pool_address); + aborts_if !exists(pool_address); + aborts_if signer::address_of(operator) != pre_stake_pool.operator_address; + + ensures validator_info.network_addresses == new_network_addresses; + ensures validator_info.fullnode_addresses == new_fullnode_addresses; + } + + spec set_operator_with_cap(owner_cap: &OwnerCapability, new_operator: address) { + let pool_address = owner_cap.pool_address; + let post stake_pool = global(pool_address); + modifies global(pool_address); + ensures stake_pool.operator_address == new_operator; + } + + // spec reactivate_stake_with_cap(owner_cap: &OwnerCapability, amount: u64) { + // let pool_address = owner_cap.pool_address; + // aborts_if !stake_pool_exists(pool_address); + + // let pre_stake_pool = global(pool_address); + // let post stake_pool = global(pool_address); + // modifies global(pool_address); + // let min_amount = diem_std::math64::min(amount,pre_stake_pool.pending_inactive.value); + + // ensures stake_pool.active.value == pre_stake_pool.active.value + min_amount; + // } + + spec rotate_consensus_key( + operator: &signer, + pool_address: address, + new_consensus_pubkey: vector, + proof_of_possession: vector, + ) { + let pre_stake_pool = global(pool_address); + let post validator_info = global(pool_address); + modifies global(pool_address); + + ensures validator_info.consensus_pubkey == new_consensus_pubkey; + } + + // spec set_delegated_voter_with_cap(owner_cap: &OwnerCapability, new_voter: address) { + // let pool_address = owner_cap.pool_address; + // let post stake_pool = global(pool_address); + // modifies global(pool_address); + // ensures stake_pool.delegated_voter == new_voter; + // } + + spec on_new_epoch { + pragma disable_invariants_in_body; + // The following resource requirement cannot be discharged by the global + // invariants because this function is called during genesis. + include ResourceRequirement; + // include staking_config::StakingRewardsConfigRequirement; + // This function should never abort. + aborts_if false; + } + + spec update_performance_statistics { + // This function is expected to be used after genesis. + requires chain_status::is_operating(); + // This function should never abort. + aborts_if false; + } + + spec update_stake_pool { + include ResourceRequirement; + // include staking_config::StakingRewardsConfigRequirement; + aborts_if !exists(pool_address); + aborts_if !exists(pool_address); + aborts_if global(pool_address).validator_index >= len(validator_perf.validators); + } + + // spec distribute_rewards { + // include ResourceRequirement; + // requires rewards_rate <= MAX_REWARDS_RATE; + // requires rewards_rate_denominator > 0; + // requires rewards_rate <= rewards_rate_denominator; + // requires num_successful_proposals <= num_total_proposals; + // aborts_if false; + // ensures old(stake.value) > 0 ==> + // result == spec_rewards_amount( + // old(stake.value), + // num_successful_proposals, + // num_total_proposals, + // rewards_rate, + // rewards_rate_denominator); + // ensures old(stake.value) > 0 ==> + // stake.value == old(stake.value) + spec_rewards_amount( + // old(stake.value), + // num_successful_proposals, + // num_total_proposals, + // rewards_rate, + // rewards_rate_denominator); + // ensures old(stake.value) == 0 ==> result == 0; + // ensures old(stake.value) == 0 ==> stake.value == old(stake.value); + // } + + // spec calculate_rewards_amount { + // pragma opaque; + // requires rewards_rate <= MAX_REWARDS_RATE; + // requires rewards_rate_denominator > 0; + // requires rewards_rate <= rewards_rate_denominator; + // requires num_successful_proposals <= num_total_proposals; + // ensures [concrete] (rewards_rate_denominator * num_total_proposals == 0) ==> result == 0; + // ensures [concrete] (rewards_rate_denominator * num_total_proposals > 0) ==> { + // let amount = ((stake_amount * rewards_rate * num_successful_proposals) / + // (rewards_rate_denominator * num_total_proposals)); + // result == amount + // }; + // aborts_if false; + + // // Used an uninterpreted spec function to avoid dealing with the arithmetic overflow and non-linear arithmetic. + // ensures [abstract] result == spec_rewards_amount( + // stake_amount, + // num_successful_proposals, + // num_total_proposals, + // rewards_rate, + // rewards_rate_denominator); + // } + + spec find_validator { + pragma opaque; + aborts_if false; + ensures option::is_none(result) ==> (forall i in 0..len(v): v[i].addr != addr); + ensures option::is_some(result) ==> v[option::borrow(result)].addr == addr; + // Additional postcondition to help the quantifier instantiation. + ensures option::is_some(result) ==> spec_contains(v, addr); + } + + spec append { + pragma opaque, verify = false; + aborts_if false; + ensures len(v1) == old(len(v1) + len(v2)); + ensures len(v2) == 0; + // The prefix of the new `v1` is the same as the old `v1`. + ensures (forall i in 0..old(len(v1)): v1[i] == old(v1[i])); + // The suffix of the new `v1` is the same as the reverse of the old `v2`. + ensures (forall i in old(len(v1))..len(v1): v1[i] == old(v2[len(v2) - (i - len(v1)) - 1])); + } + + spec remove_validators { + requires chain_status::is_operating(); + } + + spec is_current_epoch_validator { + include ResourceRequirement; + aborts_if !spec_has_stake_pool(pool_address); + ensures result == spec_is_current_epoch_validator(pool_address); + } + + spec get_validator_state { + let validator_set = global(@diem_framework); + ensures result == VALIDATOR_STATUS_PENDING_ACTIVE ==> spec_contains(validator_set.pending_active, pool_address); + ensures result == VALIDATOR_STATUS_ACTIVE ==> spec_contains(validator_set.active_validators, pool_address); + ensures result == VALIDATOR_STATUS_PENDING_INACTIVE ==> spec_contains(validator_set.pending_inactive, pool_address); + ensures result == VALIDATOR_STATUS_INACTIVE ==> ( + !spec_contains(validator_set.pending_active, pool_address) + && !spec_contains(validator_set.active_validators, pool_address) + && !spec_contains(validator_set.pending_inactive, pool_address) + ); + } + + // spec add_stake_with_cap { + // include ResourceRequirement; + // } + + // spec add_stake { + // include ResourceRequirement; + // } + + spec initialize_stake_owner { + include ResourceRequirement; + } + + // spec add_transaction_fee(validator_addr: address, fee: Coin) { + // aborts_if !exists(@diem_framework); + // } + + spec update_voting_power_increase(increase_amount: u64) { + let diem = @diem_framework; + let pre_validator_set = global(diem); + let post validator_set = global(diem); + // let staking_config = global(diem); + // let voting_power_increase_limit = staking_config.voting_power_increase_limit; + + // Correctly modified total_joining_power and the value of total_voting_power is legal. + // ensures validator_set.total_voting_power > 0 ==> validator_set.total_joining_power <= validator_set.total_voting_power * voting_power_increase_limit / 100; + ensures validator_set.total_joining_power == pre_validator_set.total_joining_power + increase_amount; + } + + spec assert_stake_pool_exists(pool_address: address) { + aborts_if !stake_pool_exists(pool_address); + } + + spec configure_allowed_validators(diem_framework: &signer, accounts: vector
) { + let diem_framework_address = signer::address_of(diem_framework); + aborts_if !system_addresses::is_diem_framework_address(diem_framework_address); + let post allowed = global(diem_framework_address); + // Make sure that the accounts of AllowedValidators are always the passed parameter. + ensures allowed.accounts == accounts; + } + + spec assert_owner_cap_exists(owner: address) { + aborts_if !exists(owner); + } + + // --------------------------------- + // Spec helper functions and schemas + // --------------------------------- + + // A predicate that all given validators have been initialized. + spec fun spec_validators_are_initialized(validators: vector): bool { + forall i in 0..len(validators): + spec_has_stake_pool(validators[i].addr) && + spec_has_validator_config(validators[i].addr) + } + + // A predicate that the validator index of each given validator in-range. + spec fun spec_validator_indices_are_valid(validators: vector): bool { + forall i in 0..len(validators): + global(validators[i].addr).validator_index < spec_validator_index_upper_bound() + } + + // The upper bound of validator indices. + spec fun spec_validator_index_upper_bound(): u64 { + len(global(@diem_framework).validators) + } + + spec fun spec_has_stake_pool(a: address): bool { + exists(a) + } + + spec fun spec_has_validator_config(a: address): bool { + exists(a) + } + + // An uninterpreted spec function to represent the stake reward formula. + spec fun spec_rewards_amount( + stake_amount: u64, + num_successful_proposals: u64, + num_total_proposals: u64, + rewards_rate: u64, + rewards_rate_denominator: u64, + ): u64; + + spec fun spec_contains(validators: vector, addr: address): bool { + exists i in 0..len(validators): validators[i].addr == addr + } + + spec fun spec_is_current_epoch_validator(pool_address: address): bool { + let validator_set = global(@diem_framework); + !spec_contains(validator_set.pending_active, pool_address) + && (spec_contains(validator_set.active_validators, pool_address) + || spec_contains(validator_set.pending_inactive, pool_address)) + } + + // These resources are required to successfully execute `on_new_epoch`, which cannot + // be discharged by the global invariants because `on_new_epoch` is called in genesis. + spec schema ResourceRequirement { + // requires exists(@diem_framework); + requires exists(@diem_framework); + requires exists(@diem_framework); + // requires exists(@diem_framework); + // requires exists(@diem_framework) || !features::spec_periodical_reward_rate_decrease_enabled(); + requires exists(@diem_framework); + requires exists(@diem_framework); + } +} diff --git a/framework/libra-framework/sources/modified_source/stake.spec.move b/framework/libra-framework/sources/modified_source/stake.spec.move index cdea361f0..69a0527dd 100644 --- a/framework/libra-framework/sources/modified_source/stake.spec.move +++ b/framework/libra-framework/sources/modified_source/stake.spec.move @@ -1,7 +1,11 @@ +// TODO: rename this to validator.move if +// diem-platform allows spec diem_framework::stake { // ----------------- // Global invariants // ----------------- + use diem_framework::chain_status; + use diem_framework::timestamp; spec module { // The validator set should satisfy its desired invariant. @@ -16,10 +20,10 @@ spec diem_framework::stake { spec fun validator_set_is_valid(): bool { let validator_set = global(@diem_framework); spec_validators_are_initialized(validator_set.active_validators) && - spec_validators_are_initialized(validator_set.pending_inactive) && - spec_validators_are_initialized(validator_set.pending_active) && - spec_validator_indices_are_valid(validator_set.active_validators) && - spec_validator_indices_are_valid(validator_set.pending_inactive) + // spec_validators_are_initialized(validator_set.pending_inactive) && + // spec_validators_are_initialized(validator_set.pending_active) && + spec_validator_indices_are_valid(validator_set.active_validators) + // spec_validator_indices_are_valid(validator_set.pending_inactive) } @@ -35,58 +39,19 @@ spec diem_framework::stake { aborts_if exists(diem_addr); } - // spec extract_owner_cap(owner: &signer): OwnerCapability { - // let owner_address = signer::address_of(owner); - // aborts_if !exists(owner_address); - // } - - // spec deposit_owner_cap(owner: &signer, owner_cap: OwnerCapability) { - // let owner_address = signer::address_of(owner); - // aborts_if exists(owner_address); - // } - - // spec unlock_with_cap(amount: u64, owner_cap: &OwnerCapability) { - // let pool_address = owner_cap.pool_address; - // let pre_stake_pool = global(pool_address); - // let post stake_pool = global(pool_address); - // modifies global(pool_address); - // let min_amount = diem_std::math64::min(amount,pre_stake_pool.active.value); - - // ensures stake_pool.pending_inactive.value == pre_stake_pool.pending_inactive.value + min_amount; - // } - - // Only active validator can update locked_until_secs. - // spec increase_lockup_with_cap(owner_cap: &OwnerCapability) { - // let config = global(@diem_framework); - // let pool_address = owner_cap.pool_address; - // let pre_stake_pool = global(pool_address); - // let post stake_pool = global(pool_address); - // let now_seconds = timestamp::spec_now_seconds(); - // let lockup = config.recurring_lockup_duration_secs; - // modifies global(pool_address); - - // aborts_if !exists(pool_address); - // aborts_if pre_stake_pool.locked_until_secs >= lockup + now_seconds; - // aborts_if lockup + now_seconds > MAX_U64; - // aborts_if !exists(@diem_framework); - // aborts_if !exists(@diem_framework); - - // ensures stake_pool.locked_until_secs == lockup + now_seconds; - // } - spec update_network_and_fullnode_addresses( operator: &signer, - pool_address: address, + validator_address: address, new_network_addresses: vector, new_fullnode_addresses: vector, ) { - let pre_stake_pool = global(pool_address); - let post validator_info = global(pool_address); - modifies global(pool_address); + let pre_stake_pool = global(validator_address); + let post validator_info = global(validator_address); + modifies global(validator_address); // Only the true operator address can update the network and full node addresses of the validator. - aborts_if !exists(pool_address); - aborts_if !exists(pool_address); + aborts_if !exists(validator_address); + aborts_if !exists(validator_address); aborts_if signer::address_of(operator) != pre_stake_pool.operator_address; ensures validator_info.network_addresses == new_network_addresses; @@ -94,19 +59,19 @@ spec diem_framework::stake { } spec set_operator_with_cap(owner_cap: &OwnerCapability, new_operator: address) { - let pool_address = owner_cap.pool_address; - let post stake_pool = global(pool_address); - modifies global(pool_address); + let validator_address = owner_cap.validator_address; + let post stake_pool = global(validator_address); + modifies global(validator_address); ensures stake_pool.operator_address == new_operator; } // spec reactivate_stake_with_cap(owner_cap: &OwnerCapability, amount: u64) { - // let pool_address = owner_cap.pool_address; - // aborts_if !stake_pool_exists(pool_address); + // let validator_address = owner_cap.validator_address; + // aborts_if !stake_pool_exists(validator_address); - // let pre_stake_pool = global(pool_address); - // let post stake_pool = global(pool_address); - // modifies global(pool_address); + // let pre_stake_pool = global(validator_address); + // let post stake_pool = global(validator_address); + // modifies global(validator_address); // let min_amount = diem_std::math64::min(amount,pre_stake_pool.pending_inactive.value); // ensures stake_pool.active.value == pre_stake_pool.active.value + min_amount; @@ -114,33 +79,33 @@ spec diem_framework::stake { spec rotate_consensus_key( operator: &signer, - pool_address: address, + validator_address: address, new_consensus_pubkey: vector, proof_of_possession: vector, ) { - let pre_stake_pool = global(pool_address); - let post validator_info = global(pool_address); - modifies global(pool_address); + let pre_stake_pool = global(validator_address); + let post validator_info = global(validator_address); + modifies global(validator_address); ensures validator_info.consensus_pubkey == new_consensus_pubkey; } // spec set_delegated_voter_with_cap(owner_cap: &OwnerCapability, new_voter: address) { - // let pool_address = owner_cap.pool_address; - // let post stake_pool = global(pool_address); - // modifies global(pool_address); + // let validator_address = owner_cap.validator_address; + // let post stake_pool = global(validator_address); + // modifies global(validator_address); // ensures stake_pool.delegated_voter == new_voter; // } - spec on_new_epoch { - pragma disable_invariants_in_body; - // The following resource requirement cannot be discharged by the global - // invariants because this function is called during genesis. - include ResourceRequirement; - // include staking_config::StakingRewardsConfigRequirement; - // This function should never abort. - aborts_if false; - } + // spec on_new_epoch { + // pragma disable_invariants_in_body; + // // The following resource requirement cannot be discharged by the global + // // invariants because this function is called during genesis. + // include ResourceRequirement; + // // include staking_config::StakingRewardsConfigRequirement; + // // This function should never abort. + // aborts_if false; + // } spec update_performance_statistics { // This function is expected to be used after genesis. @@ -149,62 +114,6 @@ spec diem_framework::stake { aborts_if false; } - spec update_stake_pool { - include ResourceRequirement; - // include staking_config::StakingRewardsConfigRequirement; - aborts_if !exists(pool_address); - aborts_if !exists(pool_address); - aborts_if global(pool_address).validator_index >= len(validator_perf.validators); - } - - // spec distribute_rewards { - // include ResourceRequirement; - // requires rewards_rate <= MAX_REWARDS_RATE; - // requires rewards_rate_denominator > 0; - // requires rewards_rate <= rewards_rate_denominator; - // requires num_successful_proposals <= num_total_proposals; - // aborts_if false; - // ensures old(stake.value) > 0 ==> - // result == spec_rewards_amount( - // old(stake.value), - // num_successful_proposals, - // num_total_proposals, - // rewards_rate, - // rewards_rate_denominator); - // ensures old(stake.value) > 0 ==> - // stake.value == old(stake.value) + spec_rewards_amount( - // old(stake.value), - // num_successful_proposals, - // num_total_proposals, - // rewards_rate, - // rewards_rate_denominator); - // ensures old(stake.value) == 0 ==> result == 0; - // ensures old(stake.value) == 0 ==> stake.value == old(stake.value); - // } - - // spec calculate_rewards_amount { - // pragma opaque; - // requires rewards_rate <= MAX_REWARDS_RATE; - // requires rewards_rate_denominator > 0; - // requires rewards_rate <= rewards_rate_denominator; - // requires num_successful_proposals <= num_total_proposals; - // ensures [concrete] (rewards_rate_denominator * num_total_proposals == 0) ==> result == 0; - // ensures [concrete] (rewards_rate_denominator * num_total_proposals > 0) ==> { - // let amount = ((stake_amount * rewards_rate * num_successful_proposals) / - // (rewards_rate_denominator * num_total_proposals)); - // result == amount - // }; - // aborts_if false; - - // // Used an uninterpreted spec function to avoid dealing with the arithmetic overflow and non-linear arithmetic. - // ensures [abstract] result == spec_rewards_amount( - // stake_amount, - // num_successful_proposals, - // num_total_proposals, - // rewards_rate, - // rewards_rate_denominator); - // } - spec find_validator { pragma opaque; aborts_if false; @@ -214,69 +123,19 @@ spec diem_framework::stake { ensures option::is_some(result) ==> spec_contains(v, addr); } - spec append { - pragma opaque, verify = false; - aborts_if false; - ensures len(v1) == old(len(v1) + len(v2)); - ensures len(v2) == 0; - // The prefix of the new `v1` is the same as the old `v1`. - ensures (forall i in 0..old(len(v1)): v1[i] == old(v1[i])); - // The suffix of the new `v1` is the same as the reverse of the old `v2`. - ensures (forall i in old(len(v1))..len(v1): v1[i] == old(v2[len(v2) - (i - len(v1)) - 1])); - } - - spec remove_validators { - requires chain_status::is_operating(); - } spec is_current_epoch_validator { include ResourceRequirement; - aborts_if !spec_has_stake_pool(pool_address); - ensures result == spec_is_current_epoch_validator(pool_address); - } - - spec get_validator_state { - let validator_set = global(@diem_framework); - ensures result == VALIDATOR_STATUS_PENDING_ACTIVE ==> spec_contains(validator_set.pending_active, pool_address); - ensures result == VALIDATOR_STATUS_ACTIVE ==> spec_contains(validator_set.active_validators, pool_address); - ensures result == VALIDATOR_STATUS_PENDING_INACTIVE ==> spec_contains(validator_set.pending_inactive, pool_address); - ensures result == VALIDATOR_STATUS_INACTIVE ==> ( - !spec_contains(validator_set.pending_active, pool_address) - && !spec_contains(validator_set.active_validators, pool_address) - && !spec_contains(validator_set.pending_inactive, pool_address) - ); + aborts_if !spec_has_stake_pool(addr); + ensures result == spec_is_current_epoch_validator(addr); } - // spec add_stake_with_cap { - // include ResourceRequirement; - // } - - // spec add_stake { - // include ResourceRequirement; - // } - spec initialize_stake_owner { include ResourceRequirement; } - // spec add_transaction_fee(validator_addr: address, fee: Coin) { - // aborts_if !exists(@diem_framework); - // } - - spec update_voting_power_increase(increase_amount: u64) { - let diem = @diem_framework; - let pre_validator_set = global(diem); - let post validator_set = global(diem); - // let staking_config = global(diem); - // let voting_power_increase_limit = staking_config.voting_power_increase_limit; - - // Correctly modified total_joining_power and the value of total_voting_power is legal. - // ensures validator_set.total_voting_power > 0 ==> validator_set.total_joining_power <= validator_set.total_voting_power * voting_power_increase_limit / 100; - ensures validator_set.total_joining_power == pre_validator_set.total_joining_power + increase_amount; - } - - spec assert_stake_pool_exists(pool_address: address) { - aborts_if !stake_pool_exists(pool_address); + spec assert_stake_pool_exists(validator_address: address) { + aborts_if !stake_pool_exists(validator_address); } spec configure_allowed_validators(diem_framework: &signer, accounts: vector
) { @@ -314,7 +173,7 @@ spec diem_framework::stake { } spec fun spec_has_stake_pool(a: address): bool { - exists(a) + exists(a) } spec fun spec_has_validator_config(a: address): bool { @@ -334,11 +193,9 @@ spec diem_framework::stake { exists i in 0..len(validators): validators[i].addr == addr } - spec fun spec_is_current_epoch_validator(pool_address: address): bool { + spec fun spec_is_current_epoch_validator(validator_address: address): bool { let validator_set = global(@diem_framework); - !spec_contains(validator_set.pending_active, pool_address) - && (spec_contains(validator_set.active_validators, pool_address) - || spec_contains(validator_set.pending_inactive, pool_address)) + spec_contains(validator_set.active_validators, validator_address) } // These resources are required to successfully execute `on_new_epoch`, which cannot diff --git a/framework/libra-framework/sources/ol_sources/mock.move b/framework/libra-framework/sources/ol_sources/mock.move index b17aeb8e0..6f1b3b311 100644 --- a/framework/libra-framework/sources/ol_sources/mock.move +++ b/framework/libra-framework/sources/ol_sources/mock.move @@ -25,7 +25,7 @@ module ol_framework::mock { use ol_framework::globals; use diem_framework::block; // use diem_framework::chain_status; - // use diem_std::debug::print; + use diem_std::debug::print; const ENO_GENESIS_END_MARKER: u64 = 1; const EDID_NOT_ADVANCE_EPOCH: u64 = 1; @@ -251,6 +251,9 @@ module ol_framework::mock { i = i + 1; }; + timestamp::fast_forward_seconds(2); // or else reconfigure wont happen + stake::test_reconfigure(root, validator_universe::get_eligible_validators()); + stake::get_current_validators() } @@ -299,6 +302,7 @@ module ol_framework::mock { // genesis(); let set = genesis_n_vals(&root, 4); + print(&set); assert!(vector::length(&set) == 4, 7357001); let addr = vector::borrow(&set, 0); diff --git a/framework/libra-framework/sources/ol_sources/tests/_meta_epoch.test.move b/framework/libra-framework/sources/ol_sources/tests/_meta_epoch.test.move index a3551d4ff..aaf6d0ca0 100644 --- a/framework/libra-framework/sources/ol_sources/tests/_meta_epoch.test.move +++ b/framework/libra-framework/sources/ol_sources/tests/_meta_epoch.test.move @@ -3,34 +3,34 @@ /// tests for external apis, and where a dependency cycle with genesis is created. module ol_framework::test_meta { use diem_framework::reconfiguration; - use diem_framework::stake; + // use diem_framework::timestamp; use ol_framework::mock; // can we trigger a reconfiguration and get to a new epoch? // see also mock::trigger_epoch() and meta_epoch() test - #[test(root = @ol_framework)] - fun test_reconfigure_custom(root: signer) { + // #[test(root = @ol_framework)] + // fun test_reconfigure_custom(root: signer) { - mock::ol_test_genesis(&root); - // NOTE: There was no genesis END event here. - // Which means we need to use test_helper_increment_epoch_dont_reconfigure + // mock::ol_test_genesis(&root); + // // NOTE: There was no genesis END event here. + // // Which means we need to use test_helper_increment_epoch_dont_reconfigure - let a = reconfiguration::get_current_epoch(); + // let a = reconfiguration::get_current_epoch(); - // create a new epoch - stake::end_epoch(); + // // create a new epoch + // stake::end_epoch(); - reconfiguration::test_helper_increment_epoch_dont_reconfigure(); + // reconfiguration::test_helper_increment_epoch_dont_reconfigure(); - let b = reconfiguration::get_current_epoch(); + // let b = reconfiguration::get_current_epoch(); - assert!(a == 0, 10001); - assert!(b == 1, 10002); + // assert!(a == 0, 10001); + // assert!(b == 1, 10002); - } + // } #[test(root = @ol_framework)] // can we trigger a reconfiguration and get to a new epoch? @@ -38,7 +38,6 @@ module ol_framework::test_meta { // NOTE: genesis_n_vals, DOES trigger a genesis END event. mock::genesis_n_vals(&root, 4); - let a = reconfiguration::get_current_epoch(); // create a new epoch diff --git a/framework/releases/head.mrb b/framework/releases/head.mrb index e80b12591..90402a0a4 100644 Binary files a/framework/releases/head.mrb and b/framework/releases/head.mrb differ diff --git a/tools/genesis/src/genesis.rs b/tools/genesis/src/genesis.rs index 63677f16d..e6d91380e 100644 --- a/tools/genesis/src/genesis.rs +++ b/tools/genesis/src/genesis.rs @@ -110,7 +110,8 @@ fn test_recovery_genesis() { .unwrap() .extract_raw_bytes() .unwrap(); - let validator_set: ValidatorSet = bcs::from_bytes(&bytes).unwrap(); + let validator_set: ValidatorSet = + bcs::from_bytes(&bytes).expect("no validator set found in bytes"); assert!( validator_set.active_validators().len() == 4, "validator set is empty" diff --git a/tools/genesis/src/genesis_functions.rs b/tools/genesis/src/genesis_functions.rs index e37151c19..f09acb589 100644 --- a/tools/genesis/src/genesis_functions.rs +++ b/tools/genesis/src/genesis_functions.rs @@ -179,6 +179,7 @@ pub fn genesis_migrate_infra_escrow( user_recovery: &LegacyRecovery, escrow_pct: f64, ) -> anyhow::Result<()> { + dbg!("infra"); if user_recovery.account.is_none() || user_recovery.auth_key.is_none() || user_recovery.balance.is_none() diff --git a/tools/txs/src/txs_cli_vals.rs b/tools/txs/src/txs_cli_vals.rs index 2121254a0..082030624 100644 --- a/tools/txs/src/txs_cli_vals.rs +++ b/tools/txs/src/txs_cli_vals.rs @@ -3,12 +3,13 @@ use std::{fs, path::PathBuf}; use crate::submit_transaction::Sender; -use anyhow::bail; +use anyhow::{bail, Context}; use diem_genesis::config::OperatorConfiguration; use diem_types::account_address::AccountAddress; use libra_cached_packages::libra_stdlib::EntryFunctionCall::{ JailUnjailByVoucher, ProofOfFeePofRetractBid, ProofOfFeePofUpdateBid, - ValidatorUniverseRegisterValidator, VouchRevoke, VouchVouchFor, + StakeUpdateNetworkAndFullnodeAddresses, ValidatorUniverseRegisterValidator, VouchRevoke, + VouchVouchFor, }; use libra_types::global_config_dir; @@ -45,6 +46,11 @@ pub enum ValidatorTxs { /// optional, Path to files with registration files operator_file: Option, }, + Update { + #[clap(short('f'), long)] + /// optional, Path to files with registration files + operator_file: Option, + }, } impl ValidatorTxs { @@ -97,21 +103,47 @@ impl ValidatorTxs { let yaml_str = fs::read_to_string(file)?; let oc: OperatorConfiguration = serde_yaml::from_str(&yaml_str)?; - let validator_address = oc + let val_net_protocol = oc .validator_host .as_network_address(oc.validator_network_public_key)?; - // let fullnode_host = oc.full_node_host.context("cannot find fullnode host")?; - // let fullnode_addr = fullnode_host.as_network_address( - // oc.full_node_network_public_key - // .context("cannot find fullnode network public key")?, - // )?; + let fullnode_host = oc.full_node_host.context("cannot find fullnode host")?; + let vfn_fullnode_protocol = fullnode_host.as_network_address( + oc.full_node_network_public_key + .context("cannot find fullnode network public key")?, + )?; ValidatorUniverseRegisterValidator { consensus_pubkey: oc.consensus_public_key.to_bytes().to_vec(), proof_of_possession: oc.consensus_proof_of_possession.to_bytes().to_vec(), - network_addresses: bcs::to_bytes(&validator_address)?, - fullnode_addresses: vec![], // TODO + network_addresses: bcs::to_bytes(&val_net_protocol)?, + fullnode_addresses: bcs::to_bytes(&vfn_fullnode_protocol)?, + } + } + ValidatorTxs::Update { operator_file } => { + let file = operator_file.to_owned().unwrap_or_else(|| { + let a = global_config_dir(); + a.join(OPERATOR_FILE) + }); + + let yaml_str = fs::read_to_string(file)?; + + let oc: OperatorConfiguration = serde_yaml::from_str(&yaml_str)?; + + let val_net_protocol = oc + .validator_host + .as_network_address(oc.validator_network_public_key)?; + + let fullnode_host = oc.full_node_host.context("cannot find fullnode host")?; + let vfn_fullnode_protocol = fullnode_host.as_network_address( + oc.full_node_network_public_key + .context("cannot find fullnode network public key")?, + )?; + + StakeUpdateNetworkAndFullnodeAddresses { + validator_address: oc.operator_account_address.into(), + new_network_addresses: bcs::to_bytes(&val_net_protocol)?, + new_fullnode_addresses: bcs::to_bytes(&vfn_fullnode_protocol)?, } } };