drk: refactor to support SMT

Additionaly, some cleanup was done, minor bugz fixed and updated DAO Propose call with the new setup and added its fee call.
This commit is contained in:
skoupidi
2024-05-25 18:29:40 +03:00
parent 3e0ddde295
commit 96a8068ec3
21 changed files with 892 additions and 552 deletions

1
Cargo.lock generated
View File

@@ -2667,6 +2667,7 @@ dependencies = [
"easy-parallel", "easy-parallel",
"lazy_static", "lazy_static",
"log", "log",
"num-bigint",
"prettytable-rs", "prettytable-rs",
"rand 0.8.5", "rand 0.8.5",
"rodio", "rodio",

View File

@@ -60,6 +60,7 @@ impl RequestHandler for Darkfid {
"blockchain.get_block" => self.blockchain_get_block(req.id, req.params).await, "blockchain.get_block" => self.blockchain_get_block(req.id, req.params).await,
"blockchain.get_tx" => self.blockchain_get_tx(req.id, req.params).await, "blockchain.get_tx" => self.blockchain_get_tx(req.id, req.params).await,
"blockchain.last_known_block" => self.blockchain_last_known_block(req.id, req.params).await, "blockchain.last_known_block" => self.blockchain_last_known_block(req.id, req.params).await,
"blockchain.best_fork_next_block_height" => self.blockchain_best_fork_next_block_height(req.id, req.params).await,
"blockchain.lookup_zkas" => self.blockchain_lookup_zkas(req.id, req.params).await, "blockchain.lookup_zkas" => self.blockchain_lookup_zkas(req.id, req.params).await,
"blockchain.subscribe_blocks" => self.blockchain_subscribe_blocks(req.id, req.params).await, "blockchain.subscribe_blocks" => self.blockchain_subscribe_blocks(req.id, req.params).await,
"blockchain.subscribe_txs" => self.blockchain_subscribe_txs(req.id, req.params).await, "blockchain.subscribe_txs" => self.blockchain_subscribe_txs(req.id, req.params).await,

View File

@@ -117,16 +117,16 @@ impl Darkfid {
} }
// RPCAPI: // RPCAPI:
// Queries the blockchain database to find the last known block // Queries the blockchain database to find the last known block.
// //
// **Params:** // **Params:**
// * `None` // * `None`
// //
// **Returns:** // **Returns:**
// * `u64` Height of the last known block, as string // * `f64` Height of the last known block
// //
// --> {"jsonrpc": "2.0", "method": "blockchain.last_known_block", "params": [], "id": 1} // --> {"jsonrpc": "2.0", "method": "blockchain.last_known_block", "params": [], "id": 1}
// <-- {"jsonrpc": "2.0", "result": "1234", "id": 1} // <-- {"jsonrpc": "2.0", "result": 1234, "id": 1}
pub async fn blockchain_last_known_block(&self, id: u16, params: JsonValue) -> JsonResult { pub async fn blockchain_last_known_block(&self, id: u16, params: JsonValue) -> JsonResult {
let params = params.get::<Vec<JsonValue>>().unwrap(); let params = params.get::<Vec<JsonValue>>().unwrap();
if !params.is_empty() { if !params.is_empty() {
@@ -141,6 +141,34 @@ impl Darkfid {
JsonResponse::new(JsonValue::Number(last_block_height.0 as f64), id).into() JsonResponse::new(JsonValue::Number(last_block_height.0 as f64), id).into()
} }
// RPCAPI:
// Queries the validator to find the current best fork next block height.
//
// **Params:**
// * `None`
//
// **Returns:**
// * `f64` Height of the last known block
//
// --> {"jsonrpc": "2.0", "method": "blockchain.best_fork_next_block_height", "params": [], "id": 1}
// <-- {"jsonrpc": "2.0", "result": 1234, "id": 1}
pub async fn blockchain_best_fork_next_block_height(
&self,
id: u16,
params: JsonValue,
) -> JsonResult {
let params = params.get::<Vec<JsonValue>>().unwrap();
if !params.is_empty() {
return JsonError::new(InvalidParams, None, id).into()
}
let Ok(next_block_height) = self.validator.best_fork_next_block_height().await else {
return JsonError::new(InternalError, None, id).into()
};
JsonResponse::new(JsonValue::Number(next_block_height as f64), id).into()
}
// RPCAPI: // RPCAPI:
// Initializes a subscription to new incoming blocks. // Initializes a subscription to new incoming blocks.
// Once a subscription is established, `darkfid` will send JSON-RPC notifications of // Once a subscription is established, `darkfid` will send JSON-RPC notifications of

View File

@@ -24,7 +24,7 @@ use darkfi::{
Error, Result, Error, Result,
}; };
use darkfi_sdk::crypto::MerkleTree; use darkfi_sdk::crypto::MerkleTree;
use log::{error, info}; use log::{debug, error, info};
use crate::Darkfid; use crate::Darkfid;
@@ -50,6 +50,7 @@ pub async fn garbage_collect_task(node: Arc<Darkfid>) -> Result<()> {
for tx in txs { for tx in txs {
let tx_hash = tx.hash(); let tx_hash = tx.hash();
let tx_vec = [tx.clone()]; let tx_vec = [tx.clone()];
let mut valid = false;
// Grab a lock over current consensus forks state // Grab a lock over current consensus forks state
let mut forks = node.validator.consensus.forks.write().await; let mut forks = node.validator.consensus.forks.write().await;
@@ -108,15 +109,32 @@ pub async fn garbage_collect_task(node: Arc<Darkfid>) -> Result<()> {
) )
.await .await
{ {
Ok(_) => {} Ok(_) => valid = true,
Err(Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(_))) => { Err(Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(_))) => {
// Remove transaction from fork's mempool // Remove transaction from fork's mempool
fork.mempool.retain(|tx| *tx != tx_hash); fork.mempool.retain(|tx| *tx != tx_hash);
} }
Err(e) => return Err(e), Err(e) => {
error!(
target: "darkfid::task::garbage_collect_task",
"Verifying transaction {tx_hash} failed: {e}"
);
return Err(e)
}
} }
} }
// Remove transaction if its invalid for all the forks
if !valid {
debug!(target: "darkfid::task::garbage_collect_task", "Removing invalid transaction: {tx_hash}");
if let Err(e) = node.validator.blockchain.remove_pending_txs_hashes(&[tx_hash]) {
error!(
target: "darkfid::task::garbage_collect_task",
"Removing invalid transaction {tx_hash} failed: {e}"
);
};
}
// Drop forks lock // Drop forks lock
drop(forks); drop(forks);
} }

View File

@@ -22,6 +22,7 @@ blake3 = "1.5.0"
bs58 = "0.5.0" bs58 = "0.5.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4.21" log = "0.4.21"
num-bigint = "0.4.4"
prettytable-rs = "0.10.0" prettytable-rs = "0.10.0"
rand = "0.8.5" rand = "0.8.5"
rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]} rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}

View File

