rpc.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  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(
  296. scan_cache,
  297. &i,
  298. &tx.calls,
  299. &tx_hash_string,
  300. &block.header.height,
  301. )
  302. .await?;
  303. if update_tree {
  304. update_money_tree = true;
  305. }
  306. if own_tx {
  307. wallet_tx = true;
  308. }
  309. continue
  310. }
  311. if call.data.contract_id == *DAO_CONTRACT_ID {
  312. println!("[scan_block] Found DAO contract in call {i}");
  313. let (update_daos_tree, update_proposals_tree, own_tx) = self
  314. .apply_tx_dao_data(scan_cache, &call.data.data, &tx_hash, &(i as u8))
  315. .await?;
  316. if update_daos_tree {
  317. update_dao_daos_tree = true;
  318. }
  319. if update_proposals_tree {
  320. update_dao_proposals_tree = true;
  321. }
  322. if own_tx {
  323. wallet_tx = true;
  324. }
  325. continue
  326. }
  327. if call.data.contract_id == *DEPLOYOOOR_CONTRACT_ID {
  328. println!("[scan_block] Found DeployoOor contract in call {i}");
  329. // TODO: implement
  330. continue
  331. }
  332. // TODO: For now we skip non-native contract calls
  333. println!("[scan_block] Found non-native contract in call {i}, skipping.");
  334. }
  335. // If this is our wallet tx we mark it for update
  336. if wallet_tx {
  337. wallet_txs.push(tx);
  338. }
  339. }
  340. // Update money merkle tree, if needed
  341. if update_money_tree {
  342. scan_cache
  343. .money_smt
  344. .store
  345. .overlay
  346. .insert_merkle_tree(SLED_MERKLE_TREES_MONEY, &scan_cache.money_tree)?;
  347. }
  348. // Update dao daos merkle tree, if needed
  349. if update_dao_daos_tree {
  350. scan_cache
  351. .money_smt
  352. .store
  353. .overlay
  354. .insert_merkle_tree(SLED_MERKLE_TREES_DAO_DAOS, &scan_cache.dao_daos_tree)?;
  355. }
  356. // Update dao proposals merkle tree, if needed
  357. if update_dao_proposals_tree {
  358. scan_cache.money_smt.store.overlay.insert_merkle_tree(
  359. SLED_MERKLE_TREES_DAO_PROPOSALS,
  360. &scan_cache.dao_proposals_tree,
  361. )?;
  362. }
  363. // Insert the block record
  364. scan_cache
  365. .money_smt
  366. .store
  367. .overlay
  368. .insert_scanned_block(&block.header.height, &block.header.hash())?;
  369. // Grab the overlay current diff
  370. let diff = scan_cache.money_smt.store.overlay.0.diff(&[])?;
  371. // Insert the state inverse diff record
  372. scan_cache
  373. .money_smt
  374. .store
  375. .overlay
  376. .insert_state_inverse_diff(&block.header.height, &diff.inverse())?;
  377. // Apply the overlay current changes
  378. scan_cache
  379. .money_smt
  380. .store
  381. .overlay
  382. .0
  383. .apply_diff(&scan_cache.money_smt.store.overlay.0.diff(&[])?)?;
  384. // Update wallet transactions records
  385. if let Err(e) =
  386. self.put_tx_history_records(&wallet_txs, "Confirmed", Some(block.header.height)).await
  387. {
  388. return Err(Error::DatabaseError(format!(
  389. "[scan_block] Inserting transaction history records failed: {e:?}"
  390. )))
  391. }
  392. Ok(())
  393. }
  394. /// Scans the blockchain for wallet relevant transactions,
  395. /// starting from the last scanned block. If a reorg has happened,
  396. /// we revert to its previous height and then scan from there.
  397. pub async fn scan_blocks(&self) -> WalletDbResult<()> {
  398. // Grab last scanned block height
  399. let (mut height, hash) = self.get_last_scanned_block()?;
  400. // Grab our last scanned block from darkfid
  401. let block = match self.get_block_by_height(height).await {
  402. Ok(b) => Some(b),
  403. // Check if block was found
  404. Err(Error::JsonRpcError((-32121, _))) => None,
  405. Err(e) => {
  406. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  407. return Err(WalletDbError::GenericError)
  408. }
  409. };
  410. // Check if a reorg has happened
  411. if block.is_none() || hash != block.unwrap().hash().to_string() {
  412. // Find the exact block height the reorg happened
  413. println!("A reorg has happened, finding last known common block...");
  414. height = height.saturating_sub(1);
  415. while height != 0 {
  416. // Grab our scanned block hash for that height
  417. let scanned_block_hash = self.get_scanned_block_hash(&height)?;
  418. // Grab the block from darkfid for that height
  419. let block = match self.get_block_by_height(height).await {
  420. Ok(b) => Some(b),
  421. // Check if block was found
  422. Err(Error::JsonRpcError((-32121, _))) => None,
  423. Err(e) => {
  424. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  425. return Err(WalletDbError::GenericError)
  426. }
  427. };
  428. // Continue to previous one if they don't match
  429. if block.is_none() || scanned_block_hash != block.unwrap().hash().to_string() {
  430. height = height.saturating_sub(1);
  431. continue
  432. }
  433. // Reset to its height
  434. println!("Last common block found: {height} - {scanned_block_hash}");
  435. self.reset_to_height(height).await?;
  436. break
  437. }
  438. }
  439. // If last scanned block is genesis(0) we reset,
  440. // otherwise continue with the next block height.
  441. if height == 0 {
  442. self.reset().await?;
  443. } else {
  444. height += 1;
  445. }
  446. // Generate a new scan cache
  447. let mut scan_cache = match self.scan_cache().await {
  448. Ok(c) => c,
  449. Err(e) => {
  450. eprintln!("[scan_blocks] Generating scan cache failed: {e:?}");
  451. return Err(WalletDbError::GenericError)
  452. }
  453. };
  454. loop {
  455. // Grab last confirmed block
  456. println!("Requested to scan from block number: {height}");
  457. let (last_height, last_hash) = match self.get_last_confirmed_block().await {
  458. Ok(last) => last,
  459. Err(e) => {
  460. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  461. return Err(WalletDbError::GenericError)
  462. }
  463. };
  464. println!("Last confirmed block reported by darkfid: {last_height} - {last_hash}");
  465. // Already scanned last confirmed block
  466. if height > last_height {
  467. return Ok(())
  468. }
  469. while height <= last_height {
  470. println!("Requesting block {height}...");
  471. let block = match self.get_block_by_height(height).await {
  472. Ok(b) => b,
  473. Err(e) => {
  474. eprintln!("[scan_blocks] RPC client request failed: {e:?}");
  475. return Err(WalletDbError::GenericError)
  476. }
  477. };
  478. println!("Block {height} received! Scanning block...");
  479. if let Err(e) = self.scan_block(&mut scan_cache, &block).await {
  480. eprintln!("[scan_blocks] Scan block failed: {e:?}");
  481. return Err(WalletDbError::GenericError)
  482. };
  483. height += 1;
  484. }
  485. }
  486. }
  487. // Queries darkfid for last confirmed block.
  488. async fn get_last_confirmed_block(&self) -> Result<(u32, String)> {
  489. let rep = self
  490. .darkfid_daemon_request("blockchain.last_confirmed_block", &JsonValue::Array(vec![]))
  491. .await?;
  492. let params = rep.get::<Vec<JsonValue>>().unwrap();
  493. let height = *params[0].get::<f64>().unwrap() as u32;
  494. let hash = params[1].get::<String>().unwrap().clone();
  495. Ok((height, hash))
  496. }
  497. // Queries darkfid for a block with given height.
  498. async fn get_block_by_height(&self, height: u32) -> Result<BlockInfo> {
  499. let params = self
  500. .darkfid_daemon_request(
  501. "blockchain.get_block",
  502. &JsonValue::Array(vec![JsonValue::String(height.to_string())]),
  503. )
  504. .await?;
  505. let param = params.get::<String>().unwrap();
  506. let bytes = base64::decode(param).unwrap();
  507. let block = deserialize_async(&bytes).await?;
  508. Ok(block)
  509. }
  510. /// Broadcast a given transaction to darkfid and forward onto the network.
  511. /// Returns the transaction ID upon success.
  512. pub async fn broadcast_tx(&self, tx: &Transaction) -> Result<String> {
  513. println!("Broadcasting transaction...");
  514. let params =
  515. JsonValue::Array(vec![JsonValue::String(base64::encode(&serialize_async(tx).await))]);
  516. let rep = self.darkfid_daemon_request("tx.broadcast", &params).await?;
  517. let txid = rep.get::<String>().unwrap().clone();
  518. // Store transactions history record
  519. if let Err(e) = self.put_tx_history_record(tx, "Broadcasted", None).await {
  520. return Err(Error::DatabaseError(format!(
  521. "[broadcast_tx] Inserting transaction history record failed: {e:?}"
  522. )))
  523. }
  524. Ok(txid)
  525. }
  526. /// Queries darkfid for a tx with given hash.
  527. pub async fn get_tx(&self, tx_hash: &TransactionHash) -> Result<Option<Transaction>> {
  528. let tx_hash_str = tx_hash.to_string();
  529. match self
  530. .darkfid_daemon_request(
  531. "blockchain.get_tx",
  532. &JsonValue::Array(vec![JsonValue::String(tx_hash_str)]),
  533. )
  534. .await
  535. {
  536. Ok(param) => {
  537. let tx_bytes = base64::decode(param.get::<String>().unwrap()).unwrap();
  538. let tx = deserialize_async(&tx_bytes).await?;
  539. Ok(Some(tx))
  540. }
  541. Err(_) => Ok(None),
  542. }
  543. }
  544. /// Simulate the transaction with the state machine.
  545. pub async fn simulate_tx(&self, tx: &Transaction) -> Result<bool> {
  546. let tx_str = base64::encode(&serialize_async(tx).await);
  547. let rep = self
  548. .darkfid_daemon_request(
  549. "tx.simulate",
  550. &JsonValue::Array(vec![JsonValue::String(tx_str)]),
  551. )
  552. .await?;
  553. let is_valid = *rep.get::<bool>().unwrap();
  554. Ok(is_valid)
  555. }
  556. /// Try to fetch zkas bincodes for the given `ContractId`.
  557. pub async fn lookup_zkas(&self, contract_id: &ContractId) -> Result<Vec<(String, Vec<u8>)>> {
  558. let params = JsonValue::Array(vec![JsonValue::String(format!("{contract_id}"))]);
  559. let rep = self.darkfid_daemon_request("blockchain.lookup_zkas", &params).await?;
  560. let params = rep.get::<Vec<JsonValue>>().unwrap();
  561. let mut ret = Vec::with_capacity(params.len());
  562. for param in params {
  563. let zkas_ns = param[0].get::<String>().unwrap().clone();
  564. let zkas_bincode_bytes = base64::decode(param[1].get::<String>().unwrap()).unwrap();
  565. ret.push((zkas_ns, zkas_bincode_bytes));
  566. }
  567. Ok(ret)
  568. }
  569. /// Queries darkfid for given transaction's required fee.
  570. pub async fn get_tx_fee(&self, tx: &Transaction, include_fee: bool) -> Result<u64> {
  571. let params = JsonValue::Array(vec![
  572. JsonValue::String(base64::encode(&serialize_async(tx).await)),
  573. JsonValue::Boolean(include_fee),
  574. ]);
  575. let rep = self.darkfid_daemon_request("tx.calculate_fee", &params).await?;
  576. let fee = *rep.get::<f64>().unwrap() as u64;
  577. Ok(fee)
  578. }
  579. /// Queries darkfid for current best fork next height.
  580. pub async fn get_next_block_height(&self) -> Result<u32> {
  581. let rep = self
  582. .darkfid_daemon_request(
  583. "blockchain.best_fork_next_block_height",
  584. &JsonValue::Array(vec![]),
  585. )
  586. .await?;
  587. let next_height = *rep.get::<f64>().unwrap() as u32;
  588. Ok(next_height)
  589. }
  590. /// Queries darkfid for currently configured block target time.
  591. pub async fn get_block_target(&self) -> Result<u32> {
  592. let rep = self
  593. .darkfid_daemon_request("blockchain.block_target", &JsonValue::Array(vec![]))
  594. .await?;
  595. let next_height = *rep.get::<f64>().unwrap() as u32;
  596. Ok(next_height)
  597. }
  598. /// Auxiliary function to ping configured darkfid daemon for liveness.
  599. pub async fn ping(&self) -> Result<()> {
  600. println!("Executing ping request to darkfid...");
  601. let latency = Instant::now();
  602. let rep = self.darkfid_daemon_request("ping", &JsonValue::Array(vec![])).await?;
  603. let latency = latency.elapsed();
  604. println!("Got reply: {rep:?}");
  605. println!("Latency: {latency:?}");
  606. Ok(())
  607. }
  608. /// Auxiliary function to execute a request towards the configured darkfid daemon JSON-RPC endpoint.
  609. pub async fn darkfid_daemon_request(
  610. &self,
  611. method: &str,
  612. params: &JsonValue,
  613. ) -> Result<JsonValue> {
  614. let Some(ref rpc_client) = self.rpc_client else { return Err(Error::RpcClientStopped) };
  615. let req = JsonRequest::new(method, params.clone());
  616. let rep = rpc_client.request(req).await?;
  617. Ok(rep)
  618. }
  619. /// Auxiliary function to stop current JSON-RPC client, if its initialized.
  620. pub async fn stop_rpc_client(&self) -> Result<()> {
  621. if let Some(ref rpc_client) = self.rpc_client {
  622. rpc_client.stop().await;
  623. };
  624. Ok(())
  625. }
  626. }