1use dpp::dashcore::ephemerealdata::chain_lock::ChainLock;
2use dpp::dashcore::{Block, BlockHash, QuorumHash, Transaction, Txid};
3use dpp::dashcore::{Header, InstantLock};
4use dpp::dashcore_rpc::dashcore_rpc_json::{
5 AssetUnlockStatusResult, ExtendedQuorumDetails, ExtendedQuorumListResult, GetChainTipsResult,
6 MasternodeListDiff, MnSyncStatus, QuorumInfoResult, QuorumType, SoftforkInfo,
7};
8use dpp::dashcore_rpc::json::GetRawTransactionResult;
9use dpp::dashcore_rpc::{Auth, Client, Error, RpcApi};
10use dpp::prelude::TimestampMillis;
11use serde_json::Value;
12use std::collections::HashMap;
13use std::time::Duration;
14
15pub type QuorumListExtendedInfo = HashMap<QuorumHash, ExtendedQuorumDetails>;
17
18pub type CoreHeight = u32;
20#[cfg_attr(any(feature = "mocks", test), mockall::automock)]
22pub trait CoreRPCLike {
23 fn get_block_hash(&self, height: CoreHeight) -> Result<BlockHash, Error>;
25
26 fn get_block_header(&self, block_hash: &BlockHash) -> Result<Header, Error>;
28
29 fn get_block_time_from_height(&self, height: CoreHeight) -> Result<TimestampMillis, Error>;
31
32 fn get_best_chain_lock(&self) -> Result<ChainLock, Error>;
34
35 fn submit_chain_lock(&self, chain_lock: &ChainLock) -> Result<u32, Error>;
37
38 fn get_transaction(&self, tx_id: &Txid) -> Result<Transaction, Error>;
40
41 fn get_asset_unlock_statuses(
43 &self,
44 indices: &[u64],
45 core_chain_locked_height: u32,
46 ) -> Result<Vec<AssetUnlockStatusResult>, Error>;
47
48 fn get_transaction_extended_info(&self, tx_id: &Txid)
50 -> Result<GetRawTransactionResult, Error>;
51
52 fn get_optional_transaction_extended_info(
55 &self,
56 transaction_id: &Txid,
57 ) -> Result<Option<GetRawTransactionResult>, Error> {
58 match self.get_transaction_extended_info(transaction_id) {
59 Ok(transaction_info) => Ok(Some(transaction_info)),
60 Err(Error::JsonRpc(dpp::dashcore_rpc::jsonrpc::error::Error::Rpc(
62 dpp::dashcore_rpc::jsonrpc::error::RpcError {
63 code: CORE_RPC_INVALID_ADDRESS_OR_KEY,
64 ..
65 },
66 ))) => Ok(None),
67 Err(e) => Err(e),
68 }
69 }
70
71 fn get_fork_info(&self, name: &str) -> Result<Option<SoftforkInfo>, Error>;
73
74 fn get_block(&self, block_hash: &BlockHash) -> Result<Block, Error>;
76
77 fn get_block_json(&self, block_hash: &BlockHash) -> Result<Value, Error>;
79
80 fn get_chain_tips(&self) -> Result<GetChainTipsResult, Error>;
82
83 fn get_quorum_listextended(
87 &self,
88 height: Option<CoreHeight>,
89 ) -> Result<ExtendedQuorumListResult, Error>;
90
91 fn get_quorum_info(
95 &self,
96 quorum_type: QuorumType,
97 hash: &QuorumHash,
98 include_secret_key_share: Option<bool>,
99 ) -> Result<QuorumInfoResult, Error>;
100
101 fn get_protx_diff_with_masternodes(
103 &self,
104 base_block: Option<u32>,
105 block: u32,
106 ) -> Result<MasternodeListDiff, Error>;
107
108 fn verify_instant_lock(
115 &self,
116 instant_lock: &InstantLock,
117 max_height: Option<u32>,
118 ) -> Result<bool, Error>;
119
120 fn verify_chain_lock(&self, chain_lock: &ChainLock) -> Result<bool, Error>;
122
123 fn masternode_sync_status(&self) -> Result<MnSyncStatus, Error>;
125
126 fn send_raw_transaction(&self, transaction: &[u8]) -> Result<Txid, Error>;
128}
129
130#[derive(Debug)]
131pub struct DefaultCoreRPC {
133 inner: Client,
134}
135
136pub const CORE_RPC_TX_CONSENSUS_ERROR: i32 = -26;
140pub const CORE_RPC_TX_ALREADY_IN_CHAIN: i32 = -27;
142pub const CORE_RPC_ERROR_IN_WARMUP: i32 = -28;
144pub const CORE_RPC_CLIENT_NOT_CONNECTED: i32 = -9;
146pub const CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD: i32 = -10;
148pub const CORE_RPC_PARSE_ERROR: i32 = -32700;
150pub const CORE_RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
152pub const CORE_RPC_INVALID_PARAMETER: i32 = -8;
154
155macro_rules! retry {
156 ($action:expr) => {{
157 const MAX_RETRIES: u32 = 4;
159 const FIB_MULTIPLIER: u64 = 1;
161
162 fn fibonacci(n: u32) -> u64 {
163 match n {
164 0 => 0,
165 1 => 1,
166 _ => fibonacci(n - 1) + fibonacci(n - 2),
167 }
168 }
169
170 let mut last_err = None;
171 let result = (0..MAX_RETRIES).find_map(|i| {
172 match $action {
173 Ok(result) => Some(Ok(result)),
174 Err(e) => {
175 match e {
176 dpp::dashcore_rpc::Error::JsonRpc(
177 dpp::dashcore_rpc::jsonrpc::error::Error::Transport(_)
179 | dpp::dashcore_rpc::jsonrpc::error::Error::Rpc(
180 dpp::dashcore_rpc::jsonrpc::error::RpcError {
182 code:
183 CORE_RPC_ERROR_IN_WARMUP
184 | CORE_RPC_CLIENT_NOT_CONNECTED
185 | CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD,
186 ..
187 },
188 ),
189 ) => {
190 last_err = Some(e);
192 let delay = fibonacci(i + 2) * FIB_MULTIPLIER;
193 std::thread::sleep(Duration::from_secs(delay));
194 None
195 }
196 _ => Some(Err(e)),
197 }
198 }
199 }
200 });
201
202 result.unwrap_or_else(|| Err(last_err.unwrap()))
203 }};
204}
205
206impl DefaultCoreRPC {
207 pub fn open(url: &str, username: String, password: String) -> Result<Self, Error> {
209 Ok(DefaultCoreRPC {
210 inner: Client::new(url, Auth::UserPass(username, password))?,
211 })
212 }
213}
214
215impl CoreRPCLike for DefaultCoreRPC {
216 fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
217 retry!(self.inner.get_block_hash(height))
218 }
219
220 fn get_block_header(&self, block_hash: &BlockHash) -> Result<Header, Error> {
221 retry!(self.inner.get_block_header(block_hash))
222 }
223
224 fn get_block_time_from_height(&self, height: CoreHeight) -> Result<TimestampMillis, Error> {
225 let block_hash = self.get_block_hash(height)?;
226 let block_header = self.get_block_header(&block_hash)?;
227 let block_time = block_header.time as u64 * 1000;
228 Ok(block_time)
229 }
230
231 fn get_best_chain_lock(&self) -> Result<ChainLock, Error> {
232 retry!(self.inner.get_best_chain_lock())
233 }
234
235 fn submit_chain_lock(&self, chain_lock: &ChainLock) -> Result<u32, Error> {
236 retry!(self.inner.submit_chain_lock(chain_lock))
237 }
238
239 fn get_transaction(&self, tx_id: &Txid) -> Result<Transaction, Error> {
240 retry!(self.inner.get_raw_transaction(tx_id, None))
241 }
242
243 fn get_transaction_extended_info(
244 &self,
245 tx_id: &Txid,
246 ) -> Result<GetRawTransactionResult, Error> {
247 retry!(self.inner.get_raw_transaction_info(tx_id, None))
248 }
249
250 fn get_fork_info(&self, name: &str) -> Result<Option<SoftforkInfo>, Error> {
251 retry!(self
252 .inner
253 .get_blockchain_info()
254 .map(|blockchain_info| blockchain_info.softforks.get(name).cloned()))
255 }
256
257 fn get_block(&self, block_hash: &BlockHash) -> Result<Block, Error> {
258 retry!(self.inner.get_block(block_hash))
259 }
260
261 fn get_block_json(&self, block_hash: &BlockHash) -> Result<Value, Error> {
262 retry!(self.inner.get_block_json(block_hash))
263 }
264
265 fn get_chain_tips(&self) -> Result<GetChainTipsResult, Error> {
266 retry!(self.inner.get_chain_tips())
267 }
268
269 fn get_quorum_listextended(
270 &self,
271 height: Option<CoreHeight>,
272 ) -> Result<ExtendedQuorumListResult, Error> {
273 retry!(self.inner.get_quorum_listextended_reversed(height))
274 }
275
276 fn get_quorum_info(
277 &self,
278 quorum_type: QuorumType,
279 hash: &QuorumHash,
280 include_secret_key_share: Option<bool>,
281 ) -> Result<QuorumInfoResult, Error> {
282 retry!(self
283 .inner
284 .get_quorum_info_reversed(quorum_type, hash, include_secret_key_share))
285 }
286
287 fn get_protx_diff_with_masternodes(
288 &self,
289 base_block: Option<u32>,
290 block: u32,
291 ) -> Result<MasternodeListDiff, Error> {
292 retry!(self
293 .inner
294 .get_protx_listdiff(base_block.unwrap_or(1), block))
295 }
296
297 fn verify_instant_lock(
301 &self,
302 instant_lock: &InstantLock,
303 max_height: Option<u32>,
304 ) -> Result<bool, Error> {
305 let request_id = instant_lock.request_id()?.to_string();
306 let transaction_id = instant_lock.txid.to_string();
307 let signature = hex::encode(instant_lock.signature);
308
309 retry!(self
310 .inner
311 .get_verifyislock(&request_id, &transaction_id, &signature, max_height))
312 }
313
314 fn verify_chain_lock(&self, chain_lock: &ChainLock) -> Result<bool, Error> {
316 let block_hash = chain_lock.block_hash.to_string();
317 let signature = hex::encode(chain_lock.signature);
318
319 retry!(self.inner.get_verifychainlock(
320 block_hash.as_str(),
321 &signature,
322 Some(chain_lock.block_height)
323 ))
324 }
325
326 fn masternode_sync_status(&self) -> Result<MnSyncStatus, Error> {
328 retry!(self.inner.mnsync_status())
329 }
330
331 fn send_raw_transaction(&self, transaction: &[u8]) -> Result<Txid, Error> {
332 retry!(self.inner.send_raw_transaction(transaction))
333 }
334
335 fn get_asset_unlock_statuses(
336 &self,
337 indices: &[u64],
338 core_chain_locked_height: u32,
339 ) -> Result<Vec<AssetUnlockStatusResult>, Error> {
340 retry!(self
341 .inner
342 .get_asset_unlock_statuses(indices, Some(core_chain_locked_height)))
343 }
344}