Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signers-Voting Chore(s): Round Up & Round-Data Getter #4496

Merged
merged 14 commits into from Mar 16, 2024
30 changes: 25 additions & 5 deletions stackslib/src/chainstate/stacks/boot/signers-voting.clar
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
obycode marked this conversation as resolved.
Show resolved Hide resolved
;; 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
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);
obycode marked this conversation as resolved.
Show resolved Hide resolved

// 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