Skip to content

Commit 9fd6728

Browse files
committed
bugfix: enforce bitcoin rpc client to be created only for miner node
1 parent 5934517 commit 9fd6728

File tree

1 file changed

+56
-20
lines changed

1 file changed

+56
-20
lines changed

stacks-node/src/burnchains/bitcoin_regtest_controller.rs

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub struct BitcoinRegtestController {
9393
burnchain_config: Option<Burnchain>,
9494
ongoing_block_commit: Option<OngoingBlockCommit>,
9595
should_keep_running: Option<Arc<AtomicBool>>,
96-
rpc_client: BitcoinRpcClient,
96+
rpc_client: Option<BitcoinRpcClient>,
9797
}
9898

9999
#[derive(Clone)]
@@ -371,8 +371,7 @@ impl BitcoinRegtestController {
371371
should_keep_running: should_keep_running.clone(),
372372
};
373373

374-
let rpc_client = BitcoinRpcClient::from_stx_config(&config)
375-
.expect("unable to instantiate the RPC client!");
374+
let rpc_client = Self::try_create_rpc_client(&config);
376375

377376
Self {
378377
use_coordinator: coordinator_channel,
@@ -421,8 +420,7 @@ impl BitcoinRegtestController {
421420
should_keep_running: None,
422421
};
423422

424-
let rpc_client = BitcoinRpcClient::from_stx_config(&config)
425-
.expect("unable to instantiate the RPC client!");
423+
let rpc_client = Self::try_create_rpc_client(&config);
426424

427425
Self {
428426
use_coordinator: None,
@@ -477,6 +475,36 @@ impl BitcoinRegtestController {
477475
}
478476
}
479477

478+
/// Attempt to create a new [`BitcoinRpcClient`] from the given [`Config`].
479+
///
480+
/// If the provided config indicates that the node is a **miner**,
481+
/// tries to instantiate it or **panics** otherwise.
482+
/// If the node is **not** a miner, returns None (e.g. follower node).
483+
fn try_create_rpc_client(config: &Config) -> Option<BitcoinRpcClient> {
484+
if config.node.miner {
485+
Some(
486+
BitcoinRpcClient::from_stx_config(&config)
487+
.expect("unable to instantiate the RPC client!"),
488+
)
489+
} else {
490+
None
491+
}
492+
}
493+
494+
/// Attempt to get a reference to the underlying [`BitcoinRpcClient`].
495+
///
496+
/// This function will panic if the RPC client has not been configured
497+
/// (i.e. [`Self::try_create_rpc_client`] returned `None` during initialization),
498+
/// but an attempt is made to use it anyway.
499+
///
500+
/// In practice, this means the node is expected to act as a miner,
501+
/// yet no [`BitcoinRpcClient`] was created or properly configured.
502+
fn try_get_rpc_client(&self) -> &BitcoinRpcClient {
503+
self.rpc_client
504+
.as_ref()
505+
.expect("BUG: BitcoinRpcClient is required, but it has not been configured properly!")
506+
}
507+
480508
/// Helium (devnet) blocks receiver. Returns the new burnchain tip.
481509
fn receive_blocks_helium(&mut self) -> BurnchainTip {
482510
let mut burnchain = self.get_burnchain();
@@ -686,7 +714,7 @@ impl BitcoinRegtestController {
686714

687715
/// Retrieve all loaded wallets.
688716
pub fn list_wallets(&self) -> BitcoinRegtestControllerResult<Vec<String>> {
689-
Ok(self.rpc_client.list_wallets()?)
717+
Ok(self.try_get_rpc_client().list_wallets()?)
690718
}
691719

692720
/// Checks if the config-supplied wallet exists.
@@ -695,7 +723,8 @@ impl BitcoinRegtestController {
695723
let wallets = self.list_wallets()?;
696724
let wallet = self.get_wallet_name();
697725
if !wallets.contains(wallet) {
698-
self.rpc_client.create_wallet(wallet, Some(true))?
726+
self.try_get_rpc_client()
727+
.create_wallet(wallet, Some(true))?
699728
}
700729
Ok(())
701730
}
@@ -1861,7 +1890,7 @@ impl BitcoinRegtestController {
18611890

18621891
const UNCAPPED_FEE: f64 = 0.0;
18631892
const MAX_BURN_AMOUNT: u64 = 1_000_000;
1864-
self.rpc_client
1893+
self.try_get_rpc_client()
18651894
.send_raw_transaction(tx, Some(UNCAPPED_FEE), Some(MAX_BURN_AMOUNT))
18661895
.map(|txid| {
18671896
debug!("Transaction {txid} sent successfully");
@@ -1933,7 +1962,9 @@ impl BitcoinRegtestController {
19331962
.expect("FATAL: invalid public key bytes");
19341963
let address = self.get_miner_address(StacksEpochId::Epoch21, &public_key);
19351964

1936-
let result = self.rpc_client.generate_to_address(num_blocks, &address);
1965+
let result = self
1966+
.try_get_rpc_client()
1967+
.generate_to_address(num_blocks, &address);
19371968
/*
19381969
Temporary: not using `BitcoinRpcClientResultExt::ok_or_log_panic` (test code related),
19391970
because we need this logic available outside `#[cfg(test)]` due to Helium network.
@@ -1966,7 +1997,7 @@ impl BitcoinRegtestController {
19661997
.expect("FATAL: invalid public key bytes");
19671998
let address = self.get_miner_address(StacksEpochId::Epoch21, &public_key);
19681999

1969-
self.rpc_client
2000+
self.try_get_rpc_client()
19702001
.generate_block(&address, &[])
19712002
.ok_or_log_panic("generating block")
19722003
}
@@ -1975,15 +2006,15 @@ impl BitcoinRegtestController {
19752006
#[cfg(test)]
19762007
pub fn invalidate_block(&self, block: &BurnchainHeaderHash) {
19772008
info!("Invalidating block {block}");
1978-
self.rpc_client
2009+
self.try_get_rpc_client()
19792010
.invalidate_block(block)
19802011
.ok_or_log_panic("invalidate block")
19812012
}
19822013

19832014
/// Retrieve the hash (as a [`BurnchainHeaderHash`]) of the block at the given height.
19842015
#[cfg(test)]
19852016
pub fn get_block_hash(&self, height: u64) -> BurnchainHeaderHash {
1986-
self.rpc_client
2017+
self.try_get_rpc_client()
19872018
.get_block_hash(height)
19882019
.unwrap_or_log_panic("retrieve block")
19892020
}
@@ -2041,7 +2072,7 @@ impl BitcoinRegtestController {
20412072
/// Retrieves a raw [`Transaction`] by its [`Txid`]
20422073
#[cfg(test)]
20432074
pub fn get_raw_transaction(&self, txid: &Txid) -> Transaction {
2044-
self.rpc_client
2075+
self.try_get_rpc_client()
20452076
.get_raw_transaction(txid)
20462077
.unwrap_or_log_panic("retrieve raw tx")
20472078
}
@@ -2069,7 +2100,7 @@ impl BitcoinRegtestController {
20692100
"Generate to address '{address}' for public key '{}'",
20702101
&pks[0].to_hex()
20712102
);
2072-
self.rpc_client
2103+
self.try_get_rpc_client()
20732104
.generate_to_address(num_blocks, &address)
20742105
.ok_or_log_panic("generating block");
20752106
return;
@@ -2087,7 +2118,7 @@ impl BitcoinRegtestController {
20872118
&pk.to_hex(),
20882119
);
20892120
}
2090-
self.rpc_client
2121+
self.try_get_rpc_client()
20912122
.generate_to_address(1, &address)
20922123
.ok_or_log_panic("generating block");
20932124
}
@@ -2105,7 +2136,7 @@ impl BitcoinRegtestController {
21052136
/// * `false` if the transaction is unconfirmed or could not be found.
21062137
pub fn is_transaction_confirmed(&self, txid: &Txid) -> bool {
21072138
match self
2108-
.rpc_client
2139+
.try_get_rpc_client()
21092140
.get_transaction(self.get_wallet_name(), txid)
21102141
{
21112142
Ok(info) => info.confirmations > 0,
@@ -2158,15 +2189,15 @@ impl BitcoinRegtestController {
21582189
);
21592190

21602191
let descriptor = format!("addr({address})");
2161-
let info = self.rpc_client.get_descriptor_info(&descriptor)?;
2192+
let info = self.try_get_rpc_client().get_descriptor_info(&descriptor)?;
21622193

21632194
let descr_req = ImportDescriptorsRequest {
21642195
descriptor: format!("addr({address})#{}", info.checksum),
21652196
timestamp: Timestamp::Time(0),
21662197
internal: Some(true),
21672198
};
21682199

2169-
self.rpc_client
2200+
self.try_get_rpc_client()
21702201
.import_descriptors(self.get_wallet_name(), &[&descr_req])?;
21712202
}
21722203
Ok(())
@@ -2227,11 +2258,11 @@ impl BitcoinRegtestController {
22272258
utxos_to_exclude: &Option<UTXOSet>,
22282259
block_height: u64,
22292260
) -> BitcoinRpcClientResult<UTXOSet> {
2230-
let bhh = self.rpc_client.get_block_hash(block_height)?;
2261+
let bhh = self.try_get_rpc_client().get_block_hash(block_height)?;
22312262

22322263
const MIN_CONFIRMATIONS: u64 = 0;
22332264
const MAX_CONFIRMATIONS: u64 = 9_999_999;
2234-
let unspents = self.rpc_client.list_unspent(
2265+
let unspents = self.try_get_rpc_client().list_unspent(
22352266
&self.get_wallet_name(),
22362267
Some(MIN_CONFIRMATIONS),
22372268
Some(MAX_CONFIRMATIONS),
@@ -2446,6 +2477,7 @@ mod tests {
24462477

24472478
pub fn create_config() -> Config {
24482479
let mut config = Config::default();
2480+
config.node.miner = true;
24492481
config.burnchain.magic_bytes = "T3".as_bytes().into();
24502482
config.burnchain.username = Some(String::from("user"));
24512483
config.burnchain.password = Some(String::from("12345"));
@@ -2964,6 +2996,10 @@ mod tests {
29642996
#[test]
29652997
#[ignore]
29662998
fn test_create_wallet_from_custom_name() {
2999+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
3000+
return;
3001+
}
3002+
29673003
let mut config = utils::create_config();
29683004
config.burnchain.wallet_name = String::from("mywallet");
29693005

0 commit comments

Comments
 (0)