Refactor Dart FFI

This commit is contained in:
Hanh 2022-06-08 20:48:16 +08:00
parent 6c5e8a6da7
commit 7827f21f98
34 changed files with 1672 additions and 254 deletions

View File

@ -10,17 +10,21 @@ edition = "2018"
name = "scan_all" name = "scan_all"
harness = false harness = false
[[bin]] #[[bin]]
name = "warp-cli" #name = "warp-cli"
path = "src/main/warp_cli.rs" #path = "src/main/warp_cli.rs"
#[[bin]] #[[bin]]
#name = "ledger" #name = "ledger"
#path = "src/main/ledger.rs" #path = "src/main/ledger.rs"
[[bin]] #[[bin]]
name = "sign" #name = "sign"
path = "src/main/sign.rs" #path = "src/main/sign.rs"
[lib]
name = "warp_api_ffi"
crate-type = ["rlib"]
[dependencies] [dependencies]
dotenv = "0.15.0" dotenv = "0.15.0"
@ -70,9 +74,14 @@ hmac = { version = "0.12.1", optional = true }
ed25519-bip32 = { version = "0.4.1", optional = true } ed25519-bip32 = { version = "0.4.1", optional = true }
ledger-transport-hid = { version = "0.9", optional = true } ledger-transport-hid = { version = "0.9", optional = true }
allo-isolate = { version = "0.1" }
once_cell = { version = "1.8.0" }
android_logger = { version = "0.10.0" }
[features] [features]
ledger = ["ledger-apdu", "hmac", "ed25519-bip32", "ledger-transport-hid"] ledger = ["ledger-apdu", "hmac", "ed25519-bip32", "ledger-transport-hid"]
ledger_sapling = ["ledger"] ledger_sapling = ["ledger"]
# dart_ffi = ["allo-isolate", "once_cell", "android_logger"]
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd # librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
@ -106,6 +115,7 @@ rev = "466806932d21597eb4f89a449347fa1983dffb22"
[build-dependencies] [build-dependencies]
tonic-build = "0.7.2" tonic-build = "0.7.2"
cbindgen = "0.19.0"
[dev-dependencies] [dev-dependencies]
criterion = "0.3.4" criterion = "0.3.4"

87
binding.h Normal file
View File

@ -0,0 +1,87 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
void dart_post_cobject(DartPostCObjectFnType ptr);
void init_wallet(char *db_path);
void set_active(uint8_t active);
void set_active_account(uint8_t coin, uint32_t id);
void set_coin_lwd_url(uint8_t coin, char *lwd_url);
void reset_app(void);
uint32_t new_account(uint8_t coin, char *name, char *data, int32_t index);
uint32_t new_sub_account(char *name, int32_t index);
uint8_t warp(uint8_t coin, bool get_tx, uint32_t anchor_offset, int64_t port);
int8_t is_valid_key(uint8_t coin, char *key);
bool valid_address(uint8_t coin, char *address);
char *new_diversified_address(void);
uint32_t get_latest_height(void);
char *send_multi_payment(char *recipients_json,
bool use_transparent,
uint32_t anchor_offset,
int64_t port);
void skip_to_last_height(uint8_t coin);
void rewind_to_height(uint32_t height);
int64_t mempool_sync(void);
void mempool_reset(void);
int64_t get_mempool_balance(void);
uint64_t get_taddr_balance(uint8_t coin, uint32_t id_account);
char *shield_taddr(void);
void scan_transparent_accounts(uint32_t gap_limit);
char *prepare_multi_payment(char *recipients_json, bool use_transparent, uint32_t anchor_offset);
char *sign(char *tx_filename, int64_t port);
char *broadcast(char *tx_filename);
char *broadcast_txhex(char *txhex);
uint32_t get_activation_date(void);
uint32_t get_block_by_time(uint32_t time);
uint32_t sync_historical_prices(int64_t now, uint32_t days, char *currency);
void store_contact(uint32_t id, char *name, char *address, bool dirty);
char *commit_unsaved_contacts(uint32_t anchor_offset);
void mark_message_read(uint32_t message, bool read);
void mark_all_messages_read(bool read);
void truncate_data(void);
void delete_account(uint8_t coin, uint32_t account);
char *make_payment_uri(char *address, uint64_t amount, char *memo);
char *parse_payment_uri(char *uri);
char *generate_random_enc_key(void);
char *get_full_backup(char *key);
char *restore_full_backup(char *key, char *backup);

View File

@ -6,4 +6,22 @@ fn main() {
&["proto"], &["proto"],
) )
.unwrap(); .unwrap();
create_c_bindings();
}
#[allow(dead_code)]
fn create_c_bindings() {
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let config = cbindgen::Config {
language: cbindgen::Language::C,
..Default::default()
};
cbindgen::Builder::new()
.with_crate(crate_dir)
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file("binding.h");
} }

11
src/api.rs Normal file
View File

@ -0,0 +1,11 @@
pub mod account;
pub mod contact;
pub mod fullbackup;
pub mod historical_prices;
pub mod mempool;
pub mod message;
pub mod payment;
pub mod payment_uri;
pub mod sync;
pub mod dart_ffi;

133
src/api/account.rs Normal file
View File

@ -0,0 +1,133 @@
// Account creation
use crate::coinconfig::{CoinConfig, ACTIVE_COIN};
use crate::key2::decode_key;
use anyhow::anyhow;
use bip39::{Language, Mnemonic};
use rand::rngs::OsRng;
use rand::RngCore;
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, encode_payment_address};
use zcash_primitives::consensus::Parameters;
pub fn new_account(
coin: u8,
name: &str,
key: Option<String>,
index: Option<u32>,
) -> anyhow::Result<u32> {
let key = match key {
Some(key) => key,
None => {
let mut entropy = [0u8; 32];
OsRng.fill_bytes(&mut entropy);
let mnemonic = Mnemonic::from_entropy(&entropy, Language::English)?;
mnemonic.phrase().to_string()
}
};
let id_account = new_account_with_key(coin, name, &key, index.unwrap_or(0))?;
Ok(id_account)
}
pub fn new_sub_account(name: &str, index: Option<u32>) -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let db = c.db()?;
let (seed, _) = db.get_seed(c.id_account)?;
let seed = seed.ok_or_else(|| anyhow!("Account has no seed"))?;
let index = index.unwrap_or_else(|| db.next_account_id(&seed).unwrap());
drop(db);
let id_account = new_account_with_key(c.coin, name, &seed, index)?;
Ok(id_account)
}
fn new_account_with_key(coin: u8, name: &str, key: &str, index: u32) -> anyhow::Result<u32> {
let c = CoinConfig::get(coin);
let (seed, sk, ivk, pa) = decode_key(coin, key, index)?;
let db = c.db()?;
let (account, exists) =
db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?;
if !exists {
db.create_taddr(account)?;
}
Ok(account)
}
pub fn new_diversified_address() -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let db = c.db()?;
let ivk = db.get_ivk(c.id_account)?;
let fvk = decode_extended_full_viewing_key(
c.chain.network().hrp_sapling_extended_full_viewing_key(),
&ivk,
)?
.unwrap();
let mut diversifier_index = db.get_diversifier(c.id_account)?;
diversifier_index.increment().unwrap();
let (new_diversifier_index, pa) = fvk
.find_address(diversifier_index)
.ok_or_else(|| anyhow::anyhow!("Cannot generate new address"))?;
db.store_diversifier(c.id_account, &new_diversifier_index)?;
let pa = encode_payment_address(c.chain.network().hrp_sapling_payment_address(), &pa);
Ok(pa)
}
pub async fn get_taddr_balance_default() -> anyhow::Result<u64> {
let c = CoinConfig::get_active();
get_taddr_balance(c.coin, c.id_account).await
}
pub async fn get_taddr_balance(coin: u8, id_account: u32) -> anyhow::Result<u64> {
let c = CoinConfig::get(coin);
let mut client = c.connect_lwd().await?;
let address = c.db()?.get_taddr(id_account)?;
let balance = match address {
None => 0u64,
Some(address) => crate::taddr::get_taddr_balance(&mut client, &address).await?,
};
Ok(balance)
}
pub async fn scan_transparent_accounts(gap_limit: usize) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
crate::taddr::scan_transparent_accounts(c.chain.network(), &mut client, gap_limit).await?;
Ok(())
}
// Account backup
pub fn get_backup(account: u32) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let (seed, sk, ivk) = c.db()?.get_backup(account)?;
if let Some(seed) = seed {
return Ok(seed);
}
if let Some(sk) = sk {
return Ok(sk);
}
Ok(ivk)
}
pub fn get_sk(account: u32) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let sk = c.db()?.get_sk(account)?;
Ok(sk)
}
pub fn reset_db(coin: u8) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
db.reset_db()
}
pub fn truncate_data() -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let db = c.db()?;
db.truncate_data()
}
pub fn delete_account(coin: u8, account: u32) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
db.delete_account(account)?;
Ok(())
}

49
src/api/contact.rs Normal file
View File

@ -0,0 +1,49 @@
use crate::api::payment::{build_sign_send_multi_payment, RecipientMemo};
use crate::api::sync::get_latest_height;
use crate::coinconfig::CoinConfig;
use crate::contact::{serialize_contacts, Contact};
use zcash_primitives::memo::Memo;
pub fn store_contact(id: u32, name: &str, address: &str, dirty: bool) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let contact = Contact {
id,
name: name.to_string(),
address: address.to_string(),
};
c.db()?.store_contact(&contact, dirty)?;
Ok(())
}
pub async fn commit_unsaved_contacts(anchor_offset: u32) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let contacts = c.db()?.get_unsaved_contacts()?;
let memos = serialize_contacts(&contacts)?;
let tx_id = save_contacts_tx(&memos, anchor_offset).await?;
Ok(tx_id)
}
pub async fn save_contacts_tx(memos: &[Memo], anchor_offset: u32) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let last_height = get_latest_height().await?;
let address = c.db()?.get_address(c.id_account)?;
let recipients: Vec<_> = memos
.iter()
.map(|m| RecipientMemo {
address: address.clone(),
amount: 0,
memo: m.clone(),
max_amount_per_note: 0,
})
.collect();
let tx_id = build_sign_send_multi_payment(
last_height,
&recipients,
false,
anchor_offset,
Box::new(|_| {}),
)
.await?;
Ok(tx_id)
}

506
src/api/dart_ffi.rs Normal file
View File

