bitcoin_blockchain_query/
lib.rs1use 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
77pub 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}