Refactor Dart FFI
This commit is contained in:
parent
6c5e8a6da7
commit
7827f21f98
22
Cargo.toml
22
Cargo.toml
|
@ -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"
|
||||
|
|
|
@ -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);
|
18
build.rs
18
build.rs
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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, ¤cy).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()))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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("es, currency)?;
|
||||
Ok(quotes.len() as u32)
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(¬e_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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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,8 +63,7 @@ impl Builder<CTree, ()> for CTreeBuilder {
|
|||
self.next_tree.right = None;
|
||||
m - 1
|
||||
}
|
||||
} else {
|
||||
if m % 2 == 0 {
|
||||
} else if m % 2 == 0 {
|
||||
self.next_tree.parents.push(None);
|
||||
m
|
||||
} else {
|
||||
|
@ -72,7 +71,6 @@ impl Builder<CTree, ()> for CTreeBuilder {
|
|||
self.next_tree.parents.push(Some(*last_node));
|
||||
m - 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
@ -240,15 +238,13 @@ impl Builder<Witness, CTreeBuilder> for WitnessBuilder {
|
|||
tree.left = Some(*CTreeBuilder::get(commitments, rp, &offset));
|
||||
tree.right = None;
|
||||
}
|
||||
} else {
|
||||
if self.p % 2 == 1 {
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let right = if depth != 0 && !context.first_block {
|
||||
context.right
|
||||
|
@ -270,12 +266,10 @@ 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() {
|
||||
} 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());
|
||||
}
|
||||
|
|
10
src/chain.rs
10
src/chain.rs
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
25
src/db.rs
25
src/db.rs
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
14
src/key.rs
14
src/key.rs
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
121
src/mempool.rs
121
src/mempool.rs
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
19
src/pay.rs
19
src/pay.rs
|
@ -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(),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
20
src/taddr.rs
20
src/taddr.rs
|
@ -1,3 +1,4 @@
|
|||
use crate::coinconfig::CoinConfig;
|
||||
use crate::{
|
||||
AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, GetAddressUtxosReply,
|
||||
};
|
||||
|
@ -28,11 +29,9 @@ 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,
|
||||
|
@ -43,20 +42,17 @@ pub async fn get_utxos(
|
|||
.await?
|
||||
.into_inner();
|
||||
Ok(utxo_rep.address_utxos)
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
@ -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)?;
|
||||
|
||||
|
|
10
src/ua.rs
10
src/ua.rs
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue