1use crate::rpc::prefetch::CorePrefetcher;
2use dpp::dashcore::ephemerealdata::chain_lock::ChainLock;
3use dpp::dashcore::{Block, BlockHash, QuorumHash, Transaction, Txid};
4use dpp::dashcore::{Header, InstantLock};
5use dpp::dashcore_rpc::dashcore_rpc_json::{
6 AssetUnlockStatusResult, ExtendedQuorumDetails, ExtendedQuorumListResult, GetChainTipsResult,
7 MasternodeListDiff, MnSyncStatus, QuorumInfoResult, QuorumType, SoftforkInfo,
8};
9use dpp::dashcore_rpc::json::GetRawTransactionResult;
10use dpp::dashcore_rpc::{Auth, Client, Error, RpcApi};
11use dpp::prelude::TimestampMillis;
12use serde_json::Value;
13use std::collections::HashMap;
14use std::time::Duration;
15
16pub type QuorumListExtendedInfo = HashMap<QuorumHash, ExtendedQuorumDetails>;
18
19pub type CoreHeight = u32;
21#[cfg_attr(any(feature = "mocks", test), mockall::automock)]
23pub trait CoreRPCLike {
24 fn get_block_hash(&self, height: CoreHeight) -> Result<BlockHash, Error>;
26
27 fn get_block_header(&self, block_hash: &BlockHash) -> Result<Header, Error>;
29
30 fn get_block_time_from_height(&self, height: CoreHeight) -> Result<TimestampMillis, Error>;
32
33 fn get_best_chain_lock(&self) -> Result<ChainLock, Error>;
35
36 fn submit_chain_lock(&self, chain_lock: &ChainLock) -> Result<u32, Error>;
38
39 fn get_transaction(&self, tx_id: &Txid) -> Result<Transaction, Error>;
41
42 fn get_asset_unlock_statuses(
44 &self,
45 indices: &[u64],
46 core_chain_locked_height: u32,
47 ) -> Result<Vec<AssetUnlockStatusResult>, Error>;
48
49 fn get_transaction_extended_info(&self, tx_id: &Txid)
51 -> Result<GetRawTransactionResult, Error>;
52
53 fn get_optional_transaction_extended_info(
56 &self,
57 transaction_id: &Txid,
58 ) -> Result<Option<GetRawTransactionResult>, Error> {
59 match self.get_transaction_extended_info(transaction_id) {
60 Ok(transaction_info) => Ok(Some(transaction_info)),
61 Err(Error::JsonRpc(dpp::dashcore_rpc::jsonrpc::error::Error::Rpc(
63 dpp::dashcore_rpc::jsonrpc::error::RpcError {
64 code: CORE_RPC_INVALID_ADDRESS_OR_KEY,
65 ..
66 },
67 ))) => Ok(None),
68 Err(e) => Err(e),
69 }
70 }
71
72 fn get_fork_info(&self, name: &str) -> Result<Option<SoftforkInfo>, Error>;
74
75 fn get_block(&self, block_hash: &BlockHash) -> Result<Block, Error>;
77
78 fn get_block_json(&self, block_hash: &BlockHash) -> Result<Value, Error>;
80
81 fn get_chain_tips(&self) -> Result<GetChainTipsResult, Error>;
83
84 fn get_quorum_listextended(
88 &self,
89 height: Option<CoreHeight>,
90 ) -> Result<ExtendedQuorumListResult, Error>;
91
92 fn get_quorum_info(
96 &self,
97 quorum_type: QuorumType,
98 hash: &QuorumHash,
99 include_secret_key_share: Option<bool>,
100 ) -> Result<QuorumInfoResult, Error>;
101
102 fn get_protx_diff_with_masternodes(
104 &self,
105 base_block: Option<u32>,
106 block: u32,
107 ) -> Result<MasternodeListDiff, Error>;
108
109 fn verify_instant_lock(
116 &self,
117 instant_lock: &InstantLock,
118 max_height: Option<u32>,
119 ) -> Result<bool, Error>;
120
121 fn verify_chain_lock(&self, chain_lock: &ChainLock) -> Result<bool, Error>;
123
124 fn masternode_sync_status(&self) -> Result<MnSyncStatus, Error>;
126
127 fn send_raw_transaction(&self, transaction: &[u8]) -> Result<Txid, Error>;
129}
130
131#[derive(Debug)]
132pub struct DefaultCoreRPC {
134 inner: Client,
135 prefetcher: Option<CorePrefetcher>,
138}
139
140pub const CORE_RPC_TX_CONSENSUS_ERROR: i32 = -26;
144pub const CORE_RPC_TX_ALREADY_IN_CHAIN: i32 = -27;
146pub const CORE_RPC_ERROR_IN_WARMUP: i32 = -28;
148pub const CORE_RPC_CLIENT_NOT_CONNECTED: i32 = -9;
150pub const CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD: i32 = -10;
152pub const CORE_RPC_PARSE_ERROR: i32 = -32700;
154pub const CORE_RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
156pub const CORE_RPC_INVALID_PARAMETER: i32 = -8;
158
159macro_rules! retry {
160 ($action:expr) => {{
161 const MAX_RETRIES: u32 = 4;
163 const FIB_MULTIPLIER: u64 = 1;
165
166 fn fibonacci(n: u32) -> u64 {
167 match n {
168 0 => 0,
169 1 => 1,
170 _ => fibonacci(n - 1) + fibonacci(n - 2),
171 }
172 }
173
174 let mut last_err = None;
175 let result = (0..MAX_RETRIES).find_map(|i| {
176 match $action {
177 Ok(result) => Some(Ok(result)),
178 Err(e) => {
179 match e {
180 dpp::dashcore_rpc::Error::JsonRpc(
181 dpp::dashcore_rpc::jsonrpc::error::Error::Transport(_)
183 | dpp::dashcore_rpc::jsonrpc::error::Error::Rpc(
184 dpp::dashcore_rpc::jsonrpc::error::RpcError {
186 code:
187 CORE_RPC_ERROR_IN_WARMUP
188 | CORE_RPC_CLIENT_NOT_CONNECTED
189 | CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD,
190 ..
191 },
192 ),
193 ) => {
194 last_err = Some(e);
196 let delay = fibonacci(i + 2) * FIB_MULTIPLIER;
197 std::thread::sleep(Duration::from_secs(delay));
198 None
199 }
200 _ => Some(Err(e)),
201 }
202 }
203 }
204 });
205
206 result.unwrap_or_else(|| Err(last_err.unwrap()))
207 }};
208}
209
210impl DefaultCoreRPC {
211 pub fn open(url: &str, username: String, password: String) -> Result<Self, Error> {
213 let prefetcher = CorePrefetcher::new(url, username.clone(), password.clone());
214 if prefetcher.is_none() {
215 tracing::warn!(
216 "could not open a second Core RPC connection; masternode and quorum updates will be fetched on the critical path"
217 );
218 }
219 Ok(DefaultCoreRPC {
220 inner: Client::new(url, Auth::UserPass(username, password))?,
221 prefetcher,
222 })
223 }
224}
225
226impl CoreRPCLike for DefaultCoreRPC {
227 fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
228 retry!(self.inner.get_block_hash(height))
229 }
230
231 fn get_block_header(&self, block_hash: &BlockHash) -> Result<Header, Error> {
232 retry!(self.inner.get_block_header(block_hash))
233 }
234
235 fn get_block_time_from_height(&self, height: CoreHeight) -> Result<TimestampMillis, Error> {
236 let block_hash = self.get_block_hash(height)?;
237 let block_header = self.get_block_header(&block_hash)?;
238 let block_time = block_header.time as u64 * 1000;
239 Ok(block_time)
240 }
241
242 fn get_best_chain_lock(&self) -> Result<ChainLock, Error> {
243 retry!(self.inner.get_best_chain_lock())
244 }
245
246 fn submit_chain_lock(&self, chain_lock: &ChainLock) -> Result<u32, Error> {
247 retry!(self.inner.submit_chain_lock(chain_lock))
248 }
249
250 fn get_transaction(&self, tx_id: &Txid) -> Result<Transaction, Error> {
251 retry!(self.inner.get_raw_transaction(tx_id, None))
252 }
253
254 fn get_transaction_extended_info(
255 &self,
256 tx_id: &Txid,
257 ) -> Result<GetRawTransactionResult, Error> {
258 retry!(self.inner.get_raw_transaction_info(tx_id, None))
259 }
260
261 fn get_fork_info(&self, name: &str) -> Result<Option<SoftforkInfo>, Error> {
262 retry!(self
263 .inner
264 .get_blockchain_info()
265 .map(|blockchain_info| blockchain_info.softforks.get(name).cloned()))
266 }
267
268 fn get_block(&self, block_hash: &BlockHash) -> Result<Block, Error> {
269 retry!(self.inner.get_block(block_hash))
270 }
271
272 fn get_block_json(&self, block_hash: &BlockHash) -> Result<Value, Error> {
273 retry!(self.inner.get_block_json(block_hash))
274 }
275
276 fn get_chain_tips(&self) -> Result<GetChainTipsResult, Error> {
277 retry!(self.inner.get_chain_tips())
278 }
279
280 fn get_quorum_listextended(
281 &self,
282 height: Option<CoreHeight>,
283 ) -> Result<ExtendedQuorumListResult, Error> {
284 let prefetched = height
290 .zip(self.prefetcher.as_ref())
291 .and_then(|(height, prefetcher)| prefetcher.take_quorum_list(height));
292
293 let result = match prefetched {
294 Some(list) => Ok(list),
295 None => retry!(self.inner.get_quorum_listextended_reversed(height)),
296 };
297
298 if let (Ok(_), Some(height), Some(prefetcher)) = (&result, height, self.prefetcher.as_ref())
299 {
300 prefetcher.start_quorum_list(height + 1);
301 }
302
303 result
304 }
305
306 fn get_quorum_info(
307 &self,
308 quorum_type: QuorumType,
309 hash: &QuorumHash,
310 include_secret_key_share: Option<bool>,
311 ) -> Result<QuorumInfoResult, Error> {
312 retry!(self
313 .inner
314 .get_quorum_info_reversed(quorum_type, hash, include_secret_key_share))
315 }
316
317 fn get_protx_diff_with_masternodes(
318 &self,
319 base_block: Option<u32>,
320 block: u32,
321 ) -> Result<MasternodeListDiff, Error> {
322 let base = base_block.unwrap_or(1);
323
324 let prefetched = self
327 .prefetcher
328 .as_ref()
329 .and_then(|prefetcher| prefetcher.take_protx_diff(base, block));
330
331 let result = match prefetched {
332 Some(diff) => Ok(diff),
333 None => retry!(self.inner.get_protx_listdiff(base, block)),
334 };
335
336 if let (Ok(_), Some(prefetcher)) = (&result, self.prefetcher.as_ref()) {
337 prefetcher.start_protx_diff(block, block + 1);
338 }
339
340 result
341 }
342
343 fn verify_instant_lock(
347 &self,
348 instant_lock: &InstantLock,
349 max_height: Option<u32>,
350 ) -> Result<bool, Error> {
351 let request_id = instant_lock.request_id()?.to_string();
352 let transaction_id = instant_lock.txid.to_string();
353 let signature = hex::encode(instant_lock.signature);
354
355 retry!(self
356 .inner
357 .get_verifyislock(&request_id, &transaction_id, &signature, max_height))
358 }
359
360 fn verify_chain_lock(&self, chain_lock: &ChainLock) -> Result<bool, Error> {
362 let block_hash = chain_lock.block_hash.to_string();
363 let signature = hex::encode(chain_lock.signature);
364
365 retry!(self.inner.get_verifychainlock(
366 block_hash.as_str(),
367 &signature,
368 Some(chain_lock.block_height)
369 ))
370 }
371
372 fn masternode_sync_status(&self) -> Result<MnSyncStatus, Error> {
374 retry!(self.inner.mnsync_status())
375 }
376
377 fn send_raw_transaction(&self, transaction: &[u8]) -> Result<Txid, Error> {
378 retry!(self.inner.send_raw_transaction(transaction))
379 }
380
381 fn get_asset_unlock_statuses(
382 &self,
383 indices: &[u64],
384 core_chain_locked_height: u32,
385 ) -> Result<Vec<AssetUnlockStatusResult>, Error> {
386 retry!(self
387 .inner
388 .get_asset_unlock_statuses(indices, Some(core_chain_locked_height)))
389 }
390}