@@ -11,6 +11,12 @@ CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_tr
tree BLOB NOT NULL tree BLOB NOT NULL
); );
-- The Sparse Merkle tree containing coins nullifiers
CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_smt (
smt_key BLOB INTEGER PRIMARY KEY NOT NULL,
smt_value BLOB NOT NULL
);
-- The keypairs in our wallet -- The keypairs in our wallet
CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_keys ( CREATE TABLE IF NOT EXISTS BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o_money_keys (
key_id INTEGER PRIMARY KEY NOT NULL, key_id INTEGER PRIMARY KEY NOT NULL,

View File

@@ -192,9 +192,14 @@ pub fn generate_completions(shell: &str) -> Result<()> {
let user_data = Arg::with_name("user-data").help("Optional user data to use"); let user_data = Arg::with_name("user-data").help("Optional user data to use");
let transfer = SubCommand::with_name("transfer") let transfer =
.about("Create a payment transaction") SubCommand::with_name("transfer").about("Create a payment transaction").args(&vec![
.args(&vec![amount, token, recipient, spend_hook.clone(), user_data.clone()]); amount.clone(),
token.clone(),
recipient.clone(),
spend_hook.clone(),
user_data.clone(),
]);
// Otc // Otc
let value_pair = Arg::with_name("value-pair") let value_pair = Arg::with_name("value-pair")
@@ -286,16 +291,19 @@ pub fn generate_completions(shell: &str) -> Result<()> {
.about("Mint an imported DAO on-chain") .about("Mint an imported DAO on-chain")
.args(&vec![name.clone()]); .args(&vec![name.clone()]);
let recipient = let duration = Arg::with_name("duration").help("Duration of the proposal, in days");
Arg::with_name("recipient").help("Pubkey to send tokens to with proposal success");
let amount = Arg::with_name("amount").help("Amount to send from DAO with proposal success"); let propose_transfer = SubCommand::with_name("propose-transfer")
.about("Create a transfer proposal for a DAO")
let token = Arg::with_name("token").help("Token ID to send from DAO with proposal success"); .args(&vec![
name.clone(),
let propose = SubCommand::with_name("propose") duration,
.about("Create a proposal for a DAO") amount,
.args(&vec![name.clone(), recipient, amount, token]); token,
recipient,
spend_hook.clone(),
user_data.clone(),
]);
let proposals = let proposals =
SubCommand::with_name("proposals").about("List DAO proposals").args(&vec![name.clone()]); SubCommand::with_name("proposals").about("List DAO proposals").args(&vec![name.clone()]);
@@ -332,7 +340,7 @@ pub fn generate_completions(shell: &str) -> Result<()> {
list, list,
balance, balance,
mint, mint,
propose, propose_transfer,
proposals, proposals,
proposal, proposal,
vote, vote,

View File

@@ -24,12 +24,13 @@ use rusqlite::types::Value;
use darkfi::{ use darkfi::{
tx::{ContractCallLeaf, Transaction, TransactionBuilder}, tx::{ContractCallLeaf, Transaction, TransactionBuilder},
util::parse::encode_base10, util::parse::{decode_base10, encode_base10},
zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit}, zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
zkas::ZkBinary, zkas::ZkBinary,
Error, Result, Error, Result,
}; };
use darkfi_dao_contract::{ use darkfi_dao_contract::{
blockwindow,
client::{make_mint_call, DaoProposeCall, DaoProposeStakeInput, DaoVoteCall, DaoVoteInput}, client::{make_mint_call, DaoProposeCall, DaoProposeStakeInput, DaoVoteCall, DaoVoteInput},
model::{Dao, DaoAuthCall, DaoBulla, DaoMintParams, DaoProposeParams, DaoVoteParams}, model::{Dao, DaoAuthCall, DaoBulla, DaoMintParams, DaoProposeParams, DaoVoteParams},
DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS, DaoFunction, DAO_CONTRACT_ZKAS_DAO_MINT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS,
@@ -37,7 +38,9 @@ use darkfi_dao_contract::{
DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
}; };
use darkfi_money_contract::{ use darkfi_money_contract::{
client::OwnCoin, model::TokenId, MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1, client::OwnCoin,
model::{CoinAttributes, TokenId},
MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
}; };
use darkfi_sdk::{ use darkfi_sdk::{
bridgetree, bridgetree,
@@ -60,7 +63,8 @@ use darkfi_serial::{
use crate::{ use crate::{
convert_named_params, convert_named_params,
error::{WalletDbError, WalletDbResult}, error::{WalletDbError, WalletDbResult},
money::BALANCE_BASE10_DECIMALS, money::{BALANCE_BASE10_DECIMALS, MONEY_SMT_COL_KEY, MONEY_SMT_COL_VALUE, MONEY_SMT_TABLE},
walletdb::{WalletSmt, WalletStorage},
Drk, Drk,
}; };
@@ -372,19 +376,14 @@ impl Drk {
pub async fn initialize_dao(&self) -> WalletDbResult<()> { pub async fn initialize_dao(&self) -> WalletDbResult<()> {
// Initialize DAO wallet schema // Initialize DAO wallet schema
let wallet_schema = include_str!("../dao.sql"); let wallet_schema = include_str!("../dao.sql");
self.wallet.exec_batch_sql(wallet_schema).await?; self.wallet.exec_batch_sql(wallet_schema)?;
// Check if we have to initialize the Merkle trees. // Check if we have to initialize the Merkle trees.
// We check if one exists, but we actually create two. This should be written // We check if one exists, but we actually create two. This should be written
// a bit better and safer. // a bit better and safer.
// For now, on success, we don't care what's returned, but in the future // For now, on success, we don't care what's returned, but in the future
// we should actually check it. // we should actually check it.
if self if self.wallet.query_single(&DAO_TREES_TABLE, &[DAO_TREES_COL_DAOS_TREE], &[]).is_err() {
.wallet
.query_single(&DAO_TREES_TABLE, &[DAO_TREES_COL_DAOS_TREE], &[])
.await
.is_err()
{
println!("Initializing DAO Merkle trees"); println!("Initializing DAO Merkle trees");
let tree = MerkleTree::new(1); let tree = MerkleTree::new(1);
self.put_dao_trees(&tree, &tree).await?; self.put_dao_trees(&tree, &tree).await?;
@@ -402,27 +401,25 @@ impl Drk {
) -> WalletDbResult<()> { ) -> WalletDbResult<()> {
// First we remove old records // First we remove old records
let query = format!("DELETE FROM {};", *DAO_TREES_TABLE); let query = format!("DELETE FROM {};", *DAO_TREES_TABLE);
self.wallet.exec_sql(&query, &[]).await?; self.wallet.exec_sql(&query, &[])?;
// then we insert the new one // then we insert the new one
let query = format!( let query = format!(
"INSERT INTO {} ({}, {}) VALUES (?1, ?2);", "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
*DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE, *DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE,
); );
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![
rusqlite::params![ serialize_async(daos_tree).await,
serialize_async(daos_tree).await, serialize_async(proposals_tree).await
serialize_async(proposals_tree).await ],
], )
)
.await
} }
/// Fetch DAO Merkle trees from the wallet. /// Fetch DAO Merkle trees from the wallet.
pub async fn get_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> { pub async fn get_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
let row = match self.wallet.query_single(&DAO_TREES_TABLE, &[], &[]).await { let row = match self.wallet.query_single(&DAO_TREES_TABLE, &[], &[]) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -507,7 +504,7 @@ impl Drk {
/// Fetch all known DAOs from the wallet. /// Fetch all known DAOs from the wallet.
pub async fn get_daos(&self) -> Result<Vec<DaoRecord>> { pub async fn get_daos(&self) -> Result<Vec<DaoRecord>> {
let rows = match self.wallet.query_multiple(&DAO_DAOS_TABLE, &[], &[]).await { let rows = match self.wallet.query_multiple(&DAO_DAOS_TABLE, &[], &[]) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!("[get_daos] DAOs retrieval failed: {e:?}"))) return Err(Error::RusqliteError(format!("[get_daos] DAOs retrieval failed: {e:?}")))
@@ -630,15 +627,11 @@ impl Drk {
))) )))
}; };
let rows = match self let rows = match self.wallet.query_multiple(
.wallet &DAO_PROPOSALS_TABLE,
.query_multiple( &[],
&DAO_PROPOSALS_TABLE, convert_named_params! {(DAO_PROPOSALS_COL_DAO_NAME, name)},
&[], ) {
convert_named_params! {(DAO_PROPOSALS_COL_DAO_NAME, name)},
)
.await
{
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -854,23 +847,21 @@ impl Drk {
DAO_DAOS_COL_NAME, DAO_DAOS_COL_NAME,
dao.name, dao.name,
); );
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![
rusqlite::params![ serialize_async(&dao.leaf_position.unwrap()).await,
serialize_async(&dao.leaf_position.unwrap()).await, serialize_async(&dao.tx_hash.unwrap()).await,
serialize_async(&dao.tx_hash.unwrap()).await, dao.call_index.unwrap()
dao.call_index.unwrap() ],
], )?;
)
.await?;
} }
Ok(()) Ok(())
} }
/// Unconfirm imported DAOs by removing the leaf position, txid, and call index. /// Unconfirm imported DAOs by removing the leaf position, txid, and call index.
pub async fn unconfirm_daos(&self, daos: &[DaoRecord]) -> WalletDbResult<()> { pub fn unconfirm_daos(&self, daos: &[DaoRecord]) -> WalletDbResult<()> {
for dao in daos { for dao in daos {
let query = format!( let query = format!(
"UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = \'{}\';", "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = \'{}\';",
@@ -881,9 +872,10 @@ impl Drk {
DAO_DAOS_COL_NAME, DAO_DAOS_COL_NAME,
dao.name, dao.name,
); );
self.wallet self.wallet.exec_sql(
.exec_sql(&query, rusqlite::params![None::<Vec<u8>>, None::<Vec<u8>>, None::<u64>,]) &query,
.await?; rusqlite::params![None::<Vec<u8>>, None::<Vec<u8>>, None::<u64>,],
)?;
} }
Ok(()) Ok(())
@@ -914,24 +906,20 @@ impl Drk {
DAO_PROPOSALS_COL_CALL_INDEX, DAO_PROPOSALS_COL_CALL_INDEX,
); );
if let Err(e) = self if let Err(e) = self.wallet.exec_sql(
.wallet &query,
.exec_sql( rusqlite::params![
&query, dao.name,
rusqlite::params![ serialize_async(&proposal.recipient).await,
dao.name, serialize_async(&proposal.amount).await,
serialize_async(&proposal.recipient).await, serialize_async(&proposal.token_id).await,
serialize_async(&proposal.amount).await, serialize_async(&proposal.bulla_blind).await,
serialize_async(&proposal.token_id).await, serialize_async(&proposal.leaf_position.unwrap()).await,
serialize_async(&proposal.bulla_blind).await, serialize_async(&proposal.money_snapshot_tree.clone().unwrap()).await,
serialize_async(&proposal.leaf_position.unwrap()).await, serialize_async(&proposal.tx_hash.unwrap()).await,
serialize_async(&proposal.money_snapshot_tree.clone().unwrap()).await, proposal.call_index,
serialize_async(&proposal.tx_hash.unwrap()).await, ],
proposal.call_index, ) {
],
)
.await
{
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
"[put_dao_proposals] Proposal insert failed: {e:?}" "[put_dao_proposals] Proposal insert failed: {e:?}"
))) )))
@@ -958,20 +946,18 @@ impl Drk {
DAO_VOTES_COL_CALL_INDEX, DAO_VOTES_COL_CALL_INDEX,
); );
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![
rusqlite::params![ vote.proposal_id,
vote.proposal_id, vote.vote_option as u64,
vote.vote_option as u64, serialize_async(&vote.yes_vote_blind).await,
serialize_async(&vote.yes_vote_blind).await, serialize_async(&vote.all_vote_value).await,
serialize_async(&vote.all_vote_value).await, serialize_async(&vote.all_vote_blind).await,
serialize_async(&vote.all_vote_blind).await, serialize_async(&vote.tx_hash.unwrap()).await,
serialize_async(&vote.tx_hash.unwrap()).await, vote.call_index.unwrap(),
vote.call_index.unwrap(), ],
], )?;
)
.await?;
println!("DAO vote added to wallet"); println!("DAO vote added to wallet");
} }
@@ -999,24 +985,24 @@ impl Drk {
return Err(WalletDbError::GenericError); return Err(WalletDbError::GenericError);
} }
}; };
self.unconfirm_daos(&daos).await?; self.unconfirm_daos(&daos)?;
println!("Successfully unconfirmed DAOs"); println!("Successfully unconfirmed DAOs");
Ok(()) Ok(())
} }
/// Reset all DAO proposals in the wallet. /// Reset all DAO proposals in the wallet.
pub async fn reset_dao_proposals(&self) -> WalletDbResult<()> { pub fn reset_dao_proposals(&self) -> WalletDbResult<()> {
println!("Resetting DAO proposals"); println!("Resetting DAO proposals");
let query = format!("DELETE FROM {};", *DAO_PROPOSALS_TABLE); let query = format!("DELETE FROM {};", *DAO_PROPOSALS_TABLE);
self.wallet.exec_sql(&query, &[]).await self.wallet.exec_sql(&query, &[])
} }
/// Reset all DAO votes in the wallet. /// Reset all DAO votes in the wallet.
pub async fn reset_dao_votes(&self) -> WalletDbResult<()> { pub fn reset_dao_votes(&self) -> WalletDbResult<()> {
println!("Resetting DAO votes"); println!("Resetting DAO votes");
let query = format!("DELETE FROM {};", *DAO_VOTES_TABLE); let query = format!("DELETE FROM {};", *DAO_VOTES_TABLE);
self.wallet.exec_sql(&query, &[]).await self.wallet.exec_sql(&query, &[])
} }
/// Import given DAO params into the wallet with a given name. /// Import given DAO params into the wallet with a given name.
@@ -1034,10 +1020,8 @@ impl Drk {
"INSERT INTO {} ({}, {}) VALUES (?1, ?2);", "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
*DAO_DAOS_TABLE, DAO_DAOS_COL_NAME, DAO_DAOS_COL_PARAMS, *DAO_DAOS_TABLE, DAO_DAOS_COL_NAME, DAO_DAOS_COL_PARAMS,
); );
if let Err(e) = self if let Err(e) =
.wallet self.wallet.exec_sql(&query, rusqlite::params![name, serialize_async(&params).await,])
.exec_sql(&query, rusqlite::params![name, serialize_async(&params).await,])
.await
{ {
return Err(Error::RusqliteError(format!("[import_dao] DAO insert failed: {e:?}"))) return Err(Error::RusqliteError(format!("[import_dao] DAO insert failed: {e:?}")))
}; };
@@ -1047,11 +1031,11 @@ impl Drk {
/// Fetch a DAO given its name. /// Fetch a DAO given its name.
pub async fn get_dao_by_name(&self, name: &str) -> Result<DaoRecord> { pub async fn get_dao_by_name(&self, name: &str) -> Result<DaoRecord> {
let row = match self let row = match self.wallet.query_single(
.wallet &DAO_DAOS_TABLE,
.query_single(&DAO_DAOS_TABLE, &[], convert_named_params! {(DAO_DAOS_COL_NAME, name)}) &[],
.await convert_named_params! {(DAO_DAOS_COL_NAME, name)},
{ ) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -1111,15 +1095,11 @@ impl Drk {
/// Fetch a DAO proposal by its ID /// Fetch a DAO proposal by its ID
pub async fn get_dao_proposal_by_id(&self, proposal_id: u64) -> Result<DaoProposal> { pub async fn get_dao_proposal_by_id(&self, proposal_id: u64) -> Result<DaoProposal> {
// Grab the proposal record // Grab the proposal record
let row = match self let row = match self.wallet.query_single(
.wallet &DAO_PROPOSALS_TABLE,
.query_single( &[],
&DAO_PROPOSALS_TABLE, convert_named_params! {(DAO_PROPOSALS_COL_PROPOSAL_ID, proposal_id)},
&[], ) {
convert_named_params! {(DAO_PROPOSALS_COL_PROPOSAL_ID, proposal_id)},
)
.await
{
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -1140,15 +1120,11 @@ impl Drk {
// Fetch all known DAO proposal votes from the wallet given a proposal ID // Fetch all known DAO proposal votes from the wallet given a proposal ID
pub async fn get_dao_proposal_votes(&self, proposal_id: u64) -> Result<Vec<DaoVote>> { pub async fn get_dao_proposal_votes(&self, proposal_id: u64) -> Result<Vec<DaoVote>> {
let rows = match self let rows = match self.wallet.query_multiple(
.wallet &DAO_VOTES_TABLE,
.query_multiple( &[],
&DAO_VOTES_TABLE, convert_named_params! {(DAO_VOTES_COL_PROPOSAL_ID, proposal_id)},
&[], ) {
convert_named_params! {(DAO_VOTES_COL_PROPOSAL_ID, proposal_id)},
)
.await
{
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -1323,89 +1299,120 @@ impl Drk {
Ok(tx) Ok(tx)
} }
/// Create a DAO proposal /// Create a DAO transfer proposal.
pub async fn dao_propose( #[allow(clippy::too_many_arguments)]
pub async fn dao_propose_transfer(
&self, &self,
name: &str, name: &str,
_recipient: PublicKey, duration_days: u64,
amount: u64, amount: &str,
token_id: TokenId, token_id: TokenId,
recipient: PublicKey,
spend_hook: Option<FuncId>,
user_data: Option<pallas::Base>,
) -> Result<Transaction> { ) -> Result<Transaction> {
// Fetch DAO and check its deployed
let dao = self.get_dao_by_name(name).await?; let dao = self.get_dao_by_name(name).await?;
if dao.leaf_position.is_none() || dao.tx_hash.is_none() { if dao.leaf_position.is_none() || dao.tx_hash.is_none() {
return Err(Error::Custom( return Err(Error::Custom(
"[dao_propose] DAO seems to not have been deployed yet".to_string(), "[dao_propose_transfer] DAO seems to not have been deployed yet".to_string(),
)) ))
} }
let bulla = dao.bulla(); // Fetch DAO unspent OwnCoins to see what its balance is
let owncoins = self.get_coins(false).await?;
let dao_spend_hook = let dao_spend_hook =
FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 } FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
.to_func_id(); .to_func_id();
let dao_bulla = dao.bulla();
let mut dao_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect(); let dao_owncoins =
dao_owncoins.retain(|x| { self.get_contract_token_coins(&token_id, &dao_spend_hook, &dao_bulla.inner()).await?;
x.note.token_id == token_id &&
x.note.spend_hook == dao_spend_hook &&
x.note.user_data == bulla.inner()
});
let mut gov_owncoins: Vec<OwnCoin> = owncoins.iter().map(|x| x.0.clone()).collect();
gov_owncoins.retain(|x| x.note.token_id == dao.params.dao.gov_token_id);
if dao_owncoins.is_empty() { if dao_owncoins.is_empty() {
return Err(Error::Custom(format!( return Err(Error::Custom(format!(
"[dao_propose] Did not find any {token_id} coins owned by this DAO" "[dao_propose_transfer] Did not find any {token_id} unspent coins owned by this DAO"
)))
}
if gov_owncoins.is_empty() {
return Err(Error::Custom(format!(
"[dao_propose] Did not find any governance {} coins in wallet",
dao.params.dao.gov_token_id
))) )))
} }
// Check DAO balance is sufficient
let amount = decode_base10(amount, BALANCE_BASE10_DECIMALS, false)?;
if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < amount { if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < amount {
return Err(Error::Custom(format!( return Err(Error::Custom(format!(
"[dao_propose] Not enough DAO balance for token ID: {}", "[dao_propose_transfer] Not enough DAO balance for token ID: {token_id}",
token_id
))) )))
} }
if gov_owncoins.iter().map(|x| x.note.value).sum::<u64>() < dao.params.dao.proposer_limit { // Fetch our own governance OwnCoins to see what our balance is
let gov_owncoins = self.get_token_coins(&dao.params.dao.gov_token_id).await?;
if gov_owncoins.is_empty() {
return Err(Error::Custom(format!( return Err(Error::Custom(format!(
"[dao_propose] Not enough gov token {} balance to propose", "[dao_propose_transfer] Did not find any governance {} coins in wallet",
dao.params.dao.gov_token_id dao.params.dao.gov_token_id
))) )))
} }
// FIXME: Here we're looking for a coin == proposer_limit but this shouldn't have to // Find which governance coins we can use
// be the case { let mut total_value = 0;
let Some(gov_coin) = let mut gov_owncoins_to_use = vec![];
gov_owncoins.iter().find(|x| x.note.value == dao.params.dao.proposer_limit) for gov_owncoin in gov_owncoins {
else { if total_value >= amount {
return Err(Error::Custom(format!( break
"[dao_propose] Did not find a single gov coin of value {}", }
dao.params.dao.proposer_limit
)))
};
// }
// Lookup the zkas bins total_value += gov_owncoin.note.value;
gov_owncoins_to_use.push(gov_owncoin);
}
// Check our governance coins balance is sufficient
if total_value < amount {
return Err(Error::Custom(format!(
"[dao_propose_transfer] Not enough gov token {} balance to propose",
dao.params.dao.gov_token_id
)))
}
// Generate proposal coin attributes
let proposal_coinattrs = vec![CoinAttributes {
public_key: recipient,
value: amount,
token_id,
spend_hook: spend_hook.unwrap_or(FuncId::none()),
user_data: user_data.unwrap_or(pallas::Base::ZERO),
blind: Blind::random(&mut OsRng),
}];
// 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. First we grab the fee call from money.
let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
else {
return Err(Error::Custom("[dao_propose_transfer] Fee circuit not found".to_string()))
};
let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
// Creating Fee circuit proving key
let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
// Now we grab the DAO bins
let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?; let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
let Some(propose_burn_zkbin) = let Some(propose_burn_zkbin) =
zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS) zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS)
else { else {
return Err(Error::Custom("[dao_propose] Propose Burn circuit not found".to_string())) return Err(Error::Custom(
"[dao_propose_transfer] Propose Burn circuit not found".to_string(),
))
}; };
let Some(propose_main_zkbin) = let Some(propose_main_zkbin) =
zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS) zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS)
else { else {
return Err(Error::Custom("[dao_propose] Propose Main circuit not found".to_string())) return Err(Error::Custom(
"[dao_propose_transfer] Propose Main circuit not found".to_string(),
))
}; };
let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?; let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?;
@@ -1416,55 +1423,36 @@ impl Drk {
let propose_main_circuit = let propose_main_circuit =
ZkCircuit::new(empty_witnesses(&propose_main_zkbin)?, &propose_main_zkbin); ZkCircuit::new(empty_witnesses(&propose_main_zkbin)?, &propose_main_zkbin);
println!("Creating Propose Burn circuit proving key"); // Creating DAO ProposeBurn and ProposeMain circuits proving keys
let propose_burn_pk = ProvingKey::build(propose_burn_zkbin.k, &propose_burn_circuit); let propose_burn_pk = ProvingKey::build(propose_burn_zkbin.k, &propose_burn_circuit);
println!("Creating Propose Main circuit proving key");
let propose_main_pk = ProvingKey::build(propose_main_zkbin.k, &propose_main_circuit); let propose_main_pk = ProvingKey::build(propose_main_zkbin.k, &propose_main_circuit);
// Now create the parameters for the proposal tx // Fetch our money Merkle tree
let signature_secret = SecretKey::random(&mut OsRng);
// Get the Merkle path for the gov coin in the money tree
let money_merkle_tree = self.get_money_tree().await?; let money_merkle_tree = self.get_money_tree().await?;
let gov_coin_merkle_path = money_merkle_tree.witness(gov_coin.leaf_position, 0).unwrap();
// Fetch the daos Merkle tree // Now we can create the proposal transaction parameters.
let (daos_tree, _) = self.get_dao_trees().await?; // We first generate the `DaoProposeStakeInput` inputs,
// using our governance OwnCoins.
let mut inputs = Vec::with_capacity(gov_owncoins_to_use.len());
for gov_owncoin in gov_owncoins_to_use {
let input = DaoProposeStakeInput {
secret: gov_owncoin.secret,
note: gov_owncoin.note.clone(),
leaf_position: gov_owncoin.leaf_position,
merkle_path: money_merkle_tree.witness(gov_owncoin.leaf_position, 0).unwrap(),
};
inputs.push(input);
}
// This is complete wrong and needs to be fixed later
// Non-trivial to fix, so we just make a workaround for now
let hasher = PoseidonFp::new();
let store = MemoryStorageFp::new();
let money_null_smt = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP);
let input = DaoProposeStakeInput {
secret: gov_coin.secret, // <-- TODO: Is this correct?
note: gov_coin.note.clone(),
leaf_position: gov_coin.leaf_position,
merkle_path: gov_coin_merkle_path,
money_null_smt: &money_null_smt,
signature_secret,
};
let (dao_merkle_path, dao_merkle_root) = {
let root = daos_tree.root(0).unwrap();
let leaf_pos = dao.leaf_position.unwrap();
let dao_merkle_path = daos_tree.witness(leaf_pos, 0).unwrap();
(dao_merkle_path, root)
};
// TODO:
/*
// Convert coin_params to actual coins // Convert coin_params to actual coins
let mut proposal_coins = vec![]; let mut proposal_coins = vec![];
for coin_params in proposal_coinattrs { for coin_params in proposal_coinattrs {
proposal_coins.push(coin_params.to_coin()); proposal_coins.push(coin_params.to_coin());
} }
*/ let mut proposal_data = vec![];
let proposal_data = vec![]; proposal_coins.encode_async(&mut proposal_data).await?;
//proposal_coins.encode_async(&mut proposal_data).await.unwrap();
// Create Auth calls
let auth_calls = vec![ let auth_calls = vec![
DaoAuthCall { DaoAuthCall {
contract_id: *DAO_CONTRACT_ID, contract_id: *DAO_CONTRACT_ID,
@@ -1478,31 +1466,55 @@ impl Drk {
}, },
]; ];
// TODO: get current height to calculate day // Retrieve current block height and compute current day
// Also contract must check we don't mint a proposal that its creation day is let current_block_height = self.get_next_block_height().await?;
// less than current height let creation_day = blockwindow(current_block_height);
// Create the actual proposal
// TODO: Simplify this model struct import once // TODO: Simplify this model struct import once
// we use the structs from contract everwhere // we use the structs from contract everwhere
let proposal = darkfi_dao_contract::model::DaoProposal { let proposal = darkfi_dao_contract::model::DaoProposal {
auth_calls, auth_calls,
creation_day: 0, creation_day,
duration_days: 30, duration_days,
user_data: pallas::Base::ZERO, user_data: user_data.unwrap_or(pallas::Base::ZERO),
dao_bulla: dao.bulla(), dao_bulla,
blind: Blind::random(&mut OsRng), blind: Blind::random(&mut OsRng),
}; };
// Now create the parameters for the proposal tx
let signature_secret = SecretKey::random(&mut OsRng);
// Fetch the daos Merkle tree to compute the DAO Merkle path and root
let (daos_tree, _) = self.get_dao_trees().await?;
let (dao_merkle_path, dao_merkle_root) = {
let root = daos_tree.root(0).unwrap();
let leaf_pos = dao.leaf_position.unwrap();
let dao_merkle_path = daos_tree.witness(leaf_pos, 0).unwrap();
(dao_merkle_path, root)
};
// Generate the Money nullifiers Sparse Merkle Tree
let store = WalletStorage::new(
&self.wallet,
&MONEY_SMT_TABLE,
MONEY_SMT_COL_KEY,
MONEY_SMT_COL_VALUE,
);
let money_null_smt = WalletSmt::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
// Create the proposal call
let call = DaoProposeCall { let call = DaoProposeCall {
inputs: vec![input], money_null_smt: &money_null_smt,
inputs,
proposal, proposal,
dao: dao.params.dao, dao: dao.params.dao,
dao_leaf_position: dao.leaf_position.unwrap(), dao_leaf_position: dao.leaf_position.unwrap(),
dao_merkle_path, dao_merkle_path,
dao_merkle_root, dao_merkle_root,
signature_secret,
}; };
println!("Creating ZK proofs...");
let (params, proofs) = call.make( let (params, proofs) = call.make(
&propose_burn_zkbin, &propose_burn_zkbin,
&propose_burn_pk, &propose_burn_pk,
@@ -1510,14 +1522,34 @@ impl Drk {
&propose_main_pk, &propose_main_pk,
)?; )?;
// Encode the call
let mut data = vec![DaoFunction::Propose as u8]; let mut data = vec![DaoFunction::Propose as u8];
params.encode_async(&mut data).await?; params.encode_async(&mut data).await?;
let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data }; let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
// Create the TransactionBuilder containing above call
let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?; let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
// We first have to execute the fee-less tx to gather its used gas, and then we feed
// it into the fee-creating function.
let mut tx = tx_builder.build()?; let mut tx = tx_builder.build()?;
let sigs = tx.create_sigs(&[signature_secret])?; let sigs = tx.create_sigs(&[signature_secret])?;
tx.signatures = vec![sigs]; tx.signatures = vec![sigs];
let tree = self.get_money_tree().await?;
let (fee_call, fee_proofs, fee_secrets) =
self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
// Append the fee call to the transaction
tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
// Now build the actual transaction and sign it with all necessary keys.
let mut tx = tx_builder.build()?;
let sigs = tx.create_sigs(&[signature_secret])?;
tx.signatures.push(sigs);
let sigs = tx.create_sigs(&fee_secrets)?;
tx.signatures.push(sigs);
Ok(tx) Ok(tx)
} }

View File

@@ -50,10 +50,10 @@ pub const DEPLOY_AUTH_COL_IS_FROZEN: &str = "is_frozen";
impl Drk { impl Drk {
/// Initialize wallet with tables for the Deployooor contract. /// Initialize wallet with tables for the Deployooor contract.
pub async fn initialize_deployooor(&self) -> WalletDbResult<()> { pub fn initialize_deployooor(&self) -> WalletDbResult<()> {
// Initialize Deployooor wallet schema // Initialize Deployooor wallet schema
let wallet_schema = include_str!("../deploy.sql"); let wallet_schema = include_str!("../deploy.sql");
self.wallet.exec_batch_sql(wallet_schema).await?; self.wallet.exec_batch_sql(wallet_schema)?;
Ok(()) Ok(())
} }
@@ -68,7 +68,7 @@ impl Drk {
"INSERT INTO {} ({}, {}) VALUES (?1, ?2);", "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
*DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_DEPLOY_AUTHORITY, DEPLOY_AUTH_COL_IS_FROZEN, *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_DEPLOY_AUTHORITY, DEPLOY_AUTH_COL_IS_FROZEN,
); );
self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&keypair).await, 0]).await?; self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&keypair).await, 0])?;
eprintln!("Created new contract deploy authority"); eprintln!("Created new contract deploy authority");
println!("Contract ID: {}", ContractId::derive_public(keypair.public)); println!("Contract ID: {}", ContractId::derive_public(keypair.public));
@@ -78,7 +78,7 @@ impl Drk {
/// List contract deploy authorities from the wallet /// List contract deploy authorities from the wallet
pub async fn list_deploy_auth(&self) -> Result<Vec<(i64, ContractId, bool)>> { pub async fn list_deploy_auth(&self) -> Result<Vec<(i64, ContractId, bool)>> {
let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]).await { let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -111,15 +111,11 @@ impl Drk {
/// Retrieve a deploy authority keypair given an index /// Retrieve a deploy authority keypair given an index
async fn get_deploy_auth(&self, idx: u64) -> Result<Keypair> { async fn get_deploy_auth(&self, idx: u64) -> Result<Keypair> {
// Find the deploy authority keypair // Find the deploy authority keypair
let row = match self let row = match self.wallet.query_single(
.wallet &DEPLOY_AUTH_TABLE,
.query_single( &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
&DEPLOY_AUTH_TABLE, convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
&[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY], ) {
convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
)
.await
{
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(

View File

@@ -25,6 +25,7 @@ pub type WalletDbResult<T> = std::result::Result<T, WalletDbError>;
pub enum WalletDbError { pub enum WalletDbError {
// Connection related errors // Connection related errors
ConnectionFailed = -32100, ConnectionFailed = -32100,
FailedToAquireLock = -32101,
// Configuration related errors // Configuration related errors
PragmaUpdateError = -32110, PragmaUpdateError = -32110,

View File

@@ -352,19 +352,28 @@ enum DaoSubcmd {
name: String, name: String,
}, },
/// Create a proposal for a DAO /// Create a transfer proposal for a DAO
Propose { ProposeTransfer {
/// Name identifier for the DAO /// Name identifier for the DAO
name: String, name: String,
/// Pubkey to send tokens to with proposal success /// Duration of the proposal, in days
recipient: String, duration: u64,
/// Amount to send from DAO with proposal success /// Amount to send
amount: String, amount: String,
/// Token ID to send from DAO with proposal success /// Token ID to send
token: String, token: String,
/// Recipient address
recipient: String,
/// Optional contract spend hook to use
spend_hook: Option<String>,
/// Optional user data to use
user_data: Option<String>,
}, },
/// List DAO proposals /// List DAO proposals
@@ -586,9 +595,9 @@ impl Drk {
} }
/// Initialize wallet with tables for drk /// Initialize wallet with tables for drk
async fn initialize_wallet(&self) -> Result<()> { fn initialize_wallet(&self) -> Result<()> {
let wallet_schema = include_str!("../wallet.sql"); let wallet_schema = include_str!("../wallet.sql");
if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await { if let Err(e) = self.wallet.exec_batch_sql(wallet_schema) {
eprintln!("Error initializing wallet: {e:?}"); eprintln!("Error initializing wallet: {e:?}");
exit(2); exit(2);
} }
@@ -655,7 +664,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?; let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
if initialize { if initialize {
drk.initialize_wallet().await?; drk.initialize_wallet()?;
if let Err(e) = drk.initialize_money().await { if let Err(e) = drk.initialize_money().await {
eprintln!("Failed to initialize Money: {e:?}"); eprintln!("Failed to initialize Money: {e:?}");
exit(2); exit(2);
@@ -664,7 +673,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
eprintln!("Failed to initialize DAO: {e:?}"); eprintln!("Failed to initialize DAO: {e:?}");
exit(2); exit(2);
} }
if let Err(e) = drk.initialize_deployooor().await { if let Err(e) = drk.initialize_deployooor() {
eprintln!("Failed to initialize Deployooor: {e:?}"); eprintln!("Failed to initialize Deployooor: {e:?}");
exit(2); exit(2);
} }
@@ -749,7 +758,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
} }
if let Some(idx) = default_address { if let Some(idx) = default_address {
if let Err(e) = drk.set_default_address(idx).await { if let Err(e) = drk.set_default_address(idx) {
eprintln!("Failed to set default address: {e:?}"); eprintln!("Failed to set default address: {e:?}");
exit(2); exit(2);
} }
@@ -1197,12 +1206,23 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
Ok(()) Ok(())
} }
DaoSubcmd::Propose { name, recipient, amount, token } => { DaoSubcmd::ProposeTransfer {
name,
duration,
amount,
token,
recipient,
spend_hook,
user_data,
} => {
let drk =
Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
if let Err(e) = f64::from_str(&amount) { if let Err(e) = f64::from_str(&amount) {
eprintln!("Invalid amount: {e:?}"); eprintln!("Invalid amount: {e:?}");
exit(2); exit(2);
} }
let amount = decode_base10(&amount, BALANCE_BASE10_DECIMALS, true)?;
let rcpt = match PublicKey::from_str(&recipient) { let rcpt = match PublicKey::from_str(&recipient) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
@@ -1211,8 +1231,6 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
} }
}; };
let drk =
Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
let token_id = match drk.get_token(token).await { let token_id = match drk.get_token(token).await {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
@@ -1221,13 +1239,51 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
} }
}; };
let tx = match drk.dao_propose(&name, rcpt, amount, token_id).await { let spend_hook = match spend_hook {
Some(s) => match FuncId::from_str(&s) {
Ok(s) => Some(s),
Err(e) => {
eprintln!("Invalid spend hook: {e:?}");
exit(2);
}
},
None => None,
};
let user_data = match user_data {
Some(u) => {
let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
Ok(b) => b,
Err(e) => {
eprintln!("Invalid user data: {e:?}");
exit(2);
}
};
match pallas::Base::from_repr(bytes).into() {
Some(v) => Some(v),
None => {
eprintln!("Invalid user data");
exit(2);
}
}
}
None => None,
};
let tx = match drk
.dao_propose_transfer(
&name, duration, &amount, token_id, rcpt, spend_hook, user_data,
)
.await
{
Ok(tx) => tx, Ok(tx) => tx,
Err(e) => { Err(e) => {
eprintln!("Failed to create DAO proposal: {e:?}"); eprintln!("Failed to create DAO transfer proposal: {e:?}");
exit(2); exit(2);
} }
}; };
println!("{}", base64::encode(&serialize_async(&tx).await)); println!("{}", base64::encode(&serialize_async(&tx).await));
Ok(()) Ok(())
} }
@@ -1498,7 +1554,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
return Ok(()) return Ok(())
} }
let map = match drk.get_txs_history().await { let map = match drk.get_txs_history() {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
eprintln!("Failed to retrieve transactions history records: {e:?}"); eprintln!("Failed to retrieve transactions history records: {e:?}");

View File

@@ -44,8 +44,10 @@ use darkfi_money_contract::{
use darkfi_sdk::{ use darkfi_sdk::{
bridgetree, bridgetree,
crypto::{ crypto::{
note::AeadEncryptedNote, BaseBlind, FuncId, Keypair, MerkleNode, MerkleTree, PublicKey, note::AeadEncryptedNote,
ScalarBlind, SecretKey, MONEY_CONTRACT_ID, smt::{PoseidonFp, EMPTY_NODES_FP},
BaseBlind, FuncId, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind, SecretKey,
MONEY_CONTRACT_ID,
}, },
dark_tree::DarkLeaf, dark_tree::DarkLeaf,
pasta::pallas, pasta::pallas,
@@ -56,7 +58,9 @@ use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
use crate::{ use crate::{
convert_named_params, convert_named_params,
error::{WalletDbError, WalletDbResult}, error::{WalletDbError, WalletDbResult},
kaching, Drk, kaching,
walletdb::{WalletSmt, WalletStorage},
Drk,
}; };
// Wallet SQL table constant names. These have to represent the `wallet.sql` // Wallet SQL table constant names. These have to represent the `wallet.sql`
@@ -66,6 +70,7 @@ lazy_static! {
format!("{}_money_info", MONEY_CONTRACT_ID.to_string()); format!("{}_money_info", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_TREE_TABLE: String = pub static ref MONEY_TREE_TABLE: String =
format!("{}_money_tree", MONEY_CONTRACT_ID.to_string()); format!("{}_money_tree", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_SMT_TABLE: String = format!("{}_money_smt", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_KEYS_TABLE: String = pub static ref MONEY_KEYS_TABLE: String =
format!("{}_money_keys", MONEY_CONTRACT_ID.to_string()); format!("{}_money_keys", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_COINS_TABLE: String = pub static ref MONEY_COINS_TABLE: String =
@@ -82,6 +87,10 @@ pub const MONEY_INFO_COL_LAST_SCANNED_BLOCK: &str = "last_scanned_block";
// MONEY_TREE_TABLE // MONEY_TREE_TABLE
pub const MONEY_TREE_COL_TREE: &str = "tree"; pub const MONEY_TREE_COL_TREE: &str = "tree";
// MONEY_SMT_TABLE
pub const MONEY_SMT_COL_KEY: &str = "smt_key";
pub const MONEY_SMT_COL_VALUE: &str = "smt_value";
// MONEY_KEYS_TABLE // MONEY_KEYS_TABLE
pub const MONEY_KEYS_COL_KEY_ID: &str = "key_id"; pub const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
pub const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default"; pub const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default";
@@ -120,7 +129,7 @@ impl Drk {
pub async fn initialize_money(&self) -> WalletDbResult<()> { pub async fn initialize_money(&self) -> WalletDbResult<()> {
// Initialize Money wallet schema // Initialize Money wallet schema
let wallet_schema = include_str!("../money.sql"); let wallet_schema = include_str!("../money.sql");
self.wallet.exec_batch_sql(wallet_schema).await?; self.wallet.exec_batch_sql(wallet_schema)?;
// Check if we have to initialize the Merkle tree. // Check if we have to initialize the Merkle tree.
// We check if we find a row in the tree table, and if not, we create a // We check if we find a row in the tree table, and if not, we create a
@@ -138,12 +147,12 @@ impl Drk {
// We maintain the last scanned block as part of the Money contract, // We maintain the last scanned block as part of the Money contract,
// but at this moment it is also somewhat applicable to DAO scans. // but at this moment it is also somewhat applicable to DAO scans.
if self.last_scanned_block().await.is_err() { if self.last_scanned_block().is_err() {
let query = format!( let query = format!(
"INSERT INTO {} ({}) VALUES (?1);", "INSERT INTO {} ({}) VALUES (?1);",
*MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_BLOCK *MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_BLOCK
); );
self.wallet.exec_sql(&query, rusqlite::params![0]).await?; self.wallet.exec_sql(&query, rusqlite::params![0])?;
} }
// Insert DRK alias // Insert DRK alias
@@ -167,16 +176,14 @@ impl Drk {
MONEY_KEYS_COL_PUBLIC, MONEY_KEYS_COL_PUBLIC,
MONEY_KEYS_COL_SECRET MONEY_KEYS_COL_SECRET
); );
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![
rusqlite::params![ is_default,
is_default, serialize_async(&keypair.public).await,
serialize_async(&keypair.public).await, serialize_async(&keypair.secret).await
serialize_async(&keypair.secret).await ],
], )?;
)
.await?;
println!("New address:"); println!("New address:");
println!("{}", keypair.public); println!("{}", keypair.public);
@@ -186,15 +193,11 @@ impl Drk {
/// Fetch default secret key from the wallet. /// Fetch default secret key from the wallet.
pub async fn default_secret(&self) -> Result<SecretKey> { pub async fn default_secret(&self) -> Result<SecretKey> {
let row = match self let row = match self.wallet.query_single(
.wallet &MONEY_KEYS_TABLE,
.query_single( &[MONEY_KEYS_COL_SECRET],
&MONEY_KEYS_TABLE, convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
&[MONEY_KEYS_COL_SECRET], ) {
convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
)
.await
{
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -213,15 +216,11 @@ impl Drk {
/// Fetch default pubkey from the wallet. /// Fetch default pubkey from the wallet.
pub async fn default_address(&self) -> Result<PublicKey> { pub async fn default_address(&self) -> Result<PublicKey> {
let row = match self let row = match self.wallet.query_single(
.wallet &MONEY_KEYS_TABLE,
.query_single( &[MONEY_KEYS_COL_PUBLIC],
&MONEY_KEYS_TABLE, convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
&[MONEY_KEYS_COL_PUBLIC], ) {
convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
)
.await
{
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -239,11 +238,11 @@ impl Drk {
} }
/// Set provided index address as default in the wallet. /// Set provided index address as default in the wallet.
pub async fn set_default_address(&self, idx: usize) -> WalletDbResult<()> { pub fn set_default_address(&self, idx: usize) -> WalletDbResult<()> {
// First we update previous default record // First we update previous default record
let is_default = 0; let is_default = 0;
let query = format!("UPDATE {} SET {} = ?1", *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT,); let query = format!("UPDATE {} SET {} = ?1", *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT,);
self.wallet.exec_sql(&query, rusqlite::params![is_default]).await?; self.wallet.exec_sql(&query, rusqlite::params![is_default])?;
// and then we set the new one // and then we set the new one
let is_default = 1; let is_default = 1;
@@ -251,12 +250,12 @@ impl Drk {
"UPDATE {} SET {} = ?1 WHERE {} = ?2", "UPDATE {} SET {} = ?1 WHERE {} = ?2",
*MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_KEY_ID, *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_KEY_ID,
); );
self.wallet.exec_sql(&query, rusqlite::params![is_default, idx]).await self.wallet.exec_sql(&query, rusqlite::params![is_default, idx])
} }
/// Fetch all pukeys from the wallet. /// Fetch all pukeys from the wallet.
pub async fn addresses(&self) -> Result<Vec<(u64, PublicKey, SecretKey, u64)>> { pub async fn addresses(&self) -> Result<Vec<(u64, PublicKey, SecretKey, u64)>> {
let rows = match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[], &[]).await { let rows = match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[], &[]) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -299,18 +298,15 @@ impl Drk {
/// Fetch all secret keys from the wallet. /// Fetch all secret keys from the wallet.
pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> { pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
let rows = match self let rows =
.wallet match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[MONEY_KEYS_COL_SECRET], &[]) {
.query_multiple(&MONEY_KEYS_TABLE, &[MONEY_KEYS_COL_SECRET], &[]) Ok(r) => r,
.await Err(e) => {
{ return Err(Error::RusqliteError(format!(
Ok(r) => r, "[get_money_secrets] Secret keys retrieval failed: {e:?}"
Err(e) => { )))
return Err(Error::RusqliteError(format!( }
"[get_money_secrets] Secret keys retrieval failed: {e:?}" };
)))
}
};
let mut secrets = Vec::with_capacity(rows.len()); let mut secrets = Vec::with_capacity(rows.len());
@@ -356,7 +352,7 @@ impl Drk {
MONEY_KEYS_COL_SECRET MONEY_KEYS_COL_SECRET
); );
if let Err(e) = if let Err(e) =
self.wallet.exec_sql(&query, rusqlite::params![is_default, public, secret]).await self.wallet.exec_sql(&query, rusqlite::params![is_default, public, secret])
{ {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
"[import_money_secrets] Inserting new address failed: {e:?}" "[import_money_secrets] Inserting new address failed: {e:?}"
@@ -393,15 +389,13 @@ impl Drk {
/// The boolean in the returned tuple notes if the coin was marked as spent. /// The boolean in the returned tuple notes if the coin was marked as spent.
pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool, String)>> { pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool, String)>> {
let query = if fetch_spent { let query = if fetch_spent {
self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[]).await self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[])
} else { } else {
self.wallet self.wallet.query_multiple(
.query_multiple( &MONEY_COINS_TABLE,
&MONEY_COINS_TABLE, &[],
&[], convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false)},
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false)}, )
)
.await
}; };
let rows = match query { let rows = match query {
@@ -427,8 +421,7 @@ impl Drk {
&MONEY_COINS_TABLE, &MONEY_COINS_TABLE,
&[], &[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)}, convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)},
) );
.await;
let rows = match query { let rows = match query {
Ok(r) => r, Ok(r) => r,
@@ -447,6 +440,36 @@ impl Drk {
Ok(owncoins) Ok(owncoins)
} }
/// Fetch provided contract specified token unspend balances from the wallet.
pub async fn get_contract_token_coins(
&self,
token_id: &TokenId,
spend_hook: &FuncId,
user_data: &pallas::Base,
) -> Result<Vec<OwnCoin>> {
let query = self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(spend_hook).await), (MONEY_COINS_COL_USER_DATA, serialize_async(user_data).await)},
);
let rows = match query {
Ok(r) => r,
Err(e) => {
return Err(Error::RusqliteError(format!(
"[get_contract_token_coins] Coins retrieval failed: {e:?}"
)))
}
};
let mut owncoins = Vec::with_capacity(rows.len());
for row in rows {
owncoins.push(self.parse_coin_record(&row).await?.0)
}
Ok(owncoins)
}
/// Auxiliary function to parse a `MONEY_COINS_TABLE` record. /// Auxiliary function to parse a `MONEY_COINS_TABLE` record.
/// The boolean in the returned tuple notes if the coin was marked as spent. /// The boolean in the returned tuple notes if the coin was marked as spent.
async fn parse_coin_record(&self, row: &[Value]) -> Result<(OwnCoin, bool, String)> { async fn parse_coin_record(&self, row: &[Value]) -> Result<(OwnCoin, bool, String)> {
@@ -539,12 +562,10 @@ impl Drk {
"INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);", "INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
*MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID, *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
); );
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![serialize_async(&alias).await, serialize_async(&token_id).await],
rusqlite::params![serialize_async(&alias).await, serialize_async(&token_id).await], )
)
.await
} }
/// Fetch all aliases from the wallet. /// Fetch all aliases from the wallet.
@@ -554,7 +575,7 @@ impl Drk {
alias_filter: Option<String>, alias_filter: Option<String>,
token_id_filter: Option<TokenId>, token_id_filter: Option<TokenId>,
) -> Result<HashMap<String, TokenId>> { ) -> Result<HashMap<String, TokenId>> {
let rows = match self.wallet.query_multiple(&MONEY_ALIASES_TABLE, &[], &[]).await { let rows = match self.wallet.query_multiple(&MONEY_ALIASES_TABLE, &[], &[]) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -612,7 +633,7 @@ impl Drk {
"DELETE FROM {} WHERE {} = ?1;", "DELETE FROM {} WHERE {} = ?1;",
*MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, *MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,
); );
self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&alias).await]).await self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&alias).await])
} }
/// Mark a given coin in the wallet as unspent. /// Mark a given coin in the wallet as unspent.
@@ -625,37 +646,34 @@ impl Drk {
MONEY_COINS_COL_SPENT_TX_HASH, MONEY_COINS_COL_SPENT_TX_HASH,
MONEY_COINS_COL_COIN MONEY_COINS_COL_COIN
); );
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![is_spend, "-", serialize_async(&coin.inner()).await],
rusqlite::params![is_spend, "-", serialize_async(&coin.inner()).await], )
)
.await
} }
/// Replace the Money Merkle tree in the wallet. /// Replace the Money Merkle tree in the wallet.
pub async fn put_money_tree(&self, tree: &MerkleTree) -> WalletDbResult<()> { pub async fn put_money_tree(&self, tree: &MerkleTree) -> WalletDbResult<()> {
// First we remove old record // First we remove old record
let query = format!("DELETE FROM {};", *MONEY_TREE_TABLE); let query = format!("DELETE FROM {};", *MONEY_TREE_TABLE);
self.wallet.exec_sql(&query, &[]).await?; self.wallet.exec_sql(&query, &[])?;
// then we insert the new one // then we insert the new one
let query = let query =
format!("INSERT INTO {} ({}) VALUES (?1);", *MONEY_TREE_TABLE, MONEY_TREE_COL_TREE,); format!("INSERT INTO {} ({}) VALUES (?1);", *MONEY_TREE_TABLE, MONEY_TREE_COL_TREE,);
self.wallet.exec_sql(&query, rusqlite::params![serialize_async(tree).await]).await self.wallet.exec_sql(&query, rusqlite::params![serialize_async(tree).await])
} }
/// Fetch the Money Merkle tree from the wallet. /// Fetch the Money Merkle tree from the wallet.
pub async fn get_money_tree(&self) -> Result<MerkleTree> { pub async fn get_money_tree(&self) -> Result<MerkleTree> {
let row = let row = match self.wallet.query_single(&MONEY_TREE_TABLE, &[MONEY_TREE_COL_TREE], &[]) {
match self.wallet.query_single(&MONEY_TREE_TABLE, &[MONEY_TREE_COL_TREE], &[]).await { Ok(r) => r,
Ok(r) => r, Err(e) => {
Err(e) => { return Err(Error::RusqliteError(format!(
return Err(Error::RusqliteError(format!( "[get_money_tree] Tree retrieval failed: {e:?}"
"[get_money_tree] Tree retrieval failed: {e:?}" )))
))) }
} };
};
let Value::Blob(ref tree_bytes) = row[0] else { let Value::Blob(ref tree_bytes) = row[0] else {
return Err(Error::ParseFailed("[get_money_tree] Tree bytes parsing failed")) return Err(Error::ParseFailed("[get_money_tree] Tree bytes parsing failed"))
@@ -665,11 +683,12 @@ impl Drk {
} }
/// Get the last scanned block height from the wallet. /// Get the last scanned block height from the wallet.
pub async fn last_scanned_block(&self) -> WalletDbResult<u32> { pub fn last_scanned_block(&self) -> WalletDbResult<u32> {
let ret = self let ret = self.wallet.query_single(
.wallet &MONEY_INFO_TABLE,
.query_single(&MONEY_INFO_TABLE, &[MONEY_INFO_COL_LAST_SCANNED_BLOCK], &[]) &[MONEY_INFO_COL_LAST_SCANNED_BLOCK],
.await?; &[],
)?;
let Value::Integer(height) = ret[0] else { let Value::Integer(height) = ret[0] else {
return Err(WalletDbError::ParseColumnValueError); return Err(WalletDbError::ParseColumnValueError);
}; };
@@ -803,6 +822,7 @@ impl Drk {
"[apply_tx_money_data] Put Money tree failed: {e:?}" "[apply_tx_money_data] Put Money tree failed: {e:?}"
))) )))
} }
self.smt_insert(&nullifiers)?;
self.mark_spent_coins(&nullifiers, tx_hash).await?; self.mark_spent_coins(&nullifiers, tx_hash).await?;
// This is the SQL query we'll be executing to insert new coins // This is the SQL query we'll be executing to insert new coins
@@ -842,7 +862,7 @@ impl Drk {
serialize_async(&owncoin.note.memo).await, serialize_async(&owncoin.note.memo).await,
]; ];
if let Err(e) = self.wallet.exec_sql(&query, params).await { if let Err(e) = self.wallet.exec_sql(&query, params) {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
"[apply_tx_money_data] Inserting Money coin failed: {e:?}" "[apply_tx_money_data] Inserting Money coin failed: {e:?}"
))) )))
@@ -855,10 +875,8 @@ impl Drk {
*MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID, *MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID,
); );
if let Err(e) = self if let Err(e) =
.wallet self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&token_id).await])
.exec_sql(&query, rusqlite::params![serialize_async(&token_id).await])
.await
{ {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
"[apply_tx_money_data] Inserting Money coin failed: {e:?}" "[apply_tx_money_data] Inserting Money coin failed: {e:?}"
@@ -920,7 +938,7 @@ impl Drk {
Ok(()) Ok(())
} }
/// Mark a coin in the wallet as spent /// Mark a coin in the wallet as spent.
pub async fn mark_spent_coin(&self, coin: &Coin, spent_tx_hash: &String) -> WalletDbResult<()> { pub async fn mark_spent_coin(&self, coin: &Coin, spent_tx_hash: &String) -> WalletDbResult<()> {
let query = format!( let query = format!(
"UPDATE {} SET {} = ?1, {} = ?2 WHERE {} = ?3;", "UPDATE {} SET {} = ?1, {} = ?2 WHERE {} = ?3;",
@@ -930,15 +948,13 @@ impl Drk {
MONEY_COINS_COL_COIN MONEY_COINS_COL_COIN
); );
let is_spent = 1; let is_spent = 1;
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![is_spent, spent_tx_hash, serialize_async(&coin.inner()).await],
rusqlite::params![is_spent, spent_tx_hash, serialize_async(&coin.inner()).await], )
)
.await
} }
/// Marks all coins in the wallet as spent, if their nullifier is in the given set /// Marks all coins in the wallet as spent, if their nullifier is in the given set.
pub async fn mark_spent_coins( pub async fn mark_spent_coins(
&self, &self,
nullifiers: &[Nullifier], nullifiers: &[Nullifier],
@@ -961,7 +977,23 @@ impl Drk {
Ok(()) Ok(())
} }
/// Reset the Money Merkle tree in the wallet /// Inserts given slice to the wallets nullifiers Sparse Merkle Tree.
pub fn smt_insert(&self, nullifiers: &[Nullifier]) -> Result<()> {
let store = WalletStorage::new(
&self.wallet,
&MONEY_SMT_TABLE,
MONEY_SMT_COL_KEY,
MONEY_SMT_COL_VALUE,
);
let mut smt = WalletSmt::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
let leaves: Vec<_> = nullifiers.iter().map(|x| (x.inner(), x.inner())).collect();
smt.insert_batch(leaves)?;
Ok(())
}
/// Reset the Money Merkle tree in the wallet.
pub async fn reset_money_tree(&self) -> WalletDbResult<()> { pub async fn reset_money_tree(&self) -> WalletDbResult<()> {
println!("Resetting Money Merkle tree"); println!("Resetting Money Merkle tree");
let mut tree = MerkleTree::new(1); let mut tree = MerkleTree::new(1);
@@ -973,11 +1005,21 @@ impl Drk {
Ok(()) Ok(())
} }
/// Reset the Money coins in the wallet /// Reset the Money nullifiers Sparse Merkle Tree in the wallet.
pub async fn reset_money_coins(&self) -> WalletDbResult<()> { pub fn reset_money_smt(&self) -> WalletDbResult<()> {
println!("Resetting Money Sparse Merkle tree");
let query = format!("DELETE FROM {};", *MONEY_SMT_TABLE);
self.wallet.exec_sql(&query, &[])?;
println!("Successfully reset Money Sparse Merkle tree");
Ok(())
}
/// Reset the Money coins in the wallet.
pub fn reset_money_coins(&self) -> WalletDbResult<()> {
println!("Resetting coins"); println!("Resetting coins");
let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE); let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE);
self.wallet.exec_sql(&query, &[]).await?; self.wallet.exec_sql(&query, &[])?;
println!("Successfully reset coins"); println!("Successfully reset coins");
Ok(()) Ok(())

View File

@@ -58,7 +58,7 @@ impl Drk {
let req = JsonRequest::new("blockchain.last_known_block", JsonValue::Array(vec![])); let req = JsonRequest::new("blockchain.last_known_block", JsonValue::Array(vec![]));
let rep = self.rpc_client.as_ref().unwrap().request(req).await?; let rep = self.rpc_client.as_ref().unwrap().request(req).await?;
let last_known = *rep.get::<f64>().unwrap() as u32; let last_known = *rep.get::<f64>().unwrap() as u32;
let last_scanned = match self.last_scanned_block().await { let last_scanned = match self.last_scanned_block() {
Ok(l) => l, Ok(l) => l,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -149,7 +149,7 @@ impl Drk {
}, },
}; };
if let Err(e) = if let Err(e) =
self.update_tx_history_records_status(&txs_hashes, "Finalized").await self.update_tx_history_records_status(&txs_hashes, "Finalized")
{ {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
"[subscribe_blocks] Update transaction history record status failed: {e:?}" "[subscribe_blocks] Update transaction history record status failed: {e:?}"
@@ -216,7 +216,7 @@ impl Drk {
// Write this block height into `last_scanned_block` // Write this block height into `last_scanned_block`
let query = let query =
format!("UPDATE {} SET {} = ?1;", *MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_BLOCK); format!("UPDATE {} SET {} = ?1;", *MONEY_INFO_TABLE, MONEY_INFO_COL_LAST_SCANNED_BLOCK);
if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![block.header.height]).await { if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![block.header.height]) {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
"[scan_block] Update last scanned block failed: {e:?}" "[scan_block] Update last scanned block failed: {e:?}"
))) )))
@@ -231,18 +231,19 @@ impl Drk {
/// it looks for a checkpoint in the wallet to reset and start scanning from. /// it looks for a checkpoint in the wallet to reset and start scanning from.
pub async fn scan_blocks(&self, reset: bool) -> WalletDbResult<()> { pub async fn scan_blocks(&self, reset: bool) -> WalletDbResult<()> {
// Grab last scanned block height // Grab last scanned block height
let mut height = self.last_scanned_block().await?; let mut height = self.last_scanned_block()?;
// If last scanned block is genesis (0) or reset flag // If last scanned block is genesis (0) or reset flag
// has been provided we reset, otherwise continue with // has been provided we reset, otherwise continue with
// the next block height // the next block height
if height == 0 || reset { if height == 0 || reset {
self.reset_money_tree().await?; self.reset_money_tree().await?;
self.reset_money_coins().await?; self.reset_money_smt()?;
self.reset_money_coins()?;
self.reset_dao_trees().await?; self.reset_dao_trees().await?;
self.reset_daos().await?; self.reset_daos().await?;
self.reset_dao_proposals().await?; self.reset_dao_proposals()?;
self.reset_dao_votes().await?; self.reset_dao_votes()?;
self.update_all_tx_history_records_status("Rejected").await?; self.update_all_tx_history_records_status("Rejected")?;
height = 0; height = 0;
} else { } else {
height += 1; height += 1;
@@ -281,7 +282,7 @@ impl Drk {
return Err(WalletDbError::GenericError) return Err(WalletDbError::GenericError)
}; };
let txs_hashes = self.insert_tx_history_records(&block.txs).await?; let txs_hashes = self.insert_tx_history_records(&block.txs).await?;
self.update_tx_history_records_status(&txs_hashes, "Finalized").await?; self.update_tx_history_records_status(&txs_hashes, "Finalized")?;
height += 1; height += 1;
} }
} }
@@ -384,4 +385,15 @@ impl Drk {
Ok(gas) Ok(gas)
} }
/// Queries darkfid for current best fork next height.
pub async fn get_next_block_height(&self) -> Result<u32> {
let req =
JsonRequest::new("blockchain.best_fork_next_block_height", JsonValue::Array(vec![]));
let rep = self.rpc_client.as_ref().unwrap().request(req).await?;
let next_height = *rep.get::<f64>().unwrap() as u32;
Ok(next_height)
}
} }

