• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 26250451051-1

21 May 2026 08:11PM UTC coverage: 85.585% (-0.1%) from 85.712%
26250451051-1

Pull #7215

github

ec9d4c
web-flow
Merge 9487bf852 into af1280aac
Pull Request #7215: Chore: fix flake in non_blocking_minority_configured_to_favour_...

188844 of 220651 relevant lines covered (85.58%)

18975267.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.81
/stacks-node/src/burnchains/rpc/bitcoin_rpc_client/test_utils.rs
1
// Copyright (C) 2025 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
//! Test-only utilities for [`BitcoinRpcClient`]
17

18
use serde::{Deserialize, Deserializer};
19
use serde_json::Value;
20
use stacks::burnchains::bitcoin::address::BitcoinAddress;
21
use stacks::burnchains::bitcoin::BitcoinNetworkType;
22
use stacks::burnchains::Txid;
23
use stacks::types::chainstate::BurnchainHeaderHash;
24
use stacks_common::deps_common::bitcoin::blockdata::transaction::Transaction;
25
use stacks_common::deps_common::bitcoin::network::serialize::deserialize_hex;
26

27
use crate::burnchains::rpc::bitcoin_rpc_client::{
28
    deserialize_string_to_bitcoin_address, BitcoinRpcClient, BitcoinRpcClientResult,
29
    TxidWrapperResponse,
30
};
31

32
/// Represents the response returned by the `getblockchaininfo` RPC call.
33
///
34
/// # Notes
35
/// This struct supports a subset of available fields to match current usage.
36
/// Additional fields can be added in the future as needed.
37
#[derive(Debug, Clone, Deserialize)]
38
pub struct GetBlockChainInfoResponse {
39
    /// the network name
40
    #[serde(deserialize_with = "deserialize_string_to_network_type")]
41
    pub chain: BitcoinNetworkType,
42
    /// the height of the most-work fully-validated chain. The genesis block has height 0
43
    pub blocks: u64,
44
    /// the current number of headers that have been validated
45
    pub headers: u64,
46
    /// the hash of the currently best block
47
    #[serde(
48
        rename = "bestblockhash",
49
        deserialize_with = "deserialize_string_to_burn_header_hash"
50
    )]
51
    pub best_block_hash: BurnchainHeaderHash,
52
}
53

54
/// Deserializes a JSON string into [`BitcoinNetworkType`]
55
fn deserialize_string_to_network_type<'de, D>(
4✔
56
    deserializer: D,
4✔
57
) -> Result<BitcoinNetworkType, D::Error>
4✔
58
where
4✔
59
    D: Deserializer<'de>,
4✔
60
{
61
    let string: String = Deserialize::deserialize(deserializer)?;
4✔
62
    match string.as_str() {
4✔
63
        "main" => Ok(BitcoinNetworkType::Mainnet),
4✔
64
        "test" => Ok(BitcoinNetworkType::Testnet),
3✔
65
        "regtest" => Ok(BitcoinNetworkType::Regtest),
2✔
66
        other => Err(serde::de::Error::custom(format!(
1✔
67
            "invalid network type: {other}"
1✔
68
        ))),
1✔
69
    }
70
}
4✔
71

72
/// Represents the response returned by the `generateblock` RPC call.
73
#[derive(Debug, Clone, Deserialize)]
74
struct GenerateBlockResponse {
75
    /// The hash of the generated block
76
    #[serde(deserialize_with = "deserialize_string_to_burn_header_hash")]
77
    hash: BurnchainHeaderHash,
78
}
79

80
/// Deserializes a JSON string into [`BurnchainHeaderHash`]
81
fn deserialize_string_to_burn_header_hash<'de, D>(
10✔
82
    deserializer: D,
10✔
83
) -> Result<BurnchainHeaderHash, D::Error>
10✔
84
where
10✔
85
    D: Deserializer<'de>,
10✔
86
{
87
    let string: String = Deserialize::deserialize(deserializer)?;
10✔
88
    BurnchainHeaderHash::from_hex(&string).map_err(serde::de::Error::custom)
10✔
89
}
10✔
90

91
/// Represents supported Bitcoin address types.
92
#[derive(Debug, Clone)]
93
pub enum AddressType {
94
    /// Legacy P2PKH address
95
    Legacy,
96
    /// P2SH-wrapped SegWit address
97
    P2shSegwit,
98
    /// Native SegWit address
99
    Bech32,
100
    /// Native SegWit v1+ address
101
    Bech32m,
102
}
103

