bitcoin_blockchain_query/
lib.rs

1use std::collections::HashMap;
2
3use bitcoind_request::{
4    client::Client as BitcoindRequestClient,
5    command::{
6        get_raw_transaction::{
7            GetRawTransactionCommand, GetRawTransactionCommandResponse,
8            Transaction as BitcoindTransaction, Vin,
9        },
10        CallableCommand,
11    },
12};
13use electrs_query::{
14    self, get_balance_for_address, get_historical_transactions_for_address, get_utxos_for_address,
15    Client as ElectrsClient,
16};
17
18pub type VinIndex = u64;
19pub type VoutIndex = u64;
20
21pub type SpentFromTransaction = BitcoindTransaction;
22pub type SpentInTransaction = BitcoindTransaction;
23
24#[derive(Debug, Clone)]
25pub enum TransactionFlow {
26    Sent(VinIndex, SpentFromTransaction, SpentInTransaction),
27    Recieved(VoutIndex, BitcoindTransaction),
28}
29fn get_transaction(
30    txid: String,
31    bitcoind_request_client: &BitcoindRequestClient,
32) -> BitcoindTransaction {
33    let transaction_response = GetRawTransactionCommand::new(txid.to_string())
34        .verbose(true)
35        .call(&bitcoind_request_client)
36        .unwrap();
37    let transaction_result = match transaction_response {
38        GetRawTransactionCommandResponse::Transaction(transaction) => Ok(transaction),
39        _ => Err("shouldn't reach"),
40    };
41    transaction_result.unwrap()
42}
43
44fn get_raw_transactions_for_address(
45    address: &str,
46    electrs_client: &ElectrsClient,
47    bitcoind_request_client: &BitcoindRequestClient,
48) -> Vec<BitcoindTransaction> {
49    let history = get_historical_transactions_for_address(&address, &electrs_client);
50    let transactions: Vec<BitcoindTransaction> = history
51        .iter()
52        .map(|historical_transaction| {
53            let txid = &historical_transaction.tx_hash;
54            let transaction_response = GetRawTransactionCommand::new(txid.to_string())
55                .verbose(true)
56                .call(&bitcoind_request_client)
57                .unwrap();
58            let transaction_result = match transaction_response {
59                GetRawTransactionCommandResponse::Transaction(transaction) => Ok(transaction),
60                _ => Err("shouldn't reach"),
61            };
62            transaction_result.unwrap()
63        })
64        .collect();
65    transactions
66}
67
68pub type TransactionFlowsWithTransaction = (BitcoindTransaction, Vec<TransactionFlow>);
69pub type TransactionFlowsForAddress = Vec<TransactionFlowsWithTransaction>;
70
71type Txid = String;
72type Blocktime = i64;
73
74type TransactionFlowsForMultipleAddressesOrganizedByTransaction =
75    HashMap<(Txid, Blocktime), Vec<TransactionFlow>>;
76
77// TODO: Not sure if this should be in this library or in the presentation layer of an application.
78pub fn organize_transaction_flows_for_mulitple_addresses_by_txid_and_blocktime(
79    transaction_flows_for_addresses: Vec<TransactionFlowsForAddress>,
80) -> TransactionFlowsForMultipleAddressesOrganizedByTransaction {
81    let mut transactions_grouped_by_transaction: TransactionFlowsForMultipleAddressesOrganizedByTransaction  =
82        HashMap::new();
83    for transaction_flows_for_address in transaction_flows_for_addresses.clone() {
84        for transaction_flows_with_transaction in transaction_flows_for_address {
85            let (tx, tx_types) = transaction_flows_with_transaction;
86            let txid = tx.txid;
87            let blocktime = tx.time as i64;
88            match transactions_grouped_by_transaction.get(&(txid.clone(), blocktime)) {
89                Some(transactions) => {
90                    let list_to_add = tx_types.clone();
91                    let new_list = transactions.iter().chain(&list_to_add).cloned().collect();
92                    transactions_grouped_by_transaction.insert((txid, blocktime), new_list);
93                }
94                None => {
95                    transactions_grouped_by_transaction.insert((txid, blocktime), tx_types);
96                }
97            }
98        }
99    }
100    transactions_grouped_by_transaction
101}
102
103pub fn get_transaction_flows_for_address(
104    address: &str,
105    electrs_client: &ElectrsClient,
106    bitcoind_request_client: &BitcoindRequestClient,
107) -> TransactionFlowsForAddress {
108    let mut transaction_flows_for_address = vec![];
109    let transactions =
110        get_raw_transactions_for_address(address, electrs_client, bitcoind_request_client);
111    for tx in &transactions {
112        let mut flows_for_transaction = vec![];
113        for vout in tx.vout.clone() {
114            let vout_address = if vout.script_pub_key.address.is_some() {
115                vout.script_pub_key.address
116            } else {
117                vout.address
118            };
119            match vout_address {
120                Some(addr) => {
121                    if addr == address {
122                        flows_for_transaction.push(TransactionFlow::Recieved(vout.n, tx.clone()));
123                    }
124                }
125                None => {}
126            }
127        }
128        for vin in tx.vin.clone() {
129            match vin {
130                Vin::Coinbase(_vin) => {
131                    todo!()
132                }
133                Vin::NonCoinbase(vin) => {
134                    let transaction_for_vin = get_transaction(vin.txid, bitcoind_request_client);
135                    let vout_for_vin = &transaction_for_vin.vout[vin.vout as usize];
136                    let vout_address = if vout_for_vin.script_pub_key.address.is_some() {
137                        &vout_for_vin.script_pub_key.address
138                    } else {
139                        &vout_for_vin.address
140                    }
141                    .clone();
142                    match vout_address {
143                        Some(addr) => {
144                            if addr == address {
145                                flows_for_transaction.push(TransactionFlow::Sent(
146                                    vout_for_vin.n,
147                                    transaction_for_vin.clone(),
148                                    tx.clone(),
149                                ));
150                            }
151                        }
152                        None => {}
153                    }
154                }
155            }
156        }
157        transaction_flows_for_address.push((tx.clone(), flows_for_transaction));
158    }
159
160    transaction_flows_for_address
161}