This commit is contained in:
Hanh 2022-11-15 18:21:47 +08:00
parent 56b70bcceb
commit 5ddacf524c
25 changed files with 345 additions and 372 deletions

View File

@ -53,7 +53,7 @@ prost = "0.10.3"
rayon = "1.5.1"
tiny-bip39 = "0.8"
rand = "0.8.4"
rusqlite = { version = "0.27.0", features = ["bundled"] }
rusqlite = { version = "0.27.0", features = ["bundled", "modern_sqlite"] }
jubjub = "0.9.0"
bls12_381 = "0.7"
ff = "0.12"

View File

@ -50,6 +50,8 @@ void deallocate_str(char *s);
void init_wallet(char *db_path);
void migrate_db(char *db_path);
void set_active(uint8_t active);
void set_active_account(uint8_t coin, uint32_t id);

View File

@ -79,19 +79,22 @@ fn new_account_with_key(coin: u8, name: &str, key: &str, index: u32) -> anyhow::
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 {
if c.chain.has_transparent() {
db.create_taddr(account)?;
let account = db.get_account_id(&ivk)?;
let account = match account {
Some(account) => account,
None => {
let account =
db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?;
if c.chain.has_transparent() {
db.create_taddr(account)?;
}
if c.chain.has_unified() {
db.create_orchard(account)?;
}
db.store_ua_settings(account, false, true, c.chain.has_unified())?;
account
}
if c.chain.has_unified() {
db.create_orchard(account)?;
}
db.store_ua_settings(account, true, true, true)?;
} else {
db.store_ua_settings(account, false, true, false)?;
}
};
Ok(account)
}
@ -319,13 +322,24 @@ pub fn get_unified_address(
Ok(address)
}
fn get_sapling_address(coin: u8, id_account: u32) -> anyhow::Result<String> {
let c = CoinConfig::get(coin);
let db = c.db()?;
let AccountData { address, .. } = db.get_account_info(id_account)?;
Ok(address)
}
pub fn get_address(coin: u8, id_account: u32, address_type: u8) -> anyhow::Result<String> {
let c = CoinConfig::get(coin);
let t = address_type & 1 != 0;
let s = address_type & 2 != 0;
let o = address_type & 4 != 0;
let address = if c.chain.has_unified() {
let t = address_type & 1 != 0;
let s = address_type & 2 != 0;
let o = address_type & 4 != 0;
let address = get_unified_address(coin, id_account, t, s, o)?;
get_unified_address(coin, id_account, t, s, o)?
} else {
get_sapling_address(coin, id_account)?
};
Ok(address)
}

View File

@ -1,4 +1,4 @@
use crate::coinconfig::{init_coin, CoinConfig};
use crate::coinconfig::{init_coin, migrate_coin, CoinConfig};
use crate::note_selection::TransactionReport;
use crate::{ChainError, TransactionPlan, Tx};
use allo_isolate::{ffi, IntoDart};
@ -95,13 +95,24 @@ pub struct CResult<T> {
error: *mut c_char,
}
const COIN_DBNAMES: &[&str] = &["zec.db", "yec.db"];
#[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-test.db", &db_path));
let _ = init_coin(1, &format!("{}/yec.db", &db_path));
let _ = init_coin(2, &format!("{}/arrr.db", &db_path));
for (coin, filename) in COIN_DBNAMES.iter().enumerate() {
let _ = init_coin(coin as u8, &format!("{}/{}", &db_path, filename));
}
}
#[no_mangle]
pub unsafe extern "C" fn migrate_db(db_path: *mut c_char) {
try_init_logger();
from_c_str!(db_path);
for (coin, filename) in COIN_DBNAMES.iter().enumerate() {
let _ = migrate_coin(coin as u8, &format!("{}/{}", &db_path, filename));
}
}
#[no_mangle]

View File

@ -1,136 +0,0 @@
//! Payments
use anyhow::anyhow;
use std::str::FromStr;
use secp256k1::SecretKey;
use crate::api::sync::get_latest_height;
use crate::coinconfig::{get_prover, CoinConfig};
use crate::pay::TxBuilder;
pub 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::{AccountData, 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],
anchor_offset: u32,
) -> anyhow::Result<(Tx, Vec<u32>)> {
let c = CoinConfig::get_active();
let change_address = c.db()?.get_account_change_address(c.id_account)?;
// let mut tx_builder = TxBuilder::new(c.coin_type, last_height);
//
// let fvk = decode_extended_full_viewing_key(
// c.chain.network().hrp_sapling_extended_full_viewing_key(),
// &fvk,
// )
// .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))
todo!()
}
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 AccountData { sk: zsk, .. } = db.get_account_info(c.id_account)?;
// let zsk = zsk.ok_or(anyhow!("Cannot sign without secret key"))?;
// 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();
// let raw_tx = tx.sign(tsk, &extsk, prover, progress_callback)?;
// Ok(raw_tx)
todo!()
}
/// 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],
use_transparent: bool,
anchor_offset: u32,
) -> anyhow::Result<Tx> {
let (tx, _) =
prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?;
// let tx_str = serde_json::to_string(&tx)?;
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,
) -> 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
/// # 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],
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, anchor_offset).await?;
let raw_tx = sign(&tx, progress_callback)?;
let tx_id = broadcast_tx(&raw_tx).await?;
c.db()?.tx_mark_spend(&note_ids)?;
let mut mempool = c.mempool.lock().unwrap();
mempool.clear()?;
Ok(tx_id)
}

View File