@ -0,0 +1,506 @@
use crate::coinconfig::{init_coin, CoinConfig};
use crate::{broadcast_tx, ChainError};
use allo_isolate::{ffi, IntoDart};
use android_logger::Config;
use lazy_static::lazy_static;
use log::Level;
use std::ffi::{CStr, CString};
use std::io::Read;
use std::os::raw::c_char;
use std::path::Path;
use std::sync::Mutex;
use zcash_primitives::transaction::builder::Progress;
static mut POST_COBJ: Option<ffi::DartPostCObjectFnType> = None;
#[no_mangle]
pub unsafe extern "C" fn dart_post_cobject(ptr: ffi::DartPostCObjectFnType) {
POST_COBJ = Some(ptr);
}
macro_rules! from_c_str {
($v: ident) => {
let $v = CStr::from_ptr($v).to_string_lossy();
};
}
fn to_c_str(s: String) -> *mut c_char {
CString::new(s).unwrap().into_raw()
}
fn try_init_logger() {
android_logger::init_once(
Config::default()
// .format(|buf, record| {
// writeln!(
// buf,
// "{:?}-{:?}: {}",
// record.file(),
// record.line(),
// record.args()
// )
// })
.with_min_level(Level::Info),
);
}
fn log_result<T: Default>(result: anyhow::Result<T>) -> T {
match result {
Err(err) => {
log::error!("{}", err);
T::default()
}
Ok(v) => v,
}
}
fn log_string(result: anyhow::Result<String>) -> String {
match result {
Err(err) => {
log::error!("{}", err);
format!("{}", err)
}
Ok(v) => v,
}
}
fn encode_tx_result(res: anyhow::Result<Vec<u8>>) -> Vec<u8> {
let mut v = vec![];
match res {
Ok(raw_tx) => {
v.push(0x00);
v.extend(raw_tx);
}
Err(e) => {
v.push(0x01);
v.extend(e.to_string().as_bytes());
}
}
v
}
#[no_mangle]
pub unsafe extern "C" fn init_wallet(db_path: *mut c_char) {
try_init_logger();
from_c_str!(db_path);
let _ = init_coin(0, &format!("{}/zec.db", &db_path));
let _ = init_coin(1, &format!("{}/yec.db", &db_path));
}
#[no_mangle]
pub unsafe extern "C" fn set_active(active: u8) {
crate::coinconfig::set_active(active);
}
#[no_mangle]
pub unsafe extern "C" fn set_active_account(coin: u8, id: u32) {
crate::coinconfig::set_active_account(coin, id);
}
#[no_mangle]
pub unsafe extern "C" fn set_coin_lwd_url(coin: u8, lwd_url: *mut c_char) {
from_c_str!(lwd_url);
crate::coinconfig::set_coin_lwd_url(coin, &lwd_url);
}
#[no_mangle]
pub unsafe extern "C" fn reset_app() {
let res = || {
crate::api::account::reset_db(0)?;
crate::api::account::reset_db(1)?;
Ok(())
};
log_result(res())
}
#[no_mangle]
pub unsafe extern "C" fn new_account(
coin: u8,
name: *mut c_char,
data: *mut c_char,
index: i32,
) -> u32 {
from_c_str!(name);
from_c_str!(data);
let data = if !data.is_empty() {
Some(data.to_string())
} else {
None
};
let index = if index >= 0 { Some(index as u32) } else { None };
let res = crate::api::account::new_account(coin, &name, data, index);
log_result(res)
}
#[no_mangle]
pub unsafe extern "C" fn new_sub_account(name: *mut c_char, index: i32) -> u32 {
from_c_str!(name);
let index = if index >= 0 { Some(index as u32) } else { None };
let res = crate::api::account::new_sub_account(&name, index);
log_result(res)
}
lazy_static! {
static ref SYNC_LOCK: Mutex<()> = Mutex::new(());
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn warp(coin: u8, get_tx: bool, anchor_offset: u32, port: i64) -> u8 {
let lock = SYNC_LOCK.try_lock();
if let Ok(_) = lock {
let res = async {
log::info!("Sync started");
let result = crate::api::sync::coin_sync(coin, get_tx, anchor_offset, move |height| {
let mut height = height.into_dart();
if port != 0 {
if let Some(p) = POST_COBJ {
p(port, &mut height);
}
}
})
.await;
log::info!("Sync finished");
crate::api::mempool::scan().await?;
match result {
Ok(_) => Ok(0),
Err(err) => {
if let Some(e) = err.downcast_ref::<ChainError>() {
match e {
ChainError::Reorg => Ok(1),
ChainError::Busy => Ok(2),
}
} else {
log::error!("{}", err);
Ok(0xFF)
}
}
}
};
let r = res.await;
log_result(r)
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn is_valid_key(coin: u8, key: *mut c_char) -> i8 {
from_c_str!(key);
crate::key2::is_valid_key(coin, &key)
}
#[no_mangle]
pub unsafe extern "C" fn valid_address(coin: u8, address: *mut c_char) -> bool {
from_c_str!(address);
crate::key2::is_valid_address(coin, &address)
}
#[no_mangle]
pub unsafe extern "C" fn new_diversified_address() -> *mut c_char {
let res = || crate::api::account::new_diversified_address();
to_c_str(log_string(res()))
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn get_latest_height() -> u32 {
let height = crate::api::sync::get_latest_height().await;
log_result(height)
}
fn report_progress(progress: Progress, port: i64) {
if port != 0 {
let progress = match progress.end() {
Some(end) => (progress.cur() * 100 / end) as i32,
None => -(progress.cur() as i32),
};
let mut progress = progress.into_dart();
unsafe {
if let Some(p) = POST_COBJ {
p(port, &mut progress);
}
}
}
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn send_multi_payment(
recipients_json: *mut c_char,
use_transparent: bool,
anchor_offset: u32,
port: i64,
) -> *mut c_char {
from_c_str!(recipients_json);
let res = async move {
let height = crate::api::sync::get_latest_height().await?;
let recipients = crate::api::payment::parse_recipients(&recipients_json)?;
let res = crate::api::payment::build_sign_send_multi_payment(
height,
&recipients,
use_transparent,
anchor_offset,
Box::new(move |progress| {
report_progress(progress, port);
}),
)
.await?;
Ok(res)
};
to_c_str(log_string(res.await))
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn skip_to_last_height(coin: u8) {
let res = crate::api::sync::skip_to_last_height(coin).await;
log_result(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn rewind_to_height(height: u32) {
let res = crate::api::sync::rewind_to_height(height).await;
log_result(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn mempool_sync() -> i64 {
let res = crate::api::mempool::scan().await;
log_result(res)
}
#[no_mangle]
pub unsafe extern "C" fn mempool_reset() {
let c = CoinConfig::get_active();
let mut mempool = c.mempool.lock().unwrap();
log_result(mempool.clear());
}
#[no_mangle]
pub unsafe extern "C" fn get_mempool_balance() -> i64 {
let c = CoinConfig::get_active();
let mempool = c.mempool.lock().unwrap();
mempool.get_unconfirmed_balance()
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn get_taddr_balance(coin: u8, id_account: u32) -> u64 {
let res = if coin == 0xFF {
crate::api::account::get_taddr_balance_default().await
} else {
crate::api::account::get_taddr_balance(coin, id_account).await
};
log_result(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn shield_taddr() -> *mut c_char {
let res = crate::api::payment::shield_taddr().await;
to_c_str(log_string(res))
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn scan_transparent_accounts(gap_limit: u32) {
let res = crate::api::account::scan_transparent_accounts(gap_limit as usize).await;
log_result(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn prepare_multi_payment(
recipients_json: *mut c_char,
use_transparent: bool,
anchor_offset: u32,
) -> *mut c_char {
from_c_str!(recipients_json);
let res = async {
let last_height = crate::api::sync::get_latest_height().await?;
let recipients = crate::api::payment::parse_recipients(&recipients_json)?;
let tx = crate::api::payment::build_only_multi_payment(
last_height,
&recipients,
use_transparent,
anchor_offset,
)
.await?;
Ok(tx)
};
to_c_str(log_string(res.await))
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn sign(tx_filename: *mut c_char, port: i64) -> *mut c_char {
from_c_str!(tx_filename);
let res = async {
let mut file = std::fs::File::open(&tx_filename.to_string())?;
let mut s = String::new();
file.read_to_string(&mut s)?;
let raw_tx = crate::api::payment::sign_only_multi_payment(
&s,
Box::new(move |progress| {
report_progress(progress, port);
}),
)
.await?;
Ok(raw_tx)
};
let tx_hex = hex::encode(encode_tx_result(res.await));
to_c_str(tx_hex)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn broadcast(tx_filename: *mut c_char) -> *mut c_char {
from_c_str!(tx_filename);
let res = async {
let mut file = std::fs::File::open(&tx_filename.to_string())?;
let mut s = String::new();
file.read_to_string(&mut s)?;
let tx = hex::decode(s.trim_end())?;
broadcast_tx(&tx).await
};
to_c_str(log_string(res.await))
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn broadcast_txhex(txhex: *mut c_char) -> *mut c_char {
from_c_str!(txhex);
let res = async {
let tx = hex::decode(&txhex.to_string())?;
broadcast_tx(&tx).await
};
to_c_str(log_string(res.await))
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn get_activation_date() -> u32 {
let res = crate::api::sync::get_activation_date().await;
log_result(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn get_block_by_time(time: u32) -> u32 {
let res = crate::api::sync::get_block_by_time(time).await;
log_result(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn sync_historical_prices(
now: i64,
days: u32,
currency: *mut c_char,
) -> u32 {
from_c_str!(currency);
let res = crate::api::historical_prices::sync_historical_prices(now, days, &currency).await;
log_result(res)
}
#[no_mangle]
pub unsafe extern "C" fn store_contact(
id: u32,
name: *mut c_char,
address: *mut c_char,
dirty: bool,
) {
from_c_str!(name);
from_c_str!(address);
let res = crate::api::contact::store_contact(id, &name, &address, dirty);
log_result(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn commit_unsaved_contacts(anchor_offset: u32) -> *mut c_char {
let res = crate::api::contact::commit_unsaved_contacts(anchor_offset).await;
to_c_str(log_string(res))
}
#[no_mangle]
pub unsafe extern "C" fn mark_message_read(message: u32, read: bool) {
let res = crate::api::message::mark_message_read(message, read);
log_result(res)
}
#[no_mangle]
pub unsafe extern "C" fn mark_all_messages_read(read: bool) {
let res = crate::api::message::mark_all_messages_read(read);
log_result(res)
}
#[no_mangle]
pub unsafe extern "C" fn truncate_data() {
let res = crate::api::account::truncate_data();
log_result(res)
}
#[no_mangle]
pub unsafe extern "C" fn delete_account(coin: u8, account: u32) {
let res = crate::api::account::delete_account(coin, account);
log_result(res)
}
#[no_mangle]
pub unsafe extern "C" fn make_payment_uri(
address: *mut c_char,
amount: u64,
memo: *mut c_char,
) -> *mut c_char {
from_c_str!(memo);
from_c_str!(address);
let res = crate::api::payment_uri::make_payment_uri(&address, amount, &memo);
to_c_str(log_string(res))
}
#[no_mangle]
pub unsafe extern "C" fn parse_payment_uri(uri: *mut c_char) -> *mut c_char {
from_c_str!(uri);
let payment_json = crate::api::payment_uri::parse_payment_uri(&uri);
to_c_str(log_string(payment_json))
}
#[no_mangle]
pub unsafe extern "C" fn generate_random_enc_key() -> *mut c_char {
to_c_str(log_string(crate::key2::generate_random_enc_key()))
}
#[no_mangle]
pub unsafe extern "C" fn get_full_backup(key: *mut c_char) -> *mut c_char {
from_c_str!(key);
let res = || {
let mut accounts = vec![];
for coin in [0, 1] {
accounts.extend(crate::api::fullbackup::get_full_backup(coin)?);
}
let backup = crate::api::fullbackup::encrypt_backup(&accounts, &key)?;
Ok(backup)
};
to_c_str(log_string(res()))
}
#[no_mangle]
pub unsafe extern "C" fn restore_full_backup(key: *mut c_char, backup: *mut c_char) -> *mut c_char {
from_c_str!(key);
from_c_str!(backup);
let res = || {
let accounts = crate::api::fullbackup::decrypt_backup(&key, &backup)?;
for coin in [0, 1] {
crate::api::fullbackup::restore_full_backup(coin, &accounts)?;
}
Ok(String::new())
};
to_c_str(log_string(res()))
}

63
src/api/fullbackup.rs Normal file
View File

@ -0,0 +1,63 @@
use crate::coinconfig::CoinConfig;
use crate::db::AccountBackup;
use bech32::FromBase32;
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
const NONCE: &[u8; 12] = b"unique nonce";
pub fn get_full_backup(coin: u8) -> anyhow::Result<Vec<AccountBackup>> {
let c = CoinConfig::get(coin);
let db = c.db()?;
db.get_full_backup()
}
pub fn restore_full_backup(coin: u8, accounts: &[AccountBackup]) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
db.restore_full_backup(accounts)
}
pub fn encrypt_backup(accounts: &[AccountBackup], key: &str) -> anyhow::Result<String> {
let accounts_bin = bincode::serialize(&accounts)?;
let backup = if !key.is_empty() {
let (hrp, key, _) = bech32::decode(key)?;
if hrp != "zwk" {
anyhow::bail!("Invalid backup key")
}
let key = Vec::<u8>::from_base32(&key)?;
let key = Key::from_slice(&key);
let cipher = ChaCha20Poly1305::new(key);
// nonce is constant because we always use a different key!
let cipher_text = cipher
.encrypt(Nonce::from_slice(NONCE), &*accounts_bin)
.map_err(|_e| anyhow::anyhow!("Failed to encrypt backup"))?;
base64::encode(cipher_text)
} else {
base64::encode(accounts_bin)
};
Ok(backup)
}
pub fn decrypt_backup(key: &str, backup: &str) -> anyhow::Result<Vec<AccountBackup>> {
let backup = if !key.is_empty() {
let (hrp, key, _) = bech32::decode(key)?;
if hrp != "zwk" {
anyhow::bail!("Not a valid decryption key");
}
let key = Vec::<u8>::from_base32(&key)?;
let key = Key::from_slice(&key);
let cipher = ChaCha20Poly1305::new(key);
let backup = base64::decode(backup)?;
cipher
.decrypt(Nonce::from_slice(NONCE), &*backup)
.map_err(|_e| anyhow::anyhow!("Failed to decrypt backup"))?
} else {
base64::decode(backup)?
};
let accounts: Vec<AccountBackup> = bincode::deserialize(&backup)?;
Ok(accounts)
}

View File

@ -0,0 +1,9 @@
use crate::coinconfig::CoinConfig;
pub async fn sync_historical_prices(now: i64, days: u32, currency: &str) -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let mut db = c.db()?;
let quotes = crate::prices::fetch_historical_prices(now, days, currency, &db).await?;
db.store_historical_prices(&quotes, currency)?;
Ok(quotes.len() as u32)
}

28
src/api/mempool.rs Normal file
View File

@ -0,0 +1,28 @@
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_primitives::consensus::Parameters;
use crate::coinconfig::CoinConfig;
use crate::get_latest_height;
pub async fn scan() -> anyhow::Result<i64> {
let c = CoinConfig::get_active();
let ivk = c.db()?.get_ivk(c.id_account)?;
let mut client = c.connect_lwd().await?;
let height = get_latest_height(&mut client).await?;
let mut mempool = c.mempool.lock().unwrap();
let current_height = c.height;
if height != current_height {
CoinConfig::set_height(height);
mempool.clear()?;
}
let fvk = decode_extended_full_viewing_key(
c.chain.network().hrp_sapling_extended_full_viewing_key(),
&ivk,
)?
.unwrap();
mempool
.update(&mut client, height, &fvk.fvk.vk.ivk())
.await?;
Ok(mempool.get_unconfirmed_balance())
}

13
src/api/message.rs Normal file
View File

@ -0,0 +1,13 @@
use crate::coinconfig::CoinConfig;
pub fn mark_message_read(message: u32, read: bool) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
c.db()?.mark_message_read(message, read)?;
Ok(())
}
pub fn mark_all_messages_read(read: bool) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
c.db()?.mark_all_messages_read(c.id_account, read)?;
Ok(())
}

199
src/api/payment.rs Normal file
View File

@ -0,0 +1,199 @@
use std::str::FromStr;
use secp256k1::SecretKey;
use crate::api::sync::get_latest_height;
use crate::coinconfig::{get_prover, CoinConfig};
use crate::pay::TxBuilder;
use crate::{broadcast_tx, Tx};
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, decode_extended_spending_key,
};
use zcash_primitives::consensus::Parameters;
use zcash_primitives::transaction::builder::Progress;
use crate::db::ZMessage;
use crate::taddr::get_utxos;
use serde::Deserialize;
use zcash_primitives::memo::Memo;
// use crate::wallet::Recipient;
type PaymentProgressCallback = Box<dyn Fn(Progress) + Send + Sync>;
async fn prepare_multi_payment(
last_height: u32,
recipients: &[RecipientMemo],
use_transparent: bool,
anchor_offset: u32,
) -> anyhow::Result<(Tx, Vec<u32>)> {
let c = CoinConfig::get_active();
let mut tx_builder = TxBuilder::new(c.coin_type, last_height);
let fvk = c.db()?.get_ivk(c.id_account)?;
let fvk = decode_extended_full_viewing_key(
c.chain.network().hrp_sapling_extended_full_viewing_key(),
&fvk,
)
.unwrap()
.unwrap();
let utxos = if use_transparent {
let mut client = c.connect_lwd().await?;
let t_address = c.db()?.get_taddr(c.id_account)?;
if let Some(t_address) = t_address {
get_utxos(&mut client, &t_address, c.id_account).await?
} else {
vec![]
}
} else {
vec![]
};
let target_amount: u64 = recipients.iter().map(|r| r.amount).sum();
let anchor_height = last_height.saturating_sub(anchor_offset);
let spendable_notes = c
.db()?
.get_spendable_notes(c.id_account, anchor_height, &fvk)?;
let note_ids = tx_builder.select_inputs(&fvk, &spendable_notes, &utxos, target_amount)?;
tx_builder.select_outputs(&fvk, recipients)?;
Ok((tx_builder.tx, note_ids))
}
fn sign(tx: &Tx, progress_callback: PaymentProgressCallback) -> anyhow::Result<Vec<u8>> {
let c = CoinConfig::get_active();
let prover = get_prover();
let db = c.db()?;
let zsk = db.get_sk(c.id_account)?;
let tsk = db
.get_tsk(c.id_account)?
.map(|tsk| SecretKey::from_str(&tsk).unwrap());
let extsk =
decode_extended_spending_key(c.chain.network().hrp_sapling_extended_spending_key(), &zsk)
.unwrap()
.unwrap();
let raw_tx = tx.sign(tsk, &extsk, prover, progress_callback)?;
Ok(raw_tx)
}
/// Build a multi payment for offline signing
pub async fn build_only_multi_payment(
last_height: u32,
recipients: &[RecipientMemo],
use_transparent: bool,
anchor_offset: u32,
) -> anyhow::Result<String> {
let (tx, _) =
prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?;
let tx_str = serde_json::to_string(&tx)?;
Ok(tx_str)
}
pub async fn sign_only_multi_payment(
tx_string: &str,
progress_callback: PaymentProgressCallback,
) -> anyhow::Result<Vec<u8>> {
let tx = serde_json::from_str::<Tx>(tx_string)?;
let raw_tx = sign(&tx, progress_callback)?;
Ok(raw_tx)
}
/// Build, sign and broadcast a multi payment
pub async fn build_sign_send_multi_payment(
last_height: u32,
recipients: &[RecipientMemo],
use_transparent: bool,
anchor_offset: u32,
progress_callback: PaymentProgressCallback,
) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let (tx, note_ids) =
prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?;
let raw_tx = sign(&tx, progress_callback)?;
let tx_id = broadcast_tx(&raw_tx).await?;
c.db()?.tx_mark_spend(&note_ids)?;
Ok(tx_id)
}
pub async fn shield_taddr() -> anyhow::Result<String> {
let last_height = get_latest_height().await?;
let tx_id = build_sign_send_multi_payment(last_height, &[], true, 0, Box::new(|_| {})).await?;
Ok(tx_id)
}
pub fn parse_recipients(recipients: &str) -> anyhow::Result<Vec<RecipientMemo>> {
let c = CoinConfig::get_active();
let address = c.db()?.get_address(c.id_account)?;
let recipients: Vec<Recipient> = serde_json::from_str(recipients)?;
let recipient_memos: Vec<_> = recipients
.iter()
.map(|r| RecipientMemo::from_recipient(&address, r))
.collect();
Ok(recipient_memos)
}
pub fn encode_memo(from: &str, include_from: bool, subject: &str, body: &str) -> String {
let from = if include_from { from } else { "" };
let msg = format!("\u{1F6E1}MSG\n{}\n{}\n{}", from, subject, body);
msg
}
pub fn decode_memo(memo: &str, recipient: &str, timestamp: u32, height: u32) -> ZMessage {
let memo_lines: Vec<_> = memo.splitn(4, '\n').collect();
let msg = if memo_lines[0] == "\u{1F6E1}MSG" {
ZMessage {
sender: if memo_lines[1].is_empty() {
None
} else {
Some(memo_lines[1].to_string())
},
recipient: recipient.to_string(),
subject: memo_lines[2].to_string(),
body: memo_lines[3].to_string(),
timestamp,
height,
}
} else {
ZMessage {
sender: None,
recipient: recipient.to_string(),
subject: memo_lines[0].chars().take(20).collect(),
body: memo.to_string(),
timestamp,
height,
}
};
msg
}
#[derive(Deserialize)]
pub struct Recipient {
pub address: String,
pub amount: u64,
pub reply_to: bool,
pub subject: String,
pub memo: String,
pub max_amount_per_note: u64,
}
pub struct RecipientMemo {
pub address: String,
pub amount: u64,
pub memo: Memo,
pub max_amount_per_note: u64,
}
impl RecipientMemo {
pub fn from_recipient(from: &str, r: &Recipient) -> Self {
let memo = if !r.reply_to && r.subject.is_empty() {
r.memo.clone()
} else {
encode_memo(from, r.reply_to, &r.subject, &r.memo)
};
RecipientMemo {
address: r.address.clone(),
amount: r.amount,
memo: Memo::from_str(&memo).unwrap(),
max_amount_per_note: r.max_amount_per_note,
}
}
}

71
src/api/payment_uri.rs Normal file
View File

@ -0,0 +1,71 @@
use crate::coinconfig::CoinConfig;
use serde::Serialize;
use std::convert::TryFrom;
use std::str::FromStr;
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::zip321::{Payment, TransactionRequest};
use zcash_primitives::memo::Memo;
use zcash_primitives::transaction::components::Amount;
pub fn make_payment_uri(address: &str, amount: u64, memo: &str) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let addr = RecipientAddress::decode(c.chain.network(), address)
.ok_or_else(|| anyhow::anyhow!("Invalid address"))?;
let payment = Payment {
recipient_address: addr,
amount: Amount::from_u64(amount).map_err(|_| anyhow::anyhow!("Invalid amount"))?,
memo: Some(Memo::from_str(memo)?.into()),
label: None,
message: None,
other_params: vec![],
};
let treq = TransactionRequest {
payments: vec![payment],
};
let uri = treq
.to_uri(c.chain.network())
.ok_or_else(|| anyhow::anyhow!("Cannot build Payment URI"))?;
let uri = format!("{}{}", c.chain.ticker(), &uri[5..]); // hack to replace the URI scheme
Ok(uri)
}
pub fn parse_payment_uri(uri: &str) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
if uri[..5].ne(c.chain.ticker()) {
anyhow::bail!("Invalid Payment URI");
}
let uri = format!("zcash{}", &uri[5..]); // hack to replace the URI scheme
let treq = TransactionRequest::from_uri(c.chain.network(), &uri)
.map_err(|_| anyhow::anyhow!("Invalid Payment URI"))?;
if treq.payments.len() != 1 {
anyhow::bail!("Invalid Payment URI")
}
let payment = &treq.payments[0];
let memo = match payment.memo {
Some(ref memo) => {
let memo = Memo::try_from(memo.clone())?;
match memo {
Memo::Text(text) => Ok(text.to_string()),
Memo::Empty => Ok(String::new()),
_ => Err(anyhow::anyhow!("Invalid Memo")),
}
}
None => Ok(String::new()),
}?;
let payment = MyPayment {
address: payment.recipient_address.encode(c.chain.network()),
amount: u64::from(payment.amount),
memo,
};
let payment_json = serde_json::to_string(&payment)?;
Ok(payment_json)
}
#[derive(Serialize)]
struct MyPayment {
address: String,
amount: u64,
memo: String,
}

105
src/api/sync.rs Normal file
View File

@ -0,0 +1,105 @@
// Sync
use crate::coinconfig::CoinConfig;
use crate::scan::AMProgressCallback;
use crate::{BlockId, CTree, CompactTxStreamerClient};
use std::sync::Arc;
use tokio::sync::Mutex;
use tonic::transport::Channel;
use tonic::Request;
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
pub async fn coin_sync(
coin: u8,
get_tx: bool,
anchor_offset: u32,
progress_callback: impl Fn(u32) + Send + 'static,
) -> anyhow::Result<()> {
let cb = Arc::new(Mutex::new(progress_callback));
coin_sync_impl(coin, get_tx, DEFAULT_CHUNK_SIZE, anchor_offset, cb.clone()).await?;
coin_sync_impl(coin, get_tx, DEFAULT_CHUNK_SIZE, 0, cb.clone()).await?;
Ok(())
}
async fn coin_sync_impl(
coin: u8,
get_tx: bool,
chunk_size: u32,
target_height_offset: u32,
progress_callback: AMProgressCallback,
) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
crate::scan::sync_async(
c.coin_type,
chunk_size,
get_tx,
&c.db_path,
target_height_offset,
progress_callback,
&c.lwd_url,
)
.await?;
Ok(())
}
pub async fn get_latest_height() -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
let last_height = crate::chain::get_latest_height(&mut client).await?;
Ok(last_height)
}
pub async fn skip_to_last_height(coin: u8) -> anyhow::Result<()> {
let c = if coin == 0xFF {
CoinConfig::get_active()
} else {
CoinConfig::get(coin)
};
let mut client = c.connect_lwd().await?;
let last_height = crate::chain::get_latest_height(&mut client).await?;
fetch_and_store_tree_state(&mut client, last_height).await?;
Ok(())
}
pub async fn rewind_to_height(height: u32) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
c.db()?.trim_to_height(height)?;
fetch_and_store_tree_state(&mut client, height).await?;
Ok(())
}
async fn fetch_and_store_tree_state(
client: &mut CompactTxStreamerClient<Channel>,
height: u32,
) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let block_id = BlockId {
height: height as u64,
hash: vec![],
};
let block = client.get_block(block_id.clone()).await?.into_inner();
let tree_state = client
.get_tree_state(Request::new(block_id))
.await?
.into_inner();
let tree = CTree::read(&*hex::decode(&tree_state.tree)?)?;
c.db()?
.store_block(height, &block.hash, block.time, &tree)?;
Ok(())
}
pub async fn get_activation_date() -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
let date_time = crate::chain::get_activation_date(c.chain.network(), &mut client).await?;
Ok(date_time)
}
pub async fn get_block_by_time(time: u32) -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
let date_time = crate::chain::get_block_by_time(c.chain.network(), &mut client, time).await?;
Ok(date_time)
}

View File

@ -10,7 +10,7 @@ use zcash_primitives::sapling::Node;
#[inline(always)] #[inline(always)]
fn batch_node_combine1(depth: usize, left: &Node, right: &Node) -> ExtendedPoint { fn batch_node_combine1(depth: usize, left: &Node, right: &Node) -> ExtendedPoint {
// Node::new(pedersen_hash(depth as u8, &left.repr, &right.repr)) // Node::new(pedersen_hash(depth as u8, &left.repr, &right.repr))
ExtendedPoint::from(pedersen_hash_inner(depth as u8, &left.repr, &right.repr)) pedersen_hash_inner(depth as u8, &left.repr, &right.repr)
} }
#[inline(always)] #[inline(always)]
@ -63,15 +63,13 @@ impl Builder<CTree, ()> for CTreeBuilder {
self.next_tree.right = None; self.next_tree.right = None;
m - 1 m - 1
} }
} else if m % 2 == 0 {
self.next_tree.parents.push(None);
m
} else { } else {
if m % 2 == 0 { let last_node = Self::get(commitments, m - 1, &offset);
self.next_tree.parents.push(None); self.next_tree.parents.push(Some(*last_node));
m m - 1
} else {
let last_node = Self::get(commitments, m - 1, &offset);
self.next_tree.parents.push(Some(*last_node));
m - 1
}
} }
} else { } else {
0 0
@ -240,13 +238,11 @@ impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
tree.left = Some(*CTreeBuilder::get(commitments, rp, &offset)); tree.left = Some(*CTreeBuilder::get(commitments, rp, &offset));
tree.right = None; tree.right = None;
} }
} else { } else if self.p % 2 == 1 {
if self.p % 2 == 1 { tree.parents
tree.parents .push(Some(*CTreeBuilder::get(commitments, rp - 1, &offset)));
.push(Some(*CTreeBuilder::get(commitments, rp - 1, &offset))); } else if self.p != 0 {
} else if self.p != 0 { tree.parents.push(None);
tree.parents.push(None);
}
} }
} }
@ -270,10 +266,8 @@ impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
if tree.right.is_none() { if tree.right.is_none() {
self.witness.filled.push(*p1); self.witness.filled.push(*p1);
} }
} else { } else if depth > tree.parents.len() || tree.parents[depth - 1].is_none() {
if depth - 1 >= tree.parents.len() || tree.parents[depth - 1].is_none() { self.witness.filled.push(*p1);
self.witness.filled.push(*p1);
}
} }
} }
0 0
@ -294,7 +288,7 @@ impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
let mut final_position = context.prev_tree.get_position() as u32; let mut final_position = context.prev_tree.get_position() as u32;
let mut witness_position = self.witness.tree.get_position() as u32; let mut witness_position = self.witness.tree.get_position() as u32;
assert_ne!(witness_position, 0); assert_ne!(witness_position, 0);
witness_position = witness_position - 1; witness_position -= 1;
// look for first not equal bit in MSB order // look for first not equal bit in MSB order
final_position = final_position.reverse_bits(); final_position = final_position.reverse_bits();
@ -434,11 +428,11 @@ mod tests {
nodes: &[Node], nodes: &[Node],
) { ) {
for n in nodes.iter() { for n in nodes.iter() {
tree.append(n.clone()).unwrap(); tree.append(*n).unwrap();
for w in ws.iter_mut() { for w in ws.iter_mut() {
w.append(n.clone()).unwrap(); w.append(*n).unwrap();
} }
let w = IncrementalWitness::<Node>::from_tree(&tree); let w = IncrementalWitness::<Node>::from_tree(tree);
ws.push(w); ws.push(w);
} }
} }
@ -450,8 +444,8 @@ mod tests {
w2.write(&mut bb2).unwrap(); w2.write(&mut bb2).unwrap();
if bb1 != bb2 { if bb1 != bb2 {
print_witness(&w1); print_witness(w1);
print_witness2(&w2); print_witness2(w2);
assert!(false); assert!(false);
} }
@ -656,7 +650,7 @@ mod tests {
failed_index = Some(i); failed_index = Some(i);
println!("FAILED AT {}", i); println!("FAILED AT {}", i);
println!("GOOD"); println!("GOOD");
print_witness(&w1); print_witness(w1);
if let Some(ref c) = w1.cursor { if let Some(ref c) = w1.cursor {
print_tree(c); print_tree(c);
} else { } else {
@ -664,7 +658,7 @@ mod tests {
} }
println!("BAD"); println!("BAD");
print_witness2(&w2); print_witness2(w2);
} }
assert!(equal && failed_index.is_none()); assert!(equal && failed_index.is_none());
} }

