mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
drk: drk rewritte skeleton added
This commit is contained in:
@@ -24,6 +24,7 @@ members = [
|
|||||||
"bin/darkfid2",
|
"bin/darkfid2",
|
||||||
"bin/darkfi-mmproxy",
|
"bin/darkfi-mmproxy",
|
||||||
#"bin/drk",
|
#"bin/drk",
|
||||||
|
#"bin/drk2",
|
||||||
#"bin/faucetd",
|
#"bin/faucetd",
|
||||||
#"bin/fud/fu",
|
#"bin/fud/fu",
|
||||||
#"bin/fud/fud",
|
#"bin/fud/fud",
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ impl Darkfid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RPCAPI:
|
// RPCAPI:
|
||||||
// Pings configured miner daemon for livenes.
|
// Pings configured miner daemon for liveness.
|
||||||
// Returns `true` on success.
|
// Returns `true` on success.
|
||||||
//
|
//
|
||||||
// --> {"jsonrpc": "2.0", "method": "ping_miner", "params": [], "id": 1}
|
// --> {"jsonrpc": "2.0", "method": "ping_miner", "params": [], "id": 1}
|
||||||
|
|||||||
31
bin/drk2/Cargo.toml
Normal file
31
bin/drk2/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[package]
|
||||||
|
name = "drk2"
|
||||||
|
version = "0.4.1"
|
||||||
|
homepage = "https://dark.fi"
|
||||||
|
description = "Command-line client for darkfid"
|
||||||
|
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||||
|
repository = "https://github.com/darkrenaissance/darkfi"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Darkfi
|
||||||
|
darkfi = {path = "../../", features = ["async-daemonize", "rpc"]}
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
log = "0.4.20"
|
||||||
|
rodio = {version = "0.17.3", default-features = false, features = ["minimp3"]}
|
||||||
|
rusqlite = {version = "0.30.0", features = ["sqlcipher"]}
|
||||||
|
url = "2.5.0"
|
||||||
|
|
||||||
|
# Daemon
|
||||||
|
easy-parallel = "3.3.1"
|
||||||
|
signal-hook-async-std = "0.2.2"
|
||||||
|
signal-hook = "0.3.17"
|
||||||
|
simplelog = "0.12.1"
|
||||||
|
smol = "1.3.0"
|
||||||
|
|
||||||
|
# Argument parsing
|
||||||
|
serde = {version = "1.0.195", features = ["derive"]}
|
||||||
|
structopt = "0.3.26"
|
||||||
|
structopt-toml = "0.5.1"
|
||||||
42
bin/drk2/Makefile
Normal file
42
bin/drk2/Makefile
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
.POSIX:
|
||||||
|
|
||||||
|
# Install prefix
|
||||||
|
PREFIX = $(HOME)/.cargo
|
||||||
|
|
||||||
|
# Cargo binary
|
||||||
|
CARGO = cargo +nightly
|
||||||
|
|
||||||
|
# Compile target
|
||||||
|
RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2)
|
||||||
|
# Uncomment when doing musl static builds
|
||||||
|
#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes
|
||||||
|
|
||||||
|
SRC = \
|
||||||
|
Cargo.toml \
|
||||||
|
../../Cargo.toml \
|
||||||
|
$(shell find src -type f -name '*.rs') \
|
||||||
|
$(shell find ../../src -type f -name '*.rs') \
|
||||||
|
$(shell find ../../src/contract -type f -name '*.wasm')
|
||||||
|
|
||||||
|
BIN = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"')
|
||||||
|
|
||||||
|
all: $(BIN)
|
||||||
|
|
||||||
|
$(BIN): $(SRC)
|
||||||
|
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@
|
||||||
|
cp -f ../../target/$(RUST_TARGET)/release/$@ $@
|
||||||
|
cp -f ../../target/$(RUST_TARGET)/release/$@ ../../$@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(BIN)
|
||||||
|
rm -f $(BIN) ../../$(BIN)
|
||||||
|
|
||||||
|
install: all
|
||||||
|
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||||
|
cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
|
||||||
|
chmod 755 $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
|
||||||
|
|
||||||
|
.PHONY: all clean install uninstall
|
||||||
16
bin/drk2/drk_config.toml
Normal file
16
bin/drk2/drk_config.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
## darkfid configuration file
|
||||||
|
##
|
||||||
|
## Please make sure you go through all the settings so you can configure
|
||||||
|
## your daemon properly.
|
||||||
|
##
|
||||||
|
## The default values are left commented. They can be overridden either by
|
||||||
|
## uncommenting, or by using the command-line.
|
||||||
|
|
||||||
|
# Path to wallet database
|
||||||
|
wallet_path = "~/.local/darkfi/drk/wallet.db"
|
||||||
|
|
||||||
|
# Password for the wallet database
|
||||||
|
wallet_pass = "changeme"
|
||||||
|
|
||||||
|
# darkfid JSON-RPC endpoint
|
||||||
|
endpoint = "tcp://127.0.0.1:8340"
|
||||||
39
bin/drk2/src/cli_util.rs
Normal file
39
bin/drk2/src/cli_util.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* This file is part of DarkFi (https://dark.fi)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use rodio::{source::Source, Decoder, OutputStream};
|
||||||
|
|
||||||
|
use darkfi::system::sleep;
|
||||||
|
|
||||||
|
/// Fun police go away
|
||||||
|
pub async fn kaching() {
|
||||||
|
const WALLET_MP3: &[u8] = include_bytes!("../wallet.mp3");
|
||||||
|
|
||||||
|
let cursor = Cursor::new(WALLET_MP3);
|
||||||
|
|
||||||
|
let Ok((_stream, stream_handle)) = OutputStream::try_default() else { return };
|
||||||
|
|
||||||
|
let Ok(source) = Decoder::new(cursor) else { return };
|
||||||
|
|
||||||
|
if stream_handle.play_raw(source.convert_samples()).is_err() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(2).await;
|
||||||
|
}
|
||||||
37
bin/drk2/src/error.rs
Normal file
37
bin/drk2/src/error.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/* This file is part of DarkFi (https://dark.fi)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Result type used in the wallet database module
|
||||||
|
pub type WalletDbResult<T> = std::result::Result<T, WalletDbError>;
|
||||||
|
|
||||||
|
/// Custom wallet database errors available for drk.
|
||||||
|
/// Please sort them sensefully.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WalletDbError {
|
||||||
|
// Connection related errors
|
||||||
|
ConnectionFailed = -32100,
|
||||||
|
|
||||||
|
// Configuration related errors
|
||||||
|
PragmaUpdateError = -32110,
|
||||||
|
|
||||||
|
// Query execution related errors
|
||||||
|
QueryPreparationFailed = -32120,
|
||||||
|
QueryExecutionFailed = -32121,
|
||||||
|
QueryFinalizationFailed = -32122,
|
||||||
|
ParseColumnValueError = -32123,
|
||||||
|
}
|
||||||
205
bin/drk2/src/main.rs
Normal file
205
bin/drk2/src/main.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/* This file is part of DarkFi (https://dark.fi)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{process::exit, sync::Arc, time::Instant};
|
||||||
|
|
||||||
|
use smol::stream::StreamExt;
|
||||||
|
use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use darkfi::{
|
||||||
|
async_daemonize, cli_desc,
|
||||||
|
rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
|
||||||
|
util::path::expand_path,
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Error codes
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
/// CLI utility functions
|
||||||
|
mod cli_util;
|
||||||
|
use cli_util::kaching;
|
||||||
|
|
||||||
|
/// Wallet database operations handler
|
||||||
|
mod walletdb;
|
||||||
|
use walletdb::{WalletDb, WalletPtr};
|
||||||
|
|
||||||
|
const CONFIG_FILE: &str = "drk_config.toml";
|
||||||
|
const CONFIG_FILE_CONTENTS: &str = include_str!("../drk_config.toml");
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
|
||||||
|
#[serde(default)]
|
||||||
|
#[structopt(name = "drk", about = cli_desc!())]
|
||||||
|
struct Args {
|
||||||
|
#[structopt(short, long)]
|
||||||
|
/// Configuration file to use
|
||||||
|
config: Option<String>,
|
||||||
|
|
||||||
|
#[structopt(long, default_value = "~/.local/darkfi/drk/wallet.db")]
|
||||||
|
/// Path to wallet database
|
||||||
|
wallet_path: String,
|
||||||
|
|
||||||
|
#[structopt(long, default_value = "changeme")]
|
||||||
|
/// Password for the wallet database
|
||||||
|
wallet_pass: String,
|
||||||
|
|
||||||
|
#[structopt(short, long, default_value = "tcp://127.0.0.1:8340")]
|
||||||
|
/// darkfid JSON-RPC endpoint
|
||||||
|
endpoint: Url,
|
||||||
|
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
/// Sub command to execute
|
||||||
|
command: Subcmd,
|
||||||
|
|
||||||
|
#[structopt(short, long)]
|
||||||
|
/// Set log file to ouput into
|
||||||
|
log: Option<String>,
|
||||||
|
|
||||||
|
#[structopt(short, parse(from_occurrences))]
|
||||||
|
/// Increase verbosity (-vvv supported)
|
||||||
|
verbose: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, StructOpt)]
|
||||||
|
enum Subcmd {
|
||||||
|
/// Fun
|
||||||
|
Kaching,
|
||||||
|
|
||||||
|
/// Send a ping request to the darkfid RPC endpoint
|
||||||
|
Ping,
|
||||||
|
|
||||||
|
// TODO: shell completions
|
||||||
|
/// Wallet operations
|
||||||
|
Wallet {
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Initialize wallet database
|
||||||
|
initialize: bool,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Generate a new keypair in the wallet
|
||||||
|
keygen: bool,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Query the wallet for known balances
|
||||||
|
balance: bool,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Get the default address in the wallet
|
||||||
|
address: bool,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Print all the secret keys from the wallet
|
||||||
|
secrets: bool,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Import secret keys from stdin into the wallet, separated by newlines
|
||||||
|
import_secrets: bool,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Print the Merkle tree in the wallet
|
||||||
|
tree: bool,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
/// Print all the coins in the wallet
|
||||||
|
coins: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CLI-util structure
|
||||||
|
pub struct Drk {
|
||||||
|
/// Wallet database operations handler
|
||||||
|
pub wallet: WalletPtr,
|
||||||
|
/// JSON-RPC client to execute requests to darkfid daemon
|
||||||
|
pub rpc_client: RpcClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drk {
|
||||||
|
async fn new(
|
||||||
|
wallet_path: String,
|
||||||
|
wallet_pass: String,
|
||||||
|
endpoint: Url,
|
||||||
|
ex: Arc<smol::Executor<'static>>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let wallet = match WalletDb::new(Some(expand_path(&wallet_path)?), Some(&wallet_pass)) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error initializing wallet: {e:?}");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let rpc_client = RpcClient::new(endpoint, ex).await?;
|
||||||
|
Ok(Self { wallet, rpc_client })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auxilliary function to ping configured darkfid daemon for liveness.
|
||||||
|
async fn ping(&self) -> Result<()> {
|
||||||
|
eprintln!("Executing ping request to darkfid...");
|
||||||
|
let latency = Instant::now();
|
||||||
|
let req = JsonRequest::new("ping", JsonValue::Array(vec![]));
|
||||||
|
let rep = self.rpc_client.oneshot_request(req).await?;
|
||||||
|
let latency = latency.elapsed();
|
||||||
|
eprintln!("Got reply: {:?}", rep);
|
||||||
|
eprintln!("Latency: {:?}", latency);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async_daemonize!(realmain);
|
||||||
|
async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
|
||||||
|
match args.command {
|
||||||
|
Subcmd::Kaching => {
|
||||||
|
kaching().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
Subcmd::Ping => {
|
||||||
|
let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
|
||||||
|
drk.ping().await
|
||||||
|
}
|
||||||
|
|
||||||
|
Subcmd::Wallet {
|
||||||
|
initialize,
|
||||||
|
keygen,
|
||||||
|
balance,
|
||||||
|
address,
|
||||||
|
secrets,
|
||||||
|
import_secrets,
|
||||||
|
tree,
|
||||||
|
coins,
|
||||||
|
} => {
|
||||||
|
if !initialize &&
|
||||||
|
!keygen &&
|
||||||
|
!balance &&
|
||||||
|
!address &&
|
||||||
|
!secrets &&
|
||||||
|
!tree &&
|
||||||
|
!coins &&
|
||||||
|
!import_secrets
|
||||||
|
{
|
||||||
|
eprintln!("Error: You must use at least one flag for this subcommand");
|
||||||
|
eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
213
bin/drk2/src/walletdb.rs
Normal file
213
bin/drk2/src/walletdb.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/* This file is part of DarkFi (https://dark.fi)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020-2024 Dyne.org foundation
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use log::{debug, error, info};
|
||||||
|
use rusqlite::{
|
||||||
|
types::{ToSql, Value},
|
||||||
|
Connection,
|
||||||
|
};
|
||||||
|
use smol::lock::Mutex;
|
||||||
|
|
||||||
|
use crate::error::{WalletDbError, WalletDbResult};
|
||||||
|
|
||||||
|
pub type WalletPtr = Arc<WalletDb>;
|
||||||
|
|
||||||
|
/// Structure representing base wallet database operations.
|
||||||
|
pub struct WalletDb {
|
||||||
|
/// Connection to the SQLite database
|
||||||
|
pub conn: Mutex<Connection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WalletDb {
|
||||||
|
/// Create a new wallet database handler. If `path` is `None`, create it in memory.
|
||||||
|
pub fn new(path: Option<PathBuf>, password: Option<&str>) -> WalletDbResult<WalletPtr> {
|
||||||
|
let Ok(conn) = (match path.clone() {
|
||||||
|
Some(p) => Connection::open(p),
|
||||||
|
None => Connection::open_in_memory(),
|
||||||
|
}) else {
|
||||||
|
return Err(WalletDbError::ConnectionFailed);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(password) = password {
|
||||||
|
if let Err(e) = conn.pragma_update(None, "key", password) {
|
||||||
|
error!(target: "walletdb::new", "[WalletDb] Pragma update failed: {e}");
|
||||||
|
return Err(WalletDbError::PragmaUpdateError);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if let Err(e) = conn.pragma_update(None, "foreign_keys", "ON") {
|
||||||
|
error!(target: "walletdb::new", "[WalletDb] Pragma update failed: {e}");
|
||||||
|
return Err(WalletDbError::PragmaUpdateError);
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(target: "walletdb::new", "[WalletDb] Opened Sqlite connection at \"{path:?}\"");
|
||||||
|
Ok(Arc::new(Self { conn: Mutex::new(conn) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub async fn exec_sql(&self, query: &str, params: &[&dyn ToSql]) -> WalletDbResult<()> {
|
||||||
|
debug!(target: "walletdb::exec_sql", "[WalletDb] Executing SQL query:\n{query}");
|
||||||
|
// If no params are provided, execute directly
|
||||||
|
if params.is_empty() {
|
||||||
|
if let Err(e) = self.conn.lock().await.execute(query, ()) {
|
||||||
|
error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
|
||||||
|
return Err(WalletDbError::QueryExecutionFailed)
|
||||||
|
};
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// First we prepare the query
|
||||||
|
let conn = self.conn.lock().await;
|
||||||
|
let Ok(mut stmt) = conn.prepare(query) else {
|
||||||
|
return Err(WalletDbError::QueryPreparationFailed)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute the query using provided params
|
||||||
|
if let Err(e) = stmt.execute(params) {
|
||||||
|
error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
|
||||||
|
return Err(WalletDbError::QueryExecutionFailed)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Finalize query and drop connection lock
|
||||||
|
if let Err(e) = stmt.finalize() {
|
||||||
|
error!(target: "walletdb::exec_sql", "[WalletDb] Query finalization failed: {e}");
|
||||||
|
return Err(WalletDbError::QueryFinalizationFailed)
|
||||||
|
};
|
||||||
|
drop(conn);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query provided table from selected column names and provided `WHERE` clauses.
|
||||||
|
/// Named parameters are supported in the `WHERE` clauses, assuming they follow the
|
||||||
|
/// normal formatting ":{column_name}"
|
||||||
|
pub async fn query_single(
|
||||||
|
&self,
|
||||||
|
table: &str,
|
||||||
|
col_names: Vec<&str>,
|
||||||
|
params: &[(&str, &dyn ToSql)],
|
||||||
|
) -> WalletDbResult<Vec<Value>> {
|
||||||
|
// Generate `SELECT` query
|
||||||
|
let mut query = format!("SELECT {} FROM {}", col_names.join(", "), table);
|
||||||
|
if !params.is_empty() {
|
||||||
|
let mut where_str = Vec::with_capacity(params.len());
|
||||||
|
for (k, _) in params {
|
||||||
|
let col = &k[1..];
|
||||||
|
where_str.push(format!("{col} = {k}"));
|
||||||
|
}
|
||||||
|
query.push_str(&format!(" WHERE {}", where_str.join(" AND ")));
|
||||||
|
};
|
||||||
|
debug!(target: "walletdb::query_single", "[WalletDb] Executing SQL query:\n{query}");
|
||||||
|
|
||||||
|
// First we prepare the query
|
||||||
|
let conn = self.conn.lock().await;
|
||||||
|
let Ok(mut stmt) = conn.prepare(&query) else {
|
||||||
|
return Err(WalletDbError::QueryPreparationFailed)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute the query using provided params
|
||||||
|
let Ok(mut rows) = stmt.query(params) else {
|
||||||
|
return Err(WalletDbError::QueryExecutionFailed)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if row exists
|
||||||
|
let Ok(next) = rows.next() else { return Err(WalletDbError::QueryExecutionFailed) };
|
||||||
|
let row = match next {
|
||||||
|
Some(row_result) => row_result,
|
||||||
|
None => return Ok(vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Grab returned values
|
||||||
|
let mut result = vec![];
|
||||||
|
for col in col_names {
|
||||||
|
let Ok(value) = row.get(col) else { return Err(WalletDbError::ParseColumnValueError) };
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use rusqlite::types::Value;
|
||||||
|
|
||||||
|
use crate::walletdb::WalletDb;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mem_wallet() {
|
||||||
|
smol::block_on(async {
|
||||||
|
let wallet = WalletDb::new(None, Some("foobar")).unwrap();
|
||||||
|
wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );", &[]).await.unwrap();
|
||||||
|
wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );", &[]).await.unwrap();
|
||||||
|
|
||||||
|
let ret = wallet.query_single("mista", vec!["numba"], &[]).await.unwrap();
|
||||||
|
assert_eq!(ret.len(), 1);
|
||||||
|
let numba: i64 = if let Value::Integer(numba) = ret[0] { numba } else { -1 };
|
||||||
|
assert_eq!(numba, 42);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_single() {
|
||||||
|
smol::block_on(async {
|
||||||
|
let wallet = WalletDb::new(None, None).unwrap();
|
||||||
|
wallet
|
||||||
|
.exec_sql(
|
||||||
|
"CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let why = 42;
|
||||||
|
let are = "are".to_string();
|
||||||
|
let you = 69;
|
||||||
|
let gae = vec![42u8; 32];
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.exec_sql(
|
||||||
|
"INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
|
||||||
|
rusqlite::params![why, are, you, gae],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let ret =
|
||||||
|
wallet.query_single("mista", vec!["why", "are", "you", "gae"], &[]).await.unwrap();
|
||||||
|
assert_eq!(ret.len(), 4);
|
||||||
|
assert_eq!(ret[0], Value::Integer(why));
|
||||||
|
assert_eq!(ret[1], Value::Text(are.clone()));
|
||||||
|
assert_eq!(ret[2], Value::Integer(you));
|
||||||
|
assert_eq!(ret[3], Value::Blob(gae.clone()));
|
||||||
|
|
||||||
|
let ret = wallet
|
||||||
|
.query_single(
|
||||||
|
"mista",
|
||||||
|
vec!["gae"],
|
||||||
|
rusqlite::named_params! {":why" : why, ":are" : are, ":you" : you},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(ret.len(), 1);
|
||||||
|
assert_eq!(ret[0], Value::Blob(gae));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
bin/drk2/wallet.mp3
Normal file
BIN
bin/drk2/wallet.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user