Skip to content

Commit

Permalink
Merge pull request #4496 from stacks-network/chore/weight-round-up
Browse files Browse the repository at this point in the history
Signers-Voting Chore(s): Round Up & Round-Data Getter
  • Loading branch information
obycode authored Mar 16, 2024
2 parents 6278946 + 6e4113c commit d3f1a31
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 6 deletions.
30 changes: 25 additions & 5 deletions stackslib/src/chainstate/stacks/boot/signers-voting.clar
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
(define-constant pox-info
(unwrap-panic (contract-call? .pox-4 get-pox-info)))

;; Threshold consensus, expressed as parts-per-thousand to allow for integer
;; division with higher precision (e.g. 700 for 70%).
(define-constant threshold-consensus u700)
;; Threshold consensus, expressed as parts-per-hundred to allow for integer
;; division with higher precision (e.g. 70 for 70%).
(define-constant threshold-consensus u70)

;; Maps reward-cycle ids to last round
(define-map rounds uint uint)
Expand All @@ -39,6 +39,9 @@
;; necessary to recalculate it on every vote.
(define-map cycle-total-weight uint uint)

;; Maps voting data (count, current weight) per reward cycle & round
(define-map round-data {reward-cycle: uint, round: uint} {votes-count: uint, votes-weight: uint})

(define-read-only (burn-height-to-reward-cycle (height uint))
(/ (- height (get first-burnchain-block-height pox-info)) (get reward-cycle-length pox-info)))

Expand All @@ -54,6 +57,9 @@
(define-read-only (get-vote (reward-cycle uint) (round uint) (signer principal))
(map-get? votes {reward-cycle: reward-cycle, round: round, signer: signer}))

(define-read-only (get-round-info (reward-cycle uint) (round uint))
(map-get? round-data {reward-cycle: reward-cycle, round: round}))

(define-read-only (get-candidate-info (reward-cycle uint) (round uint) (candidate (buff 33)))
{candidate-weight: (default-to u0 (map-get? tally {reward-cycle: reward-cycle, round: round, aggregate-public-key: candidate})),
total-weight: (map-get? cycle-total-weight reward-cycle)})
Expand Down Expand Up @@ -81,6 +87,11 @@
(define-read-only (get-approved-aggregate-key (reward-cycle uint))
(map-get? aggregate-public-keys reward-cycle))

;; get the weight required for consensus threshold
(define-read-only (get-threshold-weight (reward-cycle uint))
(let ((total-weight (default-to u0 (map-get? cycle-total-weight reward-cycle))))
(/ (+ (* total-weight threshold-consensus) u99) u100)))

