Merge pull request #4469 from stacks-network/chore/remove-self-signer

Remove self signer
This commit is contained in:
Brice Dobry
2024-03-05 17:45:53 +00:00
committed by GitHub
11 changed files with 378 additions and 1945 deletions

View File

@@ -22,6 +22,7 @@ use libstackerdb::{
stackerdb_get_chunk_path, stackerdb_get_metadata_path, stackerdb_post_chunk_path, SlotMetadata,
StackerDBChunkAckData, StackerDBChunkData,
};
use stacks_common::codec::StacksMessageCodec;
use crate::error::RPCError;
use crate::http::run_http_request;
@@ -51,7 +52,14 @@ pub trait SignerSession {
/// Returns Ok(None) if the chunk with the given version does not exist
/// Returns Err(..) on transport error
fn get_chunk(&mut self, slot_id: u32, version: u32) -> Result<Option<Vec<u8>>, RPCError> {
Ok(self.get_chunks(&[(slot_id, version)])?[0].clone())
let mut chunks = self.get_chunks(&[(slot_id, version)])?;
// check if chunks is empty because [0] and remove(0) panic on out-of-bounds
if chunks.is_empty() {
return Ok(None);
}
// swap_remove breaks the ordering of latest_chunks, but we don't care because we
// only want the first element anyways.
Ok(chunks.swap_remove(0))
}
/// Get a single latest chunk.
@@ -59,7 +67,29 @@ pub trait SignerSession {
/// Returns Ok(None) if not
/// Returns Err(..) on transport error
fn get_latest_chunk(&mut self, slot_id: u32) -> Result<Option<Vec<u8>>, RPCError> {
Ok(self.get_latest_chunks(&[(slot_id)])?[0].clone())
let mut latest_chunks = self.get_latest_chunks(&[slot_id])?;
// check if latest_chunks is empty because [0] and remove(0) panic on out-of-bounds
if latest_chunks.is_empty() {
return Ok(None);
}
// swap_remove breaks the ordering of latest_chunks, but we don't care because we
// only want the first element anyways.
Ok(latest_chunks.swap_remove(0))
}
/// Get a single latest chunk from the StackerDB and deserialize into `T` using the
/// StacksMessageCodec.
fn get_latest<T: StacksMessageCodec>(&mut self, slot_id: u32) -> Result<Option<T>, RPCError> {
let Some(latest_bytes) = self.get_latest_chunk(slot_id)? else {
return Ok(None);
};
Some(
T::consensus_deserialize(&mut latest_bytes.as_slice()).map_err(|e| {
let msg = format!("StacksMessageCodec::consensus_deserialize failure: {e}");
RPCError::Deserialize(msg)
}),
)
.transpose()
}
}

View File

@@ -91,7 +91,7 @@ const POX_4_BODY: &'static str = std::include_str!("pox-4.clar");
pub const SIGNERS_BODY: &'static str = std::include_str!("signers.clar");
pub const SIGNERS_DB_0_BODY: &'static str = std::include_str!("signers-0-xxx.clar");
pub const SIGNERS_DB_1_BODY: &'static str = std::include_str!("signers-1-xxx.clar");
const SIGNERS_VOTING_BODY: &'static str = std::include_str!("signers-voting.clar");
pub const SIGNERS_VOTING_BODY: &'static str = std::include_str!("signers-voting.clar");
pub const COSTS_1_NAME: &'static str = "costs";
pub const COSTS_2_NAME: &'static str = "costs-2";
@@ -120,7 +120,6 @@ lazy_static! {
pub static ref POX_3_TESTNET_CODE: String =
format!("{}\n{}", BOOT_CODE_POX_TESTNET_CONSTS, POX_3_BODY);
pub static ref POX_4_CODE: String = format!("{}", POX_4_BODY);
pub static ref SIGNER_VOTING_CODE: String = format!("{}", SIGNERS_VOTING_BODY);
pub static ref BOOT_CODE_COST_VOTING_TESTNET: String = make_testnet_cost_voting();
pub static ref STACKS_BOOT_CODE_MAINNET: [(&'static str, &'static str); 6] = [
("pox", &BOOT_CODE_POX_MAINNET),

View File

@@ -50,7 +50,7 @@ use crate::chainstate::stacks::boot::{
BOOT_TEST_POX_4_AGG_KEY_CONTRACT, BOOT_TEST_POX_4_AGG_KEY_FNAME, COSTS_2_NAME, COSTS_3_NAME,
MINERS_NAME, POX_2_MAINNET_CODE, POX_2_NAME, POX_2_TESTNET_CODE, POX_3_MAINNET_CODE,
POX_3_NAME, POX_3_TESTNET_CODE, POX_4_CODE, POX_4_NAME, SIGNERS_BODY, SIGNERS_DB_0_BODY,
SIGNERS_DB_1_BODY, SIGNERS_NAME, SIGNERS_VOTING_NAME, SIGNER_VOTING_CODE,
SIGNERS_DB_1_BODY, SIGNERS_NAME, SIGNERS_VOTING_BODY, SIGNERS_VOTING_NAME,
};
use crate::chainstate::stacks::db::{StacksAccount, StacksChainState};
use crate::chainstate::stacks::events::{StacksTransactionEvent, StacksTransactionReceipt};
@@ -1457,59 +1457,12 @@ impl<'a, 'b> ClarityBlockConnection<'a, 'b> {
}
}
let initialized_agg_key = if !mainnet {
let agg_key_value_opt = self
.with_readonly_clarity_env(
false,
self.chain_id,
ClarityVersion::Clarity2,
StacksAddress::burn_address(false).into(),
None,
LimitedCostTracker::Free,
|vm_env| {
vm_env.execute_contract_allow_private(
&boot_code_id(BOOT_TEST_POX_4_AGG_KEY_CONTRACT, false),
BOOT_TEST_POX_4_AGG_KEY_FNAME,
&[],
true,
)
},
)
.map(|agg_key_value| {
agg_key_value
.expect_buff(33)
.expect("FATAL: test aggregate pub key must be a buffer")
})
.ok();
agg_key_value_opt
} else {
None
};
let mut signers_voting_code = SIGNER_VOTING_CODE.clone();
if !mainnet {
if let Some(ref agg_pub_key) = initialized_agg_key {
let hex_agg_pub_key = to_hex(agg_pub_key);
for set_in_reward_cycle in 0..pox_4_first_cycle {
info!(
"Setting initial aggregate-public-key in PoX-4";
"agg_pub_key" => &hex_agg_pub_key,
"reward_cycle" => set_in_reward_cycle,
"pox_4_first_cycle" => pox_4_first_cycle,
);
let set_str = format!("(map-set aggregate-public-keys u{set_in_reward_cycle} 0x{hex_agg_pub_key})");
signers_voting_code.push_str("\n");
signers_voting_code.push_str(&set_str);
}
}
}
let signers_voting_contract_id = boot_code_id(SIGNERS_VOTING_NAME, mainnet);
let payload = TransactionPayload::SmartContract(
TransactionSmartContract {
name: ContractName::try_from(SIGNERS_VOTING_NAME)
.expect("FATAL: invalid boot-code contract name"),
code_body: StacksString::from_str(&signers_voting_code)
code_body: StacksString::from_str(SIGNERS_VOTING_BODY)
.expect("FATAL: invalid boot code body"),
},
Some(ClarityVersion::Clarity2),

View File

@@ -12,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;
@@ -33,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;
@@ -294,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:
@@ -524,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 {
@@ -663,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);
}
}
@@ -895,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> {
@@ -929,7 +810,6 @@ impl Config {
"krypton",
"xenon",
"mainnet",
"mockamoto",
"nakamoto-neon",
];
@@ -1368,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}"),
@@ -1599,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)]
@@ -1882,7 +1759,6 @@ impl Default for NodeConfig {
fault_injection_hide_blocks: false,
chain_liveness_poll_time_secs: 300,
stacker_dbs: vec![],
mockamoto_time_ms: 3_000,
}
}
}
@@ -2052,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
@@ -2100,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,
@@ -2322,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 {
@@ -2400,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)
}
@@ -2486,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)

