diff --git a/aptos-move/aptos-gas-schedule-updator/src/lib.rs b/aptos-move/aptos-gas-schedule-updator/src/lib.rs index fc42bd3943e73..9967b5bf9f488 100644 --- a/aptos-move/aptos-gas-schedule-updator/src/lib.rs +++ b/aptos-move/aptos-gas-schedule-updator/src/lib.rs @@ -86,6 +86,11 @@ fn generate_script(gas_schedule: &GasScheduleV2) -> Result { "gas_schedule::set_gas_schedule(&framework_signer, gas_schedule_blob);" ); + emitln!( + writer, + "aptos_governance::reconfigure(&framework_signer);" + ); + writer.unindent(); emitln!(writer, "}"); diff --git a/aptos-move/aptos-release-builder/src/components/consensus_config.rs b/aptos-move/aptos-release-builder/src/components/consensus_config.rs index d21499e138fd9..37e41f28faa96 100644 --- a/aptos-move/aptos-release-builder/src/components/consensus_config.rs +++ b/aptos-move/aptos-release-builder/src/components/consensus_config.rs @@ -37,11 +37,19 @@ pub fn generate_consensus_upgrade_proposal( writer, "consensus_config::set(framework_signer, consensus_blob);" ); + emitln!( + writer, + "aptos_governance::reconfigure(framework_signer);" + ); } else { emitln!( writer, "consensus_config::set(&framework_signer, consensus_blob);" ); + emitln!( + writer, + "aptos_governance::reconfigure(&framework_signer);" + ); } }, ); diff --git a/aptos-move/aptos-release-builder/src/components/execution_config.rs b/aptos-move/aptos-release-builder/src/components/execution_config.rs index 4bc5db4364468..fb04015681829 100644 --- a/aptos-move/aptos-release-builder/src/components/execution_config.rs +++ b/aptos-move/aptos-release-builder/src/components/execution_config.rs @@ -37,11 +37,19 @@ pub fn generate_execution_config_upgrade_proposal( writer, "execution_config::set(framework_signer, execution_blob);" ); + emitln!( + writer, + "aptos_governance::reconfigure(framework_signer);" + ); } else { emitln!( writer, "execution_config::set(&framework_signer, execution_blob);" ); + emitln!( + writer, + "aptos_governance::reconfigure(&framework_signer);" + ); } }, ); diff --git a/aptos-move/aptos-release-builder/src/components/feature_flags.rs b/aptos-move/aptos-release-builder/src/components/feature_flags.rs index 10c9a1f34cb0d..0bd8dc166afca 100644 --- a/aptos-move/aptos-release-builder/src/components/feature_flags.rs +++ b/aptos-move/aptos-release-builder/src/components/feature_flags.rs @@ -88,6 +88,7 @@ pub enum FeatureFlag { FeePayerAccountOptional, AggregatorV2DelayedFields, ConcurrentAssets, + ReconfigureWithDKG, } fn generate_features_blob(writer: &CodeWriter, data: &[u64]) { @@ -230,6 +231,7 @@ impl From for AptosFeatureFlag { AptosFeatureFlag::AGGREGATOR_V2_DELAYED_FIELDS }, FeatureFlag::ConcurrentAssets => AptosFeatureFlag::CONCURRENT_ASSETS, + FeatureFlag::ReconfigureWithDKG => AptosFeatureFlag::RECONFIGURE_WITH_DKG, } } } @@ -295,6 +297,7 @@ impl From for FeatureFlag { FeatureFlag::AggregatorV2DelayedFields }, AptosFeatureFlag::CONCURRENT_ASSETS => FeatureFlag::ConcurrentAssets, + AptosFeatureFlag::RECONFIGURE_WITH_DKG => FeatureFlag::ReconfigureWithDKG, } } } diff --git a/aptos-move/aptos-release-builder/src/validate.rs b/aptos-move/aptos-release-builder/src/validate.rs index 96aea9f9e9ddc..baf7264a9de8e 100644 --- a/aptos-move/aptos-release-builder/src/validate.rs +++ b/aptos-move/aptos-release-builder/src/validate.rs @@ -405,6 +405,7 @@ async fn execute_release( // Execute proposals for proposal in &release_config.proposals { + println!("[TTT] proposal.name={}", proposal.name); let mut proposal_path = proposal_folder.to_path_buf(); proposal_path.push("sources"); proposal_path.push(&release_config.name); @@ -465,6 +466,7 @@ async fn execute_release( } }, }; + println!("[TTT] about to verify proposal.name={}", proposal.name); if validate_release { release_config.validate_upgrade(&network_config.endpoint, proposal)?; } diff --git a/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/configs/config_for_next_epoch.move b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/configs/config_for_next_epoch.move new file mode 120000 index 0000000000000..4754525889658 --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib/sources/configs/config_for_next_epoch.move @@ -0,0 +1 @@ +../../../../../../../framework/move-stdlib/sources/configs/config_for_next_epoch.move \ No newline at end of file diff --git a/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/configs/config_for_next_epoch.move b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/configs/config_for_next_epoch.move new file mode 120000 index 0000000000000..4754525889658 --- /dev/null +++ b/aptos-move/e2e-move-tests/src/tests/code_publishing.data/pack_stdlib_incompat/sources/configs/config_for_next_epoch.move @@ -0,0 +1 @@ +../../../../../../../framework/move-stdlib/sources/configs/config_for_next_epoch.move \ No newline at end of file diff --git a/aptos-move/framework/aptos-framework/doc/aptos_governance.md b/aptos-move/framework/aptos-framework/doc/aptos_governance.md index 048f320585033..fc29062dc474d 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_governance.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_governance.md @@ -96,6 +96,7 @@ on a proposal multiple times as long as the total voting power of these votes do use 0x1::math64; use 0x1::option; use 0x1::reconfiguration; +use 0x1::reconfiguration_v2; use 0x1::signer; use 0x1::simple_map; use 0x1::smart_table; @@ -1472,7 +1473,11 @@ Force reconfigure. To be called at the end of a proposal that alters on-chain co
public fun reconfigure(aptos_framework: &signer) {
     system_addresses::assert_aptos_framework(aptos_framework);
-    reconfiguration::reconfigure();
+    if (features::reconfigure_with_dkg_enabled()) {
+        reconfiguration_v2::start(aptos_framework);
+    } else {
+        reconfiguration::reconfigure();
+    }
 }
 
@@ -1499,7 +1504,12 @@ Update feature flags and also trigger reconfiguration.
public fun toggle_features(aptos_framework: &signer, enable: vector<u64>, disable: vector<u64>) {
     system_addresses::assert_aptos_framework(aptos_framework);
     features::change_feature_flags(aptos_framework, enable, disable);
-    reconfiguration::reconfigure();
+
+    if (features::reconfigure_with_dkg_enabled()) {
+        reconfiguration_v2::start(aptos_framework);
+    } else {
+        reconfiguration::reconfigure();
+    }
 }
 
diff --git a/aptos-move/framework/aptos-framework/doc/block.md b/aptos-move/framework/aptos-framework/doc/block.md index 65f0cc0d3ff3b..f0f1d556d436d 100644 --- a/aptos-move/framework/aptos-framework/doc/block.md +++ b/aptos-move/framework/aptos-framework/doc/block.md @@ -14,6 +14,7 @@ This module defines a struct storing the metadata of the block and new block eve - [Function `update_epoch_interval_microsecs`](#0x1_block_update_epoch_interval_microsecs) - [Function `get_epoch_interval_secs`](#0x1_block_get_epoch_interval_secs) - [Function `block_prologue`](#0x1_block_block_prologue) +- [Function `block_prologue_v2`](#0x1_block_block_prologue_v2) - [Function `get_current_block_height`](#0x1_block_get_current_block_height) - [Function `emit_new_block_event`](#0x1_block_emit_new_block_event) - [Function `emit_genesis_block_event`](#0x1_block_emit_genesis_block_event) @@ -31,11 +32,13 @@ This module defines a struct storing the metadata of the block and new block eve
use 0x1::account;
+use 0x1::dkg;
 use 0x1::error;
 use 0x1::event;
 use 0x1::features;
 use 0x1::option;
 use 0x1::reconfiguration;
+use 0x1::reconfiguration_v2;
 use 0x1::stake;
 use 0x1::state_storage;
 use 0x1::system_addresses;
@@ -418,6 +421,93 @@ The runtime always runs this before executing the transactions in a block.
 
 
 
+
+
+
+
+## Function `block_prologue_v2`
+
+Set the metadata for the current block.
+The runtime always runs this before executing the transactions in a block.
+
+
+
fun block_prologue_v2(vm: signer, hash: address, epoch: u64, round: u64, proposer: address, failed_proposer_indices: vector<u64>, previous_block_votes_bitvec: vector<u8>, timestamp: u64, dkg_result_available: bool, dkg_result: vector<u8>)
+
+ + + +
+Implementation + + +
fun block_prologue_v2(
+    vm: signer,
+    hash: address,
+    epoch: u64,
+    round: u64,
+    proposer: address,
+    failed_proposer_indices: vector<u64>,
+    previous_block_votes_bitvec: vector<u8>,
+    timestamp: u64,
+    dkg_result_available: bool,
+    dkg_result: vector<u8>,
+) acquires BlockResource {
+    // Operational constraint: can only be invoked by the VM.
+    system_addresses::assert_vm(&vm);
+
+    // Blocks can only be produced by a valid proposer or by the VM itself for Nil blocks (no user txs).
+    assert!(
+        proposer == @vm_reserved || stake::is_current_epoch_validator(proposer),
+        error::permission_denied(EINVALID_PROPOSER),
+    );
+
+    let proposer_index = option::none();
+    if (proposer != @vm_reserved) {
+        proposer_index = option::some(stake::get_validator_index(proposer));
+    };
+
+    let block_metadata_ref = borrow_global_mut<BlockResource>(@aptos_framework);
+    block_metadata_ref.height = event::counter(&block_metadata_ref.new_block_events);
+
+    let new_block_event = NewBlockEvent {
+        hash,
+        epoch,
+        round,
+        height: block_metadata_ref.height,
+        previous_block_votes_bitvec,
+        proposer,
+        failed_proposer_indices,
+        time_microseconds: timestamp,
+    };
+    emit_new_block_event(&vm, &mut block_metadata_ref.new_block_events, new_block_event);
+
+    if (features::collect_and_distribute_gas_fees()) {
+        // Assign the fees collected from the previous block to the previous block proposer.
+        // If for any reason the fees cannot be assigned, this function burns the collected coins.
+        transaction_fee::process_collected_fees();
+        // Set the proposer of this block as the receiver of the fees, so that the fees for this
+        // block are assigned to the right account.
+        transaction_fee::register_proposer_for_fee_collection(proposer);
+    };
+
+    // Performance scores have to be updated before the epoch transition as the transaction that triggers the
+    // transition is the last block in the previous epoch.
+    stake::update_performance_statistics(proposer_index, failed_proposer_indices);
+    state_storage::on_new_block(reconfiguration::current_epoch());
+
+    if (dkg::in_progress()) {
+        let should_proceed = dkg::update(dkg_result_available, dkg_result);
+        if (should_proceed) {
+            reconfiguration_v2::finish(&vm);
+        }
+    } else if (timestamp - reconfiguration::last_reconfiguration_time() >= block_metadata_ref.epoch_interval) {
+        reconfiguration_v2::start(&vm);
+    };
+}
+
+ + +
diff --git a/aptos-move/framework/aptos-framework/doc/consensus_config.md b/aptos-move/framework/aptos-framework/doc/consensus_config.md index 5711b0917683e..1c819f8ce3b17 100644 --- a/aptos-move/framework/aptos-framework/doc/consensus_config.md +++ b/aptos-move/framework/aptos-framework/doc/consensus_config.md @@ -11,12 +11,15 @@ Reconfiguration, and may be updated by root. - [Constants](#@Constants_0) - [Function `initialize`](#0x1_consensus_config_initialize) - [Function `set`](#0x1_consensus_config_set) +- [Function `set_for_next_epoch`](#0x1_consensus_config_set_for_next_epoch) +- [Function `on_new_epoch`](#0x1_consensus_config_on_new_epoch) - [Specification](#@Specification_1) - [Function `initialize`](#@Specification_1_initialize) - [Function `set`](#@Specification_1_set) -
use 0x1::error;
+
use 0x1::config_for_next_epoch;
+use 0x1::error;
 use 0x1::reconfiguration;
 use 0x1::system_addresses;
 
@@ -29,7 +32,7 @@ Reconfiguration, and may be updated by root. -
struct ConsensusConfig has key
+
struct ConsensusConfig has drop, store, key
 
@@ -122,6 +125,58 @@ This can be called by on-chain governance to update on-chain consensus configs. + + + + +## Function `set_for_next_epoch` + + + +
public fun set_for_next_epoch(account: &signer, config: vector<u8>)
+
+ + + +
+Implementation + + +
public fun set_for_next_epoch(account: &signer, config: vector<u8>) {
+    system_addresses::assert_aptos_framework(account);
+    assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG));
+    std::config_for_next_epoch::upsert<ConsensusConfig>(account, ConsensusConfig {config});
+}
+
+ + + +
+ + + +## Function `on_new_epoch` + + + +
public(friend) fun on_new_epoch(account: &signer)
+
+ + + +
+Implementation + + +
public(friend) fun on_new_epoch(account: &signer) acquires ConsensusConfig {
+    if (config_for_next_epoch::does_exist<ConsensusConfig>()) {
+        *borrow_global_mut<ConsensusConfig>(@aptos_framework) = std::config_for_next_epoch::extract(account);
+    }
+}
+
+ + +
@@ -178,7 +233,6 @@ When setting now time must be later than last_reconfiguration_time. aborts_if !exists<ConsensusConfig>(@aptos_framework); aborts_if !(len(config) > 0); requires chain_status::is_operating(); -requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time(); requires exists<stake::ValidatorFees>(@aptos_framework); requires exists<CoinInfo<AptosCoin>>(@aptos_framework);
diff --git a/aptos-move/framework/aptos-framework/doc/dkg.md b/aptos-move/framework/aptos-framework/doc/dkg.md new file mode 100644 index 0000000000000..432e052dd022c --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/dkg.md @@ -0,0 +1,316 @@ + + + +# Module `0x1::dkg` + + + +- [Struct `StartDKGEvent`](#0x1_dkg_StartDKGEvent) +- [Struct `DKGSessionState`](#0x1_dkg_DKGSessionState) +- [Resource `DKGState`](#0x1_dkg_DKGState) +- [Constants](#@Constants_0) +- [Function `start`](#0x1_dkg_start) +- [Function `update`](#0x1_dkg_update) +- [Function `in_progress`](#0x1_dkg_in_progress) +- [Function `current_deadline`](#0x1_dkg_current_deadline) + + +
use 0x1::error;
+use 0x1::event;
+use 0x1::option;
+use 0x1::stake;
+use 0x1::timestamp;
+
+ + + + + +## Struct `StartDKGEvent` + + + +
struct StartDKGEvent has drop, store
+
+ + + +
+Fields + + +
+
+target_epoch: u64 +
+
+ +
+
+target_validator_set: stake::ValidatorSet +
+
+ +
+
+ + +
+ + + +## Struct `DKGSessionState` + +The input and output of a DKG session. +The validator set of epoch x works together and outputs a transcript for the target validator set of epoch y (typically x+1). + + +
struct DKGSessionState has copy, drop, store
+
+ + + +
+Fields + + +
+
+dealer_epoch: u64 +
+
+ +
+
+dealer_validator_set: stake::ValidatorSet +
+
+ +
+
+target_epoch: u64 +
+
+ +
+
+target_validator_set: stake::ValidatorSet +
+
+ +
+
+result: vector<u8> +
+
+ +
+
+deadline_microseconds: u64 +
+
+ +
+
+ + +
+ + + +## Resource `DKGState` + +The complete and ongoing DKG sessions. + + +
struct DKGState has key
+
+ + + +
+Fields + + +
+
+last_complete: option::Option<dkg::DKGSessionState> +
+
+ +
+
+in_progress: option::Option<dkg::DKGSessionState> +
+
+ +
+
+events: event::EventHandle<dkg::StartDKGEvent> +
+
+ +
+
+ + +
+ + + +## Constants + + + + +Another reconfiguration is in progress. + + +
const EANOTHER_RECONFIGURATION_IN_PROGRESS: u64 = 1;
+
+ + + + + +There is no reconfiguration in progress. + + +
const ENO_RECONFIGURATION_IN_PROGRESS: u64 = 2;
+
+ + + + + +## Function `start` + + + +
public(friend) fun start(dealer_epoch: u64, dealer_validator_set: stake::ValidatorSet, target_epoch: u64, target_validator_set: stake::ValidatorSet)
+
+ + + +
+Implementation + + +
public(friend) fun start(dealer_epoch: u64, dealer_validator_set: ValidatorSet, target_epoch: u64, target_validator_set: ValidatorSet) acquires DKGState {
+    let dkg_state = borrow_global_mut<DKGState>(@aptos_framework);
+    assert!(std::option::is_none(&dkg_state.in_progress), 1);
+    dkg_state.in_progress = std::option::some(DKGSessionState {
+        dealer_epoch,
+        dealer_validator_set,
+        target_epoch,
+        target_validator_set,
+        deadline_microseconds: timestamp::now_microseconds() + 60000000,
+        result: vector[],
+    });
+    event::emit_event<StartDKGEvent>(
+        &mut dkg_state.events,
+        StartDKGEvent {
+            target_epoch,
+            target_validator_set,
+        },
+    );
+}
+
+ + + +
+ + + +## Function `update` + +Update the current DKG state with a potential transcript. +Return true if the current DKG becomes inactive and we should start a new epoch. +Abort if no DKG is in progress. + + +
public(friend) fun update(dkg_result_available: bool, dkg_result: vector<u8>): bool
+
+ + + +
+Implementation + + +
public(friend) fun update(dkg_result_available: bool, dkg_result: vector<u8>): bool acquires DKGState {
+    let dkg_state = borrow_global_mut<DKGState>(@aptos_framework);
+    assert!(option::is_some(&dkg_state.in_progress), error::invalid_state(ENO_RECONFIGURATION_IN_PROGRESS));
+    let session = option::extract(&mut dkg_state.in_progress);
+    let dkg_completed = false;
+    if (dkg_result_available) {
+        session.result = dkg_result;
+        dkg_completed = true;
+    };
+    if (timestamp::now_microseconds() >= session.deadline_microseconds || dkg_completed) {
+        dkg_state.last_complete = option::some(session);
+        dkg_state.in_progress = option::none();
+        true
+    } else {
+        dkg_state.in_progress = option::some(session);
+        false
+    }
+}
+
+ + + +
+ + + +## Function `in_progress` + + + +
public(friend) fun in_progress(): bool
+
+ + + +
+Implementation + + +
public(friend) fun in_progress(): bool acquires DKGState {
+    option::is_some(&borrow_global<DKGState>(@aptos_framework).in_progress)
+}
+
+ + + +
+ + + +## Function `current_deadline` + + + +
public(friend) fun current_deadline(): u64
+
+ + + +
+Implementation + + +
public(friend) fun current_deadline(): u64 acquires DKGState {
+    let in_progress_session = option::borrow(&borrow_global<DKGState>(@aptos_framework).in_progress);
+    in_progress_session.deadline_microseconds
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/execution_config.md b/aptos-move/framework/aptos-framework/doc/execution_config.md index e369073ac7a95..c5401961b9ccb 100644 --- a/aptos-move/framework/aptos-framework/doc/execution_config.md +++ b/aptos-move/framework/aptos-framework/doc/execution_config.md @@ -10,11 +10,14 @@ Reconfiguration, and may be updated by root. - [Resource `ExecutionConfig`](#0x1_execution_config_ExecutionConfig) - [Constants](#@Constants_0) - [Function `set`](#0x1_execution_config_set) +- [Function `set_for_next_epoch`](#0x1_execution_config_set_for_next_epoch) +- [Function `on_new_epoch`](#0x1_execution_config_on_new_epoch) - [Specification](#@Specification_1) - [Function `set`](#@Specification_1_set) -
use 0x1::error;
+
use 0x1::config_for_next_epoch;
+use 0x1::error;
 use 0x1::reconfiguration;
 use 0x1::system_addresses;
 
@@ -27,7 +30,7 @@ Reconfiguration, and may be updated by root. -
struct ExecutionConfig has key
+
struct ExecutionConfig has drop, store, key
 
@@ -96,6 +99,61 @@ This can be called by on-chain governance to update on-chain execution configs. + + + + +## Function `set_for_next_epoch` + +This can be called by on-chain governance to update on-chain execution configs. + + +
public fun set_for_next_epoch(account: &signer, config: vector<u8>)
+
+ + + +
+Implementation + + +
public fun set_for_next_epoch(account: &signer, config: vector<u8>) {
+    system_addresses::assert_aptos_framework(account);
+    assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG));
+    config_for_next_epoch::upsert(account, ExecutionConfig { config });
+}
+
+ + + +
+ + + +## Function `on_new_epoch` + +Only used in reconfiguration with DKG. + + +
public(friend) fun on_new_epoch(account: &signer)
+
+ + + +
+Implementation + + +
public(friend) fun on_new_epoch(account: &signer) acquires ExecutionConfig {
+    if (config_for_next_epoch::does_exist<ExecutionConfig>()) {
+        let config = config_for_next_epoch::extract<ExecutionConfig>(account);
+        *borrow_global_mut<ExecutionConfig>(@aptos_framework) = config;
+    }
+}
+
+ + +
@@ -132,7 +190,6 @@ When setting now time must be later than last_reconfiguration_time. include features::spec_periodical_reward_rate_decrease_enabled() ==> staking_config::StakingRewardsConfigEnabledRequirement; include features::spec_collect_and_distribute_gas_fees_enabled() ==> aptos_coin::ExistsAptosCoin; requires system_addresses::is_aptos_framework_address(addr); -requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time();
diff --git a/aptos-move/framework/aptos-framework/doc/gas_schedule.md b/aptos-move/framework/aptos-framework/doc/gas_schedule.md index a502694a36031..48b3a7c414381 100644 --- a/aptos-move/framework/aptos-framework/doc/gas_schedule.md +++ b/aptos-move/framework/aptos-framework/doc/gas_schedule.md @@ -13,6 +13,8 @@ it costs to execute Move on the network. - [Constants](#@Constants_0) - [Function `initialize`](#0x1_gas_schedule_initialize) - [Function `set_gas_schedule`](#0x1_gas_schedule_set_gas_schedule) +- [Function `set_for_next_epoch`](#0x1_gas_schedule_set_for_next_epoch) +- [Function `on_new_epoch`](#0x1_gas_schedule_on_new_epoch) - [Function `set_storage_gas_config`](#0x1_gas_schedule_set_storage_gas_config) - [Specification](#@Specification_1) - [Function `initialize`](#@Specification_1_initialize) @@ -20,7 +22,8 @@ it costs to execute Move on the network. - [Function `set_storage_gas_config`](#@Specification_1_set_storage_gas_config) -
use 0x1::error;
+
use 0x1::config_for_next_epoch;
+use 0x1::error;
 use 0x1::reconfiguration;
 use 0x1::storage_gas;
 use 0x1::string;
@@ -97,7 +100,7 @@ it costs to execute Move on the network.
 
 
 
-
struct GasScheduleV2 has copy, drop, key
+
struct GasScheduleV2 has copy, drop, store, key
 
@@ -222,6 +225,62 @@ This can be called by on-chain governance to update the gas schedule. + + + + +## Function `set_for_next_epoch` + +This can be called by on-chain governance to update the gas schedule. + + +
public fun set_for_next_epoch(aptos_framework: &signer, gas_schedule_blob: vector<u8>)
+
+ + + +
+Implementation + + +
public fun set_for_next_epoch(aptos_framework: &signer, gas_schedule_blob: vector<u8>) {
+    system_addresses::assert_aptos_framework(aptos_framework);
+    assert!(!vector::is_empty(&gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE));
+    let new_gas_schedule: GasScheduleV2 = from_bytes(gas_schedule_blob);
+    config_for_next_epoch::upsert(aptos_framework, new_gas_schedule);
+}
+
+ + + +
+ + + +## Function `on_new_epoch` + + + +
public(friend) fun on_new_epoch(account: &signer)
+
+ + + +
+Implementation + + +
public(friend) fun on_new_epoch(account: &signer) acquires GasScheduleV2 {
+    if (config_for_next_epoch::does_exist<GasScheduleV2>()) {
+        let new_gas_schedule: GasScheduleV2 = config_for_next_epoch::extract<GasScheduleV2>(account);
+        let gas_schedule = borrow_global_mut<GasScheduleV2>(@aptos_framework);
+        *gas_schedule = new_gas_schedule;
+    }
+}
+
+ + +
@@ -241,9 +300,6 @@ This can be called by on-chain governance to update the gas schedule.
public fun set_storage_gas_config(aptos_framework: &signer, config: StorageGasConfig) {
     storage_gas::set_config(aptos_framework, config);
-    // Need to trigger reconfiguration so the VM is guaranteed to load the new gas fee starting from the next
-    // transaction.
-    reconfiguration::reconfigure();
 }
 
diff --git a/aptos-move/framework/aptos-framework/doc/genesis.md b/aptos-move/framework/aptos-framework/doc/genesis.md index 4e7535960e995..48380c1edaf02 100644 --- a/aptos-move/framework/aptos-framework/doc/genesis.md +++ b/aptos-move/framework/aptos-framework/doc/genesis.md @@ -771,7 +771,7 @@ encoded in a single BCS byte array. validator.consensus_pubkey, validator.proof_of_possession, ); - stake::update_network_and_fullnode_addresses( + stake::force_update_network_and_fullnode_addresses( operator, pool_address, validator.network_addresses, diff --git a/aptos-move/framework/aptos-framework/doc/overview.md b/aptos-move/framework/aptos-framework/doc/overview.md index b938675ae830d..131219c1ef288 100644 --- a/aptos-move/framework/aptos-framework/doc/overview.md +++ b/aptos-move/framework/aptos-framework/doc/overview.md @@ -27,6 +27,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::consensus_config`](consensus_config.md#0x1_consensus_config) - [`0x1::create_signer`](create_signer.md#0x1_create_signer) - [`0x1::delegation_pool`](delegation_pool.md#0x1_delegation_pool) +- [`0x1::dkg`](dkg.md#0x1_dkg) - [`0x1::event`](event.md#0x1_event) - [`0x1::execution_config`](execution_config.md#0x1_execution_config) - [`0x1::fungible_asset`](fungible_asset.md#0x1_fungible_asset) @@ -40,6 +41,7 @@ This is the reference documentation of the Aptos framework. - [`0x1::optional_aggregator`](optional_aggregator.md#0x1_optional_aggregator) - [`0x1::primary_fungible_store`](primary_fungible_store.md#0x1_primary_fungible_store) - [`0x1::reconfiguration`](reconfiguration.md#0x1_reconfiguration) +- [`0x1::reconfiguration_v2`](reconfiguration_v2.md#0x1_reconfiguration_v2) - [`0x1::resource_account`](resource_account.md#0x1_resource_account) - [`0x1::stake`](stake.md#0x1_stake) - [`0x1::staking_config`](staking_config.md#0x1_staking_config) diff --git a/aptos-move/framework/aptos-framework/doc/reconfiguration_v2.md b/aptos-move/framework/aptos-framework/doc/reconfiguration_v2.md new file mode 100644 index 0000000000000..42737053bde6b --- /dev/null +++ b/aptos-move/framework/aptos-framework/doc/reconfiguration_v2.md @@ -0,0 +1,85 @@ + + + +# Module `0x1::reconfiguration_v2` + + + +- [Function `start`](#0x1_reconfiguration_v2_start) +- [Function `finish`](#0x1_reconfiguration_v2_finish) + + +
use 0x1::config_for_next_epoch;
+use 0x1::consensus_config;
+use 0x1::dkg;
+use 0x1::execution_config;
+use 0x1::features;
+use 0x1::gas_schedule;
+use 0x1::reconfiguration;
+use 0x1::stake;
+use 0x1::version;
+
+ + + + + +## Function `start` + + + +
public(friend) fun start(account: &signer)
+
+ + + +
+Implementation + + +
public(friend) fun start(account: &signer) {
+    config_for_next_epoch::disable_upserts(account);
+    let cur_epoch = reconfiguration::current_epoch();
+    dkg::start(cur_epoch, stake::cur_validator_set(), cur_epoch + 1, stake::next_validator_set());
+}
+
+ + + +
+ + + +## Function `finish` + +Apply buffered on-chain configs. +Re-enable on-chain config changes. +Trigger the default reconfiguration. + + +
public(friend) fun finish(account: &signer)
+
+ + + +
+Implementation + + +
public(friend) fun finish(account: &signer) {
+    features::on_new_epoch(account);
+    consensus_config::on_new_epoch(account);
+    execution_config::on_new_epoch(account);
+    gas_schedule::on_new_epoch(account);
+    std::version::on_new_epoch(account);
+    config_for_next_epoch::enable_upserts(account);
+    reconfiguration::reconfigure();
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/stake.md b/aptos-move/framework/aptos-framework/doc/stake.md index 44f7b4b86a780..9124c91c82771 100644 --- a/aptos-move/framework/aptos-framework/doc/stake.md +++ b/aptos-move/framework/aptos-framework/doc/stake.md @@ -82,6 +82,7 @@ or if their stake drops below the min required, they would get removed at the en - [Function `reactivate_stake_with_cap`](#0x1_stake_reactivate_stake_with_cap) - [Function `rotate_consensus_key`](#0x1_stake_rotate_consensus_key) - [Function `update_network_and_fullnode_addresses`](#0x1_stake_update_network_and_fullnode_addresses) +- [Function `force_update_network_and_fullnode_addresses`](#0x1_stake_force_update_network_and_fullnode_addresses) - [Function `increase_lockup`](#0x1_stake_increase_lockup) - [Function `increase_lockup_with_cap`](#0x1_stake_increase_lockup_with_cap) - [Function `join_validator_set`](#0x1_stake_join_validator_set) @@ -94,6 +95,8 @@ or if their stake drops below the min required, they would get removed at the en - [Function `is_current_epoch_validator`](#0x1_stake_is_current_epoch_validator) - [Function `update_performance_statistics`](#0x1_stake_update_performance_statistics) - [Function `on_new_epoch`](#0x1_stake_on_new_epoch) +- [Function `cur_validator_set`](#0x1_stake_cur_validator_set) +- [Function `next_validator_set`](#0x1_stake_next_validator_set) - [Function `update_stake_pool`](#0x1_stake_update_stake_pool) - [Function `calculate_rewards_amount`](#0x1_stake_calculate_rewards_amount) - [Function `distribute_rewards`](#0x1_stake_distribute_rewards) @@ -144,6 +147,7 @@ or if their stake drops below the min required, they would get removed at the en use 0x1::aptos_coin; use 0x1::bls12381; use 0x1::coin; +use 0x1::config_for_next_epoch; use 0x1::error; use 0x1::event; use 0x1::features; @@ -432,7 +436,7 @@ Full ValidatorSet, stored in @aptos_framework. 3. on_new_epoch processes two pending queues and refresh ValidatorInfo from the owner's address. -
struct ValidatorSet has key
+
struct ValidatorSet has copy, drop, store, key
 
@@ -1293,6 +1297,16 @@ Validator Config not published. + + +User-level validator set change temporarily disabled. + + +
const EVALIDATOR_SET_CHANGES_DISABLED: u64 = 20;
+
+ + + Validator set exceeds the limit @@ -1847,6 +1861,9 @@ Beyond genesis, no one can create AptosCoin mint/burn capabilities. Allow on chain governance to remove validators from the validator set. +This is a system-level validator set change, therefore not affected by user-level locking. +Caller should ensure a fast reconfiguration is triggered after this. +
public fun remove_validators(aptos_framework: &signer, validators: &vector<address>)
 
@@ -1862,6 +1879,7 @@ Allow on chain governance to remove validators from the validator set. validators: &vector<address>, ) acquires ValidatorSet { system_addresses::assert_aptos_framework(aptos_framework); + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); let validator_set = borrow_global_mut<ValidatorSet>(@aptos_framework); let active_validators = &mut validator_set.active_validators; @@ -2288,6 +2306,7 @@ Add coins into pool_address. this requires the corresp
public fun add_stake_with_cap(owner_cap: &OwnerCapability, coins: Coin<AptosCoin>) acquires StakePool, ValidatorSet {
     let pool_address = owner_cap.pool_address;
     assert_stake_pool_exists(pool_address);
+    assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED));
 
     let amount = coin::value(&coins);
     if (amount == 0) {
@@ -2427,6 +2446,7 @@ Rotate the consensus key of the validator, it'll take effect in next epoch.
     proof_of_possession: vector<u8>,
 ) acquires StakePool, ValidatorConfig {
     assert_stake_pool_exists(pool_address);
+    assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED));
     let stake_pool = borrow_global_mut<StakePool>(pool_address);
     assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
 
@@ -2477,6 +2497,36 @@ Update the network and full node addresses of the validator. This only takes eff
     pool_address: address,
     new_network_addresses: vector<u8>,
     new_fullnode_addresses: vector<u8>,
+) acquires StakePool, ValidatorConfig {
+    assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED));
+    force_update_network_and_fullnode_addresses(operator, pool_address, new_network_addresses, new_fullnode_addresses);
+}
+
+ + + + + + + +## Function `force_update_network_and_fullnode_addresses` + + + +
public(friend) fun force_update_network_and_fullnode_addresses(operator: &signer, pool_address: address, new_network_addresses: vector<u8>, new_fullnode_addresses: vector<u8>)
+
+ + + +
+Implementation + + +
public(friend) fun force_update_network_and_fullnode_addresses(
+    operator: &signer,
+    pool_address: address,
+    new_network_addresses: vector<u8>,
+    new_fullnode_addresses: vector<u8>,
 ) acquires StakePool, ValidatorConfig {
     assert_stake_pool_exists(pool_address);
     let stake_pool = borrow_global_mut<StakePool>(pool_address);
@@ -2635,6 +2685,7 @@ This internal version can only be called by the Genesis module during Genesis.
     operator: &signer,
     pool_address: address
 ) acquires StakePool, ValidatorConfig, ValidatorSet {
+    assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED));
     assert_stake_pool_exists(pool_address);
     let stake_pool = borrow_global_mut<StakePool>(pool_address);
     assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR));
@@ -2799,6 +2850,7 @@ Withdraw from pool_address's inactive stake with the corresponding
     owner_cap: &OwnerCapability,
     withdraw_amount: u64
 ): Coin<AptosCoin> acquires StakePool, ValidatorSet {
+    assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED));
     let pool_address = owner_cap.pool_address;
     assert_stake_pool_exists(pool_address);
     let stake_pool = borrow_global_mut<StakePool>(pool_address);
@@ -2861,6 +2913,7 @@ Can only be called by the operator of the validator/staking pool.
         staking_config::get_allow_validator_set_change(&config),
         error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED),
     );
+    assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED));
 
     assert_stake_pool_exists(pool_address);
     let stake_pool = borrow_global_mut<StakePool>(pool_address);
@@ -3142,6 +3195,126 @@ power.
 
 
 
+
+ + + +## Function `cur_validator_set` + + + +
public(friend) fun cur_validator_set(): stake::ValidatorSet
+
+ + + +
+Implementation + + +
public(friend) fun cur_validator_set(): ValidatorSet acquires ValidatorSet {
+    *borrow_global<ValidatorSet>(@aptos_framework)
+}
+
+ + + +
+ + + +## Function `next_validator_set` + +Compute the validator set for the next epoch. + + +
public(friend) fun next_validator_set(): stake::ValidatorSet
+
+ + + +
+Implementation + + +
public(friend) fun next_validator_set(): ValidatorSet acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet, ValidatorFees {
+    // Init.
+    let cur_validator_set = borrow_global<ValidatorSet>(@aptos_framework);
+    let staking_config = staking_config::get();
+    let validator_perf = borrow_global<ValidatorPerformance>(@aptos_framework);
+    let (minimum_stake, _) = staking_config::get_required_stake(&staking_config);
+    let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(&staking_config);
+
+    // Compute new validator set.
+    let new_active_validators = vector[];
+    let num_new_actives = 0;
+    let candidate_idx = 0;
+    let new_total_power = 0;
+    let num_cur_actives = vector::length(&cur_validator_set.active_validators);
+    let num_cur_pending_actives = vector::length(&cur_validator_set.pending_active);
+    let num_candidates = num_cur_actives + num_cur_pending_actives;
+    while (candidate_idx < num_candidates) {
+        let candidate = if (candidate_idx < num_cur_actives) {
+            vector::borrow(&cur_validator_set.active_validators, candidate_idx)
+        } else {
+            vector::borrow(&cur_validator_set.pending_active, candidate_idx - num_cur_actives)
+        };
+        let stake_pool = borrow_global<StakePool>(candidate.addr);
+        let cur_active = coin::value(&stake_pool.active);
+        let cur_pending_active = coin::value(&stake_pool.pending_active);
+        let cur_pending_inactive = coin::value(&stake_pool.pending_inactive);
+
+        let cur_perf = vector::borrow(&validator_perf.validators, candidate.config.validator_index);
+        let cur_reward = if (cur_active > 0) {
+            calculate_rewards_amount(cur_active, cur_perf.successful_proposals, cur_perf.successful_proposals + cur_perf.failed_proposals, rewards_rate, rewards_rate_denominator)
+        } else {
+            0
+        };
+        let cur_fee = if (features::collect_and_distribute_gas_fees()) {
+            let fees_table = &borrow_global<ValidatorFees>(@aptos_framework).fees_table;
+            if (table::contains(fees_table, candidate.addr)) {
+                let fee = table::borrow(fees_table, candidate.addr);
+                coin::value(fee)
+            } else {
+                0
+            }
+        } else {
+            0
+        };
+
+        let new_voting_power = cur_active + cur_pending_inactive + cur_pending_active + cur_reward + cur_fee;
+
+        if (new_voting_power >= minimum_stake) {
+            let config = *borrow_global<ValidatorConfig>(candidate.addr);
+            config.validator_index = num_new_actives;
+            let new_validator_info = ValidatorInfo {
+                addr: candidate.addr,
+                voting_power: new_voting_power,
+                config,
+            };
+
+            // Update ValidatorSet.
+            new_total_power = new_total_power + (new_voting_power as u128);
+            vector::push_back(&mut new_active_validators, new_validator_info);
+            num_new_actives = num_new_actives + 1;
+
+        };
+        candidate_idx = candidate_idx + 1;
+    };
+
+    ValidatorSet {
+        consensus_scheme: cur_validator_set.consensus_scheme,
+        active_validators: new_active_validators,
+        pending_inactive: vector[],
+        pending_active: vector[],
+        total_voting_power: new_total_power,
+        total_joining_power: 0,
+    }
+}
+
+ + +
@@ -3630,138 +3803,12 @@ Returns validator's next epoch voting power, including pending_active, active, a - - - - -
fun spec_validator_index_upper_bound(): u64 {
-   len(global<ValidatorPerformance>(@aptos_framework).validators)
-}
-
- - - - - - - -
fun spec_has_stake_pool(a: address): bool {
-   exists<StakePool>(a)
-}
-
- - - - - - - -
fun spec_has_validator_config(a: address): bool {
-   exists<ValidatorConfig>(a)
-}
-
- - - - - - - -
fun spec_rewards_amount(
-   stake_amount: u64,
-   num_successful_proposals: u64,
-   num_total_proposals: u64,
-   rewards_rate: u64,
-   rewards_rate_denominator: u64,
-): u64;
-
- - - - - - - -
fun spec_contains(validators: vector<ValidatorInfo>, addr: address): bool {
-   exists i in 0..len(validators): validators[i].addr == addr
-}
-
- - - - - - - -
fun spec_is_current_epoch_validator(pool_address: address): bool {
-   let validator_set = global<ValidatorSet>(@aptos_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))
-}
-
- - - - - - - -
fun spec_get_reward_rate_1(config: StakingConfig): num {
-   if (features::spec_periodical_reward_rate_decrease_enabled()) {
-       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
-       if (epoch_rewards_rate.value == 0) {
-           0
-       } else {
-           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
-           let denominator = if (denominator_0 > MAX_U64) {
-               MAX_U64
-           } else {
-               denominator_0
-           };
-           let nominator = aptos_std::fixed_point64::spec_multiply_u128(denominator, epoch_rewards_rate);
-           nominator
-       }
-   } else {
-           config.rewards_rate
-   }
-}
-
- - - - - - - -
fun spec_get_reward_rate_2(config: StakingConfig): num {
-   if (features::spec_periodical_reward_rate_decrease_enabled()) {
-       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
-       if (epoch_rewards_rate.value == 0) {
-           1
-       } else {
-           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
-           let denominator = if (denominator_0 > MAX_U64) {
-               MAX_U64
-           } else {
-               denominator_0
-           };
-           denominator
-       }
-   } else {
-           config.rewards_rate_denominator
-   }
-}
-
- - - ### Resource `ValidatorSet` -
struct ValidatorSet has key
+
struct ValidatorSet has copy, drop, store, key
 
@@ -4638,4 +4685,147 @@ Returns validator's next epoch voting power, including pending_active, active, a
+ + + + + +
fun spec_validator_index_upper_bound(): u64 {
+   len(global<ValidatorPerformance>(@aptos_framework).validators)
+}
+
+ + + + + + + +
fun spec_has_stake_pool(a: address): bool {
+   exists<StakePool>(a)
+}
+
+ + + + + + + +
fun spec_has_validator_config(a: address): bool {
+   exists<ValidatorConfig>(a)
+}
+
+ + + + + + + +
fun spec_rewards_amount(
+   stake_amount: u64,
+   num_successful_proposals: u64,
+   num_total_proposals: u64,
+   rewards_rate: u64,
+   rewards_rate_denominator: u64,
+): u64;
+
+ + + + + + + +
fun spec_contains(validators: vector<ValidatorInfo>, addr: address): bool {
+   exists i in 0..len(validators): validators[i].addr == addr
+}
+
+ + + + + + + +
fun spec_is_current_epoch_validator(pool_address: address): bool {
+   let validator_set = global<ValidatorSet>(@aptos_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))
+}
+
+ + + + + + + +
schema ResourceRequirement {
+    requires exists<AptosCoinCapabilities>(@aptos_framework);
+    requires exists<ValidatorPerformance>(@aptos_framework);
+    requires exists<ValidatorSet>(@aptos_framework);
+    requires exists<StakingConfig>(@aptos_framework);
+    requires exists<StakingRewardsConfig>(@aptos_framework) || !features::spec_periodical_reward_rate_decrease_enabled();
+    requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+    requires exists<ValidatorFees>(@aptos_framework);
+}
+
+ + + + + + + +
fun spec_get_reward_rate_1(config: StakingConfig): num {
+   if (features::spec_periodical_reward_rate_decrease_enabled()) {
+       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
+       if (epoch_rewards_rate.value == 0) {
+           0
+       } else {
+           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
+           let denominator = if (denominator_0 > MAX_U64) {
+               MAX_U64
+           } else {
+               denominator_0
+           };
+           let nominator = aptos_std::fixed_point64::spec_multiply_u128(denominator, epoch_rewards_rate);
+           nominator
+       }
+   } else {
+           config.rewards_rate
+   }
+}
+
+ + + + + + + +
fun spec_get_reward_rate_2(config: StakingConfig): num {
+   if (features::spec_periodical_reward_rate_decrease_enabled()) {
+       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
+       if (epoch_rewards_rate.value == 0) {
+           1
+       } else {
+           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
+           let denominator = if (denominator_0 > MAX_U64) {
+               MAX_U64
+           } else {
+               denominator_0
+           };
+           denominator
+       }
+   } else {
+           config.rewards_rate_denominator
+   }
+}
+
+ + [move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/aptos-framework/doc/version.md b/aptos-move/framework/aptos-framework/doc/version.md index 25b68cd12ea8a..ce127f1d409a5 100644 --- a/aptos-move/framework/aptos-framework/doc/version.md +++ b/aptos-move/framework/aptos-framework/doc/version.md @@ -11,6 +11,8 @@ Maintains the version number for the blockchain. - [Constants](#@Constants_0) - [Function `initialize`](#0x1_version_initialize) - [Function `set_version`](#0x1_version_set_version) +- [Function `set_for_next_epoch`](#0x1_version_set_for_next_epoch) +- [Function `on_new_epoch`](#0x1_version_on_new_epoch) - [Function `initialize_for_test`](#0x1_version_initialize_for_test) - [Specification](#@Specification_1) - [Function `initialize`](#@Specification_1_initialize) @@ -18,7 +20,8 @@ Maintains the version number for the blockchain. - [Function `initialize_for_test`](#@Specification_1_initialize_for_test) -
use 0x1::error;
+
use 0x1::config_for_next_epoch;
+use 0x1::error;
 use 0x1::reconfiguration;
 use 0x1::signer;
 use 0x1::system_addresses;
@@ -32,7 +35,7 @@ Maintains the version number for the blockchain.
 
 
 
-
struct Version has key
+
struct Version has drop, store, key
 
@@ -169,6 +172,61 @@ This can be called by on chain governance. + + + + +## Function `set_for_next_epoch` + +Updates the major version to a larger version. +This can be called by on chain governance. + + +
public entry fun set_for_next_epoch(account: &signer, major: u64)
+
+ + + +
+Implementation + + +
public entry fun set_for_next_epoch(account: &signer, major: u64) acquires Version {
+    assert!(exists<SetVersionCapability>(signer::address_of(account)), error::permission_denied(ENOT_AUTHORIZED));
+    let old_major = borrow_global<Version>(@aptos_framework).major;
+    assert!(old_major < major, error::invalid_argument(EINVALID_MAJOR_VERSION_NUMBER));
+    config_for_next_epoch::upsert(account, Version {major});
+}
+
+ + + +
+ + + +## Function `on_new_epoch` + + + +
public(friend) fun on_new_epoch(account: &signer)
+
+ + + +
+Implementation + + +
public(friend) fun on_new_epoch(account: &signer) acquires Version {
+    if (config_for_next_epoch::does_exist<Version>()) {
+        *borrow_global_mut<Version>(@aptos_framework) = config_for_next_epoch::extract<Version>(account);
+    }
+}
+
+ + +
@@ -244,7 +302,6 @@ Abort if resource already exists in @aptos_framwork when initializi include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply; include staking_config::StakingRewardsConfigRequirement; requires chain_status::is_operating(); -requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time(); requires exists<stake::ValidatorFees>(@aptos_framework); requires exists<CoinInfo<AptosCoin>>(@aptos_framework); aborts_if !exists<SetVersionCapability>(signer::address_of(account)); diff --git a/aptos-move/framework/aptos-framework/doc/vesting.md b/aptos-move/framework/aptos-framework/doc/vesting.md index a07f0552649d4..9593390c4041b 100644 --- a/aptos-move/framework/aptos-framework/doc/vesting.md +++ b/aptos-move/framework/aptos-framework/doc/vesting.md @@ -931,6 +931,16 @@ withdrawable, admin can call admin_withdraw to withdraw all funds to the vesting ## Constants + + +Account is not admin or does not have the required role to take this action. + + +
const EPERMISSION_DENIED: u64 = 15;
+
+ + + Vesting schedule cannot be empty. @@ -981,16 +991,6 @@ Cannot terminate the vesting contract with pending active stake. Need to wait un - - -Account is not admin or does not have the required role to take this action. - - -
const EPERMISSION_DENIED: u64 = 15;
-
- - - The vesting account has no such management role. diff --git a/aptos-move/framework/aptos-framework/sources/aptos_governance.move b/aptos-move/framework/aptos-framework/sources/aptos_governance.move index ea8c24c4ac8e1..4609f259594fc 100644 --- a/aptos-move/framework/aptos-framework/sources/aptos_governance.move +++ b/aptos-move/framework/aptos-framework/sources/aptos_governance.move @@ -31,6 +31,7 @@ module aptos_framework::aptos_governance { use aptos_framework::staking_config; use aptos_framework::system_addresses; use aptos_framework::aptos_coin::{Self, AptosCoin}; + use aptos_framework::reconfiguration_v2; use aptos_framework::timestamp; use aptos_framework::voting; @@ -539,14 +540,23 @@ module aptos_framework::aptos_governance { /// Force reconfigure. To be called at the end of a proposal that alters on-chain configs. public fun reconfigure(aptos_framework: &signer) { system_addresses::assert_aptos_framework(aptos_framework); - reconfiguration::reconfigure(); + if (features::reconfigure_with_dkg_enabled()) { + reconfiguration_v2::start(aptos_framework); + } else { + reconfiguration::reconfigure(); + } } /// Update feature flags and also trigger reconfiguration. public fun toggle_features(aptos_framework: &signer, enable: vector, disable: vector) { system_addresses::assert_aptos_framework(aptos_framework); features::change_feature_flags(aptos_framework, enable, disable); - reconfiguration::reconfigure(); + + if (features::reconfigure_with_dkg_enabled()) { + reconfiguration_v2::start(aptos_framework); + } else { + reconfiguration::reconfigure(); + } } /// Only called in testnet where the core resources account exists and has been granted power to mint Aptos coins. diff --git a/aptos-move/framework/aptos-framework/sources/block.move b/aptos-move/framework/aptos-framework/sources/block.move index ad5bec4503234..082ddc3c56d9f 100644 --- a/aptos-move/framework/aptos-framework/sources/block.move +++ b/aptos-move/framework/aptos-framework/sources/block.move @@ -6,8 +6,10 @@ module aptos_framework::block { use std::option; use aptos_framework::account; + use aptos_framework::dkg; use aptos_framework::event::{Self, EventHandle}; use aptos_framework::reconfiguration; + use aptos_framework::reconfiguration_v2; use aptos_framework::stake; use aptos_framework::state_storage; use aptos_framework::system_addresses; @@ -156,6 +158,74 @@ module aptos_framework::block { }; } + + /// Set the metadata for the current block. + /// The runtime always runs this before executing the transactions in a block. + fun block_prologue_v2( + vm: signer, + hash: address, + epoch: u64, + round: u64, + proposer: address, + failed_proposer_indices: vector, + previous_block_votes_bitvec: vector, + timestamp: u64, + dkg_result_available: bool, + dkg_result: vector, + ) acquires BlockResource { + // Operational constraint: can only be invoked by the VM. + system_addresses::assert_vm(&vm); + + // Blocks can only be produced by a valid proposer or by the VM itself for Nil blocks (no user txs). + assert!( + proposer == @vm_reserved || stake::is_current_epoch_validator(proposer), + error::permission_denied(EINVALID_PROPOSER), + ); + + let proposer_index = option::none(); + if (proposer != @vm_reserved) { + proposer_index = option::some(stake::get_validator_index(proposer)); + }; + + let block_metadata_ref = borrow_global_mut(@aptos_framework); + block_metadata_ref.height = event::counter(&block_metadata_ref.new_block_events); + + let new_block_event = NewBlockEvent { + hash, + epoch, + round, + height: block_metadata_ref.height, + previous_block_votes_bitvec, + proposer, + failed_proposer_indices, + time_microseconds: timestamp, + }; + emit_new_block_event(&vm, &mut block_metadata_ref.new_block_events, new_block_event); + + if (features::collect_and_distribute_gas_fees()) { + // Assign the fees collected from the previous block to the previous block proposer. + // If for any reason the fees cannot be assigned, this function burns the collected coins. + transaction_fee::process_collected_fees(); + // Set the proposer of this block as the receiver of the fees, so that the fees for this + // block are assigned to the right account. + transaction_fee::register_proposer_for_fee_collection(proposer); + }; + + // Performance scores have to be updated before the epoch transition as the transaction that triggers the + // transition is the last block in the previous epoch. + stake::update_performance_statistics(proposer_index, failed_proposer_indices); + state_storage::on_new_block(reconfiguration::current_epoch()); + + if (dkg::in_progress()) { + let should_proceed = dkg::update(dkg_result_available, dkg_result); + if (should_proceed) { + reconfiguration_v2::finish(&vm); + } + } else if (timestamp - reconfiguration::last_reconfiguration_time() >= block_metadata_ref.epoch_interval) { + reconfiguration_v2::start(&vm); + }; + } + #[view] /// Get the current block height public fun get_current_block_height(): u64 acquires BlockResource { diff --git a/aptos-move/framework/aptos-framework/sources/configs/consensus_config.move b/aptos-move/framework/aptos-framework/sources/configs/consensus_config.move index f75410f6ab85a..1a8318212b3e4 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/consensus_config.move +++ b/aptos-move/framework/aptos-framework/sources/configs/consensus_config.move @@ -1,15 +1,16 @@ /// Maintains the consensus config for the blockchain. The config is stored in a /// Reconfiguration, and may be updated by root. module aptos_framework::consensus_config { + use std::config_for_next_epoch; use std::error; use std::vector; - use aptos_framework::reconfiguration; use aptos_framework::system_addresses; friend aptos_framework::genesis; + friend aptos_framework::reconfiguration_v2; - struct ConsensusConfig has key { + struct ConsensusConfig has drop, key, store { config: vector, } @@ -34,4 +35,16 @@ module aptos_framework::consensus_config { // Need to trigger reconfiguration so validator nodes can sync on the updated configs. reconfiguration::reconfigure(); } + + public fun set_for_next_epoch(account: &signer, config: vector) { + system_addresses::assert_aptos_framework(account); + assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG)); + std::config_for_next_epoch::upsert(account, ConsensusConfig {config}); + } + + public(friend) fun on_new_epoch(account: &signer) acquires ConsensusConfig { + if (config_for_next_epoch::does_exist()) { + *borrow_global_mut(@aptos_framework) = std::config_for_next_epoch::extract(account); + } + } } diff --git a/aptos-move/framework/aptos-framework/sources/configs/consensus_config.spec.move b/aptos-move/framework/aptos-framework/sources/configs/consensus_config.spec.move index ae4549993865d..95251380cf8ed 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/consensus_config.spec.move +++ b/aptos-move/framework/aptos-framework/sources/configs/consensus_config.spec.move @@ -18,7 +18,6 @@ spec aptos_framework::consensus_config { /// When setting now time must be later than last_reconfiguration_time. spec set(account: &signer, config: vector) { use aptos_framework::chain_status; - use aptos_framework::timestamp; use std::signer; use aptos_framework::stake; use aptos_framework::coin::CoinInfo; @@ -36,7 +35,6 @@ spec aptos_framework::consensus_config { aborts_if !(len(config) > 0); requires chain_status::is_operating(); - requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time(); requires exists(@aptos_framework); requires exists>(@aptos_framework); } diff --git a/aptos-move/framework/aptos-framework/sources/configs/execution_config.move b/aptos-move/framework/aptos-framework/sources/configs/execution_config.move index 54103c7f8b32b..db3d944eb5d00 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/execution_config.move +++ b/aptos-move/framework/aptos-framework/sources/configs/execution_config.move @@ -1,15 +1,17 @@ /// Maintains the execution config for the blockchain. The config is stored in a /// Reconfiguration, and may be updated by root. module aptos_framework::execution_config { + use std::config_for_next_epoch; use std::error; use std::vector; - use aptos_framework::reconfiguration; + use aptos_framework::system_addresses; friend aptos_framework::genesis; + friend aptos_framework::reconfiguration_v2; - struct ExecutionConfig has key { + struct ExecutionConfig has drop, key, store { config: vector, } @@ -30,4 +32,19 @@ module aptos_framework::execution_config { // Need to trigger reconfiguration so validator nodes can sync on the updated configs. reconfiguration::reconfigure(); } + + /// This can be called by on-chain governance to update on-chain execution configs. + public fun set_for_next_epoch(account: &signer, config: vector) { + system_addresses::assert_aptos_framework(account); + assert!(vector::length(&config) > 0, error::invalid_argument(EINVALID_CONFIG)); + config_for_next_epoch::upsert(account, ExecutionConfig { config }); + } + + /// Only used in reconfiguration with DKG. + public(friend) fun on_new_epoch(account: &signer) acquires ExecutionConfig { + if (config_for_next_epoch::does_exist()) { + let config = config_for_next_epoch::extract(account); + *borrow_global_mut(@aptos_framework) = config; + } + } } diff --git a/aptos-move/framework/aptos-framework/sources/configs/execution_config.spec.move b/aptos-move/framework/aptos-framework/sources/configs/execution_config.spec.move index f5f6ed8694a1f..bbe26d4efc7aa 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/execution_config.spec.move +++ b/aptos-move/framework/aptos-framework/sources/configs/execution_config.spec.move @@ -7,7 +7,6 @@ spec aptos_framework::execution_config { /// Ensure the caller is admin /// When setting now time must be later than last_reconfiguration_time. spec set(account: &signer, config: vector) { - use aptos_framework::timestamp; use std::signer; use std::features; use aptos_framework::transaction_fee; @@ -26,6 +25,5 @@ spec aptos_framework::execution_config { include features::spec_periodical_reward_rate_decrease_enabled() ==> staking_config::StakingRewardsConfigEnabledRequirement; include features::spec_collect_and_distribute_gas_fees_enabled() ==> aptos_coin::ExistsAptosCoin; requires system_addresses::is_aptos_framework_address(addr); - requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time(); } } diff --git a/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move b/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move index 36be0caf9c311..2510b3f482e86 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move +++ b/aptos-move/framework/aptos-framework/sources/configs/gas_schedule.move @@ -4,14 +4,16 @@ module aptos_framework::gas_schedule { use std::error; use std::string::String; use std::vector; - + use aptos_framework::config_for_next_epoch; use aptos_framework::reconfiguration; + use aptos_framework::system_addresses; use aptos_framework::util::from_bytes; use aptos_framework::storage_gas::StorageGasConfig; use aptos_framework::storage_gas; friend aptos_framework::genesis; + friend aptos_framework::reconfiguration_v2; /// The provided gas schedule bytes are empty or invalid const EINVALID_GAS_SCHEDULE: u64 = 1; @@ -26,7 +28,7 @@ module aptos_framework::gas_schedule { entries: vector } - struct GasScheduleV2 has key, copy, drop { + struct GasScheduleV2 has key, store, copy, drop { feature_version: u64, entries: vector, } @@ -67,10 +69,23 @@ module aptos_framework::gas_schedule { reconfiguration::reconfigure(); } + /// This can be called by on-chain governance to update the gas schedule. + public fun set_for_next_epoch(aptos_framework: &signer, gas_schedule_blob: vector) { + system_addresses::assert_aptos_framework(aptos_framework); + assert!(!vector::is_empty(&gas_schedule_blob), error::invalid_argument(EINVALID_GAS_SCHEDULE)); + let new_gas_schedule: GasScheduleV2 = from_bytes(gas_schedule_blob); + config_for_next_epoch::upsert(aptos_framework, new_gas_schedule); + } + + public(friend) fun on_new_epoch(account: &signer) acquires GasScheduleV2 { + if (config_for_next_epoch::does_exist()) { + let new_gas_schedule: GasScheduleV2 = config_for_next_epoch::extract(account); + let gas_schedule = borrow_global_mut(@aptos_framework); + *gas_schedule = new_gas_schedule; + } + } + public fun set_storage_gas_config(aptos_framework: &signer, config: StorageGasConfig) { storage_gas::set_config(aptos_framework, config); - // Need to trigger reconfiguration so the VM is guaranteed to load the new gas fee starting from the next - // transaction. - reconfiguration::reconfigure(); } } diff --git a/aptos-move/framework/aptos-framework/sources/configs/version.move b/aptos-move/framework/aptos-framework/sources/configs/version.move index 47b16921d9556..43cd1eda078a2 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/version.move +++ b/aptos-move/framework/aptos-framework/sources/configs/version.move @@ -1,14 +1,16 @@ /// Maintains the version number for the blockchain. module aptos_framework::version { + use std::config_for_next_epoch; use std::error; use std::signer; - use aptos_framework::reconfiguration; + use aptos_framework::system_addresses; friend aptos_framework::genesis; + friend aptos_framework::reconfiguration_v2; - struct Version has key { + struct Version has drop, key, store { major: u64, } @@ -45,6 +47,21 @@ module aptos_framework::version { reconfiguration::reconfigure(); } + /// Updates the major version to a larger version. + /// This can be called by on chain governance. + public entry fun set_for_next_epoch(account: &signer, major: u64) acquires Version { + assert!(exists(signer::address_of(account)), error::permission_denied(ENOT_AUTHORIZED)); + let old_major = borrow_global(@aptos_framework).major; + assert!(old_major < major, error::invalid_argument(EINVALID_MAJOR_VERSION_NUMBER)); + config_for_next_epoch::upsert(account, Version {major}); + } + + public(friend) fun on_new_epoch(account: &signer) acquires Version { + if (config_for_next_epoch::does_exist()) { + *borrow_global_mut(@aptos_framework) = config_for_next_epoch::extract(account); + } + } + /// Only called in tests and testnets. This allows the core resources account, which only exists in tests/testnets, /// to update the version. fun initialize_for_test(core_resources: &signer) { diff --git a/aptos-move/framework/aptos-framework/sources/configs/version.spec.move b/aptos-move/framework/aptos-framework/sources/configs/version.spec.move index 1a514c1963299..3af2eae08b24a 100644 --- a/aptos-move/framework/aptos-framework/sources/configs/version.spec.move +++ b/aptos-move/framework/aptos-framework/sources/configs/version.spec.move @@ -7,7 +7,6 @@ spec aptos_framework::version { spec set_version(account: &signer, major: u64) { use std::signer; use aptos_framework::chain_status; - use aptos_framework::timestamp; use aptos_framework::stake; use aptos_framework::coin::CoinInfo; use aptos_framework::aptos_coin::AptosCoin; @@ -18,7 +17,6 @@ spec aptos_framework::version { include transaction_fee::RequiresCollectedFeesPerValueLeqBlockAptosSupply; include staking_config::StakingRewardsConfigRequirement; requires chain_status::is_operating(); - requires timestamp::spec_now_microseconds() >= reconfiguration::last_reconfiguration_time(); requires exists(@aptos_framework); requires exists>(@aptos_framework); diff --git a/aptos-move/framework/aptos-framework/sources/dkg.move b/aptos-move/framework/aptos-framework/sources/dkg.move new file mode 100644 index 0000000000000..3e182ab5b0a99 --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/dkg.move @@ -0,0 +1,89 @@ +module aptos_framework::dkg { + use std::error; + use std::option; + use std::option::Option; + use aptos_framework::event; + use aptos_framework::stake::ValidatorSet; + use aptos_framework::timestamp; + friend aptos_framework::reconfiguration_v2; + friend aptos_framework::block; + + /// Another reconfiguration is in progress. + const EANOTHER_RECONFIGURATION_IN_PROGRESS: u64 = 1; + /// There is no reconfiguration in progress. + const ENO_RECONFIGURATION_IN_PROGRESS: u64 = 2; + + struct StartDKGEvent has drop, store { + target_epoch: u64, + target_validator_set: ValidatorSet, + } + + /// The input and output of a DKG session. + /// The validator set of epoch `x` works together and outputs a transcript for the target validator set of epoch `y` (typically `x+1`). + struct DKGSessionState has copy, store, drop { + dealer_epoch: u64, + dealer_validator_set: ValidatorSet, + target_epoch: u64, + target_validator_set: ValidatorSet, + result: vector, + deadline_microseconds: u64, + } + + /// The complete and ongoing DKG sessions. + struct DKGState has key { + last_complete: Option, + in_progress: Option, + events: event::EventHandle, + } + + public(friend) fun start(dealer_epoch: u64, dealer_validator_set: ValidatorSet, target_epoch: u64, target_validator_set: ValidatorSet) acquires DKGState { + let dkg_state = borrow_global_mut(@aptos_framework); + assert!(std::option::is_none(&dkg_state.in_progress), 1); + dkg_state.in_progress = std::option::some(DKGSessionState { + dealer_epoch, + dealer_validator_set, + target_epoch, + target_validator_set, + deadline_microseconds: timestamp::now_microseconds() + 60000000, + result: vector[], + }); + event::emit_event( + &mut dkg_state.events, + StartDKGEvent { + target_epoch, + target_validator_set, + }, + ); + } + + /// Update the current DKG state with a potential transcript. + /// Return true if the current DKG becomes inactive and we should start a new epoch. + /// Abort if no DKG is in progress. + public(friend) fun update(dkg_result_available: bool, dkg_result: vector): bool acquires DKGState { + let dkg_state = borrow_global_mut(@aptos_framework); + assert!(option::is_some(&dkg_state.in_progress), error::invalid_state(ENO_RECONFIGURATION_IN_PROGRESS)); + let session = option::extract(&mut dkg_state.in_progress); + let dkg_completed = false; + if (dkg_result_available) { + session.result = dkg_result; + dkg_completed = true; + }; + if (timestamp::now_microseconds() >= session.deadline_microseconds || dkg_completed) { + dkg_state.last_complete = option::some(session); + dkg_state.in_progress = option::none(); + true + } else { + dkg_state.in_progress = option::some(session); + false + } + } + + public(friend) fun in_progress(): bool acquires DKGState { + option::is_some(&borrow_global(@aptos_framework).in_progress) + } + + public(friend) fun current_deadline(): u64 acquires DKGState { + let in_progress_session = option::borrow(&borrow_global(@aptos_framework).in_progress); + in_progress_session.deadline_microseconds + } +} diff --git a/aptos-move/framework/aptos-framework/sources/genesis.move b/aptos-move/framework/aptos-framework/sources/genesis.move index e30268df92b4a..57b44f4c541cc 100644 --- a/aptos-move/framework/aptos-framework/sources/genesis.move +++ b/aptos-move/framework/aptos-framework/sources/genesis.move @@ -365,7 +365,7 @@ module aptos_framework::genesis { validator.consensus_pubkey, validator.proof_of_possession, ); - stake::update_network_and_fullnode_addresses( + stake::force_update_network_and_fullnode_addresses( operator, pool_address, validator.network_addresses, diff --git a/aptos-move/framework/aptos-framework/sources/reconfiguration.move b/aptos-move/framework/aptos-framework/sources/reconfiguration.move index deec0364228c8..1f50d54719918 100644 --- a/aptos-move/framework/aptos-framework/sources/reconfiguration.move +++ b/aptos-move/framework/aptos-framework/sources/reconfiguration.move @@ -20,6 +20,7 @@ module aptos_framework::reconfiguration { friend aptos_framework::execution_config; friend aptos_framework::gas_schedule; friend aptos_framework::genesis; + friend aptos_framework::reconfiguration_v2; friend aptos_framework::version; /// Event that signals consensus to start a new epoch, @@ -151,6 +152,7 @@ module aptos_framework::reconfiguration { ); } + public fun last_reconfiguration_time(): u64 acquires Configuration { borrow_global(@aptos_framework).last_reconfiguration_time } diff --git a/aptos-move/framework/aptos-framework/sources/reconfiguration_v2.move b/aptos-move/framework/aptos-framework/sources/reconfiguration_v2.move new file mode 100644 index 0000000000000..b28bc7f85cf95 --- /dev/null +++ b/aptos-move/framework/aptos-framework/sources/reconfiguration_v2.move @@ -0,0 +1,31 @@ +module aptos_framework::reconfiguration_v2 { + use std::config_for_next_epoch; + use std::features; + use aptos_framework::consensus_config; + use aptos_framework::dkg; + use aptos_framework::execution_config; + use aptos_framework::gas_schedule; + use aptos_framework::reconfiguration; + use aptos_framework::stake; + friend aptos_framework::block; + friend aptos_framework::aptos_governance; + + public(friend) fun start(account: &signer) { + config_for_next_epoch::disable_upserts(account); + let cur_epoch = reconfiguration::current_epoch(); + dkg::start(cur_epoch, stake::cur_validator_set(), cur_epoch + 1, stake::next_validator_set()); + } + + /// Apply buffered on-chain configs. + /// Re-enable on-chain config changes. + /// Trigger the default reconfiguration. + public(friend) fun finish(account: &signer) { + features::on_new_epoch(account); + consensus_config::on_new_epoch(account); + execution_config::on_new_epoch(account); + gas_schedule::on_new_epoch(account); + std::version::on_new_epoch(account); + config_for_next_epoch::enable_upserts(account); + reconfiguration::reconfigure(); + } +} diff --git a/aptos-move/framework/aptos-framework/sources/stake.move b/aptos-move/framework/aptos-framework/sources/stake.move index 2368472f33b40..3a1449050bbfd 100644 --- a/aptos-move/framework/aptos-framework/sources/stake.move +++ b/aptos-move/framework/aptos-framework/sources/stake.move @@ -18,6 +18,7 @@ /// 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 aptos_framework::stake { + use std::config_for_next_epoch; use std::error; use std::features; use std::option::{Self, Option}; @@ -38,6 +39,7 @@ module aptos_framework::stake { friend aptos_framework::block; friend aptos_framework::genesis; friend aptos_framework::reconfiguration; + friend aptos_framework::reconfiguration_v2; friend aptos_framework::transaction_fee; /// Validator Config not published. @@ -78,6 +80,8 @@ module aptos_framework::stake { const EINVALID_LOCKUP: u64 = 18; /// Table to store collected transaction fees for each validator already exists. const EFEES_TABLE_ALREADY_EXISTS: u64 = 19; + /// User-level validator set change temporarily disabled. + const EVALIDATOR_SET_CHANGES_DISABLED: u64 = 20; /// Validator status enum. We can switch to proper enum later once Move supports it. const VALIDATOR_STATUS_PENDING_ACTIVE: u64 = 1; @@ -168,7 +172,7 @@ module aptos_framework::stake { /// 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 { + struct ValidatorSet has copy, drop, key, store { consensus_scheme: u8, // Active validators for the current epoch. active_validators: vector, @@ -427,11 +431,15 @@ module aptos_framework::stake { } /// Allow on chain governance to remove validators from the validator set. + /// + /// This is a system-level validator set change, therefore not affected by user-level locking. + /// Caller should ensure a fast reconfiguration is triggered after this. public fun remove_validators( aptos_framework: &signer, validators: &vector
, ) acquires ValidatorSet { system_addresses::assert_aptos_framework(aptos_framework); + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); let validator_set = borrow_global_mut(@aptos_framework); let active_validators = &mut validator_set.active_validators; @@ -618,6 +626,7 @@ module aptos_framework::stake { 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); + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); let amount = coin::value(&coins); if (amount == 0) { @@ -697,6 +706,7 @@ module aptos_framework::stake { proof_of_possession: vector, ) acquires StakePool, ValidatorConfig { assert_stake_pool_exists(pool_address); + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); let stake_pool = borrow_global_mut(pool_address); assert!(signer::address_of(operator) == stake_pool.operator_address, error::unauthenticated(ENOT_OPERATOR)); @@ -727,6 +737,16 @@ module aptos_framework::stake { pool_address: address, new_network_addresses: vector, new_fullnode_addresses: vector, + ) acquires StakePool, ValidatorConfig { + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); + force_update_network_and_fullnode_addresses(operator, pool_address, new_network_addresses, new_fullnode_addresses); + } + + public(friend) fun force_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); @@ -805,6 +825,7 @@ module aptos_framework::stake { operator: &signer, pool_address: address ) acquires StakePool, ValidatorConfig, ValidatorSet { + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); 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)); @@ -889,6 +910,7 @@ module aptos_framework::stake { owner_cap: &OwnerCapability, withdraw_amount: u64 ): Coin acquires StakePool, ValidatorSet { + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); let pool_address = owner_cap.pool_address; assert_stake_pool_exists(pool_address); let stake_pool = borrow_global_mut(pool_address); @@ -931,6 +953,7 @@ module aptos_framework::stake { staking_config::get_allow_validator_set_change(&config), error::invalid_argument(ENO_POST_GENESIS_VALIDATOR_SET_CHANGE_ALLOWED), ); + assert!(config_for_next_epoch::upserts_enabled(), error::invalid_state(EVALIDATOR_SET_CHANGES_DISABLED)); assert_stake_pool_exists(pool_address); let stake_pool = borrow_global_mut(pool_address); @@ -1149,6 +1172,86 @@ module aptos_framework::stake { }; } + public(friend) fun cur_validator_set(): ValidatorSet acquires ValidatorSet { + *borrow_global(@aptos_framework) + } + + /// Compute the validator set for the next epoch. + public(friend) fun next_validator_set(): ValidatorSet acquires StakePool, ValidatorConfig, ValidatorPerformance, ValidatorSet, ValidatorFees { + // Init. + let cur_validator_set = borrow_global(@aptos_framework); + let staking_config = staking_config::get(); + let validator_perf = borrow_global(@aptos_framework); + let (minimum_stake, _) = staking_config::get_required_stake(&staking_config); + let (rewards_rate, rewards_rate_denominator) = staking_config::get_reward_rate(&staking_config); + + // Compute new validator set. + let new_active_validators = vector[]; + let num_new_actives = 0; + let candidate_idx = 0; + let new_total_power = 0; + let num_cur_actives = vector::length(&cur_validator_set.active_validators); + let num_cur_pending_actives = vector::length(&cur_validator_set.pending_active); + let num_candidates = num_cur_actives + num_cur_pending_actives; + while (candidate_idx < num_candidates) { + let candidate = if (candidate_idx < num_cur_actives) { + vector::borrow(&cur_validator_set.active_validators, candidate_idx) + } else { + vector::borrow(&cur_validator_set.pending_active, candidate_idx - num_cur_actives) + }; + let stake_pool = borrow_global(candidate.addr); + let cur_active = coin::value(&stake_pool.active); + let cur_pending_active = coin::value(&stake_pool.pending_active); + let cur_pending_inactive = coin::value(&stake_pool.pending_inactive); + + let cur_perf = vector::borrow(&validator_perf.validators, candidate.config.validator_index); + let cur_reward = if (cur_active > 0) { + calculate_rewards_amount(cur_active, cur_perf.successful_proposals, cur_perf.successful_proposals + cur_perf.failed_proposals, rewards_rate, rewards_rate_denominator) + } else { + 0 + }; + let cur_fee = if (features::collect_and_distribute_gas_fees()) { + let fees_table = &borrow_global(@aptos_framework).fees_table; + if (table::contains(fees_table, candidate.addr)) { + let fee = table::borrow(fees_table, candidate.addr); + coin::value(fee) + } else { + 0 + } + } else { + 0 + }; + + let new_voting_power = cur_active + cur_pending_inactive + cur_pending_active + cur_reward + cur_fee; + + if (new_voting_power >= minimum_stake) { + let config = *borrow_global(candidate.addr); + config.validator_index = num_new_actives; + let new_validator_info = ValidatorInfo { + addr: candidate.addr, + voting_power: new_voting_power, + config, + }; + + // Update ValidatorSet. + new_total_power = new_total_power + (new_voting_power as u128); + vector::push_back(&mut new_active_validators, new_validator_info); + num_new_actives = num_new_actives + 1; + + }; + candidate_idx = candidate_idx + 1; + }; + + ValidatorSet { + consensus_scheme: cur_validator_set.consensus_scheme, + active_validators: new_active_validators, + pending_inactive: vector[], + pending_active: vector[], + total_voting_power: new_total_power, + total_joining_power: 0, + } + } + /// Update individual validator's stake pool /// 1. distribute transaction fees to active/pending_inactive delegations /// 2. distribute rewards to active/pending_inactive delegations diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs index e76c4b215a106..b87b69d7bca61 100644 --- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs +++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs @@ -775,6 +775,12 @@ pub enum EntryFunctionCall { new_voter: AccountAddress, }, + /// Updates the major version to a larger version. + /// This can be called by on chain governance. + VersionSetForNextEpoch { + major: u64, + }, + /// Updates the major version to a larger version. /// This can be called by on chain governance. VersionSetVersion { @@ -1344,6 +1350,7 @@ impl EntryFunctionCall { operator, new_voter, } => staking_proxy_set_voter(operator, new_voter), + VersionSetForNextEpoch { major } => version_set_for_next_epoch(major), VersionSetVersion { major } => version_set_version(major), VestingAdminWithdraw { contract_address } => vesting_admin_withdraw(contract_address), VestingDistribute { contract_address } => vesting_distribute(contract_address), @@ -3581,6 +3588,23 @@ pub fn staking_proxy_set_voter( )) } +/// Updates the major version to a larger version. +/// This can be called by on chain governance. +pub fn version_set_for_next_epoch(major: u64) -> 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!("version").to_owned(), + ), + ident_str!("set_for_next_epoch").to_owned(), + vec![], + vec![bcs::to_bytes(&major).unwrap()], + )) +} + /// Updates the major version to a larger version. /// This can be called by on chain governance. pub fn version_set_version(major: u64) -> TransactionPayload { @@ -5159,6 +5183,16 @@ mod decoder { } } + pub fn version_set_for_next_epoch(payload: &TransactionPayload) -> Option { + if let TransactionPayload::EntryFunction(script) = payload { + Some(EntryFunctionCall::VersionSetForNextEpoch { + major: bcs::from_bytes(script.args().get(0)?).ok()?, + }) + } else { + None + } + } + pub fn version_set_version(payload: &TransactionPayload) -> Option { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::VersionSetVersion { @@ -5760,6 +5794,10 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy + +# Module `0x1::config_for_next_epoch` + +This wrapper helps store an on-chain config for the next epoch. + + +- [Resource `ForNextEpoch`](#0x1_config_for_next_epoch_ForNextEpoch) +- [Resource `UpsertLock`](#0x1_config_for_next_epoch_UpsertLock) +- [Resource `ExtractPermit`](#0x1_config_for_next_epoch_ExtractPermit) +- [Constants](#@Constants_0) +- [Function `extracts_enabled`](#0x1_config_for_next_epoch_extracts_enabled) +- [Function `enable_extracts`](#0x1_config_for_next_epoch_enable_extracts) +- [Function `disable_extracts`](#0x1_config_for_next_epoch_disable_extracts) +- [Function `upserts_enabled`](#0x1_config_for_next_epoch_upserts_enabled) +- [Function `disable_upserts`](#0x1_config_for_next_epoch_disable_upserts) +- [Function `enable_upserts`](#0x1_config_for_next_epoch_enable_upserts) +- [Function `does_exist`](#0x1_config_for_next_epoch_does_exist) +- [Function `upsert`](#0x1_config_for_next_epoch_upsert) +- [Function `extract`](#0x1_config_for_next_epoch_extract) +- [Function `upsert_lock_state`](#0x1_config_for_next_epoch_upsert_lock_state) +- [Function `latest_upsert_lock_state`](#0x1_config_for_next_epoch_latest_upsert_lock_state) +- [Function `set_upsert_lock_state`](#0x1_config_for_next_epoch_set_upsert_lock_state) +- [Function `abort_unless_vm_or_std`](#0x1_config_for_next_epoch_abort_unless_vm_or_std) + + +
use 0x1::error;
+use 0x1::signer;
+
+ + + + + +## Resource `ForNextEpoch` + +0x1::ForNextEpoch<T> holds the config payload for the next epoch, where T can be ConsnsusConfig, Features, etc. + + +
struct ForNextEpoch<T> has drop, key
+
+ + + +
+Fields + + +
+
+payload: T +
+
+ +
+
+ + +
+ + + +## Resource `UpsertLock` + +We need to temporarily reject on-chain config changes during DKG. +0x0::UpdateLock or 0x1::UpdateLock, whichever has the higher seq_num, represents whether we should reject. + + +
struct UpsertLock has copy, drop, key
+
+ + + +
+Fields + + +
+
+seq_num: u64 +
+
+ +
+
+locked: bool +
+
+ +
+
+ + +
+ + + +## Resource `ExtractPermit` + +We need to allow extraction of pending configs ONLY when we are at the end of a reconfiguration. + + +
struct ExtractPermit has copy, drop, key
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ + +
+ + + +## Constants + + + + + + +
const EPERMISSION_DENIED: u64 = 5;
+
+ + + + + + + +
const ERESOURCE_BUSY: u64 = 4;
+
+ + + + + + + +
const ESTD_OR_VM_SIGNER_NEEDED: u64 = 1;
+
+ + + + + + + +
const ESTD_SIGNER_NEEDED: u64 = 2;
+
+ + + + + + + +
const EVM_SIGNER_NEEDED: u64 = 3;
+
+ + + + + +## Function `extracts_enabled` + + + +
public fun extracts_enabled(): bool
+
+ + + +
+Implementation + + +
public fun extracts_enabled(): bool {
+    exists<ExtractPermit>(@vm) || exists<ExtractPermit>(@std)
+}
+
+ + + +
+ + + +## Function `enable_extracts` + + + +
public fun enable_extracts(account: &signer)
+
+ + + +
+Implementation + + +
public fun enable_extracts(account: &signer) {
+    move_to(account, ExtractPermit {});
+}
+
+ + + +
+ + + +## Function `disable_extracts` + + + +
public fun disable_extracts(account: &signer)
+
+ + + +
+Implementation + + +
public fun disable_extracts(account: &signer) acquires ExtractPermit {
+    move_from<ExtractPermit>(address_of(account));
+}
+
+ + + +
+ + + +## Function `upserts_enabled` + + + +
public fun upserts_enabled(): bool
+
+ + + +
+Implementation + + +
public fun upserts_enabled(): bool acquires UpsertLock {
+    !latest_upsert_lock_state().locked
+}
+
+ + + +
+ + + +## Function `disable_upserts` + +Disable on-chain config updates. Only needed when a reconfiguration with DKG starts. + + +
public fun disable_upserts(account: &signer)
+
+ + + +
+Implementation + + +
public fun disable_upserts(account: &signer) acquires UpsertLock {
+    set_upsert_lock_state(account, true);
+}
+
+ + + +
+ + + +## Function `enable_upserts` + +Enable on-chain config updates. Only needed when a reconfiguration with DKG finishes. + + +
public fun enable_upserts(account: &signer)
+
+ + + +
+Implementation + + +
public fun enable_upserts(account: &signer) acquires UpsertLock {
+    set_upsert_lock_state(account, false);
+}
+
+ + + +
+ + + +## Function `does_exist` + +Check whether there is a pending config payload for T. + + +
public fun does_exist<T: store>(): bool
+
+ + + +
+Implementation + + +
public fun does_exist<T: store>(): bool {
+    exists<ForNextEpoch<T>>(@std)
+}
+
+ + + +
+ + + +## Function `upsert` + +Save an on-chain config to the buffer for the next epoch. +If the buffer is not empty, put in the new one and discard the old one. +Typically followed by a aptos_framework::reconfigure::start_reconfigure_with_dkg() to make it effective as soon as possible. + + +
public fun upsert<T: drop, store>(std: &signer, config: T)
+
+ + + +
+Implementation + + +
public fun upsert<T: drop + store>(std: &signer, config: T) acquires ForNextEpoch, UpsertLock {
+    assert!(address_of(std) == @std, std::error::permission_denied(ESTD_SIGNER_NEEDED));
+    assert!(!latest_upsert_lock_state().locked, std::error::invalid_state(ERESOURCE_BUSY));
+    if (exists<ForNextEpoch<T>>(@std)) {
+        move_from<ForNextEpoch<T>>(@std);
+    };
+    move_to(std, ForNextEpoch { payload: config });
+}
+
+ + + +
+ + + +## Function `extract` + +Take the buffered config T out (buffer cleared). Abort if the buffer is empty. +Should only be used at the end of a reconfiguration. + +NOTE: The caller has to ensure updates are enabled using enable_updates(). + + +
public fun extract<T: store>(account: &signer): T
+
+ + + +
+Implementation + + +
public fun extract<T: store>(account: &signer): T acquires ForNextEpoch {
+    let addr = address_of(account);
+    assert!(addr == @std || addr == @vm, std::error::invalid_state(EPERMISSION_DENIED));
+    let ForNextEpoch<T> { payload } = move_from<ForNextEpoch<T>>(@std);
+    payload
+}
+
+ + + +
+ + + +## Function `upsert_lock_state` + + + +
fun upsert_lock_state(addr: address): config_for_next_epoch::UpsertLock
+
+ + + +
+Implementation + + +
fun upsert_lock_state(addr: address): UpsertLock acquires UpsertLock {
+    if (exists<UpsertLock>(addr)) {
+        *borrow_global<UpsertLock>(addr)
+    } else {
+        UpsertLock {
+            seq_num: 0,
+            locked: false,
+        }
+    }
+}
+
+ + + +
+ + + +## Function `latest_upsert_lock_state` + + + +
fun latest_upsert_lock_state(): config_for_next_epoch::UpsertLock
+
+ + + +
+Implementation + + +
fun latest_upsert_lock_state(): UpsertLock acquires UpsertLock {
+    let state_0 = upsert_lock_state(@vm);
+    let state_1 = upsert_lock_state(@std);
+    if (state_0.seq_num > state_1.seq_num) {
+        state_0
+    } else {
+        state_1
+    }
+}
+
+ + + +
+ + + +## Function `set_upsert_lock_state` + + + +
fun set_upsert_lock_state(account: &signer, locked: bool)
+
+ + + +
+Implementation + + +
fun set_upsert_lock_state(account: &signer, locked: bool) acquires UpsertLock {
+    abort_unless_vm_or_std(account);
+
+    let latest_state = latest_upsert_lock_state();
+
+    if (exists<UpsertLock>(address_of(account))) {
+        move_from<UpsertLock>(address_of(account));
+    };
+
+    let new_state = UpsertLock {
+        seq_num: latest_state.seq_num + 1,
+        locked,
+    };
+    move_to(account, new_state);
+}
+
+ + + +
+ + + +## Function `abort_unless_vm_or_std` + + + +
fun abort_unless_vm_or_std(account: &signer)
+
+ + + +
+Implementation + + +
fun abort_unless_vm_or_std(account: &signer) {
+    let addr = std::signer::address_of(account);
+    assert!(addr == @std || addr == @vm, std::error::permission_denied(ESTD_OR_VM_SIGNER_NEEDED));
+}
+
+ + + +
+ + +[move-book]: https://aptos.dev/move/book/SUMMARY diff --git a/aptos-move/framework/move-stdlib/doc/features.md b/aptos-move/framework/move-stdlib/doc/features.md index 9aa9472c30723..41696ae8702e9 100644 --- a/aptos-move/framework/move-stdlib/doc/features.md +++ b/aptos-move/framework/move-stdlib/doc/features.md @@ -76,7 +76,12 @@ return true. - [Function `sponsored_automatic_account_creation_enabled`](#0x1_features_sponsored_automatic_account_creation_enabled) - [Function `get_concurrent_assets_feature`](#0x1_features_get_concurrent_assets_feature) - [Function `concurrent_assets_enabled`](#0x1_features_concurrent_assets_enabled) +- [Function `get_reconfigure_with_dkg_feature`](#0x1_features_get_reconfigure_with_dkg_feature) +- [Function `reconfigure_with_dkg_enabled`](#0x1_features_reconfigure_with_dkg_enabled) - [Function `change_feature_flags`](#0x1_features_change_feature_flags) +- [Function `change_feature_flags_for_next_epoch`](#0x1_features_change_feature_flags_for_next_epoch) +- [Function `on_new_epoch`](#0x1_features_on_new_epoch) +- [Function `apply_diff`](#0x1_features_apply_diff) - [Function `is_enabled`](#0x1_features_is_enabled) - [Function `set`](#0x1_features_set) - [Function `contains`](#0x1_features_contains) @@ -91,7 +96,8 @@ return true. - [Function `contains`](#@Specification_1_contains) -
use 0x1::error;
+
use 0x1::config_for_next_epoch;
+use 0x1::error;
 use 0x1::signer;
 
@@ -104,7 +110,7 @@ return true. The enabled features, represented by a bitset stored on chain. -
struct Features has key
+
struct Features has copy, drop, store, key
 
@@ -390,6 +396,17 @@ Lifetime: transient + + +Whether reconfiguration with DKG is enabled. +Lifetime: transient + + +
const RECONFIGURE_WITH_DKG: u64 = 38;
+
+ + + Whether resource groups are enabled. @@ -1544,6 +1561,52 @@ Lifetime: transient + + + + +## Function `get_reconfigure_with_dkg_feature` + + + +
public fun get_reconfigure_with_dkg_feature(): u64
+
+ + + +
+Implementation + + +
public fun get_reconfigure_with_dkg_feature(): u64 { RECONFIGURE_WITH_DKG }
+
+ + + +
+ + + +## Function `reconfigure_with_dkg_enabled` + + + +
public fun reconfigure_with_dkg_enabled(): bool
+
+ + + +
+Implementation + + +
public fun reconfigure_with_dkg_enabled(): bool acquires Features {
+    is_enabled(RECONFIGURE_WITH_DKG)
+}
+
+ + +
@@ -1580,6 +1643,101 @@ Function to enable and disable features. Can only be called by a signer of @std. + + + + +## Function `change_feature_flags_for_next_epoch` + +Function to enable and disable features. Can only be called by a signer of @std. + + +
public fun change_feature_flags_for_next_epoch(framework: &signer, enable: vector<u64>, disable: vector<u64>)
+
+ + + +
+Implementation + + +
public fun change_feature_flags_for_next_epoch(framework: &signer, enable: vector<u64>, disable: vector<u64>)
+acquires Features {
+    assert!(signer::address_of(framework) == @std, error::permission_denied(EFRAMEWORK_SIGNER_NEEDED));
+    let features = if (config_for_next_epoch::does_exist<Features>()) {
+        config_for_next_epoch::extract<Features>(framework)
+    } else if (exists<Features>(@std)) {
+        *borrow_global<Features>(@std)
+    } else {
+        Features { features: vector[] }
+    };
+    apply_diff(&mut features, enable, disable);
+    config_for_next_epoch::upsert(framework, features);
+}
+
+ + + +
+ + + +## Function `on_new_epoch` + +Apply all the pending feature flag changes. Should only be used at the end of a reconfiguration with DKG. + +While the scope is public, it can only be usd in system transactions like block_prologue and governance proposals, +who have permission to set the flag that's checked in extract(). + + +
public fun on_new_epoch(account: &signer)
+
+ + + +
+Implementation + + +
public fun on_new_epoch(account: &signer) acquires Features {
+    if (config_for_next_epoch::does_exist<Features>()) {
+        let features = config_for_next_epoch::extract<Features>(account);
+        *borrow_global_mut<Features>(@std) = features;
+    }
+}
+
+ + + +
+ + + +## Function `apply_diff` + + + +
fun apply_diff(features: &mut features::Features, enable: vector<u64>, disable: vector<u64>)
+
+ + + +
+Implementation + + +
fun apply_diff(features: &mut Features, enable: vector<u64>, disable: vector<u64>) {
+    vector::for_each_ref(&enable, |feature| {
+        set(&mut features.features, *feature, true);
+    });
+    vector::for_each_ref(&disable, |feature| {
+        set(&mut features.features, *feature, false);
+    });
+}
+
+ + +
@@ -1679,7 +1837,7 @@ Helper to check whether a feature flag is enabled. ### Resource `Features` -
struct Features has key
+
struct Features has copy, drop, store, key
 
diff --git a/aptos-move/framework/move-stdlib/doc/overview.md b/aptos-move/framework/move-stdlib/doc/overview.md index fbc3291183287..67d42f61936d9 100644 --- a/aptos-move/framework/move-stdlib/doc/overview.md +++ b/aptos-move/framework/move-stdlib/doc/overview.md @@ -16,6 +16,7 @@ For on overview of the Move language, see the [Move Book][move-book]. - [`0x1::acl`](acl.md#0x1_acl) - [`0x1::bcs`](bcs.md#0x1_bcs) - [`0x1::bit_vector`](bit_vector.md#0x1_bit_vector) +- [`0x1::config_for_next_epoch`](config_for_next_epoch.md#0x1_config_for_next_epoch) - [`0x1::error`](error.md#0x1_error) - [`0x1::features`](features.md#0x1_features) - [`0x1::fixed_point32`](fixed_point32.md#0x1_fixed_point32) diff --git a/aptos-move/framework/move-stdlib/sources/configs/config_for_next_epoch.move b/aptos-move/framework/move-stdlib/sources/configs/config_for_next_epoch.move new file mode 100644 index 0000000000000..be8439508264a --- /dev/null +++ b/aptos-move/framework/move-stdlib/sources/configs/config_for_next_epoch.move @@ -0,0 +1,122 @@ +/// This wrapper helps store an on-chain config for the next epoch. +module std::config_for_next_epoch { + + use std::signer::address_of; + + const ESTD_OR_VM_SIGNER_NEEDED: u64 = 1; + const ESTD_SIGNER_NEEDED: u64 = 2; + const EVM_SIGNER_NEEDED: u64 = 3; + const ERESOURCE_BUSY: u64 = 4; + const EPERMISSION_DENIED: u64 = 5; + + /// `0x1::ForNextEpoch` holds the config payload for the next epoch, where `T` can be `ConsnsusConfig`, `Features`, etc. + struct ForNextEpoch has drop, key { + payload: T, + } + + /// We need to temporarily reject on-chain config changes during DKG. + /// `0x0::UpdateLock` or `0x1::UpdateLock`, whichever has the higher `seq_num`, represents whether we should reject. + struct UpsertLock has copy, drop, key { + seq_num: u64, + locked: bool, + } + + /// We need to allow extraction of pending configs ONLY when we are at the end of a reconfiguration. + struct ExtractPermit has copy, drop, key {} + + public fun extracts_enabled(): bool { + exists(@vm) || exists(@std) + } + + public fun enable_extracts(account: &signer) { + move_to(account, ExtractPermit {}); + } + + public fun disable_extracts(account: &signer) acquires ExtractPermit { + move_from(address_of(account)); + } + + public fun upserts_enabled(): bool acquires UpsertLock { + !latest_upsert_lock_state().locked + } + + /// Disable on-chain config updates. Only needed when a reconfiguration with DKG starts. + public fun disable_upserts(account: &signer) acquires UpsertLock { + set_upsert_lock_state(account, true); + } + + /// Enable on-chain config updates. Only needed when a reconfiguration with DKG finishes. + public fun enable_upserts(account: &signer) acquires UpsertLock { + set_upsert_lock_state(account, false); + } + + /// Check whether there is a pending config payload for `T`. + public fun does_exist(): bool { + exists>(@std) + } + + /// Save an on-chain config to the buffer for the next epoch. + /// If the buffer is not empty, put in the new one and discard the old one. + /// Typically followed by a `aptos_framework::reconfigure::start_reconfigure_with_dkg()` to make it effective as soon as possible. + public fun upsert(std: &signer, config: T) acquires ForNextEpoch, UpsertLock { + assert!(address_of(std) == @std, std::error::permission_denied(ESTD_SIGNER_NEEDED)); + assert!(!latest_upsert_lock_state().locked, std::error::invalid_state(ERESOURCE_BUSY)); + if (exists>(@std)) { + move_from>(@std); + }; + move_to(std, ForNextEpoch { payload: config }); + } + + /// Take the buffered config `T` out (buffer cleared). Abort if the buffer is empty. + /// Should only be used at the end of a reconfiguration. + /// + /// NOTE: The caller has to ensure updates are enabled using `enable_updates()`. + public fun extract(account: &signer): T acquires ForNextEpoch { + let addr = address_of(account); + assert!(addr == @std || addr == @vm, std::error::invalid_state(EPERMISSION_DENIED)); + let ForNextEpoch { payload } = move_from>(@std); + payload + } + + fun upsert_lock_state(addr: address): UpsertLock acquires UpsertLock { + if (exists(addr)) { + *borrow_global(addr) + } else { + UpsertLock { + seq_num: 0, + locked: false, + } + } + } + + fun latest_upsert_lock_state(): UpsertLock acquires UpsertLock { + let state_0 = upsert_lock_state(@vm); + let state_1 = upsert_lock_state(@std); + if (state_0.seq_num > state_1.seq_num) { + state_0 + } else { + state_1 + } + } + + fun set_upsert_lock_state(account: &signer, locked: bool) acquires UpsertLock { + abort_unless_vm_or_std(account); + + let latest_state = latest_upsert_lock_state(); + + if (exists(address_of(account))) { + move_from(address_of(account)); + }; + + let new_state = UpsertLock { + seq_num: latest_state.seq_num + 1, + locked, + }; + move_to(account, new_state); + } + + fun abort_unless_vm_or_std(account: &signer) { + let addr = std::signer::address_of(account); + assert!(addr == @std || addr == @vm, std::error::permission_denied(ESTD_OR_VM_SIGNER_NEEDED)); + } +} diff --git a/aptos-move/framework/move-stdlib/sources/configs/features.move b/aptos-move/framework/move-stdlib/sources/configs/features.move index 6f3c598b90633..86140dd158620 100644 --- a/aptos-move/framework/move-stdlib/sources/configs/features.move +++ b/aptos-move/framework/move-stdlib/sources/configs/features.move @@ -23,10 +23,10 @@ /// is a public function. However, once the feature flag is disabled, those functions can constantly /// return true. module std::features { + use std::config_for_next_epoch; use std::error; use std::signer; use std::vector; - // -------------------------------------------------------------------------------------------- // Code Publishing @@ -286,6 +286,16 @@ module std::features { is_enabled(CONCURRENT_ASSETS) } + /// Whether reconfiguration with DKG is enabled. + /// Lifetime: transient + const RECONFIGURE_WITH_DKG: u64 = 38; + + public fun get_reconfigure_with_dkg_feature(): u64 { RECONFIGURE_WITH_DKG } + + public fun reconfigure_with_dkg_enabled(): bool acquires Features { + is_enabled(RECONFIGURE_WITH_DKG) + } + // ============================================================================================ // Feature Flag Implementation @@ -293,7 +303,7 @@ module std::features { const EFRAMEWORK_SIGNER_NEEDED: u64 = 1; /// The enabled features, represented by a bitset stored on chain. - struct Features has key { + struct Features has copy, drop, key, store { features: vector, } @@ -313,6 +323,41 @@ module std::features { }); } + /// Function to enable and disable features. Can only be called by a signer of @std. + public fun change_feature_flags_for_next_epoch(framework: &signer, enable: vector, disable: vector) + acquires Features { + assert!(signer::address_of(framework) == @std, error::permission_denied(EFRAMEWORK_SIGNER_NEEDED)); + let features = if (config_for_next_epoch::does_exist()) { + config_for_next_epoch::extract(framework) + } else if (exists(@std)) { + *borrow_global(@std) + } else { + Features { features: vector[] } + }; + apply_diff(&mut features, enable, disable); + config_for_next_epoch::upsert(framework, features); + } + + /// Apply all the pending feature flag changes. Should only be used at the end of a reconfiguration with DKG. + /// + /// While the scope is public, it can only be usd in system transactions like `block_prologue` and governance proposals, + /// who have permission to set the flag that's checked in `extract()`. + public fun on_new_epoch(account: &signer) acquires Features { + if (config_for_next_epoch::does_exist()) { + let features = config_for_next_epoch::extract(account); + *borrow_global_mut(@std) = features; + } + } + + fun apply_diff(features: &mut Features, enable: vector, disable: vector) { + vector::for_each_ref(&enable, |feature| { + set(&mut features.features, *feature, true); + }); + vector::for_each_ref(&disable, |feature| { + set(&mut features.features, *feature, false); + }); + } + /// Check whether the feature is enabled. fun is_enabled(feature: u64): bool acquires Features { exists(@std) && diff --git a/types/src/on_chain_config/aptos_features.rs b/types/src/on_chain_config/aptos_features.rs index bf2c2686237ee..262bf55fefc60 100644 --- a/types/src/on_chain_config/aptos_features.rs +++ b/types/src/on_chain_config/aptos_features.rs @@ -45,6 +45,7 @@ pub enum FeatureFlag { FEE_PAYER_ACCOUNT_OPTIONAL = 35, AGGREGATOR_V2_DELAYED_FIELDS = 36, CONCURRENT_ASSETS = 37, + RECONFIGURE_WITH_DKG = 38, } /// Representation of features on chain as a bitset.