Skip to content

Commit 7863186

Browse files
committed
Merge branch 'develop' of https://github.com/stacks-network/stacks-core into feat/signer-two-phase-commit-impl
2 parents 52950a6 + e9a2d28 commit 7863186

File tree

3 files changed

+219
-67
lines changed

3 files changed

+219
-67
lines changed

stacks-node/src/burnchains/bitcoin_regtest_controller.rs

Lines changed: 174 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use stacks::burnchains::bitcoin::indexer::{
3131
BitcoinIndexer, BitcoinIndexerConfig, BitcoinIndexerRuntime,
3232
};
3333
use stacks::burnchains::bitcoin::spv::SpvClient;
34-
use stacks::burnchains::bitcoin::BitcoinNetworkType;
34+
use stacks::burnchains::bitcoin::{BitcoinNetworkType, Error as btc_error};
3535
use stacks::burnchains::db::BurnchainDB;
3636
use stacks::burnchains::indexer::BurnchainIndexer;
3737
use stacks::burnchains::{
@@ -78,7 +78,9 @@ use url::Url;
7878
use super::super::operations::BurnchainOpSigner;
7979
use super::super::Config;
8080
use super::{BurnchainController, BurnchainTip, Error as BurnchainControllerError};
81-
use crate::burnchains::rpc::bitcoin_rpc_client::{BitcoinRpcClient, BitcoinRpcClientError};
81+
use crate::burnchains::rpc::bitcoin_rpc_client::{
82+
BitcoinRpcClient, BitcoinRpcClientError, ImportDescriptorsRequest, Timestamp,
83+
};
8284

8385
/// The number of bitcoin blocks that can have
8486
/// passed since the UTXO cache was last refreshed before
@@ -302,6 +304,20 @@ impl<T> BitcoinRpcClientResultExt<T> for Result<T, BitcoinRpcClientError> {
302304
}
303305
}
304306