View File

@ -176,12 +176,12 @@ pub fn to_output_description(co: &CompactOutput) -> CompactOutputDescription {
// let epk = jubjub::ExtendedPoint::from_bytes(&epk).unwrap(); // let epk = jubjub::ExtendedPoint::from_bytes(&epk).unwrap();
let mut enc_ciphertext = [0u8; 52]; let mut enc_ciphertext = [0u8; 52];
enc_ciphertext.copy_from_slice(&co.ciphertext); enc_ciphertext.copy_from_slice(&co.ciphertext);
let od = CompactOutputDescription {
CompactOutputDescription {
ephemeral_key: EphemeralKeyBytes::from(epk), ephemeral_key: EphemeralKeyBytes::from(epk),
cmu, cmu,
enc_ciphertext, enc_ciphertext,
}; }
od
} }
struct AccountOutput<'a, N: Parameters> { struct AccountOutput<'a, N: Parameters> {
@ -208,7 +208,7 @@ impl<'a, N: Parameters> AccountOutput<'a, N> {
let epk = EphemeralKeyBytes::from(epk_bytes); let epk = EphemeralKeyBytes::from(epk_bytes);
let mut cmu_bytes = [0u8; 32]; let mut cmu_bytes = [0u8; 32];
cmu_bytes.copy_from_slice(&co.cmu); cmu_bytes.copy_from_slice(&co.cmu);
let cmu = <SaplingDomain<N> as Domain>::ExtractedCommitmentBytes::from(cmu_bytes); let cmu = cmu_bytes;
let mut ciphertext_bytes = [0u8; COMPACT_NOTE_SIZE]; let mut ciphertext_bytes = [0u8; COMPACT_NOTE_SIZE];
ciphertext_bytes.copy_from_slice(&co.ciphertext); ciphertext_bytes.copy_from_slice(&co.ciphertext);
@ -508,7 +508,7 @@ mod tests {
use crate::db::AccountViewKey; use crate::db::AccountViewKey;
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient; use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
use crate::LWD_URL; use crate::LWD_URL;
use dotenv;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Instant; use std::time::Instant;
use zcash_client_backend::encoding::decode_extended_full_viewing_key; use zcash_client_backend::encoding::decode_extended_full_viewing_key;

120
src/coinconfig.rs Normal file
View File

@ -0,0 +1,120 @@
use crate::{connect_lightwalletd, CompactTxStreamerClient, DbAdapter, MemPool};
use lazy_static::lazy_static;
use lazycell::AtomicLazyCell;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use tonic::transport::Channel;
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
use zcash_proofs::prover::LocalTxProver;
lazy_static! {
pub static ref COIN_CONFIG: [Mutex<CoinConfig>; 2] = [
Mutex::new(CoinConfig::new(0, CoinType::Zcash)),
Mutex::new(CoinConfig::new(1, CoinType::Ycash)),
];
pub static ref PROVER: AtomicLazyCell<LocalTxProver> = AtomicLazyCell::new();
}
pub static ACTIVE_COIN: AtomicU8 = AtomicU8::new(0);
pub fn set_active(active: u8) {
ACTIVE_COIN.store(active, Ordering::Release);
}
pub fn set_active_account(coin: u8, id: u32) {
let mempool = {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
c.id_account = id;
c.mempool.clone()
};
let mut mempool = mempool.lock().unwrap();
let _ = mempool.clear();
}
pub fn set_coin_lwd_url(coin: u8, lwd_url: &str) {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
c.lwd_url = lwd_url.to_string();
}
pub fn init_coin(coin: u8, db_path: &str) -> anyhow::Result<()> {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
c.set_db_path(db_path)?;
Ok(())
}
#[derive(Clone)]
pub struct CoinConfig {
pub coin: u8,
pub coin_type: CoinType,
pub id_account: u32,
pub height: u32,
pub lwd_url: String,
pub db_path: String,
pub mempool: Arc<Mutex<MemPool>>,
pub db: Option<Arc<Mutex<DbAdapter>>>,
pub chain: &'static (dyn CoinChain + Send),
}
impl CoinConfig {
pub fn new(coin: u8, coin_type: CoinType) -> Self {
let chain = get_coin_chain(coin_type);
CoinConfig {
coin,
coin_type,
id_account: 0,
height: 0,
lwd_url: String::new(),
db_path: String::new(),
db: None,
mempool: Arc::new(Mutex::new(MemPool::new(coin))),
chain,
}
}
pub fn set_db_path(&mut self, db_path: &str) -> anyhow::Result<()> {
self.db_path = db_path.to_string();
let db = DbAdapter::new(self.coin_type, &self.db_path)?;
db.init_db()?;
self.db = Some(Arc::new(Mutex::new(db)));
Ok(())
}
pub fn get(coin: u8) -> CoinConfig {
let c = COIN_CONFIG[coin as usize].lock().unwrap();
c.clone()
}
pub fn get_active() -> CoinConfig {
let coin = ACTIVE_COIN.load(Ordering::Acquire) as usize;
let c = COIN_CONFIG[coin].lock().unwrap();
c.clone()
}
pub fn set_height(height: u32) {
let coin = ACTIVE_COIN.load(Ordering::Acquire) as usize;
let mut c = COIN_CONFIG[coin].lock().unwrap();
c.height = height;
}
pub fn mempool(&self) -> MutexGuard<MemPool> {
self.mempool.lock().unwrap()
}
pub fn db(&self) -> anyhow::Result<MutexGuard<DbAdapter>> {
let db = self.db.as_ref().unwrap();
let db = db.lock().unwrap();
Ok(db)
}
pub async fn connect_lwd(&self) -> anyhow::Result<CompactTxStreamerClient<Channel>> {
connect_lightwalletd(&self.lwd_url).await
}
}
pub fn get_prover() -> &'static LocalTxProver {
if !PROVER.filled() {
let _ = PROVER.fill(LocalTxProver::from_bytes(SPEND_PARAMS, OUTPUT_PARAMS));
}
PROVER.borrow().unwrap()
}

View File

@ -101,7 +101,7 @@ impl Witness {
pub fn read<R: Read>(id_note: u32, mut reader: R) -> std::io::Result<Self> { pub fn read<R: Read>(id_note: u32, mut reader: R) -> std::io::Result<Self> {
let tree = CTree::read(&mut reader)?; let tree = CTree::read(&mut reader)?;
let filled = Vector::read(&mut reader, |r| Node::read(r))?; let filled = Vector::read(&mut reader, |r| Node::read(r))?;
let cursor = Optional::read(&mut reader, |r| CTree::read(r))?; let cursor = Optional::read(&mut reader, CTree::read)?;
let mut witness = Witness { let mut witness = Witness {
position: 0, position: 0,
@ -147,9 +147,9 @@ impl CTree {
} }
pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> { pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
let left = Optional::read(&mut reader, |r| Node::read(r))?; let left = Optional::read(&mut reader, Node::read)?;
let right = Optional::read(&mut reader, |r| Node::read(r))?; let right = Optional::read(&mut reader, Node::read)?;
let parents = Vector::read(&mut reader, |r| Optional::read(r, |r| Node::read(r)))?; let parents = Vector::read(&mut reader, |r| Optional::read(r, Node::read))?;
Ok(CTree { Ok(CTree {
left, left,

View File

@ -63,7 +63,7 @@ impl ContactDecoder {
if !self.has_contacts { if !self.has_contacts {
return Ok(Vec::new()); return Ok(Vec::new());
} }
let data: Vec<_> = self.chunks.iter().cloned().flatten().collect(); let data: Vec<_> = self.chunks.iter().flatten().cloned().collect();
let contacts = bincode::deserialize::<Vec<Contact>>(&data)?; let contacts = bincode::deserialize::<Vec<Contact>>(&data)?;
Ok(contacts) Ok(contacts)
} }
@ -84,39 +84,3 @@ impl ContactDecoder {
Ok((n, data.to_vec())) Ok((n, data.to_vec()))
} }
} }
#[cfg(test)]
mod tests {
use crate::contact::{serialize_contacts, Contact};
use crate::db::DEFAULT_DB_PATH;
use crate::{DbAdapter, Wallet, LWD_URL};
use zcash_params::coin::CoinType;
#[test]
fn test_contacts() {
let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
let contact = Contact {
id: 0,
name: "hanh".to_string(),
address:
"zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35"
.to_string(),
};
db.store_contact(&contact, true).unwrap();
}
#[tokio::test]
async fn test_serialize() {
let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
let contacts = db.get_unsaved_contacts().unwrap();
let memos = serialize_contacts(&contacts).unwrap();
for m in memos.iter() {
println!("{:?}", m);
}
let mut wallet = Wallet::new(CoinType::Zcash, "zec.db");
wallet.set_lwd_url(LWD_URL).unwrap();
let tx_id = wallet.save_contacts_tx(&memos, 1, 3).await.unwrap();
println!("{}", tx_id);
}
}

View File

@ -111,27 +111,25 @@ impl DbAdapter {
sk: Option<&str>, sk: Option<&str>,
ivk: &str, ivk: &str,
address: &str, address: &str,
) -> anyhow::Result<i32> { ) -> anyhow::Result<(u32, bool)> {
let mut statement = self let mut statement = self
.connection .connection
.prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?; .prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?;
if statement.exists(params![ivk])? { let exists = statement.exists(params![ivk])?;
return Ok(-1);
}
self.connection.execute( self.connection.execute(
"INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6) "INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
ON CONFLICT DO NOTHING", ON CONFLICT DO NOTHING",
params![name, seed, index, sk, ivk, address], params![name, seed, index, sk, ivk, address],
)?; )?;
let id_tx: i32 = self.connection.query_row( let id_account: u32 = self.connection.query_row(
"SELECT id_account FROM accounts WHERE ivk = ?1", "SELECT id_account FROM accounts WHERE ivk = ?1",
params![ivk], params![ivk],
|row| row.get(0), |row| row.get(0),
)?; )?;
Ok(id_tx) Ok((id_account, exists))
} }
pub fn next_account_id(&self, seed: &str) -> anyhow::Result<i32> { pub fn next_account_id(&self, seed: &str) -> anyhow::Result<u32> {
let index = self.connection.query_row( let index = self.connection.query_row(
"SELECT MAX(aindex) FROM accounts WHERE seed = ?1", "SELECT MAX(aindex) FROM accounts WHERE seed = ?1",
[seed], [seed],
@ -140,7 +138,7 @@ impl DbAdapter {
Ok(aindex.unwrap_or(-1)) Ok(aindex.unwrap_or(-1))
}, },
)? + 1; )? + 1;
Ok(index) Ok(index as u32)
} }
pub fn get_fvks(&self) -> anyhow::Result<HashMap<u32, AccountViewKey>> { pub fn get_fvks(&self) -> anyhow::Result<HashMap<u32, AccountViewKey>> {
@ -504,6 +502,15 @@ impl DbAdapter {
Ok(spendable_notes) Ok(spendable_notes)
} }
pub fn tx_mark_spend(&mut self, selected_notes: &[u32]) -> anyhow::Result<()> {
let db_tx = self.begin_transaction()?;
for id_note in selected_notes.iter() {
DbAdapter::mark_spent(*id_note, 0, &db_tx)?;
}
db_tx.commit()?;
Ok(())
}
pub fn mark_spent(id: u32, height: u32, tx: &Transaction) -> anyhow::Result<()> { pub fn mark_spent(id: u32, height: u32, tx: &Transaction) -> anyhow::Result<()> {
log::debug!("+mark_spent"); log::debug!("+mark_spent");
tx.execute( tx.execute(
@ -663,7 +670,7 @@ impl DbAdapter {
}, },
) )
.optional()? .optional()?
.unwrap_or_else(|| [0u8; 11]); .unwrap_or([0u8; 11]);
Ok(DiversifierIndex(diversifier_index)) Ok(DiversifierIndex(diversifier_index))
} }

View File

@ -28,7 +28,7 @@ pub fn reset_db(connection: &Connection) -> anyhow::Result<()> {
connection.execute("DROP TABLE sapling_witnesses", [])?; connection.execute("DROP TABLE sapling_witnesses", [])?;
connection.execute("DROP TABLE diversifiers", [])?; connection.execute("DROP TABLE diversifiers", [])?;
connection.execute("DROP TABLE historical_prices", [])?; connection.execute("DROP TABLE historical_prices", [])?;
update_schema_version(&connection, 0)?; update_schema_version(connection, 0)?;
Ok(()) Ok(())
} }
@ -40,7 +40,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
[], [],
)?; )?;
let version = get_schema_version(&connection)?; let version = get_schema_version(connection)?;
if version < 1 { if version < 1 {
connection.execute( connection.execute(
@ -174,7 +174,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
} }
if version != 3 { if version != 3 {
update_schema_version(&connection, 3)?; update_schema_version(connection, 3)?;
log::info!("Database migrated"); log::info!("Database migrated");
} }

View File

@ -19,7 +19,7 @@ fn read_generators_bin() -> Vec<ExtendedNielsPoint> {
for _j in 0..32 { for _j in 0..32 {
for _k in 0..256 { for _k in 0..256 {
let mut bb = [0u8; 32]; let mut bb = [0u8; 32];
generators_bin.read(&mut bb).unwrap(); generators_bin.read_exact(&mut bb).unwrap();
let p = ExtendedPoint::from(SubgroupPoint::from_bytes_unchecked(&bb).unwrap()) let p = ExtendedPoint::from(SubgroupPoint::from_bytes_unchecked(&bb).unwrap())
.to_niels(); .to_niels();
gens.push(p); gens.push(p);
@ -52,8 +52,7 @@ type Hash = [u8; 32];
pub fn pedersen_hash(depth: u8, left: &Hash, right: &Hash) -> Hash { pub fn pedersen_hash(depth: u8, left: &Hash, right: &Hash) -> Hash {
let p = pedersen_hash_inner(depth, left, right); let p = pedersen_hash_inner(depth, left, right);
let h = jubjub::ExtendedPoint::from(p).to_affine().get_u().to_repr(); p.to_affine().get_u().to_repr()
h
} }
pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoint { pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoint {
@ -99,7 +98,7 @@ pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoin
r = (r >> 8) | (right[r_byteoffset + 1] as u16) << 8; r = (r >> 8) | (right[r_byteoffset + 1] as u16) << 8;
r_byteoffset += 1; r_byteoffset += 1;
} else if byteoffset == 63 { } else if byteoffset == 63 {
r = r >> 8; r >>= 8;
} }
} }

View File

@ -30,16 +30,16 @@ impl KeyHelpers {
index: u32, index: u32,
) -> anyhow::Result<(Option<String>, Option<String>, String, String)> { ) -> anyhow::Result<(Option<String>, Option<String>, String, String)> {
let network = self.chain().network(); let network = self.chain().network();
let res = if let Ok(mnemonic) = Mnemonic::from_phrase(&key, Language::English) { let res = if let Ok(mnemonic) = Mnemonic::from_phrase(key, Language::English) {
let (sk, ivk, pa) = self.derive_secret_key(&mnemonic, index)?; let (sk, ivk, pa) = self.derive_secret_key(&mnemonic, index)?;
Ok((Some(key.to_string()), Some(sk), ivk, pa)) Ok((Some(key.to_string()), Some(sk), ivk, pa))
} else if let Ok(Some(sk)) = } else if let Ok(Some(sk)) =
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &key) decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key)
{ {
let (ivk, pa) = self.derive_viewing_key(&sk)?; let (ivk, pa) = self.derive_viewing_key(&sk)?;
Ok((None, Some(key.to_string()), ivk, pa)) Ok((None, Some(key.to_string()), ivk, pa))
} else if let Ok(Some(fvk)) = } else if let Ok(Some(fvk)) =
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &key) decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key)
{ {
let pa = self.derive_address(&fvk)?; let pa = self.derive_address(&fvk)?;
Ok((None, None, key.to_string(), pa)) Ok((None, None, key.to_string(), pa))
@ -51,16 +51,16 @@ impl KeyHelpers {
pub fn is_valid_key(&self, key: &str) -> i8 { pub fn is_valid_key(&self, key: &str) -> i8 {
let network = self.chain().network(); let network = self.chain().network();
if Mnemonic::from_phrase(&key, Language::English).is_ok() { if Mnemonic::from_phrase(key, Language::English).is_ok() {
return 0; return 0;
} }
if let Ok(Some(_)) = if let Ok(Some(_)) =
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &key) decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key)
{ {
return 1; return 1;
} }
if let Ok(Some(_)) = if let Ok(Some(_)) =
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &key) decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key)
{ {
return 2; return 2;
} }
@ -73,7 +73,7 @@ impl KeyHelpers {
index: u32, index: u32,
) -> anyhow::Result<(String, String, String)> { ) -> anyhow::Result<(String, String, String)> {
let network = self.chain().network(); let network = self.chain().network();
let seed = Seed::new(&mnemonic, ""); let seed = Seed::new(mnemonic, "");
let master = ExtendedSpendingKey::master(seed.as_bytes()); let master = ExtendedSpendingKey::master(seed.as_bytes());
let path = [ let path = [
ChildIndex::Hardened(32), ChildIndex::Hardened(32),

107
src/key2.rs Normal file
View File

@ -0,0 +1,107 @@
use crate::coinconfig::CoinConfig;
use bech32::{ToBase32, Variant};
use bip39::{Language, Mnemonic, Seed};
use rand::rngs::OsRng;
use rand::RngCore;
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::encoding::{
decode_extended_full_viewing_key, decode_extended_spending_key,
encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
};
use zcash_primitives::consensus::{Network, Parameters};
use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey};
pub fn decode_key(
coin: u8,
key: &str,
index: u32,
) -> anyhow::Result<(Option<String>, Option<String>, String, String)> {
let c = CoinConfig::get(coin);
let network = c.chain.network();
let res = if let Ok(mnemonic) = Mnemonic::from_phrase(key, Language::English) {
let (sk, ivk, pa) = derive_secret_key(network, &mnemonic, index)?;
Ok((Some(key.to_string()), Some(sk), ivk, pa))
} else if let Ok(Some(sk)) =
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key)
{
let (ivk, pa) = derive_viewing_key(network, &sk)?;
Ok((None, Some(key.to_string()), ivk, pa))
} else if let Ok(Some(fvk)) =
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key)
{
let pa = derive_address(network, &fvk)?;
Ok((None, None, key.to_string(), pa))
} else {
Err(anyhow::anyhow!("Not a valid key"))
};
res
}
pub fn is_valid_key(coin: u8, key: &str) -> i8 {
let c = CoinConfig::get(coin);
let network = c.chain.network();
if Mnemonic::from_phrase(key, Language::English).is_ok() {
return 0;
}
if let Ok(Some(_)) =
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key)
{
return 1;
}
if let Ok(Some(_)) =
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key)
{
return 2;
}
-1
}
pub fn is_valid_address(coin: u8, address: &str) -> bool {
let c = CoinConfig::get(coin);
let network = c.chain.network();
let recipient = RecipientAddress::decode(network, address);
recipient.is_some()
}
fn derive_secret_key(
network: &Network,
mnemonic: &Mnemonic,
index: u32,
) -> anyhow::Result<(String, String, String)> {
let seed = Seed::new(mnemonic, "");
let master = ExtendedSpendingKey::master(seed.as_bytes());
let path = [
ChildIndex::Hardened(32),
ChildIndex::Hardened(network.coin_type()),
ChildIndex::Hardened(index),
];
let extsk = ExtendedSpendingKey::from_path(&master, &path);
let sk = encode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk);
let (fvk, pa) = derive_viewing_key(network, &extsk)?;
Ok((sk, fvk, pa))
}
fn derive_viewing_key(
network: &Network,
extsk: &ExtendedSpendingKey,
) -> anyhow::Result<(String, String)> {
let fvk = ExtendedFullViewingKey::from(extsk);
let pa = derive_address(network, &fvk)?;
let fvk =
encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk);
Ok((fvk, pa))
}
fn derive_address(network: &Network, fvk: &ExtendedFullViewingKey) -> anyhow::Result<String> {
let (_, payment_address) = fvk.default_address();
let address = encode_payment_address(network.hrp_sapling_payment_address(), &payment_address);
Ok(address)
}
pub fn generate_random_enc_key() -> anyhow::Result<String> {
let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key);
let key = bech32::encode("zwk", key.to_base32(), Variant::Bech32)?;
Ok(key)
}

