From 83ac3276dc987dd0e8511eb0cc2756d72cebb5ab Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Thu, 22 Feb 2024 15:57:31 -0600 Subject: [PATCH 1/6] chore: remove self_signing mode * remove self_signing mode from nakamoto-node * update integration tests that used self_signing to have a blind signing thread which reads/writes through stackerdb --- libsigner/src/session.rs | 34 +- testnet/stacks-node/src/config.rs | 17 - .../stacks-node/src/nakamoto_node/miner.rs | 68 +-- testnet/stacks-node/src/run_loop/neon.rs | 89 ++-- .../src/tests/nakamoto_integrations.rs | 395 +++++++++++++----- testnet/stacks-node/src/tests/signer.rs | 7 +- 6 files changed, 376 insertions(+), 234 deletions(-) diff --git a/libsigner/src/session.rs b/libsigner/src/session.rs index e5dbd67f35..4d7e40bf71 100644 --- a/libsigner/src/session.rs +++ b/libsigner/src/session.rs @@ -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>, 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>, 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(&mut self, slot_id: u32) -> Result, 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() } } diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 7e2751d7a8..d33ecf0c17 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -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; @@ -505,19 +504,6 @@ lazy_static! { } impl Config { - #[cfg(any(test, feature = "testing"))] - pub fn self_signing(&self) -> Option { - 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 { - 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 { @@ -1998,7 +1984,6 @@ pub struct MinerConfig { pub candidate_retry_cache_size: u64, pub unprocessed_block_deadline_secs: u64, pub mining_key: Option, - pub self_signing_key: Option, /// 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 @@ -2046,7 +2031,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, @@ -2430,7 +2414,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) diff --git a/testnet/stacks-node/src/nakamoto_node/miner.rs b/testnet/stacks-node/src/nakamoto_node/miner.rs index ec57fc3ef7..d840e7f7a3 100644 --- a/testnet/stacks-node/src/nakamoto_node/miner.rs +++ b/testnet/stacks-node/src/nakamoto_node/miner.rs @@ -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}; @@ -223,21 +222,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(); @@ -548,54 +540,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 { if epoch_id < StacksEpochId::Epoch21 && self.config.miner.block_reward_recipient.is_some() { diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index 3f5c04f4c2..86235ec3bd 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -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; +#[derive(Clone)] +pub struct RunLoopCounter(pub Arc); #[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; + + 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, @@ -481,23 +481,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; - 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, diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index e97aefd42a..3fd999a603 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -13,20 +13,23 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . +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 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; @@ -40,6 +43,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 +59,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 +158,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 { + 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::>() + .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 +201,120 @@ 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, +) -> Result { + 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 rpc_sock = conf + .node + .rpc_bind + .clone() + .parse() + .expect("Failed to parse socket"); + + let mut proposed_block: NakamotoBlock = { + let miner_contract_id = boot_code_id(MINERS_NAME, false); + let mut miners_stackerdb = StackerDBSession::new(rpc_sock, 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(rpc_sock, 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,7 +333,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; @@ -355,9 +498,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, stacker_sks: &[StacksPrivateKey], signer_sks: &[StacksPrivateKey], + self_signing: Option<&TestSigners>, btc_regtest_controller: &mut BitcoinRegtestController, ) { assert_eq!(stacker_sks.len(), signer_sks.len()); @@ -439,25 +583,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", &[ - 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), @@ -477,6 +625,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 { + 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, @@ -517,63 +691,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 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); - } + // 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), + ], + ); + submit_tx(&http_origin, &voting_tx); } } } @@ -584,7 +757,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, stacker_sks: &[StacksPrivateKey], signer_sks: &[StacksPrivateKey], btc_regtest_controller: &mut BitcoinRegtestController, @@ -692,6 +865,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()); @@ -734,6 +908,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(); @@ -746,6 +921,7 @@ fn simple_neon_integration() { &blocks_processed, &[stacker_sk], &[sender_signer_sk], + Some(&signers), &mut btc_regtest_controller, ); @@ -783,6 +959,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); @@ -807,7 +985,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 @@ -844,7 +1027,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 @@ -916,6 +1104,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); @@ -960,6 +1149,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(); @@ -975,6 +1165,7 @@ fn mine_multiple_per_tenure_integration() { &blocks_processed, &[stacker_sk], &[sender_signer_sk], + Some(&signers), &mut btc_regtest_controller, ); @@ -997,6 +1188,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); @@ -1093,6 +1286,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); @@ -1149,6 +1343,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(); @@ -1280,7 +1475,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, @@ -1290,6 +1490,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(); @@ -1351,7 +1552,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 @@ -1399,6 +1605,7 @@ fn block_proposal_api_endpoint() { return; } + let signers = TestSigners::default(); let (mut conf, _miner_account) = naka_neon_integration_conf(None); let account_keys = add_initial_balances(&mut conf, 10, 1_000_000); let stacker_sk = setup_stacker(&mut conf); @@ -1430,6 +1637,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(); @@ -1442,10 +1650,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(); @@ -1493,9 +1703,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) @@ -1524,19 +1731,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 }; @@ -1594,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 })(), @@ -1614,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)), @@ -1622,7 +1823,7 @@ 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 })(), @@ -1746,6 +1947,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(); @@ -1786,6 +1988,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(); @@ -1798,10 +2001,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); @@ -1840,20 +2045,14 @@ 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); 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(); diff --git a/testnet/stacks-node/src/tests/signer.rs b/testnet/stacks-node/src/tests/signer.rs index d903f58c43..f16b4347d6 100644 --- a/testnet/stacks-node/src/tests/signer.rs +++ b/testnet/stacks-node/src/tests/signer.rs @@ -92,7 +92,6 @@ impl SignerTest { .collect::>(); let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); - naka_conf.miner.self_signing_key = None; // Setup the signer and coordinator configurations let signer_configs = build_signer_config_tomls( @@ -715,9 +714,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, } From a0174b8de1ade41a12d977a441265217168a463c Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Fri, 23 Feb 2024 13:44:08 -0600 Subject: [PATCH 2/6] chore: remove mockamoto mode --- testnet/stacks-node/src/config.rs | 122 +-- testnet/stacks-node/src/main.rs | 9 - testnet/stacks-node/src/mockamoto.rs | 1111 -------------------- testnet/stacks-node/src/mockamoto/tests.rs | 414 -------- 4 files changed, 3 insertions(+), 1653 deletions(-) delete mode 100644 testnet/stacks-node/src/mockamoto.rs delete mode 100644 testnet/stacks-node/src/mockamoto/tests.rs diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index d33ecf0c17..cc39fb1e52 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -32,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; @@ -274,102 +273,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: @@ -630,7 +533,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); } } @@ -862,15 +765,7 @@ impl Config { } pub fn from_config_file(config_file: ConfigFile) -> Result { - 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 { @@ -896,7 +791,6 @@ impl Config { "krypton", "xenon", "mainnet", - "mockamoto", "nakamoto-neon", ]; @@ -1335,7 +1229,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}"), @@ -1566,9 +1460,6 @@ pub struct NodeConfig { pub chain_liveness_poll_time_secs: u64, /// stacker DBs we replicate pub stacker_dbs: Vec, - /// if running in mockamoto mode, how long to wait between each - /// simulated bitcoin block - pub mockamoto_time_ms: u64, } #[derive(Clone, Debug)] @@ -1849,7 +1740,6 @@ impl Default for NodeConfig { fault_injection_hide_blocks: false, chain_liveness_poll_time_secs: 300, stacker_dbs: vec![], - mockamoto_time_ms: 3_000, } } } @@ -2250,9 +2140,6 @@ pub struct NodeConfigFile { pub chain_liveness_poll_time_secs: Option, /// Stacker DBs we replicate pub stacker_dbs: Option>, - /// if running in mockamoto mode, how long to wait between each - /// simulated bitcoin block - pub mockamoto_time_ms: Option, } impl NodeConfigFile { @@ -2328,9 +2215,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) } diff --git a/testnet/stacks-node/src/main.rs b/testnet/stacks-node/src/main.rs index 95a1dda4b8..bf54c1601d 100644 --- a/testnet/stacks-node/src/main.rs +++ b/testnet/stacks-node/src/main.rs @@ -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); diff --git a/testnet/stacks-node/src/mockamoto.rs b/testnet/stacks-node/src/mockamoto.rs deleted file mode 100644 index 2629f4f9b2..0000000000 --- a/testnet/stacks-node/src/mockamoto.rs +++ /dev/null @@ -1,1111 +0,0 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020-2023 Stacks Open Internet Foundation -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -use std::sync::atomic::AtomicBool; -use std::sync::mpsc::{sync_channel, Receiver, RecvTimeoutError}; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::thread::{sleep, JoinHandle}; -use std::time::Duration; - -use clarity::vm::ast::ASTRules; -use clarity::vm::Value as ClarityValue; -use lazy_static::lazy_static; -use stacks::burnchains::bitcoin::address::{ - BitcoinAddress, LegacyBitcoinAddress, LegacyBitcoinAddressType, -}; -use stacks::burnchains::bitcoin::{ - BitcoinBlock, BitcoinInputType, BitcoinNetworkType, BitcoinTransaction, - BitcoinTxInputStructured, BitcoinTxOutput, -}; -use stacks::burnchains::db::{BurnchainDB, BurnchainHeaderReader}; -use stacks::burnchains::{ - BurnchainBlock, BurnchainBlockHeader, BurnchainSigner, Error as BurnchainError, Txid, -}; -use stacks::chainstate::burn::db::sortdb::SortitionDB; -use stacks::chainstate::burn::operations::leader_block_commit::BURN_BLOCK_MINED_AT_MODULUS; -use stacks::chainstate::burn::operations::{ - BlockstackOperationType, LeaderBlockCommitOp, LeaderKeyRegisterOp, -}; -use stacks::chainstate::burn::BlockSnapshot; -use stacks::chainstate::coordinator::comm::CoordinatorReceivers; -use stacks::chainstate::coordinator::{ - ChainsCoordinator, ChainsCoordinatorConfig, CoordinatorCommunication, -}; -use stacks::chainstate::nakamoto::test_signers::TestSigners; -use stacks::chainstate::nakamoto::{ - NakamotoBlock, NakamotoBlockHeader, NakamotoChainState, SetupBlockResult, -}; -use stacks::chainstate::stacks::address::PoxAddress; -use stacks::chainstate::stacks::boot::SIGNERS_VOTING_NAME; -use stacks::chainstate::stacks::db::{ChainStateBootData, ClarityTx, StacksChainState}; -use stacks::chainstate::stacks::miner::{ - BlockBuilder, BlockBuilderSettings, BlockLimitFunction, MinerStatus, TransactionResult, -}; -use stacks::chainstate::stacks::{ - CoinbasePayload, Error as ChainstateError, StacksBlockBuilder, StacksTransaction, - StacksTransactionSigner, TenureChangeCause, TenureChangePayload, ThresholdSignature, - TransactionAuth, TransactionContractCall, TransactionPayload, TransactionVersion, - MAX_EPOCH_SIZE, MINER_BLOCK_CONSENSUS_HASH, MINER_BLOCK_HEADER_HASH, -}; -use stacks::core::mempool::MemPoolWalkSettings; -use stacks::core::{ - MemPoolDB, StacksEpoch, BLOCK_LIMIT_MAINNET_10, HELIUM_BLOCK_LIMIT_20, PEER_VERSION_EPOCH_1_0, - PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, 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, STACKS_EPOCH_3_0_MARKER, TX_BLOCK_LIMIT_PROPORTION_HEURISTIC, -}; -use stacks::net::atlas::{AtlasConfig, AtlasDB}; -use stacks::net::relay::Relayer; -use stacks::net::stackerdb::StackerDBs; -use stacks::util_lib::boot::boot_code_addr; -use stacks::util_lib::db::Error as DBError; -use stacks::util_lib::signed_structured_data::pox4::{ - make_pox_4_signer_key_signature, Pox4SignatureTopic, -}; -use stacks_common::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG}; -use stacks_common::bitvec::BitVec; -use stacks_common::codec::StacksMessageCodec; -use stacks_common::consts::{ - CHAIN_ID_TESTNET, FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, STACKS_EPOCH_MAX, -}; -use stacks_common::types::chainstate::{ - BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, StacksAddress, StacksBlockId, - StacksPrivateKey, VRFSeed, -}; -use stacks_common::types::{PrivateKey, StacksEpochId}; -use stacks_common::util::hash::{to_hex, Hash160, MerkleTree, Sha512Trunc256Sum}; -use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey}; -use stacks_common::util::vrf::{VRFPrivateKey, VRFProof, VRFPublicKey, VRF}; - -use crate::globals::{NeonGlobals as Globals, RelayerDirective}; -use crate::neon::Counters; -use crate::neon_node::{PeerThread, StacksNode, BLOCK_PROCESSOR_STACK_SIZE}; -use crate::syncctl::PoxSyncWatchdogComms; -use crate::{Config, EventDispatcher}; - -#[cfg(test)] -mod tests; - -lazy_static! { - pub static ref STACKS_EPOCHS_MOCKAMOTO: [StacksEpoch; 9] = [ - StacksEpoch { - epoch_id: StacksEpochId::Epoch10, - start_height: 0, - end_height: 0, - block_limit: BLOCK_LIMIT_MAINNET_10.clone(), - network_epoch: PEER_VERSION_EPOCH_1_0 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch20, - start_height: 0, - end_height: 1, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_2_0 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch2_05, - start_height: 1, - end_height: 2, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_2_05 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch21, - start_height: 2, - end_height: 3, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_2_1 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch22, - start_height: 3, - end_height: 4, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_2_2 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch23, - start_height: 4, - end_height: 5, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_2_3 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch24, - start_height: 5, - end_height: 6, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_2_4 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch25, - start_height: 6, - end_height: 7, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_2_5 - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch30, - start_height: 7, - end_height: STACKS_EPOCH_MAX, - block_limit: HELIUM_BLOCK_LIMIT_20.clone(), - network_epoch: PEER_VERSION_EPOCH_3_0 - }, - ]; -} - -/// Produce a mock bitcoin block that is descended from `parent_snapshot` and includes -/// `ops`. This method uses `miner_pkh` to set the inputs and outputs of any supplied -/// block commits or leader key registrations -fn make_burn_block( - parent_snapshot: &BlockSnapshot, - miner_pkh: &Hash160, - ops: Vec, -) -> Result { - let block_height = parent_snapshot.block_height + 1; - let mut mock_burn_hash_contents = [0u8; 32]; - mock_burn_hash_contents[0..8].copy_from_slice((block_height + 1).to_be_bytes().as_ref()); - - let txs = ops.into_iter().map(|op| { - let mut data = match &op { - BlockstackOperationType::LeaderKeyRegister(op) => op.serialize_to_vec(), - BlockstackOperationType::LeaderBlockCommit(op) => op.serialize_to_vec(), - _ => panic!("Attempted to mock unexpected blockstack operation."), - }; - - data.remove(0); - - let (inputs, outputs) = if let BlockstackOperationType::LeaderBlockCommit(ref op) = op { - let burn_output = BitcoinTxOutput { - units: op.burn_fee, - address: BitcoinAddress::Legacy(LegacyBitcoinAddress { - addrtype: LegacyBitcoinAddressType::PublicKeyHash, - network_id: BitcoinNetworkType::Testnet, - bytes: Hash160([0; 20]), - }), - }; - - let change_output = BitcoinTxOutput { - units: 1_000_000_000_000, - address: BitcoinAddress::Legacy(LegacyBitcoinAddress { - addrtype: LegacyBitcoinAddressType::PublicKeyHash, - network_id: BitcoinNetworkType::Testnet, - bytes: miner_pkh.clone(), - }), - }; - - let tx_ref = (parent_snapshot.winning_block_txid.clone(), 3); - - let input = BitcoinTxInputStructured { - keys: vec![], - num_required: 0, - in_type: BitcoinInputType::Standard, - tx_ref, - }; - - ( - vec![input.into()], - vec![burn_output.clone(), burn_output, change_output], - ) - } else { - ( - vec![BitcoinTxInputStructured { - keys: vec![], - num_required: 0, - in_type: BitcoinInputType::Standard, - tx_ref: (Txid([0; 32]), 0), - } - .into()], - vec![BitcoinTxOutput { - units: 1_000_000_000_000, - address: BitcoinAddress::Legacy(LegacyBitcoinAddress { - addrtype: LegacyBitcoinAddressType::PublicKeyHash, - network_id: BitcoinNetworkType::Testnet, - bytes: miner_pkh.clone(), - }), - }], - ) - }; - - BitcoinTransaction { - txid: op.txid(), - vtxindex: op.vtxindex(), - opcode: op.opcode() as u8, - data, - data_amt: 0, - inputs, - outputs, - } - }); - - Ok(BitcoinBlock { - block_height, - block_hash: BurnchainHeaderHash(mock_burn_hash_contents), - parent_block_hash: parent_snapshot.burn_header_hash.clone(), - txs: txs.collect(), - timestamp: 100 * u64::from(block_height + 1), - }) -} - -/// This struct wraps all the state required for operating a -/// stacks-node in `mockamoto` mode. -/// -/// This mode of operation is a single-node network in which bitcoin -/// blocks are simulated: no `bitcoind` is communicated with (either -/// operating as regtest, testnet or mainnet). This operation mode -/// is useful for testing the stacks-only operation of Nakamoto. -/// -/// During operation, the mockamoto node issues `stack-stx` and -/// `stack-extend` contract-calls to ensure that the miner is a member -/// of the current stacking set. This ensures nakamoto blocks can be -/// produced with tenure change txs. -/// -pub struct MockamotoNode { - sortdb: SortitionDB, - mempool: MemPoolDB, - chainstate: StacksChainState, - self_signer: TestSigners, - miner_key: StacksPrivateKey, - vrf_key: VRFPrivateKey, - relay_rcv: Option>, - coord_rcv: Option, - dispatcher: EventDispatcher, - pub globals: Globals, - config: Config, -} - -struct MockamotoBlockBuilder { - txs: Vec, - bytes_so_far: u64, -} - -/// This struct is used by mockamoto to pass the burnchain indexer -/// parameter to the `ChainsCoordinator`. It errors on every -/// invocation except `read_burnchain_headers`. -/// -/// The `ChainsCoordinator` only uses this indexer for evaluating -/// affirmation maps, which should never be evaluated in mockamoto. -/// This is passed to the Burnchain DB block processor, though, which -/// requires `read_burnchain_headers` (to generate affirmation maps) -struct MockBurnchainIndexer(BurnchainDB); - -impl BurnchainHeaderReader for MockBurnchainIndexer { - fn read_burnchain_headers( - &self, - start_height: u64, - end_height: u64, - ) -> Result, DBError> { - let mut output = vec![]; - for i in start_height..end_height { - let header = BurnchainDB::get_burnchain_header(self.0.conn(), i) - .map_err(|e| DBError::Other(e.to_string()))? - .ok_or_else(|| DBError::NotFoundError)?; - output.push(header); - } - Ok(output) - } - fn get_burnchain_headers_height(&self) -> Result { - Err(DBError::NoDBError) - } - fn find_burnchain_header_height( - &self, - _header_hash: &BurnchainHeaderHash, - ) -> Result, DBError> { - Err(DBError::NoDBError) - } -} - -impl BlockBuilder for MockamotoBlockBuilder { - fn try_mine_tx_with_len( - &mut self, - clarity_tx: &mut ClarityTx, - tx: &StacksTransaction, - tx_len: u64, - limit_behavior: &BlockLimitFunction, - ast_rules: ASTRules, - ) -> TransactionResult { - if self.bytes_so_far + tx_len >= MAX_EPOCH_SIZE.into() { - return TransactionResult::skipped(tx, "BlockSizeLimit".into()); - } - - if BlockLimitFunction::NO_LIMIT_HIT != *limit_behavior { - return TransactionResult::skipped(tx, "LimitReached".into()); - } - - let (fee, receipt) = match StacksChainState::process_transaction( - clarity_tx, tx, true, ast_rules, - ) { - Ok(x) => x, - Err(ChainstateError::CostOverflowError(cost_before, cost_after, total_budget)) => { - clarity_tx.reset_cost(cost_before.clone()); - if total_budget.proportion_largest_dimension(&cost_before) - < TX_BLOCK_LIMIT_PROPORTION_HEURISTIC - { - warn!( - "Transaction {} consumed over {}% of block budget, marking as invalid; budget was {}", - tx.txid(), - 100 - TX_BLOCK_LIMIT_PROPORTION_HEURISTIC, - &total_budget - ); - return TransactionResult::error(&tx, ChainstateError::TransactionTooBigError); - } else { - warn!( - "Transaction {} reached block cost {}; budget was {}", - tx.txid(), - &cost_after, - &total_budget - ); - return TransactionResult::skipped_due_to_error( - &tx, - ChainstateError::BlockTooBigError, - ); - } - } - Err(e) => return TransactionResult::error(&tx, e), - }; - - info!("Include tx"; - "tx" => %tx.txid(), - "payload" => tx.payload.name(), - "origin" => %tx.origin_address()); - - self.txs.push(tx.clone()); - self.bytes_so_far += tx_len; - - TransactionResult::success(tx, fee, receipt) - } -} - -impl MockamotoNode { - pub fn new(config: &Config) -> Result { - let miner_key = config - .miner - .mining_key - .clone() - .ok_or("Mockamoto node must be configured with `miner.mining_key`")?; - let vrf_key = VRFPrivateKey::new(); - - let stacker_pk = Secp256k1PublicKey::from_private(&miner_key); - let stacker_pk_hash = Hash160::from_node_public_key(&stacker_pk); - - let stacker = StacksAddress { - version: C32_ADDRESS_VERSION_TESTNET_SINGLESIG, - bytes: stacker_pk_hash, - }; - - let burnchain = config.get_burnchain(); - let (sortdb, _burndb) = burnchain - .connect_db( - true, - BurnchainHeaderHash([0; 32]), - 100, - STACKS_EPOCHS_MOCKAMOTO.to_vec(), - ) - .map_err(|e| e.to_string())?; - - let mut initial_balances: Vec<_> = config - .initial_balances - .iter() - .map(|balance| (balance.address.clone(), balance.amount)) - .collect(); - - initial_balances.push((stacker.into(), 100_000_000_000_000)); - - // Create a boot contract to initialize the aggregate public key prior to Pox-4 activation - let self_signer = TestSigners::default(); - let agg_pub_key = self_signer.aggregate_public_key.clone(); - info!("Mockamoto node setting agg public key"; "agg_pub_key" => %to_hex(&self_signer.aggregate_public_key.compress().data)); - let callback = move |clarity_tx: &mut ClarityTx| { - NakamotoChainState::aggregate_public_key_bootcode(clarity_tx, &agg_pub_key); - }; - let mut boot_data = - ChainStateBootData::new(&burnchain, initial_balances, Some(Box::new(callback))); - let (chainstate, boot_receipts) = StacksChainState::open_and_exec( - config.is_mainnet(), - config.burnchain.chain_id, - &config.get_chainstate_path_str(), - Some(&mut boot_data), - Some(config.node.get_marf_opts()), - ) - .unwrap(); - let mempool = PeerThread::connect_mempool_db(config); - - let (coord_rcv, coord_comms) = CoordinatorCommunication::instantiate(); - let miner_status = Arc::new(Mutex::new(MinerStatus::make_ready(100))); - let (relay_send, relay_rcv) = sync_channel(10); - let counters = Counters::new(); - let should_keep_running = Arc::new(AtomicBool::new(true)); - let sync_comms = PoxSyncWatchdogComms::new(should_keep_running.clone()); - - let globals = Globals::new( - coord_comms, - miner_status, - relay_send, - counters, - sync_comms, - should_keep_running, - 0, - ); - - let mut event_dispatcher = EventDispatcher::new(); - for observer in config.events_observers.iter() { - event_dispatcher.register_observer(observer); - } - - crate::run_loop::announce_boot_receipts( - &mut event_dispatcher, - &chainstate, - &burnchain.pox_constants, - &boot_receipts, - ); - - Ok(MockamotoNode { - sortdb, - self_signer, - chainstate, - miner_key, - vrf_key, - relay_rcv: Some(relay_rcv), - coord_rcv: Some(coord_rcv), - dispatcher: event_dispatcher, - mempool, - globals, - config: config.clone(), - }) - } - - fn spawn_chains_coordinator(&mut self) -> JoinHandle<()> { - let config = self.config.clone(); - let atlas_config = AtlasConfig::new(false); - - let (chainstate, _) = self.chainstate.reopen().unwrap(); - let coord_config = ChainsCoordinatorConfig { - always_use_affirmation_maps: false, - require_affirmed_anchor_blocks: false, - ..ChainsCoordinatorConfig::new() - }; - let mut dispatcher = self.dispatcher.clone(); - let burnchain = self.config.get_burnchain(); - let burndb = burnchain.open_burnchain_db(true).unwrap(); - let coordinator_indexer = MockBurnchainIndexer(burndb); - let atlas_db = AtlasDB::connect( - atlas_config.clone(), - &self.config.get_atlas_db_file_path(), - true, - ) - .unwrap(); - let miner_status = Arc::new(Mutex::new(MinerStatus::make_ready(100))); - let coordinator_receivers = self.coord_rcv.take().unwrap(); - - thread::Builder::new() - .name(format!("chains-coordinator-{}", &config.node.rpc_bind)) - .stack_size(BLOCK_PROCESSOR_STACK_SIZE) - .spawn(move || { - debug!( - "chains-coordinator thread ID is {:?}", - thread::current().id() - ); - ChainsCoordinator::run( - coord_config, - chainstate, - burnchain, - &mut dispatcher, - coordinator_receivers, - atlas_config, - Some(&mut ()), - Some(&mut ()), - miner_status, - coordinator_indexer, - atlas_db, - ); - }) - .expect("FATAL: failed to start chains coordinator thread") - } - - pub fn run(&mut self) { - info!("Starting the mockamoto node by issuing initial empty mock burn blocks"); - let coordinator = self.spawn_chains_coordinator(); - - self.produce_burnchain_block(true).unwrap(); - self.produce_burnchain_block(true).unwrap(); - self.produce_burnchain_block(true).unwrap(); - self.produce_burnchain_block(true).unwrap(); - self.produce_burnchain_block(true).unwrap(); - self.produce_burnchain_block(true).unwrap(); - - let mut p2p_net = StacksNode::setup_peer_network( - &self.config, - &self.config.atlas, - self.config.get_burnchain(), - ); - - let stackerdbs = StackerDBs::connect(&self.config.get_stacker_db_file_path(), true) - .expect("FATAL: failed to connect to stacker DB"); - - let _relayer = Relayer::from_p2p(&mut p2p_net, stackerdbs); - - let relayer_rcv = self.relay_rcv.take().unwrap(); - let relayer_globals = self.globals.clone(); - let mock_relayer_thread = thread::Builder::new() - .name("mock-relayer".into()) - .spawn(move || { - while relayer_globals.keep_running() { - match relayer_rcv.recv_timeout(Duration::from_millis(500)) { - Ok(dir) => { - if let RelayerDirective::Exit = dir { - break; - } - } - Err(RecvTimeoutError::Timeout) => continue, - Err(e) => { - warn!("Error accepting relayer directive: {e:?}"); - break; - } - } - } - }) - .expect("FATAL: failed to start mock relayer thread"); - - let peer_thread = PeerThread::new_all( - self.globals.clone(), - &self.config, - self.config.get_burnchain().pox_constants, - p2p_net, - ); - - let ev_dispatcher = self.dispatcher.clone(); - let peer_thread = thread::Builder::new() - .stack_size(BLOCK_PROCESSOR_STACK_SIZE) - .name("p2p".into()) - .spawn(move || { - StacksNode::p2p_main(peer_thread, ev_dispatcher); - }) - .expect("FATAL: failed to start p2p thread"); - - while self.globals.keep_running() { - self.produce_burnchain_block(false).unwrap(); - let expected_chain_length = self.mine_and_stage_block().unwrap(); - self.globals.coord().announce_new_stacks_block(); - let _ = self.wait_for_stacks_block(expected_chain_length); - sleep(Duration::from_millis(self.config.node.mockamoto_time_ms)); - } - - self.globals.coord().stop_chains_coordinator(); - - if let Err(e) = coordinator.join() { - warn!("Error joining coordinator thread during shutdown: {e:?}"); - } - if let Err(e) = mock_relayer_thread.join() { - warn!("Error joining coordinator thread during shutdown: {e:?}"); - } - if let Err(e) = peer_thread.join() { - warn!("Error joining p2p thread during shutdown: {e:?}"); - } - } - - #[cfg_attr(test, mutants::skip)] - fn wait_for_stacks_block(&mut self, expected_length: u64) -> Result<(), ChainstateError> { - while self.globals.keep_running() { - let chain_length = match NakamotoChainState::get_canonical_block_header( - self.chainstate.db(), - &self.sortdb, - ) { - Ok(Some(chain_tip)) => chain_tip.stacks_block_height, - Ok(None) | Err(ChainstateError::NoSuchBlockError) => 0, - Err(e) => return Err(e), - }; - if chain_length >= expected_length { - return Ok(()); - } - sleep(Duration::from_millis(100)); - } - Err(ChainstateError::NoSuchBlockError) - } - - fn produce_burnchain_block(&mut self, initializing: bool) -> Result<(), BurnchainError> { - let miner_pk = Secp256k1PublicKey::from_private(&self.miner_key); - let miner_pk_hash = Hash160::from_node_public_key(&miner_pk); - - let parent_snapshot = SortitionDB::get_canonical_burn_chain_tip(&self.sortdb.conn())?; - info!("Mocking bitcoin block"; "parent_height" => parent_snapshot.block_height); - let burn_height = parent_snapshot.block_height + 1; - - let mut ops = vec![]; - - if burn_height == 1 { - let mut txid = [2u8; 32]; - txid[0..8].copy_from_slice((burn_height + 1).to_be_bytes().as_ref()); - let key_register = LeaderKeyRegisterOp { - consensus_hash: ConsensusHash([0; 20]), - public_key: VRFPublicKey::from_private(&self.vrf_key), - memo: miner_pk_hash.as_bytes().to_vec(), - txid: Txid(txid), - vtxindex: 0, - block_height: burn_height, - burn_header_hash: BurnchainHeaderHash([0; 32]), - }; - ops.push(BlockstackOperationType::LeaderKeyRegister(key_register)); - } else if !initializing { - let mut txid = [1u8; 32]; - txid[0..8].copy_from_slice((burn_height + 1).to_be_bytes().as_ref()); - txid[8..16].copy_from_slice((0u64).to_be_bytes().as_ref()); - - let (parent_block_ptr, parent_vtxindex) = - if parent_snapshot.winning_block_txid.as_bytes() == &[0; 32] { - (0, 0) - } else { - (parent_snapshot.block_height.try_into().unwrap(), 0) - }; - - let parent_vrf_proof = NakamotoChainState::get_block_vrf_proof( - self.chainstate.db(), - &parent_snapshot.consensus_hash, - ) - .map_err(|_e| BurnchainError::MissingParentBlock)? - .unwrap_or_else(|| VRFProof::empty()); - - let vrf_seed = VRFSeed::from_proof(&parent_vrf_proof); - let parent_block_id = parent_snapshot.get_canonical_stacks_block_id(); - - let block_commit = LeaderBlockCommitOp { - block_header_hash: BlockHeaderHash(parent_block_id.0), - new_seed: vrf_seed, - parent_block_ptr, - parent_vtxindex, - key_block_ptr: 1, - key_vtxindex: 0, - memo: vec![STACKS_EPOCH_3_0_MARKER], - burn_fee: 5000, - input: (parent_snapshot.winning_block_txid.clone(), 3), - burn_parent_modulus: u8::try_from( - parent_snapshot.block_height % BURN_BLOCK_MINED_AT_MODULUS, - ) - .unwrap(), - apparent_sender: BurnchainSigner(miner_pk_hash.to_string()), - commit_outs: vec![ - PoxAddress::Standard(StacksAddress::burn_address(false), None), - PoxAddress::Standard(StacksAddress::burn_address(false), None), - ], - sunset_burn: 0, - txid: Txid(txid), - vtxindex: 0, - block_height: burn_height, - burn_header_hash: BurnchainHeaderHash([0; 32]), - }; - ops.push(BlockstackOperationType::LeaderBlockCommit(block_commit)) - } - - let new_burn_block = make_burn_block(&parent_snapshot, &miner_pk_hash, ops)?; - - let burnchain = self.config.get_burnchain(); - let burndb = burnchain.open_burnchain_db(true).unwrap(); - let indexer = MockBurnchainIndexer(burndb); - let mut burndb = burnchain.open_burnchain_db(true).unwrap(); - - burndb.store_new_burnchain_block( - &burnchain, - &indexer, - &BurnchainBlock::Bitcoin(new_burn_block), - StacksEpochId::Epoch30, - )?; - - self.globals.coord().announce_new_burn_block(); - let mut cur_snapshot = SortitionDB::get_canonical_burn_chain_tip(&self.sortdb.conn())?; - while cur_snapshot.burn_header_hash == parent_snapshot.burn_header_hash { - thread::sleep(Duration::from_millis(100)); - cur_snapshot = SortitionDB::get_canonical_burn_chain_tip(&self.sortdb.conn())?; - } - - Ok(()) - } - - fn mine_stacks_block(&mut self) -> Result { - let miner_principal = StacksAddress::from_public_keys( - C32_ADDRESS_VERSION_TESTNET_SINGLESIG, - &AddressHashMode::SerializeP2PKH, - 1, - &vec![Secp256k1PublicKey::from_private(&self.miner_key)], - ) - .unwrap() - .into(); - let sortition_tip = SortitionDB::get_canonical_burn_chain_tip(self.sortdb.conn())?; - let chain_id = self.chainstate.chain_id; - let (mut chainstate_tx, clarity_instance) = self.chainstate.chainstate_tx_begin().unwrap(); - - let (is_genesis, chain_tip_bh, chain_tip_ch) = - match NakamotoChainState::get_canonical_block_header(&chainstate_tx, &self.sortdb) { - Ok(Some(chain_tip)) => ( - false, - chain_tip.anchored_header.block_hash(), - chain_tip.consensus_hash, - ), - Ok(None) | Err(ChainstateError::NoSuchBlockError) => - // No stacks tip yet, parent should be genesis - { - ( - true, - FIRST_STACKS_BLOCK_HASH, - FIRST_BURNCHAIN_CONSENSUS_HASH, - ) - } - Err(e) => return Err(e), - }; - - let parent_block_id = StacksBlockId::new(&chain_tip_ch, &chain_tip_bh); - - let (parent_chain_length, parent_burn_height) = if is_genesis { - (0, 0) - } else { - let tip_info = NakamotoChainState::get_block_header(&chainstate_tx, &parent_block_id)? - .ok_or(ChainstateError::NoSuchBlockError)?; - (tip_info.stacks_block_height, tip_info.burn_header_height) - }; - - let miner_nonce = if is_genesis { - 0 - } else { - let sortdb_conn = self.sortdb.index_conn(); - let mut clarity_conn = clarity_instance.read_only_connection_checked( - &parent_block_id, - &chainstate_tx, - &sortdb_conn, - )?; - StacksChainState::get_nonce(&mut clarity_conn, &miner_principal) - }; - - info!( - "Mining block"; "parent_chain_length" => parent_chain_length, "chain_tip_bh" => %chain_tip_bh, - "chain_tip_ch" => %chain_tip_ch, "miner_account" => %miner_principal, "miner_nonce" => %miner_nonce, - ); - - let vrf_proof = VRF::prove(&self.vrf_key, sortition_tip.sortition_hash.as_bytes()); - let coinbase_tx_payload = - TransactionPayload::Coinbase(CoinbasePayload([1; 32]), None, Some(vrf_proof)); - let mut coinbase_tx = StacksTransaction::new( - TransactionVersion::Testnet, - TransactionAuth::from_p2pkh(&self.miner_key).unwrap(), - coinbase_tx_payload, - ); - coinbase_tx.chain_id = chain_id; - coinbase_tx.set_origin_nonce(miner_nonce + 1); - let mut coinbase_tx_signer = StacksTransactionSigner::new(&coinbase_tx); - coinbase_tx_signer.sign_origin(&self.miner_key).unwrap(); - let coinbase_tx = coinbase_tx_signer.get_tx().unwrap(); - - let miner_pk = Secp256k1PublicKey::from_private(&self.miner_key); - let miner_pk_hash = Hash160::from_node_public_key(&miner_pk); - - // Add a tenure change transaction to the block: - // as of now every mockamoto block is a tenure-change. - // If mockamoto mode changes to support non-tenure-changing blocks, this will have - // to be gated. - let tenure_change_tx_payload = TransactionPayload::TenureChange(TenureChangePayload { - tenure_consensus_hash: sortition_tip.consensus_hash.clone(), - prev_tenure_consensus_hash: chain_tip_ch.clone(), - burn_view_consensus_hash: sortition_tip.consensus_hash, - previous_tenure_end: parent_block_id, - previous_tenure_blocks: 1, - cause: TenureChangeCause::BlockFound, - pubkey_hash: miner_pk_hash, - }); - let mut tenure_tx = StacksTransaction::new( - TransactionVersion::Testnet, - TransactionAuth::from_p2pkh(&self.miner_key).unwrap(), - tenure_change_tx_payload, - ); - tenure_tx.chain_id = chain_id; - tenure_tx.set_origin_nonce(miner_nonce); - let mut tenure_tx_signer = StacksTransactionSigner::new(&tenure_tx); - tenure_tx_signer.sign_origin(&self.miner_key).unwrap(); - let tenure_tx = tenure_tx_signer.get_tx().unwrap(); - - let pox_address = PoxAddress::Standard( - StacksAddress::burn_address(false), - Some(AddressHashMode::SerializeP2PKH), - ); - - let signer_sk = Secp256k1PrivateKey::from_seed(&[1, 2, 3, 4]); - let signer_key = Secp256k1PublicKey::from_private(&signer_sk).to_bytes_compressed(); - let signer_addr = StacksAddress::from_public_keys( - C32_ADDRESS_VERSION_TESTNET_SINGLESIG, - &AddressHashMode::SerializeP2PKH, - 1, - &vec![Secp256k1PublicKey::from_private(&signer_sk)], - ) - .unwrap() - .into(); - - let block_height = sortition_tip.block_height; - let reward_cycle = self - .sortdb - .pox_constants - .block_height_to_reward_cycle(self.sortdb.first_block_height, block_height) - .unwrap(); - - let stack_stx_payload = if parent_chain_length < 2 { - let signature = make_pox_4_signer_key_signature( - &pox_address, - &signer_sk, - reward_cycle.into(), - &Pox4SignatureTopic::StackStx, - CHAIN_ID_TESTNET, - 12_u128, - ) - .unwrap() - .to_rsv(); - TransactionPayload::ContractCall(TransactionContractCall { - address: StacksAddress::burn_address(false), - contract_name: "pox-4".try_into().unwrap(), - function_name: "stack-stx".try_into().unwrap(), - function_args: vec![ - ClarityValue::UInt(99_000_000_000_000), - pox_address.as_clarity_tuple().unwrap().into(), - ClarityValue::UInt(u128::from(parent_burn_height)), - ClarityValue::UInt(12), - ClarityValue::some(ClarityValue::buff_from(signature).unwrap()).unwrap(), - ClarityValue::buff_from(signer_key).unwrap(), - ], - }) - } else { - let signature = make_pox_4_signer_key_signature( - &pox_address, - &signer_sk, - reward_cycle.into(), - &Pox4SignatureTopic::StackExtend, - CHAIN_ID_TESTNET, - 5_u128, - ) - .unwrap() - .to_rsv(); - // NOTE: stack-extend doesn't currently work, because the PoX-4 lockup - // special functions have not been implemented. - TransactionPayload::ContractCall(TransactionContractCall { - address: StacksAddress::burn_address(false), - contract_name: "pox-4".try_into().unwrap(), - function_name: "stack-extend".try_into().unwrap(), - function_args: vec![ - ClarityValue::UInt(5), - pox_address.as_clarity_tuple().unwrap().into(), - ClarityValue::some(ClarityValue::buff_from(signature).unwrap()).unwrap(), - ClarityValue::buff_from(signer_key).unwrap(), - ], - }) - }; - let mut stack_stx_tx = StacksTransaction::new( - TransactionVersion::Testnet, - TransactionAuth::from_p2pkh(&self.miner_key).unwrap(), - stack_stx_payload, - ); - stack_stx_tx.chain_id = chain_id; - stack_stx_tx.set_origin_nonce(miner_nonce + 2); - let mut stack_stx_tx_signer = StacksTransactionSigner::new(&stack_stx_tx); - stack_stx_tx_signer.sign_origin(&self.miner_key).unwrap(); - let stacks_stx_tx = stack_stx_tx_signer.get_tx().unwrap(); - - let signer_nonce = if is_genesis { - 0 - } else { - let sortdb_conn = self.sortdb.index_conn(); - let mut clarity_conn = clarity_instance.read_only_connection_checked( - &parent_block_id, - &chainstate_tx, - &sortdb_conn, - )?; - StacksChainState::get_nonce(&mut clarity_conn, &signer_addr) - }; - let mut next_signer = self.self_signer.clone(); - let next_agg_key = next_signer.generate_aggregate_key(reward_cycle + 1); - let aggregate_public_key_val = - ClarityValue::buff_from(next_agg_key.compress().data.to_vec()) - .expect("Failed to serialize aggregate public key"); - let vote_payload = TransactionPayload::new_contract_call( - boot_code_addr(false), - SIGNERS_VOTING_NAME, - "vote-for-aggregate-public-key", - vec![ - ClarityValue::UInt(0), - aggregate_public_key_val, - ClarityValue::UInt(0), - ClarityValue::UInt((reward_cycle + 1).into()), - ], - ) - .unwrap(); - let mut vote_tx = StacksTransaction::new( - TransactionVersion::Testnet, - TransactionAuth::from_p2pkh(&signer_sk).unwrap(), - vote_payload, - ); - vote_tx.chain_id = chain_id; - vote_tx.set_origin_nonce(signer_nonce); - let mut vote_tx_signer = StacksTransactionSigner::new(&vote_tx); - vote_tx_signer.sign_origin(&signer_sk).unwrap(); - let vote_tx = vote_tx_signer.get_tx().unwrap(); - - let sortdb_handle = self.sortdb.index_conn(); - let SetupBlockResult { - mut clarity_tx, - matured_miner_rewards_opt, - .. - } = NakamotoChainState::setup_block( - &mut chainstate_tx, - clarity_instance, - &sortdb_handle, - self.sortdb.first_block_height, - &self.sortdb.pox_constants, - chain_tip_ch.clone(), - chain_tip_bh.clone(), - parent_chain_length, - parent_burn_height, - sortition_tip.burn_header_hash.clone(), - sortition_tip.block_height.try_into().map_err(|_| { - ChainstateError::InvalidStacksBlock("Burn block height exceeded u32".into()) - })?, - true, - parent_chain_length + 1, - false, - )?; - - let txs = vec![tenure_tx, coinbase_tx, stacks_stx_tx, vote_tx]; - - let _ = match StacksChainState::process_block_transactions( - &mut clarity_tx, - &txs, - 0, - ASTRules::PrecheckSize, - ) { - Err(e) => { - let msg = format!("Mined invalid stacks block {e:?}"); - warn!("{msg}"); - - clarity_tx.rollback_block(); - return Err(ChainstateError::InvalidStacksBlock(msg)); - } - Ok((block_fees, _block_burns, txs_receipts)) => (block_fees, txs_receipts), - }; - - let bytes_so_far = txs.iter().map(|tx| tx.tx_len()).sum(); - let mut builder = MockamotoBlockBuilder { txs, bytes_so_far }; - let _ = match StacksBlockBuilder::select_and_apply_transactions( - &mut clarity_tx, - &mut builder, - &mut self.mempool, - parent_chain_length, - &[], - BlockBuilderSettings { - max_miner_time_ms: 15_000, - mempool_settings: MemPoolWalkSettings::default(), - miner_status: Arc::new(Mutex::new(MinerStatus::make_ready(10000))), - }, - None, - ASTRules::PrecheckSize, - ) { - Ok(x) => x, - Err(e) => { - let msg = format!("Mined invalid stacks block {e:?}"); - warn!("{msg}"); - - clarity_tx.rollback_block(); - return Err(ChainstateError::InvalidStacksBlock(msg)); - } - }; - - let _lockup_events = match NakamotoChainState::finish_block( - &mut clarity_tx, - matured_miner_rewards_opt.as_ref(), - ) { - Err(ChainstateError::InvalidStacksBlock(e)) => { - clarity_tx.rollback_block(); - return Err(ChainstateError::InvalidStacksBlock(e)); - } - Err(e) => return Err(e), - Ok(lockup_events) => lockup_events, - }; - - let state_index_root = clarity_tx.seal(); - let tx_merkle_tree: MerkleTree = builder.txs.iter().collect(); - clarity_tx - .commit_mined_block(&StacksBlockId::new( - &MINER_BLOCK_CONSENSUS_HASH, - &MINER_BLOCK_HEADER_HASH, - )) - .unwrap(); - chainstate_tx.commit().unwrap(); - - let mut block = NakamotoBlock { - header: NakamotoBlockHeader { - version: 100, - chain_length: parent_chain_length + 1, - burn_spent: sortition_tip.total_burn, - tx_merkle_root: tx_merkle_tree.root(), - state_index_root, - signer_signature: ThresholdSignature::empty(), - miner_signature: MessageSignature::empty(), - consensus_hash: sortition_tip.consensus_hash.clone(), - parent_block_id: StacksBlockId::new(&chain_tip_ch, &chain_tip_bh), - signer_bitvec: BitVec::zeros(1) - .expect("BUG: bitvec of length-1 failed to construct"), - }, - txs: builder.txs, - }; - - let miner_signature = self - .miner_key - .sign(block.header.miner_signature_hash().as_bytes()) - .unwrap(); - - block.header.miner_signature = miner_signature; - - Ok(block) - } - - #[cfg_attr(test, mutants::skip)] - fn mine_and_stage_block(&mut self) -> Result { - let mut block = self.mine_stacks_block()?; - let config = self.chainstate.config(); - let chain_length = block.header.chain_length; - let mut sortition_handle = self.sortdb.index_handle_at_tip(); - let burn_tip = SortitionDB::get_canonical_burn_chain_tip(&self.sortdb.conn())?; - let cycle = self - .sortdb - .pox_constants - .block_height_to_reward_cycle(self.sortdb.first_block_height, burn_tip.block_height) - .unwrap(); - self.self_signer.sign_nakamoto_block(&mut block, cycle); - - let aggregate_public_key = if chain_length <= 1 { - self.self_signer.aggregate_public_key - } else { - let aggregate_public_key = NakamotoChainState::get_aggregate_public_key( - &mut self.chainstate, - &self.sortdb, - &sortition_handle, - &block, - )?; - aggregate_public_key - }; - let (headers_conn, staging_tx) = self.chainstate.headers_conn_and_staging_tx_begin()?; - NakamotoChainState::accept_block( - &config, - block, - &mut sortition_handle, - &staging_tx, - headers_conn, - &aggregate_public_key, - )?; - staging_tx.commit()?; - Ok(chain_length) - } -} diff --git a/testnet/stacks-node/src/mockamoto/tests.rs b/testnet/stacks-node/src/mockamoto/tests.rs deleted file mode 100644 index cbbed76071..0000000000 --- a/testnet/stacks-node/src/mockamoto/tests.rs +++ /dev/null @@ -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"); -} From b815506502d9efdeba4a44d6990f6a80916b0492 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Fri, 23 Feb 2024 14:07:57 -0600 Subject: [PATCH 3/6] chore: remove genesis-initialization of aggregate pub key --- stackslib/src/chainstate/stacks/boot/mod.rs | 3 +- stackslib/src/clarity_vm/clarity.rs | 51 +-------------------- 2 files changed, 3 insertions(+), 51 deletions(-) diff --git a/stackslib/src/chainstate/stacks/boot/mod.rs b/stackslib/src/chainstate/stacks/boot/mod.rs index abba9be6c7..74927cdf9a 100644 --- a/stackslib/src/chainstate/stacks/boot/mod.rs +++ b/stackslib/src/chainstate/stacks/boot/mod.rs @@ -90,7 +90,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"; @@ -119,7 +119,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), diff --git a/stackslib/src/clarity_vm/clarity.rs b/stackslib/src/clarity_vm/clarity.rs index 81a421cdef..ac764e0e91 100644 --- a/stackslib/src/clarity_vm/clarity.rs +++ b/stackslib/src/clarity_vm/clarity.rs @@ -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), From b04386d4f5950484f1618703ff22b87317b6a138 Mon Sep 17 00:00:00 2001 From: Brice Dobry Date: Thu, 29 Feb 2024 00:06:09 -0500 Subject: [PATCH 4/6] feat: setup `blind-signer` lib and binary This is a thread, for use in testing, that blindly signs blocks and submits signer votes. --- Cargo.lock | 15 + Cargo.toml | 8 +- blind-signer/Cargo.toml | 30 ++ blind-signer/src/lib.rs | 290 ++++++++++++++++++ blind-signer/src/main.rs | 41 +++ testnet/stacks-node/Cargo.toml | 6 + .../burnchains/bitcoin_regtest_controller.rs | 5 +- testnet/stacks-node/src/chain_data.rs | 1 + testnet/stacks-node/src/config.rs | 2 + testnet/stacks-node/src/event_dispatcher.rs | 3 +- testnet/stacks-node/src/globals.rs | 2 +- testnet/stacks-node/src/lib.rs | 9 + testnet/stacks-node/src/main.rs | 6 +- testnet/stacks-node/src/nakamoto_node.rs | 2 +- .../stacks-node/src/nakamoto_node/miner.rs | 5 +- testnet/stacks-node/src/nakamoto_node/peer.rs | 3 +- .../stacks-node/src/nakamoto_node/relayer.rs | 8 +- testnet/stacks-node/src/neon_node.rs | 5 +- testnet/stacks-node/src/node.rs | 3 +- .../stacks-node/src/run_loop/boot_nakamoto.rs | 2 +- testnet/stacks-node/src/run_loop/helium.rs | 5 +- testnet/stacks-node/src/run_loop/nakamoto.rs | 5 +- testnet/stacks-node/src/run_loop/neon.rs | 5 +- testnet/stacks-node/src/syncctl.rs | 2 +- testnet/stacks-node/src/tenure.rs | 3 +- .../stacks-node/src/tests/bitcoin_regtest.rs | 2 +- testnet/stacks-node/src/tests/epoch_205.rs | 3 +- testnet/stacks-node/src/tests/epoch_21.rs | 3 +- testnet/stacks-node/src/tests/epoch_22.rs | 4 +- testnet/stacks-node/src/tests/epoch_23.rs | 3 +- testnet/stacks-node/src/tests/epoch_24.rs | 7 +- testnet/stacks-node/src/tests/integrations.rs | 2 +- .../src/tests/nakamoto_integrations.rs | 261 +--------------- .../src/tests/neon_integrations.rs | 53 +--- testnet/stacks-node/src/tests/signer.rs | 6 +- testnet/stacks-node/src/tests/stackerdb.rs | 5 +- testnet/stacks-node/src/utils.rs | 195 ++++++++++++ 37 files changed, 663 insertions(+), 347 deletions(-) create mode 100644 blind-signer/Cargo.toml create mode 100644 blind-signer/src/lib.rs create mode 100644 blind-signer/src/main.rs create mode 100644 testnet/stacks-node/src/lib.rs create mode 100644 testnet/stacks-node/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index aedba0ffaa..ab0e3e0e96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,6 +522,20 @@ dependencies = [ "wyz", ] +[[package]] +name = "blind-signer" +version = "0.1.0" +dependencies = [ + "libsigner", + "pico-args", + "reqwest", + "serde_json", + "slog", + "stacks-common", + "stacks-node", + "stackslib", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -3496,6 +3510,7 @@ dependencies = [ "async-std", "backtrace", "base64 0.12.3", + "blind-signer", "chrono", "clarity", "hashbrown 0.14.3", diff --git a/Cargo.toml b/Cargo.toml index 66791df99c..d24c04a8ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,13 @@ members = [ "contrib/tools/relay-server", "libsigner", "stacks-signer", - "testnet/stacks-node"] + "testnet/stacks-node", + "blind-signer", +] # Dependencies we want to keep the same between workspace members -[workspace.dependencies] -ed25519-dalek = { version = "2.1.1", features = ["serde", "rand_core"] } +[workspace.dependencies] +ed25519-dalek = { version = "2.1.1", features = ["serde", "rand_core"] } hashbrown = "0.14.3" rand_core = "0.6" rand = "0.8" diff --git a/blind-signer/Cargo.toml b/blind-signer/Cargo.toml new file mode 100644 index 0000000000..a4087aec80 --- /dev/null +++ b/blind-signer/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "blind-signer" +version = "0.1.0" +edition = "2021" + +[dependencies] +slog = { version = "2.5.2", features = ["max_level_trace"] } +pico-args = "0.5.0" +reqwest = { version = "0.11", default_features = false, features = [ + "blocking", + "json", + "rustls", + "rustls-tls", +] } +serde_json = { version = "1.0", features = [ + "arbitrary_precision", + "raw_value", +] } +stacks = { package = "stackslib", path = "../stackslib" } +stacks-common = { path = "../stacks-common" } +libsigner = { path = "../libsigner" } +stacks-node = { path = "../testnet/stacks-node" } + +[lib] +name = "blind_signer" +path = "src/lib.rs" + +[[bin]] +name = "blind-signer" +path = "src/main.rs" diff --git a/blind-signer/src/lib.rs b/blind-signer/src/lib.rs new file mode 100644 index 0000000000..5bc8d0f99d --- /dev/null +++ b/blind-signer/src/lib.rs @@ -0,0 +1,290 @@ +use libsigner::{BlockResponse, SignerMessage, SignerSession, StackerDBSession}; +use stacks::chainstate::burn::db::sortdb::SortitionDB; +use stacks::chainstate::nakamoto::signer_set::NakamotoSigners; +use stacks::chainstate::nakamoto::test_signers::TestSigners; +use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; +use stacks::chainstate::stacks::boot::{MINERS_NAME, SIGNERS_VOTING_NAME}; +use stacks::clarity::vm::types::QualifiedContractIdentifier; +use stacks::clarity::vm::Value; +use stacks::codec::StacksMessageCodec; +use stacks::libstackerdb::{SlotMetadata, StackerDBChunkData}; +use stacks::net::api::callreadonly::CallReadOnlyRequestBody; +use stacks::net::api::getstackers::GetStackersResponse; +use stacks::types::chainstate::StacksAddress; +use stacks::util::hash::to_hex; +use stacks::util_lib::boot::boot_code_id; +use stacks_common::types::chainstate::StacksPublicKey; +use stacks_common::util::hash::Sha512Trunc256Sum; +use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; +use stacks_node::config::Config; +use stacks_node::utils::{get_account, make_contract_call, submit_tx, to_addr}; +use std::{ + collections::HashSet, + thread::{self, JoinHandle}, + time::Duration, +}; + +#[allow(unused_imports)] +#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)] +extern crate slog; +#[macro_use] +extern crate stacks_common; + +/// 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, +) -> JoinHandle<()> { + let mut signed_blocks = HashSet::new(); + let conf = conf.clone(); + let signers = signers.clone(); + let signer = signer.clone(); + thread::spawn(move || loop { + thread::sleep(Duration::from_millis(500)); + 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}"); + } + } + + signer_vote_if_needed(&conf, &signers, &signer); + }) +} + +pub fn read_and_sign_block_proposal( + conf: &Config, + signers: &TestSigners, + signer: &Secp256k1PrivateKey, + signed_blocks: &HashSet, +) -> Result { + 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 rpc_sock = conf + .node + .rpc_bind + .clone() + .parse() + .expect("Failed to parse socket"); + + let mut proposed_block: NakamotoBlock = { + let miner_contract_id = boot_code_id(MINERS_NAME, false); + let mut miners_stackerdb = StackerDBSession::new(rpc_sock, 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(rpc_sock, 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) +} + +fn signer_vote_if_needed(conf: &Config, signers: &TestSigners, signer: &Secp256k1PrivateKey) { + 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 reward_cycle = burnchain + .block_height_to_reward_cycle(tip.block_height) + .unwrap(); + let prepare_phase_start = burnchain + .pox_constants + .prepare_phase_start(burnchain.first_block_height, reward_cycle); + + if tip.block_height >= prepare_phase_start { + // If the key is already set, do nothing. + if is_key_set_for_cycle(reward_cycle + 1, conf.is_mainnet(), &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://{}", &conf.node.rpc_bind); + + // Get the aggregate key + let aggregate_key = signers.clone().generate_aggregate_key(reward_cycle + 1); + let aggregate_public_key = Value::buff_from(aggregate_key.compress().data.to_vec()) + .expect("Failed to serialize aggregate public key"); + + let signer_nonce = get_account(&http_origin, &to_addr(signer)).nonce; + + // Vote on the aggregate public key + let voting_tx = make_contract_call( + &signer, + signer_nonce, + 300, + &StacksAddress::burn_address(false), + SIGNERS_VOTING_NAME, + "vote-for-aggregate-public-key", + &[ + Value::UInt(0), + aggregate_public_key.clone(), + Value::UInt(0), + Value::UInt(reward_cycle as u128 + 1), + ], + ); + submit_tx(&http_origin, &voting_tx); + } +} + +pub fn get_stacker_set(http_origin: &str, cycle: u64) -> GetStackersResponse { + let client = reqwest::blocking::Client::new(); + let path = format!("{http_origin}/v2/stacker_set/{cycle}"); + let res = client + .get(&path) + .send() + .unwrap() + .json::() + .unwrap(); + info!("Stacker set response: {res}"); + let res = serde_json::from_value(res).unwrap(); + res +} + +fn get_signer_index( + stacker_set: &GetStackersResponse, + signer_key: &Secp256k1PublicKey, +) -> Result { + 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) + ) + }) +} + +pub fn get_stackerdb_slot_version( + http_origin: &str, + contract: &QualifiedContractIdentifier, + slot_id: u64, +) -> Option { + 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::>() + .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 + } + }) +} + +fn is_key_set_for_cycle( + reward_cycle: u64, + is_mainnet: bool, + http_origin: &str, +) -> Result { + let client = reqwest::blocking::Client::new(); + let boot_address = StacksAddress::burn_address(is_mainnet); + let path = format!("http://{http_origin}/v2/contracts/call-read/{boot_address}/signers-voting/get-approved-aggregate-key"); + let body = CallReadOnlyRequestBody { + sender: boot_address.to_string(), + sponsor: None, + arguments: vec![Value::UInt(reward_cycle as u128) + .serialize_to_hex() + .map_err(|_| "Failed to serialize reward cycle")?], + }; + let res = client + .post(&path) + .json(&body) + .send() + .map_err(|_| "Failed to send request")? + .json::() + .map_err(|_| "Failed to extract json Value")?; + let result_value = Value::try_deserialize_hex_untyped( + &res.get("result") + .ok_or("No result in response")? + .as_str() + .ok_or("Result is not a string")?[2..], + ) + .map_err(|_| "Failed to deserialize Clarity value")?; + + result_value + .expect_optional() + .map(|v| v.is_some()) + .map_err(|_| "Response is not optional".to_string()) +} diff --git a/blind-signer/src/main.rs b/blind-signer/src/main.rs new file mode 100644 index 0000000000..e7409ffbff --- /dev/null +++ b/blind-signer/src/main.rs @@ -0,0 +1,41 @@ +#[macro_use] +extern crate stacks_common; + +use std::{process, thread::park}; + +use pico_args::Arguments; +use stacks::{ + chainstate::nakamoto::test_signers::TestSigners, util::secp256k1::Secp256k1PrivateKey, +}; +use stacks_node::config::{Config, ConfigFile}; + +#[allow(unused_imports)] +#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)] +extern crate slog; + +fn main() { + let mut args = Arguments::from_env(); + let config_path: String = args.value_from_str("--config").unwrap(); + args.finish(); + info!("Loading config at path {}", config_path); + let config_file = match ConfigFile::from_path(&config_path) { + Ok(config_file) => config_file, + Err(e) => { + warn!("Invalid config file: {}", e); + process::exit(1); + } + }; + + let conf = match Config::from_config_file(config_file) { + Ok(conf) => conf, + Err(e) => { + warn!("Invalid config: {}", e); + process::exit(1); + } + }; + + let signers = TestSigners::default(); + let sender_signer_sk = Secp256k1PrivateKey::new(); + blind_signer::blind_signer(&conf, &signers, &sender_signer_sk); + park(); +} diff --git a/testnet/stacks-node/Cargo.toml b/testnet/stacks-node/Cargo.toml index 71f8808a12..0e5862ddbf 100644 --- a/testnet/stacks-node/Cargo.toml +++ b/testnet/stacks-node/Cargo.toml @@ -31,6 +31,7 @@ wsts = { workspace = true } rand = { workspace = true } rand_core = { workspace = true } hashbrown = { workspace = true } +reqwest = { version = "0.11", default_features = false, features = ["blocking", "json", "rustls", "rustls-tls"] } [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = {workspace = true} @@ -44,6 +45,7 @@ clarity = { path = "../../clarity", features = ["default", "testing"]} stacks-common = { path = "../../stacks-common", features = ["default", "testing"] } stacks = { package = "stackslib", path = "../../stackslib", features = ["default", "testing"] } stacks-signer = { path = "../../stacks-signer" } +blind-signer = { path = "../../blind-signer" } tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } wsts = {workspace = true} @@ -61,6 +63,10 @@ path = "src/main.rs" name = "stacks-events" path = "src/stacks_events.rs" +[lib] +name = "stacks_node" +path = "src/lib.rs" + [features] monitoring_prom = ["stacks/monitoring_prom"] slog_json = ["stacks/slog_json", "stacks-common/slog_json", "clarity/slog_json"] diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index f2e6f69542..0507162ad4 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -53,9 +53,9 @@ use stacks_common::types::chainstate::BurnchainHeaderHash; use stacks_common::util::hash::{hex_bytes, Hash160}; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; +use stacks_node::config::Config; use super::super::operations::BurnchainOpSigner; -use super::super::Config; use super::{BurnchainController, BurnchainTip, Error as BurnchainControllerError}; /// The number of bitcoin blocks that can have @@ -2556,8 +2556,9 @@ mod tests { use std::fs::File; use std::io::Write; + use stacks_node::config::DEFAULT_SATS_PER_VB; + use super::*; - use crate::config::DEFAULT_SATS_PER_VB; #[test] fn test_get_satoshis_per_byte() { diff --git a/testnet/stacks-node/src/chain_data.rs b/testnet/stacks-node/src/chain_data.rs index 4170cf6f6d..cf502058af 100644 --- a/testnet/stacks-node/src/chain_data.rs +++ b/testnet/stacks-node/src/chain_data.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use std::process::{Command, Stdio}; +use serde::{Deserialize, Serialize}; use stacks::burnchains::bitcoin::address::BitcoinAddress; use stacks::burnchains::bitcoin::{BitcoinNetworkType, BitcoinTxOutput}; use stacks::burnchains::{Burnchain, BurnchainSigner, Error as BurnchainError, Txid}; diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index cc39fb1e52..d8b2192191 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -9,6 +9,7 @@ use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{AssetIdentifier, PrincipalData, QualifiedContractIdentifier}; use lazy_static::lazy_static; use rand::RngCore; +use serde::Deserialize; use stacks::burnchains::bitcoin::BitcoinNetworkType; use stacks::burnchains::{Burnchain, MagicBytes, BLOCKSTACK_MAGIC_MAINNET}; use stacks::chainstate::nakamoto::signer_set::NakamotoSigners; @@ -33,6 +34,7 @@ use stacks::net::{Neighbor, NeighborKey}; use stacks::util_lib::boot::boot_code_id; use stacks::util_lib::db::Error as DBError; use stacks_common::consts::SIGNER_SLOTS_PER_USER; +use stacks_common::test_debug; use stacks_common::types::chainstate::StacksAddress; use stacks_common::types::net::PeerAddress; use stacks_common::types::Address; diff --git a/testnet/stacks-node/src/event_dispatcher.rs b/testnet/stacks-node/src/event_dispatcher.rs index 90272bd0b8..88774e35eb 100644 --- a/testnet/stacks-node/src/event_dispatcher.rs +++ b/testnet/stacks-node/src/event_dispatcher.rs @@ -39,8 +39,7 @@ use stacks::net::stackerdb::StackerDBEventDispatcher; use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, StacksBlockId}; use stacks_common::util::hash::bytes_to_hex; - -use super::config::{EventKeyType, EventObserverConfig}; +use stacks_node::config::{EventKeyType, EventObserverConfig}; #[derive(Debug, Clone)] struct EventObserver { diff --git a/testnet/stacks-node/src/globals.rs b/testnet/stacks-node/src/globals.rs index a6a2fdad3c..3e4b32fbae 100644 --- a/testnet/stacks-node/src/globals.rs +++ b/testnet/stacks-node/src/globals.rs @@ -12,8 +12,8 @@ use stacks::chainstate::stacks::db::StacksChainState; use stacks::chainstate::stacks::miner::MinerStatus; use stacks::net::NetworkResult; use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, ConsensusHash}; +use stacks_node::config::MinerConfig; -use crate::config::MinerConfig; use crate::neon::Counters; use crate::neon_node::LeaderKeyRegistrationState; use crate::run_loop::RegisteredKey; diff --git a/testnet/stacks-node/src/lib.rs b/testnet/stacks-node/src/lib.rs new file mode 100644 index 0000000000..a7ca5cd959 --- /dev/null +++ b/testnet/stacks-node/src/lib.rs @@ -0,0 +1,9 @@ +#[allow(unused_imports)] +#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)] +extern crate slog; +#[macro_use] +extern crate stacks_common; + +pub mod chain_data; +pub mod config; +pub mod utils; diff --git a/testnet/stacks-node/src/main.rs b/testnet/stacks-node/src/main.rs index bf54c1601d..1d0f3d8114 100644 --- a/testnet/stacks-node/src/main.rs +++ b/testnet/stacks-node/src/main.rs @@ -12,12 +12,12 @@ extern crate slog; pub use stacks_common::util; use stacks_common::util::hash::hex_bytes; +use stacks_node::chain_data::MinerStats; +use stacks_node::config::{Config, ConfigFile}; pub mod monitoring; pub mod burnchains; -pub mod chain_data; -pub mod config; pub mod event_dispatcher; pub mod genesis_data; pub mod globals; @@ -47,13 +47,11 @@ use tikv_jemallocator::Jemalloc; pub use self::burnchains::{ BitcoinRegtestController, BurnchainController, BurnchainTip, MocknetController, }; -pub use self::config::{Config, ConfigFile}; pub use self::event_dispatcher::EventDispatcher; pub use self::keychain::Keychain; 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::neon_node::{BlockMinerThread, TipCandidate}; use crate::run_loop::boot_nakamoto; diff --git a/testnet/stacks-node/src/nakamoto_node.rs b/testnet/stacks-node/src/nakamoto_node.rs index 302382f170..d8543f8537 100644 --- a/testnet/stacks-node/src/nakamoto_node.rs +++ b/testnet/stacks-node/src/nakamoto_node.rs @@ -30,11 +30,11 @@ use stacks::net::stackerdb::StackerDBs; use stacks_common::types::chainstate::SortitionId; use stacks_common::types::StacksEpochId; -use super::{Config, EventDispatcher, Keychain}; use crate::burnchains::bitcoin_regtest_controller::addr2str; use crate::neon_node::{LeaderKeyRegistrationState, StacksNode as NeonNode}; use crate::run_loop::nakamoto::{Globals, RunLoop}; use crate::run_loop::RegisteredKey; +use crate::Keychain; pub mod miner; pub mod peer; diff --git a/testnet/stacks-node/src/nakamoto_node/miner.rs b/testnet/stacks-node/src/nakamoto_node/miner.rs index d840e7f7a3..9eac0b52ef 100644 --- a/testnet/stacks-node/src/nakamoto_node/miner.rs +++ b/testnet/stacks-node/src/nakamoto_node/miner.rs @@ -46,14 +46,15 @@ use stacks_common::types::chainstate::{StacksAddress, StacksBlockId}; use stacks_common::types::{PrivateKey, StacksEpochId}; use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum}; use stacks_common::util::vrf::VRFProof; +use stacks_node::config::Config; use wsts::curve::point::Point; use super::relayer::RelayerThread; -use super::{Config, Error as NakamotoNodeError, EventDispatcher, Keychain}; +use super::{Error as NakamotoNodeError, Keychain}; use crate::nakamoto_node::VRF_MOCK_MINER_KEY; use crate::run_loop::nakamoto::Globals; use crate::run_loop::RegisteredKey; -use crate::{neon_node, ChainTip}; +use crate::{neon_node, ChainTip, EventDispatcher}; /// If the miner was interrupted while mining a block, how long should the /// miner thread sleep before trying again? diff --git a/testnet/stacks-node/src/nakamoto_node/peer.rs b/testnet/stacks-node/src/nakamoto_node/peer.rs index eeb6789d30..0bd73da5fc 100644 --- a/testnet/stacks-node/src/nakamoto_node/peer.rs +++ b/testnet/stacks-node/src/nakamoto_node/peer.rs @@ -31,12 +31,13 @@ use stacks::net::dns::{DNSClient, DNSResolver}; use stacks::net::p2p::PeerNetwork; use stacks::net::RPCHandlerArgs; use stacks_common::util::hash::Sha256Sum; +use stacks_node::config::Config; use crate::burnchains::make_bitcoin_indexer; use crate::nakamoto_node::relayer::RelayerDirective; use crate::neon_node::open_chainstate_with_faults; use crate::run_loop::nakamoto::{Globals, RunLoop}; -use crate::{Config, EventDispatcher}; +use crate::EventDispatcher; /// Thread that runs the network state machine, handling both p2p and http requests. pub struct PeerThread { diff --git a/testnet/stacks-node/src/nakamoto_node/relayer.rs b/testnet/stacks-node/src/nakamoto_node/relayer.rs index 1ee3135c24..09d661f775 100644 --- a/testnet/stacks-node/src/nakamoto_node/relayer.rs +++ b/testnet/stacks-node/src/nakamoto_node/relayer.rs @@ -50,11 +50,9 @@ use stacks_common::types::StacksEpochId; use stacks_common::util::get_epoch_time_ms; use stacks_common::util::hash::Hash160; use stacks_common::util::vrf::{VRFProof, VRFPublicKey}; +use stacks_node::config::Config; -use super::{ - BlockCommits, Config, Error as NakamotoNodeError, EventDispatcher, Keychain, - BLOCK_PROCESSOR_STACK_SIZE, -}; +use super::{BlockCommits, Error as NakamotoNodeError, Keychain, BLOCK_PROCESSOR_STACK_SIZE}; use crate::burnchains::BurnchainController; use crate::nakamoto_node::miner::{BlockMinerThread, MinerDirective}; use crate::neon_node::{ @@ -62,7 +60,7 @@ use crate::neon_node::{ }; use crate::run_loop::nakamoto::{Globals, RunLoop}; use crate::run_loop::RegisteredKey; -use crate::BitcoinRegtestController; +use crate::{BitcoinRegtestController, EventDispatcher}; /// Command types for the Nakamoto relayer thread, issued to it by other threads pub enum RelayerDirective { diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 49064d4971..7bd69b028e 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -201,13 +201,14 @@ use stacks_common::util::hash::{to_hex, Hash160, Sha256Sum}; use stacks_common::util::secp256k1::Secp256k1PrivateKey; use stacks_common::util::vrf::{VRFProof, VRFPublicKey}; use stacks_common::util::{get_epoch_time_ms, get_epoch_time_secs}; +use stacks_node::chain_data::MinerStats; +use stacks_node::config::Config; -use super::{BurnchainController, Config, EventDispatcher, Keychain}; +use super::{BurnchainController, EventDispatcher, Keychain}; use crate::burnchains::bitcoin_regtest_controller::{ addr2str, BitcoinRegtestController, OngoingBlockCommit, }; use crate::burnchains::make_bitcoin_indexer; -use crate::chain_data::MinerStats; use crate::globals::{NeonGlobals as Globals, RelayerDirective}; use crate::run_loop::neon::RunLoop; use crate::run_loop::RegisteredKey; diff --git a/testnet/stacks-node/src/node.rs b/testnet/stacks-node/src/node.rs index 90c2123079..b004f9de3f 100644 --- a/testnet/stacks-node/src/node.rs +++ b/testnet/stacks-node/src/node.rs @@ -44,8 +44,9 @@ use stacks_common::util::get_epoch_time_secs; use stacks_common::util::hash::Sha256Sum; use stacks_common::util::secp256k1::Secp256k1PrivateKey; use stacks_common::util::vrf::VRFPublicKey; +use stacks_node::config::Config; -use super::{BurnchainController, BurnchainTip, Config, EventDispatcher, Keychain, Tenure}; +use super::{BurnchainController, BurnchainTip, EventDispatcher, Keychain, Tenure}; use crate::burnchains::make_bitcoin_indexer; use crate::genesis_data::USE_TEST_GENESIS_CHAINSTATE; use crate::run_loop; diff --git a/testnet/stacks-node/src/run_loop/boot_nakamoto.rs b/testnet/stacks-node/src/run_loop/boot_nakamoto.rs index dec1ca757f..c6116de495 100644 --- a/testnet/stacks-node/src/run_loop/boot_nakamoto.rs +++ b/testnet/stacks-node/src/run_loop/boot_nakamoto.rs @@ -24,11 +24,11 @@ use stacks::chainstate::burn::db::sortdb::SortitionDB; use stacks::chainstate::coordinator::comm::CoordinatorChannels; use stacks::core::StacksEpochExtension; use stacks_common::types::{StacksEpoch, StacksEpochId}; +use stacks_node::config::Config; use crate::neon::Counters; use crate::run_loop::nakamoto::RunLoop as NakaRunLoop; use crate::run_loop::neon::RunLoop as NeonRunLoop; -use crate::Config; /// This runloop handles booting to Nakamoto: /// During epochs [1.0, 2.5], it runs a neon run_loop. diff --git a/testnet/stacks-node/src/run_loop/helium.rs b/testnet/stacks-node/src/run_loop/helium.rs index c7212d4132..53e876d2ea 100644 --- a/testnet/stacks-node/src/run_loop/helium.rs +++ b/testnet/stacks-node/src/run_loop/helium.rs @@ -1,11 +1,10 @@ use stacks::chainstate::stacks::db::ClarityTx; use stacks_common::types::chainstate::BurnchainHeaderHash; +use stacks_node::config::Config; use super::RunLoopCallbacks; use crate::burnchains::Error as BurnchainControllerError; -use crate::{ - BitcoinRegtestController, BurnchainController, ChainTip, Config, MocknetController, Node, -}; +use crate::{BitcoinRegtestController, BurnchainController, ChainTip, MocknetController, Node}; /// RunLoop is coordinating a simulated burnchain and some simulated nodes /// taking turns in producing blocks. diff --git a/testnet/stacks-node/src/run_loop/nakamoto.rs b/testnet/stacks-node/src/run_loop/nakamoto.rs index 0b3702a994..8e64cb75a8 100644 --- a/testnet/stacks-node/src/run_loop/nakamoto.rs +++ b/testnet/stacks-node/src/run_loop/nakamoto.rs @@ -33,6 +33,7 @@ use stacks::core::StacksEpochId; use stacks::net::atlas::{AtlasConfig, AtlasDB, Attachment}; use stacks_common::types::PublicKey; use stacks_common::util::hash::Hash160; +use stacks_node::config::Config; use stx_genesis::GenesisData; use crate::burnchains::make_bitcoin_indexer; @@ -46,9 +47,7 @@ use crate::node::{ use crate::run_loop::neon; use crate::run_loop::neon::Counters; use crate::syncctl::{PoxSyncWatchdog, PoxSyncWatchdogComms}; -use crate::{ - run_loop, BitcoinRegtestController, BurnchainController, Config, EventDispatcher, Keychain, -}; +use crate::{run_loop, BitcoinRegtestController, BurnchainController, EventDispatcher, Keychain}; pub const STDERR: i32 = 2; pub type Globals = GenericGlobals; diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index 86235ec3bd..e65ee90ecf 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -27,6 +27,7 @@ use stacks_common::deps_common::ctrlc::SignalId; use stacks_common::types::PublicKey; use stacks_common::util::hash::Hash160; use stacks_common::util::{get_epoch_time_secs, sleep_ms}; +use stacks_node::config::Config; use stx_genesis::GenesisData; use super::RunLoopCallbacks; @@ -39,9 +40,7 @@ use crate::node::{ use_test_genesis_chainstate, }; use crate::syncctl::{PoxSyncWatchdog, PoxSyncWatchdogComms}; -use crate::{ - run_loop, BitcoinRegtestController, BurnchainController, Config, EventDispatcher, Keychain, -}; +use crate::{run_loop, BitcoinRegtestController, BurnchainController, EventDispatcher, Keychain}; pub const STDERR: i32 = 2; diff --git a/testnet/stacks-node/src/syncctl.rs b/testnet/stacks-node/src/syncctl.rs index ff68126a83..92915e8ed8 100644 --- a/testnet/stacks-node/src/syncctl.rs +++ b/testnet/stacks-node/src/syncctl.rs @@ -5,9 +5,9 @@ use std::sync::Arc; use stacks::burnchains::{Burnchain, Error as burnchain_error}; use stacks::chainstate::stacks::db::StacksChainState; use stacks_common::util::{get_epoch_time_secs, sleep_ms}; +use stacks_node::config::Config; use crate::burnchains::BurnchainTip; -use crate::Config; // amount of time to wait for an inv or download sync to complete. // These _really should_ complete before the PoX sync watchdog permits processing the next reward diff --git a/testnet/stacks-node/src/tenure.rs b/testnet/stacks-node/src/tenure.rs index 882a65d06b..985489acec 100644 --- a/testnet/stacks-node/src/tenure.rs +++ b/testnet/stacks-node/src/tenure.rs @@ -16,10 +16,11 @@ use stacks::core::mempool::MemPoolDB; use stacks_common::types::chainstate::VRFSeed; use stacks_common::util::hash::Hash160; use stacks_common::util::vrf::VRFProof; +use stacks_node::config::Config; /// Only used by the Helium (Mocknet) node use super::node::ChainTip; -use super::{BurnchainTip, Config}; +use super::BurnchainTip; pub struct TenureArtifacts { pub anchored_block: StacksBlock, diff --git a/testnet/stacks-node/src/tests/bitcoin_regtest.rs b/testnet/stacks-node/src/tests/bitcoin_regtest.rs index 6391dd9b2a..4f0bfe603b 100644 --- a/testnet/stacks-node/src/tests/bitcoin_regtest.rs +++ b/testnet/stacks-node/src/tests/bitcoin_regtest.rs @@ -9,9 +9,9 @@ use stacks::chainstate::burn::operations::BlockstackOperationType::{ use stacks::chainstate::stacks::StacksPrivateKey; use stacks::core::StacksEpochId; use stacks_common::util::hash::hex_bytes; +use stacks_node::config::InitialBalance; use super::PUBLISH_CONTRACT; -use crate::config::InitialBalance; use crate::helium::RunLoop; use crate::tests::to_addr; use crate::Config; diff --git a/testnet/stacks-node/src/tests/epoch_205.rs b/testnet/stacks-node/src/tests/epoch_205.rs index 0f689f00ef..1fe0e50994 100644 --- a/testnet/stacks-node/src/tests/epoch_205.rs +++ b/testnet/stacks-node/src/tests/epoch_205.rs @@ -24,8 +24,9 @@ use stacks_common::types::chainstate::{ }; use stacks_common::util::hash::hex_bytes; use stacks_common::util::sleep_ms; +use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use stacks_node::utils::{get_account, submit_tx}; -use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; use crate::tests::neon_integrations::*; use crate::tests::{ diff --git a/testnet/stacks-node/src/tests/epoch_21.rs b/testnet/stacks-node/src/tests/epoch_21.rs index e26468a254..4fdfbcc8ac 100644 --- a/testnet/stacks-node/src/tests/epoch_21.rs +++ b/testnet/stacks-node/src/tests/epoch_21.rs @@ -33,9 +33,10 @@ use stacks_common::types::PrivateKey; use stacks_common::util::hash::{Hash160, Sha256Sum}; use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; use stacks_common::util::sleep_ms; +use stacks_node::config::{Config, EventKeyType, EventObserverConfig, InitialBalance}; +use stacks_node::utils::{get_account, submit_tx}; use crate::burnchains::bitcoin_regtest_controller::UTXO; -use crate::config::{Config, EventKeyType, EventObserverConfig, InitialBalance}; use crate::neon::RunLoopCounter; use crate::operations::BurnchainOpSigner; use crate::stacks_common::address::AddressHashMode; diff --git a/testnet/stacks-node/src/tests/epoch_22.rs b/testnet/stacks-node/src/tests/epoch_22.rs index 5c58b26ded..3c9acdc70e 100644 --- a/testnet/stacks-node/src/tests/epoch_22.rs +++ b/testnet/stacks-node/src/tests/epoch_22.rs @@ -16,9 +16,9 @@ use stacks_common::types::PrivateKey; use stacks_common::util::hash::Hash160; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; +use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use stacks_node::utils::{get_account, submit_tx}; -use super::neon_integrations::get_account; -use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::neon_node::StacksNode; use crate::stacks_common::types::Address; use crate::stacks_common::util::hash::bytes_to_hex; diff --git a/testnet/stacks-node/src/tests/epoch_23.rs b/testnet/stacks-node/src/tests/epoch_23.rs index 740785e182..32c0b10b43 100644 --- a/testnet/stacks-node/src/tests/epoch_23.rs +++ b/testnet/stacks-node/src/tests/epoch_23.rs @@ -21,8 +21,9 @@ use stacks::burnchains::{Burnchain, PoxConstants}; use stacks::core; use stacks::core::STACKS_EPOCH_MAX; use stacks_common::util::sleep_ms; +use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use stacks_node::utils::submit_tx; -use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; use crate::tests::neon_integrations::*; use crate::tests::*; diff --git a/testnet/stacks-node/src/tests/epoch_24.rs b/testnet/stacks-node/src/tests/epoch_24.rs index b88441838a..d1f8b42089 100644 --- a/testnet/stacks-node/src/tests/epoch_24.rs +++ b/testnet/stacks-node/src/tests/epoch_24.rs @@ -35,12 +35,13 @@ use stacks_common::types::Address; use stacks_common::util::hash::{bytes_to_hex, hex_bytes, Hash160}; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; +use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use stacks_node::utils::{get_account, submit_tx}; -use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; use crate::tests::neon_integrations::{ - get_account, get_chain_info, get_pox_info, neon_integration_test_conf, next_block_and_wait, - submit_tx, test_observer, wait_for_runloop, + get_chain_info, get_pox_info, neon_integration_test_conf, next_block_and_wait, test_observer, + wait_for_runloop, }; use crate::tests::{make_contract_call, to_addr}; use crate::{neon, BitcoinRegtestController, BurnchainController}; diff --git a/testnet/stacks-node/src/tests/integrations.rs b/testnet/stacks-node/src/tests/integrations.rs index 2bb9bd891e..e798681a16 100644 --- a/testnet/stacks-node/src/tests/integrations.rs +++ b/testnet/stacks-node/src/tests/integrations.rs @@ -34,12 +34,12 @@ use stacks::net::api::getistraitimplemented::GetIsTraitImplementedResponse; use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, VRFSeed}; use stacks_common::util::hash::{hex_bytes, to_hex, Sha256Sum}; +use stacks_node::config::InitialBalance; use super::{ make_contract_call, make_contract_publish, make_stacks_transfer, to_addr, ADDR_4, SK_1, SK_2, SK_3, }; -use crate::config::InitialBalance; use crate::helium::RunLoop; use crate::tests::make_sponsored_stacks_transfer_on_testnet; diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 3fd999a603..652bdb5583 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -13,23 +13,22 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; 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 blind_signer::blind_signer; use clarity::vm::ast::ASTRules; use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use lazy_static::lazy_static; -use libsigner::{BlockResponse, SignerMessage, SignerSession, StackerDBSession}; +use libsigner::{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,7 +42,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::libstackerdb::SlotMetadata; use stacks::net::api::callreadonly::CallReadOnlyRequestBody; use stacks::net::api::getstackers::GetStackersResponse; use stacks::net::api::postblock_proposal::{ @@ -59,16 +58,17 @@ 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, Sha512Trunc256Sum}; +use stacks_common::util::hash::to_hex; use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey}; +use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use stacks_node::utils::{get_account, submit_tx}; use super::bitcoin_regtest::BitcoinCoreController; -use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; -use crate::neon::{Counters, RunLoopCounter}; +use crate::neon::Counters; use crate::run_loop::boot_nakamoto; use crate::tests::neon_integrations::{ - get_account, get_chain_info_result, get_pox_info, next_block_and_wait, - run_until_burnchain_height, submit_tx, test_observer, wait_for_runloop, + get_chain_info_result, get_pox_info, next_block_and_wait, run_until_burnchain_height, + test_observer, wait_for_runloop, }; use crate::tests::{make_stacks_transfer, to_addr}; use crate::{tests, BitcoinRegtestController, BurnchainController, Config, ConfigFile, Keychain}; @@ -201,120 +201,6 @@ 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, -) -> Result { - 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 rpc_sock = conf - .node - .rpc_bind - .clone() - .parse() - .expect("Failed to parse socket"); - - let mut proposed_block: NakamotoBlock = { - let miner_contract_id = boot_code_id(MINERS_NAME, false); - let mut miners_stackerdb = StackerDBSession::new(rpc_sock, 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(rpc_sock, 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(); @@ -687,70 +573,6 @@ fn is_key_set_for_cycle( .map_err(|_| "Response is not optional".to_string()) } -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, -) { - // 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), - ], - ); - 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 @@ -908,7 +730,6 @@ fn simple_neon_integration() { blocks_processed, naka_submitted_vrfs: vrfs_submitted, naka_submitted_commits: commits_submitted, - naka_proposed_blocks: proposals_submitted, .. } = run_loop.counters(); @@ -959,7 +780,7 @@ fn simple_neon_integration() { } info!("Nakamoto miner started..."); - blind_signer(&naka_conf, &signers, &sender_signer_sk, proposals_submitted); + blind_signer(&naka_conf, &signers, &sender_signer_sk); // first block wakes up the run loop, wait until a key registration has been submitted. next_block_and(&mut btc_regtest_controller, 60, || { @@ -984,13 +805,6 @@ fn simple_neon_integration() { &commits_submitted, ) .unwrap(); - - signer_vote_if_needed( - &btc_regtest_controller, - &naka_conf, - &[sender_signer_sk], - &signers, - ); } // Submit a TX @@ -1026,13 +840,6 @@ fn simple_neon_integration() { &commits_submitted, ) .unwrap(); - - 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 @@ -1149,7 +956,6 @@ 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(); @@ -1188,7 +994,7 @@ fn mine_multiple_per_tenure_integration() { .stacks_block_height; info!("Nakamoto miner started..."); - blind_signer(&naka_conf, &signers, &sender_signer_sk, proposals_submitted); + blind_signer(&naka_conf, &signers, &sender_signer_sk); // first block wakes up the run loop, wait until a key registration has been submitted. next_block_and(&mut btc_regtest_controller, 60, || { @@ -1343,7 +1149,6 @@ fn correct_burn_outs() { blocks_processed, naka_submitted_vrfs: vrfs_submitted, naka_submitted_commits: commits_submitted, - naka_proposed_blocks: proposals_submitted, .. } = run_loop.counters(); @@ -1454,33 +1259,7 @@ fn correct_burn_outs() { }) .unwrap(); - 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, - ); - - // Run until the prepare phase - run_until_burnchain_height( - &mut btc_regtest_controller, - &blocks_processed, - prepare_phase_start, - &naka_conf, - ); - - signer_vote_if_needed( - &btc_regtest_controller, - &naka_conf, - &[sender_signer_sk], - &signers, - ); + blind_signer(&naka_conf, &signers, &sender_signer_sk); run_until_burnchain_height( &mut btc_regtest_controller, @@ -1490,7 +1269,6 @@ 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(); @@ -1551,13 +1329,6 @@ fn correct_burn_outs() { tip_sn.block_height > prior_tip, "The new burnchain tip must have been processed" ); - - signer_vote_if_needed( - &btc_regtest_controller, - &naka_conf, - &[sender_signer_sk], - &signers, - ); } coord_channel @@ -1637,7 +1408,6 @@ 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(); @@ -1655,7 +1425,7 @@ fn block_proposal_api_endpoint() { ); info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); - blind_signer(&conf, &signers, &sender_signer_sk, proposals_submitted); + blind_signer(&conf, &signers, &sender_signer_sk); let burnchain = conf.get_burnchain(); let sortdb = burnchain.open_sortition_db(true).unwrap(); @@ -1988,7 +1758,6 @@ 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(); @@ -2006,7 +1775,7 @@ fn miner_writes_proposed_block_to_stackerdb() { ); info!("Nakamoto miner started..."); - blind_signer(&naka_conf, &signers, &sender_signer_sk, proposals_submitted); + blind_signer(&naka_conf, &signers, &sender_signer_sk); // 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); diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index cd0c96358e..0609bb479d 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -61,6 +61,8 @@ use stacks_common::types::chainstate::{ use stacks_common::util::hash::{bytes_to_hex, hex_bytes, to_hex, Hash160}; use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; use stacks_common::util::{get_epoch_time_ms, get_epoch_time_secs, sleep_ms}; +use stacks_node::config::{EventKeyType, EventObserverConfig, FeeEstimatorName, InitialBalance}; +use stacks_node::utils::{get_account, submit_tx}; use super::bitcoin_regtest::BitcoinCoreController; use super::{ @@ -69,7 +71,6 @@ use super::{ SK_2, SK_3, }; use crate::burnchains::bitcoin_regtest_controller::{self, BitcoinRPCRequest, UTXO}; -use crate::config::{EventKeyType, EventObserverConfig, FeeEstimatorName, InitialBalance}; use crate::neon_node::RelayerThread; use crate::operations::BurnchainOpSigner; use crate::stacks_common::types::PrivateKey; @@ -697,32 +698,6 @@ pub fn wait_for_microblocks(microblocks_processed: &Arc, timeout: u64 return true; } -/// returns Txid string -pub fn submit_tx(http_origin: &str, tx: &Vec) -> String { - let client = reqwest::blocking::Client::new(); - let path = format!("{}/v2/transactions", http_origin); - let res = client - .post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx.clone()) - .send() - .unwrap(); - if res.status().is_success() { - let res: String = res.json().unwrap(); - assert_eq!( - res, - StacksTransaction::consensus_deserialize(&mut &tx[..]) - .unwrap() - .txid() - .to_string() - ); - return res; - } else { - eprintln!("Submit tx error: {}", res.text().unwrap()); - panic!(""); - } -} - pub fn get_unconfirmed_tx(http_origin: &str, txid: &Txid) -> Option { let client = reqwest::blocking::Client::new(); let path = format!("{}/v2/transactions/unconfirmed/{}", http_origin, txid); @@ -1196,30 +1171,6 @@ pub fn get_balance(http_origin: &str, account: &F) -> u128 get_account(http_origin, account).balance } -#[derive(Debug)] -pub struct Account { - pub balance: u128, - pub locked: u128, - pub nonce: u64, -} - -pub fn get_account(http_origin: &str, account: &F) -> Account { - let client = reqwest::blocking::Client::new(); - let path = format!("{}/v2/accounts/{}?proof=0", http_origin, account); - let res = client - .get(&path) - .send() - .unwrap() - .json::() - .unwrap(); - info!("Account response: {:#?}", res); - Account { - balance: u128::from_str_radix(&res.balance[2..], 16).unwrap(), - locked: u128::from_str_radix(&res.locked[2..], 16).unwrap(), - nonce: res.nonce, - } -} - pub fn get_pox_info(http_origin: &str) -> Option { let client = reqwest::blocking::Client::new(); let path = format!("{}/v2/pox", http_origin); diff --git a/testnet/stacks-node/src/tests/signer.rs b/testnet/stacks-node/src/tests/signer.rs index f16b4347d6..3c90b0dcf5 100644 --- a/testnet/stacks-node/src/tests/signer.rs +++ b/testnet/stacks-node/src/tests/signer.rs @@ -27,6 +27,9 @@ use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId, TrieHash}; use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{MerkleTree, Sha512Trunc256Sum}; use stacks_common::util::secp256k1::MessageSignature; +use stacks_node::config::{ + Config as NeonConfig, EventKeyType, EventObserverConfig, InitialBalance, +}; use stacks_signer::client::{StackerDB, StacksClient}; use stacks_signer::config::{build_signer_config_tomls, GlobalConfig as SignerConfig, Network}; use stacks_signer::runloop::RunLoopCommand; @@ -40,7 +43,6 @@ use wsts::curve::scalar::Scalar; use wsts::state_machine::OperationResult; use wsts::taproot::SchnorrProof; -use crate::config::{Config as NeonConfig, EventKeyType, EventObserverConfig, InitialBalance}; use crate::event_dispatcher::MinedNakamotoBlockEvent; use crate::neon::Counters; use crate::run_loop::boot_nakamoto; @@ -91,7 +93,7 @@ impl SignerTest { .map(|_| StacksPrivateKey::new()) .collect::>(); - let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); + let (naka_conf, _miner_account) = naka_neon_integration_conf(None); // Setup the signer and coordinator configurations let signer_configs = build_signer_config_tomls( diff --git a/testnet/stacks-node/src/tests/stackerdb.rs b/testnet/stacks-node/src/tests/stackerdb.rs index e24b5c5c24..66eeee04ce 100644 --- a/testnet/stacks-node/src/tests/stackerdb.rs +++ b/testnet/stacks-node/src/tests/stackerdb.rs @@ -21,13 +21,14 @@ use stacks::chainstate::stacks::StacksPrivateKey; use stacks::libstackerdb::{StackerDBChunkAckData, StackerDBChunkData}; use stacks_common::types::chainstate::StacksAddress; use stacks_common::util::hash::Sha512Trunc256Sum; +use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use stacks_node::utils::submit_tx; use {reqwest, serde_json}; use super::bitcoin_regtest::BitcoinCoreController; use crate::burnchains::BurnchainController; -use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::neon_integrations::{ - neon_integration_test_conf, next_block_and_wait, submit_tx, test_observer, wait_for_runloop, + neon_integration_test_conf, next_block_and_wait, test_observer, wait_for_runloop, }; use crate::tests::{make_contract_publish, to_addr}; use crate::{neon, BitcoinRegtestController}; diff --git a/testnet/stacks-node/src/utils.rs b/testnet/stacks-node/src/utils.rs new file mode 100644 index 0000000000..3a01415c40 --- /dev/null +++ b/testnet/stacks-node/src/utils.rs @@ -0,0 +1,195 @@ +use clarity::vm::{ClarityName, ContractName, Value}; +use stacks::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG}; +use stacks::chainstate::stacks::{ + StacksTransaction, StacksTransactionSigner, TransactionAnchorMode, TransactionAuth, + TransactionContractCall, TransactionPayload, TransactionPostConditionMode, + TransactionSpendingCondition, TransactionVersion, +}; +use stacks::codec::StacksMessageCodec; +use stacks::core::CHAIN_ID_TESTNET; +use stacks::net::api::getaccount::AccountEntryResponse; +use stacks::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey}; + +#[derive(Debug)] +pub struct Account { + pub balance: u128, + pub locked: u128, + pub nonce: u64, +} + +pub fn get_account(http_origin: &str, account: &F) -> Account { + let client = reqwest::blocking::Client::new(); + let path = format!("{}/v2/accounts/{}?proof=0", http_origin, account); + let res = client + .get(&path) + .send() + .unwrap() + .json::() + .unwrap(); + info!("Account response: {:#?}", res); + Account { + balance: u128::from_str_radix(&res.balance[2..], 16).unwrap(), + locked: u128::from_str_radix(&res.locked[2..], 16).unwrap(), + nonce: res.nonce, + } +} + +pub fn to_addr(sk: &StacksPrivateKey) -> StacksAddress { + StacksAddress::from_public_keys( + C32_ADDRESS_VERSION_TESTNET_SINGLESIG, + &AddressHashMode::SerializeP2PKH, + 1, + &vec![StacksPublicKey::from_private(sk)], + ) + .unwrap() +} + +pub fn serialize_sign_standard_single_sig_tx( + payload: TransactionPayload, + sender: &StacksPrivateKey, + nonce: u64, + tx_fee: u64, +) -> Vec { + serialize_sign_standard_single_sig_tx_anchor_mode( + payload, + sender, + nonce, + tx_fee, + TransactionAnchorMode::OnChainOnly, + ) +} + +pub fn serialize_sign_standard_single_sig_tx_anchor_mode( + payload: TransactionPayload, + sender: &StacksPrivateKey, + nonce: u64, + tx_fee: u64, + anchor_mode: TransactionAnchorMode, +) -> Vec { + serialize_sign_standard_single_sig_tx_anchor_mode_version( + payload, + sender, + nonce, + tx_fee, + anchor_mode, + TransactionVersion::Testnet, + ) +} + +pub fn serialize_sign_standard_single_sig_tx_anchor_mode_version( + payload: TransactionPayload, + sender: &StacksPrivateKey, + nonce: u64, + tx_fee: u64, + anchor_mode: TransactionAnchorMode, + version: TransactionVersion, +) -> Vec { + serialize_sign_tx_anchor_mode_version( + payload, + sender, + None, + nonce, + None, + tx_fee, + anchor_mode, + version, + ) +} + +pub fn serialize_sign_tx_anchor_mode_version( + payload: TransactionPayload, + sender: &StacksPrivateKey, + payer: Option<&StacksPrivateKey>, + sender_nonce: u64, + payer_nonce: Option, + tx_fee: u64, + anchor_mode: TransactionAnchorMode, + version: TransactionVersion, +) -> Vec { + let mut sender_spending_condition = + TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private(sender)) + .expect("Failed to create p2pkh spending condition from public key."); + sender_spending_condition.set_nonce(sender_nonce); + + let auth = match (payer, payer_nonce) { + (Some(payer), Some(payer_nonce)) => { + let mut payer_spending_condition = TransactionSpendingCondition::new_singlesig_p2pkh( + StacksPublicKey::from_private(payer), + ) + .expect("Failed to create p2pkh spending condition from public key."); + payer_spending_condition.set_nonce(payer_nonce); + payer_spending_condition.set_tx_fee(tx_fee); + TransactionAuth::Sponsored(sender_spending_condition, payer_spending_condition) + } + _ => { + sender_spending_condition.set_tx_fee(tx_fee); + TransactionAuth::Standard(sender_spending_condition) + } + }; + let mut unsigned_tx = StacksTransaction::new(version, auth, payload); + unsigned_tx.anchor_mode = anchor_mode; + unsigned_tx.post_condition_mode = TransactionPostConditionMode::Allow; + unsigned_tx.chain_id = CHAIN_ID_TESTNET; + + let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx); + tx_signer.sign_origin(sender).unwrap(); + if let (Some(payer), Some(_)) = (payer, payer_nonce) { + tx_signer.sign_sponsor(payer).unwrap(); + } + + let mut buf = vec![]; + tx_signer + .get_tx() + .unwrap() + .consensus_serialize(&mut buf) + .unwrap(); + buf +} + +pub fn make_contract_call( + sender: &StacksPrivateKey, + nonce: u64, + tx_fee: u64, + contract_addr: &StacksAddress, + contract_name: &str, + function_name: &str, + function_args: &[Value], +) -> Vec { + let contract_name = ContractName::from(contract_name); + let function_name = ClarityName::from(function_name); + + let payload = TransactionContractCall { + address: contract_addr.clone(), + contract_name, + function_name, + function_args: function_args.iter().map(|x| x.clone()).collect(), + }; + + serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, tx_fee) +} + +/// returns Txid string +pub fn submit_tx(http_origin: &str, tx: &Vec) -> String { + let client = reqwest::blocking::Client::new(); + let path = format!("{}/v2/transactions", http_origin); + let res = client + .post(&path) + .header("Content-Type", "application/octet-stream") + .body(tx.clone()) + .send() + .unwrap(); + if res.status().is_success() { + let res: String = res.json().unwrap(); + assert_eq!( + res, + StacksTransaction::consensus_deserialize(&mut &tx[..]) + .unwrap() + .txid() + .to_string() + ); + return res; + } else { + eprintln!("Submit tx error: {}", res.text().unwrap()); + panic!(""); + } +} From 1fb9876412153e7c43a430621e0f7a224cdf964e Mon Sep 17 00:00:00 2001 From: Brice Dobry Date: Mon, 4 Mar 2024 11:37:29 -0500 Subject: [PATCH 5/6] Revert "feat: setup `blind-signer` lib and binary" This reverts commit b04386d4f5950484f1618703ff22b87317b6a138. Since the signer is ready, this blind signer is not necessary and it is better to not add this additional component that needs to be kept updated. --- Cargo.lock | 15 - Cargo.toml | 8 +- blind-signer/Cargo.toml | 30 -- blind-signer/src/lib.rs | 290 ------------------ blind-signer/src/main.rs | 41 --- testnet/stacks-node/Cargo.toml | 6 - .../burnchains/bitcoin_regtest_controller.rs | 5 +- testnet/stacks-node/src/chain_data.rs | 1 - testnet/stacks-node/src/config.rs | 2 - testnet/stacks-node/src/event_dispatcher.rs | 3 +- testnet/stacks-node/src/globals.rs | 2 +- testnet/stacks-node/src/lib.rs | 9 - testnet/stacks-node/src/main.rs | 6 +- testnet/stacks-node/src/nakamoto_node.rs | 2 +- .../stacks-node/src/nakamoto_node/miner.rs | 5 +- testnet/stacks-node/src/nakamoto_node/peer.rs | 3 +- .../stacks-node/src/nakamoto_node/relayer.rs | 8 +- testnet/stacks-node/src/neon_node.rs | 5 +- testnet/stacks-node/src/node.rs | 3 +- .../stacks-node/src/run_loop/boot_nakamoto.rs | 2 +- testnet/stacks-node/src/run_loop/helium.rs | 5 +- testnet/stacks-node/src/run_loop/nakamoto.rs | 5 +- testnet/stacks-node/src/run_loop/neon.rs | 5 +- testnet/stacks-node/src/syncctl.rs | 2 +- testnet/stacks-node/src/tenure.rs | 3 +- .../stacks-node/src/tests/bitcoin_regtest.rs | 2 +- testnet/stacks-node/src/tests/epoch_205.rs | 3 +- testnet/stacks-node/src/tests/epoch_21.rs | 3 +- testnet/stacks-node/src/tests/epoch_22.rs | 4 +- testnet/stacks-node/src/tests/epoch_23.rs | 3 +- testnet/stacks-node/src/tests/epoch_24.rs | 7 +- testnet/stacks-node/src/tests/integrations.rs | 2 +- .../src/tests/nakamoto_integrations.rs | 261 +++++++++++++++- .../src/tests/neon_integrations.rs | 53 +++- testnet/stacks-node/src/tests/signer.rs | 6 +- testnet/stacks-node/src/tests/stackerdb.rs | 5 +- testnet/stacks-node/src/utils.rs | 195 ------------ 37 files changed, 347 insertions(+), 663 deletions(-) delete mode 100644 blind-signer/Cargo.toml delete mode 100644 blind-signer/src/lib.rs delete mode 100644 blind-signer/src/main.rs delete mode 100644 testnet/stacks-node/src/lib.rs delete mode 100644 testnet/stacks-node/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index ab0e3e0e96..aedba0ffaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,20 +522,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blind-signer" -version = "0.1.0" -dependencies = [ - "libsigner", - "pico-args", - "reqwest", - "serde_json", - "slog", - "stacks-common", - "stacks-node", - "stackslib", -] - [[package]] name = "block-buffer" version = "0.9.0" @@ -3510,7 +3496,6 @@ dependencies = [ "async-std", "backtrace", "base64 0.12.3", - "blind-signer", "chrono", "clarity", "hashbrown 0.14.3", diff --git a/Cargo.toml b/Cargo.toml index d24c04a8ba..66791df99c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,11 @@ members = [ "contrib/tools/relay-server", "libsigner", "stacks-signer", - "testnet/stacks-node", - "blind-signer", -] + "testnet/stacks-node"] # Dependencies we want to keep the same between workspace members -[workspace.dependencies] -ed25519-dalek = { version = "2.1.1", features = ["serde", "rand_core"] } +[workspace.dependencies] +ed25519-dalek = { version = "2.1.1", features = ["serde", "rand_core"] } hashbrown = "0.14.3" rand_core = "0.6" rand = "0.8" diff --git a/blind-signer/Cargo.toml b/blind-signer/Cargo.toml deleted file mode 100644 index a4087aec80..0000000000 --- a/blind-signer/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "blind-signer" -version = "0.1.0" -edition = "2021" - -[dependencies] -slog = { version = "2.5.2", features = ["max_level_trace"] } -pico-args = "0.5.0" -reqwest = { version = "0.11", default_features = false, features = [ - "blocking", - "json", - "rustls", - "rustls-tls", -] } -serde_json = { version = "1.0", features = [ - "arbitrary_precision", - "raw_value", -] } -stacks = { package = "stackslib", path = "../stackslib" } -stacks-common = { path = "../stacks-common" } -libsigner = { path = "../libsigner" } -stacks-node = { path = "../testnet/stacks-node" } - -[lib] -name = "blind_signer" -path = "src/lib.rs" - -[[bin]] -name = "blind-signer" -path = "src/main.rs" diff --git a/blind-signer/src/lib.rs b/blind-signer/src/lib.rs deleted file mode 100644 index 5bc8d0f99d..0000000000 --- a/blind-signer/src/lib.rs +++ /dev/null @@ -1,290 +0,0 @@ -use libsigner::{BlockResponse, SignerMessage, SignerSession, StackerDBSession}; -use stacks::chainstate::burn::db::sortdb::SortitionDB; -use stacks::chainstate::nakamoto::signer_set::NakamotoSigners; -use stacks::chainstate::nakamoto::test_signers::TestSigners; -use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; -use stacks::chainstate::stacks::boot::{MINERS_NAME, SIGNERS_VOTING_NAME}; -use stacks::clarity::vm::types::QualifiedContractIdentifier; -use stacks::clarity::vm::Value; -use stacks::codec::StacksMessageCodec; -use stacks::libstackerdb::{SlotMetadata, StackerDBChunkData}; -use stacks::net::api::callreadonly::CallReadOnlyRequestBody; -use stacks::net::api::getstackers::GetStackersResponse; -use stacks::types::chainstate::StacksAddress; -use stacks::util::hash::to_hex; -use stacks::util_lib::boot::boot_code_id; -use stacks_common::types::chainstate::StacksPublicKey; -use stacks_common::util::hash::Sha512Trunc256Sum; -use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; -use stacks_node::config::Config; -use stacks_node::utils::{get_account, make_contract_call, submit_tx, to_addr}; -use std::{ - collections::HashSet, - thread::{self, JoinHandle}, - time::Duration, -}; - -#[allow(unused_imports)] -#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)] -extern crate slog; -#[macro_use] -extern crate stacks_common; - -/// 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, -) -> JoinHandle<()> { - let mut signed_blocks = HashSet::new(); - let conf = conf.clone(); - let signers = signers.clone(); - let signer = signer.clone(); - thread::spawn(move || loop { - thread::sleep(Duration::from_millis(500)); - 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}"); - } - } - - signer_vote_if_needed(&conf, &signers, &signer); - }) -} - -pub fn read_and_sign_block_proposal( - conf: &Config, - signers: &TestSigners, - signer: &Secp256k1PrivateKey, - signed_blocks: &HashSet, -) -> Result { - 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 rpc_sock = conf - .node - .rpc_bind - .clone() - .parse() - .expect("Failed to parse socket"); - - let mut proposed_block: NakamotoBlock = { - let miner_contract_id = boot_code_id(MINERS_NAME, false); - let mut miners_stackerdb = StackerDBSession::new(rpc_sock, 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(rpc_sock, 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) -} - -fn signer_vote_if_needed(conf: &Config, signers: &TestSigners, signer: &Secp256k1PrivateKey) { - 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 reward_cycle = burnchain - .block_height_to_reward_cycle(tip.block_height) - .unwrap(); - let prepare_phase_start = burnchain - .pox_constants - .prepare_phase_start(burnchain.first_block_height, reward_cycle); - - if tip.block_height >= prepare_phase_start { - // If the key is already set, do nothing. - if is_key_set_for_cycle(reward_cycle + 1, conf.is_mainnet(), &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://{}", &conf.node.rpc_bind); - - // Get the aggregate key - let aggregate_key = signers.clone().generate_aggregate_key(reward_cycle + 1); - let aggregate_public_key = Value::buff_from(aggregate_key.compress().data.to_vec()) - .expect("Failed to serialize aggregate public key"); - - let signer_nonce = get_account(&http_origin, &to_addr(signer)).nonce; - - // Vote on the aggregate public key - let voting_tx = make_contract_call( - &signer, - signer_nonce, - 300, - &StacksAddress::burn_address(false), - SIGNERS_VOTING_NAME, - "vote-for-aggregate-public-key", - &[ - Value::UInt(0), - aggregate_public_key.clone(), - Value::UInt(0), - Value::UInt(reward_cycle as u128 + 1), - ], - ); - submit_tx(&http_origin, &voting_tx); - } -} - -pub fn get_stacker_set(http_origin: &str, cycle: u64) -> GetStackersResponse { - let client = reqwest::blocking::Client::new(); - let path = format!("{http_origin}/v2/stacker_set/{cycle}"); - let res = client - .get(&path) - .send() - .unwrap() - .json::() - .unwrap(); - info!("Stacker set response: {res}"); - let res = serde_json::from_value(res).unwrap(); - res -} - -fn get_signer_index( - stacker_set: &GetStackersResponse, - signer_key: &Secp256k1PublicKey, -) -> Result { - 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) - ) - }) -} - -pub fn get_stackerdb_slot_version( - http_origin: &str, - contract: &QualifiedContractIdentifier, - slot_id: u64, -) -> Option { - 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::>() - .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 - } - }) -} - -fn is_key_set_for_cycle( - reward_cycle: u64, - is_mainnet: bool, - http_origin: &str, -) -> Result { - let client = reqwest::blocking::Client::new(); - let boot_address = StacksAddress::burn_address(is_mainnet); - let path = format!("http://{http_origin}/v2/contracts/call-read/{boot_address}/signers-voting/get-approved-aggregate-key"); - let body = CallReadOnlyRequestBody { - sender: boot_address.to_string(), - sponsor: None, - arguments: vec![Value::UInt(reward_cycle as u128) - .serialize_to_hex() - .map_err(|_| "Failed to serialize reward cycle")?], - }; - let res = client - .post(&path) - .json(&body) - .send() - .map_err(|_| "Failed to send request")? - .json::() - .map_err(|_| "Failed to extract json Value")?; - let result_value = Value::try_deserialize_hex_untyped( - &res.get("result") - .ok_or("No result in response")? - .as_str() - .ok_or("Result is not a string")?[2..], - ) - .map_err(|_| "Failed to deserialize Clarity value")?; - - result_value - .expect_optional() - .map(|v| v.is_some()) - .map_err(|_| "Response is not optional".to_string()) -} diff --git a/blind-signer/src/main.rs b/blind-signer/src/main.rs deleted file mode 100644 index e7409ffbff..0000000000 --- a/blind-signer/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -#[macro_use] -extern crate stacks_common; - -use std::{process, thread::park}; - -use pico_args::Arguments; -use stacks::{ - chainstate::nakamoto::test_signers::TestSigners, util::secp256k1::Secp256k1PrivateKey, -}; -use stacks_node::config::{Config, ConfigFile}; - -#[allow(unused_imports)] -#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)] -extern crate slog; - -fn main() { - let mut args = Arguments::from_env(); - let config_path: String = args.value_from_str("--config").unwrap(); - args.finish(); - info!("Loading config at path {}", config_path); - let config_file = match ConfigFile::from_path(&config_path) { - Ok(config_file) => config_file, - Err(e) => { - warn!("Invalid config file: {}", e); - process::exit(1); - } - }; - - let conf = match Config::from_config_file(config_file) { - Ok(conf) => conf, - Err(e) => { - warn!("Invalid config: {}", e); - process::exit(1); - } - }; - - let signers = TestSigners::default(); - let sender_signer_sk = Secp256k1PrivateKey::new(); - blind_signer::blind_signer(&conf, &signers, &sender_signer_sk); - park(); -} diff --git a/testnet/stacks-node/Cargo.toml b/testnet/stacks-node/Cargo.toml index 0e5862ddbf..71f8808a12 100644 --- a/testnet/stacks-node/Cargo.toml +++ b/testnet/stacks-node/Cargo.toml @@ -31,7 +31,6 @@ wsts = { workspace = true } rand = { workspace = true } rand_core = { workspace = true } hashbrown = { workspace = true } -reqwest = { version = "0.11", default_features = false, features = ["blocking", "json", "rustls", "rustls-tls"] } [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = {workspace = true} @@ -45,7 +44,6 @@ clarity = { path = "../../clarity", features = ["default", "testing"]} stacks-common = { path = "../../stacks-common", features = ["default", "testing"] } stacks = { package = "stackslib", path = "../../stackslib", features = ["default", "testing"] } stacks-signer = { path = "../../stacks-signer" } -blind-signer = { path = "../../blind-signer" } tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } wsts = {workspace = true} @@ -63,10 +61,6 @@ path = "src/main.rs" name = "stacks-events" path = "src/stacks_events.rs" -[lib] -name = "stacks_node" -path = "src/lib.rs" - [features] monitoring_prom = ["stacks/monitoring_prom"] slog_json = ["stacks/slog_json", "stacks-common/slog_json", "clarity/slog_json"] diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 0507162ad4..f2e6f69542 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -53,9 +53,9 @@ use stacks_common::types::chainstate::BurnchainHeaderHash; use stacks_common::util::hash::{hex_bytes, Hash160}; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; -use stacks_node::config::Config; use super::super::operations::BurnchainOpSigner; +use super::super::Config; use super::{BurnchainController, BurnchainTip, Error as BurnchainControllerError}; /// The number of bitcoin blocks that can have @@ -2556,9 +2556,8 @@ mod tests { use std::fs::File; use std::io::Write; - use stacks_node::config::DEFAULT_SATS_PER_VB; - use super::*; + use crate::config::DEFAULT_SATS_PER_VB; #[test] fn test_get_satoshis_per_byte() { diff --git a/testnet/stacks-node/src/chain_data.rs b/testnet/stacks-node/src/chain_data.rs index cf502058af..4170cf6f6d 100644 --- a/testnet/stacks-node/src/chain_data.rs +++ b/testnet/stacks-node/src/chain_data.rs @@ -17,7 +17,6 @@ use std::collections::HashMap; use std::process::{Command, Stdio}; -use serde::{Deserialize, Serialize}; use stacks::burnchains::bitcoin::address::BitcoinAddress; use stacks::burnchains::bitcoin::{BitcoinNetworkType, BitcoinTxOutput}; use stacks::burnchains::{Burnchain, BurnchainSigner, Error as BurnchainError, Txid}; diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index d8b2192191..cc39fb1e52 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -9,7 +9,6 @@ use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{AssetIdentifier, PrincipalData, QualifiedContractIdentifier}; use lazy_static::lazy_static; use rand::RngCore; -use serde::Deserialize; use stacks::burnchains::bitcoin::BitcoinNetworkType; use stacks::burnchains::{Burnchain, MagicBytes, BLOCKSTACK_MAGIC_MAINNET}; use stacks::chainstate::nakamoto::signer_set::NakamotoSigners; @@ -34,7 +33,6 @@ use stacks::net::{Neighbor, NeighborKey}; use stacks::util_lib::boot::boot_code_id; use stacks::util_lib::db::Error as DBError; use stacks_common::consts::SIGNER_SLOTS_PER_USER; -use stacks_common::test_debug; use stacks_common::types::chainstate::StacksAddress; use stacks_common::types::net::PeerAddress; use stacks_common::types::Address; diff --git a/testnet/stacks-node/src/event_dispatcher.rs b/testnet/stacks-node/src/event_dispatcher.rs index 88774e35eb..90272bd0b8 100644 --- a/testnet/stacks-node/src/event_dispatcher.rs +++ b/testnet/stacks-node/src/event_dispatcher.rs @@ -39,7 +39,8 @@ use stacks::net::stackerdb::StackerDBEventDispatcher; use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, StacksBlockId}; use stacks_common::util::hash::bytes_to_hex; -use stacks_node::config::{EventKeyType, EventObserverConfig}; + +use super::config::{EventKeyType, EventObserverConfig}; #[derive(Debug, Clone)] struct EventObserver { diff --git a/testnet/stacks-node/src/globals.rs b/testnet/stacks-node/src/globals.rs index 3e4b32fbae..a6a2fdad3c 100644 --- a/testnet/stacks-node/src/globals.rs +++ b/testnet/stacks-node/src/globals.rs @@ -12,8 +12,8 @@ use stacks::chainstate::stacks::db::StacksChainState; use stacks::chainstate::stacks::miner::MinerStatus; use stacks::net::NetworkResult; use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, ConsensusHash}; -use stacks_node::config::MinerConfig; +use crate::config::MinerConfig; use crate::neon::Counters; use crate::neon_node::LeaderKeyRegistrationState; use crate::run_loop::RegisteredKey; diff --git a/testnet/stacks-node/src/lib.rs b/testnet/stacks-node/src/lib.rs deleted file mode 100644 index a7ca5cd959..0000000000 --- a/testnet/stacks-node/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[allow(unused_imports)] -#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)] -extern crate slog; -#[macro_use] -extern crate stacks_common; - -pub mod chain_data; -pub mod config; -pub mod utils; diff --git a/testnet/stacks-node/src/main.rs b/testnet/stacks-node/src/main.rs index 1d0f3d8114..bf54c1601d 100644 --- a/testnet/stacks-node/src/main.rs +++ b/testnet/stacks-node/src/main.rs @@ -12,12 +12,12 @@ extern crate slog; pub use stacks_common::util; use stacks_common::util::hash::hex_bytes; -use stacks_node::chain_data::MinerStats; -use stacks_node::config::{Config, ConfigFile}; pub mod monitoring; pub mod burnchains; +pub mod chain_data; +pub mod config; pub mod event_dispatcher; pub mod genesis_data; pub mod globals; @@ -47,11 +47,13 @@ use tikv_jemallocator::Jemalloc; pub use self::burnchains::{ BitcoinRegtestController, BurnchainController, BurnchainTip, MocknetController, }; +pub use self::config::{Config, ConfigFile}; pub use self::event_dispatcher::EventDispatcher; pub use self::keychain::Keychain; 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::neon_node::{BlockMinerThread, TipCandidate}; use crate::run_loop::boot_nakamoto; diff --git a/testnet/stacks-node/src/nakamoto_node.rs b/testnet/stacks-node/src/nakamoto_node.rs index d8543f8537..302382f170 100644 --- a/testnet/stacks-node/src/nakamoto_node.rs +++ b/testnet/stacks-node/src/nakamoto_node.rs @@ -30,11 +30,11 @@ use stacks::net::stackerdb::StackerDBs; use stacks_common::types::chainstate::SortitionId; use stacks_common::types::StacksEpochId; +use super::{Config, EventDispatcher, Keychain}; use crate::burnchains::bitcoin_regtest_controller::addr2str; use crate::neon_node::{LeaderKeyRegistrationState, StacksNode as NeonNode}; use crate::run_loop::nakamoto::{Globals, RunLoop}; use crate::run_loop::RegisteredKey; -use crate::Keychain; pub mod miner; pub mod peer; diff --git a/testnet/stacks-node/src/nakamoto_node/miner.rs b/testnet/stacks-node/src/nakamoto_node/miner.rs index 82d48544b6..ea29833b4d 100644 --- a/testnet/stacks-node/src/nakamoto_node/miner.rs +++ b/testnet/stacks-node/src/nakamoto_node/miner.rs @@ -46,15 +46,14 @@ use stacks_common::types::chainstate::{StacksAddress, StacksBlockId}; use stacks_common::types::{PrivateKey, StacksEpochId}; use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum}; use stacks_common::util::vrf::VRFProof; -use stacks_node::config::Config; use wsts::curve::point::Point; use super::relayer::RelayerThread; -use super::{Error as NakamotoNodeError, Keychain}; +use super::{Config, Error as NakamotoNodeError, EventDispatcher, Keychain}; use crate::nakamoto_node::VRF_MOCK_MINER_KEY; use crate::run_loop::nakamoto::Globals; use crate::run_loop::RegisteredKey; -use crate::{neon_node, ChainTip, EventDispatcher}; +use crate::{neon_node, ChainTip}; /// If the miner was interrupted while mining a block, how long should the /// miner thread sleep before trying again? diff --git a/testnet/stacks-node/src/nakamoto_node/peer.rs b/testnet/stacks-node/src/nakamoto_node/peer.rs index 0bd73da5fc..eeb6789d30 100644 --- a/testnet/stacks-node/src/nakamoto_node/peer.rs +++ b/testnet/stacks-node/src/nakamoto_node/peer.rs @@ -31,13 +31,12 @@ use stacks::net::dns::{DNSClient, DNSResolver}; use stacks::net::p2p::PeerNetwork; use stacks::net::RPCHandlerArgs; use stacks_common::util::hash::Sha256Sum; -use stacks_node::config::Config; use crate::burnchains::make_bitcoin_indexer; use crate::nakamoto_node::relayer::RelayerDirective; use crate::neon_node::open_chainstate_with_faults; use crate::run_loop::nakamoto::{Globals, RunLoop}; -use crate::EventDispatcher; +use crate::{Config, EventDispatcher}; /// Thread that runs the network state machine, handling both p2p and http requests. pub struct PeerThread { diff --git a/testnet/stacks-node/src/nakamoto_node/relayer.rs b/testnet/stacks-node/src/nakamoto_node/relayer.rs index 09d661f775..1ee3135c24 100644 --- a/testnet/stacks-node/src/nakamoto_node/relayer.rs +++ b/testnet/stacks-node/src/nakamoto_node/relayer.rs @@ -50,9 +50,11 @@ use stacks_common::types::StacksEpochId; use stacks_common::util::get_epoch_time_ms; use stacks_common::util::hash::Hash160; use stacks_common::util::vrf::{VRFProof, VRFPublicKey}; -use stacks_node::config::Config; -use super::{BlockCommits, Error as NakamotoNodeError, Keychain, BLOCK_PROCESSOR_STACK_SIZE}; +use super::{ + BlockCommits, Config, Error as NakamotoNodeError, EventDispatcher, Keychain, + BLOCK_PROCESSOR_STACK_SIZE, +}; use crate::burnchains::BurnchainController; use crate::nakamoto_node::miner::{BlockMinerThread, MinerDirective}; use crate::neon_node::{ @@ -60,7 +62,7 @@ use crate::neon_node::{ }; use crate::run_loop::nakamoto::{Globals, RunLoop}; use crate::run_loop::RegisteredKey; -use crate::{BitcoinRegtestController, EventDispatcher}; +use crate::BitcoinRegtestController; /// Command types for the Nakamoto relayer thread, issued to it by other threads pub enum RelayerDirective { diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 7bd69b028e..49064d4971 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -201,14 +201,13 @@ use stacks_common::util::hash::{to_hex, Hash160, Sha256Sum}; use stacks_common::util::secp256k1::Secp256k1PrivateKey; use stacks_common::util::vrf::{VRFProof, VRFPublicKey}; use stacks_common::util::{get_epoch_time_ms, get_epoch_time_secs}; -use stacks_node::chain_data::MinerStats; -use stacks_node::config::Config; -use super::{BurnchainController, EventDispatcher, Keychain}; +use super::{BurnchainController, Config, EventDispatcher, Keychain}; use crate::burnchains::bitcoin_regtest_controller::{ addr2str, BitcoinRegtestController, OngoingBlockCommit, }; use crate::burnchains::make_bitcoin_indexer; +use crate::chain_data::MinerStats; use crate::globals::{NeonGlobals as Globals, RelayerDirective}; use crate::run_loop::neon::RunLoop; use crate::run_loop::RegisteredKey; diff --git a/testnet/stacks-node/src/node.rs b/testnet/stacks-node/src/node.rs index b004f9de3f..90c2123079 100644 --- a/testnet/stacks-node/src/node.rs +++ b/testnet/stacks-node/src/node.rs @@ -44,9 +44,8 @@ use stacks_common::util::get_epoch_time_secs; use stacks_common::util::hash::Sha256Sum; use stacks_common::util::secp256k1::Secp256k1PrivateKey; use stacks_common::util::vrf::VRFPublicKey; -use stacks_node::config::Config; -use super::{BurnchainController, BurnchainTip, EventDispatcher, Keychain, Tenure}; +use super::{BurnchainController, BurnchainTip, Config, EventDispatcher, Keychain, Tenure}; use crate::burnchains::make_bitcoin_indexer; use crate::genesis_data::USE_TEST_GENESIS_CHAINSTATE; use crate::run_loop; diff --git a/testnet/stacks-node/src/run_loop/boot_nakamoto.rs b/testnet/stacks-node/src/run_loop/boot_nakamoto.rs index c6116de495..dec1ca757f 100644 --- a/testnet/stacks-node/src/run_loop/boot_nakamoto.rs +++ b/testnet/stacks-node/src/run_loop/boot_nakamoto.rs @@ -24,11 +24,11 @@ use stacks::chainstate::burn::db::sortdb::SortitionDB; use stacks::chainstate::coordinator::comm::CoordinatorChannels; use stacks::core::StacksEpochExtension; use stacks_common::types::{StacksEpoch, StacksEpochId}; -use stacks_node::config::Config; use crate::neon::Counters; use crate::run_loop::nakamoto::RunLoop as NakaRunLoop; use crate::run_loop::neon::RunLoop as NeonRunLoop; +use crate::Config; /// This runloop handles booting to Nakamoto: /// During epochs [1.0, 2.5], it runs a neon run_loop. diff --git a/testnet/stacks-node/src/run_loop/helium.rs b/testnet/stacks-node/src/run_loop/helium.rs index 53e876d2ea..c7212d4132 100644 --- a/testnet/stacks-node/src/run_loop/helium.rs +++ b/testnet/stacks-node/src/run_loop/helium.rs @@ -1,10 +1,11 @@ use stacks::chainstate::stacks::db::ClarityTx; use stacks_common::types::chainstate::BurnchainHeaderHash; -use stacks_node::config::Config; use super::RunLoopCallbacks; use crate::burnchains::Error as BurnchainControllerError; -use crate::{BitcoinRegtestController, BurnchainController, ChainTip, MocknetController, Node}; +use crate::{ + BitcoinRegtestController, BurnchainController, ChainTip, Config, MocknetController, Node, +}; /// RunLoop is coordinating a simulated burnchain and some simulated nodes /// taking turns in producing blocks. diff --git a/testnet/stacks-node/src/run_loop/nakamoto.rs b/testnet/stacks-node/src/run_loop/nakamoto.rs index 8e64cb75a8..0b3702a994 100644 --- a/testnet/stacks-node/src/run_loop/nakamoto.rs +++ b/testnet/stacks-node/src/run_loop/nakamoto.rs @@ -33,7 +33,6 @@ use stacks::core::StacksEpochId; use stacks::net::atlas::{AtlasConfig, AtlasDB, Attachment}; use stacks_common::types::PublicKey; use stacks_common::util::hash::Hash160; -use stacks_node::config::Config; use stx_genesis::GenesisData; use crate::burnchains::make_bitcoin_indexer; @@ -47,7 +46,9 @@ use crate::node::{ use crate::run_loop::neon; use crate::run_loop::neon::Counters; use crate::syncctl::{PoxSyncWatchdog, PoxSyncWatchdogComms}; -use crate::{run_loop, BitcoinRegtestController, BurnchainController, EventDispatcher, Keychain}; +use crate::{ + run_loop, BitcoinRegtestController, BurnchainController, Config, EventDispatcher, Keychain, +}; pub const STDERR: i32 = 2; pub type Globals = GenericGlobals; diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index e65ee90ecf..86235ec3bd 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -27,7 +27,6 @@ use stacks_common::deps_common::ctrlc::SignalId; use stacks_common::types::PublicKey; use stacks_common::util::hash::Hash160; use stacks_common::util::{get_epoch_time_secs, sleep_ms}; -use stacks_node::config::Config; use stx_genesis::GenesisData; use super::RunLoopCallbacks; @@ -40,7 +39,9 @@ use crate::node::{ use_test_genesis_chainstate, }; use crate::syncctl::{PoxSyncWatchdog, PoxSyncWatchdogComms}; -use crate::{run_loop, BitcoinRegtestController, BurnchainController, EventDispatcher, Keychain}; +use crate::{ + run_loop, BitcoinRegtestController, BurnchainController, Config, EventDispatcher, Keychain, +}; pub const STDERR: i32 = 2; diff --git a/testnet/stacks-node/src/syncctl.rs b/testnet/stacks-node/src/syncctl.rs index 92915e8ed8..ff68126a83 100644 --- a/testnet/stacks-node/src/syncctl.rs +++ b/testnet/stacks-node/src/syncctl.rs @@ -5,9 +5,9 @@ use std::sync::Arc; use stacks::burnchains::{Burnchain, Error as burnchain_error}; use stacks::chainstate::stacks::db::StacksChainState; use stacks_common::util::{get_epoch_time_secs, sleep_ms}; -use stacks_node::config::Config; use crate::burnchains::BurnchainTip; +use crate::Config; // amount of time to wait for an inv or download sync to complete. // These _really should_ complete before the PoX sync watchdog permits processing the next reward diff --git a/testnet/stacks-node/src/tenure.rs b/testnet/stacks-node/src/tenure.rs index 985489acec..882a65d06b 100644 --- a/testnet/stacks-node/src/tenure.rs +++ b/testnet/stacks-node/src/tenure.rs @@ -16,11 +16,10 @@ use stacks::core::mempool::MemPoolDB; use stacks_common::types::chainstate::VRFSeed; use stacks_common::util::hash::Hash160; use stacks_common::util::vrf::VRFProof; -use stacks_node::config::Config; /// Only used by the Helium (Mocknet) node use super::node::ChainTip; -use super::BurnchainTip; +use super::{BurnchainTip, Config}; pub struct TenureArtifacts { pub anchored_block: StacksBlock, diff --git a/testnet/stacks-node/src/tests/bitcoin_regtest.rs b/testnet/stacks-node/src/tests/bitcoin_regtest.rs index 4f0bfe603b..6391dd9b2a 100644 --- a/testnet/stacks-node/src/tests/bitcoin_regtest.rs +++ b/testnet/stacks-node/src/tests/bitcoin_regtest.rs @@ -9,9 +9,9 @@ use stacks::chainstate::burn::operations::BlockstackOperationType::{ use stacks::chainstate::stacks::StacksPrivateKey; use stacks::core::StacksEpochId; use stacks_common::util::hash::hex_bytes; -use stacks_node::config::InitialBalance; use super::PUBLISH_CONTRACT; +use crate::config::InitialBalance; use crate::helium::RunLoop; use crate::tests::to_addr; use crate::Config; diff --git a/testnet/stacks-node/src/tests/epoch_205.rs b/testnet/stacks-node/src/tests/epoch_205.rs index 1fe0e50994..0f689f00ef 100644 --- a/testnet/stacks-node/src/tests/epoch_205.rs +++ b/testnet/stacks-node/src/tests/epoch_205.rs @@ -24,9 +24,8 @@ use stacks_common::types::chainstate::{ }; use stacks_common::util::hash::hex_bytes; use stacks_common::util::sleep_ms; -use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; -use stacks_node::utils::{get_account, submit_tx}; +use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; use crate::tests::neon_integrations::*; use crate::tests::{ diff --git a/testnet/stacks-node/src/tests/epoch_21.rs b/testnet/stacks-node/src/tests/epoch_21.rs index 4fdfbcc8ac..e26468a254 100644 --- a/testnet/stacks-node/src/tests/epoch_21.rs +++ b/testnet/stacks-node/src/tests/epoch_21.rs @@ -33,10 +33,9 @@ use stacks_common::types::PrivateKey; use stacks_common::util::hash::{Hash160, Sha256Sum}; use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; use stacks_common::util::sleep_ms; -use stacks_node::config::{Config, EventKeyType, EventObserverConfig, InitialBalance}; -use stacks_node::utils::{get_account, submit_tx}; use crate::burnchains::bitcoin_regtest_controller::UTXO; +use crate::config::{Config, EventKeyType, EventObserverConfig, InitialBalance}; use crate::neon::RunLoopCounter; use crate::operations::BurnchainOpSigner; use crate::stacks_common::address::AddressHashMode; diff --git a/testnet/stacks-node/src/tests/epoch_22.rs b/testnet/stacks-node/src/tests/epoch_22.rs index 3c9acdc70e..5c58b26ded 100644 --- a/testnet/stacks-node/src/tests/epoch_22.rs +++ b/testnet/stacks-node/src/tests/epoch_22.rs @@ -16,9 +16,9 @@ use stacks_common::types::PrivateKey; use stacks_common::util::hash::Hash160; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; -use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; -use stacks_node::utils::{get_account, submit_tx}; +use super::neon_integrations::get_account; +use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::neon_node::StacksNode; use crate::stacks_common::types::Address; use crate::stacks_common::util::hash::bytes_to_hex; diff --git a/testnet/stacks-node/src/tests/epoch_23.rs b/testnet/stacks-node/src/tests/epoch_23.rs index 32c0b10b43..740785e182 100644 --- a/testnet/stacks-node/src/tests/epoch_23.rs +++ b/testnet/stacks-node/src/tests/epoch_23.rs @@ -21,9 +21,8 @@ use stacks::burnchains::{Burnchain, PoxConstants}; use stacks::core; use stacks::core::STACKS_EPOCH_MAX; use stacks_common::util::sleep_ms; -use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; -use stacks_node::utils::submit_tx; +use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; use crate::tests::neon_integrations::*; use crate::tests::*; diff --git a/testnet/stacks-node/src/tests/epoch_24.rs b/testnet/stacks-node/src/tests/epoch_24.rs index d1f8b42089..b88441838a 100644 --- a/testnet/stacks-node/src/tests/epoch_24.rs +++ b/testnet/stacks-node/src/tests/epoch_24.rs @@ -35,13 +35,12 @@ use stacks_common::types::Address; use stacks_common::util::hash::{bytes_to_hex, hex_bytes, Hash160}; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks_common::util::sleep_ms; -use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; -use stacks_node::utils::{get_account, submit_tx}; +use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::bitcoin_regtest::BitcoinCoreController; use crate::tests::neon_integrations::{ - get_chain_info, get_pox_info, neon_integration_test_conf, next_block_and_wait, test_observer, - wait_for_runloop, + get_account, get_chain_info, get_pox_info, neon_integration_test_conf, next_block_and_wait, + submit_tx, test_observer, wait_for_runloop, }; use crate::tests::{make_contract_call, to_addr}; use crate::{neon, BitcoinRegtestController, BurnchainController}; diff --git a/testnet/stacks-node/src/tests/integrations.rs b/testnet/stacks-node/src/tests/integrations.rs index e798681a16..2bb9bd891e 100644 --- a/testnet/stacks-node/src/tests/integrations.rs +++ b/testnet/stacks-node/src/tests/integrations.rs @@ -34,12 +34,12 @@ use stacks::net::api::getistraitimplemented::GetIsTraitImplementedResponse; use stacks_common::codec::StacksMessageCodec; use stacks_common::types::chainstate::{StacksAddress, StacksBlockId, VRFSeed}; use stacks_common::util::hash::{hex_bytes, to_hex, Sha256Sum}; -use stacks_node::config::InitialBalance; use super::{ make_contract_call, make_contract_publish, make_stacks_transfer, to_addr, ADDR_4, SK_1, SK_2, SK_3, }; +use crate::config::InitialBalance; use crate::helium::RunLoop; use crate::tests::make_sponsored_stacks_transfer_on_testnet; diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index d6d087b2dd..1c4a5c4015 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -13,22 +13,23 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::collections::HashMap; +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 blind_signer::blind_signer; use clarity::vm::ast::ASTRules; use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; 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; @@ -44,7 +45,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; +use stacks::libstackerdb::{SlotMetadata, StackerDBChunkData}; use stacks::net::api::callreadonly::CallReadOnlyRequestBody; use stacks::net::api::getstackers::GetStackersResponse; use stacks::net::api::postblock_proposal::{ @@ -60,17 +61,16 @@ 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::hash::{to_hex, Sha512Trunc256Sum}; use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey}; -use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; -use stacks_node::utils::{get_account, submit_tx}; use super::bitcoin_regtest::BitcoinCoreController; -use crate::neon::Counters; +use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; +use crate::neon::{Counters, RunLoopCounter}; use crate::run_loop::boot_nakamoto; use crate::tests::neon_integrations::{ - get_chain_info_result, get_pox_info, next_block_and_wait, run_until_burnchain_height, - test_observer, wait_for_runloop, + get_account, get_chain_info_result, get_pox_info, next_block_and_wait, + run_until_burnchain_height, submit_tx, test_observer, wait_for_runloop, }; use crate::tests::{make_stacks_transfer, to_addr}; use crate::{tests, BitcoinRegtestController, BurnchainController, Config, ConfigFile, Keychain}; @@ -203,6 +203,120 @@ 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, +) -> Result { + 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 rpc_sock = conf + .node + .rpc_bind + .clone() + .parse() + .expect("Failed to parse socket"); + + let mut proposed_block: NakamotoBlock = { + let miner_contract_id = boot_code_id(MINERS_NAME, false); + let mut miners_stackerdb = StackerDBSession::new(rpc_sock, 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(rpc_sock, 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(); @@ -575,6 +689,70 @@ fn is_key_set_for_cycle( .map_err(|_| "Response is not optional".to_string()) } +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, +) { + // 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), + ], + ); + 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 @@ -732,6 +910,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(); @@ -782,7 +961,7 @@ fn simple_neon_integration() { } info!("Nakamoto miner started..."); - blind_signer(&naka_conf, &signers, &sender_signer_sk); + 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, || { @@ -807,6 +986,13 @@ fn simple_neon_integration() { &commits_submitted, ) .unwrap(); + + signer_vote_if_needed( + &btc_regtest_controller, + &naka_conf, + &[sender_signer_sk], + &signers, + ); } // Submit a TX @@ -842,6 +1028,13 @@ fn simple_neon_integration() { &commits_submitted, ) .unwrap(); + + 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 @@ -958,6 +1151,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(); @@ -996,7 +1190,7 @@ fn mine_multiple_per_tenure_integration() { .stacks_block_height; info!("Nakamoto miner started..."); - blind_signer(&naka_conf, &signers, &sender_signer_sk); + 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, || { @@ -1151,6 +1345,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(); @@ -1261,7 +1456,33 @@ fn correct_burn_outs() { }) .unwrap(); - blind_signer(&naka_conf, &signers, &sender_signer_sk); + 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, + ); + + // Run until the prepare phase + run_until_burnchain_height( + &mut btc_regtest_controller, + &blocks_processed, + prepare_phase_start, + &naka_conf, + ); + + signer_vote_if_needed( + &btc_regtest_controller, + &naka_conf, + &[sender_signer_sk], + &signers, + ); run_until_burnchain_height( &mut btc_regtest_controller, @@ -1271,6 +1492,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(); @@ -1331,6 +1553,13 @@ fn correct_burn_outs() { tip_sn.block_height > prior_tip, "The new burnchain tip must have been processed" ); + + signer_vote_if_needed( + &btc_regtest_controller, + &naka_conf, + &[sender_signer_sk], + &signers, + ); } coord_channel @@ -1410,6 +1639,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(); @@ -1427,7 +1657,7 @@ fn block_proposal_api_endpoint() { ); info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); - blind_signer(&conf, &signers, &sender_signer_sk); + blind_signer(&conf, &signers, &sender_signer_sk, proposals_submitted); let burnchain = conf.get_burnchain(); let sortdb = burnchain.open_sortition_db(true).unwrap(); @@ -1760,6 +1990,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(); @@ -1777,7 +2008,7 @@ fn miner_writes_proposed_block_to_stackerdb() { ); info!("Nakamoto miner started..."); - blind_signer(&naka_conf, &signers, &sender_signer_sk); + 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); diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index 0609bb479d..cd0c96358e 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -61,8 +61,6 @@ use stacks_common::types::chainstate::{ use stacks_common::util::hash::{bytes_to_hex, hex_bytes, to_hex, Hash160}; use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; use stacks_common::util::{get_epoch_time_ms, get_epoch_time_secs, sleep_ms}; -use stacks_node::config::{EventKeyType, EventObserverConfig, FeeEstimatorName, InitialBalance}; -use stacks_node::utils::{get_account, submit_tx}; use super::bitcoin_regtest::BitcoinCoreController; use super::{ @@ -71,6 +69,7 @@ use super::{ SK_2, SK_3, }; use crate::burnchains::bitcoin_regtest_controller::{self, BitcoinRPCRequest, UTXO}; +use crate::config::{EventKeyType, EventObserverConfig, FeeEstimatorName, InitialBalance}; use crate::neon_node::RelayerThread; use crate::operations::BurnchainOpSigner; use crate::stacks_common::types::PrivateKey; @@ -698,6 +697,32 @@ pub fn wait_for_microblocks(microblocks_processed: &Arc, timeout: u64 return true; } +/// returns Txid string +pub fn submit_tx(http_origin: &str, tx: &Vec) -> String { + let client = reqwest::blocking::Client::new(); + let path = format!("{}/v2/transactions", http_origin); + let res = client + .post(&path) + .header("Content-Type", "application/octet-stream") + .body(tx.clone()) + .send() + .unwrap(); + if res.status().is_success() { + let res: String = res.json().unwrap(); + assert_eq!( + res, + StacksTransaction::consensus_deserialize(&mut &tx[..]) + .unwrap() + .txid() + .to_string() + ); + return res; + } else { + eprintln!("Submit tx error: {}", res.text().unwrap()); + panic!(""); + } +} + pub fn get_unconfirmed_tx(http_origin: &str, txid: &Txid) -> Option { let client = reqwest::blocking::Client::new(); let path = format!("{}/v2/transactions/unconfirmed/{}", http_origin, txid); @@ -1171,6 +1196,30 @@ pub fn get_balance(http_origin: &str, account: &F) -> u128 get_account(http_origin, account).balance } +#[derive(Debug)] +pub struct Account { + pub balance: u128, + pub locked: u128, + pub nonce: u64, +} + +pub fn get_account(http_origin: &str, account: &F) -> Account { + let client = reqwest::blocking::Client::new(); + let path = format!("{}/v2/accounts/{}?proof=0", http_origin, account); + let res = client + .get(&path) + .send() + .unwrap() + .json::() + .unwrap(); + info!("Account response: {:#?}", res); + Account { + balance: u128::from_str_radix(&res.balance[2..], 16).unwrap(), + locked: u128::from_str_radix(&res.locked[2..], 16).unwrap(), + nonce: res.nonce, + } +} + pub fn get_pox_info(http_origin: &str) -> Option { let client = reqwest::blocking::Client::new(); let path = format!("{}/v2/pox", http_origin); diff --git a/testnet/stacks-node/src/tests/signer.rs b/testnet/stacks-node/src/tests/signer.rs index 353890aa09..1330b4879b 100644 --- a/testnet/stacks-node/src/tests/signer.rs +++ b/testnet/stacks-node/src/tests/signer.rs @@ -39,9 +39,6 @@ use stacks_common::types::chainstate::{ use stacks_common::types::StacksEpochId; use stacks_common::util::hash::{MerkleTree, Sha512Trunc256Sum}; use stacks_common::util::secp256k1::MessageSignature; -use stacks_node::config::{ - Config as NeonConfig, EventKeyType, EventObserverConfig, InitialBalance, -}; use stacks_signer::client::{StackerDB, StacksClient}; use stacks_signer::config::{build_signer_config_tomls, GlobalConfig as SignerConfig, Network}; use stacks_signer::runloop::RunLoopCommand; @@ -55,6 +52,7 @@ use wsts::curve::scalar::Scalar; use wsts::state_machine::OperationResult; use wsts::taproot::SchnorrProof; +use crate::config::{Config as NeonConfig, EventKeyType, EventObserverConfig, InitialBalance}; use crate::event_dispatcher::MinedNakamotoBlockEvent; use crate::neon::Counters; use crate::run_loop::boot_nakamoto; @@ -105,7 +103,7 @@ impl SignerTest { .map(|_| StacksPrivateKey::new()) .collect::>(); - let (naka_conf, _miner_account) = naka_neon_integration_conf(None); + let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); // Setup the signer and coordinator configurations let signer_configs = build_signer_config_tomls( diff --git a/testnet/stacks-node/src/tests/stackerdb.rs b/testnet/stacks-node/src/tests/stackerdb.rs index 66eeee04ce..e24b5c5c24 100644 --- a/testnet/stacks-node/src/tests/stackerdb.rs +++ b/testnet/stacks-node/src/tests/stackerdb.rs @@ -21,14 +21,13 @@ use stacks::chainstate::stacks::StacksPrivateKey; use stacks::libstackerdb::{StackerDBChunkAckData, StackerDBChunkData}; use stacks_common::types::chainstate::StacksAddress; use stacks_common::util::hash::Sha512Trunc256Sum; -use stacks_node::config::{EventKeyType, EventObserverConfig, InitialBalance}; -use stacks_node::utils::submit_tx; use {reqwest, serde_json}; use super::bitcoin_regtest::BitcoinCoreController; use crate::burnchains::BurnchainController; +use crate::config::{EventKeyType, EventObserverConfig, InitialBalance}; use crate::tests::neon_integrations::{ - neon_integration_test_conf, next_block_and_wait, test_observer, wait_for_runloop, + neon_integration_test_conf, next_block_and_wait, submit_tx, test_observer, wait_for_runloop, }; use crate::tests::{make_contract_publish, to_addr}; use crate::{neon, BitcoinRegtestController}; diff --git a/testnet/stacks-node/src/utils.rs b/testnet/stacks-node/src/utils.rs deleted file mode 100644 index 3a01415c40..0000000000 --- a/testnet/stacks-node/src/utils.rs +++ /dev/null @@ -1,195 +0,0 @@ -use clarity::vm::{ClarityName, ContractName, Value}; -use stacks::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG}; -use stacks::chainstate::stacks::{ - StacksTransaction, StacksTransactionSigner, TransactionAnchorMode, TransactionAuth, - TransactionContractCall, TransactionPayload, TransactionPostConditionMode, - TransactionSpendingCondition, TransactionVersion, -}; -use stacks::codec::StacksMessageCodec; -use stacks::core::CHAIN_ID_TESTNET; -use stacks::net::api::getaccount::AccountEntryResponse; -use stacks::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey}; - -#[derive(Debug)] -pub struct Account { - pub balance: u128, - pub locked: u128, - pub nonce: u64, -} - -pub fn get_account(http_origin: &str, account: &F) -> Account { - let client = reqwest::blocking::Client::new(); - let path = format!("{}/v2/accounts/{}?proof=0", http_origin, account); - let res = client - .get(&path) - .send() - .unwrap() - .json::() - .unwrap(); - info!("Account response: {:#?}", res); - Account { - balance: u128::from_str_radix(&res.balance[2..], 16).unwrap(), - locked: u128::from_str_radix(&res.locked[2..], 16).unwrap(), - nonce: res.nonce, - } -} - -pub fn to_addr(sk: &StacksPrivateKey) -> StacksAddress { - StacksAddress::from_public_keys( - C32_ADDRESS_VERSION_TESTNET_SINGLESIG, - &AddressHashMode::SerializeP2PKH, - 1, - &vec![StacksPublicKey::from_private(sk)], - ) - .unwrap() -} - -pub fn serialize_sign_standard_single_sig_tx( - payload: TransactionPayload, - sender: &StacksPrivateKey, - nonce: u64, - tx_fee: u64, -) -> Vec { - serialize_sign_standard_single_sig_tx_anchor_mode( - payload, - sender, - nonce, - tx_fee, - TransactionAnchorMode::OnChainOnly, - ) -} - -pub fn serialize_sign_standard_single_sig_tx_anchor_mode( - payload: TransactionPayload, - sender: &StacksPrivateKey, - nonce: u64, - tx_fee: u64, - anchor_mode: TransactionAnchorMode, -) -> Vec { - serialize_sign_standard_single_sig_tx_anchor_mode_version( - payload, - sender, - nonce, - tx_fee, - anchor_mode, - TransactionVersion::Testnet, - ) -} - -pub fn serialize_sign_standard_single_sig_tx_anchor_mode_version( - payload: TransactionPayload, - sender: &StacksPrivateKey, - nonce: u64, - tx_fee: u64, - anchor_mode: TransactionAnchorMode, - version: TransactionVersion, -) -> Vec { - serialize_sign_tx_anchor_mode_version( - payload, - sender, - None, - nonce, - None, - tx_fee, - anchor_mode, - version, - ) -} - -pub fn serialize_sign_tx_anchor_mode_version( - payload: TransactionPayload, - sender: &StacksPrivateKey, - payer: Option<&StacksPrivateKey>, - sender_nonce: u64, - payer_nonce: Option, - tx_fee: u64, - anchor_mode: TransactionAnchorMode, - version: TransactionVersion, -) -> Vec { - let mut sender_spending_condition = - TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private(sender)) - .expect("Failed to create p2pkh spending condition from public key."); - sender_spending_condition.set_nonce(sender_nonce); - - let auth = match (payer, payer_nonce) { - (Some(payer), Some(payer_nonce)) => { - let mut payer_spending_condition = TransactionSpendingCondition::new_singlesig_p2pkh( - StacksPublicKey::from_private(payer), - ) - .expect("Failed to create p2pkh spending condition from public key."); - payer_spending_condition.set_nonce(payer_nonce); - payer_spending_condition.set_tx_fee(tx_fee); - TransactionAuth::Sponsored(sender_spending_condition, payer_spending_condition) - } - _ => { - sender_spending_condition.set_tx_fee(tx_fee); - TransactionAuth::Standard(sender_spending_condition) - } - }; - let mut unsigned_tx = StacksTransaction::new(version, auth, payload); - unsigned_tx.anchor_mode = anchor_mode; - unsigned_tx.post_condition_mode = TransactionPostConditionMode::Allow; - unsigned_tx.chain_id = CHAIN_ID_TESTNET; - - let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx); - tx_signer.sign_origin(sender).unwrap(); - if let (Some(payer), Some(_)) = (payer, payer_nonce) { - tx_signer.sign_sponsor(payer).unwrap(); - } - - let mut buf = vec![]; - tx_signer - .get_tx() - .unwrap() - .consensus_serialize(&mut buf) - .unwrap(); - buf -} - -pub fn make_contract_call( - sender: &StacksPrivateKey, - nonce: u64, - tx_fee: u64, - contract_addr: &StacksAddress, - contract_name: &str, - function_name: &str, - function_args: &[Value], -) -> Vec { - let contract_name = ContractName::from(contract_name); - let function_name = ClarityName::from(function_name); - - let payload = TransactionContractCall { - address: contract_addr.clone(), - contract_name, - function_name, - function_args: function_args.iter().map(|x| x.clone()).collect(), - }; - - serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, tx_fee) -} - -/// returns Txid string -pub fn submit_tx(http_origin: &str, tx: &Vec) -> String { - let client = reqwest::blocking::Client::new(); - let path = format!("{}/v2/transactions", http_origin); - let res = client - .post(&path) - .header("Content-Type", "application/octet-stream") - .body(tx.clone()) - .send() - .unwrap(); - if res.status().is_success() { - let res: String = res.json().unwrap(); - assert_eq!( - res, - StacksTransaction::consensus_deserialize(&mut &tx[..]) - .unwrap() - .txid() - .to_string() - ); - return res; - } else { - eprintln!("Submit tx error: {}", res.text().unwrap()); - panic!(""); - } -} From c0d4c28c40b7515b3ecd3dbe4822a595d0460dde Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Tue, 5 Mar 2024 16:27:09 +0100 Subject: [PATCH 6/6] chore: fix merge conflict --- testnet/stacks-node/src/tests/nakamoto_integrations.rs | 10 ++-------- testnet/stacks-node/src/tests/signer.rs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 00ea8cfe70..ff0f088f3a 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -254,16 +254,10 @@ pub fn read_and_sign_block_proposal( let reward_cycle = burnchain .block_height_to_reward_cycle(tip.block_height) .unwrap(); - let rpc_sock = conf - .node - .rpc_bind - .clone() - .parse() - .expect("Failed to parse socket"); let mut 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(&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")? @@ -304,7 +298,7 @@ pub fn read_and_sign_block_proposal( 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(rpc_sock, signers_contract_id); + 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, diff --git a/testnet/stacks-node/src/tests/signer.rs b/testnet/stacks-node/src/tests/signer.rs index 9159d319ad..fc8d84aa96 100644 --- a/testnet/stacks-node/src/tests/signer.rs +++ b/testnet/stacks-node/src/tests/signer.rs @@ -103,7 +103,7 @@ impl SignerTest { .map(|_| StacksPrivateKey::new()) .collect::>(); - let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); + let (naka_conf, _miner_account) = naka_neon_integration_conf(None); // Setup the signer and coordinator configurations let signer_configs = build_signer_config_tomls(