drive_abci/rpc/
core.rs

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
15/// Information returned by QuorumListExtended
16pub type QuorumListExtendedInfo = HashMap<QuorumHash, ExtendedQuorumDetails>;
17
18/// Core height must be of type u32 (Platform heights are u64)
19pub type CoreHeight = u32;
20/// Core RPC interface
21#[cfg_attr(any(feature = "mocks", test), mockall::automock)]
22pub trait CoreRPCLike {
23    /// Get block hash by height
24    fn get_block_hash(&self, height: CoreHeight) -> Result<BlockHash, Error>;
25
26    /// Get block hash by height
27    fn get_block_header(&self, block_hash: &BlockHash) -> Result<Header, Error>;
28
29    /// Get block time of a chain locked core height
30    fn get_block_time_from_height(&self, height: CoreHeight) -> Result<TimestampMillis, Error>;
31
32    /// Get the best chain lock
33    fn get_best_chain_lock(&self) -> Result<ChainLock, Error>;
34
35    /// Submit a chain lock
36    fn submit_chain_lock(&self, chain_lock: &ChainLock) -> Result<u32, Error>;
37
38    /// Get transaction
39    fn get_transaction(&self, tx_id: &Txid) -> Result<Transaction, Error>;
40
41    /// Get asset unlock statuses
42    fn get_asset_unlock_statuses(
43        &self,
44        indices: &[u64],
45        core_chain_locked_height: u32,
46    ) -> Result<Vec<AssetUnlockStatusResult>, Error>;
47
48    /// Get transaction
49    fn get_transaction_extended_info(&self, tx_id: &Txid)
50        -> Result<GetRawTransactionResult, Error>;
51
52    /// Get optional transaction extended info
53    /// Returns None if transaction doesn't exists
54    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            // Return None if transaction with specified tx id is not present
61            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    /// Get block by hash
72    fn get_fork_info(&self, name: &str) -> Result<Option<SoftforkInfo>, Error>;
73
74    /// Get block by hash
75    fn get_block(&self, block_hash: &BlockHash) -> Result<Block, Error>;
76
77    /// Get block by hash in JSON format
78    fn get_block_json(&self, block_hash: &BlockHash) -> Result<Value, Error>;
79
80    /// Get chain tips
81    fn get_chain_tips(&self) -> Result<GetChainTipsResult, Error>;
82
83    /// Get list of quorums by type at a given height.
84    ///
85    /// See <https://dashcore.readme.io/v19.0.0/docs/core-api-ref-remote-procedure-calls-evo#quorum-listextended>
86    fn get_quorum_listextended(
87        &self,
88        height: Option<CoreHeight>,
89    ) -> Result<ExtendedQuorumListResult, Error>;
90
91    /// Get quorum information.
92    ///
93    /// See <https://dashcore.readme.io/v19.0.0/docs/core-api-ref-remote-procedure-calls-evo#quorum-info>
94    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    /// Get the difference in masternode list, return masternodes as diff elements
102    fn get_protx_diff_with_masternodes(
103        &self,
104        base_block: Option<u32>,
105        block: u32,
106    ) -> Result<MasternodeListDiff, Error>;
107
108    // /// Get the detailed information about a deterministic masternode
109    // fn get_protx_info(&self, pro_tx_hash: &ProTxHash) -> Result<ProTxInfo, Error>;
110
111    /// Verify Instant Lock signature
112    /// If `max_height` is provided the chain lock will be verified
113    /// against quorums available at this height
114    fn verify_instant_lock(
115        &self,
116        instant_lock: &InstantLock,
117        max_height: Option<u32>,
118    ) -> Result<bool, Error>;
119
120    /// Verify a chain lock signature
121    fn verify_chain_lock(&self, chain_lock: &ChainLock) -> Result<bool, Error>;
122
123    /// Returns masternode sync status
124    fn masternode_sync_status(&self) -> Result<MnSyncStatus, Error>;
125
126    /// Sends raw transaction to the network
127    fn send_raw_transaction(&self, transaction: &[u8]) -> Result<Txid, Error>;
128}
129
130#[derive(Debug)]
131/// Default implementation of Dash Core RPC using DashCoreRPC client
132pub struct DefaultCoreRPC {
133    inner: Client,
134}
135
136// TODO: Create errors for these error codes in dashcore_rpc
137
138/// TX is invalid due to consensus rules
139pub const CORE_RPC_TX_CONSENSUS_ERROR: i32 = -26;
140/// Tx already broadcasted and included in the chain
141pub const CORE_RPC_TX_ALREADY_IN_CHAIN: i32 = -27;
142/// Client still warming up
143pub const CORE_RPC_ERROR_IN_WARMUP: i32 = -28;
144/// Dash is not connected
145pub const CORE_RPC_CLIENT_NOT_CONNECTED: i32 = -9;
146/// Still downloading initial blocks
147pub const CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD: i32 = -10;
148/// Parse error
149pub const CORE_RPC_PARSE_ERROR: i32 = -32700;
150/// Invalid address or key
151pub const CORE_RPC_INVALID_ADDRESS_OR_KEY: i32 = -5;
152/// Invalid, missing or duplicate parameter
153pub const CORE_RPC_INVALID_PARAMETER: i32 = -8;
154
155macro_rules! retry {
156    ($action:expr) => {{
157        /// Maximum number of retry attempts
158        const MAX_RETRIES: u32 = 4;
159        /// // Multiplier for Fibonacci sequence
160        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                            // Retry on transport connection error
178                            dpp::dashcore_rpc::jsonrpc::error::Error::Transport(_)
179                            | dpp::dashcore_rpc::jsonrpc::error::Error::Rpc(
180                                // Retry on Core RPC "not ready" errors
181                                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                            // Delay before next try
191                            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    /// Create new instance
208    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    /// Verify Instant Lock signature
298    /// If `max_height` is provided the chain lock will be verified
299    /// against quorums available at this height
300    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    /// Verify a chain lock signature
315    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    /// Returns masternode sync status
327    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}