View File

@ -21,11 +21,13 @@ pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
mod builder; mod builder;
mod chain; mod chain;
mod coinconfig;
mod commitment; mod commitment;
mod contact; mod contact;
mod db; mod db;
mod hash; mod hash;
mod key; mod key;
mod key2;
mod mempool; mod mempool;
mod pay; mod pay;
mod prices; mod prices;
@ -34,7 +36,8 @@ mod scan;
mod taddr; mod taddr;
mod transaction; mod transaction;
mod ua; mod ua;
mod wallet; // mod wallet;
mod api;
#[cfg(feature = "ledger")] #[cfg(feature = "ledger")]
mod ledger; mod ledger;
@ -72,7 +75,7 @@ pub use crate::pay::{broadcast_tx, Tx, TxIn, TxOut};
pub use crate::print::*; pub use crate::print::*;
pub use crate::scan::{latest_height, scan_all, sync_async}; pub use crate::scan::{latest_height, scan_all, sync_async};
pub use crate::ua::{get_sapling, get_ua}; pub use crate::ua::{get_sapling, get_ua};
pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance}; // pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance};
#[cfg(feature = "ledger_sapling")] #[cfg(feature = "ledger_sapling")]
pub use crate::ledger::sapling::build_tx_ledger; pub use crate::ledger::sapling::build_tx_ledger;