104
impl ToString for AddressType {
105
    fn to_string(&self) -> String {
×
106
        match self {
×
107
            AddressType::Legacy => "legacy",
×
108
            AddressType::P2shSegwit => "p2sh-segwit",
×
109
            AddressType::Bech32 => "bech32",
×
110
            AddressType::Bech32m => "bech32m",
×
111
        }
112
        .to_string()
×
113
    }
×
114
}
115

116
/// Response for `getnewaddress` rpc, mainly used as deserialization wrapper for `BitcoinAddress`
117
struct GetNewAddressResponse(pub BitcoinAddress);
118

119
/// Deserializes a JSON string into [`BitcoinAddress`] and wrap it into [`GetNewAddressResponse`]
120
impl<'de> Deserialize<'de> for GetNewAddressResponse {
121
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2✔
122
    where
2✔
123
        D: Deserializer<'de>,
2✔
124
    {
125
        deserialize_string_to_bitcoin_address(deserializer).map(GetNewAddressResponse)
2✔
126
    }
2✔
127
}
128

129
impl BitcoinRpcClient {
130
    /// Retrieve general information about the current state of the blockchain.
131
    ///
132
    /// # Arguments
133
    /// None.
134
    ///
135
    /// # Returns
136
    /// A [`GetBlockChainInfoResponse`] struct containing blockchain metadata.
137
    pub fn get_blockchain_info(&self) -> BitcoinRpcClientResult<GetBlockChainInfoResponse> {
4✔
138
        Ok(self
4✔
139
            .endpoint
4✔
140
            .send(&self.client_id, None, "getblockchaininfo", vec![])?)
4✔
141
    }
4✔
142

143
    /// Retrieves and deserializes a raw Bitcoin transaction by its ID.
144
    ///
145
    /// # Arguments
146
    /// * `txid` - The transaction ID (as [`Txid`]) to query (in big-endian order).
147
    ///
148
    /// # Returns
149
    /// A [`Transaction`] struct representing the decoded transaction.
150
    ///
151
    /// # Availability
152
    /// - **Since**: Bitcoin Core **v0.7.0**.
153
    pub fn get_raw_transaction(&self, txid: &Txid) -> BitcoinRpcClientResult<Transaction> {
14✔
154
        let raw_hex = self.endpoint.send::<String>(
14✔
155
            &self.client_id,
14✔
156
            None,
14✔
157
            "getrawtransaction",
14✔
158
            vec![txid.to_hex().into()],
14✔
159
        )?;
×
160
        Ok(deserialize_hex(&raw_hex)?)
14✔
161
    }
14✔
162

163
    /// Mines a new block including the given transactions to a specified address.
164
    ///
165
    /// # Arguments
166
    /// * `address` - A [`BitcoinAddress`] to which the block subsidy will be paid.
167
    /// * `txs` - List of transactions to include in the block. Each entry can be:
168
    ///   - A raw hex-encoded transaction
169
    ///   - A transaction ID (must be present in the mempool)
170
    ///   If the list is empty, an empty block (with only the coinbase transaction) will be generated.
171
    ///
172
    /// # Returns
173
    /// A [`BurnchainHeaderHash`] struct containing the block hash of the newly generated block.
174
    ///
175
    /// # Availability
176
    /// - **Since**: Bitcoin Core **v22.0**.
177
    /// - Requires `regtest` or similar testing networks.
178
    pub fn generate_block(
6✔
179
        &self,
6✔
180
        address: &BitcoinAddress,
6✔
181
        txs: &[&str],
6✔
182
    ) -> BitcoinRpcClientResult<BurnchainHeaderHash> {
6✔
183
        let response = self.endpoint.send::<GenerateBlockResponse>(
6✔
184
            &self.client_id,
6✔
185
            None,
6✔
186
            "generateblock",
6✔
187
            vec![address.to_string().into(), txs.into()],
6✔
188
        )?;
1✔
189
        Ok(response.hash)
5✔
190
    }
6✔
191

192
    /// Gracefully shuts down the Bitcoin Core node.
193
    ///
194
    /// Sends the shutdown command to safely terminate `bitcoind`. This ensures all state is written
195
    /// to disk and the node exits cleanly.
196
    ///
197
    /// # Returns
198
    /// On success, returns the string: `"Bitcoin Core stopping"`
199
    ///
200
    /// # Availability
201
    /// - **Since**: Bitcoin Core **v0.1.0**.
202
    pub fn stop(&self) -> BitcoinRpcClientResult<String> {
134✔
203
        Ok(self.endpoint.send(&self.client_id, None, "stop", vec![])?)
134✔
204
    }
134✔
205

206
    /// Retrieves a new Bitcoin address from the wallet.
207
    ///
208
    /// # Arguments
209
    /// * `wallet` - The name of the wallet to query. This is used to construct the wallet-specific RPC endpoint.
210
    /// * `label` - Optional label to associate with the address.
211
    /// * `address_type` - Optional [`AddressType`] variant to specify the type of address.
212
    ///   If `None`, the address type defaults to the node’s `-addresstype` setting.
213
    ///   If `-addresstype` is also unset, the default is `"bech32"` (since v0.20.0).
214
    ///
215
    /// # Returns
216
    /// A [`BitcoinAddress`] variant representing the newly generated Bitcoin address.
217
    ///
218
    /// # Availability
219
    /// - **Since**: Bitcoin Core **v0.1.0**.  
220
    /// - `address_type` parameter supported since **v0.17.0**.
221
    /// - Defaulting to `bech32` (when unset) introduced in **v0.20.0**.
222
    pub fn get_new_address(
2✔
223
        &self,
2✔
224
        wallet: &str,
2✔
225
        label: Option<&str>,
2✔
226
        address_type: Option<AddressType>,
2✔
227
    ) -> BitcoinRpcClientResult<BitcoinAddress> {
2✔
228
        let mut params = vec![];
2✔
229

230
        let label = label.unwrap_or("");
2✔
231
        params.push(label.into());
2✔
232

233
        if let Some(at) = address_type {
2✔
234
            params.push(at.to_string().into());
×
235
        }
2✔
236

237
        let response = self.endpoint.send::<GetNewAddressResponse>(
2✔
238
            &self.client_id,
2✔
239
            Some(&Self::wallet_path(wallet)),
2✔
240
            "getnewaddress",
2✔
241
            params,
2✔
242
        )?;
1✔
243

244
        Ok(response.0)
1✔
245
    }
2✔
246

247
    /// Sends a specified amount of BTC to a given address.
248
    ///
249
    /// # Arguments
250
    /// * `wallet` - The name of the wallet to query. This is used to construct the wallet-specific RPC endpoint.
251
    /// * `address` - The destination Bitcoin address as a [`BitcoinAddress`].
252
    /// * `amount` - Amount to send in BTC (not in satoshis).
253
    ///
254
    /// # Returns
255
    /// A [`Txid`] as a transaction ID (in big-endian order)
256
    ///
257
    /// # Availability
258
    /// - **Since**: Bitcoin Core **v0.1.0**.
259
    pub fn send_to_address(
2✔
260
        &self,
2✔
261
        wallet: &str,
2✔
262
        address: &BitcoinAddress,
2✔
263
        amount: f64,
2✔
264
    ) -> BitcoinRpcClientResult<Txid> {
2✔
265
        let response = self.endpoint.send::<TxidWrapperResponse>(
2✔
266
            &self.client_id,
2✔
267
            Some(&Self::wallet_path(wallet)),
2✔
268
            "sendtoaddress",
2✔
269
            vec![address.to_string().into(), amount.into()],
2✔
270
        )?;
1✔
271
        Ok(response.0)
1✔
272
    }
2✔
273

274
    /// Invalidate a block by its block hash, forcing the node to reconsider its chain state.
275
    ///
276
    /// # Arguments
277
    /// * `hash` - The block hash (as [`BurnchainHeaderHash`]) of the block to invalidate.
278
    ///
279
    /// # Returns
280
    /// An empty `()` on success.
281
    ///
282
    /// # Availability
283
    /// - **Since**: Bitcoin Core **v0.1.0**.
284
    pub fn invalidate_block(&self, hash: &BurnchainHeaderHash) -> BitcoinRpcClientResult<()> {
31✔
285
        self.endpoint.send::<Value>(
31✔
286
            &self.client_id,
31✔
287
            None,
31✔
288
            "invalidateblock",
31✔
289
            vec![hash.to_hex().into()],
31✔
290
        )?;
×
291
        Ok(())
31✔
292
    }
31✔
293
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc