parazyd преди 3 години
родител
ревизия
4892432cde

+ 7 - 8
bin/darkfid/src/error.rs

@@ -34,9 +34,8 @@ pub enum RpcError {
     DecryptionFailed = -32106,
     */
     // Transaction-related errors
-    TxBuildFail = -32110,
+    TxSimulationFail = -32110,
     TxBroadcastFail = -32111,
-    TxSimulationFail = -32112,
 
     // State-related errors,
     NotSynced = -32120,
@@ -44,8 +43,9 @@ pub enum RpcError {
 
     // Parsing errors
     ParseError = -32190,
-    NaN = -32191,
-    LessThanNegOne = -32192,
+
+    // Contract-related errors
+    ContractZkasDbNotFound = -32200,
 }
 
 fn to_tuple(e: RpcError) -> (i64, String) {
@@ -61,16 +61,15 @@ fn to_tuple(e: RpcError) -> (i64, String) {
         RpcError::DecryptionFailed => "Decryption failed",
         */
         // Transaction-related errors
-        RpcError::TxBuildFail => "Failed building transaction",
-        RpcError::TxBroadcastFail => "Failed broadcasting transaction",
         RpcError::TxSimulationFail => "Failed simulating transaction state change",
+        RpcError::TxBroadcastFail => "Failed broadcasting transaction",
         // State-related errors
         RpcError::NotSynced => "Blockchain is not synced",
         RpcError::UnknownSlot => "Did not find slot",
         // Parsing errors
         RpcError::ParseError => "Parse error",
-        RpcError::NaN => "Not a number",
-        RpcError::LessThanNegOne => "Number cannot be lower than -1",
+        // Contract-related errors
+        RpcError::ContractZkasDbNotFound => "zkas database not found for given contract",
     };
 
     (e as i64, msg.to_string())

+ 0 - 36
bin/darkfid/src/internal.rs

@@ -1,36 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2022 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use darkfi::{tx::Transaction, Result};
-
-use super::Darkfid;
-
-impl Darkfid {
-    /// Apply a new `MemoryState` from the current validator state and simulate a state
-    /// transition with the given `Transaction`. Returns a vec of `StateUpdate` on success.
-    pub async fn simulate_transaction(&self, tx: &Transaction) -> Result<Vec<StateUpdate>> {
-        // Grab the current state and apply a new MemoryState
-        let validator_state = self.validator_state.read().await;
-        let state = validator_state.state_machine.lock().await;
-        let mem_state = MemoryState::new(state.clone());
-        drop(state);
-        drop(validator_state);
-
-        ValidatorState::validate_state_transitions(mem_state, &[tx.clone()])
-    }
-}

+ 6 - 4
bin/darkfid/src/main.rs

@@ -182,7 +182,7 @@ pub struct Darkfid {
 // JSON-RPC methods
 mod rpc_blockchain;
 mod rpc_misc;
-//mod rpc_tx;
+mod rpc_tx;
 mod rpc_wallet;
 
 // Internal methods
@@ -214,14 +214,15 @@ impl RequestHandler for Darkfid {
             Some("blockchain.subscribe_blocks") => {
                 return self.blockchain_subscribe_blocks(req.id, params).await
             }
+            Some("blockchain.lookup_zkas") => {
+                return self.blockchain_lookup_zkas(req.id, params).await
+            }
 
             // ===================
             // Transaction methods
             // ===================
-            /*
-            Some("tx.transfer") => return self.tx_transfer(req.id, params).await,
             Some("tx.broadcast") => return self.tx_broadcast(req.id, params).await,
-            */
+
             // ==============
             // Wallet methods
             // ==============
@@ -232,6 +233,7 @@ impl RequestHandler for Darkfid {
             Some("wallet.query_row_multi") => {
                 return self.wallet_query_row_multi(req.id, params).await
             }
+
             // ==============
             // Invalid method
             // ==============

+ 50 - 1
bin/darkfid/src/rpc_blockchain.rs

@@ -16,7 +16,11 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_sdk::crypto::MerkleNode;
+use darkfi_sdk::{
+    crypto::{ContractId, MerkleNode},
+    db::ZKAS_DB_NAME,
+};
+use darkfi_serial::deserialize;
 use log::{debug, error};
 use serde_json::{json, Value};
 
@@ -108,4 +112,49 @@ impl Darkfid {
 
         JsonSubscriber::new(blocks_subscriber).into()
     }
+
+    // RPCAPI:
+    // Performs a lookup of zkas bincodes for a given contract ID and returns all of
+    // them, including their namespace.
+    //
+    // --> {"jsonrpc": "2.0", "method": "blockchain.lookup_zkas", "params": ["6Ef42L1KLZXBoxBuCDto7coi9DA2D2SRtegNqNU4sd74"], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": [["Foo", [...]], ["Bar", [...]]], "id": 1}
+    pub async fn blockchain_lookup_zkas(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let contract_id = match ContractId::try_from(params[0].as_str().unwrap()) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("[RPC] blockchain.lookup_zkas: Error decoding string to ContractId: {}", e);
+                return JsonError::new(InvalidParams, None, id).into()
+            }
+        };
+
+        let blockchain = { self.validator_state.read().await.blockchain.clone() };
+
+        let Ok(zkas_db) = blockchain.contracts.lookup(&blockchain.sled_db, &contract_id, ZKAS_DB_NAME) else {
+            error!("[RPC] blockchain.lookup_zkas: Did not find zkas db for ContractId: {}", contract_id);
+            return server_error(RpcError::ContractZkasDbNotFound, id, None)
+        };
+
+        let mut ret: Vec<(String, Vec<u8>)> = vec![];
+
+        for i in zkas_db.iter() {
+            debug!("Iterating over zkas db");
+            let Ok((zkas_ns, zkas_bincode)) = i else {
+                error!("Internal sled error iterating db");
+                return JsonError::new(InternalError, None, id).into()
+            };
+
+            let Ok(zkas_ns) = deserialize(&zkas_ns) else {
+                return JsonError::new(InternalError, None, id).into()
+            };
+
+            ret.push((zkas_ns, zkas_bincode.to_vec()));
+        }
+
+        JsonResponse::new(json!(ret), id).into()
+    }
 }

+ 6 - 91
bin/darkfid/src/rpc_tx.rs

@@ -16,9 +16,6 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::str::FromStr;
-
-use darkfi_sdk::crypto::{Address, PublicKey, TokenId};
 use darkfi_serial::{deserialize, serialize};
 use log::{error, warn};
 use serde_json::{json, Value};
@@ -32,90 +29,6 @@ use super::Darkfid;
 use crate::{server_error, RpcError};
 
 impl Darkfid {
-    // RPCAPI:
-    // Transfer a given amount of some token to the given address.
-    // Returns a transaction ID upon success.
-    //
-    // * `dest_addr` -> Recipient's DarkFi address
-    // * `token_id` -> ID of the token to send
-    // * `12345` -> Amount in `u64` of the funds to send
-    //
-    // --> {"jsonrpc": "2.0", "method": "tx.transfer", "params": ["dest_addr", "token_id", 12345], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": "txID...", "id": 1}
-    pub async fn tx_transfer(&self, id: Value, params: &[Value]) -> JsonResult {
-        if params.len() != 3 ||
-            !params[0].is_string() ||
-            !params[1].is_string() ||
-            !params[2].is_u64()
-        {
-            return JsonError::new(InvalidParams, None, id).into()
-        }
-
-        if !(*self.synced.lock().await) {
-            error!("[RPC] tx.transfer: Blockchain is not synced");
-            return server_error(RpcError::NotSynced, id, None)
-        }
-
-        let address = params[0].as_str().unwrap();
-        let token = params[1].as_str().unwrap();
-        let amount = params[2].as_u64().unwrap();
-
-        let address = match Address::from_str(address) {
-            Ok(v) => v,
-            Err(e) => {
-                error!("[RPC] tx.transfer: Failed parsing address from string: {}", e);
-                return server_error(RpcError::InvalidAddressParam, id, None)
-            }
-        };
-
-        let pubkey = match PublicKey::try_from(address) {
-            Ok(v) => v,
-            Err(e) => {
-                error!("[RPC] tx.transfer: Failed parsing PublicKey from Address: {}", e);
-                return server_error(RpcError::ParseError, id, None)
-            }
-        };
-
-        let token_id = match TokenId::try_from(token) {
-            Ok(v) => v,
-            Err(e) => {
-                error!("[RPC] tx.transfer: Failed parsing Token ID from string: {}", e);
-                return server_error(RpcError::ParseError, id, None)
-            }
-        };
-
-        let tx = match self
-            .client
-            .build_transaction(
-                pubkey,
-                amount,
-                token_id,
-                false,
-                self.validator_state.read().await.state_machine.clone(),
-            )
-            .await
-        {
-            Ok(v) => v,
-            Err(e) => {
-                error!("tx.transfer: Failed building transaction: {}", e);
-                return server_error(RpcError::TxBuildFail, id, None)
-            }
-        };
-
-        if let Some(sync_p2p) = &self.sync_p2p {
-            if let Err(e) = sync_p2p.broadcast(tx.clone()).await {
-                error!("[RPC] tx.transfer: Failed broadcasting transaction: {}", e);
-                return server_error(RpcError::TxBroadcastFail, id, None)
-            }
-        } else {
-            warn!("[RPC] tx.transfer: No sync P2P network, not broadcasting transaction.");
-            return server_error(RpcError::TxBroadcastFail, id, None)
-        }
-
-        let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();
-        JsonResponse::new(json!(tx_hash), id).into()
-    }
-
     // RPCAPI:
     // Simulate a network state transition with the given transaction.
     // Returns `true` if the transaction is valid, otherwise, a corresponding
@@ -151,10 +64,10 @@ impl Darkfid {
         };
 
         // Simulate state transition
-        if let Err(e) = self.simulate_transaction(&tx).await {
+        if let Err(e) = self.validator_state.read().await.verify_transactions(&[tx], false).await {
             error!("[RPC] tx.broadcast: Failed to validate state transition: {}", e);
             return server_error(RpcError::TxSimulationFail, id, None)
-        }
+        };
 
         JsonResponse::new(json!(true), id).into()
     }
@@ -195,10 +108,12 @@ impl Darkfid {
         };
 
         // Simulate state transition
-        if let Err(e) = self.simulate_transaction(&tx).await {
+        if let Err(e) =
+            self.validator_state.read().await.verify_transactions(&[tx.clone()], false).await
+        {
             error!("[RPC] tx.broadcast: Failed to validate state transition: {}", e);
             return server_error(RpcError::TxSimulationFail, id, None)
-        }
+        };
 
         // TODO: Should we apply the state transition locally before broadcasting it?
         if let Some(sync_p2p) = &self.sync_p2p {

+ 0 - 6
bin/drk/Cargo.toml

@@ -24,9 +24,3 @@ simplelog = "0.12.0"
 libsqlite3-sys = {version = "0.24.2", features = ["bundled-sqlcipher"]}
 sqlx = {version = "0.6.2", features = ["runtime-async-std-native-tls", "sqlite"]}
 url = "2.3.1"
-#bs58 = "0.4.0"
-#clap = {version = "3.2.20", features = ["derive"]}
-#darkfi = {path = "../../", features = ["crypto", "util", "rpc", "wasm-runtime", "zkas"]}
-#indicatif = "0.17.1"
-#log = "0.4.17"
-#pasta_curves = "0.4.1"

+ 68 - 1
bin/drk/src/main.rs

@@ -16,11 +16,17 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{process::exit, str::FromStr, time::Instant};
+use std::{
+    io::{stdin, Read},
+    process::exit,
+    str::FromStr,
+    time::Instant,
+};
 
 use anyhow::{Context, Result};
 use clap::{Parser, Subcommand};
 use darkfi_sdk::crypto::{PublicKey, TokenId};
+use darkfi_serial::{deserialize, serialize};
 use serde_json::json;
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
 use url::Url;
@@ -34,6 +40,9 @@ use darkfi::{
 /// Airdrop methods
 mod rpc_airdrop;
 
+/// Payment methods
+mod rpc_transfer;
+
 /// Blockchain methods
 mod rpc_blockchain;
 
@@ -103,6 +112,21 @@ enum Subcmd {
         address: Option<String>,
     },
 
+    /// Create a payment transaction
+    Transfer {
+        /// Amount to send
+        amount: String,
+
+        /// Token ID to send
+        token: String,
+
+        /// Recipient address
+        recipient: String,
+    },
+
+    /// Read a transaction from stdin and broadcast it
+    Broadcast,
+
     /// Subscribe to incoming blocks from darkfid
     ///
     /// This subscription will listen for incoming blocks from darkfid and look
@@ -239,6 +263,49 @@ async fn main() -> Result<()> {
             Ok(())
         }
 
+        Subcmd::Transfer { amount, token, recipient } => {
+            let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
+            let token_id = TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
+            let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
+
+            let rpc_client = RpcClient::new(args.endpoint)
+                .await
+                .with_context(|| "Could not connect to darkfid RPC endpoint")?;
+
+            let drk = Drk { rpc_client };
+
+            let tx = drk
+                .transfer(&amount, token_id, rcpt)
+                .await
+                .with_context(|| "Failed to create payment transaction")?;
+
+            println!("{}", bs58::encode(&serialize(&tx)).into_string());
+
+            Ok(())
+        }
+
+        Subcmd::Broadcast => {
+            eprintln!("Reading transaction from stdin...");
+            let mut buf = String::new();
+            stdin().read_to_string(&mut buf)?;
+
+            let bytes = bs58::decode(&buf).into_vec()?;
+            let tx = deserialize(&bytes)?;
+
+            let rpc_client = RpcClient::new(args.endpoint)
+                .await
+                .with_context(|| "Could not connect to darkfid RPC endpoint")?;
+
+            let drk = Drk { rpc_client };
+
+            let txid =
+                drk.broadcast_tx(&tx).await.with_context(|| "Failed to broadcast transaction")?;
+
+            eprintln!("Transaction ID: {}", txid);
+
+            Ok(())
+        }
+
         Subcmd::Subscribe => {
             let rpc_client = RpcClient::new(args.endpoint.clone())
                 .await

+ 3 - 2
bin/drk/src/rpc_airdrop.rs

@@ -39,7 +39,8 @@ impl Drk {
         let req = JsonRequest::new("airdrop", params);
         let rep = rpc_client.oneshot_request(req).await?;
 
-        println!("{:#?}", rep);
-        Ok(format!("ack"))
+        let txid = serde_json::from_value(rep)?;
+
+        Ok(txid)
     }
 }

+ 30 - 0
bin/drk/src/rpc_blockchain.rs

@@ -25,6 +25,7 @@ use darkfi::{
         jsonrpc::{JsonRequest, JsonResult},
     },
     system::Subscriber,
+    tx::Transaction,
     wallet::walletdb::QueryType,
 };
 use darkfi_money_contract::{
@@ -86,6 +87,9 @@ impl Drk {
                     let bytes = bs58::decode(params).into_vec()?;
 
                     let block_data: BlockInfo = deserialize(&bytes)?;
+                    eprintln!("=======================================");
+                    eprintln!("Block header:\n{:#?}", block_data.header);
+                    eprintln!("=======================================");
 
                     // TODO: FIXME: Disallow this if last_scanned_slot is not this-1 or something
                     eprintln!("Deserialized successfully. Scanning block...");
@@ -237,4 +241,30 @@ impl Drk {
 
         Ok(())
     }
+
+    /// Try to fetch zkas bincodes for the given `ContractId`.
+    pub async fn lookup_zkas(&self, contract_id: &ContractId) -> Result<Vec<(String, Vec<u8>)>> {
+        eprintln!("Querying zkas bincode for {}", contract_id);
+
+        let params = json!([format!("{}", contract_id)]);
+        let req = JsonRequest::new("blockchain.lookup_zkas", params);
+
+        let rep = self.rpc_client.request(req).await?;
+
+        let ret = serde_json::from_value(rep)?;
+        Ok(ret)
+    }
+
+    /// Broadcast a given transaction to darkfid and forward onto the network.
+    /// Returns the transaction ID upon success
+    pub async fn broadcast_tx(&self, tx: &Transaction) -> Result<String> {
+        eprintln!("Broadcasting transaction...");
+
+        let params = json!([bs58::encode(&serialize(tx)).into_string()]);
+        let req = JsonRequest::new("tx.broadcast", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        let txid = serde_json::from_value(rep)?;
+        Ok(txid)
+    }
 }

+ 141 - 0
bin/drk/src/rpc_transfer.rs

@@ -0,0 +1,141 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use anyhow::{anyhow, Result};
+use darkfi::{
+    crypto::proof::ProvingKey,
+    tx::Transaction,
+    util::parse::{decode_base10, encode_base10},
+    zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
+    zkas::ZkBinary,
+};
+use darkfi_money_contract::{
+    client::{build_transfer_tx, OwnCoin},
+    MoneyFunction, ZKAS_BURN_NS, ZKAS_MINT_NS,
+};
+use darkfi_sdk::{
+    crypto::{ContractId, Keypair, PublicKey, TokenId},
+    pasta::pallas,
+    tx::ContractCall,
+};
+use darkfi_serial::Encodable;
+use rand::rngs::OsRng;
+//use serde_json::json;
+
+use super::Drk;
+
+impl Drk {
+    /// Create a payment transaction. Returns the transaction object on success.
+    pub async fn transfer(
+        &self,
+        amount: &str,
+        token_id: TokenId,
+        recipient: PublicKey,
+    ) -> Result<Transaction> {
+        // First get all unspent OwnCoins to see what our balance is.
+        eprintln!("Fetching OwnCoins");
+        let owncoins = self.wallet_coins(false).await?;
+        let mut owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
+        // We're only interested in the ones for the token_id we're sending
+        owncoins.retain(|x| x.note.token_id == token_id);
+        if owncoins.is_empty() {
+            return Err(anyhow!("Did not find any coins with token ID: {}", token_id))
+        }
+
+        // FIXME: Do not hardcode 8 decimals
+        let amount = decode_base10(amount, 8, false)?;
+        let mut balance = 0;
+        for coin in owncoins.iter() {
+            balance += coin.note.value;
+        }
+
+        if balance < amount {
+            return Err(anyhow!(
+                "Not enough balance for token ID: {}, found: {}",
+                token_id,
+                encode_base10(balance, 8)
+            ))
+        }
+
+        // We'll also need our Merkle tree
+        let tree = self.wallet_tree().await?;
+
+        // TODO: Which keypair to actually use?
+        let secrets = self.wallet_secrets().await?;
+        let keypair = Keypair::new(secrets[0]);
+
+        // TODO: FIXME: Do not hardcode the contract ID
+        let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
+
+        // Now we need to do a lookup for the zkas proof bincodes, and create
+        // the circuit objects and proving keys so we can build the transaction.
+        // We also do this through the RPC.
+        let zkas_bins = self.lookup_zkas(&contract_id).await?;
+
+        let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_MINT_NS) else {
+            return Err(anyhow!("Mint circuit not found"))
+        };
+
+        let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_BURN_NS) else {
+            return Err(anyhow!("Burn circuit not found"))
+        };
+
+        let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
+        let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
+
+        let k = 13;
+        let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin), mint_zkbin.clone());
+        let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin), burn_zkbin.clone());
+
+        eprintln!("Creating Mint circuit proving key");
+        let mint_pk = ProvingKey::build(k, &mint_circuit);
+        eprintln!("Creating Burn circuit proving key");
+        let burn_pk = ProvingKey::build(k, &burn_circuit);
+
+        // Now we should have everything we need to build the transaction
+        let (params, proofs, secrets, spent_coins) = build_transfer_tx(
+            &keypair,
+            &recipient,
+            amount,
+            token_id,
+            &owncoins,
+            &tree,
+            &mint_zkbin,
+            &mint_pk,
+            &burn_zkbin,
+            &burn_pk,
+            false,
+        )?;
+
+        // Encode and sign the transaction
+        let mut data = vec![MoneyFunction::Transfer as u8];
+        params.encode(&mut data)?;
+        let calls = vec![ContractCall { contract_id, data }];
+        let proofs = vec![proofs];
+        let mut tx = Transaction { calls, proofs, signatures: vec![] };
+        let sigs = tx.create_sigs(&mut OsRng, &secrets)?;
+        tx.signatures = vec![sigs];
+
+        // We need to mark the coins we've spent in our wallet
+        for spent_coin in spent_coins {
+            self.mark_spent_coin(&spent_coin.coin).await?;
+        }
+
+        Ok(tx)
+    }
+}

+ 137 - 9
bin/drk/src/rpc_wallet.rs

@@ -21,13 +21,21 @@ use std::collections::HashMap;
 use anyhow::{anyhow, Result};
 use darkfi::{rpc::jsonrpc::JsonRequest, util::parse::encode_base10, wallet::walletdb::QueryType};
 use darkfi_money_contract::client::{
-    MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_TOKEN_ID, MONEY_COINS_COL_VALUE, MONEY_COINS_TABLE,
-    MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE,
-    MONEY_TREE_COL_TREE, MONEY_TREE_TABLE,
+    Coin, Note, OwnCoin, MONEY_COINS_COL_COIN, MONEY_COINS_COL_COIN_BLIND,
+    MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_LEAF_POSITION, MONEY_COINS_COL_MEMO,
+    MONEY_COINS_COL_NULLIFIER, MONEY_COINS_COL_SECRET, MONEY_COINS_COL_SERIAL,
+    MONEY_COINS_COL_TOKEN_BLIND, MONEY_COINS_COL_TOKEN_ID, MONEY_COINS_COL_VALUE,
+    MONEY_COINS_COL_VALUE_BLIND, MONEY_COINS_TABLE, MONEY_KEYS_COL_IS_DEFAULT,
+    MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_SECRET, MONEY_KEYS_TABLE, MONEY_TREE_COL_TREE,
+    MONEY_TREE_TABLE,
 };
 use darkfi_sdk::{
-    crypto::{constants::MERKLE_DEPTH, Keypair, MerkleNode, PublicKey, SecretKey, TokenId},
+    crypto::{
+        constants::MERKLE_DEPTH, Keypair, MerkleNode, Nullifier, PublicKey, SecretKey, TokenId,
+    },
+    incrementalmerkletree,
     incrementalmerkletree::bridgetree::BridgeTree,
+    pasta::pallas,
 };
 use darkfi_serial::{deserialize, serialize};
 use prettytable::{format, row, Table};
