rpc_wallet.rs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 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::collections::HashMap;
  19. use anyhow::{anyhow, Result};
  20. use darkfi::{rpc::jsonrpc::JsonRequest, util::parse::encode_base10, wallet::walletdb::QueryType};
  21. use darkfi_dao_contract::dao_client::{
  22. DAO_DAOS_COL_APPROVAL_RATIO_BASE, DAO_DAOS_COL_APPROVAL_RATIO_QUOT, DAO_DAOS_COL_BULLA_BLIND,
  23. DAO_DAOS_COL_CALL_INDEX, DAO_DAOS_COL_DAO_ID, DAO_DAOS_COL_GOV_TOKEN_ID,
  24. DAO_DAOS_COL_LEAF_POSITION, DAO_DAOS_COL_NAME, DAO_DAOS_COL_PROPOSER_LIMIT,
  25. DAO_DAOS_COL_QUORUM, DAO_DAOS_COL_SECRET, DAO_DAOS_COL_TX_HASH, DAO_DAOS_TABLE,
  26. DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE, DAO_TREES_TABLE,
  27. };
  28. use darkfi_money_contract::client::{
  29. Coin, Note, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
  30. MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO,
  31. MONEY_COINS_COL_NULLIFIER, MONEY_COINS_COL_SECRET, MONEY_COINS_COL_SERIAL,
  32. MONEY_COINS_COL_SPEND_HOOK, MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID,
  33. MONEY_COINS_COL_USER_DATA, MONEY_COINS_COL_VALUE, MONEY_COINS_COL_VALUE_BLIND,
  34. MONEY_COINS_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE,
  35. MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE,
  36. MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
  37. };
  38. use darkfi_sdk::{
  39. crypto::{
  40. constants::MERKLE_DEPTH, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey, SecretKey,
  41. TokenId,
  42. },
  43. incrementalmerkletree,
  44. incrementalmerkletree::bridgetree::BridgeTree,
  45. pasta::pallas,
  46. };
  47. use darkfi_serial::{deserialize, serialize};
  48. use prettytable::{format, row, Table};
  49. use rand::rngs::OsRng;
  50. use serde_json::json;
  51. use super::Drk;
  52. use crate::dao::Dao;
  53. impl Drk {
  54. /// Initialize wallet with tables for the Money Contract.
  55. async fn wallet_initialize_money(&self) -> Result<()> {
  56. let wallet_schema = include_str!("../../../src/contract/money/wallet.sql");
  57. // We perform a request to darkfid with the schema to initialize
  58. // the necessary tables in the wallet.
  59. let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
  60. let rep = self.rpc_client.request(req).await?;
  61. if rep == true {
  62. println!("Successfully initialized wallet schema for the Money Contract");
  63. } else {
  64. println!("Got unxpected reply from darkfid: {}", rep);
  65. }
  66. // Check if we have to initialize the Merkle tree.
  67. // We check if we find a row in the tree table, and if not, we create
  68. // a new tree and push it into the table.
  69. let mut tree_needs_init = false;
  70. let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
  71. let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
  72. let req = JsonRequest::new("wallet.query_row_single", params);
  73. // For now, on success, we don't care what's returned, but maybe in
  74. // the future we should actually check it?
  75. // TODO: The RPC needs a better variant for errors so detailed inspection
  76. // can be done with error codes and all that.
  77. if (self.rpc_client.request(req).await).is_err() {
  78. tree_needs_init = true;
  79. }
  80. if tree_needs_init {
  81. println!("Initializing Merkle tree");
  82. let tree = MerkleTree::new(100);
  83. self.put_money_tree(&tree).await?;
  84. println!("Successfully initialized Merkle tree for Money Contract");
  85. }
  86. if (self.wallet_last_scanned_slot().await).is_err() {
  87. let query = format!(
  88. "INSERT INTO {} ({}) VALUES (?1);",
  89. MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
  90. );
  91. let params = json!([query, QueryType::Integer as u8, 0]);
  92. let req = JsonRequest::new("wallet.exec_sql", params);
  93. let _ = self.rpc_client.request(req).await?;
  94. }
  95. Ok(())
  96. }
  97. /// Initialize wallet with tables for the DAO Contract.
  98. async fn wallet_initialize_dao(&self) -> Result<()> {
  99. let wallet_schema = include_str!("../../../src/contract/dao/wallet.sql");
  100. // We perform a request to darkfid with the schema to initialize
  101. // the necessary tables in the wallet.
  102. let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
  103. let rep = self.rpc_client.request(req).await?;
  104. if rep == true {
  105. println!("Successfully initialized wallet schema for the DAO Contract");
  106. } else {
  107. println!("Got unxpected reply from darkfid: {}", rep);
  108. }
  109. // Check if we have to initialize the Merkle trees. We check if one exists,
  110. // but we actually have to create two.
  111. let mut tree_needs_init = false;
  112. let query = format!("SELECT {} FROM {}", DAO_TREES_COL_DAOS_TREE, DAO_TREES_TABLE);
  113. let params = json!([query, QueryType::Blob as u8, DAO_TREES_COL_DAOS_TREE]);
  114. let req = JsonRequest::new("wallet.query_row_single", params);
  115. // For now, on success, we don't care what's returned, but maybe in
  116. // the future we should actually check it?
  117. // TODO: The RPC needs a better variant for errors so detailed inspection
  118. // can be done with error codes and all that.
  119. if (self.rpc_client.request(req).await).is_err() {
  120. tree_needs_init = true;
  121. }
  122. if tree_needs_init {
  123. println!("Initializing DAO Merkle trees");
  124. let daos_tree = MerkleTree::new(100);
  125. let proposals_tree = MerkleTree::new(100);
  126. self.put_dao_trees(&daos_tree, &proposals_tree).await?;
  127. println!("Successfully initialized Merkle trees for DAO Contract");
  128. }
  129. Ok(())
  130. }
  131. /// Main orchestration for wallet initialization. Internally, it initializes
  132. /// the wallet structure for the Money contract and the DAO contract.
  133. /// This should be performed initially before doing other operations.
  134. pub async fn wallet_initialize(&self) -> Result<()> {
  135. self.wallet_initialize_money().await?;
  136. self.wallet_initialize_dao().await?;
  137. Ok(())
  138. }
  139. /// Generate a new wallet keypair and put it in the according wallet table.
  140. pub async fn wallet_keygen(&self) -> Result<()> {
  141. println!("Generating a new keypair");
  142. // TODO: We might want to have hierarchical deterministic key derivation.
  143. let keypair = Keypair::random(&mut OsRng);
  144. let public = serialize(&keypair.public);
  145. let secret = serialize(&keypair.secret);
  146. let is_default = 0;
  147. let query = format!(
  148. "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3)",
  149. MONEY_KEYS_TABLE,
  150. MONEY_KEYS_COL_IS_DEFAULT,
  151. MONEY_KEYS_COL_PUBLIC,
  152. MONEY_KEYS_COL_SECRET,
  153. );
  154. let params = json!([
  155. query,
  156. QueryType::Integer as u8,
  157. is_default,
  158. QueryType::Blob as u8,
  159. public,
  160. QueryType::Blob as u8,
  161. secret,
  162. ]);
  163. let req = JsonRequest::new("wallet.exec_sql", params);
  164. let rep = self.rpc_client.request(req).await?;
  165. if rep == true {
  166. println!("Successfully added new keypair to wallet");
  167. } else {
  168. println!("Got unexpected reply from darkfid: {}", rep);
  169. }
  170. println!("New address: {}", keypair.public);
  171. Ok(())
  172. }
  173. /// Fetch all coins and their metadata from the wallet, optionally also spent ones.
  174. /// The boolean in the return tuple marks if the coin is marked as spent.
  175. pub async fn wallet_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool)>> {
  176. eprintln!("Fetching OwnCoins from wallet");
  177. let query = if fetch_spent {
  178. format!("SELECT * FROM {}", MONEY_COINS_TABLE)
  179. } else {
  180. format!(
  181. "SELECT * FROM {} WHERE {} = {}",
  182. MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, false,
  183. )
  184. };
  185. let params = json!([
  186. query,
  187. QueryType::Blob as u8,
  188. MONEY_COINS_COL_COIN,
  189. QueryType::Integer as u8,
  190. MONEY_COINS_COL_IS_SPENT,
  191. QueryType::Blob as u8,
  192. MONEY_COINS_COL_SERIAL,
  193. QueryType::Blob as u8,
  194. MONEY_COINS_COL_VALUE,
  195. QueryType::Blob as u8,
  196. MONEY_COINS_COL_TOKEN_ID,
  197. QueryType::Blob as u8,
  198. MONEY_COINS_COL_SPEND_HOOK,
  199. QueryType::Blob as u8,
  200. MONEY_COINS_COL_USER_DATA,
  201. QueryType::Blob as u8,
  202. MONEY_COINS_COL_COIN_BLIND,
  203. QueryType::Blob as u8,
  204. MONEY_COINS_COL_VALUE_BLIND,
  205. QueryType::Blob as u8,
  206. MONEY_COINS_COL_TOKEN_BLIND,
  207. QueryType::Blob as u8,
  208. MONEY_COINS_COL_SECRET,
  209. QueryType::Blob as u8,
  210. MONEY_COINS_COL_NULLIFIER,
  211. QueryType::Blob as u8,
  212. MONEY_COINS_COL_LEAF_POSITION,
  213. QueryType::Blob as u8,
  214. MONEY_COINS_COL_MEMO,
  215. ]);
  216. let req = JsonRequest::new("wallet.query_row_multi", params);
  217. let rep = self.rpc_client.request(req).await?;
  218. // The returned thing should be an array of found rows.
  219. let Some(rows) = rep.as_array() else {
  220. return Err(anyhow!("Unexpected response from darkfid: {}", rep))
  221. };
  222. let mut owncoins = vec![];
  223. for row in rows {
  224. let Some(row) = row.as_array() else {
  225. return Err(anyhow!("Unexpected response from darkfid: {}", rep))
  226. };
  227. let coin_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
  228. let coin: Coin = deserialize(&coin_bytes)?;
  229. let is_spent: u64 = serde_json::from_value(row[1].clone())?;
  230. let is_spent = is_spent > 0;
  231. let serial_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
  232. let serial: pallas::Base = deserialize(&serial_bytes)?;
  233. let value_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
  234. let value: u64 = deserialize(&value_bytes)?;
  235. let token_id_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
  236. let token_id: TokenId = deserialize(&token_id_bytes)?;
  237. let spend_hook_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
  238. let spend_hook: pallas::Base = deserialize(&spend_hook_bytes)?;
  239. let user_data_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
  240. let user_data: pallas::Base = deserialize(&user_data_bytes)?;
  241. let coin_blind_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
  242. let coin_blind: pallas::Base = deserialize(&coin_blind_bytes)?;
  243. let value_blind_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
  244. let value_blind: pallas::Scalar = deserialize(&value_blind_bytes)?;
  245. let token_blind_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
  246. let token_blind: pallas::Scalar = deserialize(&token_blind_bytes)?;
  247. let secret_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
  248. let secret: SecretKey = deserialize(&secret_bytes)?;
  249. let nullifier_bytes: Vec<u8> = serde_json::from_value(row[11].clone())?;
  250. let nullifier: Nullifier = deserialize(&nullifier_bytes)?;
  251. let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[12].clone())?;
  252. let leaf_position: incrementalmerkletree::Position = deserialize(&leaf_position_bytes)?;
  253. let memo: Vec<u8> = serde_json::from_value(row[13].clone())?;
  254. let note = Note {
  255. serial,
  256. value,
  257. token_id,
  258. spend_hook,
  259. user_data,
  260. coin_blind,
  261. value_blind,
  262. token_blind,
  263. memo,
  264. };
  265. let owncoin = OwnCoin { coin, note, secret, nullifier, leaf_position };
  266. owncoins.push((owncoin, is_spent))
  267. }
  268. Ok(owncoins)
  269. }
  270. /// Fetch known balances from the wallet and try to print them as a table.
  271. pub async fn wallet_balance(&self) -> Result<()> {
  272. // This represents "false"
  273. let is_spent = 0;
  274. let query = format!(
  275. "SELECT {}, {} FROM {} WHERE {} = {}",
  276. MONEY_COINS_COL_VALUE,
  277. MONEY_COINS_COL_TOKEN_ID,
  278. MONEY_COINS_TABLE,
  279. MONEY_COINS_COL_IS_SPENT,
  280. is_spent,
  281. );
  282. let params = json!([
  283. query,
  284. QueryType::Blob as u8,
  285. MONEY_COINS_COL_VALUE,
  286. QueryType::Blob as u8,
  287. MONEY_COINS_COL_TOKEN_ID,
  288. ]);
  289. let req = JsonRequest::new("wallet.query_row_multi", params);
  290. let rep = self.rpc_client.request(req).await?;
  291. // The returned thing should be an array of found rows.
  292. let Some(rows) = rep.as_array() else {
  293. return Err(anyhow!("Unexpected response from darkfid: {}", rep))
  294. };
  295. // Fill this map with balances, and in the end we'll print it as a table.
  296. let mut balmap: HashMap<String, u64> = HashMap::new();
  297. // Let's scan through the rows and see if we got anything.
  298. for row in rows {
  299. let Some(row) = row.as_array() else {
  300. return Err(anyhow!("Unexpected response from darkfid: {}", rep))
  301. };
  302. if row.len() != 2 {
  303. eprintln!("Error: Got invalid array, row should contain two elements.");
  304. eprintln!("Actual contents:\n:{:#?}", row);
  305. return Err(anyhow!("Unexpected response from darkfid: {}", rep))
  306. }
  307. let value_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
  308. let mut value: u64 = deserialize(&value_bytes)?;
  309. let token_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
  310. let token_id: TokenId = deserialize(&token_bytes)?;
  311. let token_id = format!("{}", token_id);
  312. if let Some(prev) = balmap.get(&token_id) {
  313. value += prev;
  314. }
  315. balmap.insert(token_id, value);
  316. }
  317. // Create a prettytable with the new data.
  318. let mut table = Table::new();
  319. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  320. table.set_titles(row!["Token ID", "Balance"]);
  321. for (token_id, balance) in balmap.iter() {
  322. // FIXME: Don't hardcode to 8 decimals
  323. table.add_row(row![token_id, encode_base10(*balance, 8)]);
  324. }
  325. if table.is_empty() {
  326. eprintln!("No unspent balances found");
  327. } else {
  328. println!("{}", table);
  329. }
  330. Ok(())
  331. }
  332. /// Fetch pubkeys from the wallet and print the requested index.
  333. pub async fn wallet_address(&self, _idx: u64) -> Result<PublicKey> {
  334. let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_TABLE);
  335. let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_PUBLIC]);
  336. let req = JsonRequest::new("wallet.query_row_single", params);
  337. let rep = self.rpc_client.request(req).await?;
  338. let Some(arr) = rep.as_array() else {
  339. return Err(anyhow!("Unexpected response from darkfid: {}", rep));
  340. };
  341. if arr.len() != 1 {
  342. return Err(anyhow!("Unexpected response from darkfid: {}", rep))
  343. }
  344. let key_bytes: Vec<u8> = serde_json::from_value(arr[0].clone())?;
  345. let public_key: PublicKey = deserialize(&key_bytes)?;
  346. Ok(public_key)
  347. }
  348. /// Fetch secret keys from the wallet and return them if found.
  349. pub async fn wallet_secrets(&self) -> Result<Vec<SecretKey>> {
  350. let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE);
  351. let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_SECRET]);
  352. let req = JsonRequest::new("wallet.query_row_multi", params);
  353. let rep = self.rpc_client.request(req).await?;
  354. // The returned thing should be an array of found rows.
  355. let Some(rows) = rep.as_array() else {
  356. return Err(anyhow!("Unexpected response from darkfid: {}", rep))
  357. };
  358. let mut secrets = vec![];
  359. // Let's scan through the rows and see if we got anything.
  360. for row in rows {
  361. let secret_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
  362. let secret: SecretKey = deserialize(&secret_bytes)?;
  363. secrets.push(secret);
  364. }
  365. Ok(secrets)
  366. }
  367. /// Import given secret keys into the wallet. The query uses INSERT, so if the key already
  368. /// exists, it will simply be skipped.
  369. pub async fn wallet_import_secrets(&self, secrets: Vec<SecretKey>) -> Result<Vec<PublicKey>> {
  370. let mut ret = vec![];
  371. for secret in secrets {
  372. ret.push(PublicKey::from_secret(secret));
  373. let is_default = 0;
  374. let public = serialize(&PublicKey::from_secret(secret));
  375. let secret = serialize(&secret);
  376. let query = format!(
  377. "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3)",
  378. MONEY_KEYS_TABLE,
  379. MONEY_KEYS_COL_IS_DEFAULT,
  380. MONEY_KEYS_COL_PUBLIC,
  381. MONEY_KEYS_COL_SECRET,
  382. );
  383. let params = json!([
  384. query,
  385. QueryType::Integer as u8,
  386. is_default,
  387. QueryType::Blob as u8,
  388. public,
  389. QueryType::Blob as u8,
  390. secret,
  391. ]);
  392. let req = JsonRequest::new("wallet.exec_sql", params);
  393. let rep = self.rpc_client.request(req).await?;
  394. if rep != true {
  395. // Something weird happened?
  396. eprintln!("Got unexpected reply from darkfid: {}", rep);
  397. }
  398. }
  399. Ok(ret)
  400. }
  401. /// Get the Money Merkle tree from the wallet
  402. pub async fn wallet_tree(&self) -> Result<BridgeTree<MerkleNode, MERKLE_DEPTH>> {
  403. let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
  404. let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
  405. let req = JsonRequest::new("wallet.query_row_single", params);
  406. let rep = self.rpc_client.request(req).await?;
  407. let tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
  408. let tree = deserialize(&tree_bytes)?;
  409. Ok(tree)
  410. }
  411. pub async fn wallet_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
  412. let query = format!("SELECT * FROM {}", DAO_TREES_TABLE);
  413. let params = json!([
  414. query,
  415. QueryType::Blob as u8,
  416. DAO_TREES_COL_DAOS_TREE,
  417. QueryType::Blob as u8,
  418. DAO_TREES_COL_PROPOSALS_TREE
  419. ]);
  420. let req = JsonRequest::new("wallet.query_row_single", params);
  421. let rep = self.rpc_client.request(req).await?;
  422. let daos_tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
  423. let proposals_tree_bytes: Vec<u8> = serde_json::from_value(rep[1].clone())?;
  424. let daos_tree = deserialize(&daos_tree_bytes)?;
  425. let proposals_tree = deserialize(&proposals_tree_bytes)?;
  426. Ok((daos_tree, proposals_tree))
  427. }
  428. /// Get the last scanned slot from the wallet
  429. pub async fn wallet_last_scanned_slot(&self) -> Result<u64> {
  430. let query =
  431. format!("SELECT {} FROM {};", MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE);
  432. let params = json!([query, QueryType::Integer as u8, MONEY_INFO_COL_LAST_SCANNED_SLOT]);
  433. let req = JsonRequest::new("wallet.query_row_single", params);
  434. let rep = self.rpc_client.request(req).await?;
  435. Ok(serde_json::from_value(rep[0].clone())?)
  436. }
  437. /// Mark a coin in the wallet as spent
  438. pub async fn mark_spent_coin(&self, coin: &Coin) -> Result<()> {
  439. let query = format!(
  440. "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
  441. MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN
  442. );
  443. let params = json!([
  444. query,
  445. QueryType::Integer as u8,
  446. 1,
  447. QueryType::Blob as u8,
  448. serialize(&coin.inner())
  449. ]);
  450. let req = JsonRequest::new("wallet.exec_sql", params);
  451. let _ = self.rpc_client.request(req).await?;
  452. Ok(())
  453. }
  454. /// Marks all coins in the wallet as spent, if their nullifier is
  455. /// in the provided set
  456. pub async fn mark_spent_coins(&self, nullifiers: Vec<Nullifier>) -> Result<()> {
  457. if nullifiers.is_empty() {
  458. return Ok(())
  459. }
  460. for (coin, _) in self.wallet_coins(false).await? {
  461. if nullifiers.contains(&coin.nullifier) {
  462. self.mark_spent_coin(&coin.coin).await?;
  463. }
  464. }
  465. Ok(())
  466. }
  467. /// Mark a given coin in the wallet as unspent
  468. pub async fn unspend_coin(&self, coin: &Coin) -> Result<()> {
  469. let query = format!(
  470. "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
  471. MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN
  472. );
  473. let params = json!([
  474. query,
  475. QueryType::Integer as u8,
  476. 0,
  477. QueryType::Blob as u8,
  478. serialize(&coin.inner())
  479. ]);
  480. let req = JsonRequest::new("wallet.exec_sql", params);
  481. let _ = self.rpc_client.request(req).await?;
  482. Ok(())
  483. }
  484. /// Replace the Money Merkle tree in the wallet
  485. pub async fn put_money_tree(&self, tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>) -> Result<()> {
  486. let query = format!(
  487. "DELETE FROM {}; INSERT INTO {} ({}) VALUES (?1);",
  488. MONEY_TREE_TABLE, MONEY_TREE_TABLE, MONEY_TREE_COL_TREE
  489. );
  490. let params = json!([query, QueryType::Blob as u8, serialize(tree)]);
  491. let req = JsonRequest::new("wallet.exec_sql", params);
  492. let _ = self.rpc_client.request(req).await?;
  493. Ok(())
  494. }
  495. /// Reset the Money Contract Merkle tree and coins in the wallet
  496. pub async fn reset_money_tree(&self) -> Result<()> {
  497. eprintln!("Resetting Money Merkle tree");
  498. let tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
  499. self.put_money_tree(&tree).await?;
  500. eprintln!("Successfully reset Money Merkle tree");
  501. eprintln!("Resetting coins");
  502. let query = format!("DELETE FROM {};", MONEY_COINS_TABLE);
  503. let params = json!([query]);
  504. let req = JsonRequest::new("wallet.exec_sql", params);
  505. let _ = self.rpc_client.request(req).await?;
  506. eprintln!("Successfully reset coins");
  507. Ok(())
  508. }
  509. /// Replace the DAO Merkle trees in the wallet
  510. pub async fn put_dao_trees(
  511. &self,
  512. daos_tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>,
  513. proposals_tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>,
  514. ) -> Result<()> {
  515. let query = format!(
  516. "DELETE FROM {}; INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
  517. DAO_TREES_TABLE, DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE
  518. );
  519. let params = json!([
  520. query,
  521. QueryType::Blob as u8,
  522. serialize(daos_tree),
  523. QueryType::Blob as u8,
  524. serialize(proposals_tree)
  525. ]);
  526. let req = JsonRequest::new("wallet.exec_sql", params);
  527. let _ = self.rpc_client.request(req).await?;
  528. Ok(())
  529. }
  530. /// Reset the DAO Contract Merkle trees in the wallet
  531. pub async fn reset_dao_trees(&self) -> Result<()> {
  532. eprintln!("Resetting DAO Merkle trees");
  533. let tree0 = MerkleTree::new(100);
  534. let tree1 = MerkleTree::new(100);
  535. self.put_dao_trees(&tree0, &tree1).await?;
  536. eprintln!("Successfully reset DAO Merkle trees");
  537. Ok(())
  538. }
  539. /// Fetch all DAOs from the wallet
  540. /// We use this a lot because we don't worry too much about performance in this
  541. /// tool, and also in practice probably not a lot of DAOs will be in a single
  542. /// wallet.
  543. pub async fn wallet_get_daos(&self) -> Result<Vec<Dao>> {
  544. let query = format!("SELECT * FROM {}", DAO_DAOS_TABLE);
  545. let params = json!([
  546. query,
  547. QueryType::Integer as u8,
  548. DAO_DAOS_COL_DAO_ID,
  549. QueryType::Blob as u8,
  550. DAO_DAOS_COL_NAME,
  551. QueryType::Integer as u8,
  552. DAO_DAOS_COL_PROPOSER_LIMIT,
  553. QueryType::Integer as u8,
  554. DAO_DAOS_COL_QUORUM,
  555. QueryType::Integer as u8,
  556. DAO_DAOS_COL_APPROVAL_RATIO_BASE,
  557. QueryType::Integer as u8,
  558. DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
  559. QueryType::Blob as u8,
  560. DAO_DAOS_COL_GOV_TOKEN_ID,
  561. QueryType::Blob as u8,
  562. DAO_DAOS_COL_SECRET,
  563. QueryType::Blob as u8,
  564. DAO_DAOS_COL_BULLA_BLIND,
  565. QueryType::OptionBlob as u8,
  566. DAO_DAOS_COL_LEAF_POSITION,
  567. QueryType::OptionBlob as u8,
  568. DAO_DAOS_COL_TX_HASH,
  569. QueryType::OptionInteger as u8,
  570. DAO_DAOS_COL_CALL_INDEX,
  571. ]);
  572. let req = JsonRequest::new("wallet.query_row_multi", params);
  573. let rep = self.rpc_client.request(req).await?;
  574. let Some(rows) = rep.as_array() else {
  575. return Err(anyhow!("Unexpected response from darkfid: {}", rep));
  576. };
  577. let mut daos = Vec::with_capacity(rows.len());
  578. for row in rows {
  579. let id: u64 = serde_json::from_value(row[0].clone())?;
  580. let name_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
  581. let name = deserialize(&name_bytes)?;
  582. let proposer_limit = serde_json::from_value(row[2].clone())?;
  583. let quorum = serde_json::from_value(row[3].clone())?;
  584. let approval_ratio_base = serde_json::from_value(row[4].clone())?;
  585. let approval_ratio_quot = serde_json::from_value(row[5].clone())?;
  586. let gov_token_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
  587. let gov_token_id = deserialize(&gov_token_bytes)?;
  588. let secret_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
  589. let secret_key = deserialize(&secret_bytes)?;
  590. let bulla_blind_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
  591. let bulla_blind = deserialize(&bulla_blind_bytes)?;
  592. let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
  593. let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
  594. let call_index = serde_json::from_value(row[11].clone())?;
  595. let leaf_position = if leaf_position_bytes.is_empty() {
  596. None
  597. } else {
  598. Some(deserialize(&leaf_position_bytes)?)
  599. };
  600. let tx_hash =
  601. if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
  602. let dao = Dao {
  603. id,
  604. name,
  605. proposer_limit,
  606. quorum,
  607. approval_ratio_base,
  608. approval_ratio_quot,
  609. gov_token_id,
  610. secret_key,
  611. bulla_blind,
  612. leaf_position,
  613. tx_hash,
  614. call_index,
  615. };
  616. daos.push(dao);
  617. }
  618. // Sort by ID in SQL. The SELECT statement does not guarantee this.
  619. daos.sort_by(|a, b| a.id.cmp(&b.id));
  620. Ok(daos)
  621. }
  622. }