Skip to content

Commit ef7cb90

Browse files
committed
CRC: move TestFlag related functions to seperate test modules
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 36f0ab5 commit ef7cb90

File tree

10 files changed

+257
-147
lines changed

10 files changed

+257
-147
lines changed

stacks-common/src/util/mod.rs

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
3636
use std::{error, fmt, thread, time};
3737

3838
#[cfg(any(test, feature = "testing"))]
39-
#[derive(Clone)]
40-
pub struct TestFlag<T>(pub std::sync::Arc<std::sync::Mutex<Option<T>>>);
41-
42-
#[cfg(any(test, feature = "testing"))]
43-
impl<T: Default + Clone> Default for TestFlag<T> {
44-
fn default() -> Self {
45-
Self(std::sync::Arc::new(std::sync::Mutex::new(None)))
46-
}
47-
}
48-
49-
#[cfg(any(test, feature = "testing"))]
50-
impl<T: Default + Clone> TestFlag<T> {
51-
/// Set the test flag to the given value
52-
pub fn set(&self, value: T) {
53-
*self.0.lock().unwrap() = Some(value);
54-
}
55-
56-
/// Get the test flag value. Defaults otherwise.
57-
pub fn get(&self) -> T {
58-
self.0.lock().unwrap().clone().unwrap_or_default().clone()
59-
}
60-
}
39+
pub mod tests;
6140

