Skip to content

Commit

Permalink
[move] performance metrics (0LNetworkCommunity#132)
Browse files Browse the repository at this point in the history
Co-authored-by: 0o-de-lally <[email protected]>
  • Loading branch information
sirouk and 0o-de-lally committed Aug 17, 2024
1 parent d86d15d commit 8b36588
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 40 deletions.
64 changes: 43 additions & 21 deletions framework/libra-framework/sources/modified_source/stake.move
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,31 @@ module diem_framework::stake {
fees_table: Table<address, Coin<LibraCoin>>,
}

#[view]
/// @return: tuple
/// - u64: number of proposals
/// - address: the validator
public fun get_highest_net_proposer(): (u64, address) acquires ValidatorSet,
ValidatorPerformance, ValidatorConfig
{
let vals = get_current_validators();
let highest_net_proposals = 0;
let highest_addr = @0x0;
vector::for_each(vals, |v| {
let idx = get_validator_index(v);
let (success, fail) = get_current_epoch_proposal_counts(idx);
if (success > fail) {
let net = success - fail;

if (net > highest_net_proposals) {
highest_net_proposals = net;
highest_addr = v;
}
}
});
(highest_net_proposals, highest_addr)
}


#[view]
/// Returns the list of active validators
Expand Down Expand Up @@ -250,13 +275,29 @@ module diem_framework::stake {
}

#[view]
/// Return the number of successful and failed proposals for the proposal at the given validator index.
/// @return: tuple
/// - u64: the number of successful
/// - u64: and failed proposals for the proposal at the given validator index.
public fun get_current_epoch_proposal_counts(validator_index: u64): (u64, u64) acquires ValidatorPerformance {
let validator_performances = &borrow_global<ValidatorPerformance>(@diem_framework).validators;
let validator_performance = vector::borrow(validator_performances, validator_index);
(validator_performance.successful_proposals, validator_performance.failed_proposals)
}

#[view]
/// Get net proposals from address
/// @return: u64: the number of net proposals (success - fail)
public fun get_val_net_proposals(val: address): u64 acquires
ValidatorPerformance, ValidatorConfig {
let idx = get_validator_index(val);
let (proposed, failed) = get_current_epoch_proposal_counts(idx);
if (proposed > failed) {
proposed - failed
} else {
0
}
}

#[view]
/// Return the validator's config.
public fun get_validator_config(validator_address: address): (vector<u8>, vector<u8>, vector<u8>) acquires ValidatorConfig {
Expand Down Expand Up @@ -314,9 +355,6 @@ module diem_framework::stake {
if (account_address != operator) {
set_operator(owner, operator)
};
// if (account_address != voter) {
// set_delegated_voter(owner, voter)
// };
}

/// Initialize the validator account and give ownership to the signing account.
Expand Down Expand Up @@ -541,29 +579,18 @@ module diem_framework::stake {
}

/// Triggers at epoch boundary. This function shouldn't abort.
///
/// 1. Distribute transaction fees and rewards to stake pools of active and pending inactive validators (requested
/// to leave but not yet removed).
/// 2. Officially move pending active stake to active and move pending inactive stake to inactive.
/// The staking pool's voting power in this new epoch will be updated to the total active stake.
/// 3. Add pending active validators to the active set if they satisfy requirements so they can vote and remove
/// pending inactive validators so they no longer can vote.
/// 4. The validator's voting power in the validator set is updated to be the corresponding staking pool's voting
// / power.
/// NOTE: THIS ONLY EXISTS FOR VENDOR TESTS
public(friend) fun on_new_epoch() acquires ValidatorConfig, ValidatorPerformance, ValidatorSet {
let validator_set = borrow_global_mut<ValidatorSet>(@diem_framework);
// let config = staking_config::get();
let validator_perf = borrow_global_mut<ValidatorPerformance>(@diem_framework);


// Update active validator set so that network address/public key change takes effect.
// Moreover, recalculate the total voting power, and deactivate the validator whose
// voting power is less than the minimum required stake.
let next_epoch_validators = vector::empty();
// let (minimum_stake, _) = staking_config::get_required_stake(&config);
let minimum_stake = 0;


let vlen = vector::length(&validator_set.active_validators);
let total_voting_power = 0;
let i = 0;
Expand Down Expand Up @@ -595,13 +622,8 @@ module diem_framework::stake {
validator_set.total_voting_power = total_voting_power;


// validator_set.total_joining_power = 0;

// Update validator indices, reset performance scores, and renew lockups.
validator_perf.validators = vector::empty();
// let recurring_lockup_duration_secs = 0;
//staking_config::get_recurring_lockup_duration(&config);


let vlen = vector::length(&validator_set.active_validators);
let validator_index = 0;
Expand Down
73 changes: 70 additions & 3 deletions framework/libra-framework/sources/ol_sources/grade.move
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,86 @@ module ol_framework::grade {
use diem_framework::stake;
use std::fixed_point32::{Self, FixedPoint32};

// use diem_std::debug::print;

/// what threshold of failed props should the network allow
/// one validator before jailing?
const FAILED_PROPS_THRESHOLD_PCT: u64 =20;

/// how far behind the leading validator by net proposals
/// should the trailing validator be allowed
const TRAILING_VALIDATOR_THRESHOLD: u64 = 5;


#[view]
/// returns if the validator passed or failed, and the number of proposals
/// and failures, and the ratio.
/// returns: is the validator compliant, proposed blocks, failed blocks, and the ratio of proposed to failed.
/// @return: tuple
/// bool: is the validator compliant,
/// u64: proposed blocks,
/// u64: failed blocks, and
/// FixedPoint32: the ratio of proposed to failed.

public fun get_validator_grade(node_addr: address): (bool, u64, u64, FixedPoint32) {
public fun get_validator_grade(node_addr: address, highest_net_props: u64): (bool, u64, u64, FixedPoint32) {
let idx = stake::get_validator_index(node_addr);
let (proposed, failed) = stake::get_current_epoch_proposal_counts(idx);

let compliant = proposed > failed;
// first pass: should have accepted proposals
if (proposed < failed) {
return (false, proposed, failed, fixed_point32::create_from_raw_value(0))
};

let compliant = has_good_success_ratio(proposed, failed) &&
does_not_trail(proposed, failed, highest_net_props);

// make failed at leat 1 to avoid division by zero
(compliant, proposed, failed, fixed_point32::create_from_rational(proposed, (failed + 1)))
}

/// Does the validator produce far more valid proposals than failed ones.
/// suggesting here an initial 80% threshold. (i.e. only allow 20% failed
/// proposals)
/// It's unclear what the right ratio should be. On problematic epochs
/// (i.e. low consensus) we may want to have some allowance for errors.
fun has_good_success_ratio(proposed: u64, failed: u64): bool {

let fail_ratio = if (proposed >= failed) {
// +1 to prevent denomiator zero error
fixed_point32::create_from_rational(failed, (proposed + 1))
} else { return false };

// failure ratio shoul dbe BELOW FAILED_PROPS_THRESHOLD_PCT
let is_above = fixed_point32::multiply_u64(100, fail_ratio) < FAILED_PROPS_THRESHOLD_PCT;

is_above

}

/// is this user very far behind the leading proposer.
/// in 0L we give all validator seats equal chances of proposing (leader).
/// However, the "leader reputation" algorithm in LibraBFT, will score
/// validators. The score is based on successful signing and propagation of
/// blocks. So people with lower scores might a) not be very competent or,
/// b) are in networks that are further removed from the majority (remote
/// data centers, home labs, etc). So we shouldn't bias too heavily on this
// performance metric, because it will reduce heterogeneity (the big D).
/// Unfortunately the reputation heuristic score is not on-chain
/// (perhaps a future optimization).
/// A good solution would be to drop the lowest % of RMS (root mean
/// squared) of proposal. Perhaps the lowest 10% of the validator set RMS.
/// But that's harder to understand by the validators (comprehension is
// important when we are trying to shape behavior).
/// So a simpler and more actionable metric might be:
/// drop anyone who proposed less than X% of the LEADING proposal.
/// This has an additional quality, which allows for the highest performing
// validators to be a kind of "vanguard" rider ("forerider"?) which sets the
// pace for the lowest performer.
fun does_not_trail(proposed: u64, failed: u64, highest_net_props: u64): bool {
if ((proposed > failed) && (highest_net_props > 0)) {
let net = proposed - failed;
let net_props_vs_leader= fixed_point32::create_from_rational(net,
highest_net_props);
fixed_point32::multiply_u64(100, net_props_vs_leader) > FAILED_PROPS_THRESHOLD_PCT
} else { false }
}
}
14 changes: 6 additions & 8 deletions framework/libra-framework/sources/ol_sources/mock.move
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ module ol_framework::mock {
#[test_only]
public fun mock_case_1(vm: &signer, addr: address){
assert!(stake::is_valid(addr), 01);
stake::mock_performance(vm, addr, 1, 0);
let (compliant, _, _, _) = grade::get_validator_grade(addr);
assert!(compliant, 777703);
stake::mock_performance(vm, addr, 100, 10);
let (compliant, _, _, _) = grade::get_validator_grade(addr, 100);
assert!(compliant, 01);
}


Expand All @@ -63,8 +63,8 @@ module ol_framework::mock {
public fun mock_case_4(vm: &signer, addr: address){
assert!(stake::is_valid(addr), 01);
stake::mock_performance(vm, addr, 0, 100); // 100 failing proposals
let (compliant, _, _, _) = grade::get_validator_grade(addr);
assert!(!compliant, 777703);
let (compliant, _, _, _) = grade::get_validator_grade(addr, 0);
assert!(!compliant, 02);
}

// Mock all nodes being compliant case 1
Expand Down Expand Up @@ -114,7 +114,7 @@ module ol_framework::mock {
// make all validators pay auction fee
// the clearing price in the fibonacci sequence is is 1
let (alice_bid, _) = proof_of_fee::current_bid(*vector::borrow(&vals, 0));
assert!(alice_bid == 1, 777703);
assert!(alice_bid == 1, 03);
(vals, bids, expiry)
}

Expand Down Expand Up @@ -311,8 +311,6 @@ module ol_framework::mock {

#[test(root = @ol_framework)]
public entry fun meta_val_perf(root: signer) {
// genesis();

let set = genesis_n_vals(&root, 4);
assert!(vector::length(&set) == 4, 7357001);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ module ol_framework::musical_chairs {
// TODO: use status.move is_operating
if (epoch < 2) return (validators, non_compliant_nodes, fixed_point32::create_from_rational(1, 1));


let (highest_net_props, _val) = stake::get_highest_net_proposer();
let i = 0;
while (i < val_set_len) {
let addr = *vector::borrow(&validators, i);
let (compliant, _, _, _) = grade::get_validator_grade(addr);
let (compliant, _, _, _) = grade::get_validator_grade(addr, highest_net_props);
// let compliant = true;
if (compliant) {
vector::push_back(&mut compliant_nodes, addr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,98 @@ module ol_framework::test_stake {
// now make Eve not compliant
let eve = @0x1000e;
mock::mock_case_4(&root, eve);
let (compliant, _, _, _) = grade::get_validator_grade(eve);
let (compliant, _, _, _) = grade::get_validator_grade(eve, 0);
assert!(!compliant, 735701);

}

// Scenario: There's one validator that is a straggler
// that validator should be dropped when we "grade"
// the validators.
// The validator has an acceptable success ratio
#[test(root = @ol_framework)]
fun drop_trailing(root: signer) {

let set = mock::genesis_n_vals(&root, 8);
testnet::unset(&root); // set to production mode

let default_valid_props = 500;
// populate some performance
let i = 0;
while (i < vector::length(&set)) {
let addr = vector::borrow(&set, i);

let valid_props = default_valid_props; // make all validators have
let invalid_props = 1;

stake::mock_performance(&root, *addr, valid_props, invalid_props); //
// increasing performance for each
i = i + 1;
};

// set alice to be "trailing"
// this will be less than 5% of the leading validator
stake::mock_performance(&root, @0x1000a, 5, 1);
stake::mock_performance(&root, @0x10011, 1000, 1);

let (highest_score, _addr) = stake::get_highest_net_proposer();

// LOWEST TRAILING VALIDATOR WILL BE OUT
let lowest_score = stake::get_val_net_proposals(@0x1000a);

assert!(highest_score > (lowest_score*20), 7357001);
let (a, _, _, _) = grade::get_validator_grade(@0x1000a, highest_score);
assert!(a == false, 73570002);

// Second lowest is fine
let (b, _, _, _) = grade::get_validator_grade(@0x1000c, highest_score);
assert!(b == true, 73570003);

// and top is also fine
let (top, _, _, _) = grade::get_validator_grade(@0x10011, highest_score);
assert!(top == true, 73570004);

}

// Scenario: one validator has too many failing
// proposals as a ratio to successful ones.
#[test(root = @ol_framework)]
fun drop_low_performance_ratio(root: signer) {

let set = mock::genesis_n_vals(&root, 8);
testnet::unset(&root); // set to production mode

let default_valid_props = 500;
// populate some performance
let i = 0;
while (i < vector::length(&set)) {
let addr = vector::borrow(&set, i);

let valid_props = default_valid_props; // make all validators have
let invalid_props = 1;

stake::mock_performance(&root, *addr, valid_props, invalid_props); //
// increasing performance for each
i = i + 1;
};

// alice is NOT trailing in proposals
// but the percent of failed proposals are too high
// above 10%
// in this example 40% failing proposals
stake::mock_performance(&root, @0x1000a, 500, 200);

let (highest_score, _addr) = stake::get_highest_net_proposer();

// Lots of failing proposals will make you drop out
let (a, _, _, _) = grade::get_validator_grade(@0x1000a, highest_score);
assert!(a == false, 73570002);

// Other accounts are ok
let (b, _, _, _) = grade::get_validator_grade(@0x1000c, highest_score);
assert!(b == true, 73570003);

}


}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
///////////////////////////////////////////////////////////////////////////
// 0L Module
// ValidatorUniverse
///////////////////////////////////////////////////////////////////////////

Expand All @@ -11,7 +9,6 @@ module diem_framework::validator_universe {
use ol_framework::vouch;
use diem_framework::stake;


#[test_only]
use ol_framework::testnet;
#[test_only]
Expand All @@ -27,8 +24,6 @@ module diem_framework::validator_universe {
validators: vector<address>
}

// * DEPRECATED JailBit struct, now in jail.move * //

// Genesis function to initialize ValidatorUniverse struct in 0x0.
// This is triggered in new epoch by Configuration in Genesis.move
// Function code: 01 Prefix: 220101
Expand Down

0 comments on commit 8b36588

Please sign in to comment.