spark_rust/spark_test_utils/
bitcoind.rs

1use bitcoin::{
2    key::{Keypair, Secp256k1},
3    Address, BlockHash, Network, Transaction, Txid,
4};
5use bitcoincore_rpc::{
6    json::{GetBlockResult, GetRawTransactionResult},
7    Auth, Client, RpcApi,
8};
9use rand::thread_rng;
10
11use crate::{
12    error::{NetworkError, SparkSdkError, ValidationError},
13    wallet::utils::bitcoin::p2tr_address_from_public_key,
14};
15
16pub struct BitcoindClient {
17    client: Client,
18}
19
20#[allow(dead_code)]
21impl BitcoindClient {
22    pub fn new(
23        host: String,
24        port: u16,
25        username: String,
26        password: String,
27    ) -> Result<Self, SparkSdkError> {
28        Client::new(
29            format!("http://{}:{}", host, port).as_str(),
30            Auth::UserPass(username, password),
31        )
32        .map(|client| Self { client })
33        .map_err(|err| SparkSdkError::from(NetworkError::BitcoinRpc(err.to_string())))
34    }
35
36    pub fn new_from_env() -> Result<Self, SparkSdkError> {
37        let host = std::env::var("BITCOIN_RPC_HOST").unwrap_or("127.0.0.1".to_string());
38        let port = match std::env::var("BITCOIN_RPC_PORT") {
39            Ok(port) => port.parse::<u16>().map_err(|err| {
40                SparkSdkError::from(ValidationError::InvalidInput {
41                    field: format!("Failed to parse port: {err}"),
42                })
43            }),
44            Err(_) => Ok(18443),
45        }?;
46
47        let username = std::env::var("BITCOIN_RPC_USERNAME").unwrap_or("admin1".to_string());
48        let password = std::env::var("BITCOIN_RPC_PASSWORD").unwrap_or("123".to_string());
49
50        Self::new(host, port, username, password)
51    }
52
53    pub fn generate_to_address(
54        &self,
55        blocks: u64,
56        address: &Address,
57    ) -> Result<Vec<BlockHash>, SparkSdkError> {
58        self.client
59            .generate_to_address(blocks, address)
60            .map_err(|err| SparkSdkError::from(NetworkError::BitcoinRpc(err.to_string())))
61    }
62
63    pub fn get_block_info(&self, block_hash: &BlockHash) -> Result<GetBlockResult, SparkSdkError> {
64        self.client
65            .get_block_info(block_hash)
66            .map_err(|err| SparkSdkError::from(NetworkError::BitcoinRpc(err.to_string())))
67    }
68
69    pub fn get_raw_transaction_info(
70        &self,
71        txid: &Txid,
72        block_hash: Option<&BlockHash>,
73    ) -> Result<GetRawTransactionResult, SparkSdkError> {
74        self.client
75            .get_raw_transaction_info(txid, block_hash)
76            .map_err(|err| SparkSdkError::from(NetworkError::BitcoinRpc(err.to_string())))
77    }
78
79    pub fn send_raw_transaction(&self, tx: &Transaction) -> Result<Txid, SparkSdkError> {
80        self.client
81            .send_raw_transaction(tx)
82            .map_err(|err| SparkSdkError::from(NetworkError::BitcoinRpc(err.to_string())))
83    }
84
85    pub fn mine_blocks(&self, blocks: u64) -> Result<(), SparkSdkError> {
86        let secp = Secp256k1::new();
87
88        let throwaway_keypair = Keypair::new(&secp, &mut thread_rng());
89        let throwaway_address =
90            p2tr_address_from_public_key(&throwaway_keypair.public_key(), Network::Regtest);
91
92        let _ = self
93            .client
94            .generate_to_address(blocks, &throwaway_address)
95            .map_err(|err| {
96                SparkSdkError::from(NetworkError::BitcoinRpc(format!(
97                    "Failed to generate to address: {}",
98                    err
99                )))
100            })?;
101
102        Ok(())
103    }
104}