Documentation

This commit is contained in:
Hanh 2022-10-20 14:24:36 +08:00
parent 49eeb7bd54
commit 267192c42c
24 changed files with 381 additions and 134 deletions

View File

@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, Criterion};
use sync::scan_all;
use warp_api_ffi::scan_all;
use tokio::runtime::Runtime;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_primitives::consensus::{Network, Parameters};

View File

@ -0,0 +1,19 @@
[package]
name = "warpsync-rust-example"
version = "1.2.15"
authors = ["Hanh <hanh425@gmail.com>"]
edition = "2021"
[[bin]]
name = "warp-example"
path = "src/main.rs"
[dependencies]
tokio = { version = "^1.6", features = ["macros", "rt-multi-thread"] }
lazy_static = "1.4.0"
env_logger = "0.9"
log = "0.4"
[dependencies.zcash-warpsync]
path = "../.."

View File

@ -0,0 +1,42 @@
use warp_api_ffi::api::account::{get_backup, new_account};
use warp_api_ffi::api::sync::coin_sync;
use warp_api_ffi::{CoinConfig, init_coin, set_coin_lwd_url};
use lazy_static::lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref CANCEL: Mutex<bool> = Mutex::new(false);
}
const FVK: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz";
#[tokio::main]
async fn main() {
env_logger::init();
// Initialize the library for Zcash (coin = 0)
init_coin(0, "./zec.db").unwrap();
set_coin_lwd_url(0, "https://lwdv3.zecwallet.co:443"); // ZecWallet Lightwalletd URL
// Create a new account with the ZEC pages viewing key
let id_account = new_account(0, "test_account", Some(FVK.to_string()),
None).unwrap();
// Synchronize
coin_sync(0 /* zcash */,
true /* retrieve tx details */,
0 /* sync to tip */,
100 /* spam filter threshold */, |p| {
log::info!("Progress: {}", p.height);
}, &CANCEL).await.unwrap();
// Grab the database accessor
let cc = &CoinConfig::get(0 /* zcash */);
let db = cc.db.as_ref().unwrap().clone();
let db = db.lock().unwrap();
// Query the account balance
let balance = db.get_balance(id_account).unwrap();
println!("Balance = {}", balance)
}

View File

