mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-05-29 23:43:02 +08:00
Merge branch 'next' into feat/expand-new-block-event
This commit is contained in:
@@ -47,6 +47,7 @@ stacks-signer = { path = "../../stacks-signer" }
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
wsts = {workspace = true}
|
||||
mutants = "0.0.3"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "=0.24.2"
|
||||
|
||||
@@ -29,7 +29,7 @@ use stacks::burnchains::{
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::burn::operations::{
|
||||
BlockstackOperationType, DelegateStxOp, LeaderBlockCommitOp, LeaderKeyRegisterOp, PreStxOp,
|
||||
TransferStxOp, UserBurnSupportOp,
|
||||
TransferStxOp,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use stacks::chainstate::burn::Opcodes;
|
||||
@@ -900,8 +900,7 @@ impl BitcoinRegtestController {
|
||||
BlockstackOperationType::LeaderBlockCommit(_)
|
||||
| BlockstackOperationType::LeaderKeyRegister(_)
|
||||
| BlockstackOperationType::StackStx(_)
|
||||
| BlockstackOperationType::DelegateStx(_)
|
||||
| BlockstackOperationType::UserBurnSupport(_) => {
|
||||
| BlockstackOperationType::DelegateStx(_) => {
|
||||
unimplemented!();
|
||||
}
|
||||
BlockstackOperationType::PreStx(payload) => {
|
||||
@@ -1664,16 +1663,6 @@ impl BitcoinRegtestController {
|
||||
true
|
||||
}
|
||||
|
||||
fn build_user_burn_support_tx(
|
||||
&mut self,
|
||||
_epoch_id: StacksEpochId,
|
||||
_payload: UserBurnSupportOp,
|
||||
_signer: &mut BurnchainOpSigner,
|
||||
_attempt: u64,
|
||||
) -> Option<Transaction> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Send a serialized tx to the Bitcoin node. Return Some(txid) on successful send; None on
|
||||
/// failure.
|
||||
pub fn send_transaction(&self, transaction: SerializedTx) -> Option<Txid> {
|
||||
@@ -1830,9 +1819,6 @@ impl BitcoinRegtestController {
|
||||
BlockstackOperationType::LeaderKeyRegister(payload) => {
|
||||
self.build_leader_key_register_tx(epoch_id, payload, op_signer, attempt)
|
||||
}
|
||||
BlockstackOperationType::UserBurnSupport(payload) => {
|
||||
self.build_user_burn_support_tx(epoch_id, payload, op_signer, attempt)
|
||||
}
|
||||
BlockstackOperationType::PreStx(payload) => {
|
||||
self.build_pre_stacks_tx(epoch_id, payload, op_signer)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use stacks::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandleTx};
|
||||
use stacks::chainstate::burn::operations::leader_block_commit::BURN_BLOCK_MINED_AT_MODULUS;
|
||||
use stacks::chainstate::burn::operations::{
|
||||
BlockstackOperationType, DelegateStxOp, LeaderBlockCommitOp, LeaderKeyRegisterOp, PreStxOp,
|
||||
StackStxOp, TransferStxOp, UserBurnSupportOp,
|
||||
StackStxOp, TransferStxOp,
|
||||
};
|
||||
use stacks::chainstate::burn::BlockSnapshot;
|
||||
use stacks::core::{
|
||||
@@ -221,21 +221,6 @@ impl BurnchainController for MocknetController {
|
||||
burn_header_hash: next_block_header.block_hash,
|
||||
})
|
||||
}
|
||||
BlockstackOperationType::UserBurnSupport(payload) => {
|
||||
BlockstackOperationType::UserBurnSupport(UserBurnSupportOp {
|
||||
address: payload.address,
|
||||
consensus_hash: payload.consensus_hash,
|
||||
public_key: payload.public_key,
|
||||
key_block_ptr: payload.key_block_ptr,
|
||||
key_vtxindex: payload.key_vtxindex,
|
||||
block_header_hash_160: payload.block_header_hash_160,
|
||||
burn_fee: payload.burn_fee,
|
||||
txid: payload.txid,
|
||||
vtxindex: payload.vtxindex,
|
||||
block_height: next_block_header.block_height,
|
||||
burn_header_hash: next_block_header.block_hash,
|
||||
})
|
||||
}
|
||||
BlockstackOperationType::PreStx(payload) => {
|
||||
BlockstackOperationType::PreStx(PreStxOp {
|
||||
block_height: next_block_header.block_height,
|
||||
|
||||
@@ -657,7 +657,6 @@ pub mod tests {
|
||||
0x41a3ed94d3cb0a84,
|
||||
]),
|
||||
candidate: block_commit_1.clone(),
|
||||
user_burns: vec![],
|
||||
},
|
||||
BurnSamplePoint {
|
||||
burns: block_commit_2.burn_fee.into(),
|
||||
@@ -675,7 +674,6 @@ pub mod tests {
|
||||
0x8347db29a7961508,
|
||||
]),
|
||||
candidate: block_commit_2.clone(),
|
||||
user_burns: vec![],
|
||||
},
|
||||
BurnSamplePoint {
|
||||
burns: (block_commit_3.burn_fee).into(),
|
||||
@@ -688,7 +686,6 @@ pub mod tests {
|
||||
]),
|
||||
range_end: Uint256::max(),
|
||||
candidate: block_commit_3.clone(),
|
||||
user_burns: vec![],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
use std::fs;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::{fs, thread};
|
||||
|
||||
use clarity::vm::costs::ExecutionCost;
|
||||
use clarity::vm::types::{AssetIdentifier, PrincipalData, QualifiedContractIdentifier};
|
||||
@@ -13,7 +12,6 @@ use rand::RngCore;
|
||||
use stacks::burnchains::bitcoin::BitcoinNetworkType;
|
||||
use stacks::burnchains::{Burnchain, MagicBytes, BLOCKSTACK_MAGIC_MAINNET};
|
||||
use stacks::chainstate::nakamoto::signer_set::NakamotoSigners;
|
||||
use stacks::chainstate::nakamoto::test_signers::TestSigners;
|
||||
use stacks::chainstate::stacks::boot::MINERS_NAME;
|
||||
use stacks::chainstate::stacks::index::marf::MARFOpenOpts;
|
||||
use stacks::chainstate::stacks::index::storage::TrieHashCalculationMode;
|
||||
@@ -34,7 +32,6 @@ use stacks::net::connection::ConnectionOptions;
|
||||
use stacks::net::{Neighbor, NeighborKey};
|
||||
use stacks::util_lib::boot::boot_code_id;
|
||||
use stacks::util_lib::db::Error as DBError;
|
||||
use stacks_common::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG};
|
||||
use stacks_common::consts::SIGNER_SLOTS_PER_USER;
|
||||
use stacks_common::types::chainstate::StacksAddress;
|
||||
use stacks_common::types::net::PeerAddress;
|
||||
@@ -181,6 +178,25 @@ mod tests {
|
||||
"ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_load_block_proposal_token() {
|
||||
let config = Config::from_config_file(
|
||||
ConfigFile::from_str(
|
||||
r#"
|
||||
[connection_options]
|
||||
block_proposal_token = "password"
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.expect("Expected to be able to parse block proposal token from file");
|
||||
|
||||
assert_eq!(
|
||||
config.connection_options.block_proposal_token,
|
||||
Some("password".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigFile {
|
||||
@@ -276,102 +292,6 @@ impl ConfigFile {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mockamoto() -> ConfigFile {
|
||||
let epochs = vec![
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "1.0".into(),
|
||||
start_height: 0,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "2.0".into(),
|
||||
start_height: 0,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "2.05".into(),
|
||||
start_height: 1,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "2.1".into(),
|
||||
start_height: 2,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "2.2".into(),
|
||||
start_height: 3,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "2.3".into(),
|
||||
start_height: 4,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "2.4".into(),
|
||||
start_height: 5,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "2.5".into(),
|
||||
start_height: 6,
|
||||
},
|
||||
StacksEpochConfigFile {
|
||||
epoch_name: "3.0".into(),
|
||||
start_height: 7,
|
||||
},
|
||||
];
|
||||
|
||||
let burnchain = BurnchainConfigFile {
|
||||
mode: Some("mockamoto".into()),
|
||||
rpc_port: Some(8332),
|
||||
peer_port: Some(8333),
|
||||
peer_host: Some("localhost".into()),
|
||||
username: Some("blockstack".into()),
|
||||
password: Some("blockstacksystem".into()),
|
||||
magic_bytes: Some("M3".into()),
|
||||
epochs: Some(epochs),
|
||||
pox_prepare_length: Some(3),
|
||||
pox_reward_length: Some(36),
|
||||
..BurnchainConfigFile::default()
|
||||
};
|
||||
|
||||
let node = NodeConfigFile {
|
||||
bootstrap_node: None,
|
||||
miner: Some(true),
|
||||
stacker: Some(true),
|
||||
..NodeConfigFile::default()
|
||||
};
|
||||
|
||||
let mining_key = Secp256k1PrivateKey::new();
|
||||
let miner = MinerConfigFile {
|
||||
mining_key: Some(mining_key.to_hex()),
|
||||
..MinerConfigFile::default()
|
||||
};
|
||||
|
||||
let mock_private_key = Secp256k1PrivateKey::from_seed(&[0]);
|
||||
let mock_public_key = Secp256k1PublicKey::from_private(&mock_private_key);
|
||||
let mock_address = StacksAddress::from_public_keys(
|
||||
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
|
||||
&AddressHashMode::SerializeP2PKH,
|
||||
1,
|
||||
&vec![mock_public_key],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
info!(
|
||||
"Mockamoto starting. Initial balance set to mock_private_key = {}",
|
||||
mock_private_key.to_hex()
|
||||
);
|
||||
|
||||
let ustx_balance = vec![InitialBalanceFile {
|
||||
address: mock_address.to_string(),
|
||||
amount: 1_000_000_000_000,
|
||||
}];
|
||||
|
||||
ConfigFile {
|
||||
burnchain: Some(burnchain),
|
||||
node: Some(node),
|
||||
miner: Some(miner),
|
||||
ustx_balance: Some(ustx_balance),
|
||||
..ConfigFile::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn helium() -> ConfigFile {
|
||||
// ## Settings for local testnet, relying on a local bitcoind server
|
||||
// ## running with the following bitcoin.conf:
|
||||
@@ -506,19 +426,6 @@ lazy_static! {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
pub fn self_signing(&self) -> Option<TestSigners> {
|
||||
if !(self.burnchain.mode == "nakamoto-neon" || self.burnchain.mode == "mockamoto") {
|
||||
return None;
|
||||
}
|
||||
self.miner.self_signing_key.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "testing")))]
|
||||
pub fn self_signing(&self) -> Option<TestSigners> {
|
||||
return None;
|
||||
}
|
||||
|
||||
/// get the up-to-date burnchain options from the config.
|
||||
/// If the config file can't be loaded, then return the existing config
|
||||
pub fn get_burnchain_config(&self) -> BurnchainConfig {
|
||||
@@ -550,6 +457,7 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Apply any test settings to this burnchain config struct
|
||||
#[cfg_attr(test, mutants::skip)]
|
||||
fn apply_test_settings(&self, burnchain: &mut Burnchain) {
|
||||
if self.burnchain.get_bitcoin_network().1 == BitcoinNetworkType::Mainnet {
|
||||
return;
|
||||
@@ -644,7 +552,7 @@ impl Config {
|
||||
}
|
||||
|
||||
// check if the Epoch 3.0 burnchain settings as configured are going to be valid.
|
||||
if self.burnchain.mode == "nakamoto-neon" || self.burnchain.mode == "mockamoto" {
|
||||
if self.burnchain.mode == "nakamoto-neon" {
|
||||
self.check_nakamoto_config(&burnchain);
|
||||
}
|
||||
}
|
||||
@@ -876,15 +784,7 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn from_config_file(config_file: ConfigFile) -> Result<Config, String> {
|
||||
if config_file.burnchain.as_ref().map(|b| b.mode.clone()) == Some(Some("mockamoto".into()))
|
||||
{
|
||||
// in the case of mockamoto, use `ConfigFile::mockamoto()` as the default for
|
||||
// processing a user-supplied config
|
||||
let default = Self::from_config_default(ConfigFile::mockamoto(), Config::default())?;
|
||||
Self::from_config_default(config_file, default)
|
||||
} else {
|
||||
Self::from_config_default(config_file, Config::default())
|
||||
}
|
||||
Self::from_config_default(config_file, Config::default())
|
||||
}
|
||||
|
||||
fn from_config_default(config_file: ConfigFile, default: Config) -> Result<Config, String> {
|
||||
@@ -910,7 +810,6 @@ impl Config {
|
||||
"krypton",
|
||||
"xenon",
|
||||
"mainnet",
|
||||
"mockamoto",
|
||||
"nakamoto-neon",
|
||||
];
|
||||
|
||||
@@ -969,26 +868,11 @@ impl Config {
|
||||
node.require_affirmed_anchor_blocks = false;
|
||||
}
|
||||
|
||||
let miners_contract_id = boot_code_id(MINERS_NAME, is_mainnet);
|
||||
if (node.stacker || node.miner)
|
||||
&& burnchain.mode == "nakamoto-neon"
|
||||
&& !node.stacker_dbs.contains(&miners_contract_id)
|
||||
{
|
||||
debug!("A miner/stacker must subscribe to the {miners_contract_id} stacker db contract. Forcibly subscribing...");
|
||||
node.stacker_dbs.push(miners_contract_id);
|
||||
if (node.stacker || node.miner) && burnchain.mode == "nakamoto-neon" {
|
||||
node.add_miner_stackerdb(is_mainnet);
|
||||
}
|
||||
if (node.stacker || node.miner) && burnchain.mode == "nakamoto-neon" {
|
||||
for signer_set in 0..2 {
|
||||
for message_id in 0..SIGNER_SLOTS_PER_USER {
|
||||
let contract_id = NakamotoSigners::make_signers_db_contract_id(
|
||||
signer_set, message_id, is_mainnet,
|
||||
);
|
||||
if !node.stacker_dbs.contains(&contract_id) {
|
||||
debug!("A miner/stacker must subscribe to the {contract_id} stacker db contract. Forcibly subscribing...");
|
||||
node.stacker_dbs.push(contract_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
node.add_signers_stackerdbs(is_mainnet);
|
||||
}
|
||||
|
||||
let miner = match config_file.miner {
|
||||
@@ -1364,7 +1248,7 @@ impl BurnchainConfig {
|
||||
match self.mode.as_str() {
|
||||
"mainnet" => ("mainnet".to_string(), BitcoinNetworkType::Mainnet),
|
||||
"xenon" => ("testnet".to_string(), BitcoinNetworkType::Testnet),
|
||||
"helium" | "neon" | "argon" | "krypton" | "mocknet" | "mockamoto" | "nakamoto-neon" => {
|
||||
"helium" | "neon" | "argon" | "krypton" | "mocknet" | "nakamoto-neon" => {
|
||||
("regtest".to_string(), BitcoinNetworkType::Regtest)
|
||||
}
|
||||
other => panic!("Invalid stacks-node mode: {other}"),
|
||||
@@ -1595,9 +1479,6 @@ pub struct NodeConfig {
|
||||
pub chain_liveness_poll_time_secs: u64,
|
||||
/// stacker DBs we replicate
|
||||
pub stacker_dbs: Vec<QualifiedContractIdentifier>,
|
||||
/// if running in mockamoto mode, how long to wait between each
|
||||
/// simulated bitcoin block
|
||||
pub mockamoto_time_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -1878,12 +1759,32 @@ impl Default for NodeConfig {
|
||||
fault_injection_hide_blocks: false,
|
||||
chain_liveness_poll_time_secs: 300,
|
||||
stacker_dbs: vec![],
|
||||
mockamoto_time_ms: 3_000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeConfig {
|
||||
pub fn add_signers_stackerdbs(&mut self, is_mainnet: bool) {
|
||||
for signer_set in 0..2 {
|
||||
for message_id in 0..SIGNER_SLOTS_PER_USER {
|
||||
let contract_name = NakamotoSigners::make_signers_db_name(signer_set, message_id);
|
||||
let contract_id = boot_code_id(contract_name.as_str(), is_mainnet);
|
||||
if !self.stacker_dbs.contains(&contract_id) {
|
||||
debug!("A miner/stacker must subscribe to the {contract_id} stacker db contract. Forcibly subscribing...");
|
||||
self.stacker_dbs.push(contract_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_miner_stackerdb(&mut self, is_mainnet: bool) {
|
||||
let miners_contract_id = boot_code_id(MINERS_NAME, is_mainnet);
|
||||
if !self.stacker_dbs.contains(&miners_contract_id) {
|
||||
debug!("A miner/stacker must subscribe to the {miners_contract_id} stacker db contract. Forcibly subscribing...");
|
||||
self.stacker_dbs.push(miners_contract_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_neighbor(
|
||||
addr: SocketAddr,
|
||||
pubk: Secp256k1PublicKey,
|
||||
@@ -1921,7 +1822,42 @@ impl NodeConfig {
|
||||
let pubkey = Secp256k1PublicKey::from_hex(pubkey_str)
|
||||
.unwrap_or_else(|_| panic!("Invalid public key '{pubkey_str}'"));
|
||||
debug!("Resolve '{}'", &hostport);
|
||||
let sockaddr = hostport.to_socket_addrs().unwrap().next().unwrap();
|
||||
|
||||
let mut attempts = 0;
|
||||
let max_attempts = 5;
|
||||
let mut delay = Duration::from_secs(2);
|
||||
|
||||
let sockaddr = loop {
|
||||
match hostport.to_socket_addrs() {
|
||||
Ok(mut addrs) => {
|
||||
if let Some(addr) = addrs.next() {
|
||||
break addr;
|
||||
} else {
|
||||
panic!("No addresses found for '{}'", hostport);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if attempts >= max_attempts {
|
||||
panic!(
|
||||
"Failed to resolve '{}' after {} attempts: {}",
|
||||
hostport, max_attempts, e
|
||||
);
|
||||
} else {
|
||||
error!(
|
||||
"Attempt {} - Failed to resolve '{}': {}. Retrying in {:?}...",
|
||||
attempts + 1,
|
||||
hostport,
|
||||
e,
|
||||
delay
|
||||
);
|
||||
thread::sleep(delay);
|
||||
attempts += 1;
|
||||
delay *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let neighbor = NodeConfig::default_neighbor(sockaddr, pubkey, chain_id, peer_version);
|
||||
self.bootstrap_node.push(neighbor);
|
||||
}
|
||||
@@ -1992,7 +1928,6 @@ pub struct MinerConfig {
|
||||
pub candidate_retry_cache_size: u64,
|
||||
pub unprocessed_block_deadline_secs: u64,
|
||||
pub mining_key: Option<Secp256k1PrivateKey>,
|
||||
pub self_signing_key: Option<TestSigners>,
|
||||
/// Amount of time while mining in nakamoto to wait in between mining interim blocks
|
||||
pub wait_on_interim_blocks: Duration,
|
||||
/// minimum number of transactions that must be in a block if we're going to replace a pending
|
||||
@@ -2040,7 +1975,6 @@ impl Default for MinerConfig {
|
||||
candidate_retry_cache_size: 1024 * 1024,
|
||||
unprocessed_block_deadline_secs: 30,
|
||||
mining_key: None,
|
||||
self_signing_key: None,
|
||||
wait_on_interim_blocks: Duration::from_millis(2_500),
|
||||
min_tx_count: 0,
|
||||
only_increase_tx_count: false,
|
||||
@@ -2053,7 +1987,7 @@ impl Default for MinerConfig {
|
||||
filter_origins: HashSet::new(),
|
||||
max_reorg_depth: 3,
|
||||
// TODO: update to a sane value based on stackerdb benchmarking
|
||||
wait_on_signers: Duration::from_millis(10_000),
|
||||
wait_on_signers: Duration::from_secs(200),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2100,6 +2034,7 @@ pub struct ConnectionOptionsFile {
|
||||
pub force_disconnect_interval: Option<u64>,
|
||||
pub antientropy_public: Option<bool>,
|
||||
pub private_neighbors: Option<bool>,
|
||||
pub block_proposal_token: Option<String>,
|
||||
}
|
||||
|
||||
impl ConnectionOptionsFile {
|
||||
@@ -2223,6 +2158,7 @@ impl ConnectionOptionsFile {
|
||||
max_sockets: self.max_sockets.unwrap_or(800) as usize,
|
||||
antientropy_public: self.antientropy_public.unwrap_or(true),
|
||||
private_neighbors: self.private_neighbors.unwrap_or(true),
|
||||
block_proposal_token: self.block_proposal_token,
|
||||
..ConnectionOptions::default()
|
||||
})
|
||||
}
|
||||
@@ -2260,9 +2196,6 @@ pub struct NodeConfigFile {
|
||||
pub chain_liveness_poll_time_secs: Option<u64>,
|
||||
/// Stacker DBs we replicate
|
||||
pub stacker_dbs: Option<Vec<String>>,
|
||||
/// if running in mockamoto mode, how long to wait between each
|
||||
/// simulated bitcoin block
|
||||
pub mockamoto_time_ms: Option<u64>,
|
||||
}
|
||||
|
||||
impl NodeConfigFile {
|
||||
@@ -2338,9 +2271,6 @@ impl NodeConfigFile {
|
||||
.iter()
|
||||
.filter_map(|contract_id| QualifiedContractIdentifier::parse(contract_id).ok())
|
||||
.collect(),
|
||||
mockamoto_time_ms: self
|
||||
.mockamoto_time_ms
|
||||
.unwrap_or(default_node_config.mockamoto_time_ms),
|
||||
};
|
||||
Ok(node_config)
|
||||
}
|
||||
@@ -2424,7 +2354,6 @@ impl MinerConfigFile {
|
||||
.as_ref()
|
||||
.map(|x| Secp256k1PrivateKey::from_hex(x))
|
||||
.transpose()?,
|
||||
self_signing_key: Some(TestSigners::default()),
|
||||
wait_on_interim_blocks: self
|
||||
.wait_on_interim_blocks_ms
|
||||
.map(Duration::from_millis)
|
||||
|
||||
@@ -205,11 +205,13 @@ impl<T> Globals<T> {
|
||||
}
|
||||
|
||||
/// Signal system-wide stop
|
||||
#[cfg_attr(test, mutants::skip)]
|
||||
pub fn signal_stop(&self) {
|
||||
self.should_keep_running.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Should we keep running?
|
||||
#[cfg_attr(test, mutants::skip)]
|
||||
pub fn keep_running(&self) -> bool {
|
||||
self.should_keep_running.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ pub mod event_dispatcher;
|
||||
pub mod genesis_data;
|
||||
pub mod globals;
|
||||
pub mod keychain;
|
||||
pub mod mockamoto;
|
||||
pub mod nakamoto_node;
|
||||
pub mod neon_node;
|
||||
pub mod node;
|
||||
@@ -32,7 +31,6 @@ pub mod syncctl;
|
||||
pub mod tenure;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::{env, panic, process};
|
||||
|
||||
use backtrace::Backtrace;
|
||||
@@ -56,7 +54,6 @@ pub use self::node::{ChainTip, Node};
|
||||
pub use self::run_loop::{helium, neon};
|
||||
pub use self::tenure::Tenure;
|
||||
use crate::chain_data::MinerStats;
|
||||
use crate::mockamoto::MockamotoNode;
|
||||
use crate::neon_node::{BlockMinerThread, TipCandidate};
|
||||
use crate::run_loop::boot_nakamoto;
|
||||
|
||||
@@ -323,10 +320,6 @@ fn main() {
|
||||
args.finish();
|
||||
ConfigFile::mainnet()
|
||||
}
|
||||
"mockamoto" => {
|
||||
args.finish();
|
||||
ConfigFile::mockamoto()
|
||||
}
|
||||
"check-config" => {
|
||||
let config_path: String = args.value_from_str("--config").unwrap();
|
||||
args.finish();
|
||||
@@ -450,9 +443,6 @@ fn main() {
|
||||
{
|
||||
let mut run_loop = neon::RunLoop::new(conf);
|
||||
run_loop.start(None, mine_start.unwrap_or(0));
|
||||
} else if conf.burnchain.mode == "mockamoto" {
|
||||
let mut mockamoto = MockamotoNode::new(&conf).unwrap();
|
||||
mockamoto.run();
|
||||
} else if conf.burnchain.mode == "nakamoto-neon" {
|
||||
let mut run_loop = boot_nakamoto::BootRunLoop::new(conf).unwrap();
|
||||
run_loop.start(None, 0);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,414 +0,0 @@
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use clarity::vm::costs::ExecutionCost;
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::nakamoto::NakamotoChainState;
|
||||
use stacks::chainstate::stacks::db::StacksChainState;
|
||||
use stacks_common::types::chainstate::{StacksAddress, StacksPrivateKey};
|
||||
use stacks_common::types::net::PeerAddress;
|
||||
use stacks_common::types::StacksEpochId;
|
||||
use stacks_common::util::get_epoch_time_secs;
|
||||
use stacks_common::util::hash::to_hex;
|
||||
|
||||
use super::MockamotoNode;
|
||||
use crate::config::{EventKeyType, EventObserverConfig};
|
||||
use crate::neon_node::PeerThread;
|
||||
use crate::tests::neon_integrations::{get_pox_info, submit_tx, test_observer};
|
||||
use crate::tests::{make_stacks_transfer, to_addr};
|
||||
use crate::{Config, ConfigFile};
|
||||
|
||||
#[test]
|
||||
fn observe_100_blocks() {
|
||||
let mut conf = Config::from_config_file(ConfigFile::mockamoto()).unwrap();
|
||||
conf.node.working_dir = format!(
|
||||
"/tmp/stacks-node-tests/mock_observe_100_blocks-{}",
|
||||
get_epoch_time_secs()
|
||||
);
|
||||
conf.node.rpc_bind = "127.0.0.1:19343".into();
|
||||
conf.node.p2p_bind = "127.0.0.1:19344".into();
|
||||
conf.connection_options.public_ip_address = Some((PeerAddress::from_ipv4(127, 0, 0, 1), 20443));
|
||||
conf.node.mockamoto_time_ms = 10;
|
||||
|
||||
let submitter_sk = StacksPrivateKey::from_seed(&[1]);
|
||||
let submitter_addr = to_addr(&submitter_sk);
|
||||
conf.add_initial_balance(submitter_addr.to_string(), 1_000_000);
|
||||
let recipient_addr = StacksAddress::burn_address(false).into();
|
||||
|
||||
let observer_port = 19300;
|
||||
test_observer::spawn_at(observer_port);
|
||||
conf.events_observers.insert(EventObserverConfig {
|
||||
endpoint: format!("localhost:{observer_port}"),
|
||||
events_keys: vec![EventKeyType::AnyEvent],
|
||||
});
|
||||
|
||||
let mut mockamoto = MockamotoNode::new(&conf).unwrap();
|
||||
let globals = mockamoto.globals.clone();
|
||||
|
||||
let mut mempool = PeerThread::connect_mempool_db(&conf);
|
||||
let (mut chainstate, _) = StacksChainState::open(
|
||||
conf.is_mainnet(),
|
||||
conf.burnchain.chain_id,
|
||||
&conf.get_chainstate_path_str(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let burnchain = conf.get_burnchain();
|
||||
let sortdb = burnchain.open_sortition_db(true).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let node_thread = thread::Builder::new()
|
||||
.name("mockamoto-main".into())
|
||||
.spawn(move || mockamoto.run())
|
||||
.expect("FATAL: failed to start mockamoto main thread");
|
||||
|
||||
// make a transfer tx to test that the mockamoto miner picks up txs from the mempool
|
||||
let tx_fee = 200;
|
||||
let transfer_tx = make_stacks_transfer(&submitter_sk, 0, tx_fee, &recipient_addr, 100);
|
||||
let transfer_tx_hex = format!("0x{}", to_hex(&transfer_tx));
|
||||
|
||||
let mut sent_tx = false;
|
||||
|
||||
// complete within 2 minutes or abort
|
||||
let completed = loop {
|
||||
if Instant::now().duration_since(start) > Duration::from_secs(120) {
|
||||
break false;
|
||||
}
|
||||
let latest_block = test_observer::get_blocks().pop();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let Some(ref latest_block) = latest_block else {
|
||||
info!("No block observed yet!");
|
||||
continue;
|
||||
};
|
||||
let stacks_block_height = latest_block.get("block_height").unwrap().as_u64().unwrap();
|
||||
info!("Block height observed: {stacks_block_height}");
|
||||
|
||||
if stacks_block_height >= 1 && !sent_tx {
|
||||
let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
// Bypass admission checks
|
||||
mempool
|
||||
.submit_raw(
|
||||
&mut chainstate,
|
||||
&sortdb,
|
||||
&tip.consensus_hash,
|
||||
&tip.anchored_header.block_hash(),
|
||||
transfer_tx.clone(),
|
||||
&ExecutionCost::max_value(),
|
||||
&StacksEpochId::Epoch30,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
sent_tx = true;
|
||||
}
|
||||
|
||||
if stacks_block_height >= 100 {
|
||||
break true;
|
||||
}
|
||||
};
|
||||
|
||||
globals.signal_stop();
|
||||
|
||||
node_thread
|
||||
.join()
|
||||
.expect("Failed to join node thread to exit");
|
||||
|
||||
let transfer_tx_included = test_observer::get_blocks()
|
||||
.into_iter()
|
||||
.find(|block_json| {
|
||||
block_json["transactions"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|tx_json| tx_json["raw_tx"].as_str() == Some(&transfer_tx_hex))
|
||||
.is_some()
|
||||
})
|
||||
.is_some();
|
||||
|
||||
assert!(
|
||||
transfer_tx_included,
|
||||
"Mockamoto node failed to include the transfer tx"
|
||||
);
|
||||
|
||||
assert!(
|
||||
completed,
|
||||
"Mockamoto node failed to produce and announce 100 blocks before timeout"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mempool_rpc_submit() {
|
||||
let mut conf = Config::from_config_file(ConfigFile::mockamoto()).unwrap();
|
||||
conf.node.working_dir = format!(
|
||||
"/tmp/stacks-node-tests/mempool_rpc_submit-{}",
|
||||
get_epoch_time_secs()
|
||||
);
|
||||
conf.node.rpc_bind = "127.0.0.1:19743".into();
|
||||
conf.node.p2p_bind = "127.0.0.1:19744".into();
|
||||
conf.node.mockamoto_time_ms = 10;
|
||||
|
||||
let submitter_sk = StacksPrivateKey::from_seed(&[1]);
|
||||
let submitter_addr = to_addr(&submitter_sk);
|
||||
conf.add_initial_balance(submitter_addr.to_string(), 1_000);
|
||||
let recipient_addr = StacksAddress::burn_address(false).into();
|
||||
|
||||
let observer_port = 19800;
|
||||
test_observer::spawn_at(observer_port);
|
||||
conf.events_observers.insert(EventObserverConfig {
|
||||
endpoint: format!("localhost:{observer_port}"),
|
||||
events_keys: vec![EventKeyType::AnyEvent],
|
||||
});
|
||||
|
||||
let mut mockamoto = MockamotoNode::new(&conf).unwrap();
|
||||
let globals = mockamoto.globals.clone();
|
||||
|
||||
let http_origin = format!("http://{}", &conf.node.rpc_bind);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let node_thread = thread::Builder::new()
|
||||
.name("mockamoto-main".into())
|
||||
.spawn(move || mockamoto.run())
|
||||
.expect("FATAL: failed to start mockamoto main thread");
|
||||
|
||||
// make a transfer tx to test that the mockamoto miner picks up txs from the mempool
|
||||
let tx_fee = 200;
|
||||
let transfer_tx = make_stacks_transfer(&submitter_sk, 0, tx_fee, &recipient_addr, 100);
|
||||
let transfer_tx_hex = format!("0x{}", to_hex(&transfer_tx));
|
||||
|
||||
let mut sent_tx = false;
|
||||
|
||||
// complete within 2 minutes or abort
|
||||
let completed = loop {
|
||||
if Instant::now().duration_since(start) > Duration::from_secs(120) {
|
||||
break false;
|
||||
}
|
||||
let latest_block = test_observer::get_blocks().pop();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let Some(ref latest_block) = latest_block else {
|
||||
info!("No block observed yet!");
|
||||
continue;
|
||||
};
|
||||
let stacks_block_height = latest_block.get("block_height").unwrap().as_u64().unwrap();
|
||||
info!("Block height observed: {stacks_block_height}");
|
||||
|
||||
if stacks_block_height >= 1 && !sent_tx {
|
||||
// Enforce admission checks by utilizing the RPC endpoint
|
||||
submit_tx(&http_origin, &transfer_tx);
|
||||
sent_tx = true;
|
||||
}
|
||||
|
||||
if stacks_block_height >= 100 {
|
||||
break true;
|
||||
}
|
||||
};
|
||||
|
||||
globals.signal_stop();
|
||||
|
||||
node_thread
|
||||
.join()
|
||||
.expect("Failed to join node thread to exit");
|
||||
|
||||
let transfer_tx_included = test_observer::get_blocks()
|
||||
.into_iter()
|
||||
.find(|block_json| {
|
||||
block_json["transactions"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|tx_json| tx_json["raw_tx"].as_str() == Some(&transfer_tx_hex))
|
||||
.is_some()
|
||||
})
|
||||
.is_some();
|
||||
|
||||
assert!(
|
||||
transfer_tx_included,
|
||||
"Mockamoto node failed to include the transfer tx"
|
||||
);
|
||||
|
||||
assert!(
|
||||
completed,
|
||||
"Mockamoto node failed to produce and announce 100 blocks before timeout"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observe_set_aggregate_key() {
|
||||
let mut conf = Config::from_config_file(ConfigFile::mockamoto()).unwrap();
|
||||
conf.node.mockamoto_time_ms = 10;
|
||||
conf.node.p2p_bind = "127.0.0.1:20443".into();
|
||||
conf.node.rpc_bind = "127.0.0.1:20444".into();
|
||||
conf.connection_options.public_ip_address = Some((PeerAddress::from_ipv4(127, 0, 0, 1), 20443));
|
||||
|
||||
let submitter_sk = StacksPrivateKey::from_seed(&[1]);
|
||||
let submitter_addr = to_addr(&submitter_sk);
|
||||
conf.add_initial_balance(submitter_addr.to_string(), 1_000);
|
||||
|
||||
test_observer::spawn();
|
||||
let observer_port = test_observer::EVENT_OBSERVER_PORT;
|
||||
conf.events_observers.insert(EventObserverConfig {
|
||||
endpoint: format!("localhost:{observer_port}"),
|
||||
events_keys: vec![EventKeyType::AnyEvent],
|
||||
});
|
||||
|
||||
let mut mockamoto = MockamotoNode::new(&conf).unwrap();
|
||||
let mut signer = mockamoto.self_signer.clone();
|
||||
|
||||
let globals = mockamoto.globals.clone();
|
||||
|
||||
StacksChainState::open(
|
||||
conf.is_mainnet(),
|
||||
conf.burnchain.chain_id,
|
||||
&conf.get_chainstate_path_str(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let sortition_tip = SortitionDB::get_canonical_burn_chain_tip(mockamoto.sortdb.conn()).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
// Get the reward cycle of the sortition tip
|
||||
let reward_cycle = mockamoto
|
||||
.sortdb
|
||||
.pox_constants
|
||||
.block_height_to_reward_cycle(
|
||||
mockamoto.sortdb.first_block_height,
|
||||
sortition_tip.block_height,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to determine reward cycle of block height: {}",
|
||||
sortition_tip.block_height
|
||||
)
|
||||
});
|
||||
|
||||
// Get the aggregate public key of the original reward cycle to compare against
|
||||
let expected_cur_key = signer.generate_aggregate_key(reward_cycle);
|
||||
let expected_next_key = signer.generate_aggregate_key(reward_cycle + 1);
|
||||
|
||||
let node_thread = thread::Builder::new()
|
||||
.name("mockamoto-main".into())
|
||||
.spawn(move || {
|
||||
mockamoto.run();
|
||||
let aggregate_key_block_header = NakamotoChainState::get_canonical_block_header(
|
||||
mockamoto.chainstate.db(),
|
||||
&mockamoto.sortdb,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
// Get the aggregate public key of the original reward cycle
|
||||
let orig_aggregate_key = mockamoto
|
||||
.chainstate
|
||||
.get_aggregate_public_key_pox_4(
|
||||
&mockamoto.sortdb,
|
||||
&aggregate_key_block_header.index_block_hash(),
|
||||
reward_cycle,
|
||||
)
|
||||
.unwrap();
|
||||
// Get the aggregate public key of the next reward cycle that we manually overwrote
|
||||
let new_aggregate_key = mockamoto
|
||||
.chainstate
|
||||
.get_aggregate_public_key_pox_4(
|
||||
&mockamoto.sortdb,
|
||||
&aggregate_key_block_header.index_block_hash(),
|
||||
reward_cycle + 1,
|
||||
)
|
||||
.unwrap();
|
||||
(orig_aggregate_key, new_aggregate_key)
|
||||
})
|
||||
.expect("FATAL: failed to start mockamoto main thread");
|
||||
|
||||
// complete within 5 seconds or abort (we are only observing one block)
|
||||
let completed = loop {
|
||||
if Instant::now().duration_since(start) > Duration::from_secs(120) {
|
||||
break false;
|
||||
}
|
||||
let latest_block = test_observer::get_blocks().pop();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let Some(ref latest_block) = latest_block else {
|
||||
info!("No block observed yet!");
|
||||
continue;
|
||||
};
|
||||
let stacks_block_height = latest_block.get("block_height").unwrap().as_u64().unwrap();
|
||||
info!("Block height observed: {stacks_block_height}");
|
||||
if stacks_block_height >= 100 {
|
||||
break true;
|
||||
}
|
||||
};
|
||||
|
||||
globals.signal_stop();
|
||||
|
||||
let (orig_aggregate_key, new_aggregate_key) = node_thread
|
||||
.join()
|
||||
.expect("Failed to join node thread to exit");
|
||||
|
||||
assert!(
|
||||
completed,
|
||||
"Mockamoto node failed to produce and announce its block before timeout"
|
||||
);
|
||||
|
||||
// Did we set and retrieve the aggregate key correctly?
|
||||
assert_eq!(orig_aggregate_key.unwrap(), expected_cur_key);
|
||||
assert_eq!(new_aggregate_key.unwrap(), expected_next_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_pox_info() {
|
||||
let mut conf = Config::from_config_file(ConfigFile::mockamoto()).unwrap();
|
||||
conf.node.mockamoto_time_ms = 10;
|
||||
conf.node.rpc_bind = "127.0.0.1:19543".into();
|
||||
conf.node.p2p_bind = "127.0.0.1:19544".into();
|
||||
|
||||
let observer_port = 19500;
|
||||
test_observer::spawn_at(observer_port);
|
||||
conf.events_observers.insert(EventObserverConfig {
|
||||
endpoint: format!("localhost:{observer_port}"),
|
||||
events_keys: vec![EventKeyType::AnyEvent],
|
||||
});
|
||||
|
||||
let mut mockamoto = MockamotoNode::new(&conf).unwrap();
|
||||
let globals = mockamoto.globals.clone();
|
||||
|
||||
let http_origin = format!("http://{}", &conf.node.rpc_bind);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let node_thread = thread::Builder::new()
|
||||
.name("mockamoto-main".into())
|
||||
.spawn(move || mockamoto.run())
|
||||
.expect("FATAL: failed to start mockamoto main thread");
|
||||
|
||||
// mine 5 blocks
|
||||
let completed = loop {
|
||||
// complete within 2 minutes or abort
|
||||
if Instant::now().duration_since(start) > Duration::from_secs(120) {
|
||||
break false;
|
||||
}
|
||||
let latest_block = test_observer::get_blocks().pop();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let Some(ref latest_block) = latest_block else {
|
||||
info!("No block observed yet!");
|
||||
continue;
|
||||
};
|
||||
let stacks_block_height = latest_block.get("block_height").unwrap().as_u64().unwrap();
|
||||
info!("Block height observed: {stacks_block_height}");
|
||||
|
||||
if stacks_block_height >= 5 {
|
||||
break true;
|
||||
}
|
||||
};
|
||||
|
||||
// fetch rpc poxinfo
|
||||
let _pox_info = get_pox_info(&http_origin);
|
||||
|
||||
globals.signal_stop();
|
||||
|
||||
assert!(
|
||||
completed,
|
||||
"Mockamoto node failed to produce and announce 100 blocks before timeout"
|
||||
);
|
||||
node_thread
|
||||
.join()
|
||||
.expect("Failed to join node thread to exit");
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
|
||||
// Copyright (C) 2020-2023 Stacks Open Internet Foundation
|
||||
//
|
||||
@@ -13,24 +14,24 @@
|
||||
//
|
||||
// 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::convert::TryFrom;
|
||||
use std::thread;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use clarity::boot_util::boot_code_id;
|
||||
use clarity::vm::types::PrincipalData;
|
||||
use clarity::vm::clarity::ClarityConnection;
|
||||
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
|
||||
use hashbrown::HashSet;
|
||||
use libsigner::{
|
||||
BlockResponse, RejectCode, SignerMessage, SignerSession, StackerDBSession, BLOCK_MSG_ID,
|
||||
TRANSACTIONS_MSG_ID,
|
||||
};
|
||||
use stacks::burnchains::{Burnchain, BurnchainParameters};
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::burn::{BlockSnapshot, ConsensusHash};
|
||||
use stacks::chainstate::nakamoto::miner::{NakamotoBlockBuilder, NakamotoTenureInfo};
|
||||
use stacks::chainstate::nakamoto::signer_set::NakamotoSigners;
|
||||
use stacks::chainstate::nakamoto::test_signers::TestSigners;
|
||||
use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState};
|
||||
use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockVote, NakamotoChainState};
|
||||
use stacks::chainstate::stacks::boot::MINERS_NAME;
|
||||
use stacks::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo};
|
||||
use stacks::chainstate::stacks::{
|
||||
@@ -40,7 +41,7 @@ use stacks::chainstate::stacks::{
|
||||
};
|
||||
use stacks::core::FIRST_BURNCHAIN_CONSENSUS_HASH;
|
||||
use stacks::net::stackerdb::StackerDBs;
|
||||
use stacks_common::codec::read_next;
|
||||
use stacks_common::codec::{read_next, StacksMessageCodec};
|
||||
use stacks_common::types::chainstate::{StacksAddress, StacksBlockId};
|
||||
use stacks_common::types::{PrivateKey, StacksEpochId};
|
||||
use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum};
|
||||
@@ -149,10 +150,6 @@ impl BlockMinerThread {
|
||||
let miners_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet());
|
||||
let stackerdbs = StackerDBs::connect(&self.config.get_stacker_db_file_path(), true)
|
||||
.expect("FATAL: failed to connect to stacker DB");
|
||||
let rpc_sock = self.config.node.rpc_bind.parse().expect(&format!(
|
||||
"Failed to parse socket: {}",
|
||||
&self.config.node.rpc_bind
|
||||
));
|
||||
let Some(miner_privkey) = self.config.miner.mining_key else {
|
||||
warn!("No mining key configured, cannot mine");
|
||||
return;
|
||||
@@ -160,7 +157,7 @@ impl BlockMinerThread {
|
||||
// now, actually run this tenure
|
||||
loop {
|
||||
let new_block = loop {
|
||||
match self.mine_block() {
|
||||
match self.mine_block(&stackerdbs) {
|
||||
Ok(x) => break Some(x),
|
||||
Err(NakamotoNodeError::MiningFailure(ChainstateError::MinerAborted)) => {
|
||||
info!("Miner interrupted while mining, will try again");
|
||||
@@ -203,7 +200,7 @@ impl BlockMinerThread {
|
||||
// Propose the block to the observing signers through the .miners stackerdb instance
|
||||
let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet());
|
||||
let mut miners_stackerdb =
|
||||
StackerDBSession::new(rpc_sock, miner_contract_id);
|
||||
StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id);
|
||||
match miners_stackerdb.put_chunk(&chunk) {
|
||||
Ok(ack) => {
|
||||
info!("Proposed block to stackerdb: {ack:?}");
|
||||
@@ -221,21 +218,14 @@ impl BlockMinerThread {
|
||||
warn!("Failed to propose block to stackerdb: {e:?}");
|
||||
}
|
||||
}
|
||||
self.globals.counters.bump_naka_proposed_blocks();
|
||||
|
||||
if let Some(self_signer) = self.config.self_signing() {
|
||||
if let Err(e) = self.self_sign_and_broadcast(self_signer, new_block.clone()) {
|
||||
warn!("Error self-signing block: {e:?}");
|
||||
} else {
|
||||
self.globals.coord().announce_new_stacks_block();
|
||||
}
|
||||
if let Err(e) =
|
||||
self.wait_for_signer_signature_and_broadcast(&stackerdbs, new_block.clone())
|
||||
{
|
||||
warn!("Error broadcasting block: {e:?}");
|
||||
} else {
|
||||
if let Err(e) =
|
||||
self.wait_for_signer_signature_and_broadcast(&stackerdbs, new_block.clone())
|
||||
{
|
||||
warn!("Error broadcasting block: {e:?}");
|
||||
} else {
|
||||
self.globals.coord().announce_new_stacks_block();
|
||||
}
|
||||
self.globals.coord().announce_new_stacks_block();
|
||||
}
|
||||
|
||||
self.globals.counters.bump_naka_mined_blocks();
|
||||
@@ -256,24 +246,19 @@ impl BlockMinerThread {
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_signer_signature(
|
||||
fn get_stackerdb_contract_and_slots(
|
||||
&self,
|
||||
stackerdbs: &StackerDBs,
|
||||
aggregate_public_key: &Point,
|
||||
signer_signature_hash: &Sha512Trunc256Sum,
|
||||
) -> Result<ThresholdSignature, NakamotoNodeError> {
|
||||
msg_id: u32,
|
||||
reward_cycle: u64,
|
||||
) -> Result<(QualifiedContractIdentifier, HashMap<u32, StacksAddress>), NakamotoNodeError> {
|
||||
let stackerdb_contracts = stackerdbs
|
||||
.get_stackerdb_contract_ids()
|
||||
.expect("FATAL: could not get the stacker DB contract ids");
|
||||
|
||||
let reward_cycle = self
|
||||
.burnchain
|
||||
.block_height_to_reward_cycle(self.burn_block.block_height)
|
||||
.expect("FATAL: no reward cycle for burn block");
|
||||
|
||||
let signers_contract_id = NakamotoSigners::make_signers_db_contract_id(
|
||||
reward_cycle,
|
||||
BLOCK_MSG_ID,
|
||||
msg_id,
|
||||
self.config.is_mainnet(),
|
||||
);
|
||||
if !stackerdb_contracts.contains(&signers_contract_id) {
|
||||
@@ -281,21 +266,123 @@ impl BlockMinerThread {
|
||||
"No signers contract found, cannot wait for signers",
|
||||
));
|
||||
};
|
||||
// Get the block slot for every signer
|
||||
let slot_ids = stackerdbs
|
||||
// Get the slots for every signer
|
||||
let signers = stackerdbs
|
||||
.get_signers(&signers_contract_id)
|
||||
.expect("FATAL: could not get signers from stacker DB");
|
||||
let mut slot_ids_addresses = HashMap::with_capacity(signers.len());
|
||||
for (slot_id, address) in stackerdbs
|
||||
.get_signers(&signers_contract_id)
|
||||
.expect("FATAL: could not get signers from stacker DB")
|
||||
.iter()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, _)| {
|
||||
u32::try_from(id).expect("FATAL: too many signers to fit into u32 range")
|
||||
})
|
||||
.collect::<Vec<u32>>();
|
||||
{
|
||||
slot_ids_addresses.insert(
|
||||
u32::try_from(slot_id).expect("FATAL: too many signers to fit into u32 range"),
|
||||
address,
|
||||
);
|
||||
}
|
||||
Ok((signers_contract_id, slot_ids_addresses))
|
||||
}
|
||||
|
||||
fn get_signer_transactions(
|
||||
&self,
|
||||
chainstate: &mut StacksChainState,
|
||||
sortdb: &SortitionDB,
|
||||
stackerdbs: &StackerDBs,
|
||||
) -> Result<Vec<StacksTransaction>, NakamotoNodeError> {
|
||||
let next_reward_cycle = self
|
||||
.burnchain
|
||||
.block_height_to_reward_cycle(self.burn_block.block_height)
|
||||
.expect("FATAL: no reward cycle for burn block")
|
||||
.wrapping_add(1);
|
||||
let (signers_contract_id, slot_ids_addresses) = self.get_stackerdb_contract_and_slots(
|
||||
stackerdbs,
|
||||
TRANSACTIONS_MSG_ID,
|
||||
next_reward_cycle,
|
||||
)?;
|
||||
let slot_ids = slot_ids_addresses.keys().cloned().collect::<Vec<_>>();
|
||||
let addresses = slot_ids_addresses.values().cloned().collect::<HashSet<_>>();
|
||||
// Get the transactions from the signers for the next block
|
||||
let signer_chunks = stackerdbs
|
||||
.get_latest_chunks(&signers_contract_id, &slot_ids)
|
||||
.expect("FATAL: could not get latest chunks from stacker DB");
|
||||
let signer_messages: Vec<(u32, SignerMessage)> = slot_ids
|
||||
.iter()
|
||||
.zip(signer_chunks.into_iter())
|
||||
.filter_map(|(slot_id, chunk)| {
|
||||
chunk.and_then(|chunk| {
|
||||
read_next::<SignerMessage, _>(&mut &chunk[..])
|
||||
.ok()
|
||||
.map(|msg| (*slot_id, msg))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
if signer_messages.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let (consensus_hash, block_bhh) =
|
||||
SortitionDB::get_canonical_stacks_chain_tip_hash(sortdb.conn()).unwrap();
|
||||
let stacks_block_id = StacksBlockId::new(&consensus_hash, &block_bhh);
|
||||
|
||||
// Get all nonces for the signers from clarity DB to use to validate transactions
|
||||
let account_nonces = chainstate
|
||||
.with_read_only_clarity_tx(&sortdb.index_conn(), &stacks_block_id, |clarity_tx| {
|
||||
clarity_tx.with_clarity_db_readonly(|clarity_db| {
|
||||
addresses
|
||||
.iter()
|
||||
.map(|address| {
|
||||
(
|
||||
address.clone(),
|
||||
clarity_db
|
||||
.get_account_nonce(&address.clone().into())
|
||||
.unwrap_or(0),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<StacksAddress, u64>>()
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut filtered_transactions: HashMap<StacksAddress, StacksTransaction> = HashMap::new();
|
||||
for (_slot, signer_message) in signer_messages {
|
||||
match signer_message {
|
||||
SignerMessage::Transactions(transactions) => {
|
||||
NakamotoSigners::update_filtered_transactions(
|
||||
&mut filtered_transactions,
|
||||
&account_nonces,
|
||||
self.config.is_mainnet(),
|
||||
transactions,
|
||||
)
|
||||
}
|
||||
_ => {} // Any other message is ignored
|
||||
}
|
||||
}
|
||||
Ok(filtered_transactions.into_values().collect())
|
||||
}
|
||||
|
||||
fn wait_for_signer_signature(
|
||||
&self,
|
||||
stackerdbs: &StackerDBs,
|
||||
aggregate_public_key: &Point,
|
||||
signer_signature_hash: &Sha512Trunc256Sum,
|
||||
signer_weights: HashMap<StacksAddress, u64>,
|
||||
) -> Result<ThresholdSignature, NakamotoNodeError> {
|
||||
let reward_cycle = self
|
||||
.burnchain
|
||||
.block_height_to_reward_cycle(self.burn_block.block_height)
|
||||
.expect("FATAL: no reward cycle for burn block");
|
||||
let (signers_contract_id, slot_ids_addresses) =
|
||||
self.get_stackerdb_contract_and_slots(stackerdbs, BLOCK_MSG_ID, reward_cycle)?;
|
||||
let slot_ids = slot_ids_addresses.keys().cloned().collect::<Vec<_>>();
|
||||
// If more than a threshold percentage of the signers reject the block, we should not wait any further
|
||||
let rejection_threshold = slot_ids.len() / 10 * 7;
|
||||
let weights: u64 = signer_weights.values().sum();
|
||||
let rejection_threshold: u64 = (weights as f64 * 7_f64 / 10_f64).ceil() as u64;
|
||||
let mut rejections = HashSet::new();
|
||||
let mut rejections_weight: u64 = 0;
|
||||
let now = Instant::now();
|
||||
debug!("Miner: waiting for block response from reward cycle {reward_cycle } signers...");
|
||||
while now.elapsed() < self.config.miner.wait_on_signers {
|
||||
// Get the block responses from the signers for the block we just proposed
|
||||
let signer_chunks = stackerdbs
|
||||
@@ -323,6 +410,7 @@ impl BlockMinerThread {
|
||||
{
|
||||
// The signature is valid across the signer signature hash of the original proposed block
|
||||
// Immediately return and update the block with this new signature before appending it to the chain
|
||||
debug!("Miner: received a signature accross the proposed block's signer signature hash ({signer_signature_hash:?}): {signature:?}");
|
||||
return Ok(signature);
|
||||
}
|
||||
// We received an accepted block for some unknown block hash...Useless! Ignore it.
|
||||
@@ -337,8 +425,11 @@ impl BlockMinerThread {
|
||||
}
|
||||
if let RejectCode::SignedRejection(signature) = block_rejection.reason_code
|
||||
{
|
||||
let mut message = signer_signature_hash.0.to_vec();
|
||||
message.push(b'n');
|
||||
let block_vote = NakamotoBlockVote {
|
||||
signer_signature_hash: *signer_signature_hash,
|
||||
rejected: true,
|
||||
};
|
||||
let message = block_vote.serialize_to_vec();
|
||||
if signature.0.verify(aggregate_public_key, &message) {
|
||||
// A threshold number of signers signed a denial of the proposed block
|
||||
// Miner will NEVER get a signed block from the signers for this particular block
|
||||
@@ -348,10 +439,24 @@ impl BlockMinerThread {
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if rejections.contains(&signer_id) {
|
||||
// We have already received a rejection from this signer
|
||||
continue;
|
||||
}
|
||||
|
||||
// We received a rejection that is not signed. We will keep waiting for a threshold number of rejections.
|
||||
// Ensure that we do not double count a rejection from the same signer.
|
||||
rejections.insert(signer_id);
|
||||
if rejections.len() > rejection_threshold {
|
||||
rejections_weight = rejections_weight.saturating_add(
|
||||
*signer_weights
|
||||
.get(
|
||||
&slot_ids_addresses
|
||||
.get(&signer_id)
|
||||
.expect("FATAL: signer not found in slot ids"),
|
||||
)
|
||||
.expect("FATAL: signer not found in signer weights"),
|
||||
);
|
||||
if rejections_weight > rejection_threshold {
|
||||
// A threshold number of signers rejected the proposed block.
|
||||
// Miner will likely never get a signed block from the signers for this particular block
|
||||
// Return and attempt to mine a new block
|
||||
@@ -368,6 +473,7 @@ impl BlockMinerThread {
|
||||
thread::sleep(Duration::from_millis(WAIT_FOR_SIGNERS_MS));
|
||||
}
|
||||
// We have waited for the signers for too long: stop waiting so we can propose a new block
|
||||
debug!("Miner: exceeded signer signature timeout. Will propose a new block");
|
||||
Err(NakamotoNodeError::SignerSignatureError(
|
||||
"Timed out waiting for signers",
|
||||
))
|
||||
@@ -394,11 +500,23 @@ impl BlockMinerThread {
|
||||
&sortition_handle,
|
||||
&block,
|
||||
)?;
|
||||
|
||||
let reward_cycle = self
|
||||
.burnchain
|
||||
.block_height_to_reward_cycle(self.burn_block.block_height)
|
||||
.expect("FATAL: no reward cycle for burn block");
|
||||
let signer_weights = NakamotoSigners::get_signers_weights(
|
||||
&mut chain_state,
|
||||
&sort_db,
|
||||
&self.parent_tenure_id,
|
||||
reward_cycle,
|
||||
)?;
|
||||
let signature = self
|
||||
.wait_for_signer_signature(
|
||||
&stackerdbs,
|
||||
&aggregate_public_key,
|
||||
&block.header.signer_signature_hash(),
|
||||
signer_weights,
|
||||
)
|
||||
.map_err(|e| {
|
||||
ChainstateError::InvalidStacksBlock(format!("Invalid Nakamoto block: {e:?}"))
|
||||
@@ -417,54 +535,6 @@ impl BlockMinerThread {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn self_sign_and_broadcast(
|
||||
&self,
|
||||
mut signer: TestSigners,
|
||||
mut block: NakamotoBlock,
|
||||
) -> Result<(), ChainstateError> {
|
||||
let mut chain_state = neon_node::open_chainstate_with_faults(&self.config)
|
||||
.expect("FATAL: could not open chainstate DB");
|
||||
let chainstate_config = chain_state.config();
|
||||
let sort_db = SortitionDB::open(
|
||||
&self.config.get_burn_db_file_path(),
|
||||
true,
|
||||
self.burnchain.pox_constants.clone(),
|
||||
)
|
||||
.expect("FATAL: could not open sortition DB");
|
||||
|
||||
let burn_height = self.burn_block.block_height;
|
||||
let cycle = self
|
||||
.burnchain
|
||||
.block_height_to_reward_cycle(burn_height)
|
||||
.expect("FATAL: no reward cycle for burn block");
|
||||
signer.sign_nakamoto_block(&mut block, cycle);
|
||||
|
||||
let mut sortition_handle = sort_db.index_handle_at_tip();
|
||||
let aggregate_public_key = if block.header.chain_length <= 1 {
|
||||
signer.aggregate_public_key.clone()
|
||||
} else {
|
||||
let aggregate_public_key = NakamotoChainState::get_aggregate_public_key(
|
||||
&mut chain_state,
|
||||
&sort_db,
|
||||
&sortition_handle,
|
||||
&block,
|
||||
)?;
|
||||
aggregate_public_key
|
||||
};
|
||||
|
||||
let (headers_conn, staging_tx) = chain_state.headers_conn_and_staging_tx_begin()?;
|
||||
NakamotoChainState::accept_block(
|
||||
&chainstate_config,
|
||||
block,
|
||||
&mut sortition_handle,
|
||||
&staging_tx,
|
||||
headers_conn,
|
||||
&aggregate_public_key,
|
||||
)?;
|
||||
staging_tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the coinbase recipient address, if set in the config and if allowed in this epoch
|
||||
fn get_coinbase_recipient(&self, epoch_id: StacksEpochId) -> Option<PrincipalData> {
|
||||
if epoch_id < StacksEpochId::Epoch21 && self.config.miner.block_reward_recipient.is_some() {
|
||||
@@ -639,7 +709,7 @@ impl BlockMinerThread {
|
||||
|
||||
/// Try to mine a Stacks block by assembling one from mempool transactions and sending a
|
||||
/// burnchain block-commit transaction. If we succeed, then return the assembled block.
|
||||
fn mine_block(&mut self) -> Result<NakamotoBlock, NakamotoNodeError> {
|
||||
fn mine_block(&mut self, stackerdbs: &StackerDBs) -> Result<NakamotoBlock, NakamotoNodeError> {
|
||||
debug!("block miner thread ID is {:?}", thread::current().id());
|
||||
|
||||
let burn_db_path = self.config.get_burn_db_file_path();
|
||||
@@ -709,6 +779,10 @@ impl BlockMinerThread {
|
||||
let block_num = u64::try_from(self.mined_blocks.len())
|
||||
.map_err(|_| NakamotoNodeError::UnexpectedChainState)?
|
||||
.saturating_add(1);
|
||||
|
||||
let signer_transactions =
|
||||
self.get_signer_transactions(&mut chain_state, &burn_db, &stackerdbs)?;
|
||||
|
||||
// build the block itself
|
||||
let (mut block, _, _) = NakamotoBlockBuilder::build_nakamoto_block(
|
||||
&chain_state,
|
||||
@@ -724,6 +798,7 @@ impl BlockMinerThread {
|
||||
self.globals.get_miner_status(),
|
||||
),
|
||||
Some(&self.event_dispatcher),
|
||||
signer_transactions,
|
||||
)
|
||||
.map_err(|e| {
|
||||
if !matches!(
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
// 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::VecDeque;
|
||||
use std::default::Default;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::mpsc::TrySendError;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -140,8 +140,6 @@
|
||||
use std::cmp;
|
||||
use std::cmp::Ordering as CmpOrdering;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::default::Default;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::mpsc::{Receiver, TrySendError};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::thread::JoinHandle;
|
||||
use std::{env, thread, time};
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::thread::JoinHandle;
|
||||
use std::{cmp, thread};
|
||||
|
||||
use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType};
|
||||
use stacks::burnchains::Burnchain;
|
||||
use stacks::burnchains::{Burnchain, Error as burnchain_error};
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::burn::BlockSnapshot;
|
||||
use stacks::chainstate::coordinator::comm::{CoordinatorChannels, CoordinatorReceivers};
|
||||
@@ -400,13 +400,27 @@ impl RunLoop {
|
||||
|
||||
// setup the termination handler, allow it to error if a prior runloop already set it
|
||||
neon::RunLoop::setup_termination_handler(self.should_keep_running.clone(), true);
|
||||
let mut burnchain = neon::RunLoop::instantiate_burnchain_state(
|
||||
|
||||
let burnchain_result = neon::RunLoop::instantiate_burnchain_state(
|
||||
&self.config,
|
||||
self.should_keep_running.clone(),
|
||||
burnchain_opt,
|
||||
coordinator_senders.clone(),
|
||||
);
|
||||
|
||||
let mut burnchain = match burnchain_result {
|
||||
Ok(burnchain_controller) => burnchain_controller,
|
||||
Err(burnchain_error::ShutdownInitiated) => {
|
||||
info!("Exiting stacks-node");
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error initializing burnchain: {}", e);
|
||||
info!("Exiting stacks-node");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let burnchain_config = burnchain.get_burnchain();
|
||||
self.burnchain = Some(burnchain_config.clone());
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{cmp, thread};
|
||||
|
||||
use libc;
|
||||
use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType};
|
||||
use stacks::burnchains::Burnchain;
|
||||
use stacks::burnchains::{Burnchain, Error as burnchain_error};
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::burn::BlockSnapshot;
|
||||
use stacks::chainstate::coordinator::comm::{CoordinatorChannels, CoordinatorReceivers};
|
||||
@@ -17,8 +17,7 @@ use stacks::chainstate::coordinator::{
|
||||
static_get_heaviest_affirmation_map, static_get_stacks_tip_affirmation_map, ChainsCoordinator,
|
||||
ChainsCoordinatorConfig, CoordinatorCommunication, Error as coord_error,
|
||||
};
|
||||
use stacks::chainstate::nakamoto::NakamotoChainState;
|
||||
use stacks::chainstate::stacks::db::{ChainStateBootData, ClarityTx, StacksChainState};
|
||||
use stacks::chainstate::stacks::db::{ChainStateBootData, StacksChainState};
|
||||
use stacks::chainstate::stacks::miner::{signal_mining_blocked, signal_mining_ready, MinerStatus};
|
||||
use stacks::core::StacksEpochId;
|
||||
use stacks::net::atlas::{AtlasConfig, AtlasDB, Attachment};
|
||||
@@ -26,12 +25,12 @@ use stacks::util_lib::db::Error as db_error;
|
||||
use stacks_common::deps_common::ctrlc as termination;
|
||||
use stacks_common::deps_common::ctrlc::SignalId;
|
||||
use stacks_common::types::PublicKey;
|
||||
use stacks_common::util::hash::{to_hex, Hash160};
|
||||
use stacks_common::util::hash::Hash160;
|
||||
use stacks_common::util::{get_epoch_time_secs, sleep_ms};
|
||||
use stx_genesis::GenesisData;
|
||||
|
||||
use super::RunLoopCallbacks;
|
||||
use crate::burnchains::make_bitcoin_indexer;
|
||||
use crate::burnchains::{make_bitcoin_indexer, Error};
|
||||
use crate::globals::NeonGlobals as Globals;
|
||||
use crate::monitoring::{start_serving_monitoring_metrics, MonitoringError};
|
||||
use crate::neon_node::{StacksNode, BLOCK_PROCESSOR_STACK_SIZE, RELAYER_MAX_BUFFER};
|
||||
@@ -47,10 +46,12 @@ use crate::{
|
||||
pub const STDERR: i32 = 2;
|
||||
|
||||
#[cfg(test)]
|
||||
pub type RunLoopCounter = Arc<AtomicU64>;
|
||||
#[derive(Clone)]
|
||||
pub struct RunLoopCounter(pub Arc<AtomicU64>);
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub type RunLoopCounter = ();
|
||||
#[derive(Clone)]
|
||||
pub struct RunLoopCounter();
|
||||
|
||||
#[cfg(test)]
|
||||
const UNCONDITIONAL_CHAIN_LIVENESS_CHECK: u64 = 30;
|
||||
@@ -58,7 +59,27 @@ const UNCONDITIONAL_CHAIN_LIVENESS_CHECK: u64 = 30;
|
||||
#[cfg(not(test))]
|
||||
const UNCONDITIONAL_CHAIN_LIVENESS_CHECK: u64 = 300;
|
||||
|
||||
#[derive(Clone)]
|
||||
impl Default for RunLoopCounter {
|
||||
#[cfg(test)]
|
||||
fn default() -> Self {
|
||||
RunLoopCounter(Arc::new(AtomicU64::new(0)))
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
fn default() -> Self {
|
||||
Self()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl std::ops::Deref for RunLoopCounter {
|
||||
type Target = Arc<AtomicU64>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Counters {
|
||||
pub blocks_processed: RunLoopCounter,
|
||||
pub microblocks_processed: RunLoopCounter,
|
||||
@@ -69,43 +90,18 @@ pub struct Counters {
|
||||
pub naka_submitted_vrfs: RunLoopCounter,
|
||||
pub naka_submitted_commits: RunLoopCounter,
|
||||
pub naka_mined_blocks: RunLoopCounter,
|
||||
pub naka_proposed_blocks: RunLoopCounter,
|
||||
pub naka_mined_tenures: RunLoopCounter,
|
||||
}
|
||||
|
||||
impl Counters {
|
||||
#[cfg(test)]
|
||||
pub fn new() -> Counters {
|
||||
Counters {
|
||||
blocks_processed: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
microblocks_processed: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
missed_tenures: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
missed_microblock_tenures: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
cancelled_commits: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
naka_submitted_vrfs: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
naka_submitted_commits: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
naka_mined_blocks: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
naka_mined_tenures: RunLoopCounter::new(AtomicU64::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub fn new() -> Counters {
|
||||
Counters {
|
||||
blocks_processed: (),
|
||||
microblocks_processed: (),
|
||||
missed_tenures: (),
|
||||
missed_microblock_tenures: (),
|
||||
cancelled_commits: (),
|
||||
naka_submitted_vrfs: (),
|
||||
naka_submitted_commits: (),
|
||||
naka_mined_blocks: (),
|
||||
naka_mined_tenures: (),
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn inc(ctr: &RunLoopCounter) {
|
||||
ctr.fetch_add(1, Ordering::SeqCst);
|
||||
ctr.0.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
@@ -113,7 +109,7 @@ impl Counters {
|
||||
|
||||
#[cfg(test)]
|
||||
fn set(ctr: &RunLoopCounter, value: u64) {
|
||||
ctr.store(value, Ordering::SeqCst);
|
||||
ctr.0.store(value, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
@@ -151,6 +147,10 @@ impl Counters {
|
||||
Counters::inc(&self.naka_mined_blocks);
|
||||
}
|
||||
|
||||
pub fn bump_naka_proposed_blocks(&self) {
|
||||
Counters::inc(&self.naka_proposed_blocks);
|
||||
}
|
||||
|
||||
pub fn bump_naka_mined_tenures(&self) {
|
||||
Counters::inc(&self.naka_mined_tenures);
|
||||
}
|
||||
@@ -217,7 +217,7 @@ impl RunLoop {
|
||||
globals: None,
|
||||
coordinator_channels: Some(channels),
|
||||
callbacks: RunLoopCallbacks::new(),
|
||||
counters: Counters::new(),
|
||||
counters: Counters::default(),
|
||||
should_keep_running,
|
||||
event_dispatcher,
|
||||
pox_watchdog: None,
|
||||
@@ -393,13 +393,13 @@ impl RunLoop {
|
||||
should_keep_running: Arc<AtomicBool>,
|
||||
burnchain_opt: Option<Burnchain>,
|
||||
coordinator_senders: CoordinatorChannels,
|
||||
) -> BitcoinRegtestController {
|
||||
) -> Result<BitcoinRegtestController, burnchain_error> {
|
||||
// Initialize and start the burnchain.
|
||||
let mut burnchain_controller = BitcoinRegtestController::with_burnchain(
|
||||
config.clone(),
|
||||
Some(coordinator_senders),
|
||||
burnchain_opt,
|
||||
Some(should_keep_running),
|
||||
Some(should_keep_running.clone()),
|
||||
);
|
||||
|
||||
let burnchain = burnchain_controller.get_burnchain();
|
||||
@@ -448,13 +448,21 @@ impl RunLoop {
|
||||
}
|
||||
};
|
||||
|
||||
match burnchain_controller.start(Some(target_burnchain_block_height)) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
burnchain_controller
|
||||
.start(Some(target_burnchain_block_height))
|
||||
.map_err(|e| {
|
||||
match e {
|
||||
Error::CoordinatorClosed => {
|
||||
if !should_keep_running.load(Ordering::SeqCst) {
|
||||
info!("Shutdown initiated during burnchain initialization: {}", e);
|
||||
return burnchain_error::ShutdownInitiated;
|
||||
}
|
||||
}
|
||||
Error::IndexerError(_) => {}
|
||||
}
|
||||
error!("Burnchain controller stopped: {}", e);
|
||||
panic!();
|
||||
}
|
||||
};
|
||||
})?;
|
||||
|
||||
// if the chainstate DBs don't exist, this will instantiate them
|
||||
if let Err(e) = burnchain_controller.connect_dbs() {
|
||||
@@ -464,7 +472,7 @@ impl RunLoop {
|
||||
|
||||
// TODO (hack) instantiate the sortdb in the burnchain
|
||||
let _ = burnchain_controller.sortdb_mut();
|
||||
burnchain_controller
|
||||
Ok(burnchain_controller)
|
||||
}
|
||||
|
||||
/// Boot up the stacks chainstate.
|
||||
@@ -481,23 +489,10 @@ impl RunLoop {
|
||||
.map(|e| (e.address.clone(), e.amount))
|
||||
.collect();
|
||||
|
||||
// TODO: delete this once aggregate public key voting is working
|
||||
let agg_pubkey_boot_callback = if let Some(self_signer) = self.config.self_signing() {
|
||||
let agg_pub_key = self_signer.aggregate_public_key.clone();
|
||||
info!("Neon node setting agg public key"; "agg_pub_key" => %to_hex(&agg_pub_key.compress().data));
|
||||
let callback = Box::new(move |clarity_tx: &mut ClarityTx| {
|
||||
NakamotoChainState::aggregate_public_key_bootcode(clarity_tx, &agg_pub_key)
|
||||
}) as Box<dyn FnOnce(&mut ClarityTx)>;
|
||||
Some(callback)
|
||||
} else {
|
||||
warn!("Self-signing is not supported yet");
|
||||
None
|
||||
};
|
||||
|
||||
// instantiate chainstate
|
||||
let mut boot_data = ChainStateBootData {
|
||||
initial_balances,
|
||||
post_flight_callback: agg_pubkey_boot_callback,
|
||||
post_flight_callback: None,
|
||||
first_burnchain_block_hash: burnchain_config.first_block_hash,
|
||||
first_burnchain_block_height: burnchain_config.first_block_height as u32,
|
||||
first_burnchain_block_timestamp: burnchain_config.first_block_timestamp,
|
||||
@@ -514,6 +509,7 @@ impl RunLoop {
|
||||
get_bulk_initial_names: Some(Box::new(move || get_names(use_test_genesis_data))),
|
||||
};
|
||||
|
||||
info!("About to call open_and_exec");
|
||||
let (chain_state_db, receipts) = StacksChainState::open_and_exec(
|
||||
self.config.is_mainnet(),
|
||||
self.config.burnchain.chain_id,
|
||||
@@ -1007,13 +1003,27 @@ impl RunLoop {
|
||||
.expect("Run loop already started, can only start once after initialization.");
|
||||
|
||||
Self::setup_termination_handler(self.should_keep_running.clone(), false);
|
||||
let mut burnchain = Self::instantiate_burnchain_state(
|
||||
|
||||
let burnchain_result = Self::instantiate_burnchain_state(
|
||||
&self.config,
|
||||
self.should_keep_running.clone(),
|
||||
burnchain_opt,
|
||||
coordinator_senders.clone(),
|
||||
);
|
||||
|
||||
let mut burnchain = match burnchain_result {
|
||||
Ok(burnchain_controller) => burnchain_controller,
|
||||
Err(burnchain_error::ShutdownInitiated) => {
|
||||
info!("Exiting stacks-node");
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error initializing burnchain: {}", e);
|
||||
info!("Exiting stacks-node");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let burnchain_config = burnchain.get_burnchain();
|
||||
self.burnchain = Some(burnchain_config.clone());
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{env, thread};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::convert::{From, TryFrom};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use clarity::vm::costs::ExecutionCost;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
// 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;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -13,24 +13,30 @@
|
||||
//
|
||||
// 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};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, thread};
|
||||
|
||||
use clarity::vm::ast::ASTRules;
|
||||
use clarity::vm::costs::ExecutionCost;
|
||||
use clarity::vm::types::PrincipalData;
|
||||
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
|
||||
use http_types::headers::AUTHORIZATION;
|
||||
use lazy_static::lazy_static;
|
||||
use libsigner::{SignerSession, StackerDBSession};
|
||||
use libsigner::{BlockResponse, SignerMessage, SignerSession, StackerDBSession};
|
||||
use stacks::burnchains::MagicBytes;
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::coordinator::comm::CoordinatorChannels;
|
||||
use stacks::chainstate::nakamoto::miner::NakamotoBlockBuilder;
|
||||
use stacks::chainstate::nakamoto::signer_set::NakamotoSigners;
|
||||
use stacks::chainstate::nakamoto::test_signers::TestSigners;
|
||||
use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState};
|
||||
use stacks::chainstate::stacks::address::PoxAddress;
|
||||
use stacks::chainstate::stacks::boot::{MINERS_NAME, SIGNERS_VOTING_NAME};
|
||||
use stacks::chainstate::stacks::boot::{
|
||||
MINERS_NAME, SIGNERS_VOTING_FUNCTION_NAME, SIGNERS_VOTING_NAME,
|
||||
};
|
||||
use stacks::chainstate::stacks::db::StacksChainState;
|
||||
use stacks::chainstate::stacks::miner::{BlockBuilder, BlockLimitFunction, TransactionResult};
|
||||
use stacks::chainstate::stacks::{StacksTransaction, ThresholdSignature, TransactionPayload};
|
||||
@@ -40,6 +46,7 @@ use stacks::core::{
|
||||
PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4,
|
||||
PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0,
|
||||
};
|
||||
use stacks::libstackerdb::{SlotMetadata, StackerDBChunkData};
|
||||
use stacks::net::api::callreadonly::CallReadOnlyRequestBody;
|
||||
use stacks::net::api::getstackers::GetStackersResponse;
|
||||
use stacks::net::api::postblock_proposal::{
|
||||
@@ -55,8 +62,8 @@ use stacks_common::consts::{CHAIN_ID_TESTNET, STACKS_EPOCH_MAX};
|
||||
use stacks_common::types::chainstate::{
|
||||
BlockHeaderHash, StacksAddress, StacksPrivateKey, StacksPublicKey,
|
||||
};
|
||||
use stacks_common::util::hash::to_hex;
|
||||
use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey};
|
||||
use stacks_common::util::hash::{to_hex, Sha512Trunc256Sum};
|
||||
use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey};
|
||||
|
||||
use super::bitcoin_regtest::BitcoinCoreController;
|
||||
use crate::config::{EventKeyType, EventObserverConfig, InitialBalance};
|
||||
@@ -154,6 +161,32 @@ pub fn get_stacker_set(http_origin: &str, cycle: u64) -> GetStackersResponse {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_stackerdb_slot_version(
|
||||
http_origin: &str,
|
||||
contract: &QualifiedContractIdentifier,
|
||||
slot_id: u64,
|
||||
) -> Option<u32> {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let path = format!(
|
||||
"{http_origin}/v2/stackerdb/{}/{}",
|
||||
&contract.issuer, &contract.name
|
||||
);
|
||||
let res = client
|
||||
.get(&path)
|
||||
.send()
|
||||
.unwrap()
|
||||
.json::<Vec<SlotMetadata>>()
|
||||
.unwrap();
|
||||
debug!("StackerDB metadata response: {res:?}");
|
||||
res.iter().find_map(|slot| {
|
||||
if u64::from(slot.slot_id) == slot_id {
|
||||
Some(slot.slot_version)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_initial_balances(
|
||||
conf: &mut Config,
|
||||
accounts: usize,
|
||||
@@ -171,6 +204,114 @@ pub fn add_initial_balances(
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Spawn a blind signing thread. `signer` is the private key
|
||||
/// of the individual signer who broadcasts the response to the StackerDB
|
||||
pub fn blind_signer(
|
||||
conf: &Config,
|
||||
signers: &TestSigners,
|
||||
signer: &Secp256k1PrivateKey,
|
||||
proposals_count: RunLoopCounter,
|
||||
) -> JoinHandle<()> {
|
||||
let mut signed_blocks = HashSet::new();
|
||||
let conf = conf.clone();
|
||||
let signers = signers.clone();
|
||||
let signer = signer.clone();
|
||||
let mut last_count = proposals_count.load(Ordering::SeqCst);
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
let cur_count = proposals_count.load(Ordering::SeqCst);
|
||||
if cur_count <= last_count {
|
||||
continue;
|
||||
}
|
||||
last_count = cur_count;
|
||||
match read_and_sign_block_proposal(&conf, &signers, &signer, &signed_blocks) {
|
||||
Ok(signed_block) => {
|
||||
if signed_blocks.contains(&signed_block) {
|
||||
continue;
|
||||
}
|
||||
info!("Signed block"; "signer_sig_hash" => signed_block.to_hex());
|
||||
signed_blocks.insert(signed_block);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error reading and signing block proposal: {e}");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_and_sign_block_proposal(
|
||||
conf: &Config,
|
||||
signers: &TestSigners,
|
||||
signer: &Secp256k1PrivateKey,
|
||||
signed_blocks: &HashSet<Sha512Trunc256Sum>,
|
||||
) -> Result<Sha512Trunc256Sum, String> {
|
||||
let burnchain = conf.get_burnchain();
|
||||
let sortdb = burnchain.open_sortition_db(true).unwrap();
|
||||
let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
|
||||
let miner_pubkey = StacksPublicKey::from_private(&conf.get_miner_config().mining_key.unwrap());
|
||||
let miner_slot_id = NakamotoChainState::get_miner_slot(&sortdb, &tip, &miner_pubkey)
|
||||
.map_err(|_| "Unable to get miner slot")?
|
||||
.ok_or("No miner slot exists")?;
|
||||
let reward_cycle = burnchain
|
||||
.block_height_to_reward_cycle(tip.block_height)
|
||||
.unwrap();
|
||||
|
||||
let mut proposed_block: NakamotoBlock = {
|
||||
let miner_contract_id = boot_code_id(MINERS_NAME, false);
|
||||
let mut miners_stackerdb = StackerDBSession::new(&conf.node.rpc_bind, miner_contract_id);
|
||||
miners_stackerdb
|
||||
.get_latest(miner_slot_id)
|
||||
.map_err(|_| "Failed to get latest chunk from the miner slot ID")?
|
||||
.ok_or("No chunk found")?
|
||||
};
|
||||
let proposed_block_hash = format!("0x{}", proposed_block.header.block_hash());
|
||||
let signer_sig_hash = proposed_block.header.signer_signature_hash();
|
||||
if signed_blocks.contains(&signer_sig_hash) {
|
||||
// already signed off on this block, don't sign again.
|
||||
return Ok(signer_sig_hash);
|
||||
}
|
||||
|
||||
info!(
|
||||
"Fetched proposed block from .miners StackerDB";
|
||||
"proposed_block_hash" => &proposed_block_hash,
|
||||
"signer_sig_hash" => &signer_sig_hash.to_hex(),
|
||||
);
|
||||
|
||||
signers
|
||||
.clone()
|
||||
.sign_nakamoto_block(&mut proposed_block, reward_cycle);
|
||||
|
||||
let signer_message = SignerMessage::BlockResponse(BlockResponse::Accepted((
|
||||
signer_sig_hash.clone(),
|
||||
proposed_block.header.signer_signature.clone(),
|
||||
)));
|
||||
|
||||
let signers_contract_id =
|
||||
NakamotoSigners::make_signers_db_contract_id(reward_cycle, libsigner::BLOCK_MSG_ID, false);
|
||||
|
||||
let http_origin = format!("http://{}", &conf.node.rpc_bind);
|
||||
let signers_info = get_stacker_set(&http_origin, reward_cycle);
|
||||
let signer_index = get_signer_index(&signers_info, &Secp256k1PublicKey::from_private(signer))
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let next_version = get_stackerdb_slot_version(&http_origin, &signers_contract_id, signer_index)
|
||||
.map(|x| x + 1)
|
||||
.unwrap_or(0);
|
||||
let mut signers_contract_sess = StackerDBSession::new(&conf.node.rpc_bind, signers_contract_id);
|
||||
let mut chunk_to_put = StackerDBChunkData::new(
|
||||
u32::try_from(signer_index).unwrap(),
|
||||
next_version,
|
||||
signer_message.serialize_to_vec(),
|
||||
);
|
||||
chunk_to_put.sign(signer).unwrap();
|
||||
signers_contract_sess
|
||||
.put_chunk(&chunk_to_put)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(signer_sig_hash)
|
||||
}
|
||||
|
||||
/// Return a working nakamoto-neon config and the miner's bitcoin address to fund
|
||||
pub fn naka_neon_integration_conf(seed: Option<&[u8]>) -> (Config, StacksAddress) {
|
||||
let mut conf = super::new_test_conf();
|
||||
@@ -189,13 +330,9 @@ pub fn naka_neon_integration_conf(seed: Option<&[u8]>) -> (Config, StacksAddress
|
||||
|
||||
let mining_key = Secp256k1PrivateKey::from_seed(&[1]);
|
||||
conf.miner.mining_key = Some(mining_key);
|
||||
conf.miner.self_signing_key = Some(TestSigners::default());
|
||||
|
||||
conf.node.miner = true;
|
||||
conf.node.wait_time_for_microblocks = 500;
|
||||
conf.node
|
||||
.stacker_dbs
|
||||
.push(boot_code_id(MINERS_NAME, conf.is_mainnet()));
|
||||
conf.burnchain.burn_fee_cap = 20000;
|
||||
|
||||
conf.burnchain.username = Some("neon-tester".into());
|
||||
@@ -204,6 +341,8 @@ pub fn naka_neon_integration_conf(seed: Option<&[u8]>) -> (Config, StacksAddress
|
||||
conf.burnchain.local_mining_public_key =
|
||||
Some(keychain.generate_op_signer().get_public_key().to_hex());
|
||||
conf.burnchain.commit_anchor_block_within = 0;
|
||||
conf.node.add_signers_stackerdbs(false);
|
||||
conf.node.add_miner_stackerdb(false);
|
||||
|
||||
// test to make sure config file parsing is correct
|
||||
let mut cfile = ConfigFile::xenon();
|
||||
@@ -356,9 +495,10 @@ pub fn setup_stacker(naka_conf: &mut Config) -> Secp256k1PrivateKey {
|
||||
/// for pox-4 to activate
|
||||
pub fn boot_to_epoch_3(
|
||||
naka_conf: &Config,
|
||||
blocks_processed: &RunLoopCounter,
|
||||
blocks_processed: &Arc<AtomicU64>,
|
||||
stacker_sks: &[StacksPrivateKey],
|
||||
signer_sks: &[StacksPrivateKey],
|
||||
self_signing: Option<&TestSigners>,
|
||||
btc_regtest_controller: &mut BitcoinRegtestController,
|
||||
) {
|
||||
assert_eq!(stacker_sks.len(), signer_sks.len());
|
||||
@@ -440,25 +580,29 @@ pub fn boot_to_epoch_3(
|
||||
&naka_conf,
|
||||
);
|
||||
|
||||
// If we are self-signing, then we need to vote on the aggregate public key
|
||||
if let Some(mut signers) = naka_conf.self_signing() {
|
||||
// We need to vote on the aggregate public key if this test is self signing
|
||||
if let Some(signers) = self_signing {
|
||||
// Get the aggregate key
|
||||
let aggregate_key = signers.generate_aggregate_key(reward_cycle + 1);
|
||||
let aggregate_key = signers.clone().generate_aggregate_key(reward_cycle + 1);
|
||||
let aggregate_public_key =
|
||||
clarity::vm::Value::buff_from(aggregate_key.compress().data.to_vec())
|
||||
.expect("Failed to serialize aggregate public key");
|
||||
|
||||
let signer_sks_unique: HashMap<_, _> = signer_sks.iter().map(|x| (x.to_hex(), x)).collect();
|
||||
let signer_set = get_stacker_set(&http_origin, reward_cycle + 1);
|
||||
// Vote on the aggregate public key
|
||||
for (i, signer_sk) in signer_sks.iter().enumerate() {
|
||||
for signer_sk in signer_sks_unique.values() {
|
||||
let signer_index =
|
||||
get_signer_index(&signer_set, &Secp256k1PublicKey::from_private(signer_sk))
|
||||
.unwrap();
|
||||
let voting_tx = tests::make_contract_call(
|
||||
&signer_sk,
|
||||
signer_sk,
|
||||
0,
|
||||
300,
|
||||
&StacksAddress::burn_address(false),
|
||||
SIGNERS_VOTING_NAME,
|
||||
"vote-for-aggregate-public-key",
|
||||
SIGNERS_VOTING_FUNCTION_NAME,
|
||||
&[
|
||||
clarity::vm::Value::UInt(i as u128),
|
||||
clarity::vm::Value::UInt(u128::try_from(signer_index).unwrap()),
|
||||
aggregate_public_key.clone(),
|
||||
clarity::vm::Value::UInt(0),
|
||||
clarity::vm::Value::UInt(reward_cycle as u128 + 1),
|
||||
@@ -478,6 +622,32 @@ pub fn boot_to_epoch_3(
|
||||
info!("Bootstrapped to Epoch-3.0 boundary, Epoch2x miner should stop");
|
||||
}
|
||||
|
||||
fn get_signer_index(
|
||||
stacker_set: &GetStackersResponse,
|
||||
signer_key: &Secp256k1PublicKey,
|
||||
) -> Result<usize, String> {
|
||||
let Some(ref signer_set) = stacker_set.stacker_set.signers else {
|
||||
return Err("Empty signer set for reward cycle".into());
|
||||
};
|
||||
let signer_key_bytes = signer_key.to_bytes_compressed();
|
||||
signer_set
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(ix, entry)| {
|
||||
if entry.signing_key.as_slice() == signer_key_bytes.as_slice() {
|
||||
Some(ix)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Signing key not found. {} not found.",
|
||||
to_hex(&signer_key_bytes)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_key_set_for_cycle(
|
||||
reward_cycle: u64,
|
||||
is_mainnet: bool,
|
||||
@@ -518,67 +688,165 @@ fn signer_vote_if_needed(
|
||||
btc_regtest_controller: &BitcoinRegtestController,
|
||||
naka_conf: &Config,
|
||||
signer_sks: &[StacksPrivateKey], // TODO: Is there some way to get this from the TestSigners?
|
||||
signers: &TestSigners,
|
||||
) {
|
||||
if let Some(mut signers) = naka_conf.self_signing() {
|
||||
// When we reach the next prepare phase, submit new voting transactions
|
||||
let block_height = btc_regtest_controller.get_headers_height();
|
||||
let reward_cycle = btc_regtest_controller
|
||||
.get_burnchain()
|
||||
.block_height_to_reward_cycle(block_height)
|
||||
.unwrap();
|
||||
let prepare_phase_start = btc_regtest_controller
|
||||
.get_burnchain()
|
||||
.pox_constants
|
||||
.prepare_phase_start(
|
||||
btc_regtest_controller.get_burnchain().first_block_height,
|
||||
reward_cycle,
|
||||
// When we reach the next prepare phase, submit new voting transactions
|
||||
let block_height = btc_regtest_controller.get_headers_height();
|
||||
let reward_cycle = btc_regtest_controller
|
||||
.get_burnchain()
|
||||
.block_height_to_reward_cycle(block_height)
|
||||
.unwrap();
|
||||
let prepare_phase_start = btc_regtest_controller
|
||||
.get_burnchain()
|
||||
.pox_constants
|
||||
.prepare_phase_start(
|
||||
btc_regtest_controller.get_burnchain().first_block_height,
|
||||
reward_cycle,
|
||||
);
|
||||
|
||||
if block_height >= prepare_phase_start {
|
||||
// If the key is already set, do nothing.
|
||||
if is_key_set_for_cycle(
|
||||
reward_cycle + 1,
|
||||
naka_conf.is_mainnet(),
|
||||
&naka_conf.node.rpc_bind,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are self-signing, then we need to vote on the aggregate public key
|
||||
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
|
||||
|
||||
// Get the aggregate key
|
||||
let aggregate_key = signers.clone().generate_aggregate_key(reward_cycle + 1);
|
||||
let aggregate_public_key =
|
||||
clarity::vm::Value::buff_from(aggregate_key.compress().data.to_vec())
|
||||
.expect("Failed to serialize aggregate public key");
|
||||
|
||||
for (i, signer_sk) in signer_sks.iter().enumerate() {
|
||||
let signer_nonce = get_account(&http_origin, &to_addr(signer_sk)).nonce;
|
||||
|
||||
// Vote on the aggregate public key
|
||||
let voting_tx = tests::make_contract_call(
|
||||
&signer_sk,
|
||||
signer_nonce,
|
||||
300,
|
||||
&StacksAddress::burn_address(false),
|
||||
SIGNERS_VOTING_NAME,
|
||||
"vote-for-aggregate-public-key",
|
||||
&[
|
||||
clarity::vm::Value::UInt(i as u128),
|
||||
aggregate_public_key.clone(),
|
||||
clarity::vm::Value::UInt(0),
|
||||
clarity::vm::Value::UInt(reward_cycle as u128 + 1),
|
||||
],
|
||||
);
|
||||
|
||||
if block_height >= prepare_phase_start {
|
||||
// If the key is already set, do nothing.
|
||||
if is_key_set_for_cycle(
|
||||
reward_cycle + 1,
|
||||
naka_conf.is_mainnet(),
|
||||
&naka_conf.node.rpc_bind,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are self-signing, then we need to vote on the aggregate public key
|
||||
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
|
||||
|
||||
// Get the aggregate key
|
||||
let aggregate_key = signers.generate_aggregate_key(reward_cycle + 1);
|
||||
let aggregate_public_key =
|
||||
clarity::vm::Value::buff_from(aggregate_key.compress().data.to_vec())
|
||||
.expect("Failed to serialize aggregate public key");
|
||||
|
||||
for (i, signer_sk) in signer_sks.iter().enumerate() {
|
||||
let signer_nonce = get_account(&http_origin, &to_addr(signer_sk)).nonce;
|
||||
|
||||
// Vote on the aggregate public key
|
||||
let voting_tx = tests::make_contract_call(
|
||||
&signer_sk,
|
||||
signer_nonce,
|
||||
300,
|
||||
&StacksAddress::burn_address(false),
|
||||
SIGNERS_VOTING_NAME,
|
||||
"vote-for-aggregate-public-key",
|
||||
&[
|
||||
clarity::vm::Value::UInt(i as u128),
|
||||
aggregate_public_key.clone(),
|
||||
clarity::vm::Value::UInt(0),
|
||||
clarity::vm::Value::UInt(reward_cycle as u128 + 1),
|
||||
],
|
||||
);
|
||||
submit_tx(&http_origin, &voting_tx);
|
||||
}
|
||||
submit_tx(&http_origin, &voting_tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// * `stacker_sks` - must be a private key for sending a large `stack-stx` transaction in order
|
||||
/// for pox-4 to activate
|
||||
/// * `signer_pks` - must be the same size as `stacker_sks`
|
||||
pub fn boot_to_epoch_3_reward_set(
|
||||
naka_conf: &Config,
|
||||
blocks_processed: &Arc<AtomicU64>,
|
||||
stacker_sks: &[StacksPrivateKey],
|
||||
signer_sks: &[StacksPrivateKey],
|
||||
btc_regtest_controller: &mut BitcoinRegtestController,
|
||||
) {
|
||||
assert_eq!(stacker_sks.len(), signer_sks.len());
|
||||
|
||||
let epochs = naka_conf.burnchain.epochs.clone().unwrap();
|
||||
let epoch_3 = &epochs[StacksEpoch::find_epoch_by_id(&epochs, StacksEpochId::Epoch30).unwrap()];
|
||||
let reward_cycle_len = naka_conf.get_burnchain().pox_constants.reward_cycle_length as u64;
|
||||
let prepare_phase_len = naka_conf.get_burnchain().pox_constants.prepare_length as u64;
|
||||
|
||||
let epoch_3_start_height = epoch_3.start_height;
|
||||
assert!(
|
||||
epoch_3_start_height > 0,
|
||||
"Epoch 3.0 start height must be greater than 0"
|
||||
);
|
||||
let epoch_3_reward_cycle_boundary =
|
||||
epoch_3_start_height.saturating_sub(epoch_3_start_height % reward_cycle_len);
|
||||
let epoch_3_reward_set_calculation_boundary =
|
||||
epoch_3_reward_cycle_boundary.saturating_sub(prepare_phase_len);
|
||||
let epoch_3_reward_set_calculation = epoch_3_reward_set_calculation_boundary.wrapping_add(2); // +2 to ensure we are at the second block of the prepare phase
|
||||
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
|
||||
next_block_and_wait(btc_regtest_controller, &blocks_processed);
|
||||
next_block_and_wait(btc_regtest_controller, &blocks_processed);
|
||||
// first mined stacks block
|
||||
next_block_and_wait(btc_regtest_controller, &blocks_processed);
|
||||
|
||||
// stack enough to activate pox-4
|
||||
let block_height = btc_regtest_controller.get_headers_height();
|
||||
let reward_cycle = btc_regtest_controller
|
||||
.get_burnchain()
|
||||
.block_height_to_reward_cycle(block_height)
|
||||
.unwrap();
|
||||
let lock_period = 12;
|
||||
debug!("Test Cycle Info";
|
||||
"prepare_phase_len" => {prepare_phase_len},
|
||||
"reward_cycle_len" => {reward_cycle_len},
|
||||
"block_height" => {block_height},
|
||||
"reward_cycle" => {reward_cycle},
|
||||
"epoch_3_reward_cycle_boundary" => {epoch_3_reward_cycle_boundary},
|
||||
"epoch_3_reward_set_calculation" => {epoch_3_reward_set_calculation},
|
||||
"epoch_3_start_height" => {epoch_3_start_height},
|
||||
);
|
||||
for (stacker_sk, signer_sk) in stacker_sks.iter().zip(signer_sks.iter()) {
|
||||
let pox_addr = PoxAddress::from_legacy(
|
||||
AddressHashMode::SerializeP2PKH,
|
||||
tests::to_addr(&stacker_sk).bytes,
|
||||
);
|
||||
let pox_addr_tuple: clarity::vm::Value =
|
||||
pox_addr.clone().as_clarity_tuple().unwrap().into();
|
||||
let signature = make_pox_4_signer_key_signature(
|
||||
&pox_addr,
|
||||
&signer_sk,
|
||||
reward_cycle.into(),
|
||||
&Pox4SignatureTopic::StackStx,
|
||||
CHAIN_ID_TESTNET,
|
||||
lock_period,
|
||||
)
|
||||
.unwrap()
|
||||
.to_rsv();
|
||||
|
||||
let signer_pk = StacksPublicKey::from_private(signer_sk);
|
||||
let stacking_tx = tests::make_contract_call(
|
||||
&stacker_sk,
|
||||
0,
|
||||
1000,
|
||||
&StacksAddress::burn_address(false),
|
||||
"pox-4",
|
||||
"stack-stx",
|
||||
&[
|
||||
clarity::vm::Value::UInt(POX_4_DEFAULT_STACKER_STX_AMT),
|
||||
pox_addr_tuple.clone(),
|
||||
clarity::vm::Value::UInt(block_height as u128),
|
||||
clarity::vm::Value::UInt(lock_period),
|
||||
clarity::vm::Value::some(clarity::vm::Value::buff_from(signature).unwrap())
|
||||
.unwrap(),
|
||||
clarity::vm::Value::buff_from(signer_pk.to_bytes_compressed()).unwrap(),
|
||||
],
|
||||
);
|
||||
submit_tx(&http_origin, &stacking_tx);
|
||||
}
|
||||
|
||||
run_until_burnchain_height(
|
||||
btc_regtest_controller,
|
||||
&blocks_processed,
|
||||
epoch_3_reward_set_calculation,
|
||||
&naka_conf,
|
||||
);
|
||||
|
||||
info!("Bootstrapped to Epoch 3.0 reward set calculation height: {epoch_3_reward_set_calculation}.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
/// This test spins up a nakamoto-neon node.
|
||||
@@ -594,6 +862,7 @@ fn simple_neon_integration() {
|
||||
return;
|
||||
}
|
||||
|
||||
let signers = TestSigners::default();
|
||||
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
|
||||
let prom_bind = format!("{}:{}", "127.0.0.1", 6000);
|
||||
naka_conf.node.prometheus_bind = Some(prom_bind.clone());
|
||||
@@ -636,6 +905,7 @@ fn simple_neon_integration() {
|
||||
blocks_processed,
|
||||
naka_submitted_vrfs: vrfs_submitted,
|
||||
naka_submitted_commits: commits_submitted,
|
||||
naka_proposed_blocks: proposals_submitted,
|
||||
..
|
||||
} = run_loop.counters();
|
||||
|
||||
@@ -648,6 +918,7 @@ fn simple_neon_integration() {
|
||||
&blocks_processed,
|
||||
&[stacker_sk],
|
||||
&[sender_signer_sk],
|
||||
Some(&signers),
|
||||
&mut btc_regtest_controller,
|
||||
);
|
||||
|
||||
@@ -685,6 +956,8 @@ fn simple_neon_integration() {
|
||||
}
|
||||
|
||||
info!("Nakamoto miner started...");
|
||||
blind_signer(&naka_conf, &signers, &sender_signer_sk, proposals_submitted);
|
||||
|
||||
// first block wakes up the run loop, wait until a key registration has been submitted.
|
||||
next_block_and(&mut btc_regtest_controller, 60, || {
|
||||
let vrf_count = vrfs_submitted.load(Ordering::SeqCst);
|
||||
@@ -709,7 +982,12 @@ fn simple_neon_integration() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
signer_vote_if_needed(&btc_regtest_controller, &naka_conf, &[sender_signer_sk]);
|
||||
signer_vote_if_needed(
|
||||
&btc_regtest_controller,
|
||||
&naka_conf,
|
||||
&[sender_signer_sk],
|
||||
&signers,
|
||||
);
|
||||
}
|
||||
|
||||
// Submit a TX
|
||||
@@ -746,7 +1024,12 @@ fn simple_neon_integration() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
signer_vote_if_needed(&btc_regtest_controller, &naka_conf, &[sender_signer_sk]);
|
||||
signer_vote_if_needed(
|
||||
&btc_regtest_controller,
|
||||
&naka_conf,
|
||||
&[sender_signer_sk],
|
||||
&signers,
|
||||
);
|
||||
}
|
||||
|
||||
// load the chain tip, and assert that it is a nakamoto block and at least 30 blocks have advanced in epoch 3
|
||||
@@ -818,6 +1101,7 @@ fn mine_multiple_per_tenure_integration() {
|
||||
return;
|
||||
}
|
||||
|
||||
let signers = TestSigners::default();
|
||||
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
|
||||
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
|
||||
naka_conf.miner.wait_on_interim_blocks = Duration::from_secs(1);
|
||||
@@ -862,6 +1146,7 @@ fn mine_multiple_per_tenure_integration() {
|
||||
blocks_processed,
|
||||
naka_submitted_vrfs: vrfs_submitted,
|
||||
naka_submitted_commits: commits_submitted,
|
||||
naka_proposed_blocks: proposals_submitted,
|
||||
..
|
||||
} = run_loop.counters();
|
||||
|
||||
@@ -877,6 +1162,7 @@ fn mine_multiple_per_tenure_integration() {
|
||||
&blocks_processed,
|
||||
&[stacker_sk],
|
||||
&[sender_signer_sk],
|
||||
Some(&signers),
|
||||
&mut btc_regtest_controller,
|
||||
);
|
||||
|
||||
@@ -899,6 +1185,8 @@ fn mine_multiple_per_tenure_integration() {
|
||||
.stacks_block_height;
|
||||
|
||||
info!("Nakamoto miner started...");
|
||||
blind_signer(&naka_conf, &signers, &sender_signer_sk, proposals_submitted);
|
||||
|
||||
// first block wakes up the run loop, wait until a key registration has been submitted.
|
||||
next_block_and(&mut btc_regtest_controller, 60, || {
|
||||
let vrf_count = vrfs_submitted.load(Ordering::SeqCst);
|
||||
@@ -995,6 +1283,7 @@ fn correct_burn_outs() {
|
||||
return;
|
||||
}
|
||||
|
||||
let signers = TestSigners::default();
|
||||
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
|
||||
naka_conf.burnchain.pox_reward_length = Some(10);
|
||||
naka_conf.burnchain.pox_prepare_length = Some(3);
|
||||
@@ -1051,6 +1340,7 @@ fn correct_burn_outs() {
|
||||
blocks_processed,
|
||||
naka_submitted_vrfs: vrfs_submitted,
|
||||
naka_submitted_commits: commits_submitted,
|
||||
naka_proposed_blocks: proposals_submitted,
|
||||
..
|
||||
} = run_loop.counters();
|
||||
|
||||
@@ -1182,7 +1472,12 @@ fn correct_burn_outs() {
|
||||
&naka_conf,
|
||||
);
|
||||
|
||||
signer_vote_if_needed(&btc_regtest_controller, &naka_conf, &[sender_signer_sk]);
|
||||
signer_vote_if_needed(
|
||||
&btc_regtest_controller,
|
||||
&naka_conf,
|
||||
&[sender_signer_sk],
|
||||
&signers,
|
||||
);
|
||||
|
||||
run_until_burnchain_height(
|
||||
&mut btc_regtest_controller,
|
||||
@@ -1192,6 +1487,7 @@ fn correct_burn_outs() {
|
||||
);
|
||||
|
||||
info!("Bootstrapped to Epoch-3.0 boundary, Epoch2x miner should stop");
|
||||
blind_signer(&naka_conf, &signers, &sender_signer_sk, proposals_submitted);
|
||||
|
||||
// we should already be able to query the stacker set via RPC
|
||||
let burnchain = naka_conf.get_burnchain();
|
||||
@@ -1255,7 +1551,12 @@ fn correct_burn_outs() {
|
||||
"The new burnchain tip must have been processed"
|
||||
);
|
||||
|
||||
signer_vote_if_needed(&btc_regtest_controller, &naka_conf, &[sender_signer_sk]);
|
||||
signer_vote_if_needed(
|
||||
&btc_regtest_controller,
|
||||
&naka_conf,
|
||||
&[sender_signer_sk],
|
||||
&signers,
|
||||
);
|
||||
}
|
||||
|
||||
coord_channel
|
||||
@@ -1340,7 +1641,10 @@ fn block_proposal_api_endpoint() {
|
||||
return;
|
||||
}
|
||||
|
||||
let signers = TestSigners::default();
|
||||
let (mut conf, _miner_account) = naka_neon_integration_conf(None);
|
||||
let password = "12345".to_string();
|
||||
conf.connection_options.block_proposal_token = Some(password.clone());
|
||||
let account_keys = add_initial_balances(&mut conf, 10, 1_000_000);
|
||||
let stacker_sk = setup_stacker(&mut conf);
|
||||
let sender_signer_sk = Secp256k1PrivateKey::new();
|
||||
@@ -1371,6 +1675,7 @@ fn block_proposal_api_endpoint() {
|
||||
blocks_processed,
|
||||
naka_submitted_vrfs: vrfs_submitted,
|
||||
naka_submitted_commits: commits_submitted,
|
||||
naka_proposed_blocks: proposals_submitted,
|
||||
..
|
||||
} = run_loop.counters();
|
||||
|
||||
@@ -1383,10 +1688,12 @@ fn block_proposal_api_endpoint() {
|
||||
&blocks_processed,
|
||||
&[stacker_sk],
|
||||
&[sender_signer_sk],
|
||||
Some(&signers),
|
||||
&mut btc_regtest_controller,
|
||||
);
|
||||
|
||||
info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner");
|
||||
blind_signer(&conf, &signers, &sender_signer_sk, proposals_submitted);
|
||||
|
||||
let burnchain = conf.get_burnchain();
|
||||
let sortdb = burnchain.open_sortition_db(true).unwrap();
|
||||
@@ -1434,9 +1741,6 @@ fn block_proposal_api_endpoint() {
|
||||
// TODO (hack) instantiate the sortdb in the burnchain
|
||||
_ = btc_regtest_controller.sortdb_mut();
|
||||
|
||||
// Set up test signer
|
||||
let signer = conf.miner.self_signing_key.as_mut().unwrap();
|
||||
|
||||
// ----- Setup boilerplate finished, test block proposal API endpoint -----
|
||||
|
||||
let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
|
||||
@@ -1465,19 +1769,13 @@ fn block_proposal_api_endpoint() {
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// Apply both miner/stacker signatures
|
||||
let mut sign = |mut p: NakamotoBlockProposal| {
|
||||
// Apply miner signature
|
||||
let sign = |p: &NakamotoBlockProposal| {
|
||||
let mut p = p.clone();
|
||||
p.block
|
||||
.header
|
||||
.sign_miner(&privk)
|
||||
.expect("Miner failed to sign");
|
||||
let burn_height = burnchain
|
||||
.get_highest_burnchain_block()
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.block_height;
|
||||
let cycle = burnchain.block_height_to_reward_cycle(burn_height).unwrap();
|
||||
signer.sign_nakamoto_block(&mut p.block, cycle);
|
||||
p
|
||||
};
|
||||
|
||||
@@ -1532,18 +1830,19 @@ fn block_proposal_api_endpoint() {
|
||||
|
||||
const HTTP_ACCEPTED: u16 = 202;
|
||||
const HTTP_TOO_MANY: u16 = 429;
|
||||
const HTTP_NOT_AUTHORIZED: u16 = 401;
|
||||
let test_cases = [
|
||||
(
|
||||
"Valid Nakamoto block proposal",
|
||||
sign(proposal.clone()),
|
||||
sign(&proposal),
|
||||
HTTP_ACCEPTED,
|
||||
Some(Ok(())),
|
||||
),
|
||||
("Must wait", sign(proposal.clone()), HTTP_TOO_MANY, None),
|
||||
("Must wait", sign(&proposal), HTTP_TOO_MANY, None),
|
||||
(
|
||||
"Corrupted (bit flipped after signing)",
|
||||
(|| {
|
||||
let mut sp = sign(proposal.clone());
|
||||
let mut sp = sign(&proposal);
|
||||
sp.block.header.consensus_hash.0[3] ^= 0x07;
|
||||
sp
|
||||
})(),
|
||||
@@ -1555,7 +1854,7 @@ fn block_proposal_api_endpoint() {
|
||||
(|| {
|
||||
let mut p = proposal.clone();
|
||||
p.chain_id ^= 0xFFFFFFFF;
|
||||
sign(p)
|
||||
sign(&p)
|
||||
})(),
|
||||
HTTP_ACCEPTED,
|
||||
Some(Err(ValidateRejectCode::InvalidBlock)),
|
||||
@@ -1563,13 +1862,14 @@ fn block_proposal_api_endpoint() {
|
||||
(
|
||||
"Invalid `miner_signature`",
|
||||
(|| {
|
||||
let mut sp = sign(proposal.clone());
|
||||
let mut sp = sign(&proposal);
|
||||
sp.block.header.miner_signature.0[1] ^= 0x80;
|
||||
sp
|
||||
})(),
|
||||
HTTP_ACCEPTED,
|
||||
Some(Err(ValidateRejectCode::ChainstateError)),
|
||||
),
|
||||
("Not authorized", sign(&proposal), HTTP_NOT_AUTHORIZED, None),
|
||||
];
|
||||
|
||||
// Build HTTP client
|
||||
@@ -1586,12 +1886,18 @@ fn block_proposal_api_endpoint() {
|
||||
test_cases.iter().enumerate()
|
||||
{
|
||||
// Send POST request
|
||||
let mut response = client
|
||||
let request_builder = client
|
||||
.post(&path)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(block_proposal)
|
||||
.send()
|
||||
.expect("Failed to POST");
|
||||
.json(block_proposal);
|
||||
let mut response = if expected_http_code == &HTTP_NOT_AUTHORIZED {
|
||||
request_builder.send().expect("Failed to POST")
|
||||
} else {
|
||||
request_builder
|
||||
.header(AUTHORIZATION.to_string(), password.to_string())
|
||||
.send()
|
||||
.expect("Failed to POST")
|
||||
};
|
||||
let start_time = Instant::now();
|
||||
while ix != 1 && response.status().as_u16() == HTTP_TOO_MANY {
|
||||
if start_time.elapsed() > Duration::from_secs(30) {
|
||||
@@ -1600,20 +1906,29 @@ fn block_proposal_api_endpoint() {
|
||||
}
|
||||
info!("Waiting for prior request to finish processing, and then resubmitting");
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
response = client
|
||||
let request_builder = client
|
||||
.post(&path)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(block_proposal)
|
||||
.send()
|
||||
.expect("Failed to POST");
|
||||
.json(block_proposal);
|
||||
response = if expected_http_code == &HTTP_NOT_AUTHORIZED {
|
||||
request_builder.send().expect("Failed to POST")
|
||||
} else {
|
||||
request_builder
|
||||
.header(AUTHORIZATION.to_string(), password.to_string())
|
||||
.send()
|
||||
.expect("Failed to POST")
|
||||
};
|
||||
}
|
||||
|
||||
let response_code = response.status().as_u16();
|
||||
let response_json = response.json::<serde_json::Value>();
|
||||
|
||||
let response_json = if expected_http_code != &HTTP_NOT_AUTHORIZED {
|
||||
response.json::<serde_json::Value>().unwrap().to_string()
|
||||
} else {
|
||||
"No json response".to_string()
|
||||
};
|
||||
info!(
|
||||
"Block proposal submitted and checked for HTTP response";
|
||||
"response_json" => %response_json.unwrap(),
|
||||
"response_json" => response_json,
|
||||
"request_json" => serde_json::to_string(block_proposal).unwrap(),
|
||||
"response_code" => response_code,
|
||||
"test_description" => test_description,
|
||||
@@ -1687,6 +2002,7 @@ fn miner_writes_proposed_block_to_stackerdb() {
|
||||
return;
|
||||
}
|
||||
|
||||
let signers = TestSigners::default();
|
||||
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
|
||||
naka_conf.miner.wait_on_interim_blocks = Duration::from_secs(1000);
|
||||
let sender_sk = Secp256k1PrivateKey::new();
|
||||
@@ -1727,6 +2043,7 @@ fn miner_writes_proposed_block_to_stackerdb() {
|
||||
blocks_processed,
|
||||
naka_submitted_vrfs: vrfs_submitted,
|
||||
naka_submitted_commits: commits_submitted,
|
||||
naka_proposed_blocks: proposals_submitted,
|
||||
..
|
||||
} = run_loop.counters();
|
||||
|
||||
@@ -1739,10 +2056,12 @@ fn miner_writes_proposed_block_to_stackerdb() {
|
||||
&blocks_processed,
|
||||
&[stacker_sk],
|
||||
&[sender_signer_sk],
|
||||
Some(&signers),
|
||||
&mut btc_regtest_controller,
|
||||
);
|
||||
|
||||
info!("Nakamoto miner started...");
|
||||
blind_signer(&naka_conf, &signers, &sender_signer_sk, proposals_submitted);
|
||||
// first block wakes up the run loop, wait until a key registration has been submitted.
|
||||
next_block_and(&mut btc_regtest_controller, 60, || {
|
||||
let vrf_count = vrfs_submitted.load(Ordering::SeqCst);
|
||||
@@ -1766,13 +2085,6 @@ fn miner_writes_proposed_block_to_stackerdb() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let rpc_sock = naka_conf
|
||||
.node
|
||||
.rpc_bind
|
||||
.clone()
|
||||
.parse()
|
||||
.expect("Failed to parse socket");
|
||||
|
||||
let sortdb = naka_conf.get_burnchain().open_sortition_db(true).unwrap();
|
||||
let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
|
||||
let miner_pubkey =
|
||||
@@ -1781,20 +2093,15 @@ fn miner_writes_proposed_block_to_stackerdb() {
|
||||
.expect("Unable to get miner slot")
|
||||
.expect("No miner slot exists");
|
||||
|
||||
let chunk = std::thread::spawn(move || {
|
||||
let proposed_block: NakamotoBlock = {
|
||||
let miner_contract_id = boot_code_id(MINERS_NAME, false);
|
||||
let mut miners_stackerdb = StackerDBSession::new(rpc_sock, miner_contract_id);
|
||||
let mut miners_stackerdb =
|
||||
StackerDBSession::new(&naka_conf.node.rpc_bind, miner_contract_id);
|
||||
miners_stackerdb
|
||||
.get_latest_chunk(slot_id)
|
||||
.get_latest(slot_id)
|
||||
.expect("Failed to get latest chunk from the miner slot ID")
|
||||
.expect("No chunk found")
|
||||
})
|
||||
.join()
|
||||
.expect("Failed to join chunk handle");
|
||||
|
||||
// We should now successfully deserialize a chunk
|
||||
let proposed_block = NakamotoBlock::consensus_deserialize(&mut &chunk[..])
|
||||
.expect("Failed to deserialize chunk into block");
|
||||
};
|
||||
let proposed_block_hash = format!("0x{}", proposed_block.header.block_hash());
|
||||
|
||||
let mut proposed_zero_block = proposed_block.clone();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{mpsc, Arc};
|
||||
@@ -6083,6 +6082,7 @@ fn pox_integration_test() {
|
||||
let http_origin = format!("http://{}", &conf.node.rpc_bind);
|
||||
|
||||
btc_regtest_controller.bootstrap_chain(201);
|
||||
let burnchain = burnchain_config.clone();
|
||||
|
||||
eprintln!("Chain bootstrapped...");
|
||||
|
||||
@@ -6140,9 +6140,12 @@ fn pox_integration_test() {
|
||||
pox_info.rejection_fraction,
|
||||
Some(pox_constants.pox_rejection_fraction)
|
||||
);
|
||||
assert_eq!(pox_info.reward_cycle_id, 0);
|
||||
assert_eq!(pox_info.current_cycle.id, 0);
|
||||
assert_eq!(pox_info.next_cycle.id, 1);
|
||||
let reward_cycle = burnchain
|
||||
.block_height_to_reward_cycle(sort_height)
|
||||
.expect("Expected to be able to get reward cycle");
|
||||
assert_eq!(pox_info.reward_cycle_id, reward_cycle);
|
||||
assert_eq!(pox_info.current_cycle.id, reward_cycle);
|
||||
assert_eq!(pox_info.next_cycle.id, reward_cycle + 1);
|
||||
assert_eq!(
|
||||
pox_info.reward_cycle_length as u32,
|
||||
pox_constants.reward_cycle_length
|
||||
@@ -6185,6 +6188,9 @@ fn pox_integration_test() {
|
||||
}
|
||||
|
||||
let pox_info = get_pox_info(&http_origin).unwrap();
|
||||
let reward_cycle = burnchain
|
||||
.block_height_to_reward_cycle(sort_height)
|
||||
.expect("Expected to be able to get reward cycle");
|
||||
|
||||
assert_eq!(
|
||||
&pox_info.contract_id,
|
||||
@@ -6208,9 +6214,9 @@ fn pox_integration_test() {
|
||||
pox_info.rejection_fraction,
|
||||
Some(pox_constants.pox_rejection_fraction)
|
||||
);
|
||||
assert_eq!(pox_info.reward_cycle_id, 14);
|
||||
assert_eq!(pox_info.current_cycle.id, 14);
|
||||
assert_eq!(pox_info.next_cycle.id, 15);
|
||||
assert_eq!(pox_info.reward_cycle_id, reward_cycle);
|
||||
assert_eq!(pox_info.current_cycle.id, reward_cycle);
|
||||
assert_eq!(pox_info.next_cycle.id, reward_cycle + 1);
|
||||
assert_eq!(
|
||||
pox_info.reward_cycle_length as u32,
|
||||
pox_constants.reward_cycle_length
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user