@@ -79,7 +87,7 @@ impl Drk {
             );
             let params = json!([query, QueryType::Blob as u8, tree_bytes]);
             let req = JsonRequest::new("wallet.exec_sql", params);
-            let _ = self.rpc_client.oneshot_request(req).await?;
+            let _ = self.rpc_client.request(req).await?;
             println!("Successfully initialized Merkle tree");
         }
 
@@ -114,7 +122,7 @@ impl Drk {
         ]);
 
         let req = JsonRequest::new("wallet.exec_sql", params);
-        let rep = self.rpc_client.oneshot_request(req).await?;
+        let rep = self.rpc_client.request(req).await?;
 
         if rep == true {
             println!("Successfully added new keypair to wallet");
@@ -126,6 +134,105 @@ impl Drk {
         Ok(())
     }
 
+    /// Fetch all coins and their metadata from the wallet, optionally also spent ones.
+    /// The boolean in the return tuple marks if the coin is marked as spent.
+    pub async fn wallet_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool)>> {
+        eprintln!("Fetching OwnCoins from wallet");
+        let query = if fetch_spent {
+            format!("SELECT * FROM {}", MONEY_COINS_TABLE)
+        } else {
+            format!(
+                "SELECT * FROM {} WHERE {} = {}",
+                MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, 0
+            )
+        };
+
+        let params = json!([
+            query,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_COIN,
+            QueryType::Integer as u8,
+            MONEY_COINS_COL_IS_SPENT,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_SERIAL,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_VALUE,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_TOKEN_ID,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_COIN_BLIND,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_VALUE_BLIND,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_TOKEN_BLIND,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_SECRET,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_NULLIFIER,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_LEAF_POSITION,
+            QueryType::Blob as u8,
+            MONEY_COINS_COL_MEMO,
+        ]);
+
+        let req = JsonRequest::new("wallet.query_row_multi", params);
+        let rep = self.rpc_client.request(req).await?;
+
+        // The returned thing should be an array of found rows.
+        let Some(rows) = rep.as_array() else {
+            return Err(anyhow!("Unexpected response from darkfid: {}", rep))
+        };
+
+        let mut owncoins = vec![];
+
+        for row in rows {
+            let Some(row) = row.as_array() else {
+                return Err(anyhow!("Unexpected response from darkfid: {}", rep))
+            };
+
+            let coin_bytes: Vec<u8> = serde_json::from_value(row[0].clone())?;
+            let coin: Coin = deserialize(&coin_bytes)?;
+
+            let is_spent: bool = serde_json::from_value(row[1].clone())?;
+
+            let serial_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
+            let serial: pallas::Base = deserialize(&serial_bytes)?;
+
+            let value_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
+            let value: u64 = deserialize(&value_bytes)?;
+
+            let token_id_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
+            let token_id: TokenId = deserialize(&token_id_bytes)?;
+
+            let coin_blind_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
+            let coin_blind: pallas::Base = deserialize(&coin_blind_bytes)?;
+
+            let value_blind_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
+            let value_blind: pallas::Scalar = deserialize(&value_blind_bytes)?;
+
+            let token_blind_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
+            let token_blind: pallas::Scalar = deserialize(&token_blind_bytes)?;
+
+            let secret_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
+            let secret: SecretKey = deserialize(&secret_bytes)?;
+
+            let nullifier_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
+            let nullifier: Nullifier = deserialize(&nullifier_bytes)?;
+
+            let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
+            let leaf_position: incrementalmerkletree::Position = deserialize(&leaf_position_bytes)?;
+
+            let memo: Vec<u8> = serde_json::from_value(row[11].clone())?;
+
+            let note = Note { serial, value, token_id, coin_blind, value_blind, token_blind, memo };
+            let owncoin = OwnCoin { coin, note, secret, nullifier, leaf_position };
+
+            owncoins.push((owncoin, is_spent))
+        }
+
+        Ok(owncoins)
+    }
+
     /// Fetch known balances from the wallet and try to print them as a table.
     pub async fn wallet_balance(&self) -> Result<()> {
         // This represents "false"
@@ -149,7 +256,7 @@ impl Drk {
         ]);
 
         let req = JsonRequest::new("wallet.query_row_multi", params);
