mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-06-17 10:51:22 +08:00
Merge branch 'feat/nakamoto-block-inv' of https://github.com/stacks-network/stacks-core into feat/nakamoto-block-inv
This commit is contained in:
@@ -15,3 +15,15 @@ path = "../../stackslib/src/chainstate/stacks/boot/pox-4.clar"
|
||||
depends_on = []
|
||||
clarity = 2
|
||||
epoch = 2.4
|
||||
|
||||
[contracts.signers]
|
||||
path = "../../stackslib/src/chainstate/stacks/boot/signers.clar"
|
||||
depends_on = []
|
||||
clarity = 2
|
||||
epoch = 2.4
|
||||
|
||||
[contracts.signers-voting]
|
||||
path = "../../stackslib/src/chainstate/stacks/boot/signers-voting.clar"
|
||||
depends_on = []
|
||||
clarity = 2
|
||||
epoch = 2.4
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Cl } from "@stacks/transactions";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
const accounts = simnet.getAccounts();
|
||||
const alice = accounts.get("wallet_1")!;
|
||||
const bob = accounts.get("wallet_2")!;
|
||||
const charlie = accounts.get("wallet_3")!;
|
||||
|
||||
const ERR_SIGNER_INDEX_MISMATCH = 10000;
|
||||
const ERR_INVALID_SIGNER_INDEX = 10001;
|
||||
const ERR_OUT_OF_VOTING_WINDOW = 10002
|
||||
const ERR_OLD_ROUND = 10003;
|
||||
const ERR_ILL_FORMED_AGGREGATE_PUBLIC_KEY = 10004;
|
||||
const ERR_DUPLICATE_AGGREGATE_PUBLIC_KEY = 10005;
|
||||
const ERR_DUPLICATE_VOTE = 10006;
|
||||
const ERR_INVALID_BURN_BLOCK_HEIGHT = 10007
|
||||
|
||||
const KEY_1 = "123456789a123456789a123456789a123456789a123456789a123456789a010203";
|
||||
const KEY_2 = "123456789a123456789a123456789a123456789a123456789a123456789ab0b1b2";
|
||||
const SIGNERS_VOTING = "signers-voting";
|
||||
|
||||
describe("test signers-voting contract voting rounds", () => {
|
||||
describe("test pox-info", () => {
|
||||
it("should return correct burn-height", () => {
|
||||
const { result:result1 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"reward-cycle-to-burn-height",
|
||||
[Cl.uint(1)],
|
||||
alice)
|
||||
expect(result1).toEqual(Cl.uint(1050))
|
||||
|
||||
const { result:result2 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"reward-cycle-to-burn-height",
|
||||
[Cl.uint(2)],
|
||||
alice)
|
||||
expect(result2).toEqual(Cl.uint(2100))
|
||||
})
|
||||
|
||||
it("should return correct reward-cycle", () => {
|
||||
const { result: result1 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"burn-height-to-reward-cycle",
|
||||
[Cl.uint(1)],
|
||||
alice)
|
||||
expect(result1).toEqual(Cl.uint(0))
|
||||
|
||||
const { result: result2000 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"burn-height-to-reward-cycle",
|
||||
[Cl.uint(2000)],
|
||||
alice)
|
||||
expect(result2000).toEqual(Cl.uint(1))
|
||||
})
|
||||
|
||||
it("should return true if in prepare phase", () => {
|
||||
const { result:result999 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"is-in-prepare-phase",
|
||||
[Cl.uint(999)],
|
||||
alice)
|
||||
expect(result999).toEqual(Cl.bool(false))
|
||||
|
||||
const { result } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"is-in-prepare-phase",
|
||||
[Cl.uint(1000)],
|
||||
alice)
|
||||
expect(result).toEqual(Cl.bool(true))
|
||||
|
||||
const { result: result1001 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"is-in-prepare-phase",
|
||||
[Cl.uint(1001)],
|
||||
alice)
|
||||
expect(result1001).toEqual(Cl.bool(true))
|
||||
|
||||
|
||||
const { result: result0 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"is-in-prepare-phase",
|
||||
[Cl.uint(1049)],
|
||||
alice)
|
||||
expect(result0).toEqual(Cl.bool(true))
|
||||
|
||||
const { result: result1 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"is-in-prepare-phase",
|
||||
[Cl.uint(1050)],
|
||||
alice)
|
||||
expect(result1).toEqual(Cl.bool(false))
|
||||
|
||||
const { result: result2 } = simnet.callReadOnlyFn(SIGNERS_VOTING,
|
||||
"is-in-prepare-phase",
|
||||
[Cl.uint(1051)],
|
||||
alice)
|
||||
expect(result2).toEqual(Cl.bool(false))
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
@@ -127,7 +127,9 @@ fn create_event_info_data_code(
|
||||
start-burn-height: {start_burn_height},
|
||||
;; how long to lock, in burn blocks
|
||||
;; equal to args[3]
|
||||
lock-period: {lock_period}
|
||||
lock-period: {lock_period},
|
||||
;; equal to args[4]
|
||||
signer-key: {signer_key}
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
@@ -135,6 +137,7 @@ fn create_event_info_data_code(
|
||||
lock_period = &args[3],
|
||||
pox_addr = &args[1],
|
||||
start_burn_height = &args[2],
|
||||
signer_key = &args.get(3).map_or("none".to_string(), |v| v.to_string()),
|
||||
)
|
||||
}
|
||||
"delegate-stack-stx" => {
|
||||
@@ -244,12 +247,15 @@ fn create_event_info_data_code(
|
||||
;; equal to args[0]
|
||||
extend-count: {extend_count},
|
||||
;; new unlock burnchain block height
|
||||
unlock-burn-height: new-unlock-ht
|
||||
unlock-burn-height: new-unlock-ht,
|
||||
;; equal to args[2]
|
||||
signer-key: {signer_key}
|
||||
}}
|
||||
}})
|
||||
"#,
|
||||
extend_count = &args[0],
|
||||
pox_addr = &args[1],
|
||||
signer_key = &args.get(2).map_or("none".to_string(), |v| v.to_string()),
|
||||
)
|
||||
}
|
||||
"delegate-stack-extend" => {
|
||||
@@ -307,12 +313,15 @@ fn create_event_info_data_code(
|
||||
(unwrap-panic (map-get? logged-partial-stacked-by-cycle
|
||||
{{ pox-addr: {pox_addr}, sender: tx-sender, reward-cycle: {reward_cycle} }}))),
|
||||
;; delegator (this is the caller)
|
||||
delegator: tx-sender
|
||||
delegator: tx-sender,
|
||||
;; equal to args[2]
|
||||
signer-key: {signer_key}
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
pox_addr = &args[0],
|
||||
reward_cycle = &args[1]
|
||||
reward_cycle = &args[1],
|
||||
signer_key = &args.get(2).map_or("none".to_string(), |v| v.to_string()),
|
||||
)
|
||||
}
|
||||
"delegate-stx" => {
|
||||
|
||||
@@ -105,6 +105,7 @@ pub fn boot_nakamoto<'a>(
|
||||
mut initial_balances: Vec<(PrincipalData, u64)>,
|
||||
test_signers: &TestSigners,
|
||||
test_stackers: Option<Vec<&TestStacker>>,
|
||||
observer: Option<&'a TestEventObserver>,
|
||||
) -> TestPeer<'a> {
|
||||
let aggregate_public_key = test_signers.aggregate_public_key.clone();
|
||||
let mut peer_config = TestPeerConfig::new(test_name, 0, 0);
|
||||
@@ -135,7 +136,7 @@ pub fn boot_nakamoto<'a>(
|
||||
(0..test_signers.num_keys)
|
||||
.map(|index| {
|
||||
let stacker_private_key = StacksPrivateKey::from_seed(&index.to_be_bytes());
|
||||
let signer_private_key = StacksPrivateKey::from_seed(&(index + 1000).to_be_bytes());
|
||||
let signer_private_key = StacksPrivateKey::from_seed(&index.to_be_bytes());
|
||||
TestStacker {
|
||||
stacker_private_key,
|
||||
signer_private_key,
|
||||
@@ -151,7 +152,7 @@ pub fn boot_nakamoto<'a>(
|
||||
.map(|test_stacker| {
|
||||
(
|
||||
PrincipalData::from(key_to_stacks_addr(&test_stacker.stacker_private_key)),
|
||||
u64::try_from(test_stacker.amount).expect("Stacking amount too large"),
|
||||
u64::try_from(test_stacker.amount + 10000).expect("Stacking amount too large"),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@@ -163,7 +164,7 @@ pub fn boot_nakamoto<'a>(
|
||||
peer_config.burnchain.pox_constants.v3_unlock_height = 27;
|
||||
peer_config.burnchain.pox_constants.pox_4_activation_height = 31;
|
||||
peer_config.test_stackers = Some(test_stackers.clone());
|
||||
let mut peer = TestPeer::new(peer_config);
|
||||
let mut peer = TestPeer::new_with_observer(peer_config, observer);
|
||||
|
||||
advance_to_nakamoto(&mut peer, &test_signers, test_stackers);
|
||||
|
||||
@@ -296,7 +297,7 @@ fn replay_reward_cycle(
|
||||
#[test]
|
||||
fn test_simple_nakamoto_coordinator_bootup() {
|
||||
let mut test_signers = TestSigners::default();
|
||||
let mut peer = boot_nakamoto(function_name!(), vec![], &test_signers, None);
|
||||
let mut peer = boot_nakamoto(function_name!(), vec![], &test_signers, None, None);
|
||||
|
||||
let (burn_ops, mut tenure_change, miner_key) =
|
||||
peer.begin_nakamoto_tenure(TenureChangeCause::BlockFound);
|
||||
@@ -357,6 +358,7 @@ fn test_simple_nakamoto_coordinator_1_tenure_10_blocks() {
|
||||
vec![(addr.into(), 100_000_000)],
|
||||
&test_signers,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let (burn_ops, mut tenure_change, miner_key) =
|
||||
@@ -479,6 +481,7 @@ fn test_nakamoto_chainstate_getters() {
|
||||
vec![(addr.into(), 100_000_000)],
|
||||
&test_signers,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let sort_tip = {
|
||||
@@ -968,6 +971,7 @@ pub fn simple_nakamoto_coordinator_10_tenures_10_sortitions<'a>() -> TestPeer<'a
|
||||
vec![(addr.into(), 100_000_000)],
|
||||
&test_signers,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut all_blocks = vec![];
|
||||
@@ -1295,6 +1299,7 @@ pub fn simple_nakamoto_coordinator_2_tenures_3_sortitions<'a>() -> TestPeer<'a>
|
||||
vec![(addr.into(), 100_000_000)],
|
||||
&test_signers,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut rc_burn_ops = vec![];
|
||||
@@ -1630,6 +1635,7 @@ pub fn simple_nakamoto_coordinator_10_extended_tenures_10_sortitions() -> TestPe
|
||||
vec![(addr.into(), 100_000_000)],
|
||||
&test_signers,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut all_blocks = vec![];
|
||||
|
||||
@@ -1502,7 +1502,7 @@ fn make_fork_run_with_arrivals(
|
||||
#[test]
|
||||
pub fn test_get_highest_nakamoto_tenure() {
|
||||
let test_signers = TestSigners::default();
|
||||
let mut peer = boot_nakamoto(function_name!(), vec![], &test_signers, None);
|
||||
let mut peer = boot_nakamoto(function_name!(), vec![], &test_signers, None, None);
|
||||
|
||||
// extract chainstate and sortdb -- we don't need the peer anymore
|
||||
let chainstate = &mut peer.stacks_node.as_mut().unwrap().chainstate;
|
||||
@@ -1644,7 +1644,7 @@ pub fn test_get_highest_nakamoto_tenure() {
|
||||
#[test]
|
||||
fn test_make_miners_stackerdb_config() {
|
||||
let test_signers = TestSigners::default();
|
||||
let mut peer = boot_nakamoto(function_name!(), vec![], &test_signers, None);
|
||||
let mut peer = boot_nakamoto(function_name!(), vec![], &test_signers, None, None);
|
||||
|
||||
let naka_miner_hash160 = peer.miner.nakamoto_miner_hash160();
|
||||
let miner_keys: Vec<_> = (0..10).map(|_| StacksPrivateKey::new()).collect();
|
||||
|
||||
@@ -79,6 +79,7 @@ pub const POX_2_NAME: &'static str = "pox-2";
|
||||
pub const POX_3_NAME: &'static str = "pox-3";
|
||||
pub const POX_4_NAME: &'static str = "pox-4";
|
||||
pub const SIGNERS_NAME: &'static str = "signers";
|
||||
pub const SIGNERS_VOTING_NAME: &'static str = "signers-voting";
|
||||
/// This is the name of a variable in the `.signers` contract which tracks the most recently updated
|
||||
/// reward cycle number.
|
||||
pub const SIGNERS_UPDATE_STATE: &'static str = "last-set-cycle";
|
||||
@@ -89,6 +90,7 @@ const POX_2_BODY: &'static str = std::include_str!("pox-2.clar");
|
||||
const POX_3_BODY: &'static str = std::include_str!("pox-3.clar");
|
||||
const POX_4_BODY: &'static str = std::include_str!("pox-4.clar");
|
||||
pub const SIGNERS_BODY: &'static str = std::include_str!("signers.clar");
|
||||
const SIGNERS_VOTING_BODY: &'static str = std::include_str!("signers-voting.clar");
|
||||
|
||||
pub const COSTS_1_NAME: &'static str = "costs";
|
||||
pub const COSTS_2_NAME: &'static str = "costs-2";
|
||||
@@ -117,6 +119,7 @@ lazy_static! {
|
||||
pub static ref POX_3_TESTNET_CODE: String =
|
||||
format!("{}\n{}", BOOT_CODE_POX_TESTNET_CONSTS, POX_3_BODY);
|
||||
pub static ref POX_4_CODE: String = format!("{}", POX_4_BODY);
|
||||
pub static ref SIGNER_VOTING_CODE: String = format!("{}", SIGNERS_VOTING_BODY);
|
||||
pub static ref BOOT_CODE_COST_VOTING_TESTNET: String = make_testnet_cost_voting();
|
||||
pub static ref STACKS_BOOT_CODE_MAINNET: [(&'static str, &'static str); 6] = [
|
||||
("pox", &BOOT_CODE_POX_MAINNET),
|
||||
@@ -1307,6 +1310,8 @@ pub mod pox_3_tests;
|
||||
pub mod pox_4_tests;
|
||||
#[cfg(test)]
|
||||
mod signers_tests;
|
||||
#[cfg(test)]
|
||||
pub mod signers_voting_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
@@ -1314,7 +1319,9 @@ pub mod test {
|
||||
use std::convert::From;
|
||||
use std::fs;
|
||||
|
||||
use clarity::boot_util::boot_code_addr;
|
||||
use clarity::vm::contracts::Contract;
|
||||
use clarity::vm::tests::symbols_from_values;
|
||||
use clarity::vm::types::*;
|
||||
use stacks_common::util::hash::to_hex;
|
||||
use stacks_common::util::*;
|
||||
@@ -1874,6 +1881,30 @@ pub mod test {
|
||||
make_tx(key, nonce, 0, payload)
|
||||
}
|
||||
|
||||
pub fn make_signers_vote_for_aggregate_public_key(
|
||||
key: &StacksPrivateKey,
|
||||
nonce: u64,
|
||||
signer_index: u128,
|
||||
aggregate_public_key: &Point,
|
||||
round: u128,
|
||||
) -> StacksTransaction {
|
||||
let aggregate_public_key = Value::buff_from(aggregate_public_key.compress().data.to_vec())
|
||||
.expect("Failed to serialize aggregate public key");
|
||||
let payload = TransactionPayload::new_contract_call(
|
||||
boot_code_test_addr(),
|
||||
SIGNERS_VOTING_NAME,
|
||||
"vote-for-aggregate-public-key",
|
||||
vec![
|
||||
Value::UInt(signer_index),
|
||||
aggregate_public_key,
|
||||
Value::UInt(round),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
// TODO set tx_fee back to 0 once these txs are free
|
||||
make_tx(key, nonce, 1, payload)
|
||||
}
|
||||
|
||||
pub fn make_pox_2_increase(
|
||||
key: &StacksPrivateKey,
|
||||
nonce: u64,
|
||||
|
||||
@@ -82,7 +82,7 @@ const ERR_REUSED_SIGNER_KEY: i128 = 33;
|
||||
|
||||
/// Return the BlockSnapshot for the latest sortition in the provided
|
||||
/// SortitionDB option-reference. Panics on any errors.
|
||||
fn get_tip(sortdb: Option<&SortitionDB>) -> BlockSnapshot {
|
||||
pub fn get_tip(sortdb: Option<&SortitionDB>) -> BlockSnapshot {
|
||||
SortitionDB::get_canonical_burn_chain_tip(&sortdb.unwrap().conn()).unwrap()
|
||||
}
|
||||
|
||||
|
||||
105
stackslib/src/chainstate/stacks/boot/signers-voting.clar
Normal file
105
stackslib/src/chainstate/stacks/boot/signers-voting.clar
Normal file
@@ -0,0 +1,105 @@
|
||||
;;
|
||||
;; @contract voting for the aggregate public key
|
||||
;;
|
||||
|
||||
;; maps dkg round and signer to proposed aggregate public key
|
||||
(define-map votes {reward-cycle: uint, round: uint, signer: principal} {aggregate-public-key: (buff 33), reward-slots: uint})
|
||||
;; maps dkg round and aggregate public key to weights of signers supporting this key so far
|
||||
(define-map tally {reward-cycle: uint, round: uint, aggregate-public-key: (buff 33)} uint)
|
||||
;; maps aggregate public keys to rewards cycles and rounds
|
||||
(define-map used-aggregate-public-keys (buff 33) {reward-cycle: uint, round: uint})
|
||||
|
||||
(define-constant err-signer-index-mismatch (err u10000))
|
||||
(define-constant err-invalid-signer-index (err u10001))
|
||||
(define-constant err-out-of-voting-window (err u10002))
|
||||
(define-constant err-old-round (err u10003))
|
||||
(define-constant err-ill-formed-aggregate-public-key (err u10004))
|
||||
(define-constant err-duplicate-aggregate-public-key (err u10005))
|
||||
(define-constant err-duplicate-vote (err u10006))
|
||||
(define-constant err-invalid-burn-block-height (err u10007))
|
||||
|
||||
(define-constant pox-info
|
||||
(unwrap-panic (contract-call? .pox-4 get-pox-info)))
|
||||
|
||||
;; maps reward-cycle ids to last round
|
||||
(define-map rounds uint uint)
|
||||
|
||||
(define-data-var state-1 {reward-cycle: uint, round: uint, aggregate-public-key: (optional (buff 33)),
|
||||
total-votes: uint} {reward-cycle: u0, round: u0, aggregate-public-key: none, total-votes: u0})
|
||||
(define-data-var state-2 {reward-cycle: uint, round: uint, aggregate-public-key: (optional (buff 33)),
|
||||
total-votes: uint} {reward-cycle: u0, round: u0, aggregate-public-key: none, total-votes: u0})
|
||||
|
||||
;; get voting info by burn block height
|
||||
(define-read-only (get-info (height uint))
|
||||
(ok (at-block (unwrap! (get-block-info? id-header-hash height) err-invalid-burn-block-height) (get-current-info))))
|
||||
|
||||
;; get current voting info
|
||||
(define-read-only (get-current-info)
|
||||
(var-get state-1))
|
||||
|
||||
(define-read-only (burn-height-to-reward-cycle (height uint))
|
||||
(/ (- height (get first-burnchain-block-height pox-info)) (get reward-cycle-length pox-info)))
|
||||
|
||||
(define-read-only (reward-cycle-to-burn-height (reward-cycle uint))
|
||||
(+ (* reward-cycle (get reward-cycle-length pox-info)) (get first-burnchain-block-height pox-info)))
|
||||
|
||||
(define-read-only (current-reward-cycle)
|
||||
(burn-height-to-reward-cycle burn-block-height))
|
||||
|
||||
(define-read-only (get-last-round (reward-cycle uint))
|
||||
(map-get? rounds reward-cycle))
|
||||
|
||||
(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-tally (reward-cycle uint) (round uint) (aggregate-public-key (buff 33)))
|
||||
(map-get? tally {reward-cycle: reward-cycle, round: round, aggregate-public-key: aggregate-public-key}))
|
||||
|
||||
(define-read-only (get-signer-slots (signer-index uint) (reward-cycle uint))
|
||||
(let ((height (reward-cycle-to-burn-height reward-cycle)))
|
||||
(ok (at-block
|
||||
(unwrap! (get-block-info? id-header-hash height) err-invalid-burn-block-height)
|
||||
(get-current-signer-slots signer-index)))))
|
||||
|
||||
(define-read-only (get-current-signer-slots (signer-index uint))
|
||||
(let ((details (unwrap! (unwrap-panic (contract-call? .signers stackerdb-get-signer-by-index signer-index)) err-invalid-signer-index)))
|
||||
(asserts! (is-eq (get signer details) tx-sender) err-signer-index-mismatch)
|
||||
(ok (get num-slots details))))
|
||||
|
||||
;; aggregate public key must be unique and can be used only in a single cycle-round pair
|
||||
(define-read-only (is-valid-aggregated-public-key (key (buff 33)) (dkg-id {reward-cycle: uint, round: uint}))
|
||||
(is-eq (default-to dkg-id (map-get? used-aggregate-public-keys key)) dkg-id))
|
||||
|
||||
(define-read-only (is-in-prepare-phase (height uint))
|
||||
(< (mod (+ (- height (get first-burnchain-block-height pox-info))
|
||||
(get prepare-cycle-length pox-info))
|
||||
(get reward-cycle-length pox-info)
|
||||
)
|
||||
(get prepare-cycle-length pox-info)))
|
||||
|
||||
(define-private (is-in-voting-window (height uint) (reward-cycle uint))
|
||||
(let ((last-cycle (unwrap-panic (contract-call? .signers stackerdb-get-last-set-cycle))))
|
||||
(and (is-eq last-cycle reward-cycle)
|
||||
(is-in-prepare-phase height))))
|
||||
|
||||
(define-public (vote-for-aggregate-public-key (signer-index uint) (key (buff 33)) (round uint))
|
||||
(let ((reward-cycle (+ u1 (burn-height-to-reward-cycle burn-block-height)))
|
||||
(tally-key {reward-cycle: reward-cycle, round: round, aggregate-public-key: key})
|
||||
;; one slot, one vote
|
||||
(num-slots (try! (get-current-signer-slots signer-index)))
|
||||
(new-total (+ num-slots (default-to u0 (map-get? tally tally-key)))))
|
||||
(asserts! (is-in-voting-window burn-block-height reward-cycle) err-out-of-voting-window)
|
||||
(asserts! (>= round (default-to u0 (map-get? rounds reward-cycle))) err-old-round)
|
||||
(asserts! (is-eq (len key) u33) err-ill-formed-aggregate-public-key)
|
||||
(asserts! (is-valid-aggregated-public-key key {reward-cycle: reward-cycle, round: round}) err-duplicate-aggregate-public-key)
|
||||
(asserts! (map-insert votes {reward-cycle: reward-cycle, round: round, signer: tx-sender} {aggregate-public-key: key, reward-slots: num-slots}) err-duplicate-vote)
|
||||
(map-set tally tally-key new-total)
|
||||
(map-set used-aggregate-public-keys key {reward-cycle: reward-cycle, round: round})
|
||||
(update-last-round reward-cycle round)
|
||||
(print "voted")
|
||||
(ok true)))
|
||||
|
||||
(define-private (update-last-round (reward-cycle uint) (round uint))
|
||||
(match (map-get? rounds reward-cycle)
|
||||
last-round (and (> round last-round) (map-set rounds reward-cycle round))
|
||||
(map-set rounds reward-cycle round)))
|
||||
@@ -13,6 +13,10 @@
|
||||
(define-read-only (stackerdb-get-signer-slots)
|
||||
(ok (var-get stackerdb-signer-slots)))
|
||||
|
||||
(define-read-only (stackerdb-get-signer-by-index (signer-index uint))
|
||||
(ok (element-at (var-get stackerdb-signer-slots) signer-index))
|
||||
)
|
||||
|
||||
(define-read-only (stackerdb-get-config)
|
||||
(ok
|
||||
{ chunk-size: CHUNK_SIZE,
|
||||
@@ -21,3 +25,6 @@
|
||||
max-neighbors: u32,
|
||||
hint-replicas: (list) }
|
||||
))
|
||||
|
||||
(define-read-only (stackerdb-get-last-set-cycle)
|
||||
(ok (var-get last-set-cycle)))
|
||||
@@ -18,7 +18,9 @@ use clarity::vm::clarity::ClarityConnection;
|
||||
use clarity::vm::contexts::OwnedEnvironment;
|
||||
use clarity::vm::costs::LimitedCostTracker;
|
||||
use clarity::vm::tests::symbols_from_values;
|
||||
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, TupleData};
|
||||
use clarity::vm::types::{
|
||||
PrincipalData, QualifiedContractIdentifier, StacksAddressExtensions, TupleData,
|
||||
};
|
||||
use clarity::vm::Value::Principal;
|
||||
use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value};
|
||||
use stacks_common::address::AddressHashMode;
|
||||
@@ -44,7 +46,7 @@ use crate::chainstate::stacks::boot::pox_4_tests::{
|
||||
use crate::chainstate::stacks::boot::test::{
|
||||
instantiate_pox_peer_with_epoch, key_to_stacks_addr, make_pox_4_lockup, with_sortdb,
|
||||
};
|
||||
use crate::chainstate::stacks::boot::{NakamotoSignerEntry, SIGNERS_NAME};
|
||||
use crate::chainstate::stacks::boot::{NakamotoSignerEntry, SIGNERS_NAME, SIGNERS_VOTING_NAME};
|
||||
use crate::chainstate::stacks::db::StacksChainState;
|
||||
use crate::chainstate::stacks::index::marf::MarfConnection;
|
||||
use crate::chainstate::stacks::{
|
||||
@@ -197,8 +199,12 @@ fn signers_get_signer_keys_from_stackerdb() {
|
||||
let stacker_1 = TestStacker::from_seed(&[3, 4]);
|
||||
let stacker_2 = TestStacker::from_seed(&[5, 6]);
|
||||
|
||||
let (mut peer, test_signers, latest_block_id) =
|
||||
prepare_signers_test(function_name!(), Some(vec![&stacker_1, &stacker_2]));
|
||||
let (mut peer, test_signers, latest_block_id, _) = prepare_signers_test(
|
||||
function_name!(),
|
||||
vec![],
|
||||
Some(vec![&stacker_1, &stacker_2]),
|
||||
None,
|
||||
);
|
||||
|
||||
let private_key = peer.config.private_key.clone();
|
||||
|
||||
@@ -239,13 +245,21 @@ fn signers_get_signer_keys_from_stackerdb() {
|
||||
assert_eq!(signers, expected_stackerdb_slots);
|
||||
}
|
||||
|
||||
fn prepare_signers_test<'a>(
|
||||
pub fn prepare_signers_test<'a>(
|
||||
test_name: &str,
|
||||
initial_balances: Vec<(PrincipalData, u64)>,
|
||||
stackers: Option<Vec<&TestStacker>>,
|
||||
) -> (TestPeer<'a>, TestSigners, StacksBlockId) {
|
||||
observer: Option<&'a TestEventObserver>,
|
||||
) -> (TestPeer<'a>, TestSigners, StacksBlockId, u128) {
|
||||
let mut test_signers = TestSigners::default();
|
||||
|
||||
let mut peer = boot_nakamoto(test_name, vec![], &test_signers, stackers);
|
||||
let mut peer = boot_nakamoto(
|
||||
test_name,
|
||||
initial_balances,
|
||||
&test_signers,
|
||||
stackers,
|
||||
observer,
|
||||
);
|
||||
|
||||
let (burn_ops, mut tenure_change, miner_key) =
|
||||
peer.begin_nakamoto_tenure(TenureChangeCause::BlockFound);
|
||||
@@ -269,7 +283,30 @@ fn prepare_signers_test<'a>(
|
||||
);
|
||||
let latest_block_id = blocks_and_sizes.last().unwrap().0.block_id();
|
||||
|
||||
(peer, test_signers, latest_block_id)
|
||||
let current_reward_cycle = readonly_call(
|
||||
&mut peer,
|
||||
&latest_block_id,
|
||||
SIGNERS_VOTING_NAME.into(),
|
||||
"current-reward-cycle".into(),
|
||||
vec![],
|
||||
)
|
||||
.expect_u128();
|
||||
|
||||
assert_eq!(current_reward_cycle, 7);
|
||||
|
||||
let last_set_cycle = readonly_call(
|
||||
&mut peer,
|
||||
&latest_block_id,
|
||||
SIGNERS_NAME.into(),
|
||||
"stackerdb-get-last-set-cycle".into(),
|
||||
vec![],
|
||||
)
|
||||
.expect_result_ok()
|
||||
.expect_u128();
|
||||
|
||||
assert_eq!(last_set_cycle, 7);
|
||||
|
||||
(peer, test_signers, latest_block_id, current_reward_cycle)
|
||||
}
|
||||
|
||||
fn advance_blocks(
|
||||
@@ -355,3 +392,33 @@ fn readonly_call(
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_signer_index(
|
||||
peer: &mut TestPeer<'_>,
|
||||
latest_block_id: StacksBlockId,
|
||||
signer_address: StacksAddress,
|
||||
) -> u128 {
|
||||
let signers = readonly_call(
|
||||
peer,
|
||||
&latest_block_id,
|
||||
"signers".into(),
|
||||
"stackerdb-get-signer-slots".into(),
|
||||
vec![],
|
||||
)
|
||||
.expect_result_ok()
|
||||
.expect_list();
|
||||
|
||||
signers
|
||||
.iter()
|
||||
.position(|value| {
|
||||
value
|
||||
.clone()
|
||||
.expect_tuple()
|
||||
.get("signer")
|
||||
.unwrap()
|
||||
.clone()
|
||||
.expect_principal()
|
||||
== signer_address.to_account_principal()
|
||||
})
|
||||
.expect("signer not found") as u128
|
||||
}
|
||||
|
||||
435
stackslib/src/chainstate/stacks/boot/signers_voting_tests.rs
Normal file
435
stackslib/src/chainstate/stacks/boot/signers_voting_tests.rs
Normal file
@@ -0,0 +1,435 @@
|
||||
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
|
||||
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use clarity::boot_util::boot_code_addr;
|
||||
use clarity::vm::clarity::ClarityConnection;
|
||||
use clarity::vm::contexts::OwnedEnvironment;
|
||||
use clarity::vm::contracts::Contract;
|
||||
use clarity::vm::costs::{CostOverflowingMath, LimitedCostTracker};
|
||||
use clarity::vm::database::*;
|
||||
use clarity::vm::errors::{
|
||||
CheckErrors, Error, IncomparableError, InterpreterError, InterpreterResult, RuntimeErrorType,
|
||||
};
|
||||
use clarity::vm::eval;
|
||||
use clarity::vm::events::StacksTransactionEvent;
|
||||
use clarity::vm::representations::SymbolicExpression;
|
||||
use clarity::vm::tests::{execute, is_committed, is_err_code, symbols_from_values};
|
||||
use clarity::vm::types::Value::Response;
|
||||
use clarity::vm::types::{
|
||||
BuffData, OptionalData, PrincipalData, QualifiedContractIdentifier, ResponseData, SequenceData,
|
||||
StacksAddressExtensions, StandardPrincipalData, TupleData, TupleTypeSignature, TypeSignature,
|
||||
Value, NONE,
|
||||
};
|
||||
use stacks_common::address::AddressHashMode;
|
||||
use stacks_common::types::chainstate::{
|
||||
BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, VRFSeed,
|
||||
};
|
||||
use stacks_common::types::Address;
|
||||
use stacks_common::util::hash::{hex_bytes, to_hex, Sha256Sum, Sha512Trunc256Sum};
|
||||
use stacks_common::util::secp256k1::Secp256k1PrivateKey;
|
||||
use wsts::curve::point::{Compressed, Point};
|
||||
|
||||
use super::test::*;
|
||||
use super::RawRewardSetEntry;
|
||||
use crate::burnchains::{Burnchain, PoxConstants};
|
||||
use crate::chainstate::burn::db::sortdb::{self, SortitionDB};
|
||||
use crate::chainstate::burn::operations::*;
|
||||
use crate::chainstate::burn::{BlockSnapshot, ConsensusHash};
|
||||
use crate::chainstate::nakamoto::coordinator::tests::make_token_transfer;
|
||||
use crate::chainstate::nakamoto::tests::get_account;
|
||||
use crate::chainstate::nakamoto::tests::node::{TestSigners, TestStacker};
|
||||
use crate::chainstate::nakamoto::NakamotoBlock;
|
||||
use crate::chainstate::stacks::address::{PoxAddress, PoxAddressType20, PoxAddressType32};
|
||||
use crate::chainstate::stacks::boot::pox_2_tests::{
|
||||
check_pox_print_event, generate_pox_clarity_value, get_reward_set_entries_at,
|
||||
get_stacking_state_pox, get_stx_account_at, with_clarity_db_ro, PoxPrintFields,
|
||||
StackingStateCheckData,
|
||||
};
|
||||
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::{
|
||||
BOOT_CODE_COST_VOTING_TESTNET as BOOT_CODE_COST_VOTING, BOOT_CODE_POX_TESTNET, SIGNERS_NAME,
|
||||
SIGNERS_VOTING_NAME,
|
||||
};
|
||||
use crate::chainstate::stacks::db::{
|
||||
MinerPaymentSchedule, StacksChainState, StacksHeaderInfo, MINER_REWARD_MATURITY,
|
||||
};
|
||||
use crate::chainstate::stacks::events::{StacksTransactionReceipt, TransactionOrigin};
|
||||
use crate::chainstate::stacks::index::marf::MarfConnection;
|
||||
use crate::chainstate::stacks::index::MarfTrieId;
|
||||
use crate::chainstate::stacks::tests::make_coinbase;
|
||||
use crate::chainstate::{self, stacks::*};
|
||||
use crate::clarity_vm::clarity::{ClarityBlockConnection, Error as ClarityError};
|
||||
use crate::clarity_vm::database::marf::{MarfedKV, WritableMarfStore};
|
||||
use crate::clarity_vm::database::HeadersDBConn;
|
||||
use crate::core::*;
|
||||
use crate::net::test::{TestEventObserver, TestPeer};
|
||||
use crate::util_lib::boot::boot_code_id;
|
||||
use crate::util_lib::db::{DBConn, FromRow};
|
||||
|
||||
pub fn prepare_pox4_test<'a>(
|
||||
test_name: &str,
|
||||
observer: Option<&'a TestEventObserver>,
|
||||
) -> (
|
||||
Burnchain,
|
||||
TestPeer<'a>,
|
||||
Vec<StacksPrivateKey>,
|
||||
StacksBlockId,
|
||||
u64,
|
||||
usize,
|
||||
) {
|
||||
let (epochs, pox_constants) = make_test_epochs_pox();
|
||||
|
||||
let mut burnchain = Burnchain::default_unittest(
|
||||
0,
|
||||
&BurnchainHeaderHash::from_hex(BITCOIN_REGTEST_FIRST_BLOCK_HASH).unwrap(),
|
||||
);
|
||||
burnchain.pox_constants = pox_constants.clone();
|
||||
|
||||
let (mut peer, keys) =
|
||||
instantiate_pox_peer_with_epoch(&burnchain, test_name, Some(epochs.clone()), observer);
|
||||
|
||||
assert_eq!(burnchain.pox_constants.reward_slots(), 6);
|
||||
let mut coinbase_nonce = 0;
|
||||
|
||||
// Advance into pox4
|
||||
let target_height = burnchain.pox_constants.pox_4_activation_height;
|
||||
let mut latest_block = peer.tenure_with_txs(&[], &mut coinbase_nonce);
|
||||
while get_tip(peer.sortdb.as_ref()).block_height < u64::from(target_height) {
|
||||
latest_block = peer.tenure_with_txs(&[], &mut coinbase_nonce);
|
||||
// if we reach epoch 2.1, perform the check
|
||||
if get_tip(peer.sortdb.as_ref()).block_height > epochs[3].start_height {
|
||||
assert_latest_was_burn(&mut peer);
|
||||
}
|
||||
}
|
||||
|
||||
let block_height = get_tip(peer.sortdb.as_ref()).block_height;
|
||||
|
||||
info!("Block height: {}", block_height);
|
||||
|
||||
(
|
||||
burnchain,
|
||||
peer,
|
||||
keys,
|
||||
latest_block,
|
||||
block_height,
|
||||
coinbase_nonce,
|
||||
)
|
||||
}
|
||||
|
||||
/// In this test case, Alice votes in the first block of the first tenure of the prepare phase.
|
||||
/// Alice can vote successfully.
|
||||
/// A second vote on the same key and round fails with "duplicate vote" error
|
||||
#[test]
|
||||
fn vote_for_aggregate_public_key_in_first_block() {
|
||||
let stacker_1 = TestStacker::from_seed(&[3, 4]);
|
||||
let stacker_2 = TestStacker::from_seed(&[5, 6]);
|
||||
let observer = TestEventObserver::new();
|
||||
|
||||
let signer = key_to_stacks_addr(&stacker_1.signer_private_key).to_account_principal();
|
||||
|
||||
let (mut peer, mut test_signers, latest_block_id, current_reward_cycle) = prepare_signers_test(
|
||||
function_name!(),
|
||||
vec![(signer, 1000)],
|
||||
Some(vec![&stacker_1, &stacker_2]),
|
||||
Some(&observer),
|
||||
);
|
||||
|
||||
// create vote txs
|
||||
|
||||
let signer_nonce = 0;
|
||||
let signer_key = &stacker_1.signer_private_key;
|
||||
let signer_address = key_to_stacks_addr(signer_key);
|
||||
let signer_principal = PrincipalData::from(signer_address);
|
||||
let cycle_id = current_reward_cycle;
|
||||
|
||||
let signer_index = get_signer_index(&mut peer, latest_block_id, signer_address);
|
||||
|
||||
let aggregate_public_key: Point = Point::new();
|
||||
let aggreagte_public_key_value =
|
||||
Value::buff_from(aggregate_public_key.compress().data.to_vec())
|
||||
.expect("Failed to serialize aggregate public key");
|
||||
|
||||
let txs = vec![
|
||||
// cast a vote for the aggregate public key
|
||||
make_signers_vote_for_aggregate_public_key(
|
||||
signer_key,
|
||||
signer_nonce,
|
||||
signer_index,
|
||||
&aggregate_public_key,
|
||||
0,
|
||||
),
|
||||
// cast the vote twice
|
||||
make_signers_vote_for_aggregate_public_key(
|
||||
signer_key,
|
||||
signer_nonce + 1,
|
||||
signer_index,
|
||||
&aggregate_public_key,
|
||||
0,
|
||||
),
|
||||
];
|
||||
|
||||
//
|
||||
// vote in the first burn block of prepare phase
|
||||
//
|
||||
let blocks_and_sizes = nakamoto_tenure(&mut peer, &mut test_signers, vec![txs], signer_key);
|
||||
|
||||
// check the last two txs in the last block
|
||||
let block = observer.get_blocks().last().unwrap().clone();
|
||||
let receipts = block.receipts.as_slice();
|
||||
assert_eq!(receipts.len(), 4);
|
||||
// ignore tenure change tx
|
||||
// ignore tenure coinbase tx
|
||||
|
||||
// first vote should succeed
|
||||
let tx1 = &receipts[receipts.len() - 2];
|
||||
assert_eq!(
|
||||
tx1.result,
|
||||
Value::Response(ResponseData {
|
||||
committed: true,
|
||||
data: Box::new(Value::Bool(true))
|
||||
})
|
||||
);
|
||||
|
||||
// second vote should fail with duplicate vote error
|
||||
let tx2 = &receipts[receipts.len() - 1];
|
||||
assert_eq!(
|
||||
tx2.result,
|
||||
Value::Response(ResponseData {
|
||||
committed: false,
|
||||
data: Box::new(Value::UInt(10006)) // err-duplicate-vote
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/// In this test case, Alice votes in the first block of the last tenure of the prepare phase.
|
||||
/// Bob votes in the second block of that tenure.
|
||||
/// Alice can vote successfully.
|
||||
/// Bob is out of the voting window.
|
||||
#[test]
|
||||
fn vote_for_aggregate_public_key_in_last_block() {
|
||||
let stacker_1 = TestStacker::from_seed(&[3, 4]);
|
||||
let stacker_2 = TestStacker::from_seed(&[5, 6]);
|
||||
let observer = TestEventObserver::new();
|
||||
|
||||
let signer_1 = key_to_stacks_addr(&stacker_1.signer_private_key).to_account_principal();
|
||||
let signer_2 = key_to_stacks_addr(&stacker_2.signer_private_key).to_account_principal();
|
||||
|
||||
let (mut peer, mut test_signers, latest_block_id, current_reward_cycle) = prepare_signers_test(
|
||||
function_name!(),
|
||||
vec![(signer_1, 1000), (signer_2, 1000)],
|
||||
Some(vec![&stacker_1, &stacker_2]),
|
||||
Some(&observer),
|
||||
);
|
||||
|
||||
let mut stacker_1_nonce: u64 = 1;
|
||||
let dummy_tx_1 = make_dummy_tx(
|
||||
&mut peer,
|
||||
&stacker_1.stacker_private_key,
|
||||
&mut stacker_1_nonce,
|
||||
);
|
||||
let dummy_tx_2 = make_dummy_tx(
|
||||
&mut peer,
|
||||
&stacker_1.stacker_private_key,
|
||||
&mut stacker_1_nonce,
|
||||
);
|
||||
let dummy_tx_3 = make_dummy_tx(
|
||||
&mut peer,
|
||||
&stacker_1.stacker_private_key,
|
||||
&mut stacker_1_nonce,
|
||||
);
|
||||
|
||||
let cycle_id: u128 = current_reward_cycle;
|
||||
let aggregated_public_key: Point = Point::new();
|
||||
|
||||
// create vote txs for alice
|
||||
let signer_1_nonce = 0;
|
||||
let signer_1_key = &stacker_1.signer_private_key;
|
||||
let signer_1_address = key_to_stacks_addr(signer_1_key);
|
||||
let signer_1_principal = PrincipalData::from(signer_1_address);
|
||||
let signer_1_index = get_signer_index(&mut peer, latest_block_id, signer_1_address);
|
||||
|
||||
let txs_1 = vec![
|
||||
// cast a vote for the aggregate public key
|
||||
make_signers_vote_for_aggregate_public_key(
|
||||
signer_1_key,
|
||||
signer_1_nonce,
|
||||
signer_1_index,
|
||||
&aggregated_public_key,
|
||||
0,
|
||||
),
|
||||
// cast the vote twice
|
||||
make_signers_vote_for_aggregate_public_key(
|
||||
signer_1_key,
|
||||
signer_1_nonce + 1,
|
||||
signer_1_index,
|
||||
&aggregated_public_key,
|
||||
0,
|
||||
),
|
||||
];
|
||||
|
||||
// create vote txs for bob
|
||||
let signer_2_nonce = 0;
|
||||
let signer_2_key = &stacker_2.signer_private_key;
|
||||
let signer_2_address = key_to_stacks_addr(signer_2_key);
|
||||
let signer_2_principal = PrincipalData::from(signer_2_address);
|
||||
let signer_2_index = get_signer_index(&mut peer, latest_block_id, signer_2_address);
|
||||
|
||||
let txs_2 = vec![
|
||||
// cast a vote for the aggregate public key
|
||||
make_signers_vote_for_aggregate_public_key(
|
||||
signer_2_key,
|
||||
signer_2_nonce,
|
||||
signer_2_index,
|
||||
&aggregated_public_key,
|
||||
0,
|
||||
),
|
||||
];
|
||||
|
||||
//
|
||||
// vote in the last burn block of prepare phase
|
||||
//
|
||||
|
||||
nakamoto_tenure(
|
||||
&mut peer,
|
||||
&mut test_signers,
|
||||
vec![vec![dummy_tx_1]],
|
||||
signer_1_key,
|
||||
);
|
||||
|
||||
nakamoto_tenure(
|
||||
&mut peer,
|
||||
&mut test_signers,
|
||||
vec![vec![dummy_tx_2]],
|
||||
signer_1_key,
|
||||
);
|
||||
|
||||
// alice votes in first block of tenure
|
||||
// bob votes in second block of tenure
|
||||
let blocks_and_sizes = nakamoto_tenure(
|
||||
&mut peer,
|
||||
&mut test_signers,
|
||||
vec![txs_1, txs_2],
|
||||
signer_1_key,
|
||||
);
|
||||
|
||||
// check alice's and bob's txs
|
||||
let blocks = observer.get_blocks();
|
||||
// alice's block
|
||||
let block = &blocks[blocks.len() - 2].clone();
|
||||
let receipts = &block.receipts;
|
||||
assert_eq!(receipts.len(), 4);
|
||||
|
||||
// first vote should succeed
|
||||
let tx1 = &receipts[receipts.len() - 2];
|
||||
assert_eq!(
|
||||
tx1.result,
|
||||
Value::Response(ResponseData {
|
||||
committed: true,
|
||||
data: Box::new(Value::Bool(true))
|
||||
})
|
||||
);
|
||||
|
||||
// second vote should fail with duplicate vote error
|
||||
let tx2 = &receipts[receipts.len() - 1];
|
||||
assert_eq!(
|
||||
tx2.result,
|
||||
Value::Response(ResponseData {
|
||||
committed: false,
|
||||
data: Box::new(Value::UInt(10006)) // err-duplicate-vote
|
||||
})
|
||||
);
|
||||
|
||||
// bob's block
|
||||
let block = blocks.last().unwrap().clone();
|
||||
let receipts = block.receipts.as_slice();
|
||||
assert_eq!(receipts.len(), 1);
|
||||
|
||||
// vote should succeed
|
||||
let tx1 = &receipts[receipts.len() - 1];
|
||||
assert_eq!(
|
||||
tx1.result,
|
||||
Value::Response(ResponseData {
|
||||
committed: false,
|
||||
data: Box::new(Value::UInt(10002)) // err-out-of-voting-window
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
fn nakamoto_tenure(
|
||||
peer: &mut TestPeer,
|
||||
test_signers: &mut TestSigners,
|
||||
txs_of_blocks: Vec<Vec<StacksTransaction>>,
|
||||
stacker_private_key: &StacksPrivateKey,
|
||||
) -> Vec<(NakamotoBlock, u64, ExecutionCost)> {
|
||||
let current_height = peer.get_burnchain_view().unwrap().burn_block_height;
|
||||
|
||||
info!("current height: {}", current_height);
|
||||
|
||||
let (burn_ops, mut tenure_change, miner_key) =
|
||||
peer.begin_nakamoto_tenure(TenureChangeCause::BlockFound);
|
||||
|
||||
let (_, _, consensus_hash) = peer.next_burnchain_block(burn_ops);
|
||||
|
||||
let vrf_proof = peer.make_nakamoto_vrf_proof(miner_key);
|
||||
|
||||
tenure_change.tenure_consensus_hash = consensus_hash.clone();
|
||||
tenure_change.burn_view_consensus_hash = consensus_hash.clone();
|
||||
let tenure_change_tx = peer
|
||||
.miner
|
||||
.make_nakamoto_tenure_change(tenure_change.clone());
|
||||
let coinbase_tx = peer.miner.make_nakamoto_coinbase(None, vrf_proof);
|
||||
let recipient_addr = boot_code_addr(false);
|
||||
let mut mutable_txs_of_blocks = txs_of_blocks.clone();
|
||||
mutable_txs_of_blocks.reverse();
|
||||
let blocks_and_sizes = peer.make_nakamoto_tenure(
|
||||
tenure_change_tx,
|
||||
coinbase_tx.clone(),
|
||||
test_signers,
|
||||
|miner, chainstate, sortdb, blocks| mutable_txs_of_blocks.pop().unwrap_or(vec![]),
|
||||
);
|
||||
info!("tenure length {}", blocks_and_sizes.len());
|
||||
blocks_and_sizes
|
||||
}
|
||||
|
||||
fn make_dummy_tx(
|
||||
peer: &mut TestPeer,
|
||||
private_key: &StacksPrivateKey,
|
||||
nonce: &mut u64,
|
||||
) -> StacksTransaction {
|
||||
peer.with_db_state(|sortdb, chainstate, _, _| {
|
||||
let addr = key_to_stacks_addr(&private_key);
|
||||
let account = get_account(chainstate, sortdb, &addr);
|
||||
let recipient_addr = boot_code_addr(false);
|
||||
let stx_transfer = make_token_transfer(
|
||||
chainstate,
|
||||
sortdb,
|
||||
&private_key,
|
||||
*nonce,
|
||||
1,
|
||||
1,
|
||||
&recipient_addr,
|
||||
);
|
||||
*nonce += 1;
|
||||
Ok(stx_transfer)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -49,6 +49,7 @@ use crate::chainstate::stacks::boot::{
|
||||
BOOT_TEST_POX_4_AGG_KEY_CONTRACT, BOOT_TEST_POX_4_AGG_KEY_FNAME, COSTS_2_NAME, COSTS_3_NAME,
|
||||
MINERS_NAME, POX_2_MAINNET_CODE, POX_2_NAME, POX_2_TESTNET_CODE, POX_3_MAINNET_CODE,
|
||||
POX_3_NAME, POX_3_TESTNET_CODE, POX_4_CODE, POX_4_NAME, SIGNERS_BODY, SIGNERS_NAME,
|
||||
SIGNERS_VOTING_NAME, SIGNER_VOTING_CODE,
|
||||
};
|
||||
use crate::chainstate::stacks::db::{StacksAccount, StacksChainState};
|
||||
use crate::chainstate::stacks::events::{StacksTransactionEvent, StacksTransactionReceipt};
|
||||
@@ -1436,7 +1437,7 @@ impl<'a, 'b> ClarityBlockConnection<'a, 'b> {
|
||||
&boot_code_account,
|
||||
ASTRules::PrecheckSize,
|
||||
)
|
||||
.expect("FATAL: Failed to process .miners contract initialization");
|
||||
.expect("FATAL: Failed to process .signers contract initialization");
|
||||
receipt
|
||||
});
|
||||
|
||||
@@ -1449,6 +1450,43 @@ impl<'a, 'b> ClarityBlockConnection<'a, 'b> {
|
||||
);
|
||||
}
|
||||
|
||||
let signers_voting_code = &*SIGNER_VOTING_CODE;
|
||||
let signers_voting_contract_id = boot_code_id(SIGNERS_VOTING_NAME, mainnet);
|
||||
let payload = TransactionPayload::SmartContract(
|
||||
TransactionSmartContract {
|
||||
name: ContractName::try_from(SIGNERS_VOTING_NAME)
|
||||
.expect("FATAL: invalid boot-code contract name"),
|
||||
code_body: StacksString::from_str(signers_voting_code)
|
||||
.expect("FATAL: invalid boot code body"),
|
||||
},
|
||||
Some(ClarityVersion::Clarity2),
|
||||
);
|
||||
|
||||
let signers_contract_tx =
|
||||
StacksTransaction::new(tx_version.clone(), boot_code_auth.clone(), payload);
|
||||
|
||||
let signers_voting_initialization_receipt = self.as_transaction(|tx_conn| {
|
||||
// initialize with a synthetic transaction
|
||||
debug!("Instantiate {} contract", &signers_voting_contract_id);
|
||||
let receipt = StacksChainState::process_transaction_payload(
|
||||
tx_conn,
|
||||
&signers_contract_tx,
|
||||
&boot_code_account,
|
||||
ASTRules::PrecheckSize,
|
||||
)
|
||||
.expect("FATAL: Failed to process .signers-voting contract initialization");
|
||||
receipt
|
||||
});
|
||||
|
||||
if signers_voting_initialization_receipt.result != Value::okay_true()
|
||||
|| signers_voting_initialization_receipt.post_condition_aborted
|
||||
{
|
||||
panic!(
|
||||
"FATAL: Failure processing signers-voting contract initialization: {:#?}",
|
||||
&signers_voting_initialization_receipt
|
||||
);
|
||||
}
|
||||
|
||||
debug!("Epoch 2.5 initialized");
|
||||
(old_cost_tracker, Ok(vec![pox_4_initialization_receipt]))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user