Skip to content

Commit d8b3287

Browse files
sirouk0o-de-lally
andcommitted
[move] performance metrics (#132)
Co-authored-by: 0o-de-lally <[email protected]>
1 parent fbdb421 commit d8b3287

File tree

6 files changed

+210
-40
lines changed

6 files changed

+210
-40
lines changed

framework/libra-framework/sources/modified_source/stake.move

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,31 @@ module diem_framework::stake {
188188
fees_table: Table<address, Coin<LibraCoin>>,
189189
}
190190

191+
#[view]
192+
/// @return: tuple
193+
/// - u64: number of proposals
194+
/// - address: the validator
195+
public fun get_highest_net_proposer(): (u64, address) acquires ValidatorSet,
196+
ValidatorPerformance, ValidatorConfig
197+
{
198+
let vals = get_current_validators();
199+
let highest_net_proposals = 0;
200+
let highest_addr = @0x0;
201+
vector::for_each(vals, |v| {
202+
let idx = get_validator_index(v);
203+
let (success, fail) = get_current_epoch_proposal_counts(idx);
204+
if (success > fail) {
205+
let net = success - fail;
206+
207+
if (net > highest_net_proposals) {
208+
highest_net_proposals = net;
209+
highest_addr = v;
210+
}
211+
}
212+
});
213+
(highest_net_proposals, highest_addr)
214+
}
215+
191216

192217
#[view]
193218
/// Returns the list of active validators
@@ -250,13 +275,29 @@ module diem_framework::stake {
250275
}
251276

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

287+
#[view]
288+
/// Get net proposals from address
289+
/// @return: u64: the number of net proposals (success - fail)
290+
public fun get_val_net_proposals(val: address): u64 acquires
291+
ValidatorPerformance, ValidatorConfig {
292+
let idx = get_validator_index(val);
293+
let (proposed, failed) = get_current_epoch_proposal_counts(idx);
294+
if (proposed > failed) {
295+
proposed - failed
296+
} else {
297+
0
298+
}
299+
}
300+
260301
#[view]
261302
/// Return the validator's config.
262303
public fun get_validator_config(validator_address: address): (vector<u8>, vector<u8>, vector<u8>) acquires ValidatorConfig {
@@ -314,9 +355,6 @@ module diem_framework::stake {
314355
if (account_address != operator) {
315356
set_operator(owner, operator)
316357
};
317-
// if (account_address != voter) {
318-
// set_delegated_voter(owner, voter)
319-
// };
320358
}
321359

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

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

558587

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

566-
567594
let vlen = vector::length(&validator_set.active_validators);
568595
let total_voting_power = 0;
569596
let i = 0;
@@ -595,13 +622,8 @@ module diem_framework::stake {
595622
validator_set.total_voting_power = total_voting_power;
596623

597624

598-
// validator_set.total_joining_power = 0;
599-
600625
// Update validator indices, reset performance scores, and renew lockups.
601626
validator_perf.validators = vector::empty();
602-
// let recurring_lockup_duration_secs = 0;
603-
//staking_config::get_recurring_lockup_duration(&config);
604-
605627

606628
let vlen = vector::length(&validator_set.active_validators);
607629
let validator_index = 0;

framework/libra-framework/sources/ol_sources/grade.move

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,86 @@ module ol_framework::grade {
1111
use diem_framework::stake;
1212
use std::fixed_point32::{Self, FixedPoint32};
1313

14+
// use diem_std::debug::print;
15+
16+
/// what threshold of failed props should the network allow
17+
/// one validator before jailing?
18+
const FAILED_PROPS_THRESHOLD_PCT: u64 =20;
19+
20+
/// how far behind the leading validator by net proposals
21+
/// should the trailing validator be allowed
22+
const TRAILING_VALIDATOR_THRESHOLD: u64 = 5;
23+
1424

1525
#[view]
1626
/// returns if the validator passed or failed, and the number of proposals
1727
/// and failures, and the ratio.
18-
/// returns: is the validator compliant, proposed blocks, failed blocks, and the ratio of proposed to failed.
28+
/// @return: tuple
29+
/// bool: is the validator compliant,
30+
/// u64: proposed blocks,
31+
/// u64: failed blocks, and
32+
/// FixedPoint32: the ratio of proposed to failed.
1933

20-
public fun get_validator_grade(node_addr: address): (bool, u64, u64, FixedPoint32) {
34+
public fun get_validator_grade(node_addr: address, highest_net_props: u64): (bool, u64, u64, FixedPoint32) {
2135
let idx = stake::get_validator_index(node_addr);
2236
let (proposed, failed) = stake::get_current_epoch_proposal_counts(idx);
2337

24-
let compliant = proposed > failed;
38+
// first pass: should have accepted proposals
39+
if (proposed < failed) {
40+
return (false, proposed, failed, fixed_point32::create_from_raw_value(0))
41+
};
42+
43+
let compliant = has_good_success_ratio(proposed, failed) &&
44+
does_not_trail(proposed, failed, highest_net_props);
45+
2546
// make failed at leat 1 to avoid division by zero
2647
(compliant, proposed, failed, fixed_point32::create_from_rational(proposed, (failed + 1)))
2748
}
2849

50+
/// Does the validator produce far more valid proposals than failed ones.
51+
/// suggesting here an initial 80% threshold. (i.e. only allow 20% failed
52+
/// proposals)
53+
/// It's unclear what the right ratio should be. On problematic epochs
54+
/// (i.e. low consensus) we may want to have some allowance for errors.
55+
fun has_good_success_ratio(proposed: u64, failed: u64): bool {
56+
57+
let fail_ratio = if (proposed >= failed) {
58+
// +1 to prevent denomiator zero error
59+
fixed_point32::create_from_rational(failed, (proposed + 1))
60+
} else { return false };
61+
62+
// failure ratio shoul dbe BELOW FAILED_PROPS_THRESHOLD_PCT
63+
let is_above = fixed_point32::multiply_u64(100, fail_ratio) < FAILED_PROPS_THRESHOLD_PCT;
64+
65+
is_above
66+
67+
}
68+
69+
/// is this user very far behind the leading proposer.
70+
/// in 0L we give all validator seats equal chances of proposing (leader).
71+
/// However, the "leader reputation" algorithm in LibraBFT, will score
72+
/// validators. The score is based on successful signing and propagation of
73+
/// blocks. So people with lower scores might a) not be very competent or,
74+
/// b) are in networks that are further removed from the majority (remote
75+
/// data centers, home labs, etc). So we shouldn't bias too heavily on this
76+
// performance metric, because it will reduce heterogeneity (the big D).
77+
/// Unfortunately the reputation heuristic score is not on-chain
78+
/// (perhaps a future optimization).
79+
/// A good solution would be to drop the lowest % of RMS (root mean
80+
/// squared) of proposal. Perhaps the lowest 10% of the validator set RMS.
81+
/// But that's harder to understand by the validators (comprehension is
82+
// important when we are trying to shape behavior).
83+
/// So a simpler and more actionable metric might be:
84+
/// drop anyone who proposed less than X% of the LEADING proposal.
85+
/// This has an additional quality, which allows for the highest performing
86+
// validators to be a kind of "vanguard" rider ("forerider"?) which sets the
87+
// pace for the lowest performer.
88+
fun does_not_trail(proposed: u64, failed: u64, highest_net_props: u64): bool {
89+
if ((proposed > failed) && (highest_net_props > 0)) {
90+
let net = proposed - failed;
91+
let net_props_vs_leader= fixed_point32::create_from_rational(net,
92+
highest_net_props);
93+
fixed_point32::multiply_u64(100, net_props_vs_leader) > FAILED_PROPS_THRESHOLD_PCT
94+
} else { false }
95+
}
2996
}

framework/libra-framework/sources/ol_sources/mock.move

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ module ol_framework::mock {
5252
#[test_only]
5353
public fun mock_case_1(vm: &signer, addr: address){
5454
assert!(stake::is_valid(addr), 01);
55-
stake::mock_performance(vm, addr, 1, 0);
56-
let (compliant, _, _, _) = grade::get_validator_grade(addr);
57-
assert!(compliant, 777703);
55+
stake::mock_performance(vm, addr, 100, 10);
56+
let (compliant, _, _, _) = grade::get_validator_grade(addr, 100);
57+
assert!(compliant, 01);
5858
}
5959

6060

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

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

@@ -311,8 +311,6 @@ module ol_framework::mock {
311311

312312
#[test(root = @ol_framework)]
313313
public entry fun meta_val_perf(root: signer) {
314-
// genesis();
315-
316314
let set = genesis_n_vals(&root, 4);
317315
assert!(vector::length(&set) == 4, 7357001);
318316

framework/libra-framework/sources/ol_sources/musical_chairs.move

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,11 @@ module ol_framework::musical_chairs {
148148
// TODO: use status.move is_operating
149149
if (epoch < 2) return (validators, non_compliant_nodes, fixed_point32::create_from_rational(1, 1));
150150

151-
151+
let (highest_net_props, _val) = stake::get_highest_net_proposer();
152152
let i = 0;
153153
while (i < val_set_len) {
154154
let addr = *vector::borrow(&validators, i);
155-
let (compliant, _, _, _) = grade::get_validator_grade(addr);
155+
let (compliant, _, _, _) = grade::get_validator_grade(addr, highest_net_props);
156156
// let compliant = true;
157157
if (compliant) {
158158
vector::push_back(&mut compliant_nodes, addr);

framework/libra-framework/sources/ol_sources/tests/stake.test.move

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,98 @@ module ol_framework::test_stake {
166166
// now make Eve not compliant
167167
let eve = @0x1000e;
168168
mock::mock_case_4(&root, eve);
169-
let (compliant, _, _, _) = grade::get_validator_grade(eve);
169+
let (compliant, _, _, _) = grade::get_validator_grade(eve, 0);
170170
assert!(!compliant, 735701);
171171

172172
}
173173

174+
// Scenario: There's one validator that is a straggler
175+
// that validator should be dropped when we "grade"
176+
// the validators.
177+
// The validator has an acceptable success ratio
178+
#[test(root = @ol_framework)]
179+
fun drop_trailing(root: signer) {
180+
181+
let set = mock::genesis_n_vals(&root, 8);
182+
testnet::unset(&root); // set to production mode
183+
184+
let default_valid_props = 500;
185+
// populate some performance
186+
let i = 0;
187+
while (i < vector::length(&set)) {
188+
let addr = vector::borrow(&set, i);
189+
190+
let valid_props = default_valid_props; // make all validators have
191+
let invalid_props = 1;
192+
193+
stake::mock_performance(&root, *addr, valid_props, invalid_props); //
194+
// increasing performance for each
195+
i = i + 1;
196+
};
197+
198+
// set alice to be "trailing"
199+
// this will be less than 5% of the leading validator
200+
stake::mock_performance(&root, @0x1000a, 5, 1);
201+
stake::mock_performance(&root, @0x10011, 1000, 1);
202+
203+
let (highest_score, _addr) = stake::get_highest_net_proposer();
204+
205+
// LOWEST TRAILING VALIDATOR WILL BE OUT
206+
let lowest_score = stake::get_val_net_proposals(@0x1000a);
207+
208+
assert!(highest_score > (lowest_score*20), 7357001);
209+
let (a, _, _, _) = grade::get_validator_grade(@0x1000a, highest_score);
210+
assert!(a == false, 73570002);
211+
212+
// Second lowest is fine
213+
let (b, _, _, _) = grade::get_validator_grade(@0x1000c, highest_score);
214+
assert!(b == true, 73570003);
215+
216+
// and top is also fine
217+
let (top, _, _, _) = grade::get_validator_grade(@0x10011, highest_score);
218+
assert!(top == true, 73570004);
219+
220+
}
221+
222+
// Scenario: one validator has too many failing
223+
// proposals as a ratio to successful ones.
224+
#[test(root = @ol_framework)]
225+
fun drop_low_performance_ratio(root: signer) {
226+
227+
let set = mock::genesis_n_vals(&root, 8);
228+
testnet::unset(&root); // set to production mode
229+
230+
let default_valid_props = 500;
231+
// populate some performance
232+
let i = 0;
233+
while (i < vector::length(&set)) {
234+
let addr = vector::borrow(&set, i);
235+
236+
let valid_props = default_valid_props; // make all validators have
237+
let invalid_props = 1;
238+
239+
stake::mock_performance(&root, *addr, valid_props, invalid_props); //
240+
// increasing performance for each
241+
i = i + 1;
242+
};
243+
244+
// alice is NOT trailing in proposals
245+
// but the percent of failed proposals are too high
246+
// above 10%
247+
// in this example 40% failing proposals
248+
stake::mock_performance(&root, @0x1000a, 500, 200);
249+
250+
let (highest_score, _addr) = stake::get_highest_net_proposer();
251+
252+
// Lots of failing proposals will make you drop out
253+
let (a, _, _, _) = grade::get_validator_grade(@0x1000a, highest_score);
254+
assert!(a == false, 73570002);
255+
256+
// Other accounts are ok
257+
let (b, _, _, _) = grade::get_validator_grade(@0x1000c, highest_score);
258+
assert!(b == true, 73570003);
259+
260+
}
261+
174262

175263
}

framework/libra-framework/sources/ol_sources/validator_universe.move

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
///////////////////////////////////////////////////////////////////////////
2-
// 0L Module
31
// ValidatorUniverse
42
///////////////////////////////////////////////////////////////////////////
53

@@ -11,7 +9,6 @@ module diem_framework::validator_universe {
119
use ol_framework::vouch;
1210
use diem_framework::stake;
1311

14-
1512
#[test_only]
1613
use ol_framework::testnet;
1714
#[test_only]
@@ -27,8 +24,6 @@ module diem_framework::validator_universe {
2724
validators: vector<address>
2825
}
2926

30-
// * DEPRECATED JailBit struct, now in jail.move * //
31-
3227
// Genesis function to initialize ValidatorUniverse struct in 0x0.
3328
// This is triggered in new epoch by Configuration in Genesis.move
3429
// Function code: 01 Prefix: 220101

0 commit comments

Comments
 (0)