6241
pub fn get_epoch_time_secs() -> u64 {
6342
let start = SystemTime::now();

stacks-common/src/util/tests.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
use std::sync::{Arc, Mutex};
17+
/// `TestFlag` is a thread-safe utility designed for managing shared state in testing scenarios. It wraps
18+
/// a value of type `T` inside an `Arc<Mutex<Option<T>>>`, allowing you to set and retrieve a value
19+
/// across different parts of your codebase while ensuring thread safety.
20+
///
21+
/// This structure is particularly useful when:
22+
/// - You need a global or static variable in tests.
23+
/// - You want to control the execution of custom test code paths by setting and checking a shared value.
24+
///
25+
/// # Type Parameter
26+
/// - `T`: The type of the value managed by the `TestFlag`. It must implement the `Default` and `Clone` traits.
27+
///
28+
/// # Examples
29+
///
30+
/// ```rust
31+
/// use stacks_common::util::tests::TestFlag;
32+
/// use std::sync::{Arc, Mutex};
33+
///
34+
/// // Create a TestFlag instance
35+
/// let test_flag = TestFlag::default();
36+
///
37+
/// // Set a value in the test flag
38+
/// test_flag.set("test_value".to_string());
39+
///
40+
/// // Retrieve the value
41+
/// assert_eq!(test_flag.get(), "test_value".to_string());
42+
///
43+
/// // Reset the value to default
44+
/// test_flag.set("".to_string());
45+
/// assert_eq!(test_flag.get(), "".to_string());
46+
/// ```
47+
#[derive(Clone)]
48+
pub struct TestFlag<T>(pub Arc<Mutex<Option<T>>>);
49+
50+
impl<T: Default + Clone> Default for TestFlag<T> {
51+
fn default() -> Self {
52+
Self(Arc::new(Mutex::new(None)))
53+
}
54+
}
55+
56+
impl<T: Default + Clone> TestFlag<T> {
57+
/// Sets the value of the test flag.
58+
///
59+
/// This method updates the value stored inside the `TestFlag`, replacing any existing value.
60+
///
61+
/// # Arguments
62+
/// - `value`: The new value to set for the `TestFlag`.
63+
///
64+
/// # Examples
65+
///
66+
/// ```rust
67+
/// let test_flag = TestFlag::default();
68+
/// test_flag.set(42);
69+
/// assert_eq!(test_flag.get(), 42);
70+
/// ```
71+
pub fn set(&self, value: T) {
72+
*self.0.lock().unwrap() = Some(value);
73+
}
74+
75+
/// Retrieves the current value of the test flag.
76+
///
77+
/// If no value has been set, this method returns the default value for the type `T`.
78+
///
79+
/// # Returns
80+
/// - The current value of the test flag, or the default value of `T` if none has been set.
81+
///
82+
/// # Examples
83+
///
84+
/// ```rust
85+
/// let test_flag = TestFlag::default();
86+
///
87+
/// // Get the default value
88+
/// assert_eq!(test_flag.get(), 0); // For T = i32, default is 0
89+
///
90+
/// // Set a value
91+
/// test_flag.set(123);
92+
///
93+
/// // Get the updated value
94+
/// assert_eq!(test_flag.get(), 123);
95+
/// ```
96+
pub fn get(&self) -> T {
97+
self.0.lock().unwrap().clone().unwrap_or_default().clone()
98+
}
99+
}

stacks-signer/src/v0/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
/// The signer module for processing events
1818
pub mod signer;
1919

20+
#[cfg(any(test, feature = "testing"))]
21+
/// Test specific functions for the signer module
22+
pub mod tests;
23+
2024
use libsigner::v0::messages::SignerMessage;
2125

2226
use crate::v0::signer::Signer;

stacks-signer/src/v0/signer.rs

Lines changed: 7 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
use std::collections::HashMap;
1616
use std::fmt::Debug;
1717
use std::sync::mpsc::Sender;
18-
#[cfg(any(test, feature = "testing"))]
19-
use std::sync::LazyLock;
2018
use std::time::{Duration, Instant};
2119

2220
use blockstack_lib::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader};
@@ -35,12 +33,8 @@ use libsigner::v0::messages::{
3533
use libsigner::{BlockProposal, SignerEvent};
3634
use slog::{slog_debug, slog_error, slog_info, slog_warn};
3735
use stacks_common::types::chainstate::StacksAddress;
38-
#[cfg(any(test, feature = "testing"))]
39-
use stacks_common::types::chainstate::StacksPublicKey;
4036
use stacks_common::util::get_epoch_time_secs;
4137
use stacks_common::util::secp256k1::MessageSignature;
42-
#[cfg(any(test, feature = "testing"))]
43-
use stacks_common::util::TestFlag;
4438
use stacks_common::{debug, error, info, warn};
4539

4640
use crate::chainstate::{ProposalEvalConfig, SortitionsView};
@@ -50,27 +44,13 @@ use crate::runloop::SignerResult;
5044
use crate::signerdb::{BlockInfo, BlockState, SignerDb};
5145
use crate::Signer as SignerTrait;
5246

53-
#[cfg(any(test, feature = "testing"))]
54-
/// A global variable that can be used to reject all block proposals if the signer's public key is in the provided list
55-
pub static TEST_REJECT_ALL_BLOCK_PROPOSAL: LazyLock<TestFlag<Vec<StacksPublicKey>>> =
56-
LazyLock::new(TestFlag::default);
57-
58-
#[cfg(any(test, feature = "testing"))]
59-
/// A global variable that can be used to ignore block proposals if the signer's public key is in the provided list
60-
pub static TEST_IGNORE_ALL_BLOCK_PROPOSALS: LazyLock<TestFlag<Vec<StacksPublicKey>>> =
61-
LazyLock::new(TestFlag::default);
62-
63-
#[cfg(any(test, feature = "testing"))]
64-
/// Pause the block broadcast
65-
pub static TEST_PAUSE_BLOCK_BROADCAST: LazyLock<TestFlag<bool>> = LazyLock::new(TestFlag::default);
66-
67-
#[cfg(any(test, feature = "testing"))]
68-
/// Skip broadcasting the block to the network
69-
pub static TEST_SKIP_BLOCK_BROADCAST: LazyLock<TestFlag<bool>> = LazyLock::new(TestFlag::default);
70-
7147
/// The stacks signer registered for the reward cycle
7248
#[derive(Debug)]
7349
pub struct Signer {
50+
/// The private key of the signer
51+
#[cfg(any(test, feature = "testing"))]
52+
pub private_key: StacksPrivateKey,
53+
#[cfg(not(any(test, feature = "testing")))]
7454
/// The private key of the signer
7555
private_key: StacksPrivateKey,
7656
/// The stackerdb client
@@ -175,20 +155,8 @@ impl SignerTrait<SignerMessage> for Signer {
175155
match message {
176156
SignerMessage::BlockProposal(block_proposal) => {
177157
#[cfg(any(test, feature = "testing"))]
178-
{
179-
let public_keys = TEST_IGNORE_ALL_BLOCK_PROPOSALS.get();
180-
if public_keys.contains(
181-
&stacks_common::types::chainstate::StacksPublicKey::from_private(
182-
&self.private_key,
183-
),
184-
) {
185-
warn!("{self}: Ignoring block proposal due to testing directive";
186-
"block_id" => %block_proposal.block.block_id(),
187-
"height" => block_proposal.block.header.chain_length,
188-
"consensus_hash" => %block_proposal.block.header.consensus_hash
189-
);
190-
continue;
191-
}
158+
if self.test_ignore_all_block_proposals(block_proposal) {
159+
continue;
192160
}
193161
self.handle_block_proposal(
194162
stacks_client,
@@ -1121,87 +1089,6 @@ impl Signer {
11211089
}
11221090
}
11231091

1124-
#[cfg(any(test, feature = "testing"))]
1125-
fn test_skip_block_broadcast(&self, block: &NakamotoBlock) -> bool {
1126-
if TEST_SKIP_BLOCK_BROADCAST.get() {
1127-
let block_hash = block.header.signer_signature_hash();
1128-
warn!(
1129-
"{self}: Skipping block broadcast due to testing directive";
1130-
"block_id" => %block.block_id(),
1131-
"height" => block.header.chain_length,
1132-
"consensus_hash" => %block.header.consensus_hash
1133-
);
1134-
1135-
if let Err(e) = self
1136-
.signer_db
1137-
.set_block_broadcasted(&block_hash, get_epoch_time_secs())
1138-
{
1139-
warn!("{self}: Failed to set block broadcasted for {block_hash}: {e:?}");
1140-
}
1141-
return true;
1142-
}
1143-
false
1144-
}
1145-
1146-
#[cfg(any(test, feature = "testing"))]
1147-
fn test_reject_block_proposal(
1148-
&mut self,
1149-
block_proposal: &BlockProposal,
1150-
block_info: &mut BlockInfo,
1151-
block_response: Option<BlockResponse>,
1152-
) -> Option<BlockResponse> {
1153-
let public_keys = TEST_REJECT_ALL_BLOCK_PROPOSAL.get();
1154-
if public_keys.contains(
1155-
&stacks_common::types::chainstate::StacksPublicKey::from_private(&self.private_key),
1156-
) {
1157-
warn!("{self}: Rejecting block proposal automatically due to testing directive";
1158-
"block_id" => %block_proposal.block.block_id(),
1159-
"height" => block_proposal.block.header.chain_length,
1160-
"consensus_hash" => %block_proposal.block.header.consensus_hash
1161-
);
1162-
if let Err(e) = block_info.mark_locally_rejected() {
1163-
warn!("{self}: Failed to mark block as locally rejected: {e:?}",);
1164-
};
1165-
// We must insert the block into the DB to prevent subsequent repeat proposals being accepted (should reject
1166-
// as invalid since we rejected in a prior round if this crops up again)
1167-
// in case this is the first time we saw this block. Safe to do since this is testing case only.
1168-
self.signer_db
1169-
.insert_block(block_info)
1170-
.unwrap_or_else(|e| self.handle_insert_block_error(e));
1171-
Some(BlockResponse::rejected(
1172-
block_proposal.block.header.signer_signature_hash(),
1173-
RejectCode::TestingDirective,
1174-
&self.private_key,
1175-
self.mainnet,
1176-
self.signer_db.calculate_tenure_extend_timestamp(
1177-
self.proposal_config.tenure_idle_timeout,
1178-
&block_proposal.block,
1179-
false,
1180-
),
1181-
))
1182-
} else {
1183-
block_response
1184-
}
1185-
}
1186-
1187-
#[cfg(any(test, feature = "testing"))]
1188-
fn test_pause_block_broadcast(&self, block_info: &BlockInfo) {
1189-
if TEST_PAUSE_BLOCK_BROADCAST.get() {
1190-
// Do an extra check just so we don't log EVERY time.
1191-
warn!("{self}: Block broadcast is stalled due to testing directive.";
1192-
"block_id" => %block_info.block.block_id(),
1193-
"height" => block_info.block.header.chain_length,
1194-
);
1195-
while TEST_PAUSE_BLOCK_BROADCAST.get() {
1196-
std::thread::sleep(std::time::Duration::from_millis(10));
1197-
}
1198-
info!("{self}: Block validation is no longer stalled due to testing directive.";
1199-
"block_id" => %block_info.block.block_id(),
1200-
"height" => block_info.block.header.chain_length,
1201-
);
1202-
}
1203-
}
1204-
12051092
/// Send a mock signature to stackerdb to prove we are still alive
12061093
fn mock_sign(&mut self, mock_proposal: MockProposal) {
12071094
info!("{self}: Mock signing mock proposal: {mock_proposal:?}");
@@ -1216,7 +1103,7 @@ impl Signer {
12161103
}
12171104

12181105
/// Helper for logging insert_block error
1219-
fn handle_insert_block_error(&self, e: DBError) {
1106+
pub fn handle_insert_block_error(&self, e: DBError) {
12201107
error!("{self}: Failed to insert block into signer-db: {e:?}");
12211108
panic!("{self} Failed to write block to signerdb: {e}");
12221109
}

0 commit comments

Comments
 (0)