This commit is contained in:
Hanh 2022-12-30 07:03:35 +08:00
parent 4cfb0b30d6
commit 97ae66956d
5 changed files with 2231 additions and 4 deletions

View File

@ -1,4 +1,3 @@
#ifndef __APPLE__
typedef char int8_t;
typedef unsigned char uint8_t;
typedef short int uint16_t;
@ -10,9 +9,9 @@ typedef long int uint32_t;
#ifndef __cplusplus
typedef char bool;
#endif
#endif
typedef void *DartPostCObjectFnType;
#define QR_DATA_SIZE 256
#define MAX_ATTEMPTS 10
@ -22,29 +21,99 @@ typedef void *DartPostCObjectFnType;
typedef struct CResult_u8 {
uint8_t value;
char *error;
uint32_t len;
} CResult_u8;
typedef struct CResult_u32 {
uint32_t value;
char *error;
uint32_t len;
} CResult_u32;
typedef struct CResult_____c_char {
char *value;
char *error;
uint32_t len;
} CResult_____c_char;
typedef struct CResult_u64 {
uint64_t value;
char *error;
uint32_t len;
} CResult_u64;
typedef struct CResult______u8 {
const uint8_t *value;
char *error;
uint32_t len;
} CResult______u8;
#define Account_VT_ID 4
#define Account_VT_NAME 6
#define Account_VT_BALANCE 8
#define AccountVec_VT_ACCOUNTS 4
#define Balance_VT_SHIELDED 4
#define Balance_VT_UNCONFIRMED_SPENT 6
#define Balance_VT_UNDER_CONFIRMED 10
#define Balance_VT_EXCLUDED 12
#define Balance_VT_SAPLING 14
#define Balance_VT_ORCHARD 16
#define Height_VT_HEIGHT 4
#define Height_VT_TIMESTAMP 6
#define ShieldedNote_VT_VALUE 8
#define ShieldedNote_VT_SPENT 16
#define ShieldedNoteVec_VT_NOTES 4
#define AdressbookEntry_VT_ID_ADDRESS 4
#define AdressbookEntry_VT_ADDRESS 8
#define Addressbook_VT_CONTACTS 6
#define AccountBalance_VT_COIN 4
#define AccountBalance_VT_ID_ACCOUNT 6
#define AccountBalance_VT_TBALANCE 12
#define ZMessage_VT_TX_ID 6
#define ZMessage_VT_INCOMING 8
#define ZMessage_VT_SENDER 10
#define ZMessage_VT_RECIPIENT 12
#define ZMessage_VT_SUBJECT 14
#define ZMessage_VT_BODY 16
#define ZMessage_VT_READ 22
#define ZMessages_VT_MESSAGES 4
void dummy_export(void);
void dart_post_cobject(DartPostCObjectFnType ptr);
void deallocate_str(char *s);
void deallocate_bytes(uint8_t *ptr, uint32_t len);
struct CResult_u8 init_wallet(uint8_t coin, char *db_path);
struct CResult_u8 migrate_db(uint8_t coin, char *db_path);
@ -184,6 +253,22 @@ struct CResult_u32 save_send_template(uint8_t coin, char *template_);
struct CResult_u8 delete_send_template(uint8_t coin, uint32_t id);
struct CResult______u8 get_account_list(uint8_t coin);
struct CResult_u32 get_available_account_id(uint8_t coin, uint32_t id);
struct CResult_____c_char get_t_addr(uint8_t coin, uint32_t id);
struct CResult_____c_char get_sk(uint8_t coin, uint32_t id);
struct CResult_u8 update_account_name(uint8_t coin, uint32_t id, char *name);
struct CResult______u8 get_balances(uint8_t coin, uint32_t id, uint32_t confirmed_height);
struct CResult______u8 get_db_height(uint8_t coin);
struct CResult______u8 get_notes(uint8_t coin, uint32_t id);
bool has_cuda(void);
bool has_metal(void);

View File