-        let rep = self.rpc_client.oneshot_request(req).await?;
+        let rep = self.rpc_client.request(req).await?;
 
         // The returned thing should be an array of found rows.
         let Some(rows) = rep.as_array() else {
@@ -205,11 +312,11 @@ impl Drk {
     }
 
     /// Fetch pubkeys from the wallet and print the requested index.
-    pub async fn wallet_address(&self, idx: u64) -> Result<PublicKey> {
+    pub async fn wallet_address(&self, _idx: u64) -> Result<PublicKey> {
         let query = format!("SELECT {} FROM {};", MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_TABLE);
         let params = json!([query, QueryType::Blob as u8, MONEY_KEYS_COL_PUBLIC]);
         let req = JsonRequest::new("wallet.query_row_single", params);
-        let rep = self.rpc_client.oneshot_request(req).await?;
+        let rep = self.rpc_client.request(req).await?;
 
         let Some(arr) = rep.as_array() else {
             return Err(anyhow!("Unexpected response from darkfid: {}", rep));
@@ -260,4 +367,25 @@ impl Drk {
         let tree = deserialize(&tree_bytes)?;
         Ok(tree)
     }
+
+    /// Mark a coin in the wallet as spent
+    pub async fn mark_spent_coin(&self, coin: &Coin) -> Result<()> {
+        let query = format!(
+            "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
+            MONEY_COINS_TABLE, MONEY_COINS_COL_IS_SPENT, MONEY_COINS_COL_COIN
+        );
+
+        let params = json!([
+            query,
+            QueryType::Integer as u8,
+            1,
+            QueryType::Blob as u8,
+            serialize(&coin.inner())
+        ]);
+
+        let req = JsonRequest::new("wallet.exec_sql", params);
+        let _ = self.rpc_client.request(req).await?;
+
+        Ok(())
+    }
 }

+ 1 - 1
bin/faucetd/src/main.rs

@@ -427,7 +427,7 @@ impl Faucetd {
         };
 
         // Create money contract params and proofs
-        let (params, proofs, secret_keys) = match build_transfer_tx(
+        let (params, proofs, secret_keys, _spent_coins) = match build_transfer_tx(
             &self.keypair,
             &pubkey,
             amount,

+ 3 - 4
src/contract/money/src/client.rs

@@ -490,7 +490,7 @@ pub fn build_transfer_tx(
     burn_zkbin: &ZkBinary,
     burn_pk: &ProvingKey,
     clear_input: bool,
-) -> Result<(MoneyTransferParams, Vec<Proof>, Vec<SecretKey>)> {
+) -> Result<(MoneyTransferParams, Vec<Proof>, Vec<SecretKey>, Vec<OwnCoin>)> {
     debug!("Building money contract transaction");
     assert!(value != 0);
     if !clear_input {
@@ -534,7 +534,7 @@ pub fn build_transfer_tx(
             };
 
             inputs.push(input);
-            spent_coins.push(coin);
+            spent_coins.push(coin.clone());
         }
 
         if inputs_value < value {
@@ -689,9 +689,8 @@ pub fn build_transfer_tx(
 
     // Now we should have all the params, zk proofs, and signature secrets.
     // We return it all and let the caller deal with it.
-    // TODO: Return also spent coins
 
-    Ok((params, zk_proofs, signature_secrets))
+    Ok((params, zk_proofs, signature_secrets, spent_coins))
 }
 
 fn compute_remainder_blind(

+ 3 - 3
src/contract/money/tests/contract_exec.rs

@@ -148,7 +148,7 @@ async fn money_contract_execution() -> Result<()> {
     let amount = decode_base10("42.69", 8, true)?;
 
     info!("Building transfer tx for clear inputs");
-    let (params, proofs, secret_keys) = build_transfer_tx(
+    let (params, proofs, secret_keys, _spent_coins) = build_transfer_tx(
         &faucet_kp,
         &alice_kp.public,
         amount,
@@ -211,7 +211,7 @@ async fn money_contract_execution() -> Result<()> {
 
     // Alice can spend the coin and send another one to herself
     info!("Building transfer tx for Alice from Alice");
-    let (params, proofs, secret_keys) = build_transfer_tx(
+    let (params, proofs, secret_keys, _spent_coins) = build_transfer_tx(
         &alice_kp,
         &alice_kp.public,
         amount,
@@ -267,7 +267,7 @@ async fn money_contract_execution() -> Result<()> {
 
     // Alice can spend the coin and send another one to herself
     info!("Building transfer tx for Alice from Alice");
-    let (params, proofs, secret_keys) = build_transfer_tx(
+    let (params, proofs, secret_keys, _spent_coins) = build_transfer_tx(
         &alice_kp,
         &alice_kp.public,
         amount,

+ 2 - 2
src/contract/money/wallet.sql

@@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS money_tree (
 -- The keypairs in our wallet
 CREATE TABLE IF NOT EXISTS money_keys (
 	key_id INTEGER PRIMARY KEY NOT NULL,
-	is_default BOOLEAN NOT NULL,
+	is_default INTEGER NOT NULL,
 	public BLOB NOT NULL,
 	secret BLOB NOT NULL
 );
@@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS money_keys (
 -- The coins we have the information to and can spend
 CREATE TABLE IF NOT EXISTS money_coins (
 	coin BLOB PRIMARY KEY NOT NULL,
-	is_spent BOOLEAN NOT NULL,
+	is_spent INTEGER NOT NULL,
 	serial BLOB NOT NULL,
 	value BLOB NOT NULL,
 	token_id BLOB NOT NULL,