Skip to content

Commit

Permalink
[move] Recovery mode for epoch boundary (#1090)
Browse files Browse the repository at this point in the history
* refactor DebugMode into Recovery Mode. Prevent payments until recovery is complete

* set epoch explicitly

* add tests for recovery mode

* Squashed commit of the following:

commit 9114a6ce380248b1f666d782d8e50add7e2f8795
Author: 0o-de-lally <[email protected]>
Date:   Sun Apr 24 13:52:04 2022 -0400

    include more vouch tests

commit 074e222
Author: 0o-de-lally <[email protected]>
Date:   Sun Apr 24 10:43:29 2022 -0400

    Vouch tx (#1088)

    * vouch tx scaffold

    * patch

    * cli for vouch

    * cargo fix

    * rename script

    * stdlib build

* No longer check for Autopay in Audit. Remove deprecated tests

* drawks thinks my commit history is sloppy :). This commit is a patch on a failing functional test for epoch boundary validator audit checking.

* in recovery mode make the fixed validator set case expire after # epochs

* patch integration test for test-autopay grep, to use updated values from the proof of burn patches
  • Loading branch information
0o-de-lally authored Apr 25, 2022
1 parent c8fadfc commit bd62f62
Show file tree
Hide file tree
Showing 17 changed files with 866 additions and 57 deletions.
14 changes: 13 additions & 1 deletion language/diem-framework/modules/0L/Audit.move
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ address 0x1 {
use 0x1::Testnet;
use 0x1::Vouch;

use 0x1::Debug::print;


public fun val_audit_passing(val: address): bool {
print(&11111);
// has valid configs
if (!ValidatorConfig::is_valid(val)) return false;
// has operator account set to another address
Expand All @@ -23,13 +26,22 @@ address 0x1 {
// operator account has balance
// if (DiemAccount::balance<GAS>(oper) < 50000 && !Testnet::is_testnet()) return false;
// has autopay enabled
if (!AutoPay::is_enabled(val)) return false;
print(&111110001);

// if (!AutoPay::is_enabled(val)) return false;

print(&111110002);

// has mining state
if (!TowerState::is_init(val)) return false;
print(&111110003);

// is a slow wallet
if (!DiemAccount::is_slow(val)) return false;
print(&111110004);

if (!Vouch::unrelated_buddies_above_thresh(val)) return false;
print(&111110005);

true
}
Expand Down
59 changes: 19 additions & 40 deletions language/diem-framework/modules/0L/EpochBoundary.move
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
///////////////////////////////////////////////////////////////////////////
// 0L Module
// Epoch Prologue
// Epoch Boundary
///////////////////////////////////////////////////////////////////////////
// The prologue for transitioning to next epoch after every n blocks.
// File Prefix for errors: 1800
Expand All @@ -27,46 +27,15 @@ module EpochBoundary {
use 0x1::ValidatorUniverse;
use 0x1::Testnet;
use 0x1::StagingNet;
use 0x1::RecoveryMode;

use 0x1::Debug::print;

struct DebugMode has copy, key, drop, store{
fixed_set: vector<address>
}

// private function so that it can only be called by vm session.
// should never be used in production.
fun init_debug(vm: &signer, vals: vector<address>) {
if (!is_debug()) {
move_to<DebugMode>(vm, DebugMode {
fixed_set: vals
});
}
}

fun remove_debug(vm: &signer) acquires DebugMode {
CoreAddresses::assert_vm(vm);
if (is_debug()) {
_ = move_from<DebugMode>(CoreAddresses::VM_RESERVED_ADDRESS());
}
}

fun is_debug(): bool {
exists<DebugMode>(CoreAddresses::VM_RESERVED_ADDRESS())
}

fun get_debug_vals(): vector<address> acquires DebugMode {
if (is_debug()) {
let d = borrow_global<DebugMode>(CoreAddresses::VM_RESERVED_ADDRESS());
*&d.fixed_set
} else {
Vector::empty<address>()
}
}

// This function is called by block-prologue once after n blocks.
// Function code: 01. Prefix: 180001
public fun reconfigure(vm: &signer, height_now: u64) acquires DebugMode{
public fun reconfigure(vm: &signer, height_now: u64) {

CoreAddresses::assert_vm(vm);
let height_start = Epoch::get_timer_height_start(vm);
Expand Down Expand Up @@ -128,7 +97,12 @@ module EpochBoundary {
let count = TowerState::get_count_above_thresh_in_epoch(addr);

let miner_subsidy = count * proof_price;
FullnodeSubsidy::distribute_fullnode_subsidy(vm, addr, miner_subsidy);

// don't pay while we are in recovery mode, since that creates a frontrunning opportunity
if (!RecoveryMode::is_recovery()){
FullnodeSubsidy::distribute_fullnode_subsidy(vm, addr, miner_subsidy);
}

};

k = k + 1;
Expand All @@ -143,19 +117,24 @@ module EpochBoundary {

if (Vector::is_empty<address>(&outgoing_compliant_set)) return;

if (subsidy_units > 0) {
// don't pay while we are in recovery mode, since that creates a frontrunning opportunity
if (subsidy_units > 0 && !RecoveryMode::is_recovery()) {
Subsidy::process_subsidy(vm, subsidy_units, &outgoing_compliant_set);
};

Subsidy::process_fees(vm, &outgoing_compliant_set);
}

fun propose_new_set(vm: &signer, height_start: u64, height_now: u64): vector<address> acquires DebugMode{
fun propose_new_set(vm: &signer, height_start: u64, height_now: u64): vector<address> {
// Propose upcoming validator set:

// in emergency admin roles set the validator set
if (is_debug()) {
return get_debug_vals()
// there may be a recovery set to be used.
// if there is no rescue mission validators, just do usual procedure.

if (RecoveryMode::is_recovery()) {
let recovery_vals = RecoveryMode::get_debug_vals();
if (Vector::length(&recovery_vals) > 0) return recovery_vals;
};

// save all the eligible list, before the jailing removes them.
Expand Down Expand Up @@ -222,8 +201,8 @@ module EpochBoundary {

Epoch::reset_timer(vm, height_now);

RecoveryMode::maybe_remove_debug_at_epoch(vm);
// Reconfig should be the last event.

// Reconfigure the network
DiemSystem::bulk_update_validators(vm, proposed_set);

Expand Down
103 changes: 103 additions & 0 deletions language/diem-framework/modules/0L/RecoveryMode.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
///////////////////////////////////////////////////////////////////////////
// 0L Module
// Recovery Mode
///////////////////////////////////////////////////////////////////////////
// For when an admin upgrade or network halt recovery needs to be exectuted.
// For use for example in preventing front running by miners and validators
// for rewards while the network is unstable.
///////////////////////////////////////////////////////////////////////////



address 0x1 {
module RecoveryMode {

use 0x1::CoreAddresses;
use 0x1::DiemConfig;
use 0x1::DiemSystem;
use 0x1::Vector;
use 0x1::Testnet;
use 0x1::StagingNet;

struct RecoveryMode has copy, key, drop, store{
// set this if a validator set needs to be overriden
// if list is empty, it will use validator set.
fixed_set: vector<address>,
epoch_ends: u64,
}

// private function so that it can only be called by vm session.
// should never be used in production.
fun init_recovery(vm: &signer, vals: vector<address>, epoch_ends: u64) {
if (!is_recovery()) {
move_to<RecoveryMode>(vm, RecoveryMode {
fixed_set: vals,
epoch_ends,
});
}
}

public fun maybe_remove_debug_at_epoch(vm: &signer) acquires RecoveryMode {
CoreAddresses::assert_vm(vm);
if (!exists<RecoveryMode>(CoreAddresses::VM_RESERVED_ADDRESS())) return;

let enough_vals = if (
Testnet::is_testnet() ||
StagingNet::is_staging_net()
){ true }
else { (DiemSystem::validator_set_size() >= 21) };
let d = borrow_global<RecoveryMode>(CoreAddresses::VM_RESERVED_ADDRESS());

let enough_epochs = DiemConfig::get_current_epoch() >= d.epoch_ends;


// In the case that we set a fixed group of validators. Make it expire after enough time has passed.
if (enough_epochs) {
if (Vector::length(&d.fixed_set) > 0) {
remove_debug(vm);
} else {
// Otherwise, we are keeping the same validator selection logic.
// In that case the system needs to pick enough validators for this to disable.
if (enough_vals){
remove_debug(vm);
}
}
}
}





fun remove_debug(vm: &signer) acquires RecoveryMode {
CoreAddresses::assert_vm(vm);
if (is_recovery()) {
_ = move_from<RecoveryMode>(CoreAddresses::VM_RESERVED_ADDRESS());
}
}

public fun is_recovery(): bool {
exists<RecoveryMode>(CoreAddresses::VM_RESERVED_ADDRESS())
}

public fun get_debug_vals(): vector<address> acquires RecoveryMode {
if (is_recovery()) {
let d = borrow_global<RecoveryMode>(CoreAddresses::VM_RESERVED_ADDRESS());
*&d.fixed_set
} else {
Vector::empty<address>()
}
}


/////////////// TEST HELPERS ///////////////////

public fun test_init_recovery(vm: &signer, vals: vector<address>, epoch_ends: u64) {
CoreAddresses::assert_vm(vm);
if (Testnet::is_testnet()) {
init_recovery(vm, vals, epoch_ends);
}
}

}
}
14 changes: 11 additions & 3 deletions language/diem-framework/modules/0L/Vouch.move
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ address 0x1 {
use 0x1::StagingNet;
use 0x1::CoreAddresses;

use 0x1::Debug::print;

// triggered once per epoch
struct Vouch has key {
vals: vector<address>,
Expand Down Expand Up @@ -42,8 +44,9 @@ address 0x1 {
if (!exists<Vouch>(val)) return;

let v = borrow_global_mut<Vouch>(val);
Vector::push_back<address>(&mut v.vals, buddy_acc);

if (!Vector::contains(&v.vals, &buddy_acc)) { // prevent duplicates
Vector::push_back<address>(&mut v.vals, buddy_acc);
}
}

public fun vm_migrate(vm: &signer, val: address, buddy_list: vector<address>) acquires Vouch {
Expand Down Expand Up @@ -131,14 +134,19 @@ address 0x1 {
}

public fun unrelated_buddies_above_thresh(val: address): bool acquires Vouch{
print(&222222);
if (Testnet::is_testnet() || StagingNet::is_staging_net()) {
return true
};
print(&22222200001);

if (!exists<Vouch>(val)) return false;
print(&22222200002);

let len = Vector::length(&unrelated_buddies(val));
(len > 3) // TODO: move to Globals
print(&22222200003);

(len >= 4) // TODO: move to Globals
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,13 @@ pub fn ol_writset_encode_migrations(


/// set the EpochBoundary debug mode.
pub fn ol_writeset_debug_epoch(path: PathBuf, vals: Vec<AccountAddress>) -> WriteSetPayload {
pub fn ol_writeset_recover_mode(path: PathBuf, vals: Vec<AccountAddress>, epoch_ending: u64) -> WriteSetPayload {
if vals.len() == 0 {
println!("need to provide list of addresses");
exit(1)
};

let debug_mode = ol_set_epoch_debug_mode(path.clone(), vals).unwrap();
let debug_mode = ol_set_epoch_recovery_mode(path.clone(), vals, epoch_ending).unwrap();
let reconfig = ol_reconfig_changeset(path).unwrap();

WriteSetPayload::Direct(merge_change_set(debug_mode, reconfig).unwrap())
Expand Down Expand Up @@ -657,7 +657,7 @@ fn test_epoch() {
ol_epoch_timestamp_update("/home/node/.0L/db".parse().unwrap());
}

fn ol_set_epoch_debug_mode(path: PathBuf, vals: Vec<AccountAddress>) -> Result<ChangeSet> {
fn ol_set_epoch_recovery_mode(path: PathBuf, vals: Vec<AccountAddress>, end_epoch: u64) -> Result<ChangeSet> {
let db = DiemDebugger::db(path)?;
let v = db.get_latest_version()?;

Expand All @@ -668,14 +668,15 @@ fn ol_set_epoch_debug_mode(path: PathBuf, vals: Vec<AccountAddress>) -> Result<C
let txn_args = vec![
TransactionArgument::Address(diem_root_address()),
TransactionArgument::AddressVector(vals),
TransactionArgument::U64(end_epoch),
];
session
.execute_function(
&ModuleId::new(
account_config::CORE_CODE_ADDRESS,
Identifier::new("EpochBoundary").unwrap(),
Identifier::new("RecoveryMode").unwrap(),
),
&Identifier::new("init_debug").unwrap(),
&Identifier::new("init_recovery").unwrap(),
vec![],
convert_txn_args(&txn_args),
&mut gas_status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use diem_types::{
use diem_writeset_generator::{
create_release, encode_custom_script, encode_halt_network_payload,
encode_remove_validators_payload, script_bulk_update_vals_payload, release_flow::artifacts::load_latest_artifact,
verify_release, ol_writeset_stdlib_upgrade, ol_create_reconfig_payload, ol_writset_encode_rescue, ol_writset_update_timestamp, ol_writeset_force_boundary, ol_writeset_set_testnet, ol_writeset_debug_epoch, ol_writeset_update_epoch_time, ol_writeset_ancestry, ol_writset_encode_migrations, ol_debug
verify_release, ol_writeset_stdlib_upgrade, ol_create_reconfig_payload, ol_writset_encode_rescue, ol_writset_update_timestamp, ol_writeset_force_boundary, ol_writeset_set_testnet, ol_writeset_recover_mode, ol_writeset_update_epoch_time, ol_writeset_ancestry, ol_writset_encode_migrations, ol_debug
};
use move_binary_format::CompiledModule;
use std::path::PathBuf;
Expand Down Expand Up @@ -47,7 +47,7 @@ enum Command {
#[structopt(name = "rescue")]
Rescue { addresses: Vec<AccountAddress> },
#[structopt(name = "debug-epoch")]
DebugEpoch { addresses: Vec<AccountAddress> },
RecoveryMode { addresses: Vec<AccountAddress> , epoch_ending: u64},
#[structopt(name = "boundary")]
Boundary { addresses: Vec<AccountAddress> },
#[structopt(name = "ancestry")]
Expand Down Expand Up @@ -148,7 +148,7 @@ fn main() -> Result<()> {
Command::Rescue { addresses } => ol_writset_encode_rescue(opt.db.unwrap(), addresses),
Command::Timestamp {} => ol_writset_update_timestamp(opt.db.unwrap()),
Command::Testnet {} => ol_writeset_set_testnet(opt.db.unwrap()),
Command::DebugEpoch { addresses } => ol_writeset_debug_epoch(opt.db.unwrap(), addresses),
Command::RecoveryMode { addresses, epoch_ending } => ol_writeset_recover_mode(opt.db.unwrap(), addresses, epoch_ending),
Command::EpochTime {} => ol_writeset_update_epoch_time(opt.db.unwrap()),
Command::Ancestry { ancestry_file } => ol_writeset_ancestry(opt.db.unwrap(), ancestry_file),
Command::Migrate { ancestry_file, makewhole_file, addresses} => ol_writset_encode_migrations(opt.db.unwrap(), ancestry_file, makewhole_file, addresses),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ script {
//! sender: eve
script {
use 0x1::TowerState;
use 0x1::AutoPay;

fun main(sender: signer) {
// Skip eve forcing audit to fail
// AutoPay::enable_autopay(&sender);
AutoPay::enable_autopay(&sender);

// Miner is the only one that can update their mining stats. Hence this first transaction.
TowerState::test_helper_mock_mining(&sender, 5);
Expand Down Expand Up @@ -172,8 +172,8 @@ script {
// We are in a new epoch.
assert(DiemConfig::get_current_epoch() == 2, 7357008015007);
// Tests on initial size of validators
assert(DiemSystem::validator_set_size() == 4, 7357008015008);
assert(DiemSystem::is_validator(@{{eve}}) == false, 7357008015009);
assert(DiemSystem::validator_set_size() == 5, 7357008015008);
assert(DiemSystem::is_validator(@{{eve}}), 7357008015009);
}
}
//check: EXECUTED
Loading

0 comments on commit bd62f62

Please sign in to comment.