@ -1,3 +1,5 @@
//! Account related API
// Account creation
use crate::coinconfig::CoinConfig;
@ -5,7 +7,7 @@ use crate::db::AccountData;
use crate::key2::decode_key;
use crate::taddr::{derive_taddr, derive_tkeys};
use crate::transaction::retrieve_tx_info;
use crate::{connect_lightwalletd, derive_zip32, AccountInfo, KeyPack};
use crate::{connect_lightwalletd, AccountInfo, KeyPack};
use anyhow::anyhow;
use bip39::{Language, Mnemonic};
use rand::rngs::OsRng;
@ -14,7 +16,21 @@ use std::fs::File;
use std::io::BufReader;
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, encode_payment_address};
use zcash_primitives::consensus::Parameters;
use crate::zip32::derive_zip32;
/// Create a new account
/// # Arguments
///
/// * `coin`: 0 for zcash, 1 for ycash
/// * `name`: account name
/// * `key`: `Some(key)` where key is either a passphrase,
/// a secret key or a viewing key for an existing account,
/// or `None` for a new randomly generated account
/// * `index`: `Some(x)` for account at index `x` or
/// `None` for main account (same as x = 0)
///
/// # Returns
/// `account id`
pub fn new_account(
coin: u8,
name: &str,
@ -34,6 +50,17 @@ pub fn new_account(
Ok(id_account)
}
/// Create one or many sub accounts of the current account
///
/// # Example
/// ```rust
/// crate::api::account::new_sub_account("test", None, 5)
/// ```
///
/// # Arguments
/// * `name`: name of the sub accounts. Every sub account will have the same name
/// * `index`: Starting index. If `None`, use the index following the highest used index
/// * `count`: Number of subaccounts to create
pub fn new_sub_account(name: &str, index: Option<u32>, count: u32) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let db = c.db()?;
@ -59,6 +86,14 @@ fn new_account_with_key(coin: u8, name: &str, key: &str, index: u32) -> anyhow::
Ok(account)
}
/// Update the transparent secret key for the given account from a derivation path
///
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `id_account`: account id as returned from [new_account]
/// * `path`: derivation path
///
/// Account must have a seed phrase
pub fn import_transparent_key(coin: u8, id_account: u32, path: &str) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
@ -69,6 +104,12 @@ pub fn import_transparent_key(coin: u8, id_account: u32, path: &str) -> anyhow::
Ok(())
}
/// Update the transparent secret key for the given account
///
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `id_account`: account id as returned from [new_account]
/// * `sk`: secret key
pub fn import_transparent_secret_key(coin: u8, id_account: u32, sk: &str) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
@ -77,6 +118,7 @@ pub fn import_transparent_secret_key(coin: u8, id_account: u32, sk: &str) -> any
Ok(())
}
/// Generate a new diversified address
pub fn new_diversified_address() -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let db = c.db()?;
@ -96,11 +138,16 @@ pub fn new_diversified_address() -> anyhow::Result<String> {
Ok(pa)
}
/// Retrieve the transparent balance for the current account from the LWD server
pub async fn get_taddr_balance_default() -> anyhow::Result<u64> {
let c = CoinConfig::get_active();
get_taddr_balance(c.coin, c.id_account).await
}
/// Retrieve the transparent balance from the LWD server
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `id_account`: account id as returned from [new_account]
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?;
@ -112,6 +159,10 @@ pub async fn get_taddr_balance(coin: u8, id_account: u32) -> anyhow::Result<u64>
Ok(balance)
}
/// Look for accounts that have some transparent balance. Stop when the gap limit
/// is exceeded and no balance was found
/// # Arguments
/// * `gap_limit`: number of accounts with 0 balance before the scan stops
pub async fn scan_transparent_accounts(gap_limit: usize) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
@ -119,8 +170,12 @@ pub async fn scan_transparent_accounts(gap_limit: usize) -> anyhow::Result<()> {
Ok(())
}
// Account backup
/// Get the backup string. It is either the passphrase, the secret key or the viewing key
/// depending on how the account was created
/// # Arguments
/// * `id_account`: account id as returned from [new_account]
///
/// Use the current active coin
pub fn get_backup(account: u32) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let AccountData { seed, sk, fvk, .. } = c.db()?.get_account_info(account)?;
@ -133,30 +188,44 @@ pub fn get_backup(account: u32) -> anyhow::Result<String> {
Ok(fvk)
}
/// Get the secret key. Returns empty string if the account has no secret key
/// # Arguments
/// * `id_account`: account id as returned from [new_account]
///
/// Use the current active coin
pub fn get_sk(account: u32) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let AccountData { sk, .. } = c.db()?.get_account_info(account)?;
Ok(sk.unwrap_or(String::new()))
}
/// Reset the database
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
pub fn reset_db(coin: u8) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
db.reset_db()
}
/// Truncate all non account data for the current active coin
pub fn truncate_data() -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let db = c.db()?;
db.truncate_data()
}
/// Truncate all synchronization data for the current active coin
pub fn truncate_sync_data() -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let db = c.db()?;
db.truncate_sync_data()
}
/// Delete an account
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `id_account`: account id as returned from [new_account]
pub fn delete_account(coin: u8, account: u32) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
@ -164,9 +233,14 @@ pub fn delete_account(coin: u8, account: u32) -> anyhow::Result<()> {
Ok(())
}
/// Import a ZWL data file
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `name`: prefix for the imported accounts
/// * `data`: data file
pub fn import_from_zwl(coin: u8, name: &str, data: &str) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let sks = crate::read_zwl(data)?;
let sks = crate::misc::read_zwl(data)?;
let db = c.db()?;
for (i, key) in sks.iter().enumerate() {
let name = format!("{}-{}", name, i + 1);
@ -176,6 +250,13 @@ pub fn import_from_zwl(coin: u8, name: &str, data: &str) -> anyhow::Result<()> {
Ok(())
}
/// Derive keys using Zip-32
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `id_account`: account id as returned from [new_account]. Must have a passphrase
/// * `account`: derived account index
/// * `external`: external/internal
/// * `address`: address index
pub fn derive_keys(
coin: u8,
id_account: u32,
@ -190,6 +271,10 @@ pub fn derive_keys(
derive_zip32(c.chain.network(), &seed, account, external, address)
}
/// Import synchronization data obtained from external source
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `file`: file that contains the synchronization data
pub async fn import_sync_data(coin: u8, file: &str) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let mut db = c.db()?;

View File

@ -1,3 +1,5 @@
//! Contact Address book
use crate::api::payment::{build_sign_send_multi_payment, RecipientMemo};
use crate::api::sync::get_latest_height;
use crate::coinconfig::CoinConfig;
@ -5,6 +7,12 @@ use crate::contact::{serialize_contacts, Contact};
use crate::db::AccountData;
use zcash_primitives::memo::Memo;
/// Store contact in the database
/// # Arguments
/// * `id`: contact id
/// * `name`: contact name
/// * `address`: contact address
/// * `dirty`: true if the database hasn't been saved to the blockchain yet
pub fn store_contact(id: u32, name: &str, address: &str, dirty: bool) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
let contact = Contact {
@ -16,6 +24,9 @@ pub fn store_contact(id: u32, name: &str, address: &str, dirty: bool) -> anyhow:
Ok(())
}
/// Save the new/modified contacts to the blockchain
/// # Arguments
/// * `anchor_offset`: minimum confirmations required for note selection
pub async fn commit_unsaved_contacts(anchor_offset: u32) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let contacts = c.db()?.get_unsaved_contacts()?;
@ -24,7 +35,7 @@ pub async fn commit_unsaved_contacts(anchor_offset: u32) -> anyhow::Result<Strin
Ok(tx_id)
}
pub async fn save_contacts_tx(memos: &[Memo], anchor_offset: u32) -> anyhow::Result<String> {
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 AccountData { address, .. } = c.db()?.get_account_info(c.id_account)?;

View File

@ -8,13 +8,11 @@ use log::Level;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::atomic::AtomicBool;
use std::sync::Mutex;
use tokio::sync::Semaphore;
use zcash_primitives::transaction::builder::Progress;
static mut POST_COBJ: Option<ffi::DartPostCObjectFnType> = None;
static IS_ERROR: AtomicBool = AtomicBool::new(false);
const MAX_COINS: u8 = 3;
@ -90,56 +88,12 @@ fn try_init_logger() {
let _ = env_logger::try_init();
}
// fn log_result<T: Default>(result: anyhow::Result<T>) -> T {
// match result {
// Err(err) => {
// log::error!("ERROR: {}", err);
// let last_error = LAST_ERROR.lock().unwrap();
// last_error.replace(err.to_string());
// IS_ERROR.store(true, Ordering::Release);
// T::default()
// }
// Ok(v) => {
// IS_ERROR.store(false, Ordering::Release);
// v
// }
// }
// }
//
// fn log_string(result: anyhow::Result<String>) -> String {
// match result {
// Err(err) => {
// log::error!("{}", err);
// let last_error = LAST_ERROR.lock().unwrap();
// last_error.replace(err.to_string());
// IS_ERROR.store(true, Ordering::Release);
// format!("{}", err)
// }
// Ok(v) => {
// IS_ERROR.store(false, Ordering::Release);
// v
// }
// }
// }
#[repr(C)]
pub struct CResult<T> {
value: T,
error: *mut c_char,
}
// #[no_mangle]
// pub unsafe extern "C" fn get_error() -> bool {
// IS_ERROR.load(Ordering::Acquire)
// }
//
// #[no_mangle]
// pub unsafe extern "C" fn get_error_msg() -> *mut c_char {
// let error = LAST_ERROR.lock().unwrap();
// let e = error.take();
// to_c_str(e)
// }
#[no_mangle]
pub unsafe extern "C" fn init_wallet(db_path: *mut c_char) {
try_init_logger();
@ -254,14 +208,6 @@ pub async unsafe extern "C" fn warp(
get_tx,
anchor_offset,
max_cost,
move |downloaded| {
let mut downloaded = downloaded.into_dart();
if port != 0 {
if let Some(p) = POST_COBJ {
p(port, &mut downloaded);
}
}
},
move |progress| {
let mut progress = serde_json::to_string(&progress).unwrap().into_dart();
if port != 0 {
@ -588,7 +534,7 @@ pub unsafe extern "C" fn parse_payment_uri(uri: *mut c_char) -> CResult<*mut c_c
#[no_mangle]
pub unsafe extern "C" fn generate_random_enc_key() -> CResult<*mut c_char> {
to_cresult_str(crate::key::generate_random_enc_key())
to_cresult_str(crate::api::fullbackup::generate_random_enc_key())
}
#[no_mangle]
@ -624,7 +570,7 @@ pub unsafe extern "C" fn restore_full_backup(key: *mut c_char, backup: *mut c_ch
pub unsafe extern "C" fn split_data(id: u32, data: *mut c_char) -> CResult<*mut c_char> {
from_c_str!(data);
let res = || {
let res = crate::FountainCodes::encode_into_drops(id, &base64::decode(&*data)?)?;
let res = crate::fountain::FountainCodes::encode_into_drops(id, &base64::decode(&*data)?)?;
let output = serde_json::to_string(&res)?;
Ok(output)
};
@ -632,11 +578,10 @@ pub unsafe extern "C" fn split_data(id: u32, data: *mut c_char) -> CResult<*mut
}
#[no_mangle]
// TODO: who uses this?
pub unsafe extern "C" fn merge_data(drop: *mut c_char) -> CResult<*mut c_char> {
from_c_str!(drop);
let res = || {
let res = crate::put_drop(&*drop)?
let res = crate::fountain::put_drop(&*drop)?
.map(|d| base64::encode(&d))
.unwrap_or(String::new());
Ok::<_, anyhow::Error>(res)
@ -650,7 +595,7 @@ pub unsafe extern "C" fn get_tx_summary(tx: *mut c_char) -> CResult<*mut c_char>
from_c_str!(tx);
let res = || {
let tx: Tx = serde_json::from_str(&tx)?;
let summary = crate::get_tx_summary(&tx)?;
let summary = crate::pay::get_tx_summary(&tx)?;
let summary = serde_json::to_string(&summary)?;
Ok::<_, anyhow::Error>(summary)
};
@ -710,22 +655,22 @@ pub unsafe extern "C" fn disable_wal(db_path: *mut c_char) {
#[no_mangle]
pub unsafe extern "C" fn has_cuda() -> bool {
crate::has_cuda()
crate::gpu::has_cuda()
}
#[no_mangle]
pub unsafe extern "C" fn has_metal() -> bool {
crate::has_metal()
crate::gpu::has_metal()
}
#[no_mangle]
pub unsafe extern "C" fn has_gpu() -> bool {
crate::has_gpu()
crate::gpu::has_gpu()
}
#[no_mangle]
pub unsafe extern "C" fn use_gpu(v: bool) {
crate::use_gpu(v)
crate::gpu::use_gpu(v)
}
#[tokio::main]

View File

@ -1,23 +1,37 @@
//! Save/Load account data as JSON
use crate::coinconfig::CoinConfig;
use crate::db::AccountBackup;
use bech32::FromBase32;
use bech32::{FromBase32, ToBase32, Variant};
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use rand::RngCore;
use rand::rngs::OsRng;
const NONCE: &[u8; 12] = b"unique nonce";
/// Return backup data of every account for a given coin
/// # Argument
/// * `coin`: 0 for zcash, 1 for ycash
pub fn get_full_backup(coin: u8) -> anyhow::Result<Vec<AccountBackup>> {
let c = CoinConfig::get(coin);
let db = c.db()?;
db.get_full_backup(coin)
}
/// Import backup data for a given coin
/// # Argument
/// * `coin`: 0 for zcash, 1 for ycash
/// * `accounts`: list of backups
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)
}
/// Encrypt a list of account backups
/// # Argument
/// * `accounts`: list of backups
/// * `key`: encryption key
pub fn encrypt_backup(accounts: &[AccountBackup], key: &str) -> anyhow::Result<String> {
let accounts_bin = bincode::serialize(&accounts)?;
let backup = if !key.is_empty() {
@ -40,6 +54,10 @@ pub fn encrypt_backup(accounts: &[AccountBackup], key: &str) -> anyhow::Result<S
Ok(backup)
}
/// Decrypt a list of account backups
/// # Argument
/// * `key`: encryption key
/// * `backup`: encrypted 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)?;
@ -61,3 +79,11 @@ pub fn decrypt_backup(key: &str, backup: &str) -> anyhow::Result<Vec<AccountBack
let accounts: Vec<AccountBackup> = bincode::deserialize(&backup)?;
Ok(accounts)
}
/// Generate a random encryption key
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

@ -1,5 +1,12 @@
//! Retrieve Historical Prices from coingecko
use crate::coinconfig::CoinConfig;
/// Retrieve historical prices
/// # Arguments
/// * `now`: current timestamp
/// * `days`: how many days to fetch
/// * `currency`: base currency
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()?;

View File

@ -1,15 +1,17 @@
//! Access to server mempool
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_primitives::consensus::Parameters;
use crate::api::sync::get_latest_height;
use crate::coinconfig::CoinConfig;
use crate::db::AccountData;
use crate::get_latest_height;
/// Scan the mempool and return the unconfirmed balance
pub async fn scan() -> anyhow::Result<i64> {
let c = CoinConfig::get_active();
let AccountData { fvk, .. } = c.db()?.get_account_info(c.id_account)?;
let mut client = c.connect_lwd().await?;
let height = get_latest_height(&mut client).await?;
let height = get_latest_height().await?;
let mut mempool = c.mempool.lock().unwrap();
let current_height = c.height;
if height != current_height {
@ -21,6 +23,7 @@ pub async fn scan() -> anyhow::Result<i64> {
&fvk,
)?
.unwrap();
let mut client = c.connect_lwd().await?;
mempool
.update(&mut client, height, &fvk.fvk.vk.ivk())
.await?;

View File

@ -1,11 +1,20 @@
//! Mark messages read
use crate::coinconfig::CoinConfig;
/// Mark a given message as read or unread
/// # Arguments
/// * `message`: message id
/// * `read`: read or unread
pub fn mark_message_read(message: u32, read: bool) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
c.db()?.mark_message_read(message, read)?;
Ok(())
}
/// Mark all messages as read or unread
/// # Arguments
/// * `read`: read or unread
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)?;

View File

@ -1,3 +1,5 @@
//! Payments
use anyhow::anyhow;
use std::str::FromStr;
@ -77,6 +79,11 @@ fn sign(tx: &Tx, progress_callback: PaymentProgressCallback) -> anyhow::Result<V
}
/// Build a multi payment for offline signing
/// # Arguments
/// * `last_height`: current block height
/// * `recipients`: list of recipients
/// * `use_transparent`: include transparent balance
/// * `anchor_offset`: minimum number of confirmations for note selection
pub async fn build_only_multi_payment(
last_height: u32,
recipients: &[RecipientMemo],
@ -89,6 +96,10 @@ pub async fn build_only_multi_payment(
Ok(tx)
}
/// Sign a transaction
/// # Arguments
/// * `tx`: transaction to sign
/// * `progress_callback`: function callback during transaction building
pub async fn sign_only_multi_payment(
tx: &Tx,
progress_callback: PaymentProgressCallback,
@ -99,6 +110,12 @@ pub async fn sign_only_multi_payment(
}
/// Build, sign and broadcast a multi payment
/// # Arguments
/// * `last_height`: current block height
/// * `recipients`: list of recipients
/// * `use_transparent`: include transparent balance
/// * `anchor_offset`: minimum number of confirmations for note selection
/// * `progress_callback`: function callback during transaction building
pub async fn build_sign_send_multi_payment(
last_height: u32,
recipients: &[RecipientMemo],
@ -118,12 +135,14 @@ pub async fn build_sign_send_multi_payment(
Ok(tx_id)
}
/// Make a transaction that shields the transparent balance
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)
}
/// Parse a json document that contains a list of recipients
pub fn parse_recipients(recipients: &str) -> anyhow::Result<Vec<RecipientMemo>> {
let c = CoinConfig::get_active();
let AccountData { address, .. } = c.db()?.get_account_info(c.id_account)?;
@ -135,12 +154,14 @@ pub fn parse_recipients(recipients: &str) -> anyhow::Result<Vec<RecipientMemo>>
Ok(recipient_memos)
}
/// Encode a message into a memo
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
}
/// Decode a memo into a message
pub fn decode_memo(
id_tx: u32,
memo: &str,

View File

@ -1,3 +1,5 @@
//! encode and decode Payment URI
use crate::coinconfig::CoinConfig;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
@ -7,6 +9,11 @@ use zcash_client_backend::zip321::{Payment, TransactionRequest};
use zcash_primitives::memo::Memo;
use zcash_primitives::transaction::components::Amount;
/// Build a payment URI
/// # Arguments
/// * `address`: recipient address
/// * `amount`: amount in zats
/// * `memo`: memo text
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)
@ -29,6 +36,9 @@ pub fn make_payment_uri(address: &str, amount: u64, memo: &str) -> anyhow::Resul
Ok(uri)
}
/// Decode a payment uri
/// # Arguments
/// * `uri`: payment uri
pub fn parse_payment_uri(uri: &str) -> anyhow::Result<PaymentURI> {
let c = CoinConfig::get_active();
let scheme = c.chain.ticker();

View File

@ -1,17 +1,26 @@
// Sync
//! Warp Synchronize
use crate::coinconfig::CoinConfig;
use crate::db::PlainNote;
use crate::scan::{AMProgressCallback, Progress};
use crate::{AccountData, BlockId, CTree, CompactTxStreamerClient, DbAdapter};
use crate::{AccountData, BlockId, CompactTxStreamerClient, DbAdapter};
use std::sync::Arc;
use tokio::sync::Mutex;
use tonic::transport::Channel;
use tonic::Request;
use zcash_primitives::sapling::Note;
use crate::commitment::CTree;
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
/// Asynchronously perform warp sync
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
/// * `get_tx`: true to retrieve transaction details
/// * `anchor_offset`: minimum number of confirmations for note selection
/// * `max_cost`: tx that have a higher spending cost are excluded
/// * `progress_callback`: function callback during synchronization
/// * `cancel`: cancellation mutex, set to true to abort
pub async fn coin_sync(
coin: u8,
get_tx: bool,
@ -69,6 +78,7 @@ async fn coin_sync_impl(
Ok(())
}
/// Return the latest block height
pub async fn get_latest_height() -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
@ -76,12 +86,17 @@ pub async fn get_latest_height() -> anyhow::Result<u32> {
Ok(last_height)
}
/// Return the latest block height synchronized
pub fn get_synced_height() -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let db = c.db()?;
db.get_last_sync_height().map(|h| h.unwrap_or(0))
}
/// Skip block synchronization and directly mark the chain synchronized
/// Used for new accounts that have no transaction history
/// # Arguments
/// * `coin`: 0 for zcash, 1 for ycash
pub async fn skip_to_last_height(coin: u8) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let mut client = c.connect_lwd().await?;
@ -90,16 +105,17 @@ pub async fn skip_to_last_height(coin: u8) -> anyhow::Result<()> {
Ok(())
}
// if exact = true, do not snap the height to a pre existing checkpoint
// and get the tree_state from the server
// this option is used when we rescan from a given height
// We ignore any transaction that occurred before
/// Rewind to a previous block height
///
/// Height is snapped to a closest earlier checkpoint.
/// The effective height is returned
pub async fn rewind_to(height: u32) -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let height = c.db()?.trim_to_height(height)?;
Ok(height)
}
/// Synchronize from a given height
pub async fn rescan_from(height: u32) -> anyhow::Result<()> {
let c = CoinConfig::get_active();
c.db()?.truncate_sync_data()?;
@ -129,6 +145,7 @@ async fn fetch_and_store_tree_state(
Ok(())
}
/// Return the date of sapling activation
pub async fn get_activation_date() -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
@ -136,6 +153,9 @@ pub async fn get_activation_date() -> anyhow::Result<u32> {
Ok(date_time)
}
/// Return the block height for a given timestamp
/// # Arguments
/// * `time`: seconds since epoch
pub async fn get_block_by_time(time: u32) -> anyhow::Result<u32> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;
@ -143,7 +163,7 @@ pub async fn get_block_by_time(time: u32) -> anyhow::Result<u32> {
Ok(date_time)
}
pub fn trial_decrypt(
fn trial_decrypt(
height: u32,
cmu: &[u8],
epk: &[u8],

View File

@ -3,10 +3,9 @@ use crate::db::AccountViewKey;
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
use crate::lw_rpc::*;
use crate::scan::Blocks;
use crate::{advance_tree, has_cuda};
use crate::builder::advance_tree;
use ff::PrimeField;
use futures::{future, FutureExt};
use lazy_static::lazy_static;
use log::info;
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
@ -470,7 +469,7 @@ impl DecryptNode {
if blocks.is_empty() {
return vec![];
}
if has_cuda() {
if crate::gpu::has_cuda() {
let processor = CudaProcessor::setup_decrypt(network, blocks).unwrap();
return trial_decrypt(processor, self.vks.iter()).unwrap();
}
@ -546,7 +545,8 @@ fn calculate_tree_state_v1(
witnesses
}
pub fn calculate_tree_state_v2(cbs: &[CompactBlock], blocks: &[DecryptedBlock]) -> Vec<Witness> {
#[allow(dead_code)]
fn calculate_tree_state_v2(cbs: &[CompactBlock], blocks: &[DecryptedBlock]) -> Vec<Witness> {
let mut p = 0usize;
let mut nodes: Vec<Node> = vec![];
let mut positions: Vec<usize> = vec![];
@ -593,6 +593,7 @@ pub fn calculate_tree_state_v2(cbs: &[CompactBlock], blocks: &[DecryptedBlock])
new_witnesses
}
/// Connect to a lightwalletd server
pub async fn connect_lightwalletd(url: &str) -> anyhow::Result<CompactTxStreamerClient<Channel>> {
log::info!("LWD URL: {}", url);
let mut channel = tonic::transport::Channel::from_shared(url.to_owned())?;
@ -613,6 +614,8 @@ async fn get_height(server: String) -> Option<(String, u32)> {
Some((server, height))
}
/// Return the URL of the best server given a list of servers
/// The best server is the one that has the highest height
pub async fn get_best_server(servers: &[String]) -> Option<String> {
let mut server_heights = vec![];
for s in servers.iter() {

View File

@ -1,4 +1,6 @@
use crate::{connect_lightwalletd, CompactTxStreamerClient, DbAdapter, FountainCodes, MemPool};
use crate::{connect_lightwalletd, CompactTxStreamerClient, DbAdapter};
use crate::fountain::FountainCodes;
use crate::mempool::MemPool;
use anyhow::anyhow;
use lazy_static::lazy_static;
use lazycell::AtomicLazyCell;
@ -21,10 +23,12 @@ lazy_static! {
pub static ACTIVE_COIN: AtomicU8 = AtomicU8::new(0);
/// Set the active coin
pub fn set_active(active: u8) {
ACTIVE_COIN.store(active, Ordering::Release);
}
/// Set the active account for a given coin
pub fn set_active_account(coin: u8, id: u32) {
let mempool = {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
@ -35,16 +39,19 @@ pub fn set_active_account(coin: u8, id: u32) {
let _ = mempool.clear();
}
/// Set the lightwalletd url for a given coin
pub fn set_coin_lwd_url(coin: u8, lwd_url: &str) {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
c.lwd_url = Some(lwd_url.to_string());
}
/// Get the URL of the lightwalletd server for a given coin
pub fn get_coin_lwd_url(coin: u8) -> String {
let c = COIN_CONFIG[coin as usize].lock().unwrap();
c.lwd_url.clone().unwrap_or_default()
}
/// Initialize a coin with a database path
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)?;

View File

@ -3,7 +3,7 @@ use crate::contact::Contact;
use crate::prices::Quote;
use crate::taddr::{derive_tkeys, TBalance};
use crate::transaction::TransactionInfo;
use crate::{CTree, Witness};
use crate::commitment::{CTree, Witness};
use rusqlite::Error::QueryReturnedNoRows;
use rusqlite::{params, Connection, OptionalExtension, Transaction};
use serde::{Deserialize, Serialize};
@ -1148,7 +1148,7 @@ pub struct AccountData {
#[cfg(test)]
mod tests {
use crate::db::{DbAdapter, ReceivedNote, DEFAULT_DB_PATH};
use crate::{CTree, Witness};
use crate::commitment::{CTree, Witness};
use zcash_params::coin::CoinType;
#[test]
@ -1157,8 +1157,8 @@ mod tests {
db.init_db().unwrap();
db.trim_to_height(0).unwrap();
db.store_block(1, &[0u8; 32], 0, &CTree::new()).unwrap();
let db_tx = db.begin_transaction().unwrap();
DbAdapter::store_block(&db_tx, 1, &[0u8; 32], 0, &CTree::new()).unwrap();
let id_tx = DbAdapter::store_transaction(&[0; 32], 1, 1, 0, 20, &db_tx).unwrap();
DbAdapter::store_received_note(
&ReceivedNote {
@ -1184,8 +1184,8 @@ mod tests {
filled: vec![],
cursor: CTree::new(),
};
DbAdapter::store_witnesses(&db_tx, &witness, 1000, 1).unwrap();
db_tx.commit().unwrap();
Db::store_witnesses(&witness, 1000, 1).unwrap();
}
#[test]

View File

@ -1,7 +1,7 @@
use crate::chain::DecryptedBlock;
use crate::gpu::{collect_nf, GPUProcessor};
use crate::lw_rpc::CompactBlock;
use crate::{Hash, GENERATORS_EXP};
use crate::{Hash, hash::GENERATORS_EXP};
use anyhow::Result;
use ff::BatchInverter;
use jubjub::Fq;

View File

@ -112,10 +112,3 @@ impl KeyHelpers {
recipient.is_some()
}
}
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

@ -1,12 +1,65 @@
// #![allow(dead_code)]
// #![allow(unused_imports)]
// #![warn(missing_docs)]
//! A library for fast synchronization of y/zcash blockchain
//!
//! - Implements the warp sync algorithm for sapling
//! - Multi Account management
//! # Example
//! ```rust
//! use warp_api_ffi::api::account::{get_backup, new_account};
//! use warp_api_ffi::api::sync::coin_sync;
//! use warp_api_ffi::{CoinConfig, init_coin, set_coin_lwd_url};
//! use lazy_static::lazy_static;
//! use std::sync::Mutex;
//!
//! lazy_static! {
//! static ref CANCEL: Mutex<bool> = Mutex::new(false);
//! }
//!
//! const FVK: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz";
//!
//! #[tokio::main]
//! async fn main() {
//! env_logger::init();
//!
//! // Initialize the library for Zcash (coin = 0)
//! init_coin(0, "./zec.db").unwrap();
//! set_coin_lwd_url(0, "https://lwdv3.zecwallet.co:443"); // ZecWallet Lightwalletd URL
//!
//! // Create a new account with the ZEC pages viewing key
//! let id_account = new_account(0, "test_account", Some(FVK.to_string()),
//! None).unwrap();
//!
//! // Synchronize
//! coin_sync(0 /* zcash */,
//! true /* retrieve tx details */,
//! 0 /* sync to tip */,
//! 100 /* spam filter threshold */, |p| {
//! log::info!("Progress: {}", p.height);
//! }, &CANCEL).await.unwrap();
//!
//! // Grab the database accessor
//! let cc = &CoinConfig::get(0 /* zcash */);
//! let db = cc.db.as_ref().unwrap().clone();
//! let db = db.lock().unwrap();
//!
//! // Query the account balance
//! let balance = db.get_balance(id_account).unwrap();
//!
//! println!("Balance = {}", balance)
//! }
//! ```
#[path = "generated/cash.z.wallet.sdk.rpc.rs"]
pub mod lw_rpc;
pub use zcash_params::coin::{get_branch, get_coin_type, CoinType};
use zcash_params::coin::{get_branch, get_coin_type, CoinType};
// Mainnet
pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067";
// pub const LWD_URL: &str = "https://lwdv3.zecwallet.co";
// pub const LWD_URL: &str = "http://lwd.hanh.me:9067";
// pub const LWD_URL: &str = "http://127.0.0.1:9067";
@ -40,6 +93,7 @@ mod transaction;
mod ua;
mod zip32;
// mod wallet;
/// accounts, sync, payments, etc.
pub mod api;
#[cfg(feature = "ledger")]
@ -56,34 +110,21 @@ mod ledger {
}
}
pub fn hex_to_hash(hex: &str) -> anyhow::Result<[u8; 32]> {
let mut hash = [0u8; 32];
hex::decode_to_slice(hex, &mut hash)?;
Ok(hash)
}
pub use crate::builder::advance_tree;
pub use crate::chain::{
calculate_tree_state_v2, connect_lightwalletd, download_chain, get_best_server,
get_latest_height, ChainError, DecryptNode,
connect_lightwalletd, get_best_server,
ChainError,
};
pub use crate::coinconfig::{
init_coin, set_active, set_active_account, set_coin_lwd_url, CoinConfig,
};
pub use crate::commitment::{CTree, Witness};
pub use crate::db::{AccountData, AccountInfo, AccountRec, DbAdapter, TxRec};
pub use crate::fountain::{put_drop, FountainCodes, RaptorQDrops};
pub use crate::hash::{pedersen_hash, Hash, GENERATORS_EXP};
pub use crate::key::{generate_random_enc_key, KeyHelpers};
// pub use crate::fountain::FountainCodes;
pub use crate::hash::Hash;
pub use crate::key::KeyHelpers;
pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
pub use crate::lw_rpc::*;
pub use crate::mempool::MemPool;
pub use crate::misc::read_zwl;
pub use crate::pay::{broadcast_tx, get_tx_summary, Tx, TxIn, TxOut};
pub use crate::print::*;
pub use crate::scan::{latest_height, sync_async};
pub use crate::ua::{get_sapling, get_ua};
pub use zip32::{derive_zip32, KeyPack};
pub use crate::pay::{broadcast_tx, Tx, TxIn, TxOut};
pub use zip32::KeyPack;
// pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance};
#[cfg(feature = "ledger_sapling")]
@ -97,4 +138,3 @@ pub mod nodejs;
mod gpu;
pub use gpu::{has_cuda, has_gpu, has_metal, use_gpu};

View File

@ -2,7 +2,7 @@ use crate::db::SpendableNote;
// use crate::wallet::RecipientMemo;
use crate::api::payment::RecipientMemo;
use crate::coinconfig::CoinConfig;
use crate::{get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction};
use crate::{GetAddressUtxosReply, Hash, RawTransaction};
use anyhow::anyhow;
use jubjub::Fr;
use rand::prelude::SliceRandom;
@ -28,6 +28,7 @@ use zcash_primitives::transaction::builder::{Builder, Progress};
use zcash_primitives::transaction::components::amount::{DEFAULT_FEE, MAX_MONEY};
use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut as ZTxOut};
use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
use crate::chain::get_latest_height;
#[derive(Serialize, Deserialize, Debug)]
pub struct Tx {
@ -318,7 +319,7 @@ impl Tx {
let mut builder = Builder::new(*chain.network(), last_height);
let efvk = ExtendedFullViewingKey::from(zsk);
let ovk = hex_to_hash(&self.ovk)?;
let ovk: Hash = hex::decode(&self.ovk)?.try_into().unwrap();
builder.send_change_to(
OutgoingViewingKey(ovk),
decode_payment_address(chain.network().hrp_sapling_payment_address(), &self.change)
@ -406,6 +407,7 @@ impl Tx {
}
}
/// Broadcast a raw signed transaction to the network
pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result<String> {
let c = CoinConfig::get_active();
let mut client = c.connect_lwd().await?;

View File

@ -1,4 +1,4 @@
use crate::{CTree, Witness};
use crate::commitment::{CTree, Witness};
use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness};
use zcash_primitives::sapling::Node;

View File

@ -1,14 +1,16 @@
use crate::builder::BlockProcessor;
use crate::chain::{DecryptedBlock, Nf, NfRef};
use crate::chain::{DecryptedBlock, get_latest_height, Nf, NfRef};
use crate::db::{AccountViewKey, DbAdapter, PlainNote, ReceivedNote};
use serde::Serialize;
use std::cmp::Ordering;
use crate::transaction::retrieve_tx_info;
use crate::{
connect_lightwalletd, download_chain, get_latest_height, CompactBlock, CompactSaplingOutput,
CompactTx, DecryptNode, Witness,
connect_lightwalletd, CompactBlock, CompactSaplingOutput,
CompactTx
};
use crate::chain::{DecryptNode, download_chain};
use crate::commitment::Witness;
use ff::PrimeField;
use anyhow::anyhow;
@ -43,9 +45,9 @@ impl std::fmt::Debug for Blocks {
#[derive(Clone, Serialize)]
pub struct Progress {
height: u32,
trial_decryptions: u64,
downloaded: usize,
pub height: u32,
pub trial_decryptions: u64,
pub downloaded: usize,
}
pub type ProgressCallback = dyn Fn(Progress) + Send;
@ -326,7 +328,8 @@ pub async fn sync_async(
db_transaction.commit()?;
// db_transaction is dropped here
}
log::info!("progress: {}", dec_block.height);
progress.height = dec_block.height;
log::info!("progress: {}", progress.height);
let callback = proc_callback.lock().await;
callback(progress.clone());
}

View File

@ -292,7 +292,8 @@ mod tests {
nf_map.insert((nf.0, nf.2.clone()), nf.1);
}
}
let fvk = db.get_ivk(account).unwrap();
let account = db.get_account_info(account).unwrap();
let fvk = account.fvk.clone();
let fvk = decode_extended_full_viewing_key(
Network::MainNetwork.hrp_sapling_extended_full_viewing_key(),
&fvk,

View File

@ -38,7 +38,7 @@ impl FromAddress for MyReceiver {
}
}
pub fn get_ua(_sapling_addr: &str, _transparent_addr: &str) -> anyhow::Result<ZcashAddress> {
fn get_ua(_sapling_addr: &str, _transparent_addr: &str) -> anyhow::Result<ZcashAddress> {
todo!()
// let sapling_addr = ZcashAddress::try_from_encoded(sapling_addr)?;
// let transparent_addr = ZcashAddress::try_from_encoded(transparent_addr)?;
@ -53,7 +53,7 @@ pub fn get_ua(_sapling_addr: &str, _transparent_addr: &str) -> anyhow::Result<Zc
// Ok(ua_address)
}
pub fn get_sapling(ua_addr: &str) -> anyhow::Result<ZcashAddress> {
fn get_sapling(ua_addr: &str) -> anyhow::Result<ZcashAddress> {
let ua_addr = ZcashAddress::try_from_encoded(ua_addr)?;
let r = ua_addr.convert::<MyReceiver>()?;
if let Receiver::Sapling(data) = r.receiver {
@ -64,7 +64,7 @@ pub fn get_sapling(ua_addr: &str) -> anyhow::Result<ZcashAddress> {
#[cfg(test)]
mod tests {
use crate::ua::{get_sapling, get_ua};
use super::{get_sapling, get_ua};
#[test]
fn test_ua() -> anyhow::Result<()> {