wallet_money.rs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  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, str::FromStr};
  19. use anyhow::{anyhow, Result};
  20. use darkfi::{rpc::jsonrpc::JsonRequest, tx::Transaction, wallet::walletdb::QueryType};
  21. use darkfi_money_contract::{
  22. client::{
  23. MoneyNote, OwnCoin, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
  24. MONEY_ALIASES_TABLE, MONEY_COINS_COL_COIN, MONEY_COINS_COL_IS_SPENT,
  25. MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO, MONEY_COINS_COL_NULLIFIER,
  26. MONEY_COINS_COL_SECRET, MONEY_COINS_COL_SERIAL, MONEY_COINS_COL_SPEND_HOOK,
  27. MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID, MONEY_COINS_COL_USER_DATA,
  28. MONEY_COINS_COL_VALUE, MONEY_COINS_COL_VALUE_BLIND, MONEY_COINS_TABLE,
  29. MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE, MONEY_KEYS_COL_IS_DEFAULT,
  30. MONEY_KEYS_COL_KEY_ID, MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE,
  31. MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID, MONEY_TOKENS_TABLE,
  32. MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
  33. },
  34. model::{
  35. Coin, MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1, MoneyTransferParamsV1, Output,
  36. },
  37. MoneyFunction,
  38. };
  39. use darkfi_sdk::{
  40. bridgetree,
  41. crypto::{
  42. pasta_prelude::Field, poseidon_hash, Keypair, MerkleNode, MerkleTree, Nullifier, PublicKey,
  43. SecretKey, TokenId, MONEY_CONTRACT_ID,
  44. },
  45. pasta::pallas,
  46. };
  47. use darkfi_serial::{deserialize, serialize};
  48. use rand::rngs::OsRng;
  49. use serde_json::json;
  50. use super::Drk;
  51. use crate::cli_util::kaching;
  52. impl Drk {
  53. /// Initialize wallet with tables for the Money contract
  54. pub async fn initialize_money(&self) -> Result<()> {
  55. let wallet_schema = include_str!("../../../src/contract/money/wallet.sql");
  56. // We perform a request to darkfid with the schema to initialize
  57. // the necessary tables in the wallet.
  58. let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
  59. let rep = self.rpc_client.request(req).await?;
  60. if rep == true {
  61. eprintln!("Successfully initialized wallet schema for the Money contract");
  62. } else {
  63. eprintln!("[initialize_money] Got unexpected reply from darkfid: {}", rep);
  64. }
  65. // Check if we have to initialize the Merkle tree.
  66. // We check if we find a row in the tree table, and if not, we create a
  67. // new tree and push it into the table.
  68. let mut tree_needs_init = false;
  69. let query = format!("SELECT {} FROM {}", MONEY_TREE_COL_TREE, MONEY_TREE_TABLE);
  70. let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
  71. let req = JsonRequest::new("wallet.query_row_single", params);
  72. // For now, on success, we don't care what's returned, but in the future
  73. // we should actually check it.
  74. // TODO: The RPC needs a better variant for errors so detailed inspection
  75. // can be done with error codes and all that.
  76. if (self.rpc_client.request(req).await).is_err() {
  77. tree_needs_init = true;
  78. }
  79. if tree_needs_init {
  80. eprintln!("Initializing Money Merkle tree");
  81. let mut tree = MerkleTree::new(100);
  82. tree.append(MerkleNode::from(pallas::Base::ZERO));
  83. let _ = tree.mark().unwrap();
  84. self.put_money_tree(&tree).await?;
  85. eprintln!("Successfully initialized Merkle tree for the Money contract");
  86. }
  87. // We maintain the last scanned slot as part of the Money contract,
  88. // but at this moment it is also somewhat applicable to DAO scans.
  89. if (self.last_scanned_slot().await).is_err() {
  90. let query = format!(
  91. "INSERT INTO {} ({}) VALUES (?1);",
  92. MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_SLOT
  93. );
  94. let params = json!([query, QueryType::Integer as u8, 0]);
  95. let req = JsonRequest::new("wallet.exec_sql", params);
  96. let _ = self.rpc_client.request(req).await?;
  97. }
  98. Ok(())
  99. }
  100. /// Generate a new keypair and place it into the wallet.
  101. pub async fn money_keygen(&self) -> Result<()> {
  102. eprintln!("Generating a new keypair");
  103. // TODO: We might want to have hierarchical deterministic key derivation.
  104. let keypair = Keypair::random(&mut OsRng);
  105. let is_default = 0;
  106. let query = format!(
  107. "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
  108. MONEY_KEYS_TABLE,
  109. MONEY_KEYS_COL_IS_DEFAULT,
  110. MONEY_KEYS_COL_PUBLIC,
  111. MONEY_KEYS_COL_SECRET,
  112. );
  113. let params = json!([
  114. query,
  115. QueryType::Integer as u8,
  116. is_default,
  117. QueryType::Blob as u8,
  118. serialize(&keypair.public),
  119. QueryType::Blob as u8,
  120. serialize(&keypair.secret),
  121. ]);
  122. let req = JsonRequest::new("wallet.exec_sql", params);
  123. let rep = self.rpc_client.request(req).await?;
  124. if rep == true {
  125. eprintln!("Successfully added new keypair to wallet");
  126. } else {
  127. eprintln!("[money_keygen] Got unexpected reply from darkfid: {}", rep);
  128. }
  129. eprintln!("New address:");
  130. println!("{}", keypair.public);
  131. Ok(())
  132. }
  133. /// Fetch all secret keys from the wallet
  134. pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
  135. let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE);
  136. let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_SECRET]);
  137. let req = JsonRequest::new("wallet.query_row_multi", params);
  138. let rep = self.rpc_client.request(req).await?;
  139. // The returned thing should be an array of found rows.
  140. let Some(rows) = rep.as_array() else {
  141. return Err(anyhow!("[get_money_secrets] Unexpected response from darkfid: {}", rep))
  142. };
  143. let mut secrets = Vec::with_capacity(rows.len());
  144. // Let's scan through the rows and see if we got anything.
  145. for row in rows {
  146. let secret_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
  147. let secret = deserialize(&secret_bytes)?;
  148. secrets.push(secret);
  149. }
  150. Ok(secrets)
  151. }
  152. /// Import given secret keys into the wallet.
  153. /// The query uses INSERT, so if the key already exists, it will be skipped.
  154. /// Returns the respective PublicKey objects for the imported keys.
  155. pub async fn import_money_secrets(&self, secrets: Vec<SecretKey>) -> Result<Vec<PublicKey>> {
  156. let mut ret = Vec::with_capacity(secrets.len());
  157. for secret in secrets {
  158. ret.push(PublicKey::from_secret(secret));
  159. let is_default = 0;
  160. let public = serialize(&PublicKey::from_secret(secret));
  161. let secret = serialize(&secret);
  162. let query = format!(
  163. "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
  164. MONEY_KEYS_TABLE,
  165. MONEY_KEYS_COL_IS_DEFAULT,
  166. MONEY_KEYS_COL_PUBLIC,
  167. MONEY_KEYS_COL_SECRET,
  168. );
  169. let params = json!([
  170. query,
  171. QueryType::Integer as u8,
  172. is_default,
  173. QueryType::Blob as u8,
  174. public,
  175. QueryType::Blob as u8,
  176. secret,
  177. ]);
  178. let req = JsonRequest::new("wallet.exec_sql", params);
  179. let _ = self.rpc_client.request(req).await?;
  180. }
  181. Ok(ret)
  182. }
  183. /// Fetch pubkeys from the wallet and return the requested index.
  184. pub async fn wallet_address(&self, idx: u64) -> Result<PublicKey> {
  185. let query = format!(
  186. "SELECT {} FROM {} WHERE {} = {};",
  187. MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_TABLE, MONEY_KEYS_COL_KEY_ID, idx
  188. );
  189. let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_PUBLIC]);
  190. let req = JsonRequest::new("wallet.query_row_single", params);
  191. let rep = self.rpc_client.request(req).await?;
  192. let Some(arr) = rep.as_array() else {
  193. return Err(anyhow!("[wallet_address] Unexpected response from darkfid: {}", rep))
  194. };
  195. if arr.len() != 1 {
  196. return Err(anyhow!("Did not find pubkey with index {}", idx))
  197. }
  198. let key_bytes: Vec<u8> = serde_json::from_value(arr[0].clone())?;
  199. let public_key: PublicKey = deserialize(&key_bytes)?;
  200. Ok(public_key)
  201. }
  202. /// Fetch all coins and their metadata related to the Money contract from the wallet.
  203. /// Optionally also fetch spent ones.
  204. /// The boolean in the returned tuple notes if the coin was marked as spent.
  205. pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool)>> {
  206. let query = if fetch_spent {
  207. format!("SELECT * FROM {}", MONEY_COINS_TABLE)
  208. } else {
  209. format!(
  210. "SELECT * FROM {} WHERE {} = {}",
  211. MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, false,
  212. )
  213. };
  214. let params = json!([
  215. query,
  216. QueryType::Blob as u8,
  217. MONEY_COINS_COL_COIN,
  218. QueryType::Integer as u8,
  219. MONEY_COINS_COL_IS_SPENT,
  220. QueryType::Blob as u8,
  221. MONEY_COINS_COL_SERIAL,
  222. QueryType::Blob as u8,
  223. MONEY_COINS_COL_VALUE,
  224. QueryType::Blob as u8,
  225. MONEY_COINS_COL_TOKEN_ID,
  226. QueryType::Blob as u8,
  227. MONEY_COINS_COL_SPEND_HOOK,
  228. QueryType::Blob as u8,
  229. MONEY_COINS_COL_USER_DATA,
  230. QueryType::Blob as u8,
  231. MONEY_COINS_COL_VALUE_BLIND,
  232. QueryType::Blob as u8,
  233. MONEY_COINS_COL_TOKEN_BLIND,
  234. QueryType::Blob as u8,
  235. MONEY_COINS_COL_SECRET,
  236. QueryType::Blob as u8,
  237. MONEY_COINS_COL_NULLIFIER,
  238. QueryType::Blob as u8,
  239. MONEY_COINS_COL_LEAF_POSITION,
  240. QueryType::Blob as u8,
  241. MONEY_COINS_COL_MEMO,
  242. ]);
  243. let req = JsonRequest::new("wallet.query_row_multi", params);
  244. let rep = self.rpc_client.request(req).await?;
  245. // The returned thing should be an array of found rows.
  246. let Some(rows) = rep.as_array() else {
  247. return Err(anyhow!("[get_coins] Unexpected response from darkfid: {}", rep))
  248. };
  249. let mut owncoins = Vec::with_capacity(rows.len());
  250. for row in rows {
  251. let Some(row) = row.as_array() else {
  252. return Err(anyhow!("[get_coins] Unexpected response from darkfid: {}", rep))
  253. };
  254. let coin_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
  255. let coin: Coin = deserialize(&coin_bytes)?;
  256. let is_spent: u64 = serde_json::from_value(row[1].clone())?;
  257. let is_spent = is_spent > 0;
  258. let serial_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
  259. let serial: pallas::Base = deserialize(&serial_bytes)?;
  260. let value_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
  261. let value: u64 = deserialize(&value_bytes)?;
  262. let token_id_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
  263. let token_id: TokenId = deserialize(&token_id_bytes)?;
  264. let spend_hook_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
  265. let spend_hook: pallas::Base = deserialize(&spend_hook_bytes)?;
  266. let user_data_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
  267. let user_data: pallas::Base = deserialize(&user_data_bytes)?;
  268. let value_blind_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
  269. let value_blind: pallas::Scalar = deserialize(&value_blind_bytes)?;
  270. let token_blind_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
  271. let token_blind: pallas::Base = deserialize(&token_blind_bytes)?;
  272. let secret_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
  273. let secret: SecretKey = deserialize(&secret_bytes)?;
  274. let nullifier_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
  275. let nullifier: Nullifier = deserialize(&nullifier_bytes)?;
  276. let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[11].clone())?;
  277. let leaf_position: bridgetree::Position = deserialize(&leaf_position_bytes)?;
  278. let memo: Vec<u8> = serde_json::from_value(row[12].clone())?;
  279. let note = MoneyNote {
  280. serial,
  281. value,
  282. token_id,
  283. spend_hook,
  284. user_data,
  285. value_blind,
  286. token_blind,
  287. memo,
  288. };
  289. let owncoin = OwnCoin { coin, note, secret, nullifier, leaf_position };
  290. owncoins.push((owncoin, is_spent))
  291. }
  292. Ok(owncoins)
  293. }
  294. /// Mark a coin in the wallet as spent
  295. pub async fn mark_spent_coin(&self, coin: &Coin) -> Result<()> {
  296. let query = format!(
  297. "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
  298. MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN
  299. );
  300. let params = json!([
  301. query,
  302. QueryType::Integer as u8,
  303. 1,
  304. QueryType::Blob as u8,
  305. serialize(&coin.inner())
  306. ]);
  307. let req = JsonRequest::new("wallet.exec_sql", params);
  308. let _ = self.rpc_client.request(req).await?;
  309. Ok(())
  310. }
  311. /// Marks all coins in the wallet as spent, if their nullifier is in the given set
  312. pub async fn mark_spent_coins(&self, nullifiers: &[Nullifier]) -> Result<()> {
  313. if nullifiers.is_empty() {
  314. return Ok(())
  315. }
  316. for (coin, _) in self.get_coins(false).await? {
  317. if nullifiers.contains(&coin.nullifier) {
  318. self.mark_spent_coin(&coin.coin).await?;
  319. }
  320. }
  321. Ok(())
  322. }
  323. /// Mark a given coin in the wallet as unspent
  324. pub async fn unspend_coin(&self, coin: &Coin) -> Result<()> {
  325. let query = format!(
  326. "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
  327. MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN,
  328. );
  329. let params = json!([
  330. query,
  331. QueryType::Integer as u8,
  332. 0,
  333. QueryType::Blob as u8,
  334. serialize(&coin.inner())
  335. ]);
  336. let req = JsonRequest::new("wallet.exec_sql", params);
  337. let _ = self.rpc_client.request(req).await?;
  338. Ok(())
  339. }
  340. /// Replace the Money Merkle tree in the wallet.
  341. pub async fn put_money_tree(&self, tree: &MerkleTree) -> Result<()> {
  342. let query = format!(
  343. "DELETE FROM {}; INSERT INTO {} ({}) VALUES (?1);",
  344. MONEY_TREE_TABLE, MONEY_TREE_TABLE, MONEY_TREE_COL_TREE,
  345. );
  346. let params = json!([query, QueryType::Blob as u8, serialize(tree)]);
  347. let req = JsonRequest::new("wallet.exec_sql", params);
  348. let _ = self.rpc_client.request(req).await?;
  349. Ok(())
  350. }
  351. /// Fetch the Money Merkle tree from the wallet
  352. pub async fn get_money_tree(&self) -> Result<MerkleTree> {
  353. let query = format!("SELECT * FROM {}", MONEY_TREE_TABLE);
  354. let params = json!([query, QueryType::Blob as u8, MONEY_TREE_COL_TREE]);
  355. let req = JsonRequest::new("wallet.query_row_single", params);
  356. let rep = self.rpc_client.request(req).await?;
  357. let tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
  358. let tree = deserialize(&tree_bytes)?;
  359. Ok(tree)
  360. }
  361. /// Reset the Money Merkle tree in the wallet
  362. pub async fn reset_money_tree(&self) -> Result<()> {
  363. eprintln!("Resetting Money Merkle tree");
  364. let mut tree = MerkleTree::new(100);
  365. tree.append(MerkleNode::from(pallas::Base::ZERO));
  366. let _ = tree.mark().unwrap();
  367. self.put_money_tree(&tree).await?;
  368. eprintln!("Successfully reset Money Merkle tree");
  369. Ok(())
  370. }
  371. /// Reset the Money coins in the wallet
  372. pub async fn reset_money_coins(&self) -> Result<()> {
  373. eprintln!("Resetting coins");
  374. let query = format!("DELETE FROM {};", MONEY_COINS_TABLE);
  375. let params = json!([query]);
  376. let req = JsonRequest::new("wallet.exec_sql", params);
  377. let _ = self.rpc_client.request(req).await?;
  378. eprintln!("Successfully reset coins");
  379. Ok(())
  380. }
  381. /// Fetch known unspent balances from the wallet and return them as a hashmap.
  382. pub async fn money_balance(&self) -> Result<HashMap<String, u64>> {
  383. let mut coins = self.get_coins(false).await?;
  384. coins.retain(|x| x.0.note.spend_hook == pallas::Base::zero());
  385. // Fill this map with balances
  386. let mut balmap: HashMap<String, u64> = HashMap::new();
  387. for coin in coins {
  388. let mut value = coin.0.note.value;
  389. if let Some(prev) = balmap.get(&coin.0.note.token_id.to_string()) {
  390. value += prev;
  391. }
  392. balmap.insert(coin.0.note.token_id.to_string(), value);
  393. }
  394. Ok(balmap)
  395. }
  396. /// Append data related to Money contract transactions into the wallet database.
  397. pub async fn apply_tx_money_data(&self, tx: &Transaction, _confirm: bool) -> Result<()> {
  398. let cid = *MONEY_CONTRACT_ID;
  399. let mut nullifiers: Vec<Nullifier> = vec![];
  400. let mut outputs: Vec<Output> = vec![];
  401. let mut freezes: Vec<TokenId> = vec![];
  402. for (i, call) in tx.calls.iter().enumerate() {
  403. if call.contract_id == cid && call.data[0] == MoneyFunction::TransferV1 as u8 {
  404. eprintln!("Found Money::TransferV1 in call {}", i);
  405. let params: MoneyTransferParamsV1 = deserialize(&call.data[1..])?;
  406. for input in params.inputs {
  407. nullifiers.push(input.nullifier);
  408. }
  409. for output in params.outputs {
  410. outputs.push(output);
  411. }
  412. continue
  413. }
  414. if call.contract_id == cid && call.data[0] == MoneyFunction::OtcSwapV1 as u8 {
  415. eprintln!("Found Money::OtcSwapV1 in call {}", i);
  416. let params: MoneyTransferParamsV1 = deserialize(&call.data[1..])?;
  417. for input in params.inputs {
  418. nullifiers.push(input.nullifier);
  419. }
  420. for output in params.outputs {
  421. outputs.push(output);
  422. }
  423. continue
  424. }
  425. if call.contract_id == cid && call.data[0] == MoneyFunction::TokenMintV1 as u8 {
  426. eprintln!("Found Money::MintV1 in call {}", i);
  427. let params: MoneyTokenMintParamsV1 = deserialize(&call.data[1..])?;
  428. outputs.push(params.output);
  429. continue
  430. }
  431. if call.contract_id == cid && call.data[0] == MoneyFunction::TokenFreezeV1 as u8 {
  432. eprintln!("Found Money::FreezeV1 in call {}", i);
  433. let params: MoneyTokenFreezeParamsV1 = deserialize(&call.data[1..])?;
  434. let token_id = TokenId::derive_public(params.signature_public);
  435. freezes.push(token_id);
  436. }
  437. }
  438. let secrets = self.get_money_secrets().await?;
  439. let dao_secrets = self.get_dao_secrets().await?;
  440. let mut tree = self.get_money_tree().await?;
  441. let mut owncoins = vec![];
  442. for output in outputs {
  443. let coin = output.coin;
  444. // Append the new coin to the Merkle tree. Every coin has to be added.
  445. tree.append(MerkleNode::from(coin.inner()));
  446. // Attempt to decrypt the note
  447. for secret in secrets.iter().chain(dao_secrets.iter()) {
  448. if let Ok(note) = output.note.decrypt::<MoneyNote>(secret) {
  449. eprintln!("Successfully decrypted a Money Note");
  450. eprintln!("Witnessing coin in Merkle tree");
  451. let leaf_position = tree.mark().unwrap();
  452. let owncoin = OwnCoin {
  453. coin,
  454. note: note.clone(),
  455. secret: *secret,
  456. nullifier: Nullifier::from(poseidon_hash([secret.inner(), note.serial])),
  457. leaf_position,
  458. };
  459. owncoins.push(owncoin);
  460. }
  461. }
  462. }
  463. self.put_money_tree(&tree).await?;
  464. if !nullifiers.is_empty() {
  465. self.mark_spent_coins(&nullifiers).await?;
  466. }
  467. // This is the SQL query we'll be executing to insert new coins
  468. // into the wallet
  469. let query = format!(
  470. "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13);",
  471. MONEY_COINS_TABLE,
  472. MONEY_COINS_COL_COIN,
  473. MONEY_COINS_COL_IS_SPENT,
  474. MONEY_COINS_COL_SERIAL,
  475. MONEY_COINS_COL_VALUE,
  476. MONEY_COINS_COL_TOKEN_ID,
  477. MONEY_COINS_COL_SPEND_HOOK,
  478. MONEY_COINS_COL_USER_DATA,
  479. MONEY_COINS_COL_VALUE_BLIND,
  480. MONEY_COINS_COL_TOKEN_BLIND,
  481. MONEY_COINS_COL_SECRET,
  482. MONEY_COINS_COL_NULLIFIER,
  483. MONEY_COINS_COL_LEAF_POSITION,
  484. MONEY_COINS_COL_MEMO,
  485. );
  486. eprintln!("Found {} OwnCoin(s) in transaction", owncoins.len());
  487. for owncoin in &owncoins {
  488. eprintln!("OwnCoin: {:?}", owncoin.coin);
  489. let params = json!([
  490. query,
  491. QueryType::Blob as u8,
  492. serialize(&owncoin.coin),
  493. QueryType::Integer as u8,
  494. 0, // <-- is_spent
  495. QueryType::Blob as u8,
  496. serialize(&owncoin.note.serial),
  497. QueryType::Blob as u8,
  498. serialize(&owncoin.note.value),
  499. QueryType::Blob as u8,
  500. serialize(&owncoin.note.token_id),
  501. QueryType::Blob as u8,
  502. serialize(&owncoin.note.spend_hook),
  503. QueryType::Blob as u8,
  504. serialize(&owncoin.note.user_data),
  505. QueryType::Blob as u8,
  506. serialize(&owncoin.note.value_blind),
  507. QueryType::Blob as u8,
  508. serialize(&owncoin.note.token_blind),
  509. QueryType::Blob as u8,
  510. serialize(&owncoin.secret),
  511. QueryType::Blob as u8,
  512. serialize(&owncoin.nullifier),
  513. QueryType::Blob as u8,
  514. serialize(&owncoin.leaf_position),
  515. QueryType::Blob as u8,
  516. serialize(&owncoin.note.memo),
  517. ]);
  518. let req = JsonRequest::new("wallet.exec_sql", params);
  519. let _ = self.rpc_client.request(req).await?;
  520. }
  521. for token_id in freezes {
  522. let query = format!(
  523. "UPDATE {} SET {} = 1 WHERE {} = ?1;",
  524. MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID,
  525. );
  526. let params = json!([query, QueryType::Blob as u8, serialize(&token_id)]);
  527. let req = JsonRequest::new("wallet.exec_sql", params);
  528. let _ = self.rpc_client.request(req).await?;
  529. }
  530. if !owncoins.is_empty() {
  531. kaching().await;
  532. }
  533. Ok(())
  534. }
  535. /// Get the last scanned slot from the wallet
  536. pub async fn last_scanned_slot(&self) -> Result<u64> {
  537. let query =
  538. format!("SELECT {} FROM {};", MONEY_INFO_COL_LAST_SCANNED_SLOT, MONEY_INFO_TABLE);
  539. let params = json!([query, QueryType::Integer as u8, MONEY_INFO_COL_LAST_SCANNED_SLOT]);
  540. let req = JsonRequest::new("wallet.query_row_single", params);
  541. let rep = self.rpc_client.request(req).await?;
  542. Ok(serde_json::from_value(rep[0].clone())?)
  543. }
  544. /// Create an alias record for provided Token ID
  545. pub async fn add_alias(&self, alias: String, token_id: TokenId) -> Result<()> {
  546. eprintln!("Generating alias {} for Token: {}", alias, token_id);
  547. let query = format!(
  548. "INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
  549. MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
  550. );
  551. let params = json!([
  552. query,
  553. QueryType::Blob as u8,
  554. serialize(&alias),
  555. QueryType::Blob as u8,
  556. serialize(&token_id),
  557. ]);
  558. let req = JsonRequest::new("wallet.exec_sql", params);
  559. let rep = self.rpc_client.request(req).await?;
  560. if rep == true {
  561. eprintln!("Successfully added new alias to wallet");
  562. } else {
  563. eprintln!("[add_alias] Got unexpected reply from darkfid: {}", rep);
  564. }
  565. Ok(())
  566. }
  567. /// Fetch all aliases from the wallet.
  568. /// Optionally filter using alias name and/or token id.
  569. pub async fn get_aliases(
  570. &self,
  571. alias_filter: Option<String>,
  572. token_id_filter: Option<TokenId>,
  573. ) -> Result<HashMap<String, TokenId>> {
  574. let query = format!("SELECT * FROM {}", MONEY_ALIASES_TABLE);
  575. let params = json!([
  576. query,
  577. QueryType::Blob as u8,
  578. MONEY_ALIASES_COL_ALIAS,
  579. QueryType::Blob as u8,
  580. MONEY_ALIASES_COL_TOKEN_ID,
  581. ]);
  582. let req = JsonRequest::new("wallet.query_row_multi", params);
  583. let rep = self.rpc_client.request(req).await?;
  584. // The returned thing should be an array of found rows.
  585. let Some(rows) = rep.as_array() else {
  586. return Err(anyhow!("[get_aliases] Unexpected response from darkfid: {}", rep))
  587. };
  588. // Fill this map with aliases
  589. let mut map: HashMap<String, TokenId> = HashMap::new();
  590. for row in rows {
  591. let Some(row) = row.as_array() else {
  592. return Err(anyhow!("[get_aliases] Unexpected response from darkfid: {}", rep))
  593. };
  594. let alias_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
  595. let alias: String = deserialize(&alias_bytes)?;
  596. if alias_filter.is_some() && alias_filter.as_ref().unwrap() != &alias {
  597. continue
  598. }
  599. let token_id_bytes: Vec<u8> = serde_json::from_value(row[1].clone())?;
  600. let token_id: TokenId = deserialize(&token_id_bytes)?;
  601. if token_id_filter.is_some() && token_id_filter.as_ref().unwrap() != &token_id {
  602. continue
  603. }
  604. map.insert(alias, token_id);
  605. }
  606. Ok(map)
  607. }
  608. /// Fetch all aliases from the wallet, mapped by token id.
  609. pub async fn get_aliases_mapped_by_token(&self) -> Result<HashMap<String, String>> {
  610. let aliases = self.get_aliases(None, None).await?;
  611. let mut map: HashMap<String, String> = HashMap::new();
  612. for (alias, token_id) in aliases {
  613. let aliases_string = if let Some(prev) = map.get(&token_id.to_string()) {
  614. format!("{}, {}", prev, alias)
  615. } else {
  616. alias
  617. };
  618. map.insert(token_id.to_string(), aliases_string);
  619. }
  620. Ok(map)
  621. }
  622. /// Retrieve token by provided string.
  623. /// Input string represents either an alias or a token id.
  624. pub async fn get_token(&self, input: String) -> Result<TokenId> {
  625. // Check if input is an alias(max 5 characters)
  626. if input.chars().count() <= 5 {
  627. let aliases = self.get_aliases(Some(input.clone()), None).await?;
  628. if let Some(token_id) = aliases.get(&input) {
  629. return Ok(*token_id)
  630. }
  631. }
  632. // Else parse input
  633. Ok(TokenId::from_str(input.as_str())?)
  634. }
  635. /// Create an alias record for provided Token ID
  636. pub async fn remove_alias(&self, alias: String) -> Result<()> {
  637. eprintln!("Removing alias: {}", alias);
  638. let query =
  639. format!("DELETE FROM {} WHERE {} = ?1;", MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,);
  640. let params = json!([query, QueryType::Blob as u8, serialize(&alias),]);
  641. let req = JsonRequest::new("wallet.exec_sql", params);
  642. let rep = self.rpc_client.request(req).await?;
  643. if rep == true {
  644. eprintln!("Successfully removed alias from wallet");
  645. } else {
  646. eprintln!("[remove_alias] Got unexpected reply from darkfid: {}", rep);
  647. }
  648. Ok(())
  649. }
  650. }