diff --git a/language/diem-framework/modules/0L/Audit.move b/language/diem-framework/modules/0L/Audit.move index 1c24ff21a3..110b29e068 100644 --- a/language/diem-framework/modules/0L/Audit.move +++ b/language/diem-framework/modules/0L/Audit.move @@ -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 @@ -23,13 +26,22 @@ address 0x1 { // operator account has balance // if (DiemAccount::balance(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 } diff --git a/language/diem-framework/modules/0L/EpochBoundary.move b/language/diem-framework/modules/0L/EpochBoundary.move index bda5c95519..a53b67cec8 100644 --- a/language/diem-framework/modules/0L/EpochBoundary.move +++ b/language/diem-framework/modules/0L/EpochBoundary.move @@ -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 @@ -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
- } - - // 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
) { - if (!is_debug()) { - move_to(vm, DebugMode { - fixed_set: vals - }); - } - } - fun remove_debug(vm: &signer) acquires DebugMode { - CoreAddresses::assert_vm(vm); - if (is_debug()) { - _ = move_from(CoreAddresses::VM_RESERVED_ADDRESS()); - } - } - - fun is_debug(): bool { - exists(CoreAddresses::VM_RESERVED_ADDRESS()) - } - - fun get_debug_vals(): vector
acquires DebugMode { - if (is_debug()) { - let d = borrow_global(CoreAddresses::VM_RESERVED_ADDRESS()); - *&d.fixed_set - } else { - Vector::empty
() - } - } // 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); @@ -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; @@ -143,19 +117,24 @@ module EpochBoundary { if (Vector::is_empty
(&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
acquires DebugMode{ + fun propose_new_set(vm: &signer, height_start: u64, height_now: u64): vector
{ // 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. @@ -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); diff --git a/language/diem-framework/modules/0L/RecoveryMode.move b/language/diem-framework/modules/0L/RecoveryMode.move new file mode 100644 index 0000000000..f9eaf0e2a1 --- /dev/null +++ b/language/diem-framework/modules/0L/RecoveryMode.move @@ -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
, + 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
, epoch_ends: u64) { + if (!is_recovery()) { + move_to(vm, RecoveryMode { + fixed_set: vals, + epoch_ends, + }); + } + } + + public fun maybe_remove_debug_at_epoch(vm: &signer) acquires RecoveryMode { + CoreAddresses::assert_vm(vm); + if (!exists(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(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(CoreAddresses::VM_RESERVED_ADDRESS()); + } + } + + public fun is_recovery(): bool { + exists(CoreAddresses::VM_RESERVED_ADDRESS()) + } + + public fun get_debug_vals(): vector
acquires RecoveryMode { + if (is_recovery()) { + let d = borrow_global(CoreAddresses::VM_RESERVED_ADDRESS()); + *&d.fixed_set + } else { + Vector::empty
() + } + } + + + /////////////// TEST HELPERS /////////////////// + + public fun test_init_recovery(vm: &signer, vals: vector
, epoch_ends: u64) { + CoreAddresses::assert_vm(vm); + if (Testnet::is_testnet()) { + init_recovery(vm, vals, epoch_ends); + } + } + +} +} \ No newline at end of file diff --git a/language/diem-framework/modules/0L/Vouch.move b/language/diem-framework/modules/0L/Vouch.move index b163e052f9..2954667dc2 100644 --- a/language/diem-framework/modules/0L/Vouch.move +++ b/language/diem-framework/modules/0L/Vouch.move @@ -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
, @@ -42,8 +44,9 @@ address 0x1 { if (!exists(val)) return; let v = borrow_global_mut(val); - Vector::push_back
(&mut v.vals, buddy_acc); - + if (!Vector::contains(&v.vals, &buddy_acc)) { // prevent duplicates + Vector::push_back
(&mut v.vals, buddy_acc); + } } public fun vm_migrate(vm: &signer, val: address, buddy_list: vector
) acquires Vouch { @@ -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(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 } } } \ No newline at end of file diff --git a/language/diem-tools/writeset-transaction-generator/src/admin_script_builder.rs b/language/diem-tools/writeset-transaction-generator/src/admin_script_builder.rs index e526deaeb0..bbaaaf9540 100644 --- a/language/diem-tools/writeset-transaction-generator/src/admin_script_builder.rs +++ b/language/diem-tools/writeset-transaction-generator/src/admin_script_builder.rs @@ -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) -> WriteSetPayload { +pub fn ol_writeset_recover_mode(path: PathBuf, vals: Vec, 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()) @@ -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) -> Result { +fn ol_set_epoch_recovery_mode(path: PathBuf, vals: Vec, end_epoch: u64) -> Result { let db = DiemDebugger::db(path)?; let v = db.get_latest_version()?; @@ -668,14 +668,15 @@ fn ol_set_epoch_debug_mode(path: PathBuf, vals: Vec) -> Result }, #[structopt(name = "debug-epoch")] - DebugEpoch { addresses: Vec }, + RecoveryMode { addresses: Vec , epoch_ending: u64}, #[structopt(name = "boundary")] Boundary { addresses: Vec }, #[structopt(name = "ancestry")] @@ -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), diff --git a/language/move-lang/functional-tests/tests/0L/audit/audit_check_autopay_disabled.move b/language/move-lang/functional-tests/tests/0L/audit/audit_check_autopay_disabled.depr similarity index 100% rename from language/move-lang/functional-tests/tests/0L/audit/audit_check_autopay_disabled.move rename to language/move-lang/functional-tests/tests/0L/audit/audit_check_autopay_disabled.depr diff --git a/language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit.move b/language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit.move index 277ec561c6..67e922aa2c 100644 --- a/language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit.move +++ b/language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit.move @@ -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); @@ -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 \ No newline at end of file diff --git a/language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit_rejoin.move b/language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit_rejoin.depr similarity index 100% rename from language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit_rejoin.move rename to language/move-lang/functional-tests/tests/0L/reconfiguration/reconfig_audit_rejoin.depr diff --git a/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_enable_disable copy.move b/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_enable_disable copy.move new file mode 100644 index 0000000000..44ce210bd5 --- /dev/null +++ b/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_enable_disable copy.move @@ -0,0 +1,77 @@ +//! account: alice, 1000000, 0, validator + +// Tests the prologue reconfigures based on wall clock + +//! block-prologue +//! proposer: alice +//! block-time: 1 +//! round: 1 + +//! new-transaction +//! sender: diemroot + +script { + use 0x1::RecoveryMode; + use 0x1::Vector; + + fun main(vm: signer){ + RecoveryMode::test_init_recovery(&vm, Vector::empty
(), 2); + assert(RecoveryMode::is_recovery(), 7357001); + } +} +// check: EXECUTED + + +////////////////////////////////////////////// +///// Trigger reconfiguration at 61 seconds //// +//! block-prologue +//! proposer: alice +//! block-time: 61000000 +//! round: 15 + +///// TEST RECONFIGURATION IS HAPPENING //// +// check: NewEpochEvent +////////////////////////////////////////////// + +//! new-transaction +//! sender: diemroot + +script { + use 0x1::RecoveryMode; + use 0x1::Debug::print; + + fun main(_vm: signer){ + // RecoveryMode::test_init_recovery(&vm, Vector::empty
(), 2); + // assert(RecoveryMode::is_recovery(), 7357001); + print(&RecoveryMode::is_recovery()); + } +} +// check: EXECUTED + + +////////////////////////////////////////////// +///// Trigger second reconfiguration at 61*2 seconds //// +//! block-prologue +//! proposer: alice +//! block-time: 122000000 +//! round: 30 + +///// TEST RECONFIGURATION IS HAPPENING //// +// check: NewEpochEvent +////////////////////////////////////////////// + + +//! new-transaction +//! sender: diemroot + +script { + use 0x1::RecoveryMode; + // use 0x1::Debug::print; + + fun main(_vm: signer){ + // RecoveryMode::test_init_recovery(&vm, Vector::empty
(), 2); + assert(!RecoveryMode::is_recovery(), 7357002); + // print(&RecoveryMode::is_recovery()); + } +} +// check: EXECUTED diff --git a/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_fullnode_pay.move b/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_fullnode_pay.move new file mode 100644 index 0000000000..d809aff4aa --- /dev/null +++ b/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_fullnode_pay.move @@ -0,0 +1,178 @@ +//! account: alice, 1000000GAS, 0, validator + +// Create three end user miner accounts +//! account: bob, 1000000GAS, 0 +//! account: carol, 1000000GAS, 0 +//! account: dave, 1000000GAS, 0 // Dave will not mine above threshold + +// Bob, Carol, Dave are end-users running the Carpe app, and submitting miner proofs. +// He is the only one in the epoch submitting proofs. He should get the entirety of the Identity Subsidy pool avaialable (one validator's worth) + +// 0. Initialize Bob's miner state with a first proof + +//! new-transaction +//! sender: diemroot + +script { + use 0x1::RecoveryMode; + use 0x1::Vector; + + fun main(vm: signer){ + RecoveryMode::test_init_recovery(&vm, Vector::empty
(), 2); + assert(RecoveryMode::is_recovery(), 7357001); + } +} +// check: EXECUTED + + +//! new-transaction +//! sender: bob +script { + use 0x1::TowerState; + use 0x1::TestFixtures; + + fun main(sender: signer) { + TowerState::test_helper_init_val( + &sender, + TestFixtures::easy_chal(), + TestFixtures::easy_sol(), + TestFixtures::easy_difficulty(), + TestFixtures::security(), + ); + } +} + +//! new-transaction +//! sender: carol +script { + use 0x1::TowerState; + use 0x1::TestFixtures; + + fun main(sender: signer) { + TowerState::test_helper_init_val( + &sender, + TestFixtures::easy_chal(), + TestFixtures::easy_sol(), + TestFixtures::easy_difficulty(), + TestFixtures::security(), + ); + } +} + + +//! new-transaction +//! sender: dave +script { + use 0x1::TowerState; + use 0x1::TestFixtures; + + fun main(sender: signer) { + TowerState::test_helper_init_val( + &sender, + TestFixtures::easy_chal(), + TestFixtures::easy_sol(), + TestFixtures::easy_difficulty(), + TestFixtures::security(), + ); + } +} + + +// 2. Make sure there are validator subsidies available. +// so we need Alice to be a Case 1 validator so that there is a subsidy to be paid to validator set. + +//! new-transaction +//! sender: diemroot +script { + use 0x1::Mock; + use 0x1::TowerState; + use 0x1::Debug::print; + + fun main(vm: signer) { + TowerState::test_epoch_reset_counter(&vm); + TowerState::test_helper_mock_reconfig(&vm, @{{alice}}); + TowerState::test_helper_mock_reconfig(&vm, @{{bob}}); + TowerState::test_helper_mock_reconfig(&vm, @{{carol}}); + TowerState::test_helper_mock_reconfig(&vm, @{{dave}}); + + + // Mock the end-users submitting proofs above threshold. + // Add 12: make it so that +2 gets above threshold so that 10 are counted as above thresh. + TowerState::test_helper_mock_mining_vm(&vm, @{{bob}}, 12); + TowerState::test_helper_mock_mining_vm(&vm, @{{carol}}, 12); + TowerState::test_helper_mock_mining_vm(&vm, @{{dave}}, 1); + + print(&TowerState::get_fullnode_proofs_in_epoch()); + print(&TowerState::get_fullnode_proofs_in_epoch_above_thresh()); + print(&TowerState::get_count_in_epoch(@{{bob}})); + print(&TowerState::get_count_above_thresh_in_epoch(@{{bob}})); + + Mock::mock_case_1(&vm, @{{alice}}); + + } +} +//check: EXECUTED + + + +////////////////////////////////////////////// +///// Trigger reconfiguration at 61 seconds //// +//! block-prologue +//! proposer: alice +//! block-time: 61000000 +//! round: 15 + +///// TEST RECONFIGURATION IS HAPPENING //// +// check: NewEpochEvent +////////////////////////////////////////////// + +//! new-transaction +//! sender: diemroot +script { + use 0x1::GAS::GAS; + use 0x1::DiemAccount; + use 0x1::Subsidy; + use 0x1::Globals; + use 0x1::Debug::print; + use 0x1::TowerState; + + fun main(_vm: signer) { + // We are in a new epoch. + print(&TowerState::get_fullnode_proofs_in_epoch()); + print(&TowerState::get_fullnode_proofs_in_epoch_above_thresh()); + print(&TowerState::get_count_in_epoch(@{{bob}})); + print(&TowerState::get_count_above_thresh_in_epoch(@{{bob}})); + + // we expect that Bob receives the share that one validator would get. + let expected_subsidy = Subsidy::subsidy_curve( + Globals::get_subsidy_ceiling_gas(), + 1, // alice is the only validator (but below 4 the reward is the same in testnet: 296000000) + Globals::get_max_validators_per_set(), + ); + + let starting_balance = 1000000; + + print(&expected_subsidy); + let each = expected_subsidy/2; + print(&each); + + let ending_balance = starting_balance + expected_subsidy/2; + + print(&DiemAccount::balance(@{{alice}})); + + print(&DiemAccount::balance(@{{bob}})); + print(&DiemAccount::balance(@{{carol}})); + + //////// RESCUE MODE SHOULD NOT PAY EXPECTED AMOUNT ////////////// + // bob and carol EXPECT TO SHARE half the identity subsidy but DONT GET IT + assert(DiemAccount::balance(@{{bob}}) != ending_balance, 735711); + assert(DiemAccount::balance(@{{bob}}) == starting_balance, 735712); + + assert(DiemAccount::balance(@{{carol}}) != ending_balance, 735713); + assert(DiemAccount::balance(@{{carol}}) == starting_balance, 735714); + // dave's balance is unchanged + assert(DiemAccount::balance(@{{dave}}) == starting_balance, 735715); + + } +} +//check: EXECUTED \ No newline at end of file diff --git a/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_val_pay.move b/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_val_pay.move new file mode 100644 index 0000000000..9eef6a9c5f --- /dev/null +++ b/language/move-lang/functional-tests/tests/0L/recovery_mode/recovery_mode_val_pay.move @@ -0,0 +1,174 @@ +// This tests consensus Case 1. +// ALICE is a validator. +// DID validate successfully. +// DID mine above the threshold for the epoch. + +//! account: alice, 1000000GAS, 0, validator +//! account: bob, 1000000GAS, 0, validator +//! account: carol, 1000000GAS, 0, validator +//! account: dave, 1000000GAS, 0, validator +//! account: eve, 1000000GAS, 0, validator + + + +//! block-prologue +//! proposer: alice +//! block-time: 1 +//! NewBlockEvent + + +//! new-transaction +//! sender: diemroot + +script { + use 0x1::RecoveryMode; + use 0x1::Vector; + + fun main(vm: signer){ + RecoveryMode::test_init_recovery(&vm, Vector::empty
(), 2); + assert(RecoveryMode::is_recovery(), 7357001); + } +} +// check: EXECUTED + +//! new-transaction +//! sender: alice +script { + use 0x1::DiemSystem; + use 0x1::TowerState; + use 0x1::NodeWeight; + use 0x1::GAS::GAS; + use 0x1::DiemAccount; + use 0x1::Debug::print; + + fun main(sender: signer) { + // Tests on initial size of validators + assert(DiemSystem::validator_set_size() == 5, 7357300101011000); + assert(DiemSystem::is_validator(@{{alice}}) == true, 7357300101021000); + assert(DiemSystem::is_validator(@{{eve}}) == true, 7357300101031000); + + assert(TowerState::get_count_in_epoch(@{{alice}}) == 0, 7357300101041000); + assert(DiemAccount::balance(@{{alice}}) == 1000000, 7357300101051000); + assert(NodeWeight::proof_of_weight(@{{alice}}) == 0, 7357300101051000); + + // Alice continues to mine after genesis. + // This test is adapted from chained_from_genesis.move + TowerState::test_helper_mock_mining(&sender, 5); + let a = TowerState::get_epochs_compliant(@{{alice}}); + print(&a); + + assert(TowerState::get_count_in_epoch(@{{alice}}) == 5, 7357300101071000); + assert(TowerState::node_above_thresh(@{{alice}}), 7357300101081000); + + } +} +// check: EXECUTED + + +//! new-transaction +//! sender: diemroot +script { + use 0x1::Vector; + use 0x1::Stats; + + // This is the the epoch boundary. + fun main(vm: signer) { + // This is not an onboarding case, steady state. + // FullnodeState::test_set_fullnode_fixtures( + // &vm, @{{alice}}, 0, 0, 0, 200, 200, 1000000 + // ); + + let voters = Vector::empty
(); + Vector::push_back
(&mut voters, @{{alice}}); + Vector::push_back
(&mut voters, @{{bob}}); + Vector::push_back
(&mut voters, @{{carol}}); + Vector::push_back
(&mut voters, @{{dave}}); + Vector::push_back
(&mut voters, @{{eve}}); + + // Overwrite the statistics to mock that all have been validating. + let i = 1; + while (i < 16) { + // Mock the validator doing work for 15 blocks, and stats being updated. + Stats::process_set_votes(&vm, &voters); + i = i + 1; + }; + } +} +//check: EXECUTED + +//! new-transaction +//! sender: diemroot +script { + use 0x1::Cases; + use 0x1::Vector; + use 0x1::DiemSystem; + + fun main(vm: signer) { + // We are in a new epoch. + // Check alice is in the the correct case during reconfigure + assert(Cases::get_case(&vm, @{{alice}}, 0, 15) == 1, 735700018010901); + assert(Cases::get_case(&vm, @{{bob}}, 0, 15) == 2, 735700018010902); + assert(Cases::get_case(&vm, @{{carol}}, 0, 15) == 2, 735700018010903); + assert(Cases::get_case(&vm, @{{dave}}, 0, 15) == 2, 735700018010904); + assert(Cases::get_case(&vm, @{{eve}}, 0, 15) == 2, 735700018010905); + + // check only 1 val is getting the subsidy + let (vals, _) = DiemSystem::get_fee_ratio(&vm, 0, 100); + assert(Vector::length
(&vals) == 1, 7357000180111); + + } +} + +////////////////////////////////////////////// +///// Trigger reconfiguration at 61 seconds //// +//! block-prologue +//! proposer: alice +//! block-time: 61000000 +//! round: 15 + +///// TEST RECONFIGURATION IS HAPPENING //// +// check: NewEpochEvent +////////////////////////////////////////////// + + +//! new-transaction +//! sender: diemroot +script { + use 0x1::GAS::GAS; + use 0x1::DiemAccount; + use 0x1::Subsidy; + use 0x1::Globals; + + use 0x1::Debug::print; + fun main(_vm: signer) { + // We are in a new epoch. + + let expected_subsidy = Subsidy::subsidy_curve( + Globals::get_subsidy_ceiling_gas(), + 1, + Globals::get_max_validators_per_set(), + ); + + let starting_balance = 1000000; + + let operator_refund = 4336 * 5; // BASELINE_TX_COST * proofs = 21680 + + // Note since there's only 1 validator and the reward to alice was the entirety of subsidy available. + let burn = expected_subsidy/2; // 50% of the rewrd to validator. + + + let usual_ending_balance = starting_balance + expected_subsidy - operator_refund - burn; + + // let recovery_ending_balance = starting_balance - operator_refund - burn; + + // print(&ending_balance); + print(&DiemAccount::balance(@{{alice}})); + + //////// RESCUE MODE SHOULD NOT PAY EXPECTED AMOUNT ////////////// + assert(DiemAccount::balance(@{{alice}}) != usual_ending_balance, 7357000180113); + + // the operator_refund and burn depleted the account, and there was no new reward. + assert(DiemAccount::balance(@{{alice}}) == 0, 7357000180114); + } +} +//check: EXECUTED \ No newline at end of file diff --git a/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_fail_thresh.move b/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_fail_thresh.move new file mode 100644 index 0000000000..e4b8ca1f11 --- /dev/null +++ b/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_fail_thresh.move @@ -0,0 +1,44 @@ +//! account: alice, 1000000GAS, 0, validator +//! account: bob, 1000000GAS, 0, validator +//! account: carol, 1000000GAS, 0, validator +//! account: dave, 1000000GAS, 0, validator +//! account: eve, 1000000GAS, 0, validator + +//! new-transaction +//! sender: diemroot +//! execute-as: alice +script { + use 0x1::Audit; + use 0x1::ValidatorConfig; + use 0x1::Vector; + // use 0x1::TowerState; + // use 0x1::GAS::GAS; + use 0x1::Vouch; + use 0x1::Testnet; + use 0x1::Debug::print; + + fun main(vm: signer, alice_sig: signer) { + /// NOTE: when you remove testnet, you will not see the specific error code of the assert() that fails. You will only see a writeset rejection. + Testnet::remove_testnet(&vm); + + assert(ValidatorConfig::is_valid(@{{alice}}), 7257001); + + Vouch::init(&alice_sig); + + let buddies = Vector::singleton
(@{{bob}}); + Vector::push_back(&mut buddies, @{{carol}}); + Vector::push_back(&mut buddies, @{{dave}}); + + // Not enough people vouching, EVE did not. + // Vector::push_back(&mut buddies, @{{eve}}); + + Vouch::vm_migrate(&vm, @{{alice}}, buddies); + + // // audit must pass + print(&Vouch::unrelated_buddies_above_thresh(@{{alice}})); + print(&Audit::val_audit_passing(@{{alice}})); + assert(!Audit::val_audit_passing(@{{alice}}), 7357002); + + } +} +// check: EXECUTED \ No newline at end of file diff --git a/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_fail_unset.move b/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_fail_unset.move new file mode 100644 index 0000000000..c2c2932ab8 --- /dev/null +++ b/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_fail_unset.move @@ -0,0 +1,39 @@ +//! account: alice, 1000000GAS, 0, validator + +//! new-transaction +//! sender: diemroot +//! execute-as: alice +script { + use 0x1::Audit; + use 0x1::ValidatorConfig; + // use 0x1::AutoPay; + // use 0x1::TowerState; + // use 0x1::GAS::GAS; + use 0x1::Vouch; + use 0x1::Testnet; + use 0x1::Debug::print; + + fun main(vm: signer, _alice_sig: signer) { + Testnet::remove_testnet(&vm); + // // Test audit function val_audit_passing satisfying all conditions + assert(ValidatorConfig::is_valid(@{{alice}}), 7257001); + + // // operator has gas from genesis + // let oper = ValidatorConfig::get_operator(@{{alice}}); + // assert(DiemAccount::balance(oper) == 1000000, 7357007003002); + + // // enable autopay + // assert(!AutoPay::is_enabled(@{{alice}}), 7357007003003); + // AutoPay::enable_autopay(&alice_sig); + // assert(AutoPay::is_enabled(@{{alice}}), 7357007003004); + + // assert(TowerState::is_init(@{{alice}}), 7357007003005); + assert(!Audit::val_audit_passing(@{{alice}}), 7357002); + + // // audit must pass + print(&Vouch::unrelated_buddies_above_thresh(@{{alice}})); + print(&Audit::val_audit_passing(@{{alice}})); + + } +} +// check: EXECUTED \ No newline at end of file diff --git a/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_pass.move b/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_pass.move new file mode 100644 index 0000000000..be7288b5f6 --- /dev/null +++ b/language/move-lang/functional-tests/tests/0L/vouch/vouch_audit_pass.move @@ -0,0 +1,37 @@ +//! account: alice, 1000000GAS, 0, validator +//! account: bob, 1000000GAS, 0, validator +//! account: carol, 1000000GAS, 0, validator +//! account: dave, 1000000GAS, 0, validator +//! account: eve, 1000000GAS, 0, validator + +//! new-transaction +//! sender: diemroot +//! execute-as: alice +script { + use 0x1::Audit; + use 0x1::ValidatorConfig; + use 0x1::Vector; + use 0x1::Vouch; + use 0x1::Testnet; + + fun main(vm: signer, alice_sig: signer) { + /// NOTE: when you remove testnet, you will not see the specific error code of the assert() that fails. You will only see a writeset rejection. + Testnet::remove_testnet(&vm); + + assert(ValidatorConfig::is_valid(@{{alice}}), 7257001); + + Vouch::init(&alice_sig); + + let buddies = Vector::singleton
(@{{bob}}); + Vector::push_back(&mut buddies, @{{carol}}); + Vector::push_back(&mut buddies, @{{dave}}); + Vector::push_back(&mut buddies, @{{eve}}); + + Vouch::vm_migrate(&vm, @{{alice}}, buddies); + + assert(Audit::val_audit_passing(@{{alice}}), 7357002); + + + } +} +// check: EXECUTED diff --git a/language/move-lang/functional-tests/tests/0L/vouch/vouch_validator_set_reject.move b/language/move-lang/functional-tests/tests/0L/vouch/vouch_validator_set_reject.move new file mode 100644 index 0000000000..7d717450c9 --- /dev/null +++ b/language/move-lang/functional-tests/tests/0L/vouch/vouch_validator_set_reject.move @@ -0,0 +1,157 @@ +// This tests consensus Case 1. +// ALICE is a validator. +// DID validate successfully. +// DID mine above the threshold for the epoch. + +//! account: alice, 1000000GAS, 0, validator +//! account: bob, 1000000GAS, 0, validator +//! account: carol, 1000000GAS, 0, validator +//! account: dave, 1000000GAS, 0, validator +//! account: eve, 1000000GAS, 0, validator + +//! block-prologue +//! proposer: alice +//! block-time: 1 +//! NewBlockEvent + +//! new-transaction +//! sender: alice +script { + use 0x1::DiemSystem; + use 0x1::TowerState; + use 0x1::NodeWeight; + use 0x1::GAS::GAS; + use 0x1::DiemAccount; + use 0x1::Debug::print; + + fun main(sender: signer) { + // Tests on initial size of validators + assert(DiemSystem::validator_set_size() == 5, 7357300101011000); + assert(DiemSystem::is_validator(@{{alice}}) == true, 7357300101021000); + assert(DiemSystem::is_validator(@{{eve}}) == true, 7357300101031000); + + assert(TowerState::get_count_in_epoch(@{{alice}}) == 0, 7357300101041000); + assert(DiemAccount::balance(@{{alice}}) == 1000000, 7357300101051000); + assert(NodeWeight::proof_of_weight(@{{alice}}) == 0, 7357300101051000); + + // Alice continues to mine after genesis. + // This test is adapted from chained_from_genesis.move + TowerState::test_helper_mock_mining(&sender, 5); + let a = TowerState::get_epochs_compliant(@{{alice}}); + print(&a); + + assert(TowerState::get_count_in_epoch(@{{alice}}) == 5, 7357300101071000); + assert(TowerState::node_above_thresh(@{{alice}}), 7357300101081000); + + } +} +// check: EXECUTED + + +//! new-transaction +//! sender: diemroot +script { + use 0x1::Vector; + use 0x1::Stats; + + // This is the the epoch boundary. + fun main(vm: signer) { + // This is not an onboarding case, steady state. + // FullnodeState::test_set_fullnode_fixtures( + // &vm, @{{alice}}, 0, 0, 0, 200, 200, 1000000 + // ); + + let voters = Vector::empty
(); + Vector::push_back
(&mut voters, @{{alice}}); + Vector::push_back
(&mut voters, @{{bob}}); + Vector::push_back
(&mut voters, @{{carol}}); + Vector::push_back
(&mut voters, @{{dave}}); + Vector::push_back
(&mut voters, @{{eve}}); + + // Overwrite the statistics to mock that all have been validating. + let i = 1; + while (i < 16) { + // Mock the validator doing work for 15 blocks, and stats being updated. + Stats::process_set_votes(&vm, &voters); + i = i + 1; + }; + } +} +//check: EXECUTED + +//! new-transaction +//! sender: diemroot +script { + use 0x1::Cases; + use 0x1::Vector; + use 0x1::DiemSystem; + + fun main(vm: signer) { + // We are in a new epoch. + // Check alice is in the the correct case during reconfigure + assert(Cases::get_case(&vm, @{{alice}}, 0, 15) == 1, 735700018010901); + assert(Cases::get_case(&vm, @{{bob}}, 0, 15) == 2, 735700018010902); + assert(Cases::get_case(&vm, @{{carol}}, 0, 15) == 2, 735700018010903); + assert(Cases::get_case(&vm, @{{dave}}, 0, 15) == 2, 735700018010904); + assert(Cases::get_case(&vm, @{{eve}}, 0, 15) == 2, 735700018010905); + + // check only 1 val is getting the subsidy + let (vals, _) = DiemSystem::get_fee_ratio(&vm, 0, 100); + assert(Vector::length
(&vals) == 1, 7357000180111); + + } +} + +////////////////////////////////////////////// +///// Trigger reconfiguration at 61 seconds //// +//! block-prologue +//! proposer: alice +//! block-time: 61000000 +//! round: 15 + +///// TEST RECONFIGURATION IS HAPPENING //// +// check: NewEpochEvent +////////////////////////////////////////////// + + +//! new-transaction +//! sender: diemroot +script { + use 0x1::NodeWeight; + use 0x1::GAS::GAS; + use 0x1::DiemAccount; + use 0x1::Subsidy; + use 0x1::Globals; + use 0x1::TowerState; + + use 0x1::Debug::print; + fun main(_vm: signer) { + // We are in a new epoch. + + let expected_subsidy = Subsidy::subsidy_curve( + Globals::get_subsidy_ceiling_gas(), + 1, + Globals::get_max_validators_per_set(), + ); + + let starting_balance = 1000000; + + let operator_refund = 4336 * 5; // BASELINE_TX_COST * proofs = 21680 + + // Note since there's only 1 validator and the reward to alice was the entirety of subsidy available. + let burn = expected_subsidy/2; // 50% of the rewrd to validator. + + + let ending_balance = starting_balance + expected_subsidy - operator_refund - burn; + print(&ending_balance); + print(&DiemAccount::balance(@{{alice}})); + + assert(DiemAccount::balance(@{{alice}}) == ending_balance, 7357000180113); + assert(NodeWeight::proof_of_weight(@{{alice}}) == 5, 7357000180114); + + // Case 1, increments the epochs_validating_and_mining, which is used for rate-limiting onboarding + assert(TowerState::get_epochs_compliant(@{{alice}}) == 1, 7357000180115); + + } +} +//check: EXECUTED \ No newline at end of file diff --git a/ol/integration-tests/test-autopay.mk b/ol/integration-tests/test-autopay.mk index 2a0338bc66..952f37433d 100644 --- a/ol/integration-tests/test-autopay.mk +++ b/ol/integration-tests/test-autopay.mk @@ -112,7 +112,7 @@ check-transfer: # all tests above push the balance back up to 10, 11 or 15 @while [[ ${NOW} -le ${END} ]] ; do \ - if PERSONA=alice make -f ${MAKE_FILE} balance-bob | grep -e '10' -e '11' -e '15'; then \ + if PERSONA=alice make -f ${MAKE_FILE} balance-bob | grep -e '16' -e '17' -e '15'; then \ echo TX SUCCESS ; \ break ; \ else \