rpc.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. collections::{BTreeMap, HashMap},
  20. sync::Arc,
  21. time::Instant,
  22. };
  23. use url::Url;
  24. use darkfi::{
  25. blockchain::BlockInfo,
  26. rpc::{
  27. client::RpcClient,
  28. jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult},
  29. util::JsonValue,
  30. },
  31. system::{Publisher, StoppableTask},
  32. tx::Transaction,
  33. util::encoding::base64,
  34. Error, Result,
  35. };
  36. use darkfi_dao_contract::model::{DaoBulla, DaoProposalBulla};
  37. use darkfi_money_contract::model::TokenId;
  38. use darkfi_sdk::{
  39. crypto::{
  40. smt::{PoseidonFp, EMPTY_NODES_FP},
  41. ContractId, MerkleTree, SecretKey, DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID,
  42. MONEY_CONTRACT_ID,
  43. },
  44. tx::TransactionHash,
  45. };
  46. use darkfi_serial::{deserialize_async, serialize_async};
  47. use crate::{
  48. cache::{CacheOverlay, CacheSmt, CacheSmtStorage, SLED_MONEY_SMT_TREE},
  49. dao::{SLED_MERKLE_TREES_DAO_DAOS, SLED_MERKLE_TREES_DAO_PROPOSALS},
  50. error::{WalletDbError, WalletDbResult},
  51. money::SLED_MERKLE_TREES_MONEY,
  52. Drk,
  53. };
  54. /// Auxiliary structure holding various in memory caches to use during scan
  55. pub struct ScanCache {
  56. /// The Money Merkle tree containing coins
  57. pub money_tree: MerkleTree,
  58. /// The Money Sparse Merkle tree containing coins nullifiers
  59. pub money_smt: CacheSmt,
  60. /// All our known secrets to decrypt coin notes
  61. pub notes_secrets: Vec<SecretKey>,
  62. /// Our own coins nullifiers
  63. pub owncoins_nullifiers: BTreeMap<[u8; 32], [u8; 32]>,
  64. /// Our own tokens to track freezes
  65. pub own_tokens: Vec<TokenId>,
  66. /// The DAO Merkle tree containing DAO bullas
  67. pub dao_daos_tree: MerkleTree,
  68. /// The DAO Merkle tree containing proposals bullas
  69. pub dao_proposals_tree: MerkleTree,
  70. /// Our own DAOs with their proposals and votes keys
  71. pub own_daos: HashMap<DaoBulla, (Option<SecretKey>, Option<SecretKey>)>,
  72. /// Our own DAOs proposals with their corresponding DAO reference
  73. pub own_proposals: HashMap<DaoProposalBulla, DaoBulla>,
  74. }
  75. impl Drk {
  76. /// Auxiliarry function to generate a new [`ScanCache`] for the
  77. /// wallet.
  78. pub async fn scan_cache(&self) -> Result<ScanCache> {
  79. let money_tree = self.get_money_tree().await?;
  80. let smt_store = CacheSmtStorage::new(CacheOverlay::new(&self.cache)?, SLED_MONEY_SMT_TREE);
  81. let money_smt = CacheSmt::new(smt_store, PoseidonFp::new(), &EMPTY_NODES_FP);
  82. let mut notes_secrets = self.get_money_secrets().await?;
  83. let mut owncoins_nullifiers = BTreeMap::new();
  84. for coin in self.get_coins(true).await? {
  85. owncoins_nullifiers.insert(coin.0.nullifier().to_bytes(), coin.0.coin.to_bytes());
  86. }
  87. let mint_authorities = self.get_mint_authorities().await?;
  88. let mut own_tokens = Vec::with_capacity(mint_authorities.len());
  89. for (token, _, _, _) in mint_authorities {
  90. own_tokens.push(token);
  91. }
  92. let (dao_daos_tree, dao_proposals_tree) = self.get_dao_trees().await?;
  93. let mut own_daos = HashMap::new();
  94. for dao in self.get_daos().await? {
  95. own_daos.insert(
  96. dao.bulla(),
  97. (dao.params.proposals_secret_key, dao.params.votes_secret_key),
  98. );
  99. if let Some(secret_key) = dao.params.notes_secret_key {
  100. notes_secrets.push(secret_key);
  101. }
  102. }
  103. let mut own_proposals = HashMap::new();
  104. for proposal in self.get_proposals().await? {
  105. own_proposals.insert(proposal.bulla(), proposal.proposal.dao_bulla);
  106. }
  107. Ok(ScanCache {
  108. money_tree,
  109. money_smt,
  110. notes_secrets,
  111. owncoins_nullifiers,
  112. own_tokens,
  113. dao_daos_tree,
  114. dao_proposals_tree,
  115. own_daos,
  116. own_proposals,
  117. })
  118. }
  119. /// Subscribes to darkfid's JSON-RPC notification endpoint that serves
  120. /// new confirmed blocks. Upon receiving them, all the transactions are
  121. /// scanned and we check if any of them call the money contract, and if
  122. /// the payments are intended for us. If so, we decrypt them and append
  123. /// the metadata to our wallet. If a reorg block is received, we revert
  124. /// to its previous height and then scan it. We assume that the blocks
  125. /// up to that point are unchanged, since darkfid will just broadcast
  126. /// the sequence after the reorg.
  127. pub async fn subscribe_blocks(
  128. &self,
  129. endpoint: Url,
  130. ex: Arc<smol::Executor<'static>>,
  131. ) -> Result<()> {
  132. // Grab last confirmed block height
  133. let (last_confirmed_height, _) = self.get_last_confirmed_block().await?;
  134. // Handle genesis(0) block
  135. if last_confirmed_height == 0 {
  136. if let Err(e) = self.scan_blocks().await {
  137. return Err(Error::DatabaseError(format!(
  138. "[subscribe_blocks] Scanning from genesis block failed: {e:?}"
  139. )))
  140. }
  141. }
  142. // Grab last confirmed block again
  143. let (last_confirmed_height, last_confirmed_hash) = self.get_last_confirmed_block().await?;
  144. // Grab last scanned block
  145. let (mut last_scanned_height, last_scanned_hash) = match self.get_last_scanned_block() {
  146. Ok(last) => last,
  147. Err(e) => {
  148. return Err(Error::DatabaseError(format!(
  149. "[subscribe_blocks] Retrieving last scanned block failed: {e:?}"
  150. )))
  151. }
  152. };
  153. // Check if other blocks have been created
  154. if last_confirmed_height != last_scanned_height || last_confirmed_hash != last_scanned_hash
  155. {
  156. eprintln!("Warning: Last scanned block is not the last confirmed block.");
  157. eprintln!("You should first fully scan the blockchain, and then subscribe");
  158. return Err(Error::DatabaseError(
  159. "[subscribe_blocks] Blockchain not fully scanned".to_string(),
  160. ))
  161. }
  162. println!("Subscribing to receive notifications of incoming blocks");
  163. let publisher = Publisher::new();
  164. let subscription = publisher.clone().subscribe().await;
  165. let _publisher = publisher.clone();
  166. let _ex = ex.clone();
  167. StoppableTask::new().start(
  168. // Weird hack to prevent lifetimes hell
  169. async move {
  170. let rpc_client = RpcClient::new(endpoint, _ex).await?;
  171. let req = JsonRequest::new("blockchain.subscribe_blocks", JsonValue::Array(vec![]));
  172. rpc_client.subscribe(req, _publisher).await
  173. },
  174. |res| async move {
  175. match res {
  176. Ok(()) => { /* Do nothing */ }
  177. Err(e) => {
  178. eprintln!("[subscribe_blocks] JSON-RPC server error: {e:?}");
  179. publisher
  180. .notify(JsonResult::Error(JsonError::new(
  181. ErrorCode::InternalError,
  182. None,
  183. 0,
  184. )))
  185. .await;
  186. }
  187. }
  188. },
  189. Error::RpcServerStopped,
  190. ex,
  191. );
  192. println!("Detached subscription to background");
  193. println!("All is good. Waiting for block notifications...");
  194. let e = loop {
  195. match subscription.receive().await {
  196. JsonResult::Notification(n) => {
  197. println!("Got Block notification from darkfid subscription");
  198. if n.method != "blockchain.subscribe_blocks" {
  199. break Error::UnexpectedJsonRpc(format!(
  200. "Got foreign notification from darkfid: {}",
  201. n.method
  202. ))
  203. }
  204. // Verify parameters
  205. if !n.params.is_array() {
  206. break Error::UnexpectedJsonRpc(
  207. "Received notification params are not an array".to_string(),
  208. )
  209. }
  210. let params = n.params.get::<Vec<JsonValue>>().unwrap();
  211. if params.is_empty() {
  212. break Error::UnexpectedJsonRpc(
  213. "Notification parameters are empty".to_string(),
  214. )
  215. }
  216. for param in params {
  217. let param = param.get::<String>().unwrap();
  218. let bytes = base64::decode(param).unwrap();
  219. let block: BlockInfo = deserialize_async(&bytes).await?;
  220. println!("Deserialized successfully. Scanning block...");
  221. // Check if a reorg block was received, to reset to its previous
  222. if block.header.height <= last_scanned_height {
  223. let reset_height = block.header.height.saturating_sub(1);
  224. if let Err(e) = self.reset_to_height(reset_height).await {
  225. return Err(Error::DatabaseError(format!(
  226. "[subscribe_blocks] Wallet state reset failed: {e:?}"
  227. )))
  228. }
  229. // Scan genesis again if needed
  230. if reset_height == 0 {
  231. let genesis = match self.get_block_by_height(reset_height).await {
  232. Ok(b) => b,
  233. Err(e) => {
  234. return Err(Error::Custom(format!(
  235. "[subscribe_blocks] RPC client request failed: {e:?}"
  236. )))
  237. }
  238. };
  239. if let Err(e) =
  240. self.scan_block(&mut self.scan_cache().await?, &genesis).await
  241. {
  242. return Err(Error::DatabaseError(format!(
  243. "[subscribe_blocks] Scanning block failed: {e:?}"
  244. )))
  245. };
  246. }
  247. }
  248. if let Err(e) = self.scan_block(&mut self.scan_cache().await?, &block).await
  249. {
  250. return Err(Error::DatabaseError(format!(
  251. "[subscribe_blocks] Scanning block failed: {e:?}"
  252. )))
  253. }
  254. // Set new last scanned block height
  255. last_scanned_height = block.header.height;
  256. }
  257. }
  258. JsonResult::Error(e) => {
  259. // Some error happened in the transmission
  260. break Error::UnexpectedJsonRpc(format!("Got error from JSON-RPC: {e:?}"))
  261. }
  262. x => {
  263. // And this is weird
  264. break Error::UnexpectedJsonRpc(format!(
  265. "Got unexpected data from JSON-RPC: {x:?}"
  266. ))
  267. }
  268. }
  269. };
  270. Err(e)
  271. }
  272. /// `scan_block` will go over over transactions in a block and handle their calls
  273. /// based on the called contract.
  274. async fn scan_block(&self, scan_cache: &mut ScanCache, block: &BlockInfo) -> Result<()> {
  275. // Keep track of the trees we need to update and our wallet
  276. // transactions.
  277. let mut update_money_tree = false;
  278. let mut update_dao_daos_tree = false;
  279. let mut update_dao_proposals_tree = false;
  280. let mut wallet_txs = vec![];
  281. // Scan the block
  282. println!("=======================================");
  283. println!("{}", block.header);
  284. println!("=======================================");
  285. println!("[scan_block] Iterating over {} transactions", block.txs.len());
  286. for tx in block.txs.iter() {
  287. let tx_hash = tx.hash();
  288. let tx_hash_string = tx_hash.to_string();
  289. let mut wallet_tx = false;
  290. println!("[scan_block] Processing transaction: {tx_hash_string}");
  291. for (i, call) in tx.calls.iter().enumerate() {
  292. if call.data.contract_id == *MONEY_CONTRACT_ID {
  293. println!("[scan_block] Found Money contract in call {i}");
  294. let (update_tree, own_tx) = self
  295. .apply_tx_money_data(scan_cache, &i, &tx.calls, &tx_hash_string)
  296. .await?;
  297. if update_tree {
  298. update_money_tree = true;
  299. }
  300. if own_tx {
  301. wallet_tx = true;
  302. }
  303. continue
  304. }
  305. if call.data.contract_id == *DAO_CONTRACT_ID {
  306. println!("[scan_block] Found DAO contract in call {i}");
  307. let (update_daos_tree, update_proposals_tree, own_tx) = self
  308. .apply_tx_dao_data(scan_cache, &call.data.data, &tx_hash, &(i as u8))
  309. .await?;
  310. if update_daos_tree {
  311. update_dao_daos_tree = true;
  312. }
  313. if update_proposals_tree {
  314. update_dao_proposals_tree = true;
  315. }
  316. if own_tx {
  317. wallet_tx = true;
  318. }
  319. continue
  320. }
  321. if call.data.contract_id == *DEPLOYOOOR_CONTRACT_ID {
  322. println!("[scan_block] Found DeployoOor contract in call {i}");
  323. // TODO: implement
  324. continue
  325. }
  326. // TODO: For now we skip non-native contract calls
  327. println!("[scan_block] Found non-native contract in call {i}, skipping.");
  328. }
  329. // If this is our wallet tx we mark it for update
  330. if wallet_tx {
  331. wallet_txs.push(tx);
  332. }
  333. }
  334. // Update money merkle tree, if needed
  335. if update_money_tree {
  336. scan_cache
  337. .money_smt
  338. .store
  339. .overlay
  340. .insert_merkle_tree(SLED_MERKLE_TREES_MONEY, &scan_cache.money_tree)?;
  341. }
  342. // Update dao daos merkle tree, if needed
  343. if update_dao_daos_tree {
  344. scan_cache
  345. .money_smt
  346. .store
  347. .overlay
  348. .insert_merkle_tree(SLED_MERKLE_TREES_DAO_DAOS, &scan_cache.dao_daos_tree)?;
  349. }
  350. // Update dao proposals merkle tree, if needed
  351. if update_dao_proposals_tree {
  352. scan_cache.money_smt.store.overlay.insert_merkle_tree(
  353. SLED_MERKLE_TREES_DAO_PROPOSALS,
  354. &scan_cache.dao_proposals_tree,
  355. )?;
  356. }
  357. // Insert the block record
  358. scan_cache
  359. .money_smt
  360. .store
  361. .overlay
  362. .insert_scanned_block(&block.header.height, &block.header.hash())?;
  363. // Grab the overlay current diff
  364. let diff = scan_cache.money_smt.store.overlay.0.diff(&[])?;
  365. // Insert the state inverse diff record
  366. scan_cache
  367. .money_smt
  368. .store
  369. .overlay
  370. .insert_state_inverse_diff(&block.header.height, &diff.inverse())?;
  371. // Apply the overlay current changes
  372. scan_cache
  373. .money_smt
  374. .store
  375. .overlay
  376. .0
  377. .apply_diff(&scan_cache.money_smt.store.overlay.0.diff(&[])?)?;
  378. // Update wallet transactions records
  379. if let Err(e) = self.put_tx_history_records(&wallet_txs, "Confirmed").await {
  380. return Err(Error::DatabaseError(format!(
  381. "[scan_block] Inserting transaction history records failed: {e:?}"
  382. )))
  383. }
  384. Ok(())
  385. }
  386. /// Scans the blockchain for wallet relevant transactions,
  387. /// starting from the last scanned block. If a reorg has happened,
  388. /// we revert to its previous height and then scan from there.
  389. pub async fn scan_blocks(&self) -> WalletDbResult<()> {
  390. // Grab last scanned block height
  391. let (mut height, hash) = self.get_last_scanned_block()?;
  392. // Grab our last scanned block from darkfid
  393. let block = match self.get_block_by_height(height).await {
  394. Ok(b) => Some(b),
  395. // Check if block was found
  396. Err(Error::JsonRpcError((-32121, _))) => None,
  397. Err(e) => {
  398. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  399. return Err(WalletDbError::GenericError)
  400. }
  401. };
  402. // Check if a reorg has happened
  403. if block.is_none() || hash != block.unwrap().hash().to_string() {
  404. // Find the exact block height the reorg happened
  405. println!("A reorg has happened, finding last known common block...");
  406. height = height.saturating_sub(1);
  407. while height != 0 {
  408. // Grab our scanned block hash for that height
  409. let scanned_block_hash = self.get_scanned_block_hash(&height)?;
  410. // Grab the block from darkfid for that height
  411. let block = match self.get_block_by_height(height).await {
  412. Ok(b) => Some(b),
  413. // Check if block was found
  414. Err(Error::JsonRpcError((-32121, _))) => None,
  415. Err(e) => {
  416. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  417. return Err(WalletDbError::GenericError)
  418. }
  419. };
  420. // Continue to previous one if they don't match
  421. if block.is_none() || scanned_block_hash != block.unwrap().hash().to_string() {
  422. height = height.saturating_sub(1);
  423. continue
  424. }
  425. // Reset to its height
  426. println!("Last common block found: {height} - {scanned_block_hash}");
  427. self.reset_to_height(height).await?;
  428. break
  429. }
  430. }
  431. // If last scanned block is genesis(0) we reset,
  432. // otherwise continue with the next block height.
  433. if height == 0 {
  434. self.reset().await?;
  435. } else {
  436. height += 1;
  437. }
  438. // Generate a new scan cache
  439. let mut scan_cache = match self.scan_cache().await {
  440. Ok(c) => c,
  441. Err(e) => {
  442. eprintln!("[scan_blocks] Generating scan cache failed: {e:?}");
  443. return Err(WalletDbError::GenericError)
  444. }
  445. };
  446. loop {
  447. // Grab last confirmed block
  448. println!("Requested to scan from block number: {height}");
  449. let (last_height, last_hash) = match self.get_last_confirmed_block().await {
  450. Ok(last) => last,
  451. Err(e) => {
  452. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  453. return Err(WalletDbError::GenericError)
  454. }
  455. };
  456. println!("Last confirmed block reported by darkfid: {last_height} - {last_hash}");
  457. // Already scanned last confirmed block
  458. if height > last_height {
  459. return Ok(())
  460. }
  461. while height <= last_height {
  462. println!("Requesting block {height}...");
  463. let block = match self.get_block_by_height(height).await {
  464. Ok(b) => b,
  465. Err(e) => {
  466. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  467. return Err(WalletDbError::GenericError)
  468. }
  469. };
  470. println!("Block {height} received! Scanning block...");
  471. if let Err(e) = self.scan_block(&mut scan_cache, &block).await {
  472. eprintln!("[scan_blocks] Scan block failed: {e:?}");
  473. return Err(WalletDbError::GenericError)
  474. };
  475. height += 1;
  476. }
  477. }
  478. }
  479. // Queries darkfid for last confirmed block.
  480. async fn get_last_confirmed_block(&self) -> Result<(u32, String)> {
  481. let rep = self
  482. .darkfid_daemon_request("blockchain.last_confirmed_block", &JsonValue::Array(vec![]))
  483. .await?;
  484. let params = rep.get::<Vec<JsonValue>>().unwrap();
  485. let height = *params[0].get::<f64>().unwrap() as u32;
  486. let hash = params[1].get::<String>().unwrap().clone();
  487. Ok((height, hash))
  488. }
  489. // Queries darkfid for a block with given height.
  490. async fn get_block_by_height(&self, height: u32) -> Result<BlockInfo> {
  491. let params = self
  492. .darkfid_daemon_request(
  493. "blockchain.get_block",
  494. &JsonValue::Array(vec![JsonValue::String(height.to_string())]),
  495. )
  496. .await?;
  497. let param = params.get::<String>().unwrap();
  498. let bytes = base64::decode(param).unwrap();
  499. let block = deserialize_async(&bytes).await?;
  500. Ok(block)
  501. }
  502. /// Broadcast a given transaction to darkfid and forward onto the network.
  503. /// Returns the transaction ID upon success.
  504. pub async fn broadcast_tx(&self, tx: &Transaction) -> Result<String> {
  505. println!("Broadcasting transaction...");
  506. let params =
  507. JsonValue::Array(vec![JsonValue::String(base64::encode(&serialize_async(tx).await))]);
  508. let rep = self.darkfid_daemon_request("tx.broadcast", &params).await?;
  509. let txid = rep.get::<String>().unwrap().clone();
  510. // Store transactions history record
  511. if let Err(e) = self.put_tx_history_record(tx, "Broadcasted").await {
  512. return Err(Error::DatabaseError(format!(
  513. "[broadcast_tx] Inserting transaction history record failed: {e:?}"
  514. )))
  515. }
  516. Ok(txid)
  517. }
  518. /// Queries darkfid for a tx with given hash.
  519. pub async fn get_tx(&self, tx_hash: &TransactionHash) -> Result<Option<Transaction>> {
  520. let tx_hash_str = tx_hash.to_string();
  521. match self
  522. .darkfid_daemon_request(
  523. "blockchain.get_tx",
  524. &JsonValue::Array(vec![JsonValue::String(tx_hash_str)]),
  525. )
  526. .await
  527. {
  528. Ok(param) => {
  529. let tx_bytes = base64::decode(param.get::<String>().unwrap()).unwrap();
  530. let tx = deserialize_async(&tx_bytes).await?;
  531. Ok(Some(tx))
  532. }
  533. Err(_) => Ok(None),
  534. }
  535. }
  536. /// Simulate the transaction with the state machine.
  537. pub async fn simulate_tx(&self, tx: &Transaction) -> Result<bool> {
  538. let tx_str = base64::encode(&serialize_async(tx).await);
  539. let rep = self
  540. .darkfid_daemon_request(
  541. "tx.simulate",
  542. &JsonValue::Array(vec![JsonValue::String(tx_str)]),
  543. )
  544. .await?;
  545. let is_valid = *rep.get::<bool>().unwrap();
  546. Ok(is_valid)
  547. }
  548. /// Try to fetch zkas bincodes for the given `ContractId`.
  549. pub async fn lookup_zkas(&self, contract_id: &ContractId) -> Result<Vec<(String, Vec<u8>)>> {
  550. let params = JsonValue::Array(vec![JsonValue::String(format!("{contract_id}"))]);
  551. let rep = self.darkfid_daemon_request("blockchain.lookup_zkas", &params).await?;
  552. let params = rep.get::<Vec<JsonValue>>().unwrap();
  553. let mut ret = Vec::with_capacity(params.len());
  554. for param in params {
  555. let zkas_ns = param[0].get::<String>().unwrap().clone();
  556. let zkas_bincode_bytes = base64::decode(param[1].get::<String>().unwrap()).unwrap();
  557. ret.push((zkas_ns, zkas_bincode_bytes));
  558. }
  559. Ok(ret)
  560. }
  561. /// Queries darkfid for given transaction's required fee.
  562. pub async fn get_tx_fee(&self, tx: &Transaction, include_fee: bool) -> Result<u64> {
  563. let params = JsonValue::Array(vec![
  564. JsonValue::String(base64::encode(&serialize_async(tx).await)),
  565. JsonValue::Boolean(include_fee),
  566. ]);
  567. let rep = self.darkfid_daemon_request("tx.calculate_fee", &params).await?;
  568. let fee = *rep.get::<f64>().unwrap() as u64;
  569. Ok(fee)
  570. }
  571. /// Queries darkfid for current best fork next height.
  572. pub async fn get_next_block_height(&self) -> Result<u32> {
  573. let rep = self
  574. .darkfid_daemon_request(
  575. "blockchain.best_fork_next_block_height",
  576. &JsonValue::Array(vec![]),
  577. )
  578. .await?;
  579. let next_height = *rep.get::<f64>().unwrap() as u32;
  580. Ok(next_height)
  581. }
  582. /// Queries darkfid for currently configured block target time.
  583. pub async fn get_block_target(&self) -> Result<u32> {
  584. let rep = self
  585. .darkfid_daemon_request("blockchain.block_target", &JsonValue::Array(vec![]))
  586. .await?;
  587. let next_height = *rep.get::<f64>().unwrap() as u32;
  588. Ok(next_height)
  589. }
  590. /// Auxiliary function to ping configured darkfid daemon for liveness.
  591. pub async fn ping(&self) -> Result<()> {
  592. println!("Executing ping request to darkfid...");
  593. let latency = Instant::now();
  594. let rep = self.darkfid_daemon_request("ping", &JsonValue::Array(vec![])).await?;
  595. let latency = latency.elapsed();
  596. println!("Got reply: {rep:?}");
  597. println!("Latency: {latency:?}");
  598. Ok(())
  599. }
  600. /// Auxiliary function to execute a request towards the configured darkfid daemon JSON-RPC endpoint.
  601. pub async fn darkfid_daemon_request(
  602. &self,
  603. method: &str,
  604. params: &JsonValue,
  605. ) -> Result<JsonValue> {
  606. let Some(ref rpc_client) = self.rpc_client else { return Err(Error::RpcClientStopped) };
  607. let req = JsonRequest::new(method, params.clone());
  608. let rep = rpc_client.request(req).await?;
  609. Ok(rep)
  610. }
  611. /// Auxiliary function to stop current JSON-RPC client, if its initialized.
  612. pub async fn stop_rpc_client(&self) -> Result<()> {
  613. if let Some(ref rpc_client) = self.rpc_client {
  614. rpc_client.stop().await;
  615. };
  616. Ok(())
  617. }
  618. }