View File

@@ -98,19 +98,15 @@ impl Drk {
MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_IS_FROZEN,
); );
if let Err(e) = self if let Err(e) = self.wallet.exec_sql(
.wallet &query,
.exec_sql( rusqlite::params![
&query, serialize_async(&token_id).await,
rusqlite::params![ serialize_async(&mint_authority).await,
serialize_async(&token_id).await, serialize_async(&token_blind).await,
serialize_async(&mint_authority).await, is_frozen,
serialize_async(&token_blind).await, ],
is_frozen, ) {
],
)
.await
{
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
"[import_mint_authority] Inserting mint authority failed: {e:?}" "[import_mint_authority] Inserting mint authority failed: {e:?}"
))) )))
@@ -158,7 +154,7 @@ impl Drk {
/// Fetch all token mint authorities from the wallet. /// Fetch all token mint authorities from the wallet.
pub async fn get_mint_authorities(&self) -> Result<Vec<(TokenId, SecretKey, BaseBlind, bool)>> { pub async fn get_mint_authorities(&self) -> Result<Vec<(TokenId, SecretKey, BaseBlind, bool)>> {
let rows = match self.wallet.query_multiple(&MONEY_TOKENS_TABLE, &[], &[]).await { let rows = match self.wallet.query_multiple(&MONEY_TOKENS_TABLE, &[], &[]) {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -180,15 +176,18 @@ impl Drk {
&self, &self,
token_id: &TokenId, token_id: &TokenId,
) -> Result<(TokenId, SecretKey, BaseBlind, bool)> { ) -> Result<(TokenId, SecretKey, BaseBlind, bool)> {
let row = let row = match self.wallet.query_single(
match self.wallet.query_single(&MONEY_TOKENS_TABLE, &[], convert_named_params! {(MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await)}).await { &MONEY_TOKENS_TABLE,
Ok(r) => r, &[],
Err(e) => { convert_named_params! {(MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await)},
return Err(Error::RusqliteError(format!( ) {
"[get_token_mint_authority] Token mint autority retrieval failed: {e:?}" Ok(r) => r,
))) Err(e) => {
} return Err(Error::RusqliteError(format!(
}; "[get_token_mint_authority] Token mint autority retrieval failed: {e:?}"
)))
}
};
let token = self.parse_mint_authority_record(&row).await?; let token = self.parse_mint_authority_record(&row).await?;

View File

@@ -45,12 +45,10 @@ impl Drk {
WALLET_TXS_HISTORY_COL_TX, WALLET_TXS_HISTORY_COL_TX,
); );
let tx_hash = tx.hash().to_string(); let tx_hash = tx.hash().to_string();
self.wallet self.wallet.exec_sql(
.exec_sql( &query,
&query, rusqlite::params![tx_hash, "Broadcasted", &serialize_async(tx).await,],
rusqlite::params![tx_hash, "Broadcasted", &serialize_async(tx).await,], )?;
)
.await?;
Ok(tx_hash) Ok(tx_hash)
} }
@@ -72,15 +70,11 @@ impl Drk {
&self, &self,
tx_hash: &str, tx_hash: &str,
) -> Result<(String, String, Transaction)> { ) -> Result<(String, String, Transaction)> {
let row = match self let row = match self.wallet.query_single(
.wallet WALLET_TXS_HISTORY_TABLE,
.query_single( &[],
WALLET_TXS_HISTORY_TABLE, convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
&[], ) {
convert_named_params! {(WALLET_TXS_HISTORY_COL_TX_HASH, tx_hash)},
)
.await
{
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Err(Error::RusqliteError(format!( return Err(Error::RusqliteError(format!(
@@ -110,15 +104,12 @@ impl Drk {
} }
/// Fetch all transactions history records, excluding bytes column. /// Fetch all transactions history records, excluding bytes column.
pub async fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String)>> { pub fn get_txs_history(&self) -> WalletDbResult<Vec<(String, String)>> {
let rows = self let rows = self.wallet.query_multiple(
.wallet WALLET_TXS_HISTORY_TABLE,
.query_multiple( &[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS],
WALLET_TXS_HISTORY_TABLE, &[],
&[WALLET_TXS_HISTORY_COL_TX_HASH, WALLET_TXS_HISTORY_COL_STATUS], )?;
&[],
)
.await?;
let mut ret = Vec::with_capacity(rows.len()); let mut ret = Vec::with_capacity(rows.len());
for row in rows { for row in rows {
@@ -137,7 +128,7 @@ impl Drk {
} }
/// Update given transactions history record statuses to the given one. /// Update given transactions history record statuses to the given one.
pub async fn update_tx_history_records_status( pub fn update_tx_history_records_status(
&self, &self,
txs_hashes: &[String], txs_hashes: &[String],
status: &str, status: &str,
@@ -155,15 +146,15 @@ impl Drk {
txs_hashes_string txs_hashes_string
); );
self.wallet.exec_sql(&query, rusqlite::params![status]).await self.wallet.exec_sql(&query, rusqlite::params![status])
} }
/// Update all transaction history records statuses to the given one. /// Update all transaction history records statuses to the given one.
pub async fn update_all_tx_history_records_status(&self, status: &str) -> WalletDbResult<()> { pub fn update_all_tx_history_records_status(&self, status: &str) -> WalletDbResult<()> {
let query = format!( let query = format!(
"UPDATE {} SET {} = ?1", "UPDATE {} SET {} = ?1",
WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS, WALLET_TXS_HISTORY_TABLE, WALLET_TXS_HISTORY_COL_STATUS,
); );
self.wallet.exec_sql(&query, rusqlite::params![status]).await self.wallet.exec_sql(&query, rusqlite::params![status])
} }
} }

View File

@@ -16,14 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{path::PathBuf, sync::Arc}; use std::{
path::PathBuf,
sync::{Arc, Mutex},
};
use darkfi_sdk::{
crypto::{
pasta_prelude::PrimeField,
smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
},
error::{ContractError, ContractResult},
pasta::pallas,
};
use log::{debug, error}; use log::{debug, error};
use num_bigint::BigUint;
use rusqlite::{ use rusqlite::{
types::{ToSql, Value}, types::{ToSql, Value},
Connection, Connection,
}; };
use smol::lock::Mutex;
use crate::error::{WalletDbError, WalletDbResult}; use crate::error::{WalletDbError, WalletDbResult};
@@ -62,9 +73,10 @@ impl WalletDb {
/// This function executes a given SQL query that contains multiple SQL statements, /// This function executes a given SQL query that contains multiple SQL statements,
/// that don't contain any parameters. /// that don't contain any parameters.
pub async fn exec_batch_sql(&self, query: &str) -> WalletDbResult<()> { pub fn exec_batch_sql(&self, query: &str) -> WalletDbResult<()> {
debug!(target: "walletdb::exec_batch_sql", "[WalletDb] Executing batch SQL query:\n{query}"); debug!(target: "walletdb::exec_batch_sql", "[WalletDb] Executing batch SQL query:\n{query}");
if let Err(e) = self.conn.lock().await.execute_batch(query) { let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
if let Err(e) = conn.execute_batch(query) {
error!(target: "walletdb::exec_batch_sql", "[WalletDb] Query failed: {e}"); error!(target: "walletdb::exec_batch_sql", "[WalletDb] Query failed: {e}");
return Err(WalletDbError::QueryExecutionFailed) return Err(WalletDbError::QueryExecutionFailed)
}; };
@@ -74,11 +86,13 @@ impl WalletDb {
/// This function executes a given SQL query, but isn't able to return anything. /// This function executes a given SQL query, but isn't able to return anything.
/// Therefore it's best to use it for initializing a table or similar things. /// Therefore it's best to use it for initializing a table or similar things.
pub async fn exec_sql(&self, query: &str, params: &[&dyn ToSql]) -> WalletDbResult<()> { pub fn exec_sql(&self, query: &str, params: &[&dyn ToSql]) -> WalletDbResult<()> {
debug!(target: "walletdb::exec_sql", "[WalletDb] Executing SQL query:\n{query}"); debug!(target: "walletdb::exec_sql", "[WalletDb] Executing SQL query:\n{query}");
let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
// If no params are provided, execute directly // If no params are provided, execute directly
if params.is_empty() { if params.is_empty() {
if let Err(e) = self.conn.lock().await.execute(query, ()) { if let Err(e) = conn.execute(query, ()) {
error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}"); error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
return Err(WalletDbError::QueryExecutionFailed) return Err(WalletDbError::QueryExecutionFailed)
}; };
@@ -86,7 +100,6 @@ impl WalletDb {
} }
// First we prepare the query // First we prepare the query
let conn = self.conn.lock().await;
let Ok(mut stmt) = conn.prepare(query) else { let Ok(mut stmt) = conn.prepare(query) else {
return Err(WalletDbError::QueryPreparationFailed) return Err(WalletDbError::QueryPreparationFailed)
}; };
@@ -137,7 +150,7 @@ impl WalletDb {
/// Query provided table from selected column names and provided `WHERE` clauses, /// Query provided table from selected column names and provided `WHERE` clauses,
/// for a single row. /// for a single row.
pub async fn query_single( pub fn query_single(
&self, &self,
table: &str, table: &str,
col_names: &[&str], col_names: &[&str],
@@ -148,7 +161,8 @@ impl WalletDb {
debug!(target: "walletdb::query_single", "[WalletDb] Executing SQL query:\n{query}"); debug!(target: "walletdb::query_single", "[WalletDb] Executing SQL query:\n{query}");
// First we prepare the query // First we prepare the query
let conn = self.conn.lock().await; let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
let Ok(mut stmt) = conn.prepare(&query) else { let Ok(mut stmt) = conn.prepare(&query) else {
return Err(WalletDbError::QueryPreparationFailed) return Err(WalletDbError::QueryPreparationFailed)
}; };
@@ -188,7 +202,7 @@ impl WalletDb {
/// Query provided table from selected column names and provided `WHERE` clauses, /// Query provided table from selected column names and provided `WHERE` clauses,
/// for multiple rows. /// for multiple rows.
pub async fn query_multiple( pub fn query_multiple(
&self, &self,
table: &str, table: &str,
col_names: &[&str], col_names: &[&str],
@@ -199,16 +213,13 @@ impl WalletDb {
debug!(target: "walletdb::multiple", "[WalletDb] Executing SQL query:\n{query}"); debug!(target: "walletdb::multiple", "[WalletDb] Executing SQL query:\n{query}");
// First we prepare the query // First we prepare the query
let conn = self.conn.lock().await; let Ok(conn) = self.conn.lock() else { return Err(WalletDbError::FailedToAquireLock) };
let Ok(mut stmt) = conn.prepare(&query) else { let Ok(mut stmt) = conn.prepare(&query) else {
return Err(WalletDbError::QueryPreparationFailed) return Err(WalletDbError::QueryPreparationFailed)
}; };
// Execute the query using provided converted params // Execute the query using provided converted params
let Ok(mut rows) = stmt.query(params) else { let Ok(mut rows) = stmt.query(params) else {
if let Err(e) = stmt.query(params) {
println!("eeer: {e:?}");
}
return Err(WalletDbError::QueryExecutionFailed) return Err(WalletDbError::QueryExecutionFailed)
}; };
@@ -263,127 +274,251 @@ macro_rules! convert_named_params {
}; };
} }
/// Wallet SMT definition
pub type WalletSmt<'a> = SparseMerkleTree<
'static,
SMT_FP_DEPTH,
{ SMT_FP_DEPTH + 1 },
pallas::Base,
PoseidonFp,
WalletStorage<'a>,
>;
/// An SMT adapter for wallet SQLite database storage.
pub struct WalletStorage<'a> {
wallet: &'a WalletPtr,
table: &'a str,
key_col: &'a str,
value_col: &'a str,
}
impl<'a> WalletStorage<'a> {
pub fn new(
wallet: &'a WalletPtr,
table: &'a str,
key_col: &'a str,
value_col: &'a str,
) -> Self {
Self { wallet, table, key_col, value_col }
}
}
impl<'a> StorageAdapter for WalletStorage<'a> {
type Value = pallas::Base;
fn put(&mut self, key: BigUint, value: pallas::Base) -> ContractResult {
let query = format!(
"INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
self.table, self.key_col, self.value_col
);
if let Err(e) =
self.wallet.exec_sql(&query, rusqlite::params![key.to_bytes_le(), value.to_repr()])
{
error!(target: "walletdb::StorageAdapter::put", "Inserting key {key:?}, value {value:?} into DB failed: {e:?}");
return Err(ContractError::SmtPutFailed)
}
Ok(())
}
fn get(&self, key: &BigUint) -> Option<pallas::Base> {
let row = match self.wallet.query_single(
self.table,
&[self.value_col],
convert_named_params! {(self.key_col, key.to_bytes_le())},
) {
Ok(r) => r,
Err(WalletDbError::RowNotFound) => return None,
Err(e) => {
error!(target: "walletdb::StorageAdapter::get", "Fetching key {key:?} from DB failed: {e:?}");
return None
}
};
let Value::Blob(ref value_bytes) = row[0] else {
error!(target: "walletdb::StorageAdapter::get", "Parsing key {key:?} value bytes");
return None
};
let mut repr = [0; 32];
repr.copy_from_slice(value_bytes);
pallas::Base::from_repr(repr).into()
}
fn del(&mut self, key: &BigUint) -> ContractResult {
let query = format!("DELETE FROM {} WHERE {} = ?1;", self.table, self.key_col);
if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![key.to_bytes_le()]) {
error!(target: "walletdb::StorageAdapter::del", "Removing key {key:?} from DB failed: {e:?}");
return Err(ContractError::SmtDelFailed)
}
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use darkfi::zk::halo2::Field;
use darkfi_sdk::{
crypto::smt::{gen_empty_nodes, util::FieldHasher, PoseidonFp, SparseMerkleTree},
pasta::pallas,
};
use rand::rngs::OsRng;
use rusqlite::types::Value; use rusqlite::types::Value;
use crate::walletdb::WalletDb; use crate::walletdb::{WalletDb, WalletStorage};
#[test] #[test]
fn test_mem_wallet() { fn test_mem_wallet() {
smol::block_on(async { let wallet = WalletDb::new(None, Some("foobar")).unwrap();
let wallet = WalletDb::new(None, Some("foobar")).unwrap(); wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );", &[]).unwrap();
wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );", &[]).await.unwrap(); wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );", &[]).unwrap();
wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );", &[]).await.unwrap();
let ret = wallet.query_single("mista", &["numba"], &[]).await.unwrap(); let ret = wallet.query_single("mista", &["numba"], &[]).unwrap();
assert_eq!(ret.len(), 1); assert_eq!(ret.len(), 1);
let numba: i64 = if let Value::Integer(numba) = ret[0] { numba } else { -1 }; let numba: i64 = if let Value::Integer(numba) = ret[0] { numba } else { -1 };
assert_eq!(numba, 42); assert_eq!(numba, 42);
});
} }
#[test] #[test]
fn test_query_single() { fn test_query_single() {
smol::block_on(async { let wallet = WalletDb::new(None, None).unwrap();
let wallet = WalletDb::new(None, None).unwrap(); wallet
wallet .exec_sql("CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );", &[])
.exec_sql( .unwrap();
"CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
&[],
)
.await
.unwrap();
let why = 42; let why = 42;
let are = "are".to_string(); let are = "are".to_string();
let you = 69; let you = 69;
let gae = vec![42u8; 32]; let gae = vec![42u8; 32];
wallet wallet
.exec_sql( .exec_sql(
"INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);", "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
rusqlite::params![why, are, you, gae], rusqlite::params![why, are, you, gae],
) )
.await .unwrap();
.unwrap();
let ret = let ret = wallet.query_single("mista", &["why", "are", "you", "gae"], &[]).unwrap();
wallet.query_single("mista", &["why", "are", "you", "gae"], &[]).await.unwrap(); assert_eq!(ret.len(), 4);
assert_eq!(ret.len(), 4); assert_eq!(ret[0], Value::Integer(why));
assert_eq!(ret[0], Value::Integer(why)); assert_eq!(ret[1], Value::Text(are.clone()));
assert_eq!(ret[1], Value::Text(are.clone())); assert_eq!(ret[2], Value::Integer(you));
assert_eq!(ret[2], Value::Integer(you)); assert_eq!(ret[3], Value::Blob(gae.clone()));
assert_eq!(ret[3], Value::Blob(gae.clone()));
let ret = wallet let ret = wallet
.query_single( .query_single(
"mista", "mista",
&["gae"], &["gae"],
rusqlite::named_params! {":why": why, ":are": are, ":you": you}, rusqlite::named_params! {":why": why, ":are": are, ":you": you},
) )
.await .unwrap();
.unwrap(); assert_eq!(ret.len(), 1);
assert_eq!(ret.len(), 1); assert_eq!(ret[0], Value::Blob(gae));
assert_eq!(ret[0], Value::Blob(gae));
});
} }
#[test] #[test]
fn test_query_multi() { fn test_query_multi() {
smol::block_on(async { let wallet = WalletDb::new(None, None).unwrap();
let wallet = WalletDb::new(None, None).unwrap(); wallet
wallet .exec_sql("CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );", &[])
.exec_sql( .unwrap();
"CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
&[],
)
.await
.unwrap();
let why = 42; let why = 42;
let are = "are".to_string(); let are = "are".to_string();
let you = 69; let you = 69;
let gae = vec![42u8; 32]; let gae = vec![42u8; 32];
wallet wallet
.exec_sql( .exec_sql(
"INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);", "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
rusqlite::params![why, are, you, gae], rusqlite::params![why, are, you, gae],
) )
.await .unwrap();
.unwrap(); wallet
wallet .exec_sql(
.exec_sql( "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
"INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);", rusqlite::params![why, are, you, gae],
rusqlite::params![why, are, you, gae], )
) .unwrap();
.await
.unwrap();
let ret = wallet.query_multiple("mista", &[], &[]).await.unwrap(); let ret = wallet.query_multiple("mista", &[], &[]).unwrap();
assert_eq!(ret.len(), 2); assert_eq!(ret.len(), 2);
for row in ret { for row in ret {
assert_eq!(row.len(), 4); assert_eq!(row.len(), 4);
assert_eq!(row[0], Value::Integer(why)); assert_eq!(row[0], Value::Integer(why));
assert_eq!(row[1], Value::Text(are.clone())); assert_eq!(row[1], Value::Text(are.clone()));
assert_eq!(row[2], Value::Integer(you)); assert_eq!(row[2], Value::Integer(you));
assert_eq!(row[3], Value::Blob(gae.clone())); assert_eq!(row[3], Value::Blob(gae.clone()));
} }
let ret = wallet let ret = wallet
.query_multiple( .query_multiple(
"mista", "mista",
&["gae"], &["gae"],
convert_named_params! {("why", why), ("are", are), ("you", you)}, convert_named_params! {("why", why), ("are", are), ("you", you)},
) )
.await .unwrap();
.unwrap(); assert_eq!(ret.len(), 2);
assert_eq!(ret.len(), 2); for row in ret {
for row in ret { assert_eq!(row.len(), 1);
assert_eq!(row.len(), 1); assert_eq!(row[0], Value::Blob(gae.clone()));
assert_eq!(row[0], Value::Blob(gae.clone())); }
} }
});
#[test]
fn test_sqlite_smt() {
// Setup SQLite database
let table = &"smt";
let key_col = &"smt_key";
let value_col = &"smt_value";
let wallet = WalletDb::new(None, None).unwrap();
wallet.exec_sql(&format!("CREATE TABLE {table} ( {key_col} BLOB INTEGER PRIMARY KEY NOT NULL, {value_col} BLOB NOT NULL);"), &[]).unwrap();
// Setup SMT
const HEIGHT: usize = 3;
let hasher = PoseidonFp::new();
let empty_leaf = pallas::Base::ZERO;
let empty_nodes = gen_empty_nodes::<{ HEIGHT + 1 }, _, _>(&hasher, empty_leaf);
let store = WalletStorage::new(&wallet, table, key_col, value_col);
let mut smt = SparseMerkleTree::<HEIGHT, { HEIGHT + 1 }, _, _, _>::new(
store,
hasher.clone(),
&empty_nodes,
);
let leaves = vec![
(pallas::Base::from(1), pallas::Base::random(&mut OsRng)),
(pallas::Base::from(2), pallas::Base::random(&mut OsRng)),
(pallas::Base::from(3), pallas::Base::random(&mut OsRng)),
];
smt.insert_batch(leaves.clone()).unwrap();
let hash1 = leaves[0].1;
let hash2 = leaves[1].1;
let hash3 = leaves[2].1;
let hash = |l, r| hasher.hash([l, r]);
let hash01 = hash(empty_nodes[3], hash1);
let hash23 = hash(hash2, hash3);
let hash0123 = hash(hash01, hash23);
let root = hash(hash0123, empty_nodes[1]);
assert_eq!(root, smt.root());
// Now try to construct a membership proof for leaf 3
let pos = leaves[2].0;
let path = smt.prove_membership(&pos);
assert_eq!(path.path[0], empty_nodes[1]);
assert_eq!(path.path[1], hash01);
assert_eq!(path.path[2], hash2);
assert_eq!(hash23, hash(path.path[2], hash3));
assert_eq!(hash0123, hash(path.path[1], hash(path.path[2], hash3)));
assert_eq!(root, hash(hash(path.path[1], hash(path.path[2], hash3)), path.path[0]));
assert!(path.verify(&root, &hash3, &pos));
} }
} }

View File

@@ -21,9 +21,12 @@ use darkfi_sdk::{
bridgetree, bridgetree,
bridgetree::Hashable, bridgetree::Hashable,
crypto::{ crypto::{
note::AeadEncryptedNote, pasta_prelude::*, pedersen::pedersen_commitment_u64, note::AeadEncryptedNote,
poseidon_hash, smt::SmtMemoryFp, Blind, FuncId, MerkleNode, PublicKey, ScalarBlind, pasta_prelude::*,
SecretKey, pedersen::pedersen_commitment_u64,
poseidon_hash,
smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH},
Blind, FuncId, MerkleNode, PublicKey, ScalarBlind, SecretKey,
}, },
pasta::pallas, pasta::pallas,
}; };
@@ -40,25 +43,26 @@ use crate::{
model::{Dao, DaoProposal, DaoProposeParams, DaoProposeParamsInput, VecAuthCallCommit}, model::{Dao, DaoProposal, DaoProposeParams, DaoProposeParamsInput, VecAuthCallCommit},
}; };
pub struct DaoProposeStakeInput<'a> { pub struct DaoProposeStakeInput {
pub secret: SecretKey, pub secret: SecretKey,
pub note: darkfi_money_contract::client::MoneyNote, pub note: darkfi_money_contract::client::MoneyNote,
pub leaf_position: bridgetree::Position, pub leaf_position: bridgetree::Position,
pub merkle_path: Vec<MerkleNode>, pub merkle_path: Vec<MerkleNode>,
pub money_null_smt: &'a SmtMemoryFp,
pub signature_secret: SecretKey,
} }
pub struct DaoProposeCall<'a> { pub struct DaoProposeCall<'a, T: StorageAdapter<Value = pallas::Base>> {
pub inputs: Vec<DaoProposeStakeInput<'a>>, pub money_null_smt:
&'a SparseMerkleTree<'a, SMT_FP_DEPTH, { SMT_FP_DEPTH + 1 }, pallas::Base, PoseidonFp, T>,
pub inputs: Vec<DaoProposeStakeInput>,
pub proposal: DaoProposal, pub proposal: DaoProposal,
pub dao: Dao, pub dao: Dao,
pub dao_leaf_position: bridgetree::Position, pub dao_leaf_position: bridgetree::Position,
pub dao_merkle_path: Vec<MerkleNode>, pub dao_merkle_path: Vec<MerkleNode>,
pub dao_merkle_root: MerkleNode, pub dao_merkle_root: MerkleNode,
pub signature_secret: SecretKey,
} }
impl<'a> DaoProposeCall<'a> { impl<'a, T: StorageAdapter<Value = pallas::Base>> DaoProposeCall<'a, T> {
pub fn make( pub fn make(
self, self,
burn_zkbin: &ZkBinary, burn_zkbin: &ZkBinary,
@@ -70,6 +74,10 @@ impl<'a> DaoProposeCall<'a> {
let gov_token_blind = Blind::random(&mut OsRng); let gov_token_blind = Blind::random(&mut OsRng);
let smt_null_root = self.money_null_smt.root();
let signature_public = PublicKey::from_secret(self.signature_secret);
let (sig_x, sig_y) = signature_public.xy();
let mut inputs = vec![]; let mut inputs = vec![];
let mut total_funds = 0; let mut total_funds = 0;
let mut total_funds_blinds = ScalarBlind::ZERO; let mut total_funds_blinds = ScalarBlind::ZERO;
@@ -79,8 +87,6 @@ impl<'a> DaoProposeCall<'a> {
total_funds += input.note.value; total_funds += input.note.value;
total_funds_blinds += funds_blind; total_funds_blinds += funds_blind;
let signature_public = PublicKey::from_secret(input.signature_secret);
// Note from the previous output // Note from the previous output
let note = input.note; let note = input.note;
let leaf_pos: u64 = input.leaf_position.into(); let leaf_pos: u64 = input.leaf_position.into();
@@ -97,8 +103,7 @@ impl<'a> DaoProposeCall<'a> {
.to_coin(); .to_coin();
let nullifier = poseidon_hash([input.secret.inner(), coin.inner()]); let nullifier = poseidon_hash([input.secret.inner(), coin.inner()]);
let smt_null_root = input.money_null_smt.root(); let smt_null_path = self.money_null_smt.prove_membership(&nullifier);
let smt_null_path = input.money_null_smt.prove_membership(&nullifier);
if !smt_null_path.verify(&smt_null_root, &pallas::Base::ZERO, &nullifier) { if !smt_null_path.verify(&smt_null_root, &pallas::Base::ZERO, &nullifier) {
return Err( return Err(
ClientFailed::VerifyError(DaoError::InvalidInputMerkleRoot.to_string()).into() ClientFailed::VerifyError(DaoError::InvalidInputMerkleRoot.to_string()).into()
@@ -117,7 +122,7 @@ impl<'a> DaoProposeCall<'a> {
Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())), Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
Witness::SparseMerklePath(Value::known(smt_null_path.path)), Witness::SparseMerklePath(Value::known(smt_null_path.path)),
Witness::Base(Value::known(input.signature_secret.inner())), Witness::Base(Value::known(self.signature_secret.inner())),
]; ];
// TODO: We need a generic ZkSet widget to avoid doing this all the time // TODO: We need a generic ZkSet widget to avoid doing this all the time
@@ -144,8 +149,6 @@ impl<'a> DaoProposeCall<'a> {
let value_commit = pedersen_commitment_u64(note.value, funds_blind); let value_commit = pedersen_commitment_u64(note.value, funds_blind);
let value_coords = value_commit.to_affine().coordinates().unwrap(); let value_coords = value_commit.to_affine().coordinates().unwrap();
let (sig_x, sig_y) = signature_public.xy();
let public_inputs = vec![ let public_inputs = vec![
smt_null_root, smt_null_root,
*value_coords.x(), *value_coords.x(),

View File

@@ -71,8 +71,6 @@ impl TestHarness {
.unwrap() .unwrap()
.clone(); .clone();
let signature_secret = SecretKey::random(&mut OsRng);
// Useful code snippet to dump a sled contract DB // Useful code snippet to dump a sled contract DB
/*{ /*{
let blockchain = &wallet.validator.blockchain; let blockchain = &wallet.validator.blockchain;
@@ -95,8 +93,6 @@ impl TestHarness {
.money_merkle_tree .money_merkle_tree
.witness(propose_owncoin.leaf_position, 0) .witness(propose_owncoin.leaf_position, 0)
.unwrap(), .unwrap(),
money_null_smt: &wallet.money_null_smt,
signature_secret,
}; };
// Convert coin_params to actual coins // Convert coin_params to actual coins
@@ -131,7 +127,10 @@ impl TestHarness {
blind: Blind::random(&mut OsRng), blind: Blind::random(&mut OsRng),
}; };
let signature_secret = SecretKey::random(&mut OsRng);
let call = DaoProposeCall { let call = DaoProposeCall {
money_null_smt: &wallet.money_null_smt,
inputs: vec![input], inputs: vec![input],
proposal: proposal.clone(), proposal: proposal.clone(),
dao: dao.clone(), dao: dao.clone(),
@@ -141,6 +140,7 @@ impl TestHarness {
.witness(*wallet.dao_leafs.get(dao_bulla).unwrap(), 0) .witness(*wallet.dao_leafs.get(dao_bulla).unwrap(), 0)
.unwrap(), .unwrap(),
dao_merkle_root: wallet.dao_merkle_tree.root(0).unwrap(), dao_merkle_root: wallet.dao_merkle_tree.root(0).unwrap(),
signature_secret,
}; };
let (params, proofs) = call.make( let (params, proofs) = call.make(

View File

@@ -179,7 +179,7 @@ impl Wallet {
let hasher = PoseidonFp::new(); let hasher = PoseidonFp::new();
let store = MemoryStorageFp::new(); let store = MemoryStorageFp::new();
let money_null_smt = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP); let money_null_smt = SmtMemoryFp::new(store, hasher, &EMPTY_NODES_FP);
Ok(Self { Ok(Self {
keypair, keypair,

View File

@@ -72,7 +72,7 @@ pub use empty::EMPTY_NODES_FP;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
mod util; pub mod util;
pub use util::Poseidon; pub use util::Poseidon;
pub mod wasmdb; pub mod wasmdb;

View File

@@ -721,4 +721,14 @@ impl Validator {
Ok(()) Ok(())
} }
/// Auxiliary function to retrieve current best fork next block height.
pub async fn best_fork_next_block_height(&self) -> Result<u32> {
let forks = self.consensus.forks.read().await;
let fork = &forks[best_fork_index(&forks)?];
let next_block_height = fork.get_next_block_height()?;
drop(forks);
Ok(next_block_height)
}
} }