@ -1,7 +1,7 @@
use crate::coinconfig::{init_coin, CoinConfig, MEMPOOL, MEMPOOL_RUNNER};
use crate::db::FullEncryptedBackup;
use crate::note_selection::TransactionReport;
use crate::{ChainError, TransactionPlan, Tx};
use crate::{ChainError, DbAdapter, TransactionPlan, Tx};
use allo_isolate::{ffi, IntoDart};
use android_logger::Config;
use lazy_static::lazy_static;
@ -9,7 +9,8 @@ use log::Level;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::Path;
use std::sync::Mutex;
use std::sync::{Mutex, MutexGuard};
use rusqlite::Connection;
use tokio::sync::Semaphore;
use zcash_primitives::transaction::builder::Progress;
use crate::template::SendTemplate;
@ -41,12 +42,14 @@ fn to_cresult<T>(res: Result<T, anyhow::Error>) -> CResult<T> {
match res {
Ok(v) => CResult {
value: v,
len: 0,
error: std::ptr::null_mut::<c_char>(),
},
Err(e) => {
log::error!("{}", e);
CResult {
value: unsafe { std::mem::zeroed() },
len: 0,
error: to_c_str(e),
}
}
@ -64,11 +67,39 @@ fn log_error(res: Result<(), anyhow::Error>) {
}
}
fn to_cresult_bytes(res: Result<Vec<u8>, anyhow::Error>) -> CResult<*const u8> {
match res {
Ok(v) => {
let ptr = v.as_ptr();
let len = v.len();
std::mem::forget(v);
CResult {
value: ptr,
len: len as u32,
error: std::ptr::null_mut::<c_char>(),
}
},
Err(e) => {
log::error!("{}", e);
CResult {
value: unsafe { std::mem::zeroed() },
len: 0,
error: to_c_str(e.to_string()),
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn deallocate_str(s: *mut c_char) {
let _ = CString::from_raw(s);
}
#[no_mangle]
pub unsafe extern "C" fn deallocate_bytes(ptr: *mut u8, len: u32) {
drop(Vec::from_raw_parts(ptr, len as usize, len as usize));
}
fn try_init_logger() {
android_logger::init_once(
Config::default()
@ -90,6 +121,7 @@ fn try_init_logger() {
pub struct CResult<T> {
value: T,
error: *mut c_char,
pub len: u32,
}
#[no_mangle]
@ -796,6 +828,86 @@ pub unsafe extern "C" fn delete_send_template(coin: u8, id: u32) -> CResult<u8>
to_cresult(res())
}
fn with_account<T, F: Fn(u8, u32, &Connection) -> anyhow::Result<T>>(coin: u8, id: u32, f: F) -> anyhow::Result<T> {
let c = CoinConfig::get(coin);
let db = c.db()?;
let connection = &db.connection;
f(coin, id, connection)
}
#[no_mangle]
pub unsafe extern "C" fn get_account_list(coin: u8) -> CResult<*const u8> {
let res = |coin: u8, id: u32, connection: &Connection| {
let accounts = crate::db::read::get_account_list(connection)?;
Ok(accounts)
};
to_cresult_bytes(with_account(coin, 0, res))
}
#[no_mangle]
pub unsafe extern "C" fn get_available_account_id(coin: u8, id: u32) -> CResult<u32> {
let res = |coin: u8, id: u32, connection: &Connection| {
let new_id = crate::db::read::get_available_account_id(connection, id)?;
Ok(new_id)
};
to_cresult(with_account(coin, id, res))
}
#[no_mangle]
pub unsafe extern "C" fn get_t_addr(coin: u8, id: u32) -> CResult<*mut c_char> {
let res = |coin: u8, id: u32, connection: &Connection| {
let address = crate::db::read::get_t_addr(connection, id)?;
Ok(address)
};
to_cresult_str(with_account(coin, id, res))
}
#[no_mangle]
pub unsafe extern "C" fn get_sk(coin: u8, id: u32) -> CResult<*mut c_char> {
let res = |coin: u8, id: u32, connection: &Connection| {
let sk = crate::db::read::get_sk(connection, id)?;
Ok(sk)
};
to_cresult_str(with_account(coin, id, res))
}
#[no_mangle]
pub unsafe extern "C" fn update_account_name(coin: u8, id: u32, name: *mut c_char) -> CResult<u8> {
from_c_str!(name);
let res = |coin: u8, id: u32, connection: &Connection| {
crate::db::read::update_account_name(connection, id, &name)?;
Ok(0)
};
to_cresult(with_account(coin, id, res))
}
#[no_mangle]
pub unsafe extern "C" fn get_balances(coin: u8, id: u32, confirmed_height: u32) -> CResult<*const u8> {
let res = |coin: u8, id: u32, connection: &Connection| {
let data = crate::db::read::get_balances(connection, id, confirmed_height)?;
Ok(data)
};
to_cresult_bytes(with_account(coin, id, res))
}
#[no_mangle]
pub unsafe extern "C" fn get_db_height(coin: u8) -> CResult<*const u8> {
let res = |coin: u8, _id: u32, connection: &Connection| {
let data = crate::db::read::get_db_height(connection)?;
Ok(data)
};
to_cresult_bytes(with_account(coin, 0, res))
}
#[no_mangle]
pub unsafe extern "C" fn get_notes(coin: u8, id: u32) -> CResult<*const u8> {
let res = |coin: u8, id: u32, connection: &Connection| {
let data = crate::db::read::get_notes(connection, id)?;
Ok(data)
};
to_cresult_bytes(with_account(coin, id, res))
}
#[no_mangle]
pub unsafe extern "C" fn has_cuda() -> bool {
crate::gpu::has_cuda()

View File

@ -26,6 +26,8 @@ use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey};
mod backup;
mod migration;
pub mod data_generated;
pub mod read;
pub use backup::FullEncryptedBackup;
use crate::template::SendTemplate;

1713
src/db/data_generated.rs Normal file

File diff suppressed because it is too large Load Diff

315
src/db/read.rs Normal file
View File

@ -0,0 +1,315 @@
use rusqlite::{Connection, OptionalExtension, params};
use anyhow::Result;
use crate::db::data_generated::fb::*;
use crate::DbAdapter;
pub fn has_account(connection: &Connection) -> Result<bool> {
let res = connection.query_row("SELECT 1 FROM accounts", [], |_| { Ok(()) }).optional()?;
Ok(res.is_some())
}
pub fn get_account_list(connection: &Connection) -> Result<Vec<u8>> {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let mut stmt = connection.prepare("WITH notes AS (SELECT a.id_account, a.name, CASE WHEN r.spent IS NULL THEN r.value ELSE 0 END AS nv FROM accounts a LEFT JOIN received_notes r ON a.id_account = r.account), \
accounts2 AS (SELECT id_account, name, COALESCE(sum(nv), 0) AS balance FROM notes GROUP by id_account) \
SELECT a.id_account, a.name, a.balance FROM accounts2 a")?;
let rows = stmt.query_map([], |row| {
let id: u32 = row.get("id_account")?;
let name: String = row.get("name")?;
let balance: i64 = row.get("balance")?;
let name = builder.create_string(&name);
let account = Account::create(&mut builder, &AccountArgs {
id,
name: Some(name),
balance: balance as u64,
});
Ok(account)
})?;
let mut accounts = vec![];
for r in rows {
accounts.push(r?);
}
let accounts = builder.create_vector(&accounts);
let accounts = AccountVec::create(&mut builder, &AccountVecArgs { accounts: Some(accounts) });
builder.finish(accounts, None);
let data = builder.finished_data().to_vec();
Ok(data)
}
pub fn get_available_account_id(connection: &Connection, id: u32) -> Result<u32> {
let r = connection.query_row("SELECT 1 FROM accounts WHERE id_account = ?1", [id], |_| { Ok(()) }).optional()?;
if r.is_some() { return Ok(id) }
let r = connection.query_row("SELECT MAX(id_account) FROM accounts", [], |row| {
let id: Option<u32> = row.get(0)?;
Ok(id)
})?.unwrap_or(0);
Ok(r)
}
pub fn get_t_addr(connection: &Connection, id: u32) -> Result<String> {
let address = connection.query_row("SELECT address FROM taddrs WHERE account = ?1", [id], |row| {
let address: String = row.get(0)?;
Ok(address)
})?;
Ok(address)
}
pub fn get_sk(connection: &Connection, id: u32) -> Result<String> {
let sk = connection.query_row("SELECT sk FROM accounts WHERE id_account = ?1", [id], |row| {
let sk: Option<String> = row.get(0)?;
Ok(sk.unwrap_or(String::new()))
})?;
Ok(sk)
}
pub fn update_account_name(connection: &Connection, id: u32, name: &str) -> Result<()> {
connection.execute("UPDATE accounts SET name = ?2 WHERE id_account = ?1", params![id, name])?;
Ok(())
}
pub fn get_balances(connection: &Connection, id: u32, confirmed_height: u32) -> Result<Vec<u8>> {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let shielded = connection.query_row(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL",
params![id], |row| {
let value: Option<i64> = row.get(0)?;
Ok(value.unwrap_or(0) as u64)
})?; // funds not spent yet
let unconfirmed_spent = connection.query_row(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent = 0",
params![id], |row| {
let value: Option<i64> = row.get(0)?;
Ok(value.unwrap_or(0) as u64)
})?; // funds used in unconfirmed tx
let balance = shielded + unconfirmed_spent;
let under_confirmed = connection.query_row(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL AND height > ?2",
params![id, confirmed_height], |row| {
let value: Option<i64> = row.get(0)?;
Ok(value.unwrap_or(0) as u64)
})?; // funds received but not old enough
let excluded = connection.query_row(
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL \
AND height <= ?2 AND excluded",
params![id, confirmed_height], |row| {
let value: Option<i64> = row.get(0)?;
Ok(value.unwrap_or(0) as u64)
})?; // funds excluded from spending
let sapling = connection.query_row(
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL AND orchard = 0 AND height <= ?2",
params![id, confirmed_height], |row| {
let value: Option<i64> = row.get(0)?;
Ok(value.unwrap_or(0) as u64)
})?;
let orchard = connection.query_row(
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL AND orchard = 1 AND height <= ?2",
params![id, confirmed_height], |row| {
let value: Option<i64> = row.get(0)?;
Ok(value.unwrap_or(0) as u64)
})?;
let balance = Balance::create(&mut builder, &BalanceArgs {
shielded,
unconfirmed_spent,
balance,
under_confirmed,
excluded,
sapling,
orchard
});
builder.finish(balance, None);
let data = builder.finished_data().to_vec();
Ok(data)
}
pub fn get_db_height(connection: &Connection) -> Result<Vec<u8>> {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let height = connection.query_row(
"SELECT height, timestamp FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)",
[], |row| {
let height: u32 = row.get(0)?;
let timestamp: u32 = row.get(1)?;
let height = Height::create(&mut builder, &HeightArgs {
height,
timestamp,
});
Ok(height)
}).optional()?;
let data = height.map(|h| {
builder.finish(h, None);
builder.finished_data().to_vec()
}).unwrap_or(vec![]);
Ok(data)
}
pub fn get_notes(connection: &Connection, id: u32) -> Result<Vec<u8>> {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let mut stmt = connection.prepare(
"SELECT n.id_note, n.height, n.value, t.timestamp, n.orchard, n.excluded, n.spent FROM received_notes n, transactions t \
WHERE n.account = ?1 AND (n.spent IS NULL OR n.spent = 0) \
AND n.tx = t.id_tx ORDER BY n.height DESC")?;
let rows = stmt.query_map(params![id], |row| {
let id: u32 = row.get("id_note")?;
let height: u32 = row.get("height")?;
let value: i64 = row.get("value")?;
let timestamp: u32 = row.get("timestamp")?;
let orchard: u8 = row.get("orchard")?;
let excluded: Option<bool> = row.get("excluded")?;
let spent: Option<u32> = row.get("spent")?;
let note = ShieldedNote::create(&mut builder, &ShieldedNoteArgs {
id,
height,
value: value as u64,
timestamp,
orchard: orchard == 1,
excluded: excluded.unwrap_or(false),
spent: spent.is_some()
});
Ok(note)
})?;
let mut notes = vec![];
for r in rows {
notes.push(r?);
}
let notes = builder.create_vector(&notes);
let notes = ShieldedNoteVec::create(&mut builder, &ShieldedNoteVecArgs { notes: Some(notes) });
builder.finish(notes, None);
let data = builder.finished_data().to_vec();
Ok(data)
}
/* final id = row['id_note'];
final height = row['height'];
final timestamp = DateTime.fromMillisecondsSinceEpoch(row['timestamp'] * 1000);
final orchard = row['orchard'] != 0;
final excluded = (row['excluded'] ?? 0) != 0;
final spent = row['spent'] == 0;
final rows = await db.rawQuery("SELECT height, timestamp FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)");
if (rows.isNotEmpty) {
final row = rows.first;
final height = row['height'] as int;
final timestampEpoch = row['timestamp'] as int;
final timestamp = DateTime.fromMillisecondsSinceEpoch(timestampEpoch * 1000);
final blockInfo = BlockInfo(height, timestamp);
return blockInfo;
}
return null;
return Sqflite.firstIntValue(await db.rawQuery("SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL AND orchard = 0",
[id])) ?? 0;
}
Future<int> getOrchardBalance() async {
return Sqflite.firstIntValue(await db.rawQuery("SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL AND orchard = 1",
[id])) ?? 0;
final balance = Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND (spent IS NULL OR spent = 0)",
[id])) ?? 0;
final shieldedBalance = Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL",
[id])) ?? 0;
final unconfirmedSpentBalance = Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent = 0",
[id])) ?? 0;
final underConfirmedBalance = Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL AND height > ?2",
[id, confirmHeight])) ?? 0;
final excludedBalance = Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL "
"AND height <= ?2 AND excluded",
[id, confirmHeight])) ?? 0;
"", [id]);
await db.execute("UPDATE accounts SET name = ?2 WHERE id_account = ?1",
final List<Map> res1 = await db.rawQuery(
"SELECT address FROM taddrs WHERE account = ?1", [id]);
final taddress = res1.isNotEmpty ? res1[0]['address'] : "";
return taddress;
// check that the account still exists
// if not, pick any account
// if there are none, return 0
Future<AccountId?> getAvailableAccountId() async {
final List<Map> res1 = await db.rawQuery(
"SELECT 1 FROM accounts WHERE id_account = ?1", [id]);
if (res1.isNotEmpty)
return AccountId(coin, id);
final List<Map> res2 = await db.rawQuery(
"SELECT id_account FROM accounts", []);
if (res2.isNotEmpty) {
final id = res2[0]['id_account'];
return AccountId(coin, id);
}
return null;
}
Future<List<Account>> getAccountList() async {
List<Account> accounts = [];
final List<Map> res = await db.rawQuery(
"WITH notes AS (SELECT a.id_account, a.name, CASE WHEN r.spent IS NULL THEN r.value ELSE 0 END AS nv FROM accounts a LEFT JOIN received_notes r ON a.id_account = r.account),"
"accounts2 AS (SELECT id_account, name, COALESCE(sum(nv), 0) AS balance FROM notes GROUP by id_account) "
"SELECT a.id_account, a.name, a.balance FROM accounts2 a",
[]);
for (var r in res) {
final int id = r['id_account'];
final account = Account(
coin,
id,
r['name'],
r['balance'],
0,
null);
accounts.add(account);
}
return accounts;
}
// check that the account still exists
// if not, pick any account
// if there are none, return 0
Future<AccountId?> getAvailableAccountId() async {
final List<Map> res1 = await db.rawQuery(
"SELECT 1 FROM accounts WHERE id_account = ?1", [id]);
if (res1.isNotEmpty)
return AccountId(coin, id);
final List<Map> res2 = await db.rawQuery(
"SELECT id_account FROM accounts", []);
if (res2.isNotEmpty) {
final id = res2[0]['id_account'];
return AccountId(coin, id);
}
return null;
}
Future<String> getTAddr() async {
final List<Map> res1 = await db.rawQuery(
"SELECT address FROM taddrs WHERE account = ?1", [id]);
final taddress = res1.isNotEmpty ? res1[0]['address'] : "";
return taddress;
}
Future<String?> getSK() async {
final List<Map> res1 = await db.rawQuery(
"SELECT sk FROM accounts WHERE id_account = ?1", [id]);
final sk = res1.isNotEmpty ? res1[0]['address'] : null;
return sk;
}
Future<void> changeAccountName(String name) async {
await db.execute("UPDATE accounts SET name = ?2 WHERE id_account = ?1",
[id, name]);
}
*/