mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-07 22:04:03 -05:00
drk: drk rewritte skeleton added
This commit is contained in:
@@ -24,6 +24,7 @@ members = [
|
||||
"bin/darkfid2",
|
||||
"bin/darkfi-mmproxy",
|
||||
#"bin/drk",
|
||||
#"bin/drk2",
|
||||
#"bin/faucetd",
|
||||
#"bin/fud/fu",
|
||||
#"bin/fud/fud",
|
||||
|
||||
@@ -146,7 +146,7 @@ impl Darkfid {
|
||||
}
|
||||
|
||||
// RPCAPI:
|
||||
// Pings configured miner daemon for livenes.
|
||||
// Pings configured miner daemon for liveness.
|
||||
// Returns `true` on success.
|
||||
//
|
||||
// --> {"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