@ -2,7 +2,7 @@ use crate::api::account::get_unified_address;
use crate::api::recipient::{RecipientMemo, RecipientShort};
use crate::api::sync::get_latest_height;
pub use crate::broadcast_tx;
use crate::note_selection::{FeeZIP327, Order, TransactionReport};
use crate::note_selection::{FeeFlat, FeeZIP327, Order, TransactionReport};
use crate::{
build_tx, fetch_utxos, get_secret_keys, AccountData, CoinConfig, TransactionBuilderConfig,
TransactionPlan, TxBuilderContext,
@ -22,12 +22,17 @@ pub async fn build_tx_plan(
confirmations: u32,
) -> anyhow::Result<TransactionPlan> {
let c = CoinConfig::get(coin);
let fvk = {
let (fvk, checkpoint_height) = {
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
fvk
let anchor_height = last_height.saturating_sub(confirmations);
let checkpoint_height = db
.get_checkpoint_height(anchor_height)?
.unwrap_or_else(|| db.sapling_activation_height()); // get the latest checkpoint before the requested anchor height
(fvk, checkpoint_height)
};
let change_address = get_unified_address(coin, account, true, true, true)?;
let context = TxBuilderContext::from_height(coin, checkpoint_height)?;
let mut orders = vec![];
let mut id_order = 0;
@ -47,14 +52,15 @@ pub async fn build_tx_plan(
id_order += 1;
}
}
let utxos = fetch_utxos(coin, account, last_height, true, confirmations).await?;
let utxos = fetch_utxos(coin, account, checkpoint_height, true).await?;
log::info!("UTXO: {:?}", utxos);
let config = TransactionBuilderConfig::new(&change_address);
let tx_plan = crate::note_selection::build_tx_plan::<FeeZIP327>(
let tx_plan = crate::note_selection::build_tx_plan::<FeeFlat>(
&fvk,
last_height,
checkpoint_height,
&context.orchard_anchor,
&utxos,
&orders,
&config,
@ -75,8 +81,7 @@ pub fn sign_plan(coin: u8, account: u32, tx_plan: &TransactionPlan) -> anyhow::R
}
let keys = get_secret_keys(coin, account)?;
let context = TxBuilderContext::from_height(c.coin, tx_plan.height)?;
let tx = build_tx(c.chain.network(), &keys, &tx_plan, context, OsRng).unwrap();
let tx = build_tx(c.chain.network(), &keys, &tx_plan, OsRng)?;
Ok(tx)
}

View File

@ -68,6 +68,7 @@ pub fn decode_memo(
recipient: &str,
timestamp: u32,
height: u32,
incoming: bool,
) -> ZMessage {
let memo_lines: Vec<_> = memo.splitn(4, '\n').collect();
let msg = if memo_lines[0] == "\u{1F6E1}MSG" {
@ -83,6 +84,7 @@ pub fn decode_memo(
body: memo_lines[3].to_string(),
timestamp,
height,
incoming,
}
} else {
ZMessage {
@ -93,6 +95,7 @@ pub fn decode_memo(
body: memo.to_string(),
timestamp,
height,
incoming,
}
};
msg

View File

@ -3,7 +3,7 @@
use crate::coinconfig::CoinConfig;
use crate::scan::{AMProgressCallback, Progress};
use crate::sync::CTree;
use crate::{AccountData, BlockId, CompactTxStreamerClient, DbAdapter};
use crate::{AccountData, BlockId, ChainError, CompactTxStreamerClient, DbAdapter};
use std::sync::Arc;
use tokio::sync::Mutex;
use tonic::transport::Channel;
@ -29,33 +29,14 @@ pub async fn coin_sync(
cancel: &'static std::sync::Mutex<bool>,
) -> anyhow::Result<()> {
let p_cb = Arc::new(Mutex::new(progress_callback));
coin_sync_impl(
coin,
get_tx,
DEFAULT_CHUNK_SIZE,
anchor_offset,
max_cost,
p_cb.clone(),
cancel,
)
.await?;
coin_sync_impl(
coin,
get_tx,
DEFAULT_CHUNK_SIZE,
0,
u32::MAX,
p_cb.clone(),
cancel,
)
.await?;
coin_sync_impl(coin, get_tx, anchor_offset, max_cost, p_cb.clone(), cancel).await?;
coin_sync_impl(coin, get_tx, 0, u32::MAX, p_cb.clone(), cancel).await?;
Ok(())
}
async fn coin_sync_impl(
coin: u8,
get_tx: bool,
chunk_size: u32,
target_height_offset: u32,
max_cost: u32,
progress_callback: AMProgressCallback,
@ -63,15 +44,13 @@ async fn coin_sync_impl(
) -> anyhow::Result<()> {
crate::scan::sync_async(
coin,
chunk_size,
get_tx,
target_height_offset,
max_cost,
progress_callback,
cancel,
)
.await?;
Ok(())
.await
}
/// Return the latest block height
@ -135,9 +114,21 @@ async fn fetch_and_store_tree_state(
.get_tree_state(Request::new(block_id))
.await?
.into_inner();
let tree = CTree::read(&*hex::decode(&tree_state.sapling_tree)?)?; // retrieve orchard state and store it too
let sapling_tree = CTree::read(&*hex::decode(&tree_state.sapling_tree)?)?; // retrieve orchard state and store it too
let orchard_tree = if !tree_state.orchard_tree.is_empty() {
CTree::read(&*hex::decode(&tree_state.orchard_tree)?)? // retrieve orchard state and store it too
} else {
CTree::new()
};
let db = c.db()?;
DbAdapter::store_block(&db.connection, height, &block.hash, block.time, &tree, None)?;
DbAdapter::store_block(
&db.connection,
height,
&block.hash,
block.time,
&sapling_tree,
&orchard_tree,
)?;
Ok(())
}

View File

@ -7,7 +7,7 @@ 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::coin::{get_coin_chain, get_coin_type, CoinChain, CoinType};
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
use zcash_proofs::prover::LocalTxProver;
@ -56,6 +56,15 @@ pub fn get_coin_lwd_url(coin: u8) -> 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)?;
c.migrate_db()?; // if the db was already migrated, this is a no-op
c.open_db()?;
Ok(())
}
/// Upgrade database schema for given coin and db path
pub fn migrate_coin(coin: u8, db_path: &str) -> anyhow::Result<()> {
let chain = get_coin_chain(get_coin_type(coin));
DbAdapter::migrate_db(chain.network(), db_path, chain.has_unified())?;
Ok(())
}
@ -90,7 +99,21 @@ impl CoinConfig {
pub fn set_db_path(&mut self, db_path: &str) -> anyhow::Result<()> {
self.db_path = Some(db_path.to_string());
let mut db = DbAdapter::new(self.coin_type, db_path)?;
Ok(())
}
pub fn migrate_db(&self) -> anyhow::Result<()> {
let network = self.chain.network();
DbAdapter::migrate_db(
network,
self.db_path.as_ref().unwrap(),
self.chain.has_unified(),
)?;
Ok(())
}
pub fn open_db(&mut self) -> anyhow::Result<()> {
let mut db = DbAdapter::new(self.coin_type, self.db_path.as_ref().unwrap())?;
db.init_db()?;
self.db = Some(Arc::new(Mutex::new(db)));
Ok(())

134
src/db.rs
View File

@ -4,11 +4,11 @@ use crate::note_selection::{Source, UTXO};
use crate::orchard::{derive_orchard_keys, OrchardKeyBytes, OrchardViewKey};
use crate::prices::Quote;
use crate::sapling::SaplingViewKey;
use crate::sync;
use crate::sync::tree::{CTree, TreeCheckpoint};
use crate::taddr::{derive_tkeys, TBalance};
use crate::transaction::{GetTransactionDetailRequest, TransactionDetails};
use crate::unified::UnifiedAddressType;
use crate::{get_unified_address, sync};
use orchard::keys::FullViewingKey;
use rusqlite::Error::QueryReturnedNoRows;
use rusqlite::{params, Connection, OptionalExtension, Transaction};
@ -113,6 +113,12 @@ impl DbAdapter {
})
}
pub fn migrate_db(network: &Network, db_path: &str, has_ua: bool) -> anyhow::Result<()> {
let connection = Connection::open(db_path)?;
migration::init_db(&connection, network, has_ua)?;
Ok(())
}
pub fn disable_wal(db_path: &str) -> anyhow::Result<()> {
let connection = Connection::open(db_path)?;
connection.query_row("PRAGMA journal_mode = OFF", [], |_| Ok(()))?;
@ -125,7 +131,6 @@ impl DbAdapter {
}
pub fn init_db(&mut self) -> anyhow::Result<()> {
migration::init_db(&self.connection, self.network())?;
self.delete_incomplete_scan()?;
Ok(())
}
@ -135,6 +140,21 @@ impl DbAdapter {
Ok(())
}
pub fn get_account_id(&self, ivk: &str) -> anyhow::Result<Option<u32>> {
let r = self
.connection
.query_row(
"SELECT id_account FROM accounts WHERE ivk = ?1",
params![ivk],
|r| {
let id: u32 = r.get(0)?;
Ok(id)
},
)
.optional()?;
Ok(r)
}
pub fn store_account(
&self,
name: &str,
@ -143,11 +163,7 @@ impl DbAdapter {
sk: Option<&str>,
ivk: &str,
address: &str,
) -> anyhow::Result<(u32, bool)> {
let mut statement = self
.connection
.prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?;
let exists = statement.exists(params![ivk])?;
) -> anyhow::Result<u32> {
self.connection.execute(
"INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
params![name, seed, index, sk, ivk, address],
@ -160,7 +176,7 @@ impl DbAdapter {
|row| row.get(0),
)
.map_err(wrap_query_no_rows("store_account/id_account"))?;
Ok((id_account, exists))
Ok(id_account)
}
pub fn next_account_id(&self, seed: &str) -> anyhow::Result<u32> {
@ -231,6 +247,15 @@ impl DbAdapter {
Ok(fvks)
}
pub fn drop_last_checkpoint(&mut self) -> anyhow::Result<u32> {
let height = self.get_last_sync_height()?;
if let Some(height) = height {
let height = self.trim_to_height(height - 1)?;
return Ok(height);
}
Ok(self.sapling_activation_height())
}
pub fn trim_to_height(&mut self, height: u32) -> anyhow::Result<u32> {
// snap height to an existing checkpoint
let height = self.connection.query_row(
@ -286,20 +311,25 @@ impl DbAdapter {
hash: &[u8],
timestamp: u32,
sapling_tree: &CTree,
orchard_tree: Option<&CTree>,
orchard_tree: &CTree,
) -> anyhow::Result<()> {
log::debug!("+block");
log::info!("+store_block");
let mut sapling_bb: Vec<u8> = vec![];
sapling_tree.write(&mut sapling_bb)?;
let orchard_bb = orchard_tree.map(|tree| {
let mut bb: Vec<u8> = vec![];
tree.write(&mut bb).unwrap();
bb
});
connection.execute(
"INSERT INTO blocks(height, hash, timestamp, sapling_tree, orchard_tree)
VALUES (?1, ?2, ?3, ?4, ?5)",
params![height, hash, timestamp, &sapling_bb, orchard_bb],
"INSERT INTO blocks(height, hash, timestamp)
VALUES (?1, ?2, ?3)",
params![height, hash, timestamp],
)?;
connection.execute(
"INSERT INTO sapling_tree(height, tree) VALUES (?1, ?2)",
params![height, &sapling_bb],
)?;
let mut orchard_bb: Vec<u8> = vec![];
orchard_tree.write(&mut orchard_bb)?;
connection.execute(
"INSERT INTO orchard_tree(height, tree) VALUES (?1, ?2)",
params![height, &orchard_bb],
)?;
log::debug!("-block");
Ok(())
@ -431,20 +461,25 @@ impl DbAdapter {
}
pub fn get_last_sync_height(&self) -> anyhow::Result<Option<u32>> {
let height: Option<u32> = self
.connection
.query_row("SELECT MAX(height) FROM blocks", [], |row| row.get(0))
.map_err(wrap_query_no_rows(""))?;
let height: Option<u32> =
self.connection
.query_row("SELECT MAX(height) FROM blocks", [], |row| row.get(0))?;
Ok(height)
}
pub fn get_checkpoint_height(&self, max_height: u32) -> anyhow::Result<Option<u32>> {
let height: Option<u32> = self.connection.query_row(
"SELECT MAX(height) FROM blocks WHERE height <= ?1",
[max_height],
|row| row.get(0),
)?;
Ok(height)
}
pub fn get_db_height(&self) -> anyhow::Result<u32> {
let height: u32 = self.get_last_sync_height()?.unwrap_or_else(|| {
self.network()
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
.into()
});
let height: u32 = self
.get_last_sync_height()?
.unwrap_or_else(|| self.sapling_activation_height());
Ok(height)
}
@ -556,15 +591,15 @@ impl DbAdapter {
pub fn get_unspent_received_notes(
&self,
account: u32,
anchor_height: u32,
checkpoint_height: u32,
) -> anyhow::Result<Vec<UTXO>> {
let mut notes = vec![];
let mut statement = self.connection.prepare(
"SELECT id_note, diversifier, value, rcm, witness FROM received_notes r, sapling_witnesses w WHERE spent IS NULL AND account = ?2 AND rho IS NULL
AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = (
SELECT MAX(height) FROM sapling_witnesses WHERE height <= ?1
) AND r.id_note = w.note")?;
let rows = statement.query_map(params![anchor_height, account], |row| {
AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ?1
AND r.id_note = w.note")?;
let rows = statement.query_map(params![checkpoint_height, account], |row| {
let id_note: u32 = row.get(0)?;
let diversifier: Vec<u8> = row.get(1)?;
let amount: i64 = row.get(2)?;
@ -589,10 +624,9 @@ impl DbAdapter {
let mut statement = self.connection.prepare(
"SELECT id_note, diversifier, value, rcm, rho, witness FROM received_notes r, orchard_witnesses w WHERE spent IS NULL AND account = ?2 AND rho IS NOT NULL
AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = (
SELECT MAX(height) FROM orchard_witnesses WHERE height <= ?1
) AND r.id_note = w.note")?;
let rows = statement.query_map(params![anchor_height, account], |row| {
AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ?1
AND r.id_note = w.note")?;
let rows = statement.query_map(params![checkpoint_height, account], |row| {
let id_note: u32 = row.get(0)?;
let diversifier: Vec<u8> = row.get(1)?;
let amount: i64 = row.get(2)?;
@ -968,6 +1002,10 @@ impl DbAdapter {
"DELETE FROM orchard_addrs WHERE account = ?1",
params![account],
)?;
self.connection.execute(
"DELETE FROM ua_settings WHERE account = ?1",
params![account],
)?;
self.connection
.execute("DELETE FROM messages WHERE account = ?1", params![account])?;
Ok(())
@ -1031,8 +1069,8 @@ impl DbAdapter {
}
pub fn store_message(&self, account: u32, message: &ZMessage) -> anyhow::Result<()> {
self.connection.execute("INSERT INTO messages(account, id_tx, sender, recipient, subject, body, timestamp, height, read) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9)",
params![account, message.id_tx, message.sender, message.recipient, message.subject, message.body, message.timestamp, message.height, false])?;
self.connection.execute("INSERT INTO messages(account, id_tx, sender, recipient, subject, body, timestamp, height, incoming, read) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10)",
params![account, message.id_tx, message.sender, message.recipient, message.subject, message.body, message.timestamp, message.height, message.incoming, false])?;
Ok(())
}
@ -1119,18 +1157,20 @@ impl DbAdapter {
}
pub fn get_txid_without_memo(&self) -> anyhow::Result<Vec<GetTransactionDetailRequest>> {
let mut stmt = self
.connection
.prepare("SELECT account, id_tx, height, txid FROM transactions WHERE memo IS NULL")?;
let mut stmt = self.connection.prepare(
"SELECT account, id_tx, height, timestamp, txid FROM transactions WHERE memo IS NULL",
)?;
let rows = stmt.query_map([], |row| {
let account: u32 = row.get(0)?;
let id_tx: u32 = row.get(1)?;
let height: u32 = row.get(2)?;
let txid: Vec<u8> = row.get(3)?;
let timestamp: u32 = row.get(3)?;
let txid: Vec<u8> = row.get(4)?;
Ok(GetTransactionDetailRequest {
account,
id_tx,
height,
timestamp,
txid: txid.try_into().unwrap(),
})
})?;
@ -1204,6 +1244,13 @@ impl DbAdapter {
let chain = get_coin_chain(self.coin_type);
chain.network()
}
pub fn sapling_activation_height(&self) -> u32 {
self.network()
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
.into()
}
}
pub struct ZMessage {
@ -1214,6 +1261,7 @@ pub struct ZMessage {
pub body: String,
pub timestamp: u32,
pub height: u32,
pub incoming: bool,
}
impl ZMessage {

View File

@ -34,7 +34,7 @@ pub fn reset_db(connection: &Connection) -> anyhow::Result<()> {
Ok(())
}
pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()> {
pub fn init_db(connection: &Connection, network: &Network, has_ua: bool) -> anyhow::Result<()> {
connection.execute(
"CREATE TABLE IF NOT EXISTS schema_version (
id INTEGER PRIMARY KEY NOT NULL,
@ -195,7 +195,9 @@ pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()>
orchard BOOL NOT NULL)",
[],
)?;
upgrade_accounts(&connection, network)?;
if has_ua {
upgrade_accounts(&connection, network)?;
}
connection.execute(
"CREATE TABLE sapling_tree(
height INTEGER PRIMARY KEY,
@ -257,10 +259,15 @@ pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()>
"CREATE INDEX IF NOT EXISTS i_orchard_witness ON orchard_witnesses(height)",
[],
)?;
connection.execute(
"ALTER TABLE messages ADD incoming BOOL NOT NULL DEFAULT (true)",
[],
)?;
}
if version != 5 {
update_schema_version(connection, 5)?;
connection.cache_flush()?;
log::info!("Database migrated");
}

View File

@ -289,15 +289,17 @@ pub async fn pay(payment: Json<Payment>, config: &State<Config>) -> Result<Strin
.iter()
.map(|p| RecipientMemo::from_recipient(&from, p))
.collect();
let txid = warp_api_ffi::api::payment_v2::build_sign_send_multi_payment(
let tx_plan = warp_api_ffi::api::payment_v2::build_tx_plan(
c.coin,
c.id_account,
latest,
&recipients,
payment.confirmations,
Box::new(|_| {}),
)
.await?;
let txid =
warp_api_ffi::api::payment_v2::sign_and_broadcast(c.coin, c.id_account, &tx_plan)
.await?;
Ok(txid)
}
}
@ -350,8 +352,7 @@ pub async fn build_from_plan(tx_plan: Json<TransactionPlan>) -> Result<String, E
}
let keys = get_secret_keys(c.coin, c.id_account)?;
let context = TxBuilderContext::from_height(c.coin, tx_plan.height)?;
let tx = build_tx(c.chain.network(), &keys, &tx_plan, context, OsRng).unwrap();
let tx = build_tx(c.chain.network(), &keys, &tx_plan, OsRng).unwrap();
let tx = hex::encode(&tx);
Ok(tx)
}

View File

@ -7,7 +7,7 @@ use crate::orchard::{get_proving_key, OrchardHasher, ORCHARD_ROOTS};
use crate::sapling::{SaplingHasher, SAPLING_ROOTS};
use crate::sync::tree::TreeCheckpoint;
use crate::sync::Witness;
use crate::{broadcast_tx, init_coin, set_active, set_coin_lwd_url, AccountData, CoinConfig};
use crate::{broadcast_tx, init_coin, set_active, set_coin_lwd_url, AccountData, CoinConfig, Hash};
use anyhow::anyhow;
use jubjub::Fr;
use orchard::builder::Builder as OrchardBuilder;
@ -75,7 +75,6 @@ pub fn build_tx(
network: &Network,
skeys: &SecretKeys,
plan: &TransactionPlan,
context: TxBuilderContext,
mut rng: impl RngCore + CryptoRng + Clone,
) -> anyhow::Result<Vec<u8>> {
let secp = Secp256k1::<All>::new();
@ -100,8 +99,9 @@ pub fn build_tx(
};
let mut has_orchard = false;
let mut builder = Builder::new(*network, BlockHeight::from_u32(context.height));
let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&context.orchard_anchor)
let mut builder = Builder::new(*network, BlockHeight::from_u32(plan.height));
log::info!("ANCHOR {}", hex::encode(&plan.orchard_anchor));
let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&plan.orchard_anchor)
.unwrap()
.into();
let mut orchard_builder = OrchardBuilder::new(Flags::from_parts(true, true), anchor);
@ -204,7 +204,7 @@ pub fn build_tx(
get_prover(),
&mut ctx,
&mut rng,
BlockHeight::from_u32(context.height),
BlockHeight::from_u32(plan.height),
None,
)
.unwrap();
@ -219,7 +219,7 @@ pub fn build_tx(
TxVersion::Zip225,
BranchId::Nu5,
0,
BlockHeight::from_u32(context.height + EXPIRY_HEIGHT),
BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT),
transparent_bundle,
None,
sapling_bundle,
@ -261,7 +261,7 @@ pub fn build_tx(
TxVersion::Zip225,
BranchId::Nu5,
0,
BlockHeight::from_u32(context.height + EXPIRY_HEIGHT),
BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT),
transparent_bundle,
None,
sapling_bundle,
@ -347,7 +347,9 @@ async fn dummy_test() {
orders.push(Order::new(7, "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8", 70000, MemoBytes::empty()));
log::info!("Building tx plan");
let tx_plan = build_tx_plan::<FeeFlat>("", 0, &utxos, &mut orders, &config).unwrap();
let tx_plan =
build_tx_plan::<FeeFlat>("", 0, &[Hash::default(); 2], &utxos, &mut orders, &config)
.unwrap();
log::info!("Plan: {}", serde_json::to_string(&tx_plan).unwrap());
log::info!("Building tx");

View File

@ -3,6 +3,7 @@ use crate::note_selection::fee::FeeCalculator;
use crate::note_selection::ua::decode;
use crate::note_selection::TransactionBuilderError::TxTooComplex;
use crate::note_selection::{TransactionBuilderError, MAX_ATTEMPTS};
use crate::Hash;
use anyhow::anyhow;
use std::str::FromStr;
use zcash_primitives::memo::{Memo, MemoBytes};
@ -282,6 +283,7 @@ pub fn outputs_for_change(
pub fn build_tx_plan<F: FeeCalculator>(
fvk: &str,
height: u32,
orchard_anchor: &Hash,
utxos: &[UTXO],
orders: &[Order],
config: &TransactionBuilderConfig,
@ -312,6 +314,7 @@ pub fn build_tx_plan<F: FeeCalculator>(
let tx_plan = TransactionPlan {
fvk: fvk.to_string(),
height,
orchard_anchor: orchard_anchor.clone(),
spends: notes,
outputs: fills,
net_chg,

View File

@ -57,7 +57,7 @@ impl TransactionReport {
let net_sapling = outs[1] as i64 - spends[1] as i64;
let net_orchard = outs[2] as i64 - spends[2] as i64;
let privacy_level = if outs[0] != 0 && spends[0] != 0 {
let privacy_level = if outs[0] - changes[0] != 0 && spends[0] != 0 {
0 // very low privacy: t2t
} else if outs[0] != 0 || spends[0] != 0 {
1 // low privacy: t2z or z2t

View File

@ -5,6 +5,7 @@ use crate::note_selection::build_tx_plan;
use crate::note_selection::fee::{FeeCalculator, FeeZIP327};
use crate::note_selection::optimize::{outputs_for_change, select_inputs};
use crate::note_selection::ua::decode;
use crate::Hash;
use assert_matches::assert_matches;
use serde::Serialize;
use serde_json::Value;
@ -13,6 +14,7 @@ use zcash_primitives::memo::MemoBytes;
macro_rules! utxo {
($id:expr, $q:expr) => {
UTXO {
id: $id,
amount: $q * 1000,
source: Source::Transparent {
txid: [0u8; 32],
@ -25,6 +27,7 @@ macro_rules! utxo {
macro_rules! sapling {
($id:expr, $q:expr) => {
UTXO {
id: $id,
amount: $q * 1000,
source: Source::Sapling {
id_note: $id,
@ -39,6 +42,7 @@ macro_rules! sapling {
macro_rules! orchard {
($id:expr, $q:expr) => {
UTXO {
id: $id,
amount: $q * 1000,
source: Source::Orchard {
id_note: $id,
@ -731,6 +735,7 @@ fn test_tx_plan() {
let tx_plan = build_tx_plan::<FeeZIP327>(
"",
0,
&[Hash::default(); 2],
&utxos,
&orders,
&TransactionBuilderConfig {

View File

@ -1,6 +1,7 @@
use super::ser::MemoBytesProxy;
use crate::note_selection::ua::decode;
use crate::unified::orchard_as_unified;
use crate::Hash;
use orchard::Address;
use serde::{Deserialize, Serialize};
use serde_hex::{SerHex, Strict};
@ -124,9 +125,12 @@ pub struct RecipientShort {
}
#[derive(Serialize, Deserialize, Default)]
#[serde_as]
pub struct TransactionPlan {
pub fvk: String,
pub height: u32,
#[serde(with = "SerHex::<Strict>")]
pub orchard_anchor: Hash,
pub spends: Vec<UTXO>,
pub outputs: Vec<Fill>,
pub fee: u64,

View File

@ -5,9 +5,8 @@ use crate::CoinConfig;
pub async fn fetch_utxos(
coin: u8,
account: u32,
last_height: u32,
checkpoint_height: u32,
use_transparent_inputs: bool,
anchor_offset: u32,
) -> anyhow::Result<Vec<UTXO>> {
let mut utxos = vec![];
if use_transparent_inputs {
@ -16,8 +15,7 @@ pub async fn fetch_utxos(
let coin = CoinConfig::get(coin);
let db = coin.db.as_ref().unwrap();
let db = db.lock().unwrap();
let anchor_height = last_height.saturating_sub(anchor_offset);
utxos.extend(db.get_unspent_received_notes(account, anchor_height)?);
utxos.extend(db.get_unspent_received_notes(account, checkpoint_height)?);
Ok(utxos)
}

View File

@ -16,6 +16,7 @@ pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH";
lazy_static! {
pub static ref ORCHARD_ROOTS: Vec<Hash> = {
log::info!("Initialize Orchard Hasher");
let h = OrchardHasher::new();
h.empty_roots(32)
};
@ -37,7 +38,7 @@ impl OrchardHasher {
let mut acc = self.Q;
let (S_x, S_y) = SINSEMILLA_S[depth as usize];
let S_chunk = Affine::from_xy(S_x, S_y).unwrap();
acc = (acc + S_chunk) + acc; // TODO Bail if + gives point at infinity?
acc = (acc + S_chunk) + acc; // TODO Bail if + gives point at infinity? Shouldn't happen if data was validated
// Shift right by 1 bit and overwrite the 256th bit of left
let mut left = *left;

View File

@ -96,9 +96,13 @@ impl<N: Parameters> TrialDecrypter<N, OrchardDomain, OrchardViewKey, DecryptedOr
fn spends(&self, vtx: &CompactTx) -> Vec<Nf> {
vtx.actions
.iter()
.map(|co| {
let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap();
Nf(nf)
.filter_map(|co| {
if !co.nullifier.is_empty() {
let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap();
Some(Nf(nf))
} else {
None
}
})
.collect()
}

View File

@ -5,7 +5,7 @@ use serde::Serialize;
use crate::chain::{download_chain, DecryptNode};
use crate::transaction::{get_transaction_details, retrieve_tx_info, GetTransactionDetailRequest};
use crate::{
connect_lightwalletd, CoinConfig, CompactBlock, CompactSaplingOutput, CompactTx,
connect_lightwalletd, ChainError, CoinConfig, CompactBlock, CompactSaplingOutput, CompactTx,
DbAdapterBuilder,
};
@ -78,8 +78,35 @@ type OrchardSynchronizer = Synchronizer<
pub async fn sync_async<'a>(
coin: u8,
_chunk_size: u32,
get_tx: bool, // TODO
get_tx: bool,
target_height_offset: u32,
max_cost: u32,
progress_callback: AMProgressCallback, // TODO
cancel: &'static std::sync::Mutex<bool>,
) -> anyhow::Result<()> {
let result = sync_async_inner(
coin,
get_tx,
target_height_offset,
max_cost,
progress_callback,
cancel,
)
.await;
if let Err(ref e) = result {
if let Some(ChainError::Reorg) = e.downcast_ref::<ChainError>() {
log::info!("Drop latest checkpoint");
let c = CoinConfig::get(coin);
let mut db = c.db()?;
db.drop_last_checkpoint()?;
}
}
result
}
async fn sync_async_inner<'a>(
coin: u8,
get_tx: bool,
target_height_offset: u32,
max_cost: u32,
progress_callback: AMProgressCallback, // TODO
@ -109,7 +136,7 @@ pub async fn sync_async<'a>(
let mut height = start_height;
let (blocks_tx, mut blocks_rx) = mpsc::channel::<Blocks>(1);
tokio::spawn(async move {
let downloader = tokio::spawn(async move {
download_chain(
&mut client,
start_height,
@ -174,7 +201,9 @@ pub async fn sync_async<'a>(
"orchard".to_string(),
);
synchronizer.initialize(height)?;
log::info!("Process orchard start");
progress.trial_decryptions += synchronizer.process(&blocks.0)? as u64;
log::info!("Process orchard end");
}
}
@ -185,6 +214,8 @@ pub async fn sync_async<'a>(
cb(progress.clone());
}
downloader.await??;
if get_tx {
get_transaction_details(coin).await?;
}

View File

@ -85,24 +85,32 @@ impl<
}
pub fn process(&mut self, blocks: &[CompactBlock]) -> Result<usize> {
log::info!("=");
if blocks.is_empty() {
return Ok(0);
}
log::info!("+1");
let decrypter = self.decrypter.clone();
log::info!("+2");
let decrypted_blocks: Vec<_> = blocks
.par_iter()
.map(|b| decrypter.decrypt_notes(b, &self.vks))
.collect();
log::info!("+");
let count_outputs: usize = decrypted_blocks
.iter()
.map(|b| b.count_outputs)
.sum::<u32>() as usize;
log::info!("+");
let mut db = self.db.build()?;
log::info!("+");
self.warper.initialize(&self.tree, &self.witnesses);
log::info!("+");
let db_tx = db.begin_transaction()?;
// Detect new received notes
log::info!("+");
let mut new_witnesses = vec![];
for decb in decrypted_blocks.iter() {
for dectx in decb.txs.iter() {
@ -143,6 +151,7 @@ impl<
}
self.note_position += decb.count_outputs as usize;
}
log::info!("+");
// Detect spends and collect note commitments
let mut new_cmx = vec![];
@ -172,18 +181,24 @@ impl<
}
// Run blocks through warp sync
log::info!("+");
self.warper.add_nodes(&mut new_cmx, &new_witnesses);
log::info!("+");
let (updated_tree, updated_witnesses) = self.warper.finalize();
// Store witnesses
log::info!("+");
for w in updated_witnesses.iter() {
DbAdapter::store_witness(w, height, w.id_note, &db_tx, &self.shielded_pool)?;
}
log::info!("+");
DbAdapter::store_tree(height, &updated_tree, &db_tx, &self.shielded_pool)?;
log::info!("+");
self.tree = updated_tree;
self.witnesses = updated_witnesses;
db_tx.commit()?;
log::info!("+");
Ok(count_outputs * self.vks.len())
}
}

View File

@ -109,7 +109,11 @@ impl From<&CompactSaplingOutput> for CompactOutputBytes {
impl From<&CompactOrchardAction> for CompactOutputBytes {
fn from(co: &CompactOrchardAction) -> Self {
CompactOutputBytes {
nullifier: co.nullifier.clone().try_into().unwrap(),
nullifier: if co.nullifier.is_empty() {
[0u8; 32]
} else {
co.nullifier.clone().try_into().unwrap()
},
epk: if co.ephemeral_key.is_empty() {
[0u8; 32]
} else {

View File

@ -1,3 +1,4 @@
use crate::api::recipient::decode_memo;
use crate::contact::{Contact, ContactDecoder};
use crate::unified::orchard_as_unified;
use crate::{AccountData, CoinConfig, CompactTxStreamerClient, DbAdapter, Hash, TxFilter};
@ -41,8 +42,10 @@ pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> {
let db = db.lock().unwrap();
let reqs = db.get_txid_without_memo()?;
for req in reqs.iter() {
let decryption_keys = get_decryption_keys(network, req.account, &db)?;
keys.insert(req.account, decryption_keys);
if !keys.contains_key(&req.account) {
let decryption_keys = get_decryption_keys(network, req.account, &db)?;
keys.insert(req.account, decryption_keys);
}
}
reqs
// Make sure we don't hold a mutex across await
@ -50,15 +53,7 @@ pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> {
let mut details = vec![];
for req in reqs.iter() {
let tx_details = retrieve_tx_info(
network,
&mut client,
req.height,
req.id_tx,
&req.txid,
&keys[&req.account],
)
.await?;
let tx_details = retrieve_tx_info(network, &mut client, req, &keys[&req.account]).await?;
log::info!("{:?}", tx_details);
details.push(tx_details);
}
@ -70,6 +65,17 @@ pub async fn get_transaction_details(coin: u8) -> anyhow::Result<()> {
for c in tx_details.contacts.iter() {
db.store_contact(c, false)?;
}
let z_msg = decode_memo(
tx_details.id_tx,
&tx_details.memo,
&tx_details.address,
tx_details.timestamp,
tx_details.height,
tx_details.incoming,
);
if !z_msg.is_empty() {
db.store_message(tx_details.account, &z_msg)?;
}
}
Ok(())
@ -103,14 +109,16 @@ pub struct DecryptionKeys {
pub fn decode_transaction(
network: &Network,
account: u32,
height: u32,
timestamp: u32,
id_tx: u32,
tx: Transaction,
decryption_keys: &DecryptionKeys,
) -> anyhow::Result<TransactionDetails> {
let (sapling_ivk, sapling_ovk) = decryption_keys.sapling_keys.clone();
let height = BlockHeight::from_u32(height);
let block_height = BlockHeight::from_u32(height);
let mut taddress: Option<String> = None;
let mut zaddress: Option<String> = None;
let mut oaddress: Option<String> = None;
@ -120,6 +128,7 @@ pub fn decode_transaction(
let mut tx_memo: Memo = Memo::Empty;
let mut contacts = vec![];
let mut incoming = true;
if let Some(transparent_bundle) = tx.transparent_bundle() {
for output in transparent_bundle.vout.iter() {
@ -138,7 +147,7 @@ pub fn decode_transaction(
for output in sapling_bundle.shielded_outputs.iter() {
let pivk = PreparedIncomingViewingKey::new(&sapling_ivk);
if let Some((_note, pa, memo)) =
try_sapling_note_decryption(network, height, &pivk, output)
try_sapling_note_decryption(network, block_height, &pivk, output)
{
let memo = Memo::try_from(memo)?;
if zaddress.is_none() {
@ -152,7 +161,7 @@ pub fn decode_transaction(
}
}
if let Some((_note, pa, memo, ..)) =
try_sapling_output_recovery(network, height, &sapling_ovk, output)
try_sapling_output_recovery(network, block_height, &sapling_ovk, output)
{
let _ = contact_decoder.add_memo(&memo); // ignore memo that is not for contacts, if we cannot decode it with ovk, we didn't make create this memo
zaddress = Some(encode_payment_address(
@ -162,6 +171,7 @@ pub fn decode_transaction(
let memo = Memo::try_from(memo)?;
if memo != Memo::Empty {
tx_memo = memo;
incoming = false;
}
}
}
@ -208,9 +218,13 @@ pub fn decode_transaction(
Memo::Arbitrary(_) => "Unrecognized".to_string(),
};
let tx_details = TransactionDetails {
account,
id_tx,
address,
height,
timestamp,
memo,
incoming,
contacts,
};
@ -243,13 +257,19 @@ fn get_decryption_keys(
pub async fn retrieve_tx_info(
network: &Network,
client: &mut CompactTxStreamerClient<Channel>,
height: u32,
id_tx: u32,
txid: &Hash,
req: &GetTransactionDetailRequest,
decryption_keys: &DecryptionKeys,
) -> anyhow::Result<TransactionDetails> {
let transaction = fetch_raw_transaction(network, client, height, txid).await?;
let tx_details = decode_transaction(network, height, id_tx, transaction, &decryption_keys)?;
let transaction = fetch_raw_transaction(network, client, req.height, &req.txid).await?;
let tx_details = decode_transaction(
network,
req.account,
req.height,
req.timestamp,
req.id_tx,
transaction,
&decryption_keys,
)?;
Ok(tx_details)
}
@ -257,15 +277,20 @@ pub async fn retrieve_tx_info(
pub struct GetTransactionDetailRequest {
pub account: u32,
pub height: u32,
pub timestamp: u32,
pub id_tx: u32,
pub txid: Hash,
}
#[derive(Serialize, Debug)]
pub struct TransactionDetails {
pub account: u32,
pub id_tx: u32,
pub height: u32,
pub timestamp: u32,
pub address: String,
pub memo: String,
pub incoming: bool,
pub contacts: Vec<Contact>,
}

View File

@ -1,88 +0,0 @@
//! This file is not in use!
use zcash_address::unified::{Address, Container, Receiver};
use zcash_address::{FromAddress, Network, ToAddress, UnsupportedAddress, ZcashAddress};
#[derive(Debug, Clone)]
pub struct MyReceiver {
pub net: Network,
pub receiver: Receiver,
}
impl FromAddress for MyReceiver {
fn from_sapling(net: Network, data: [u8; 43]) -> Result<Self, UnsupportedAddress> {
Ok(MyReceiver {
net,
receiver: Receiver::Sapling(data),
})
}
fn from_unified(net: Network, data: Address) -> Result<Self, UnsupportedAddress> {
for r in data.items_as_parsed().iter() {
match r {
Receiver::Sapling(data) => {
return Ok(MyReceiver {
net,
receiver: Receiver::Sapling(*data),
});
}
_ => (),
}
}
FromAddress::from_unified(net, data)
}
fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Result<Self, UnsupportedAddress> {
Ok(MyReceiver {
net,
receiver: Receiver::P2pkh(data),
})
}
}
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)?;
// let receivers: Vec<_> = vec![sapling_addr, transparent_addr]
// .iter()
// .map(|r| r.clone().convert::<MyReceiver>().unwrap())
// .collect();
// let net = receivers.first().unwrap().net.clone();
// let receivers: Vec<_> = receivers.iter().map(|r| r.receiver.clone()).collect();
// let ua: Address = Address::from_inner(receivers)?;
// let ua_address = ZcashAddress::from_unified(net, ua);
// Ok(ua_address)
}
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 {
return Ok(ZcashAddress::from_sapling(r.net, data));
}
anyhow::bail!("Invalid UA");
}
#[cfg(test)]
mod tests {
use super::{get_sapling, get_ua};
#[test]
fn test_ua() -> anyhow::Result<()> {
let ua = get_ua(
"zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35",
"t1UWSSWaojmV5dgDhrSfZC6MAfCwVQ9LLoo",
)?;
let ua_str = ua.to_string();
println!("{}", ua);
let za = get_sapling(&ua_str)?;
println!("{}", za);
Ok(())
}
}
// t1UWSSWaojmV5dgDhrSfZC6MAfCwVQ9LLoo
// zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35
// u16cdcqfguv574pnntjx7dfh78u8m5cu3myxyvs9gedkymstj60u366vpn9qhkcch77e26rzyecyhem7qnzrl7ws2huraj8se8tgek4t3ngn4lfs95l4774mhvgyea4jj93gm92jhg3z7