View File

@ -68,8 +68,8 @@ fn test_increasing_notes() {
fn mk_node(pos: usize) -> Node { fn mk_node(pos: usize) -> Node {
let mut bb = [0u8; 32]; let mut bb = [0u8; 32];
bb[0..8].copy_from_slice(&pos.to_be_bytes()); bb[0..8].copy_from_slice(&pos.to_be_bytes());
let node = Node::new(bb);
node Node::new(bb)
} }
fn test_increasing_gap(run_normal: bool, run_warp: bool) { fn test_increasing_gap(run_normal: bool, run_warp: bool) {

View File

@ -1,13 +1,12 @@
use crate::chain::to_output_description; use crate::chain::to_output_description;
use crate::{ use crate::{CompactTx, CompactTxStreamerClient, Exclude};
connect_lightwalletd, get_latest_height, CompactTx, CompactTxStreamerClient, DbAdapter, Exclude,
};
use std::collections::HashMap; use std::collections::HashMap;
use tonic::transport::Channel; use tonic::transport::Channel;
use tonic::Request; use tonic::Request;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType}; use crate::coinconfig::CoinConfig;
use zcash_primitives::consensus::{BlockHeight, Parameters}; use zcash_params::coin::CoinChain;
use zcash_primitives::consensus::BlockHeight;
use zcash_primitives::sapling::note_encryption::try_sapling_compact_note_decryption; use zcash_primitives::sapling::note_encryption::try_sapling_compact_note_decryption;
use zcash_primitives::sapling::SaplingIvk; use zcash_primitives::sapling::SaplingIvk;
@ -20,85 +19,38 @@ struct MemPoolTransacton {
} }
pub struct MemPool { pub struct MemPool {
coin_type: CoinType, coin: u8,
db_path: String,
account: u32,
ivk: Option<SaplingIvk>,
height: BlockHeight,
transactions: HashMap<Vec<u8>, MemPoolTransacton>, transactions: HashMap<Vec<u8>, MemPoolTransacton>,
nfs: HashMap<Vec<u8>, u64>, nfs: HashMap<Vec<u8>, u64>,
balance: i64, balance: i64,
ld_url: String,
} }
impl MemPool { impl MemPool {
pub fn new(coin_type: CoinType, db_path: &str) -> MemPool { pub fn new(coin: u8) -> MemPool {
MemPool { MemPool {
coin_type, coin,
db_path: db_path.to_string(),
account: 0,
ivk: None,
height: BlockHeight::from(0),
transactions: HashMap::new(), transactions: HashMap::new(),
nfs: HashMap::new(), nfs: HashMap::new(),
balance: 0, balance: 0,
ld_url: "".to_string(),
} }
} }
pub fn set_account(&mut self, account: u32) -> anyhow::Result<()> {
let db = DbAdapter::new(self.coin_type, &self.db_path)?;
let ivk = db.get_ivk(account)?;
self.account = account;
self.set_ivk(&ivk);
self.clear(0)?;
Ok(())
}
fn set_ivk(&mut self, ivk: &str) {
let fvk = decode_extended_full_viewing_key(
self.chain()
.network()
.hrp_sapling_extended_full_viewing_key(),
&ivk,
)
.unwrap()
.unwrap();
let ivk = fvk.fvk.vk.ivk();
self.ivk = Some(ivk);
}
pub async fn scan(&mut self) -> anyhow::Result<i64> {
if self.ivk.is_some() {
let ivk = self.ivk.as_ref().unwrap().clone();
let mut client = connect_lightwalletd(&self.ld_url).await?;
let height = get_latest_height(&mut client).await?;
if self.height != BlockHeight::from(height) {
// New blocks invalidate the mempool
self.clear(height)?;
}
self.update(&mut client, &ivk).await?;
}
Ok(self.balance)
}
pub fn get_unconfirmed_balance(&self) -> i64 { pub fn get_unconfirmed_balance(&self) -> i64 {
self.balance self.balance
} }
pub fn clear(&mut self, height: u32) -> anyhow::Result<()> { pub fn clear(&mut self) -> anyhow::Result<()> {
let db = DbAdapter::new(self.coin_type, &self.db_path)?; let c = CoinConfig::get(self.coin);
self.height = BlockHeight::from_u32(height); self.nfs = c.db()?.get_nullifier_amounts(c.id_account, true)?;
self.nfs = db.get_nullifier_amounts(self.account, true)?;
self.transactions.clear(); self.transactions.clear();
self.balance = 0; self.balance = 0;
Ok(()) Ok(())
} }
async fn update( pub async fn update(
&mut self, &mut self,
client: &mut CompactTxStreamerClient<Channel>, client: &mut CompactTxStreamerClient<Channel>,
height: u32,
ivk: &SaplingIvk, ivk: &SaplingIvk,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let filter: Vec<_> = self let filter: Vec<_> = self
@ -122,7 +74,7 @@ impl MemPool {
tx.exclude_len += 1; // server sent us the same tx: make the filter more specific tx.exclude_len += 1; // server sent us the same tx: make the filter more specific
} }
None => { None => {
let balance = self.scan_transaction(&tx, ivk); let balance = self.scan_transaction(height, &tx, ivk);
let mempool_tx = MemPoolTransacton { let mempool_tx = MemPoolTransacton {
balance, balance,
exclude_len: DEFAULT_EXCLUDE_LEN, exclude_len: DEFAULT_EXCLUDE_LEN,
@ -136,7 +88,8 @@ impl MemPool {
Ok(()) Ok(())
} }
fn scan_transaction(&self, tx: &CompactTx, ivk: &SaplingIvk) -> i64 { fn scan_transaction(&self, height: u32, tx: &CompactTx, ivk: &SaplingIvk) -> i64 {
let c = CoinConfig::get_active();
let mut balance = 0i64; let mut balance = 0i64;
for cs in tx.spends.iter() { for cs in tx.spends.iter() {
if let Some(&value) = self.nfs.get(&*cs.nf) { if let Some(&value) = self.nfs.get(&*cs.nf) {
@ -146,46 +99,16 @@ impl MemPool {
} }
for co in tx.outputs.iter() { for co in tx.outputs.iter() {
let od = to_output_description(co); let od = to_output_description(co);
if let Some((note, _)) = if let Some((note, _)) = try_sapling_compact_note_decryption(
try_sapling_compact_note_decryption(self.chain().network(), self.height, ivk, &od) c.chain.network(),
{ BlockHeight::from_u32(height),
ivk,
&od,
) {
balance += note.value as i64; // value is incoming balance += note.value as i64; // value is incoming
} }
} }
balance balance
} }
pub fn set_lwd_url(&mut self, ld_url: &str) -> anyhow::Result<()> {
self.ld_url = ld_url.to_string();
Ok(())
}
fn chain(&self) -> &dyn CoinChain {
get_coin_chain(self.coin_type)
}
}
#[cfg(test)]
mod tests {
use crate::db::DEFAULT_DB_PATH;
use crate::mempool::MemPool;
use crate::{DbAdapter, LWD_URL};
use std::time::Duration;
use zcash_params::coin::CoinType;
#[tokio::test]
async fn test_mempool() {
let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap();
let ivk = db.get_ivk(1).unwrap();
let mut mempool = MemPool::new(CoinType::Zcash, "zec.db");
mempool.set_lwd_url(LWD_URL).unwrap();
mempool.set_ivk(&ivk);
loop {
mempool.scan().await.unwrap();
let unconfirmed = mempool.get_unconfirmed_balance();
println!("{}", unconfirmed);
tokio::time::sleep(Duration::from_secs(10)).await;
}
}
} }

View File

@ -1,8 +1,8 @@
use crate::db::SpendableNote; use crate::db::SpendableNote;
use crate::wallet::RecipientMemo; // use crate::wallet::RecipientMemo;
use crate::{ use crate::api::payment::RecipientMemo;
connect_lightwalletd, get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction, use crate::coinconfig::CoinConfig;
}; use crate::{get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction};
use anyhow::anyhow; use anyhow::anyhow;
use jubjub::Fr; use jubjub::Fr;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
@ -115,7 +115,7 @@ impl TxBuilder {
self.chain() self.chain()
.network() .network()
.hrp_sapling_extended_full_viewing_key(), .hrp_sapling_extended_full_viewing_key(),
&fvk, fvk,
), ),
amount: u64::from(amount), amount: u64::from(amount),
rseed: hex::encode(rseed), rseed: hex::encode(rseed),
@ -249,7 +249,7 @@ impl TxBuilder {
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let ovk = &fvk.fvk.ovk; let ovk = &fvk.fvk.ovk;
let (_, change) = fvk.default_address(); let (_, change) = fvk.default_address();
self.set_change(&ovk, &change)?; self.set_change(ovk, &change)?;
for r in recipients.iter() { for r in recipients.iter() {
let to_addr = RecipientAddress::decode(self.chain().network(), &r.address) let to_addr = RecipientAddress::decode(self.chain().network(), &r.address)
@ -274,7 +274,7 @@ impl TxBuilder {
match &to_addr { match &to_addr {
RecipientAddress::Shielded(_pa) => { RecipientAddress::Shielded(_pa) => {
log::info!("Sapling output: {}", r.amount); log::info!("Sapling output: {}", r.amount);
self.add_z_output(&r.address, ovk, note_amount, &memo) self.add_z_output(&r.address, ovk, note_amount, memo)
} }
RecipientAddress::Transparent(_address) => { RecipientAddress::Transparent(_address) => {
self.add_t_output(&r.address, note_amount) self.add_t_output(&r.address, note_amount)
@ -391,8 +391,9 @@ impl Tx {
} }
} }
pub async fn broadcast_tx(tx: &[u8], ld_url: &str) -> anyhow::Result<String> { pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result<String> {
let mut client = connect_lightwalletd(ld_url).await?; let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
let latest_height = get_latest_height(&mut client).await?; let latest_height = get_latest_height(&mut client).await?;
let raw_tx = RawTransaction { let raw_tx = RawTransaction {
data: tx.to_vec(), data: tx.to_vec(),

View File

@ -25,7 +25,7 @@ pub fn print_witness(w: &IncrementalWitness<Node>) {
print_node(n); print_node(n);
} }
println!("Cursor"); println!("Cursor");
w.cursor.as_ref().map(|c| print_tree(c)); w.cursor.as_ref().map(print_tree);
} }
pub fn print_ctree(t: &CTree) { pub fn print_ctree(t: &CTree) {

View File

@ -76,7 +76,8 @@ impl std::fmt::Debug for Blocks {
} }
} }
pub type ProgressCallback = Arc<Mutex<dyn Fn(u32) + Send>>; pub type ProgressCallback = dyn Fn(u32) + Send;
pub type AMProgressCallback = Arc<Mutex<ProgressCallback>>;
#[derive(PartialEq, PartialOrd, Debug, Hash, Eq)] #[derive(PartialEq, PartialOrd, Debug, Hash, Eq)]
pub struct TxIdHeight { pub struct TxIdHeight {
@ -91,14 +92,14 @@ pub async fn sync_async(
get_tx: bool, get_tx: bool,
db_path: &str, db_path: &str,
target_height_offset: u32, target_height_offset: u32,
progress_callback: ProgressCallback, progress_callback: AMProgressCallback,
ld_url: &str, ld_url: &str,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let ld_url = ld_url.to_owned(); let ld_url = ld_url.to_owned();
let db_path = db_path.to_string(); let db_path = db_path.to_string();
let network = { let network = {
let chain = get_coin_chain(coin_type); let chain = get_coin_chain(coin_type);
chain.network().clone() *chain.network()
}; };
let mut client = connect_lightwalletd(&ld_url).await?; let mut client = connect_lightwalletd(&ld_url).await?;
@ -308,7 +309,7 @@ pub async fn sync_async(
if c == Ordering::Equal { if c == Ordering::Equal {
return a.index.cmp(&b.index); return a.index.cmp(&b.index);
} }
return c; c
}); });
let ids: Vec<_> = ids.into_iter().map(|e| e.id_tx).collect(); let ids: Vec<_> = ids.into_iter().map(|e| e.id_tx).collect();
retrieve_tx_info(coin_type, &mut client, &db_path2, &ids) retrieve_tx_info(coin_type, &mut client, &db_path2, &ids)

View File

@ -1,3 +1,4 @@
use crate::coinconfig::CoinConfig;
use crate::{ use crate::{
AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, GetAddressUtxosReply, AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, GetAddressUtxosReply,
}; };
@ -28,35 +29,30 @@ pub async fn get_taddr_balance(
pub async fn get_utxos( pub async fn get_utxos(
client: &mut CompactTxStreamerClient<Channel>, client: &mut CompactTxStreamerClient<Channel>,
db: &DbAdapter, t_address: &str,
account: u32, _account: u32,
) -> anyhow::Result<Vec<GetAddressUtxosReply>> { ) -> anyhow::Result<Vec<GetAddressUtxosReply>> {
let t_address = db.get_taddr(account)?; let req = GetAddressUtxosArg {
if let Some(t_address) = t_address { addresses: vec![t_address.to_string()],
let req = GetAddressUtxosArg { start_height: 0,
addresses: vec![t_address.to_string()], max_entries: 0,
start_height: 0, };
max_entries: 0, let utxo_rep = client
}; .get_address_utxos(Request::new(req))
let utxo_rep = client .await?
.get_address_utxos(Request::new(req)) .into_inner();
.await? Ok(utxo_rep.address_utxos)
.into_inner();
Ok(utxo_rep.address_utxos)
} else {
Ok(vec![])
}
} }
pub async fn scan_transparent_accounts( pub async fn scan_transparent_accounts(
network: &Network, network: &Network,
client: &mut CompactTxStreamerClient<Channel>, client: &mut CompactTxStreamerClient<Channel>,
db: &DbAdapter,
account: u32,
gap_limit: usize, gap_limit: usize,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let mut addresses = vec![]; let mut addresses = vec![];
let (seed, mut index) = db.get_seed(account)?; let db = c.db()?;
let (seed, mut index) = db.get_seed(c.id_account)?;
if let Some(seed) = seed { if let Some(seed) = seed {
let mut gap = 0; let mut gap = 0;
while gap < gap_limit { while gap < gap_limit {
@ -86,10 +82,10 @@ pub fn derive_tkeys(
phrase: &str, phrase: &str,
path: &str, path: &str,
) -> anyhow::Result<(String, String)> { ) -> anyhow::Result<(String, String)> {
let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?; let mnemonic = Mnemonic::from_phrase(phrase, Language::English)?;
let seed = Seed::new(&mnemonic, ""); let seed = Seed::new(&mnemonic, "");
let secp = Secp256k1::<All>::new(); let secp = Secp256k1::<All>::new();
let ext = ExtendedPrivKey::derive(&seed.as_bytes(), path).unwrap(); let ext = ExtendedPrivKey::derive(seed.as_bytes(), path).unwrap();
let secret_key = SecretKey::from_slice(&ext.secret()).unwrap(); let secret_key = SecretKey::from_slice(&ext.secret()).unwrap();
let pub_key = PublicKey::from_secret_key(&secp, &secret_key); let pub_key = PublicKey::from_secret_key(&secp, &secret_key);
let pub_key = pub_key.serialize(); let pub_key = pub_key.serialize();

View File

@ -1,5 +1,6 @@
use crate::contact::{Contact, ContactDecoder}; use crate::contact::{Contact, ContactDecoder};
use crate::wallet::decode_memo; // use crate::wallet::decode_memo;
use crate::api::payment::decode_memo;
use crate::{CompactTxStreamerClient, DbAdapter, TxFilter}; use crate::{CompactTxStreamerClient, DbAdapter, TxFilter};
use anyhow::anyhow; use anyhow::anyhow;
use futures::StreamExt; use futures::StreamExt;
@ -54,7 +55,7 @@ pub async fn decode_transaction(
timestamp: u32, timestamp: u32,
index: u32, index: u32,
) -> anyhow::Result<TransactionInfo> { ) -> anyhow::Result<TransactionInfo> {
let consensus_branch_id = get_branch(network, u32::from(height)); let consensus_branch_id = get_branch(network, height);
let ivk = fvk.fvk.vk.ivk(); let ivk = fvk.fvk.vk.ivk();
let ovk = fvk.fvk.ovk; let ovk = fvk.fvk.ovk;
@ -112,7 +113,7 @@ pub async fn decode_transaction(
tx_memo = memo; tx_memo = memo;
} }
} else if let Some((_note, pa, memo)) = } else if let Some((_note, pa, memo)) =
try_sapling_output_recovery(network, height, &ovk, &output) try_sapling_output_recovery(network, height, &ovk, output)
{ {
zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa); zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa);
let memo = Memo::try_from(memo)?; let memo = Memo::try_from(memo)?;
@ -176,7 +177,7 @@ pub async fn retrieve_tx_info(
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let network = { let network = {
let chain = get_coin_chain(coin_type); let chain = get_coin_chain(coin_type);
chain.network().clone() *chain.network()
}; };
let db = DbAdapter::new(coin_type, db_path)?; let db = DbAdapter::new(coin_type, db_path)?;

View File

@ -10,7 +10,7 @@ pub struct MyReceiver {
impl FromAddress for MyReceiver { impl FromAddress for MyReceiver {
fn from_sapling(net: Network, data: [u8; 43]) -> Result<Self, UnsupportedAddress> { fn from_sapling(net: Network, data: [u8; 43]) -> Result<Self, UnsupportedAddress> {
Ok(MyReceiver { Ok(MyReceiver {
net: net, net,
receiver: Receiver::Sapling(data), receiver: Receiver::Sapling(data),
}) })
} }
@ -20,19 +20,19 @@ impl FromAddress for MyReceiver {
match r { match r {
Receiver::Sapling(data) => { Receiver::Sapling(data) => {
return Ok(MyReceiver { return Ok(MyReceiver {
net: net, net,
receiver: Receiver::Sapling(data.clone()), receiver: Receiver::Sapling(*data),
}); });
} }
_ => (), _ => (),
} }
} }
return FromAddress::from_unified(net, data); FromAddress::from_unified(net, data)
} }
fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Result<Self, UnsupportedAddress> { fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Result<Self, UnsupportedAddress> {
Ok(MyReceiver { Ok(MyReceiver {
net: net, net,
receiver: Receiver::P2pkh(data), receiver: Receiver::P2pkh(data),
}) })
} }

View File

@ -5,7 +5,7 @@ use crate::key::KeyHelpers;
use crate::pay::Tx; use crate::pay::Tx;
use crate::pay::TxBuilder; use crate::pay::TxBuilder;
use crate::prices::fetch_historical_prices; use crate::prices::fetch_historical_prices;
use crate::scan::ProgressCallback; use crate::scan::AM_ProgressCallback;
use crate::taddr::{get_taddr_balance, get_utxos, scan_transparent_accounts}; use crate::taddr::{get_taddr_balance, get_utxos, scan_transparent_accounts};
use crate::{ use crate::{
broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree, CompactTxStreamerClient, broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree, CompactTxStreamerClient,
@ -92,7 +92,7 @@ pub struct RecipientMemo {
} }
impl RecipientMemo { impl RecipientMemo {
fn from_recipient(from: &str, r: &Recipient) -> Self { pub fn from_recipient(from: &str, r: &Recipient) -> Self {
let memo = if !r.reply_to && r.subject.is_empty() { let memo = if !r.reply_to && r.subject.is_empty() {
r.memo.clone() r.memo.clone()
} else { } else {
@ -224,7 +224,7 @@ impl Wallet {
db_path: &str, db_path: &str,
chunk_size: u32, chunk_size: u32,
target_height_offset: u32, target_height_offset: u32,
progress_callback: ProgressCallback, progress_callback: AM_ProgressCallback,
ld_url: &str, ld_url: &str,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
crate::scan::sync_async( crate::scan::sync_async(