1use std::{
20 collections::HashMap,
21 io::{Cursor, Read},
22 sync::Arc,
23 time::Duration,
24};
25
26use log::{error, info, warn};
27use rand::{prelude::SliceRandom, rngs::OsRng};
28use sha2::{Digest, Sha256};
29use smol::lock::RwLock;
30use tinyjson::JsonValue;
31use url::Url;
32
33use darkfi::{
34 rpc::{client::RpcClient, jsonrpc::JsonRequest},
35 system::{timeout::timeout, ExecutorPtr},
36 Error, Result,
37};
38use darkfi_sdk::{hex::decode_hex, GenericResult};
39
40use crate::pow::PowSettings;
41
42pub type BitcoinBlockHash = [u8; 32];
43
44pub struct BitcoinHashCache {
52 settings: Arc<RwLock<PowSettings>>,
54 pub block_hashes: Vec<BitcoinBlockHash>,
56 ex: ExecutorPtr,
58}
59
60impl BitcoinHashCache {
61 pub fn new(settings: Arc<RwLock<PowSettings>>, ex: ExecutorPtr) -> Self {
62 Self { settings, block_hashes: vec![], ex }
63 }
64
65 pub async fn update(&mut self) -> Result<Vec<BitcoinBlockHash>> {
67 info!(target: "fud::BitcoinHashCache::update()", "[BTC] Updating block hashes...");
68
69 let mut block_hashes = vec![];
70 let btc_electrum_nodes = self.settings.read().await.btc_electrum_nodes.clone();
71
72 let mut rng = OsRng;
73 let mut shuffled_nodes: Vec<_> = btc_electrum_nodes.clone();
74 shuffled_nodes.shuffle(&mut rng);
75
76 for addr in shuffled_nodes {
77 let client = match self.create_rpc_client(&addr).await {
79 Ok(client) => client,
80 Err(e) => {
81 warn!(target: "fud::BitcoinHashCache::update()", "[BTC] Error while creating RPC client for Electrum node {addr}: {e}");
82 continue
83 }
84 };
85 info!(target: "fud::BitcoinHashCache::update()", "[BTC] Connected to {addr}");
86
87 let current_height = match self.fetch_current_height(&client).await {
89 Ok(height) => height,
90 Err(e) => {
91 warn!(target: "fud::BitcoinHashCache::update()", "[BTC] Error while fetching current height: {e}");
92 client.stop().await;
93 continue
94 }
95 };
96 info!(target: "fud::BitcoinHashCache::update()", "[BTC] Found current height {current_height}");
97
98 match self.fetch_hashes(current_height, &client).await {
100 Ok(hashes) => {
101 client.stop().await;
102 if !hashes.is_empty() {
103 block_hashes = hashes;
104 break
105 }
106 warn!(target: "fud::BitcoinHashCache::update()", "[BTC] The Electrum node replied with an empty list of block headers");
107 continue
108 }
109 Err(e) => {
110 warn!(target: "fud::BitcoinHashCache::update()", "[BTC] Error while fetching block hashes: {e}");
111 client.stop().await;
112 continue
113 }
114 };
115 }
116
117 if block_hashes.is_empty() {
118 let err_str = "Could not find any block hash";
119 error!(target: "fud::BitcoinHashCache::update()", "[BTC] {err_str}");
120 return Err(Error::Custom(err_str.to_string()))
121 }
122
123 info!(target: "fud::BitcoinHashCache::update()", "[BTC] Found {} block hashes", block_hashes.len());
124
125 self.block_hashes = block_hashes.clone();
126 Ok(block_hashes)
127 }
128
129 async fn create_rpc_client(&self, addr: &Url) -> Result<RpcClient> {
130 let btc_timeout = Duration::from_secs(self.settings.read().await.btc_timeout);
131 let client = timeout(btc_timeout, RpcClient::new(addr.clone(), self.ex.clone())).await??;
132 Ok(client)
133 }
134
135 async fn fetch_current_height(&self, client: &RpcClient) -> Result<u64> {
137 let btc_timeout = Duration::from_secs(self.settings.read().await.btc_timeout);
138 let req = JsonRequest::new("blockchain.headers.subscribe", vec![].into());
139 let rep = timeout(btc_timeout, client.request(req)).await??;
140
141 rep.get::<HashMap<String, JsonValue>>()
142 .and_then(|res| res.get("height"))
143 .and_then(|h| h.get::<f64>())
144 .map(|h| *h as u64)
145 .ok_or_else(|| {
146 Error::JsonParseError(
147 "Failed to parse `blockchain.headers.subscribe` response".into(),
148 )
149 })
150 }
151
152 async fn fetch_hashes(&self, height: u64, client: &RpcClient) -> Result<Vec<BitcoinBlockHash>> {
154 let count = self.settings.read().await.btc_hash_count;
155 let btc_timeout = Duration::from_secs(self.settings.read().await.btc_timeout);
156 let req = JsonRequest::new(
157 "blockchain.block.headers",
158 vec![
159 JsonValue::Number((height as f64) - (count as f64)),
160 JsonValue::Number(count as f64),
161 ]
162 .into(),
163 );
164 let rep = timeout(btc_timeout, client.request(req)).await??;
165
166 let hex: &String = rep
167 .get::<HashMap<String, JsonValue>>()
168 .and_then(|res| res.get("hex"))
169 .and_then(|h| h.get::<String>())
170 .ok_or_else(|| {
171 Error::JsonParseError("Failed to parse `blockchain.block.headers` response".into())
172 })?;
173
174 let decoded_bytes = decode_hex(hex.as_str()).collect::<GenericResult<Vec<_>>>()?;
175 Self::decode_block_hashes(decoded_bytes)
176 }
177
178 fn decode_block_hashes(data: Vec<u8>) -> Result<Vec<BitcoinBlockHash>> {
180 let mut cursor = Cursor::new(&data);
181 let count = data.len() / 80;
182
183 let mut hashes = Vec::with_capacity(count);
184 for _ in 0..count {
185 let mut header = [0u8; 80];
187 cursor.read_exact(&mut header)?;
188
189 let first_hash = Sha256::digest(header);
191 let second_hash = Sha256::digest(first_hash);
192
193 let mut be_hash = [0u8; 32];
195 be_hash.copy_from_slice(&second_hash);
196 be_hash.reverse();
197
198 hashes.push(be_hash);
199 }
200
201 Ok(hashes)
202 }
203}