307+
/// Represents errors that can occur when using [`BitcoinRegtestController`].
308+
#[derive(Debug, thiserror::Error)]
309+
pub enum BitcoinRegtestControllerError {
310+
/// Error related to Bitcoin RPC failures.
311+
#[error("Bitcoin RPC error: {0}")]
312+
Rpc(#[from] BitcoinRpcClientError),
313+
/// Error related to invalid or malformed [`Secp256k1PublicKey`].
314+
#[error("Invalid public key: {0}")]
315+
InvalidPublicKey(btc_error),
316+
}
317+
318+
/// Alias for results returned from [`BitcoinRegtestController`] operations.
319+
pub type BitcoinRegtestControllerResult<T> = Result<T, BitcoinRegtestControllerError>;
320+
305321
impl BitcoinRegtestController {
306322
pub fn new(config: Config, coordinator_channel: Option<CoordinatorChannels>) -> Self {
307323
BitcoinRegtestController::with_burnchain(config, coordinator_channel, None, None)
@@ -664,7 +680,10 @@ impl BitcoinRegtestController {
664680
};
665681

666682
test_debug!("Import public key '{}'", &pubk.to_hex());
667-
let _result = BitcoinRPCRequest::import_public_key(&self.config, &pubk);
683+
let result = self.import_public_key(&pubk);
684+
if let Err(error) = result {
685+
warn!("Import public key '{}' failed: {error:?}", &pubk.to_hex());
686+
}
668687

669688
sleep_ms(1000);
670689

@@ -740,13 +759,13 @@ impl BitcoinRegtestController {
740759
}
741760

742761
/// Retrieve all loaded wallets.
743-
pub fn list_wallets(&self) -> Result<Vec<String>, BitcoinRpcClientError> {
744-
self.rpc_client.list_wallets()
762+
pub fn list_wallets(&self) -> BitcoinRegtestControllerResult<Vec<String>> {
763+
Ok(self.rpc_client.list_wallets()?)
745764
}
746765

747766
/// Checks if the config-supplied wallet exists.
748767
/// If it does not exist, this function creates it.
749-
pub fn create_wallet_if_dne(&self) -> Result<(), BitcoinRpcClientError> {
768+
pub fn create_wallet_if_dne(&self) -> BitcoinRegtestControllerResult<()> {
750769
let wallets = self.list_wallets()?;
751770
let wallet = self.get_wallet_name();
752771
if !wallets.contains(wallet) {
@@ -807,7 +826,10 @@ impl BitcoinRegtestController {
807826
// Assuming that miners are in charge of correctly operating their bitcoind nodes sounds
808827
// reasonable to me.
809828
// $ bitcoin-cli importaddress mxVFsFW5N4mu1HPkxPttorvocvzeZ7KZyk
810-
let _result = BitcoinRPCRequest::import_public_key(&self.config, &pubk);
829+
let result = self.import_public_key(&pubk);
830+
if let Err(error) = result {
831+
warn!("Import public key '{}' failed: {error:?}", &pubk.to_hex());
832+
}
811833
sleep_ms(1000);
812834
}
813835

@@ -2111,7 +2133,7 @@ impl BitcoinRegtestController {
21112133

21122134
for pk in pks {
21132135
debug!("Import public key '{}'", &pk.to_hex());
2114-
if let Err(e) = BitcoinRPCRequest::import_public_key(&self.config, pk) {
2136+
if let Err(e) = self.import_public_key(pk) {
21152137
warn!("Error when importing pubkey: {e:?}");
21162138
}
21172139
}
@@ -2174,6 +2196,57 @@ impl BitcoinRegtestController {
21742196
fn get_wallet_name(&self) -> &String {
21752197
&self.config.burnchain.wallet_name
21762198
}
2199+
2200+
/// Imports a public key into configured wallet by registering its
2201+
/// corresponding addresses as descriptors.
2202+
///
2203+
/// This computes both **legacy (P2PKH)** and, if the miner is configured
2204+
/// with `segwit` enabled, also **SegWit (P2WPKH)** addresses, then imports
2205+
/// the related descriptors into the wallet.
2206+
pub fn import_public_key(
2207+
&self,
2208+
public_key: &Secp256k1PublicKey,
2209+
) -> BitcoinRegtestControllerResult<()> {
2210+
let pkh = Hash160::from_data(&public_key.to_bytes())
2211+
.to_bytes()
2212+
.to_vec();
2213+
let (_, network_id) = self.config.burnchain.get_bitcoin_network();
2214+
2215+
// import both the legacy and segwit variants of this public key
2216+
let mut addresses = vec![BitcoinAddress::from_bytes_legacy(
2217+
network_id,
2218+
LegacyBitcoinAddressType::PublicKeyHash,
2219+
&pkh,
2220+
)
2221+
.map_err(BitcoinRegtestControllerError::InvalidPublicKey)?];
2222+
2223+
if self.config.miner.segwit {
2224+
addresses.push(
2225+
BitcoinAddress::from_bytes_segwit_p2wpkh(network_id, &pkh)
2226+
.map_err(BitcoinRegtestControllerError::InvalidPublicKey)?,
2227+
);
2228+
}
2229+
2230+
for address in addresses.into_iter() {
2231+
debug!(
2232+
"Import address {address} for public key {}",
2233+
public_key.to_hex()
2234+
);
2235+
2236+
let descriptor = format!("addr({address})");
2237+
let info = self.rpc_client.get_descriptor_info(&descriptor)?;
2238+
2239+
let descr_req = ImportDescriptorsRequest {
2240+
descriptor: format!("addr({address})#{}", info.checksum),
2241+
timestamp: Timestamp::Time(0),
2242+
internal: Some(true),
2243+
};
2244+
2245+
self.rpc_client
2246+
.import_descriptors(self.get_wallet_name(), &[&descr_req])?;
2247+
}
2248+
Ok(())
2249+
}
21772250
}
21782251

21792252
impl BurnchainController for BitcoinRegtestController {
@@ -2643,64 +2716,6 @@ impl BitcoinRPCRequest {
26432716
Ok(())
26442717
}
26452718

2646-
pub fn import_public_key(config: &Config, public_key: &Secp256k1PublicKey) -> RPCResult<()> {
2647-
let pkh = Hash160::from_data(&public_key.to_bytes())
2648-
.to_bytes()
2649-
.to_vec();
2650-
let (_, network_id) = config.burnchain.get_bitcoin_network();
2651-
2652-
// import both the legacy and segwit variants of this public key
2653-
let mut addresses = vec![BitcoinAddress::from_bytes_legacy(
2654-
network_id,
2655-
LegacyBitcoinAddressType::PublicKeyHash,
2656-
&pkh,
2657-
)
2658-
.expect("Public key incorrect")];
2659-
2660-
if config.miner.segwit {
2661-
addresses.push(
2662-
BitcoinAddress::from_bytes_segwit_p2wpkh(network_id, &pkh)
2663-
.expect("Public key incorrect"),
2664-
);
2665-
}
2666-
2667-
for address in addresses.into_iter() {
2668-
debug!(
2669-
"Import address {address} for public key {}",
2670-
public_key.to_hex()
2671-
);
2672-
2673-
let payload = BitcoinRPCRequest {
2674-
method: "getdescriptorinfo".to_string(),
2675-
params: vec![format!("addr({address})").into()],
2676-
id: "stacks".to_string(),
2677-
jsonrpc: "2.0".to_string(),
2678-
};
2679-
2680-
let result = BitcoinRPCRequest::send(config, payload)?;
2681-
let checksum = result
2682-
.get("result")
2683-
.and_then(|res| res.as_object())
2684-
.and_then(|obj| obj.get("checksum"))
2685-
.and_then(|checksum_val| checksum_val.as_str())
2686-
.ok_or(RPCError::Bitcoind(format!(
2687-
"Did not receive an object with `checksum` from `getdescriptorinfo \"{address}\"`",
2688-
)))?;
2689-
2690-
let payload = BitcoinRPCRequest {
2691-
method: "importdescriptors".to_string(),
2692-
params: vec![
2693-
json!([{ "desc": format!("addr({address})#{checksum}"), "timestamp": 0, "internal": true }]),
2694-
],
2695-
id: "stacks".to_string(),
2696-
jsonrpc: "2.0".to_string(),
2697-
};
2698-
2699-
BitcoinRPCRequest::send(config, payload)?;
2700-
}
2701-
Ok(())
2702-
}
2703-
27042719
pub fn send(config: &Config, payload: BitcoinRPCRequest) -> RPCResult<serde_json::Value> {
27052720
let request = BitcoinRPCRequest::build_rpc_request(config, &payload);
27062721
let timeout = Duration::from_secs(u64::from(config.burnchain.timeout));
@@ -3430,6 +3445,99 @@ mod tests {
34303445
);
34313446
}
34323447

3448+
#[test]
3449+
#[ignore]
3450+
fn test_import_public_key_ok() {
3451+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
3452+
return;
3453+
}
3454+
3455+
let miner_pubkey = utils::create_miner1_pubkey();
3456+
3457+
let config = utils::create_config();
3458+
3459+
let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
3460+
btcd_controller
3461+
.start_bitcoind()
3462+
.expect("bitcoind should be started!");
3463+
3464+
let btc_controller = BitcoinRegtestController::new(config.clone(), None);
3465+
btc_controller
3466+
.create_wallet_if_dne()
3467+
.expect("Wallet should be created!");
3468+
3469+
let result = btc_controller.import_public_key(&miner_pubkey);
3470+
assert!(
3471+
result.is_ok(),
3472+
"Should be ok, got err instead: {:?}",
3473+
result.unwrap_err()
3474+
);
3475+
}
3476+
3477+
#[test]
3478+
#[ignore]
3479+
fn test_import_public_key_twice_ok() {
3480+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
3481+
return;
3482+
}
3483+
3484+
let miner_pubkey = utils::create_miner1_pubkey();
3485+
3486+
let config = utils::create_config();
3487+
3488+
let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
3489+
btcd_controller
3490+
.start_bitcoind()
3491+
.expect("bitcoind should be started!");
3492+
3493+
let btc_controller = BitcoinRegtestController::new(config.clone(), None);
3494+
btc_controller
3495+
.create_wallet_if_dne()
3496+
.expect("Wallet should be created!");
3497+
3498+
btc_controller
3499+
.import_public_key(&miner_pubkey)
3500+
.expect("Import should be ok: first time!");
3501+
3502+
//ok, but it is basically a no-op
3503+
let result = btc_controller.import_public_key(&miner_pubkey);
3504+
assert!(
3505+
result.is_ok(),
3506+
"Should be ok, got err instead: {:?}",
3507+
result.unwrap_err()
3508+
);
3509+
}
3510+
3511+
#[test]
3512+
#[ignore]
3513+
fn test_import_public_key_segwit_ok() {
3514+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
3515+
return;
3516+
}
3517+
3518+
let miner_pubkey = utils::create_miner1_pubkey();
3519+
3520+
let mut config = utils::create_config();
3521+
config.miner.segwit = true;
3522+
3523+
let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
3524+
btcd_controller
3525+
.start_bitcoind()
3526+
.expect("bitcoind should be started!");
3527+
3528+
let btc_controller = BitcoinRegtestController::new(config.clone(), None);
3529+
btc_controller
3530+
.create_wallet_if_dne()
3531+
.expect("Wallet should be created!");
3532+
3533+
let result = btc_controller.import_public_key(&miner_pubkey);
3534+
assert!(
3535+
result.is_ok(),
3536+
"Should be ok, got err instead: {:?}",
3537+
result.unwrap_err()
3538+
);
3539+
}
3540+
34333541
/// Tests related to Leader Block Commit operation
34343542
mod leader_commit_op {
34353543
use super::*;

stacks-node/src/burnchains/rpc/bitcoin_rpc_client/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ fn convert_sat_to_btc_string(amount: u64) -> String {
268268
}
269269

270270
/// Represents an error message returned when importing descriptors fails.
271-
#[derive(Debug, Clone, Deserialize)]
271+
#[derive(Debug, Clone, Deserialize, PartialEq)]
272272
pub struct ImportDescriptorsErrorMessage {
273273
/// Numeric error code identifying the type of error.
274274
pub code: i64,

stacks-node/src/tests/bitcoin_rpc_integrations.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,50 @@ fn test_import_descriptor_ok() {
689689
.expect("import descriptor ok!");
690690
assert_eq!(1, response.len());
691691
assert!(response[0].success);
692+
assert_eq!(0, response[0].warnings.len());
693+
assert_eq!(None, response[0].error);
694+
}
695+
696+
#[ignore]
697+
#[test]
698+
fn test_import_descriptor_twice_ok() {
699+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
700+
return;
701+
}
702+
703+
let config = utils::create_stx_config();
704+
let wallet = &config.burnchain.wallet_name;
705+
706+
let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
707+
btcd_controller
708+
.start_bitcoind()
709+
.expect("bitcoind should be started!");
710+
711+
let client = BitcoinRpcClient::from_stx_config(&config).expect("Client creation ok!");
712+
client
713+
.create_wallet(wallet, Some(true))
714+
.expect("create wallet ok!");
715+
716+
let address = "mqqxPdP1dsGk75S7ta2nwyU8ujDnB2Yxvu";
717+
let checksum = "spfcmvsn";
718+
719+
let desc_req = ImportDescriptorsRequest {
720+
descriptor: format!("addr({address})#{checksum}"),
721+
timestamp: Timestamp::Time(0),
722+
internal: Some(true),
723+
};
724+
725+
let _ = client
726+
.import_descriptors(wallet, &[&desc_req])
727+
.expect("import descriptor ok: first time!");
728+
729+
let response = client
730+
.import_descriptors(wallet, &[&desc_req])
731+
.expect("import descriptor ok: second time!");
732+
assert_eq!(1, response.len());
733+
assert!(response[0].success);
734+
assert_eq!(0, response[0].warnings.len());
735+
assert_eq!(None, response[0].error);
692736
}
693737

694738
#[ignore]

0 commit comments

Comments
 (0)