mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
Make cashierd compile.
This commit is contained in:
@@ -5,21 +5,24 @@ use async_std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
use clap::clap_app;
|
||||
use easy_parallel::Parallel;
|
||||
use log::debug;
|
||||
use incrementalmerkletree::bridgetree::BridgeTree;
|
||||
use log::{debug, info};
|
||||
use pasta_curves::pallas;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use drk::{
|
||||
blockchain::{rocks::columns, Rocks, RocksColumn},
|
||||
circuit::{MintContract, SpendContract},
|
||||
cli::{CashierdConfig, Config},
|
||||
client::{Client, State},
|
||||
crypto::{mint_proof::MintProofKeys, schnorr, spend_proof::SpendProofKeys},
|
||||
client::Client,
|
||||
crypto::{merkle_node::MerkleNode, proof::VerifyingKey, schnorr},
|
||||
rpc::{
|
||||
jsonrpc::{error as jsonerr, response as jsonresp, ErrorCode::*, JsonRequest, JsonResult},
|
||||
rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig},
|
||||
},
|
||||
serial::{deserialize, serialize},
|
||||
service::{bridge, bridge::Bridge},
|
||||
state::State,
|
||||
types::DrkTokenId,
|
||||
util::{expand_path, generate_id, join_config_path, parse::truncate, NetworkName},
|
||||
wallet::{cashierdb::TokenKey, CashierDb, WalletDb},
|
||||
@@ -342,13 +345,13 @@ impl Cashierd {
|
||||
cashier_public = addr.public;
|
||||
} else {
|
||||
let cashier_secret = schnorr::SecretKey::random();
|
||||
cashier_public = cashier_secret.public_key();
|
||||
cashier_public = cashier_secret.public_key().inner();
|
||||
|
||||
self.cashier_wallet
|
||||
.put_withdraw_keys(
|
||||
&address,
|
||||
&cashier_public,
|
||||
&cashier_secret,
|
||||
&cashier_secret.inner(),
|
||||
&network,
|
||||
&token_id,
|
||||
mint_address.into(),
|
||||
@@ -669,15 +672,19 @@ async fn start(
|
||||
|
||||
let rocks = Rocks::new(expand_path(&config.database_path.clone())?.as_path())?;
|
||||
|
||||
let mint_proof_keys = MintProofKeys::initialize();
|
||||
let spend_proof_keys = SpendProofKeys::initialize();
|
||||
/*
|
||||
let mint_pk = ProvingKey::build(11, MintContract::default());
|
||||
let spend_pk = ProvingKey::build(11, SpendContract::default());
|
||||
*/
|
||||
info!("Building verifying key for the mint contract...");
|
||||
let mint_vk = VerifyingKey::build(11, MintContract::default());
|
||||
info!("Building verifying key for the spend contract...");
|
||||
let spend_vk = VerifyingKey::build(11, SpendContract::default());
|
||||
|
||||
let client = Client::new(
|
||||
rocks.clone(),
|
||||
(config.gateway_protocol_url.parse()?, config.gateway_publisher_url.parse()?),
|
||||
client_wallet.clone(),
|
||||
mint_proof_keys,
|
||||
spend_proof_keys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -694,13 +701,13 @@ async fn start(
|
||||
tree: BridgeTree::<MerkleNode, 32>::new(100),
|
||||
merkle_roots,
|
||||
nullifiers,
|
||||
mint_vk: mint_proof_keys.vk,
|
||||
spend_vk: spend_proof_keys.vk,
|
||||
mint_vk,
|
||||
spend_vk,
|
||||
public_keys: cashier_public_keys,
|
||||
}));
|
||||
|
||||
if get_address_flag {
|
||||
println!("Public Key: {}", cashier_public_str);
|
||||
info!("Public Key: {}", cashier_public_str);
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
@@ -750,7 +757,7 @@ async fn main() -> Result<()> {
|
||||
config.client_wallet_password.clone(),
|
||||
)?;
|
||||
|
||||
client_wallet.remove_own_coins()?;
|
||||
client_wallet.remove_own_coins().await?;
|
||||
|
||||
let wallet = CashierDb::new(
|
||||
expand_path(&config.cashier_wallet_path)?.as_path(),
|
||||
@@ -764,7 +771,7 @@ async fn main() -> Result<()> {
|
||||
std::fs::remove_dir_all(path)?;
|
||||
}
|
||||
|
||||
println!("Wallet got updated successfully.");
|
||||
info!("Wallet got updated successfully.");
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ impl MemoryState {
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cashier_signature_secret = schnorr::SecretKey::random();
|
||||
let cashier_signature_public = cashier_secret.public_key();
|
||||
let cashier_signature_public = cashier_signature_secret.public_key();
|
||||
|
||||
let keypair = Keypair::random(&mut OsRng);
|
||||
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
pub mod mint_contract;
|
||||
pub mod spend_contract;
|
||||
|
||||
pub use mint_contract::MintContract;
|
||||
pub use spend_contract::SpendContract;
|
||||
|
||||
448
src/client.rs
448
src/client.rs
@@ -1,26 +1,24 @@
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use incrementalmerkletree::bridgetree::BridgeTree;
|
||||
use log::{debug, info};
|
||||
use incrementalmerkletree::Tree;
|
||||
use log::{debug, info, warn};
|
||||
use pasta_curves::pallas;
|
||||
use smol::Executor;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
blockchain::{rocks::columns, Rocks, RocksColumn, Slab},
|
||||
crypto::{
|
||||
coin::Coin, merkle_node::MerkleNode, mint_proof::MintProofKeys, nullifier::Nullifier,
|
||||
proof::VerifyingKey, schnorr, spend_proof::SpendProofKeys,
|
||||
},
|
||||
serial::{serialize, Encodable},
|
||||
crypto::{coin::Coin, merkle_node::MerkleNode, schnorr, util::mod_r_p},
|
||||
serial::{serialize, Decodable, Encodable},
|
||||
service::GatewayClient,
|
||||
state::{state_transition, ProgramState},
|
||||
state::{state_transition, State},
|
||||
tx,
|
||||
wallet::{Keypair, WalletPtr},
|
||||
wallet::{CashierDbPtr, Keypair, WalletPtr},
|
||||
Result,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum ClientFailed {
|
||||
#[error("here is no enough value {0}")]
|
||||
#[error("Here is not enough value {0}")]
|
||||
NotEnoughValue(u64),
|
||||
#[error("Invalid Address {0}")]
|
||||
InvalidAddress(String),
|
||||
@@ -46,12 +44,24 @@ pub enum ClientFailed {
|
||||
VerifyError(String),
|
||||
}
|
||||
|
||||
pub type ClientResult<T> = std::result::Result<T, ClientFailed>;
|
||||
|
||||
impl From<super::error::Error> for ClientFailed {
|
||||
fn from(err: super::error::Error) -> ClientFailed {
|
||||
ClientFailed::ClientError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::state::VerifyFailed> for ClientFailed {
|
||||
fn from(err: crate::state::VerifyFailed) -> ClientFailed {
|
||||
ClientFailed::VerifyError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
main_keypair: Keypair,
|
||||
pub main_keypair: Keypair,
|
||||
gateway: GatewayClient,
|
||||
wallet: WalletPtr,
|
||||
mint_proof_keys: MintProofKeys,
|
||||
spend_proof_keys: SpendProofKeys,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -59,70 +69,69 @@ impl Client {
|
||||
rocks: Arc<Rocks>,
|
||||
gateway_addrs: (Url, Url),
|
||||
wallet: WalletPtr,
|
||||
mint_proof_keys: MintProofKeys,
|
||||
spend_proof_keys: SpendProofKeys,
|
||||
) -> Result<Self> {
|
||||
wallet.init_db()?;
|
||||
wallet.init_db().await?;
|
||||
|
||||
// Generate a new keypair if we don't have any.
|
||||
if wallet.get_keypairs()?.is_empty() {
|
||||
wallet.key_gen()?;
|
||||
if wallet.get_keypairs().await?.is_empty() {
|
||||
wallet.key_gen().await?;
|
||||
}
|
||||
|
||||
// TODO: Think about multiple keypairs.
|
||||
let main_keypair = wallet.get_keypairs()?[0].clone();
|
||||
// TODO: Think about multiple keypairs
|
||||
let main_keypair = wallet.get_keypairs().await?[0].clone();
|
||||
info!("Main keypair: {}", bs58::encode(&serialize(&main_keypair.public)).into_string());
|
||||
|
||||
let slabstore = RocksColumn::<columns::Slabs>::new(rocks);
|
||||
|
||||
debug!("Creating GatewayClient");
|
||||
let slabstore = RocksColumn::<columns::Slabs>::new(rocks);
|
||||
let gateway = GatewayClient::new(gateway_addrs.0, gateway_addrs.1, slabstore)?;
|
||||
|
||||
let client = Client { main_keypair, gateway, wallet, mint_proof_keys, spend_proof_keys };
|
||||
let client = Client { main_keypair, gateway, wallet };
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> Result<()> {
|
||||
self.gateway.start().await?;
|
||||
Ok(())
|
||||
self.gateway.start().await
|
||||
}
|
||||
|
||||
async fn build_slab_from_tx(
|
||||
&mut self,
|
||||
pub_key: pallas::Point,
|
||||
pubkey: pallas::Point,
|
||||
value: u64,
|
||||
token_id: pallas::Base,
|
||||
clear_input: bool,
|
||||
state: Arc<Mutex<State>>,
|
||||
) -> ClientResult<Vec<Coin>> {
|
||||
debug!(target: "CLIENT", "Start build slab from tx");
|
||||
|
||||
debug!("Start build slab from tx");
|
||||
let mut clear_inputs: Vec<tx::TransactionBuilderClearInputInfo> = vec![];
|
||||
let mut inputs: Vec<tx::TransactionBuilderInputInfo> = vec![];
|
||||
let mut outputs: Vec<tx::TransactionBuilderOutputInfo> = vec![];
|
||||
let mut coins: Vec<Coin> = vec![];
|
||||
|
||||
if clear_input {
|
||||
let signature_secret = self.main_keypair.private;
|
||||
// TODO: FIXME:
|
||||
let base_secret = self.main_keypair.private;
|
||||
let signature_secret = schnorr::SecretKey(mod_r_p(base_secret));
|
||||
let input = tx::TransactionBuilderClearInputInfo { value, token_id, signature_secret };
|
||||
clear_inputs.push(input);
|
||||
} else {
|
||||
debug!(target: "CLIENT", "Start build inputs");
|
||||
|
||||
let mut inputs_value: u64 = 0;
|
||||
|
||||
let own_coins = self.wallet.get_own_coins()?;
|
||||
debug!("Start build inputs");
|
||||
let mut inputs_value = 0_u64;
|
||||
let state_m = state.lock().await;
|
||||
let own_coins = self.wallet.get_own_coins().await?;
|
||||
|
||||
for own_coin in own_coins.iter() {
|
||||
if inputs_value >= value {
|
||||
break
|
||||
}
|
||||
|
||||
let witness = &own_coin.witness;
|
||||
let merkle_path = witness.path().unwrap();
|
||||
let node = MerkleNode(own_coin.coin.inner());
|
||||
let (leaf_position, merkle_path) = state_m.tree.authentication_path(&node).unwrap();
|
||||
// TODO: What is this counting? Is it everything or does it know to separate
|
||||
// different tokens?
|
||||
inputs_value += own_coin.note.value;
|
||||
|
||||
let input = tx::TransactionBuilderInputInfo {
|
||||
leaf_position,
|
||||
merkle_path,
|
||||
secret: own_coin.secret,
|
||||
note: own_coin.note.clone(),
|
||||
@@ -146,157 +155,90 @@ impl Client {
|
||||
});
|
||||
}
|
||||
|
||||
debug!(target: "CLIENT", "End build inputs");
|
||||
debug!("End build inputs");
|
||||
}
|
||||
|
||||
outputs.push(tx::TransactionBuilderOutputInfo { value, token_id, public: pub_key });
|
||||
outputs.push(tx::TransactionBuilderOutputInfo { value, token_id, public: pubkey });
|
||||
|
||||
let builder = tx::TransactionBuilder { clear_inputs, inputs, outputs };
|
||||
|
||||
let tx: tx::Transaction;
|
||||
|
||||
let mut tx_data = vec![];
|
||||
{
|
||||
tx = builder.build()?;
|
||||
tx.encode(&mut tx_data).expect("encode tx");
|
||||
}
|
||||
tx = builder.build()?;
|
||||
tx.encode(&mut tx_data).expect("encode tx");
|
||||
|
||||
let slab = Slab::new(tx_data);
|
||||
debug!("End build slab from tx");
|
||||
|
||||
debug!(target: "CLIENT", "End build slab from tx");
|
||||
|
||||
// check if it's valid before send it to gateway
|
||||
// Check if it's valid before sending to gateway
|
||||
let state = &*state.lock().await;
|
||||
|
||||
state_transition(state, tx)?;
|
||||
|
||||
self.gateway.put_slab(slab).await?;
|
||||
|
||||
Ok(coins)
|
||||
}
|
||||
|
||||
pub async fn send(
|
||||
&mut self,
|
||||
pub_key: pallas::Point,
|
||||
pubkey: pallas::Point,
|
||||
amount: u64,
|
||||
token_id: pallas::Base,
|
||||
clear_input: bool,
|
||||
state: Arc<Mutex<State>>,
|
||||
) -> ClientResult<()> {
|
||||
debug!(target: "CLIENT", "Start send {}", amount);
|
||||
// TODO: TOKEN debug
|
||||
debug!("Start send {}", amount);
|
||||
|
||||
if amount == 0 {
|
||||
return Err(ClientFailed::InvalidAmount(amount as u64))
|
||||
return Err(ClientFailed::InvalidAmount(0))
|
||||
}
|
||||
|
||||
let coins = self.build_slab_from_tx(pub_key, amount, token_id, clear_input, state).await?;
|
||||
|
||||
let coins = self.build_slab_from_tx(pubkey, amount, token_id, clear_input, state).await?;
|
||||
for coin in coins.iter() {
|
||||
self.wallet.confirm_spend_coin(coin)?;
|
||||
self.wallet.confirm_spend_coin(coin).await?;
|
||||
}
|
||||
|
||||
debug!(target: "CLIENT", "End send {}", amount);
|
||||
|
||||
debug!("End send {}", amount);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn transfer(
|
||||
&mut self,
|
||||
token_id: pallas::Base,
|
||||
pub_key: pallas::Point,
|
||||
amount: u64,
|
||||
async fn update_state(
|
||||
secret_keys: Vec<pallas::Base>,
|
||||
slab: &Slab,
|
||||
state: Arc<Mutex<State>>,
|
||||
) -> ClientResult<()> {
|
||||
debug!(target: "CLIENT", "Start transfer {}", amount);
|
||||
|
||||
let token_id_exists = self.wallet.token_id_exists(&token_id)?;
|
||||
|
||||
if token_id_exists {
|
||||
self.send(pub_key, amount, token_id, false, state).await?;
|
||||
} else {
|
||||
return Err(ClientFailed::NotEnoughValue(amount))
|
||||
}
|
||||
|
||||
debug!(target: "CLIENT", "End transfer {}", amount);
|
||||
wallet: WalletPtr,
|
||||
notify: Option<async_channel::Sender<(pallas::Point, u64)>>,
|
||||
) -> Result<()> {
|
||||
debug!("Build tx from slab and update the state");
|
||||
let tx = tx::Transaction::decode(&slab.get_payload()[..])?;
|
||||
|
||||
let st = &*state.lock().await;
|
||||
let update = state_transition(st, tx)?;
|
||||
let mut st = state.lock().await;
|
||||
st.apply(update, secret_keys, notify, wallet).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
// The entire merkle tree state
|
||||
pub tree: BridgeTree<MerkleNode, 32>,
|
||||
// List of all previous and the current merkle roots
|
||||
// This is the hashed value of all the children.
|
||||
pub merkle_roots: RocksColumn<columns::MerkleRoots>,
|
||||
// Nullifiers prevent double spending
|
||||
pub nullifiers: RocksColumn<columns::Nullifiers>,
|
||||
// Mint verifying key used by ZK
|
||||
pub mint_vk: VerifyingKey,
|
||||
// Spend verifying key used by ZK
|
||||
pub spend_vk: VerifyingKey,
|
||||
// List of cashier public keys
|
||||
pub public_keys: Vec<pallas::Point>,
|
||||
}
|
||||
|
||||
impl ProgramState for State {
|
||||
fn is_valid_cashier_public_key(&self, public: &schnorr::PublicKey) -> bool {
|
||||
debug!(target: "CLIENT STATE", "Check if it is valid cashier public key");
|
||||
self.public_keys.contains(public)
|
||||
}
|
||||
|
||||
fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
|
||||
debug!(target: "CLIENT STATE", "Check if it is valid merkle");
|
||||
|
||||
if let Ok(mr) = self.merkle_roots.key_exist(*merkle_root) {
|
||||
return mr
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||
debug!(target: "CLIENT STATE", "Check if nullifier exists");
|
||||
if let Ok(nl) = self.nullifiers.key_exist(nullifier.to_bytes()) {
|
||||
return nl
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn mint_vk(&self) -> &VerifyingKey {
|
||||
&self.mint_vk
|
||||
}
|
||||
|
||||
fn spend_vk(&self) -> &VerifyingKey {
|
||||
&self.spend_vk
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl Client {
|
||||
|
||||
pub async fn connect_to_subscriber_from_cashier(
|
||||
&self,
|
||||
state: Arc<Mutex<State>>,
|
||||
cashier_wallet: CashierDbPtr,
|
||||
notify: async_channel::Sender<(jubjub::SubgroupPoint, u64)>,
|
||||
notify: async_channel::Sender<(pallas::Point, u64)>,
|
||||
executor: Arc<Executor<'_>>,
|
||||
) -> Result<()> {
|
||||
// start subscribing
|
||||
debug!(target: "CLIENT", "Start subscriber for cashier");
|
||||
let gateway_slabs_sub: GatewaySlabsSubscriber =
|
||||
self.gateway.start_subscriber(executor.clone()).await?;
|
||||
debug!("Start subscriber for cashier");
|
||||
let gateway_slabs_sub = self.gateway.start_subscriber(executor.clone()).await?;
|
||||
|
||||
let secret_key = self.main_keypair.private;
|
||||
let wallet = self.wallet.clone();
|
||||
|
||||
//let task: smol::Task<Result<()>> = executor.spawn(async move {
|
||||
let task: smol::Task<Result<()>> = executor.spawn(async move {
|
||||
loop {
|
||||
let slab = gateway_slabs_sub.recv().await?;
|
||||
debug!("Received new slab");
|
||||
|
||||
debug!(target: "CLIENT", "Received new slab");
|
||||
|
||||
let mut secret_keys: Vec<jubjub::Fr> = vec![secret_key];
|
||||
let mut withdraw_keys = cashier_wallet.get_withdraw_private_keys()?;
|
||||
let mut secret_keys: Vec<pallas::Base> = vec![secret_key];
|
||||
let mut withdraw_keys = cashier_wallet.get_withdraw_private_keys().await?;
|
||||
secret_keys.append(&mut withdraw_keys);
|
||||
|
||||
let update_state = Self::update_state(
|
||||
@@ -306,246 +248,16 @@ impl Client {
|
||||
wallet.clone(),
|
||||
Some(notify.clone()),
|
||||
)
|
||||
.await;
|
||||
.await;
|
||||
|
||||
if let Err(e) = update_state {
|
||||
warn!("Update state: {}", e.to_string());
|
||||
continue;
|
||||
warn!("Update state: {}", e);
|
||||
continue
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
task.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect_to_subscriber(
|
||||
&self,
|
||||
state: Arc<Mutex<State>>,
|
||||
executor: Arc<Executor<'_>>,
|
||||
) -> Result<()> {
|
||||
// start subscribing
|
||||
debug!(target: "CLIENT", "Start subscriber");
|
||||
let gateway_slabs_sub: GatewaySlabsSubscriber =
|
||||
self.gateway.start_subscriber(executor.clone()).await?;
|
||||
|
||||
let secret_key = self.main_keypair.private;
|
||||
let wallet = self.wallet.clone();
|
||||
|
||||
let task: smol::Task<Result<()>> = executor.spawn(async move {
|
||||
loop {
|
||||
let slab = gateway_slabs_sub.recv().await?;
|
||||
|
||||
debug!(target: "CLIENT", "Received new slab");
|
||||
|
||||
let update_state = Self::update_state(
|
||||
vec![secret_key],
|
||||
&slab,
|
||||
state.clone(),
|
||||
wallet.clone(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = update_state {
|
||||
warn!("Update state: {}", e.to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
task.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_state(
|
||||
secret_keys: Vec<jubjub::Fr>,
|
||||
slab: &Slab,
|
||||
state: Arc<Mutex<State>>,
|
||||
wallet: WalletPtr,
|
||||
notify: Option<async_channel::Sender<(jubjub::SubgroupPoint, u64)>>,
|
||||
) -> Result<()> {
|
||||
debug!(target: "CLIENT", "Build tx from slab and update the state");
|
||||
|
||||
let tx = tx::Transaction::decode(&slab.get_payload()[..])?;
|
||||
|
||||
|
||||
let update: StateUpdate;
|
||||
|
||||
{
|
||||
let state = &*state.lock().await;
|
||||
update = state_transition(state, tx)?;
|
||||
}
|
||||
|
||||
let mut state = state.lock().await;
|
||||
|
||||
state
|
||||
.apply(update, secret_keys.clone(), notify, wallet)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_db(&self) -> Result<()> {
|
||||
self.wallet.init_db()
|
||||
}
|
||||
|
||||
pub fn get_own_coins(&self) -> Result<Vec<OwnCoin>> {
|
||||
self.wallet.get_own_coins()
|
||||
}
|
||||
|
||||
pub fn confirm_spend_coin(&self, coin: &Coin) -> Result<()> {
|
||||
self.wallet.confirm_spend_coin(coin)
|
||||
}
|
||||
|
||||
pub fn key_gen(&self) -> Result<()> {
|
||||
self.wallet.key_gen()
|
||||
}
|
||||
|
||||
pub fn get_balances(&self) -> Result<Balances> {
|
||||
self.wallet.get_balances()
|
||||
}
|
||||
|
||||
pub fn token_id_exists(&self, token_id: &jubjub::Fr) -> Result<bool> {
|
||||
self.wallet.token_id_exists(token_id)
|
||||
}
|
||||
|
||||
pub fn get_token_id(&self) -> Result<Vec<jubjub::Fr>> {
|
||||
self.wallet.get_token_id()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
// The entire merkle tree state
|
||||
pub tree: CommitmentTree<MerkleNode>,
|
||||
// List of all previous and the current merkle roots
|
||||
// This is the hashed value of all the children.
|
||||
pub merkle_roots: RocksColumn<columns::MerkleRoots>,
|
||||
// Nullifiers prevent double spending
|
||||
pub nullifiers: RocksColumn<columns::Nullifiers>,
|
||||
// Mint verifying key used by ZK
|
||||
pub mint_pvk: groth16::PreparedVerifyingKey<Bls12>,
|
||||
// Spend verifying key used by ZK
|
||||
pub spend_pvk: groth16::PreparedVerifyingKey<Bls12>,
|
||||
// List of cashier public keys
|
||||
pub public_keys: Vec<jubjub::SubgroupPoint>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub async fn apply(
|
||||
&mut self,
|
||||
update: StateUpdate,
|
||||
secret_keys: Vec<jubjub::Fr>,
|
||||
notify: Option<async_channel::Sender<(jubjub::SubgroupPoint, u64)>>,
|
||||
wallet: WalletPtr,
|
||||
) -> Result<()> {
|
||||
// Extend our list of nullifiers with the ones from the update
|
||||
|
||||
debug!(target: "CLIENT STATE", "Extend nullifiers");
|
||||
for nullifier in update.nullifiers {
|
||||
self.nullifiers.put(nullifier, vec![] as Vec<u8>)?;
|
||||
}
|
||||
|
||||
debug!(target: "CLIENT STATE", "Update merkle tree and witness ");
|
||||
// Update merkle tree and witnesses
|
||||
for (coin, enc_note) in update.coins.into_iter().zip(update.enc_notes.iter()) {
|
||||
// Add the new coins to the merkle tree
|
||||
let node = MerkleNode::from_coin(&coin);
|
||||
self.tree.append(node).expect("Append to merkle tree");
|
||||
|
||||
debug!(target: "CLIENT STATE", "Keep track of all merkle roots");
|
||||
|
||||
// Keep track of all merkle roots that have existed
|
||||
self.merkle_roots.put(self.tree.root(), vec![] as Vec<u8>)?;
|
||||
|
||||
debug!(target: "CLIENT STATE", "Update witness");
|
||||
|
||||
// Also update all the coin witnesses
|
||||
let mut updated_witnesses = wallet.get_witnesses()?;
|
||||
|
||||
updated_witnesses.iter_mut().for_each(|(_, witness)| {
|
||||
witness.append(node).expect("Append to witness");
|
||||
});
|
||||
|
||||
wallet.update_witnesses(updated_witnesses)?;
|
||||
|
||||
debug!(target: "CLIENT STATE", "iterate over secret_keys to decrypt note");
|
||||
|
||||
for secret in secret_keys.iter() {
|
||||
if let Some(note) = Self::try_decrypt_note(enc_note, *secret) {
|
||||
// We need to keep track of the witness for this coin.
|
||||
// This allows us to prove inclusion of the coin in the merkle tree with ZK.
|
||||
// Just as we update the merkle tree with every new coin, so we do the same with
|
||||
// the witness.
|
||||
|
||||
// Derive the current witness from the current tree.
|
||||
// This is done right after we add our coin to the tree (but before any other
|
||||
// coins are added)
|
||||
|
||||
// Make a new witness for this coin
|
||||
let witness = IncrementalWitness::from_tree(&self.tree);
|
||||
|
||||
let mut nullifier = [0; 32];
|
||||
nullifier.copy_from_slice(
|
||||
Blake2sParams::new()
|
||||
.hash_length(32)
|
||||
.personal(zcash_primitives::constants::PRF_NF_PERSONALIZATION)
|
||||
.to_state()
|
||||
.update(&secret.to_bytes())
|
||||
.update(¬e.serial.to_bytes())
|
||||
.finalize()
|
||||
.as_bytes(),
|
||||
);
|
||||
|
||||
let nullifier = Nullifier::new(nullifier);
|
||||
|
||||
let own_coin = OwnCoin {
|
||||
coin: coin.clone(),
|
||||
note: note.clone(),
|
||||
secret: *secret,
|
||||
witness: witness.clone(),
|
||||
nullifier,
|
||||
};
|
||||
|
||||
wallet.put_own_coins(own_coin)?;
|
||||
let pub_key = zcash_primitives::constants::SPENDING_KEY_GENERATOR * secret;
|
||||
|
||||
debug!(target: "CLIENT STATE", "Received a coin: amount {} ", note.value);
|
||||
|
||||
debug!(target: "CLIENT STATE", "Send a notification");
|
||||
|
||||
if let Some(ch) = notify.clone() {
|
||||
ch.send((pub_key, note.value)).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_decrypt_note(ciphertext: &EncryptedNote, secret: jubjub::Fr) -> Option<Note> {
|
||||
match ciphertext.decrypt(&secret) {
|
||||
// ... and return the decrypted note for this coin.
|
||||
Ok(note) => Some(note),
|
||||
// We weren't able to decrypt the note with our key.
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl From<super::error::Error> for ClientFailed {
|
||||
fn from(err: super::error::Error) -> ClientFailed {
|
||||
ClientFailed::ClientError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::state::VerifyFailed> for ClientFailed {
|
||||
fn from(err: crate::state::VerifyFailed) -> ClientFailed {
|
||||
ClientFailed::VerifyError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type ClientResult<T> = std::result::Result<T, ClientFailed>;
|
||||
|
||||
@@ -19,11 +19,9 @@ impl Coin {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) fn inner(&self) -> pallas::Base {
|
||||
self.0
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl Encodable for Coin {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use std::io;
|
||||
|
||||
use halo2_gadgets::primitives::{
|
||||
poseidon,
|
||||
poseidon::{ConstantLength, P128Pow5T3},
|
||||
};
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
|
||||
use crate::{
|
||||
@@ -11,6 +15,12 @@ use crate::{
|
||||
pub struct Nullifier(pub(crate) pallas::Base);
|
||||
|
||||
impl Nullifier {
|
||||
pub fn new(secret: pallas::Base, serial: pallas::Base) -> Self {
|
||||
let nullifier = [secret, serial];
|
||||
let nullifier = poseidon::Hash::init(P128Pow5T3, ConstantLength::<2>).hash(nullifier);
|
||||
Nullifier(nullifier)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
|
||||
pallas::Base::from_bytes(bytes).map(Nullifier).unwrap()
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ impl SecretKey {
|
||||
let public_key = OrchardFixedBases::SpendAuthG.generator() * self.0;
|
||||
PublicKey(public_key)
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> pallas::Scalar {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -67,6 +71,10 @@ impl PublicKey {
|
||||
OrchardFixedBases::SpendAuthG.generator() * signature.response - self.0 * challenge ==
|
||||
signature.commit
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> pallas::Point {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for PublicKey {
|
||||
|
||||
129
src/state.rs
129
src/state.rs
@@ -1,18 +1,30 @@
|
||||
use halo2_gadgets::ecc::FixedPoints;
|
||||
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree};
|
||||
use log::debug;
|
||||
use pasta_curves::pallas;
|
||||
|
||||
use crate::{
|
||||
blockchain::{rocks::columns, RocksColumn},
|
||||
crypto::{
|
||||
coin::Coin, merkle_node::MerkleNode, note::EncryptedNote, nullifier::Nullifier,
|
||||
proof::VerifyingKey, schnorr,
|
||||
coin::Coin,
|
||||
constants::OrchardFixedBases,
|
||||
merkle_node::MerkleNode,
|
||||
note::{EncryptedNote, Note},
|
||||
nullifier::Nullifier,
|
||||
proof::VerifyingKey,
|
||||
schnorr,
|
||||
util::mod_r_p,
|
||||
OwnCoin,
|
||||
},
|
||||
tx::Transaction,
|
||||
wallet::WalletPtr,
|
||||
Result,
|
||||
};
|
||||
|
||||
pub trait ProgramState {
|
||||
fn is_valid_cashier_public_key(&self, public: &schnorr::PublicKey) -> bool;
|
||||
fn is_valid_merkle(&self, merkle: &MerkleNode) -> bool;
|
||||
fn nullifier_exists(&self, nullifier: &Nullifier) -> bool;
|
||||
|
||||
fn mint_vk(&self) -> &VerifyingKey;
|
||||
fn spend_vk(&self) -> &VerifyingKey;
|
||||
}
|
||||
@@ -102,3 +114,114 @@ pub fn state_transition<S: ProgramState>(state: &S, tx: Transaction) -> VerifyRe
|
||||
|
||||
Ok(StateUpdate { nullifiers, coins, enc_notes })
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
/// The entire Merkle tree state
|
||||
pub tree: BridgeTree<MerkleNode, 32>,
|
||||
/// List of all previous and the current merkle roots.
|
||||
/// This is the hashed value of all the children.
|
||||
pub merkle_roots: RocksColumn<columns::MerkleRoots>,
|
||||
/// Nullifiers prevent double-spending
|
||||
pub nullifiers: RocksColumn<columns::Nullifiers>,
|
||||
/// List of Cashier public keys
|
||||
pub public_keys: Vec<pallas::Point>,
|
||||
/// Verifying key for the Mint contract
|
||||
pub mint_vk: VerifyingKey,
|
||||
/// Verifying key for the Spend contract
|
||||
pub spend_vk: VerifyingKey,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub async fn apply(
|
||||
&mut self,
|
||||
update: StateUpdate,
|
||||
secret_keys: Vec<pallas::Base>,
|
||||
notify: Option<async_channel::Sender<(pallas::Point, u64)>>,
|
||||
wallet: WalletPtr,
|
||||
) -> Result<()> {
|
||||
// Extend our list of nullifiers with the ones from the update.
|
||||
debug!("Extend nullifiers");
|
||||
for nullifier in update.nullifiers {
|
||||
self.nullifiers.put(nullifier, vec![] as Vec<u8>)?;
|
||||
}
|
||||
|
||||
debug!("Update Merkle tree and witness");
|
||||
for (coin, enc_note) in update.coins.into_iter().zip(update.enc_notes.iter()) {
|
||||
// Add the new coins to the Merkle tree
|
||||
let node = MerkleNode(coin.0);
|
||||
self.tree.append(&node);
|
||||
|
||||
// Keep track of all Merkle roots that have existed
|
||||
self.merkle_roots.put(self.tree.root(), vec![] as Vec<u8>)?;
|
||||
|
||||
for secret in secret_keys.iter() {
|
||||
if let Some(note) = State::try_decrypt_note(enc_note, *secret) {
|
||||
// TODO: What to do with witnesses?
|
||||
self.tree.witness();
|
||||
let nullifier = Nullifier::new(*secret, note.serial);
|
||||
|
||||
let own_coin = OwnCoin {
|
||||
coin: coin.clone(),
|
||||
note: note.clone(),
|
||||
secret: *secret,
|
||||
// witness: witness.clone(),
|
||||
nullifier,
|
||||
};
|
||||
|
||||
wallet.put_own_coins(own_coin).await?;
|
||||
|
||||
// TODO: Place somewhere proper
|
||||
let pubkey = OrchardFixedBases::NullifierK.generator() * mod_r_p(*secret);
|
||||
|
||||
debug!("Received a coin: amount {}", note.value);
|
||||
debug!("Send a notification");
|
||||
if let Some(ch) = notify.clone() {
|
||||
ch.send((pubkey, note.value)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("apply() exiting successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_decrypt_note(ciphertext: &EncryptedNote, secret: pallas::Base) -> Option<Note> {
|
||||
match ciphertext.decrypt(&secret) {
|
||||
Ok(note) => Some(note),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProgramState for State {
|
||||
// TODO: Proper keypair type
|
||||
fn is_valid_cashier_public_key(&self, public: &schnorr::PublicKey) -> bool {
|
||||
debug!("Check if it is a valid cashier public key");
|
||||
self.public_keys.contains(&public.inner())
|
||||
}
|
||||
|
||||
fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
|
||||
debug!("Check if it is valid merkle");
|
||||
if let Ok(mr) = self.merkle_roots.key_exist(merkle_root.clone()) {
|
||||
return mr
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||
debug!("Check if nullifier exists");
|
||||
if let Ok(nl) = self.nullifiers.key_exist(nullifier.to_bytes()) {
|
||||
return nl
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn mint_vk(&self) -> &VerifyingKey {
|
||||
&self.mint_vk
|
||||
}
|
||||
|
||||
fn spend_vk(&self) -> &VerifyingKey {
|
||||
&self.spend_vk
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{path::Path, sync::Arc};
|
||||
use std::path::Path;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use log::{debug, error, info};
|
||||
use pasta_curves::arithmetic::Field;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -46,7 +47,7 @@ impl Balances {
|
||||
}
|
||||
|
||||
pub struct WalletDb {
|
||||
pub conn: Connection,
|
||||
pub conn: Mutex<Connection>,
|
||||
}
|
||||
|
||||
impl WalletApi for WalletDb {}
|
||||
@@ -63,25 +64,27 @@ impl WalletDb {
|
||||
conn.pragma_update(None, "key", &password)?;
|
||||
info!(target: "WALLETDB", "Opened connection at path: {:?}", path);
|
||||
|
||||
Ok(Arc::new(Self { conn }))
|
||||
Ok(Arc::new(Self { conn: Mutex::new(conn) }))
|
||||
}
|
||||
|
||||
pub fn init_db(&self) -> Result<()> {
|
||||
pub async fn init_db(&self) -> Result<()> {
|
||||
debug!(target: "WALLETDB", "Initialize...");
|
||||
let contents = include_str!("../../sql/schema.sql");
|
||||
Ok(self.conn.execute_batch(contents)?)
|
||||
let conn = self.conn.lock().await;
|
||||
Ok(conn.execute_batch(contents)?)
|
||||
}
|
||||
|
||||
pub fn key_gen(&self) -> Result<()> {
|
||||
pub async fn key_gen(&self) -> Result<()> {
|
||||
debug!(target: "WALLETDB", "Attempting to generate keys...");
|
||||
let mut stmt = self.conn.prepare("SELECT * FROM keys WHERE key_id > ?")?;
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare("SELECT * FROM keys WHERE key_id > ?")?;
|
||||
|
||||
let key_check = stmt.exists(params!["0"])?;
|
||||
|
||||
if !key_check {
|
||||
let secret = DrkSecretKey::random(&mut OsRng);
|
||||
let public = derive_public_key(secret);
|
||||
self.put_keypair(&public, &secret)?;
|
||||
self.put_keypair(&public, &secret).await?;
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
@@ -89,12 +92,17 @@ impl WalletDb {
|
||||
Err(Error::from(ClientFailed::KeyExists))
|
||||
}
|
||||
|
||||
pub fn put_keypair(&self, key_public: &DrkPublicKey, key_private: &DrkSecretKey) -> Result<()> {
|
||||
pub async fn put_keypair(
|
||||
&self,
|
||||
key_public: &DrkPublicKey,
|
||||
key_private: &DrkSecretKey,
|
||||
) -> Result<()> {
|
||||
debug!(target: "WALLETDB", "put_keypair()");
|
||||
let key_public = serial::serialize(key_public);
|
||||
let key_private = serial::serialize(key_private);
|
||||
|
||||
self.conn.execute(
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"INSERT INTO keys(key_public, key_private) VALUES (?1, ?2)",
|
||||
params![key_public, key_private],
|
||||
)?;
|
||||
@@ -102,9 +110,10 @@ impl WalletDb {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_keypairs(&self) -> Result<Vec<Keypair>> {
|
||||
pub async fn get_keypairs(&self) -> Result<Vec<Keypair>> {
|
||||
debug!(target: "WALLETDB", "Returning keypairs...");
|
||||
let mut stmt = self.conn.prepare("SELECT * FROM keys")?;
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare("SELECT * FROM keys")?;
|
||||
|
||||
// this just gets the first key. maybe we should randomize this
|
||||
let key_iter = stmt.query_map([], |row| Ok((row.get(1)?, row.get(2)?)))?;
|
||||
@@ -122,11 +131,12 @@ impl WalletDb {
|
||||
Ok(keypairs)
|
||||
}
|
||||
|
||||
pub fn get_own_coins(&self) -> Result<OwnCoins> {
|
||||
pub async fn get_own_coins(&self) -> Result<OwnCoins> {
|
||||
debug!(target: "WALLETDB", "Get own coins");
|
||||
let is_spent = 0;
|
||||
|
||||
let mut coins = self.conn.prepare("SELECT * FROM coins WHERE is_spent = :is_spent ;")?;
|
||||
let conn = self.conn.lock().await;
|
||||
let mut coins = conn.prepare("SELECT * FROM coins WHERE is_spent = :is_spent ;")?;
|
||||
|
||||
let rows = coins.query_map(&[(":is_spent", &is_spent)], |row| {
|
||||
Ok((
|
||||
@@ -178,7 +188,7 @@ impl WalletDb {
|
||||
Ok(own_coins)
|
||||
}
|
||||
|
||||
pub fn put_own_coins(&self, own_coin: OwnCoin) -> Result<()> {
|
||||
pub async fn put_own_coins(&self, own_coin: OwnCoin) -> Result<()> {
|
||||
debug!(target: "WALLETDB", "Put own coins");
|
||||
let coin = self.get_value_serialized(&own_coin.coin.to_bytes())?;
|
||||
let serial = self.get_value_serialized(&own_coin.note.serial)?;
|
||||
@@ -191,7 +201,8 @@ impl WalletDb {
|
||||
let is_spent = 0;
|
||||
let nullifier = self.get_value_serialized(&own_coin.nullifier)?;
|
||||
|
||||
self.conn.execute(
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO coins
|
||||
(coin, serial, value, token_id, coin_blind,
|
||||
valcom_blind, witness, secret, is_spent, nullifier)
|
||||
@@ -215,18 +226,20 @@ impl WalletDb {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_own_coins(&self) -> Result<()> {
|
||||
pub async fn remove_own_coins(&self) -> Result<()> {
|
||||
debug!(target: "WALLETDB", "Remove own coins");
|
||||
let _rows = self.conn.execute("DROP TABLE coins;", [])?;
|
||||
let conn = self.conn.lock().await;
|
||||
let _rows = conn.execute("DROP TABLE coins;", [])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn confirm_spend_coin(&self, coin: &Coin) -> Result<()> {
|
||||
pub async fn confirm_spend_coin(&self, coin: &Coin) -> Result<()> {
|
||||
debug!(target: "WALLETDB", "Confirm spend coin");
|
||||
let is_spent = 1;
|
||||
let coin = self.get_value_serialized(coin)?;
|
||||
|
||||
self.conn.execute(
|
||||
let conn = self.conn.lock().await;
|
||||
conn.execute(
|
||||
"UPDATE coins
|
||||
SET is_spent = ?1
|
||||
WHERE coin = ?2 ;",
|
||||
@@ -284,11 +297,12 @@ impl WalletDb {
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn get_balances(&self) -> Result<Balances> {
|
||||
pub async fn get_balances(&self) -> Result<Balances> {
|
||||
debug!(target: "WALLETDB", "Get token and balances...");
|
||||
let is_spent = 0;
|
||||
|
||||
let mut stmt = self.conn.prepare(
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT value, token_id, nullifier FROM coins WHERE is_spent = :is_spent ;",
|
||||
)?;
|
||||
|
||||
@@ -309,12 +323,12 @@ impl WalletDb {
|
||||
Ok(balances)
|
||||
}
|
||||
|
||||
pub fn get_token_id(&self) -> Result<Vec<DrkTokenId>> {
|
||||
pub async fn get_token_id(&self) -> Result<Vec<DrkTokenId>> {
|
||||
debug!(target: "WALLETDB", "Get token ID...");
|
||||
let is_spent = 0;
|
||||
|
||||
let mut stmt =
|
||||
self.conn.prepare("SELECT token_id FROM coins WHERE is_spent = :is_spent ;")?;
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare("SELECT token_id FROM coins WHERE is_spent = :is_spent ;")?;
|
||||
|
||||
let rows = stmt.query_map(&[(":is_spent", &is_spent)], |row| row.get(0))?;
|
||||
|
||||
@@ -329,21 +343,22 @@ impl WalletDb {
|
||||
Ok(token_ids)
|
||||
}
|
||||
|
||||
pub fn token_id_exists(&self, token_id: &DrkTokenId) -> Result<bool> {
|
||||
pub async fn token_id_exists(&self, token_id: &DrkTokenId) -> Result<bool> {
|
||||
debug!(target: "WALLETDB", "Check tokenID exists");
|
||||
let is_spent = 0;
|
||||
let id = self.get_value_serialized(token_id)?;
|
||||
|
||||
let mut stmt =
|
||||
self.conn.prepare("SELECT * FROM coins WHERE token_id = ? AND is_spent = ? ;")?;
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare("SELECT * FROM coins WHERE token_id = ? AND is_spent = ? ;")?;
|
||||
|
||||
let id_check = stmt.exists(params![id, is_spent])?;
|
||||
|
||||
Ok(id_check)
|
||||
}
|
||||
|
||||
pub fn test_wallet(&self) -> Result<()> {
|
||||
let mut stmt = self.conn.prepare("SELECT * FROM keys")?;
|
||||
pub async fn test_wallet(&self) -> Result<()> {
|
||||
let conn = self.conn.lock().await;
|
||||
let mut stmt = conn.prepare("SELECT * FROM keys")?;
|
||||
let _rows = stmt.query([])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user