(define-private (is-in-voting-window (height uint) (reward-cycle uint))
(let ((last-cycle (unwrap-panic (contract-call? .signers get-last-set-cycle))))
(and (is-eq last-cycle reward-cycle)
Expand Down Expand Up @@ -134,7 +145,12 @@
;; vote by signer weight
(signer-weight (try! (get-signer-weight signer-index reward-cycle)))
(new-total (+ signer-weight (default-to u0 (map-get? tally tally-key))))
(total-weight (try! (get-and-cache-total-weight reward-cycle))))
(cached-weight (try! (get-and-cache-total-weight reward-cycle)))
(threshold-weight (get-threshold-weight reward-cycle))
(current-round (default-to {
votes-count: u0,
votes-weight: u0} (map-get? round-data {reward-cycle: reward-cycle, round: round})))
)
;; Check that the key has not yet been set for this reward cycle
(asserts! (is-none (map-get? aggregate-public-keys reward-cycle)) (err ERR_OUT_OF_VOTING_WINDOW))
;; Check that the aggregate public key is the correct length
Expand All @@ -147,6 +163,10 @@
(try! (update-last-round reward-cycle round))
;; Update the tally for this aggregate public key candidate
(map-set tally tally-key new-total)
;; Update the current round data
(map-set round-data {reward-cycle: reward-cycle, round: round} {
votes-count: (+ (get votes-count current-round) u1),
votes-weight: (+ (get votes-weight current-round) signer-weight)})
;; Update used aggregate public keys
(map-set used-aggregate-public-keys key reward-cycle)
(print {
Expand All @@ -158,7 +178,7 @@
new-total: new-total,
})
;; If the new total weight is greater than or equal to the threshold consensus
(if (>= (/ (* new-total u1000) total-weight) threshold-consensus)
(if (>= new-total threshold-weight)
;; Save this approved aggregate public key for this reward cycle.
;; If there is not already a key for this cycle, the insert will
;; return true and an event will be created.
Expand Down
121 changes: 120 additions & 1 deletion stackslib/src/chainstate/stacks/boot/signers_voting_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ use crate::chainstate::stacks::boot::pox_2_tests::{
use crate::chainstate::stacks::boot::pox_4_tests::{
assert_latest_was_burn, get_last_block_sender_transactions, get_tip, make_test_epochs_pox,
};
use crate::chainstate::stacks::boot::signers_tests::{get_signer_index, prepare_signers_test};
use crate::chainstate::stacks::boot::signers_tests::{
get_signer_index, prepare_signers_test, readonly_call,
};
use crate::chainstate::stacks::boot::{
BOOT_CODE_COST_VOTING_TESTNET as BOOT_CODE_COST_VOTING, BOOT_CODE_POX_TESTNET, SIGNERS_NAME,
SIGNERS_VOTING_NAME,
Expand Down Expand Up @@ -2048,6 +2050,123 @@ fn vote_for_aggregate_public_key_mixed_rounds() {
assert_eq!(alice_vote_tx.events.len(), 0);
}

// In this test case, Alice & Bob advance through setup & check
// the round info from the very first reward cycle & round.
#[test]
fn test_get_round_info() {
// Test setup
let alice = TestStacker::from_seed(&[3, 4]);
let bob = TestStacker::from_seed(&[5, 6]);
let observer = TestEventObserver::new();

// Alice - Signer 1
let alice_key = &alice.signer_private_key;
let alice_address = key_to_stacks_addr(alice_key);
let alice_principal = PrincipalData::from(alice_address);

// Bob - Signer 2
let bob_key = &bob.signer_private_key;
let bob_address = key_to_stacks_addr(bob_key);
let bob_principal = PrincipalData::from(bob_address);

let (mut peer, test_signers, latest_block_id, current_reward_cycle) = prepare_signers_test(
function_name!(),
vec![
(alice_principal.clone(), 1000),
(bob_principal.clone(), 1000),
],
&[alice.clone(), bob.clone()],
Some(&observer),
);

// Get the current creward cycle
let cycle_id = current_reward_cycle;

let round_info = get_round_info(&mut peer, latest_block_id, cycle_id, 0)
.unwrap()
.expect_tuple()
.unwrap();
let votes_count = round_info.get("votes-count").unwrap();
let votes_weight = round_info.get("votes-weight").unwrap();

assert_eq!(votes_count, &Value::UInt(2));
assert_eq!(votes_weight, &Value::UInt(4));
}

pub fn get_round_info(
peer: &mut TestPeer<'_>,
latest_block_id: StacksBlockId,
reward_cycle: u128,
round: u128,
) -> Option<Value> {
let round_tuple = readonly_call(
peer,
&latest_block_id,
"signers-voting".into(),
"get-round-info".into(),
vec![Value::UInt(reward_cycle), Value::UInt(round)],
)
.expect_optional()
.unwrap();
round_tuple
}

// In this test case, Alice & Bob advance through setup & check
// the weight threshold info from the very first reward cycle & round.
#[test]
fn test_get_threshold_weight() {
// Test setup
let alice = TestStacker::from_seed(&[3, 4]);
let bob = TestStacker::from_seed(&[5, 6]);
let observer = TestEventObserver::new();

// Alice - Signer 1
let alice_key = &alice.signer_private_key;
let alice_address = key_to_stacks_addr(alice_key);
let alice_principal = PrincipalData::from(alice_address);

// Bob - Signer 2
let bob_key = &bob.signer_private_key;
let bob_address = key_to_stacks_addr(bob_key);
let bob_principal = PrincipalData::from(bob_address);

let (mut peer, test_signers, latest_block_id, current_reward_cycle) = prepare_signers_test(
function_name!(),
vec![
(alice_principal.clone(), 1000),
(bob_principal.clone(), 1000),
],
&[alice.clone(), bob.clone()],
Some(&observer),
);

// Get the current creward cycle
let cycle_id = current_reward_cycle;

// Call get-threshold-weight
let threshold_weight: u128 = get_threshold_weight(&mut peer, latest_block_id, cycle_id);

// Since there are four votes, the threshold weight should be 3 (75% of 4)
assert_eq!(threshold_weight, 3);
}

pub fn get_threshold_weight(
peer: &mut TestPeer<'_>,
latest_block_id: StacksBlockId,
reward_cycle: u128,
) -> u128 {
let threshold_weight = readonly_call(
peer,
&latest_block_id,
"signers-voting".into(),
"get-threshold-weight".into(),
vec![Value::UInt(reward_cycle)],
)
.expect_u128()
.unwrap();
threshold_weight
}

fn nakamoto_tenure(
peer: &mut TestPeer,
test_signers: &mut TestSigners,
Expand Down

0 comments on commit d3f1a31

Please sign in to comment.