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"
harness = false
[[bin]]
name = "warp-cli"
path = "src/main/warp_cli.rs"
#[[bin]]
#name = "warp-cli"
#path = "src/main/warp_cli.rs"
#[[bin]]
#name = "ledger"
#path = "src/main/ledger.rs"
[[bin]]
name = "sign"
path = "src/main/sign.rs"
#[[bin]]
#name = "sign"
#path = "src/main/sign.rs"
[lib]
name = "warp_api_ffi"
crate-type = ["rlib"]
[dependencies]
dotenv = "0.15.0"
@ -70,9 +74,14 @@ hmac = { version = "0.12.1", optional = true }
ed25519-bip32 = { version = "0.4.1", 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]
ledger = ["ledger-apdu", "hmac", "ed25519-bip32", "ledger-transport-hid"]
ledger_sapling = ["ledger"]
# dart_ffi = ["allo-isolate", "once_cell", "android_logger"]
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
@ -106,6 +115,7 @@ rev = "466806932d21597eb4f89a449347fa1983dffb22"
[build-dependencies]
tonic-build = "0.7.2"
cbindgen = "0.19.0"
[dev-dependencies]
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"],
)
.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)]
fn batch_node_combine1(depth: usize, left: &Node, right: &Node) -> ExtendedPoint {
// 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)]
@ -63,15 +63,13 @@ impl Builder<CTree, ()> for CTreeBuilder {
self.next_tree.right = None;
m - 1
}
} else if m % 2 == 0 {
self.next_tree.parents.push(None);
m
} else {
if m % 2 == 0 {
self.next_tree.parents.push(None);
m
} else {
let last_node = Self::get(commitments, m - 1, &offset);
self.next_tree.parents.push(Some(*last_node));
m - 1
}
let last_node = Self::get(commitments, m - 1, &offset);
self.next_tree.parents.push(Some(*last_node));
m - 1
}
} else {
0
@ -240,13 +238,11 @@ impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
tree.left = Some(*CTreeBuilder::get(commitments, rp, &offset));
tree.right = None;
}
} else {
if self.p % 2 == 1 {
tree.parents
.push(Some(*CTreeBuilder::get(commitments, rp - 1, &offset)));
} else if self.p != 0 {
tree.parents.push(None);
}
} else if self.p % 2 == 1 {
tree.parents
.push(Some(*CTreeBuilder::get(commitments, rp - 1, &offset)));
} else if self.p != 0 {
tree.parents.push(None);
}
}
@ -270,10 +266,8 @@ impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
if tree.right.is_none() {
self.witness.filled.push(*p1);
}
} else {
if depth - 1 >= tree.parents.len() || tree.parents[depth - 1].is_none() {
self.witness.filled.push(*p1);
}
} else if depth > tree.parents.len() || tree.parents[depth - 1].is_none() {
self.witness.filled.push(*p1);
}
}
0
@ -294,7 +288,7 @@ impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
let mut final_position = context.prev_tree.get_position() as u32;
let mut witness_position = self.witness.tree.get_position() as u32;
assert_ne!(witness_position, 0);
witness_position = witness_position - 1;
witness_position -= 1;
// look for first not equal bit in MSB order
final_position = final_position.reverse_bits();
@ -434,11 +428,11 @@ mod tests {
nodes: &[Node],
) {
for n in nodes.iter() {
tree.append(n.clone()).unwrap();
tree.append(*n).unwrap();
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);
}
}
@ -450,8 +444,8 @@ mod tests {
w2.write(&mut bb2).unwrap();
if bb1 != bb2 {
print_witness(&w1);
print_witness2(&w2);
print_witness(w1);
print_witness2(w2);
assert!(false);
}
@ -656,7 +650,7 @@ mod tests {
failed_index = Some(i);
println!("FAILED AT {}", i);
println!("GOOD");
print_witness(&w1);
print_witness(w1);
if let Some(ref c) = w1.cursor {
print_tree(c);
} else {
@ -664,7 +658,7 @@ mod tests {
}
println!("BAD");
print_witness2(&w2);
print_witness2(w2);
}
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 mut enc_ciphertext = [0u8; 52];
enc_ciphertext.copy_from_slice(&co.ciphertext);
let od = CompactOutputDescription {
CompactOutputDescription {
ephemeral_key: EphemeralKeyBytes::from(epk),
cmu,
enc_ciphertext,
};
od
}
}
struct AccountOutput<'a, N: Parameters> {
@ -208,7 +208,7 @@ impl<'a, N: Parameters> AccountOutput<'a, N> {
let epk = EphemeralKeyBytes::from(epk_bytes);
let mut cmu_bytes = [0u8; 32];
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];
ciphertext_bytes.copy_from_slice(&co.ciphertext);
@ -508,7 +508,7 @@ mod tests {
use crate::db::AccountViewKey;
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
use crate::LWD_URL;
use dotenv;
use std::collections::HashMap;
use std::time::Instant;
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> {
let tree = CTree::read(&mut reader)?;
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 {
position: 0,
@ -147,9 +147,9 @@ impl CTree {
}
pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
let left = Optional::read(&mut reader, |r| Node::read(r))?;
let right = Optional::read(&mut reader, |r| Node::read(r))?;
let parents = Vector::read(&mut reader, |r| Optional::read(r, |r| Node::read(r)))?;
let left = Optional::read(&mut reader, Node::read)?;
let right = Optional::read(&mut reader, Node::read)?;
let parents = Vector::read(&mut reader, |r| Optional::read(r, Node::read))?;
Ok(CTree {
left,

View File

@ -63,7 +63,7 @@ impl ContactDecoder {
if !self.has_contacts {
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)?;
Ok(contacts)
}
@ -84,39 +84,3 @@ impl ContactDecoder {
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>,
ivk: &str,
address: &str,
) -> anyhow::Result<i32> {
) -> anyhow::Result<(u32, bool)> {
let mut statement = self
.connection
.prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?;
if statement.exists(params![ivk])? {
return Ok(-1);
}
let exists = statement.exists(params![ivk])?;
self.connection.execute(
"INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
ON CONFLICT DO NOTHING",
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",
params![ivk],
|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(
"SELECT MAX(aindex) FROM accounts WHERE seed = ?1",
[seed],
@ -140,7 +138,7 @@ impl DbAdapter {
Ok(aindex.unwrap_or(-1))
},
)? + 1;
Ok(index)
Ok(index as u32)
}
pub fn get_fvks(&self) -> anyhow::Result<HashMap<u32, AccountViewKey>> {
@ -504,6 +502,15 @@ impl DbAdapter {
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<()> {
log::debug!("+mark_spent");
tx.execute(
@ -663,7 +670,7 @@ impl DbAdapter {
},
)
.optional()?
.unwrap_or_else(|| [0u8; 11]);
.unwrap_or([0u8; 11]);
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 diversifiers", [])?;
connection.execute("DROP TABLE historical_prices", [])?;
update_schema_version(&connection, 0)?;
update_schema_version(connection, 0)?;
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 {
connection.execute(
@ -174,7 +174,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
}
if version != 3 {
update_schema_version(&connection, 3)?;
update_schema_version(connection, 3)?;
log::info!("Database migrated");
}

View File

@ -19,7 +19,7 @@ fn read_generators_bin() -> Vec<ExtendedNielsPoint> {
for _j in 0..32 {
for _k in 0..256 {
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())
.to_niels();
gens.push(p);
@ -52,8 +52,7 @@ type Hash = [u8; 32];
pub fn pedersen_hash(depth: u8, left: &Hash, right: &Hash) -> Hash {
let p = pedersen_hash_inner(depth, left, right);
let h = jubjub::ExtendedPoint::from(p).to_affine().get_u().to_repr();
h
p.to_affine().get_u().to_repr()
}
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_byteoffset += 1;
} else if byteoffset == 63 {
r = r >> 8;
r >>= 8;
}
}

View File

@ -30,16 +30,16 @@ impl KeyHelpers {
index: u32,
) -> anyhow::Result<(Option<String>, Option<String>, String, String)> {
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)?;
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)
decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key)
{
let (ivk, pa) = self.derive_viewing_key(&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)
decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key)
{
let pa = self.derive_address(&fvk)?;
Ok((None, None, key.to_string(), pa))
@ -51,16 +51,16 @@ impl KeyHelpers {
pub fn is_valid_key(&self, key: &str) -> i8 {
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;
}
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;
}
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;
}
@ -73,7 +73,7 @@ impl KeyHelpers {
index: u32,
) -> anyhow::Result<(String, String, String)> {
let network = self.chain().network();
let seed = Seed::new(&mnemonic, "");
let seed = Seed::new(mnemonic, "");
let master = ExtendedSpendingKey::master(seed.as_bytes());
let path = [
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 chain;
mod coinconfig;
mod commitment;
mod contact;
mod db;
mod hash;
mod key;
mod key2;
mod mempool;
mod pay;
mod prices;
@ -34,7 +36,8 @@ mod scan;
mod taddr;
mod transaction;
mod ua;
mod wallet;
// mod wallet;
mod api;
#[cfg(feature = "ledger")]
mod ledger;
@ -72,7 +75,7 @@ pub use crate::pay::{broadcast_tx, Tx, TxIn, TxOut};
pub use crate::print::*;
pub use crate::scan::{latest_height, scan_all, sync_async};
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")]
pub use crate::ledger::sapling::build_tx_ledger;

View File

@ -68,8 +68,8 @@ fn test_increasing_notes() {
fn mk_node(pos: usize) -> Node {
let mut bb = [0u8; 32];
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) {

View File

@ -1,13 +1,12 @@
use crate::chain::to_output_description;
use crate::{
connect_lightwalletd, get_latest_height, CompactTx, CompactTxStreamerClient, DbAdapter, Exclude,
};
use crate::{CompactTx, CompactTxStreamerClient, Exclude};
use std::collections::HashMap;
use tonic::transport::Channel;
use tonic::Request;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_params::coin::{get_coin_chain, CoinChain, CoinType};
use zcash_primitives::consensus::{BlockHeight, Parameters};
use crate::coinconfig::CoinConfig;
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::SaplingIvk;
@ -20,85 +19,38 @@ struct MemPoolTransacton {
}
pub struct MemPool {
coin_type: CoinType,
db_path: String,
account: u32,
ivk: Option<SaplingIvk>,
height: BlockHeight,
coin: u8,
transactions: HashMap<Vec<u8>, MemPoolTransacton>,
nfs: HashMap<Vec<u8>, u64>,
balance: i64,
ld_url: String,
}
impl MemPool {
pub fn new(coin_type: CoinType, db_path: &str) -> MemPool {
pub fn new(coin: u8) -> MemPool {
MemPool {
coin_type,
db_path: db_path.to_string(),
account: 0,
ivk: None,
height: BlockHeight::from(0),
coin,
transactions: HashMap::new(),
nfs: HashMap::new(),
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 {
self.balance
}
pub fn clear(&mut self, height: u32) -> anyhow::Result<()> {
let db = DbAdapter::new(self.coin_type, &self.db_path)?;
self.height = BlockHeight::from_u32(height);
self.nfs = db.get_nullifier_amounts(self.account, true)?;
pub fn clear(&mut self) -> anyhow::Result<()> {
let c = CoinConfig::get(self.coin);
self.nfs = c.db()?.get_nullifier_amounts(c.id_account, true)?;
self.transactions.clear();
self.balance = 0;
Ok(())
}
async fn update(
pub async fn update(
&mut self,
client: &mut CompactTxStreamerClient<Channel>,
height: u32,
ivk: &SaplingIvk,
) -> anyhow::Result<()> {
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
}
None => {
let balance = self.scan_transaction(&tx, ivk);
let balance = self.scan_transaction(height, &tx, ivk);
let mempool_tx = MemPoolTransacton {
balance,
exclude_len: DEFAULT_EXCLUDE_LEN,
@ -136,7 +88,8 @@ impl MemPool {
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;
for cs in tx.spends.iter() {
if let Some(&value) = self.nfs.get(&*cs.nf) {
@ -146,46 +99,16 @@ impl MemPool {
}
for co in tx.outputs.iter() {
let od = to_output_description(co);
if let Some((note, _)) =
try_sapling_compact_note_decryption(self.chain().network(), self.height, ivk, &od)
{
if let Some((note, _)) = try_sapling_compact_note_decryption(
c.chain.network(),
BlockHeight::from_u32(height),
ivk,
&od,
) {
balance += note.value as i64; // value is incoming
}
}
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::wallet::RecipientMemo;
use crate::{
connect_lightwalletd, get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction,
};
// use crate::wallet::RecipientMemo;
use crate::api::payment::RecipientMemo;
use crate::coinconfig::CoinConfig;
use crate::{get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction};
use anyhow::anyhow;
use jubjub::Fr;
use rand::prelude::SliceRandom;
@ -115,7 +115,7 @@ impl TxBuilder {
self.chain()
.network()
.hrp_sapling_extended_full_viewing_key(),
&fvk,
fvk,
),
amount: u64::from(amount),
rseed: hex::encode(rseed),
@ -249,7 +249,7 @@ impl TxBuilder {
) -> anyhow::Result<()> {
let ovk = &fvk.fvk.ovk;
let (_, change) = fvk.default_address();
self.set_change(&ovk, &change)?;
self.set_change(ovk, &change)?;
for r in recipients.iter() {
let to_addr = RecipientAddress::decode(self.chain().network(), &r.address)
@ -274,7 +274,7 @@ impl TxBuilder {
match &to_addr {
RecipientAddress::Shielded(_pa) => {
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) => {
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> {
let mut client = connect_lightwalletd(ld_url).await?;
pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
let latest_height = get_latest_height(&mut client).await?;
let raw_tx = RawTransaction {
data: tx.to_vec(),

View File

@ -25,7 +25,7 @@ pub fn print_witness(w: &IncrementalWitness<Node>) {
print_node(n);
}
println!("Cursor");
w.cursor.as_ref().map(|c| print_tree(c));
w.cursor.as_ref().map(print_tree);
}
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)]
pub struct TxIdHeight {
@ -91,14 +92,14 @@ pub async fn sync_async(
get_tx: bool,
db_path: &str,
target_height_offset: u32,
progress_callback: ProgressCallback,
progress_callback: AMProgressCallback,
ld_url: &str,
) -> anyhow::Result<()> {
let ld_url = ld_url.to_owned();
let db_path = db_path.to_string();
let network = {
let chain = get_coin_chain(coin_type);
chain.network().clone()
*chain.network()
};
let mut client = connect_lightwalletd(&ld_url).await?;
@ -308,7 +309,7 @@ pub async fn sync_async(
if c == Ordering::Equal {
return a.index.cmp(&b.index);
}
return c;
c
});
let ids: Vec<_> = ids.into_iter().map(|e| e.id_tx).collect();
retrieve_tx_info(coin_type, &mut client, &db_path2, &ids)

View File

@ -1,3 +1,4 @@
use crate::coinconfig::CoinConfig;
use crate::{
AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, GetAddressUtxosReply,
};
@ -28,35 +29,30 @@ pub async fn get_taddr_balance(
pub async fn get_utxos(
client: &mut CompactTxStreamerClient<Channel>,
db: &DbAdapter,
account: u32,
t_address: &str,
_account: u32,
) -> anyhow::Result<Vec<GetAddressUtxosReply>> {
let t_address = db.get_taddr(account)?;
if let Some(t_address) = t_address {
let req = GetAddressUtxosArg {
addresses: vec![t_address.to_string()],
start_height: 0,
max_entries: 0,
};
let utxo_rep = client
.get_address_utxos(Request::new(req))
.await?
.into_inner();
Ok(utxo_rep.address_utxos)
} else {
Ok(vec![])
}
let req = GetAddressUtxosArg {
addresses: vec![t_address.to_string()],
start_height: 0,
max_entries: 0,
};
let utxo_rep = client
.get_address_utxos(Request::new(req))
.await?
.into_inner();
Ok(utxo_rep.address_utxos)
}
pub async fn scan_transparent_accounts(
network: &Network,
client: &mut CompactTxStreamerClient<Channel>,
db: &DbAdapter,
account: u32,
gap_limit: usize,
) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
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 {
let mut gap = 0;
while gap < gap_limit {
@ -86,10 +82,10 @@ pub fn derive_tkeys(
phrase: &str,
path: &str,
) -> 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 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 pub_key = PublicKey::from_secret_key(&secp, &secret_key);
let pub_key = pub_key.serialize();

View File

@ -1,5 +1,6 @@
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 anyhow::anyhow;
use futures::StreamExt;
@ -54,7 +55,7 @@ pub async fn decode_transaction(
timestamp: u32,
index: u32,
) -> 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 ovk = fvk.fvk.ovk;
@ -112,7 +113,7 @@ pub async fn decode_transaction(
tx_memo = 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);
let memo = Memo::try_from(memo)?;
@ -176,7 +177,7 @@ pub async fn retrieve_tx_info(
) -> anyhow::Result<()> {
let network = {
let chain = get_coin_chain(coin_type);
chain.network().clone()
*chain.network()
};
let db = DbAdapter::new(coin_type, db_path)?;

View File

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

View File

@ -5,7 +5,7 @@ use crate::key::KeyHelpers;
use crate::pay::Tx;
use crate::pay::TxBuilder;
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::{
broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree, CompactTxStreamerClient,
@ -92,7 +92,7 @@ pub struct 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() {
r.memo.clone()
} else {
@ -224,7 +224,7 @@ impl Wallet {
db_path: &str,
chunk_size: u32,
target_height_offset: u32,
progress_callback: ProgressCallback,
progress_callback: AM_ProgressCallback,
ld_url: &str,
) -> anyhow::Result<()> {
crate::scan::sync_async(