View File

@@ -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;
@@ -55,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;
@@ -322,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();
@@ -449,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

View File

@@ -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");
}

View File

@@ -31,7 +31,6 @@ 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, NakamotoBlockVote, NakamotoChainState};
use stacks::chainstate::stacks::boot::MINERS_NAME;
use stacks::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo};
@@ -219,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();
@@ -543,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() {

View File

@@ -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,7 +25,7 @@ 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;
@@ -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,
@@ -489,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 {
debug!("Neon node booting with no aggregate public key. Must have signers available to sign blocks.");
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,

View File

@@ -13,21 +13,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::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;
@@ -43,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::{
@@ -58,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};
@@ -157,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,
@@ -174,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();
@@ -192,7 +330,6 @@ 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;
@@ -358,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());
@@ -442,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,
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),
@@ -480,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,
@@ -520,63 +688,62 @@ 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,
SIGNERS_VOTING_FUNCTION_NAME,
&[
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);
}
}
}
@@ -587,7 +754,7 @@ fn signer_vote_if_needed(
/// * `signer_pks` - must be the same size as `stacker_sks`
pub fn boot_to_epoch_3_reward_set(
naka_conf: &Config,
blocks_processed: &RunLoopCounter,
blocks_processed: &Arc<AtomicU64>,
stacker_sks: &[StacksPrivateKey],
signer_sks: &[StacksPrivateKey],
btc_regtest_controller: &mut BitcoinRegtestController,
@@ -695,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());
@@ -737,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();
@@ -749,6 +918,7 @@ fn simple_neon_integration() {
&blocks_processed,
&[stacker_sk],
&[sender_signer_sk],
Some(&signers),
&mut btc_regtest_controller,
);
@@ -786,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);
@@ -810,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
@@ -847,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
@@ -919,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);
@@ -963,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();
@@ -978,6 +1162,7 @@ fn mine_multiple_per_tenure_integration() {
&blocks_processed,
&[stacker_sk],
&[sender_signer_sk],
Some(&signers),
&mut btc_regtest_controller,
);
@@ -1000,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);
@@ -1096,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);
@@ -1152,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();
@@ -1283,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,
@@ -1293,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();
@@ -1354,7 +1549,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
@@ -1402,6 +1602,7 @@ 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());
@@ -1435,6 +1636,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();
@@ -1447,10 +1649,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();
@@ -1498,9 +1702,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)
@@ -1529,19 +1730,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
};
@@ -1600,15 +1795,15 @@ fn block_proposal_api_endpoint() {
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
})(),
@@ -1620,7 +1815,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)),
@@ -1628,19 +1823,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.clone()),
HTTP_NOT_AUTHORIZED,
None,
),
("Not authorized", sign(&proposal), HTTP_NOT_AUTHORIZED, None),
];
// Build HTTP client
@@ -1773,6 +1963,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();
@@ -1813,6 +2004,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();
@@ -1825,10 +2017,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);
@@ -1860,21 +2054,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(&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();

View File

@@ -104,7 +104,6 @@ impl SignerTest {
.collect::<Vec<StacksPrivateKey>>();
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
naka_conf.miner.self_signing_key = None;
// So the combination is... one, two, three, four, five? That's the stupidest combination I've ever heard in my life!
// That's the kind of thing an idiot would have on his luggage!
let password = "12345";
@@ -891,9 +890,9 @@ fn setup_stx_btc_node(
btc_regtest_controller,
run_loop_thread,
run_loop_stopper,
vrfs_submitted,
commits_submitted,
blocks_processed,
vrfs_submitted: vrfs_submitted.0,
commits_submitted: commits_submitted.0,
blocks_processed: blocks_processed.0,
coord_channel,
conf: naka_conf,
}