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" rayon = "1.5.1"
tiny-bip39 = "0.8" tiny-bip39 = "0.8"
rand = "0.8.4" rand = "0.8.4"
rusqlite = { version = "0.27.0", features = ["bundled"] } rusqlite = { version = "0.27.0", features = ["bundled", "modern_sqlite"] }
jubjub = "0.9.0" jubjub = "0.9.0"
bls12_381 = "0.7" bls12_381 = "0.7"
ff = "0.12" ff = "0.12"

View File

@ -50,6 +50,8 @@ void deallocate_str(char *s);
void init_wallet(char *db_path); void init_wallet(char *db_path);
void migrate_db(char *db_path);
void set_active(uint8_t active); void set_active(uint8_t active);
void set_active_account(uint8_t coin, uint32_t id); 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 c = CoinConfig::get(coin);
let (seed, sk, ivk, pa) = decode_key(coin, key, index)?; let (seed, sk, ivk, pa) = decode_key(coin, key, index)?;
let db = c.db()?; let db = c.db()?;
let (account, exists) = let account = db.get_account_id(&ivk)?;
db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?; let account = match account {
if !exists { Some(account) => account,
if c.chain.has_transparent() { None => {
db.create_taddr(account)?; 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) Ok(account)
} }
@ -319,13 +322,24 @@ pub fn get_unified_address(
Ok(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> { pub fn get_address(coin: u8, id_account: u32, address_type: u8) -> anyhow::Result<String> {
let c = CoinConfig::get(coin); let c = CoinConfig::get(coin);
let t = address_type & 1 != 0; let address = if c.chain.has_unified() {
let s = address_type & 2 != 0; let t = address_type & 1 != 0;
let o = address_type & 4 != 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) 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::note_selection::TransactionReport;
use crate::{ChainError, TransactionPlan, Tx}; use crate::{ChainError, TransactionPlan, Tx};
use allo_isolate::{ffi, IntoDart}; use allo_isolate::{ffi, IntoDart};
@ -95,13 +95,24 @@ pub struct CResult<T> {
error: *mut c_char, error: *mut c_char,
} }
const COIN_DBNAMES: &[&str] = &["zec.db", "yec.db"];
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn init_wallet(db_path: *mut c_char) { pub unsafe extern "C" fn init_wallet(db_path: *mut c_char) {
try_init_logger(); try_init_logger();
from_c_str!(db_path); from_c_str!(db_path);
let _ = init_coin(0, &format!("{}/zec-test.db", &db_path)); for (coin, filename) in COIN_DBNAMES.iter().enumerate() {
let _ = init_coin(1, &format!("{}/yec.db", &db_path)); let _ = init_coin(coin as u8, &format!("{}/{}", &db_path, filename));
let _ = init_coin(2, &format!("{}/arrr.db", &db_path)); }
}
#[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] #[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::recipient::{RecipientMemo, RecipientShort};
use crate::api::sync::get_latest_height; use crate::api::sync::get_latest_height;
pub use crate::broadcast_tx; pub use crate::broadcast_tx;
use crate::note_selection::{FeeZIP327, Order, TransactionReport}; use crate::note_selection::{FeeFlat, FeeZIP327, Order, TransactionReport};
use crate::{ use crate::{
build_tx, fetch_utxos, get_secret_keys, AccountData, CoinConfig, TransactionBuilderConfig, build_tx, fetch_utxos, get_secret_keys, AccountData, CoinConfig, TransactionBuilderConfig,
TransactionPlan, TxBuilderContext, TransactionPlan, TxBuilderContext,
@ -22,12 +22,17 @@ pub async fn build_tx_plan(
confirmations: u32, confirmations: u32,
) -> anyhow::Result<TransactionPlan> { ) -> anyhow::Result<TransactionPlan> {
let c = CoinConfig::get(coin); let c = CoinConfig::get(coin);
let fvk = { let (fvk, checkpoint_height) = {
let db = c.db()?; let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?; 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 change_address = get_unified_address(coin, account, true, true, true)?;
let context = TxBuilderContext::from_height(coin, checkpoint_height)?;
let mut orders = vec![]; let mut orders = vec![];
let mut id_order = 0; let mut id_order = 0;
@ -47,14 +52,15 @@ pub async fn build_tx_plan(
id_order += 1; 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); log::info!("UTXO: {:?}", utxos);
let config = TransactionBuilderConfig::new(&change_address); 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, &fvk,
last_height, checkpoint_height,
&context.orchard_anchor,
&utxos, &utxos,
&orders, &orders,
&config, &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 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, OsRng)?;
let tx = build_tx(c.chain.network(), &keys, &tx_plan, context, OsRng).unwrap();
Ok(tx) Ok(tx)
} }

View File

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

View File

@ -3,7 +3,7 @@
use crate::coinconfig::CoinConfig; use crate::coinconfig::CoinConfig;
use crate::scan::{AMProgressCallback, Progress}; use crate::scan::{AMProgressCallback, Progress};
use crate::sync::CTree; use crate::sync::CTree;
use crate::{AccountData, BlockId, CompactTxStreamerClient, DbAdapter}; use crate::{AccountData, BlockId, ChainError, CompactTxStreamerClient, DbAdapter};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tonic::transport::Channel; use tonic::transport::Channel;
@ -29,33 +29,14 @@ pub async fn coin_sync(
cancel: &'static std::sync::Mutex<bool>, cancel: &'static std::sync::Mutex<bool>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let p_cb = Arc::new(Mutex::new(progress_callback)); let p_cb = Arc::new(Mutex::new(progress_callback));
coin_sync_impl( coin_sync_impl(coin, get_tx, anchor_offset, max_cost, p_cb.clone(), cancel).await?;
coin, coin_sync_impl(coin, get_tx, 0, u32::MAX, p_cb.clone(), cancel).await?;
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?;
Ok(()) Ok(())
} }
async fn coin_sync_impl( async fn coin_sync_impl(
coin: u8, coin: u8,
get_tx: bool, get_tx: bool,
chunk_size: u32,
target_height_offset: u32, target_height_offset: u32,
max_cost: u32, max_cost: u32,
progress_callback: AMProgressCallback, progress_callback: AMProgressCallback,
@ -63,15 +44,13 @@ async fn coin_sync_impl(
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
crate::scan::sync_async( crate::scan::sync_async(
coin, coin,
chunk_size,
get_tx, get_tx,
target_height_offset, target_height_offset,
max_cost, max_cost,
progress_callback, progress_callback,
cancel, cancel,
) )
.await?; .await
Ok(())
} }
/// Return the latest block height /// Return the latest block height
@ -135,9 +114,21 @@ async fn fetch_and_store_tree_state(
.get_tree_state(Request::new(block_id)) .get_tree_state(Request::new(block_id))
.await? .await?
.into_inner(); .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()?; 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(()) Ok(())
} }

View File

@ -7,7 +7,7 @@ use lazycell::AtomicLazyCell;
use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use tonic::transport::Channel; 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_params::{OUTPUT_PARAMS, SPEND_PARAMS};
use zcash_proofs::prover::LocalTxProver; 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<()> { pub fn init_coin(coin: u8, db_path: &str) -> anyhow::Result<()> {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap(); let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
c.set_db_path(db_path)?; 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(()) Ok(())
} }
@ -90,7 +99,21 @@ impl CoinConfig {
pub fn set_db_path(&mut self, db_path: &str) -> anyhow::Result<()> { pub fn set_db_path(&mut self, db_path: &str) -> anyhow::Result<()> {
self.db_path = Some(db_path.to_string()); 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()?; db.init_db()?;
self.db = Some(Arc::new(Mutex::new(db))); self.db = Some(Arc::new(Mutex::new(db)));
Ok(()) 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::orchard::{derive_orchard_keys, OrchardKeyBytes, OrchardViewKey};
use crate::prices::Quote; use crate::prices::Quote;
use crate::sapling::SaplingViewKey; use crate::sapling::SaplingViewKey;
use crate::sync;
use crate::sync::tree::{CTree, TreeCheckpoint}; use crate::sync::tree::{CTree, TreeCheckpoint};
use crate::taddr::{derive_tkeys, TBalance}; use crate::taddr::{derive_tkeys, TBalance};
use crate::transaction::{GetTransactionDetailRequest, TransactionDetails}; use crate::transaction::{GetTransactionDetailRequest, TransactionDetails};
use crate::unified::UnifiedAddressType; use crate::unified::UnifiedAddressType;
use crate::{get_unified_address, sync};
use orchard::keys::FullViewingKey; use orchard::keys::FullViewingKey;
use rusqlite::Error::QueryReturnedNoRows; use rusqlite::Error::QueryReturnedNoRows;
use rusqlite::{params, Connection, OptionalExtension, Transaction}; 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<()> { pub fn disable_wal(db_path: &str) -> anyhow::Result<()> {
let connection = Connection::open(db_path)?; let connection = Connection::open(db_path)?;
connection.query_row("PRAGMA journal_mode = OFF", [], |_| Ok(()))?; connection.query_row("PRAGMA journal_mode = OFF", [], |_| Ok(()))?;
@ -125,7 +131,6 @@ impl DbAdapter {
} }
pub fn init_db(&mut self) -> anyhow::Result<()> { pub fn init_db(&mut self) -> anyhow::Result<()> {
migration::init_db(&self.connection, self.network())?;
self.delete_incomplete_scan()?; self.delete_incomplete_scan()?;
Ok(()) Ok(())
} }
@ -135,6 +140,21 @@ impl DbAdapter {
Ok(()) 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( pub fn store_account(
&self, &self,
name: &str, name: &str,
@ -143,11 +163,7 @@ impl DbAdapter {
sk: Option<&str>, sk: Option<&str>,
ivk: &str, ivk: &str,
address: &str, address: &str,
) -> anyhow::Result<(u32, bool)> { ) -> anyhow::Result<u32> {
let mut statement = self
.connection
.prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?;
let exists = statement.exists(params![ivk])?;
self.connection.execute( self.connection.execute(
"INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", "INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
params![name, seed, index, sk, ivk, address], params![name, seed, index, sk, ivk, address],
@ -160,7 +176,7 @@ impl DbAdapter {
|row| row.get(0), |row| row.get(0),
) )
.map_err(wrap_query_no_rows("store_account/id_account"))?; .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> { pub fn next_account_id(&self, seed: &str) -> anyhow::Result<u32> {
@ -231,6 +247,15 @@ impl DbAdapter {
Ok(fvks) 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> { pub fn trim_to_height(&mut self, height: u32) -> anyhow::Result<u32> {
// snap height to an existing checkpoint // snap height to an existing checkpoint
let height = self.connection.query_row( let height = self.connection.query_row(
@ -286,20 +311,25 @@ impl DbAdapter {
hash: &[u8], hash: &[u8],
timestamp: u32, timestamp: u32,
sapling_tree: &CTree, sapling_tree: &CTree,
orchard_tree: Option<&CTree>, orchard_tree: &CTree,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
log::debug!("+block"); log::info!("+store_block");
let mut sapling_bb: Vec<u8> = vec![]; let mut sapling_bb: Vec<u8> = vec![];
sapling_tree.write(&mut sapling_bb)?; 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( connection.execute(
"INSERT INTO blocks(height, hash, timestamp, sapling_tree, orchard_tree) "INSERT INTO blocks(height, hash, timestamp)
VALUES (?1, ?2, ?3, ?4, ?5)", VALUES (?1, ?2, ?3)",
params![height, hash, timestamp, &sapling_bb, orchard_bb], 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"); log::debug!("-block");
Ok(()) Ok(())
@ -431,20 +461,25 @@ impl DbAdapter {
} }
pub fn get_last_sync_height(&self) -> anyhow::Result<Option<u32>> { pub fn get_last_sync_height(&self) -> anyhow::Result<Option<u32>> {
let height: Option<u32> = self let height: Option<u32> =
.connection self.connection
.query_row("SELECT MAX(height) FROM blocks", [], |row| row.get(0)) .query_row("SELECT MAX(height) FROM blocks", [], |row| row.get(0))?;
.map_err(wrap_query_no_rows(""))?; 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) Ok(height)
} }
pub fn get_db_height(&self) -> anyhow::Result<u32> { pub fn get_db_height(&self) -> anyhow::Result<u32> {
let height: u32 = self.get_last_sync_height()?.unwrap_or_else(|| { let height: u32 = self
self.network() .get_last_sync_height()?
.activation_height(NetworkUpgrade::Sapling) .unwrap_or_else(|| self.sapling_activation_height());
.unwrap()
.into()
});
Ok(height) Ok(height)
} }
@ -556,15 +591,15 @@ impl DbAdapter {
pub fn get_unspent_received_notes( pub fn get_unspent_received_notes(
&self, &self,
account: u32, account: u32,
anchor_height: u32, checkpoint_height: u32,
) -> anyhow::Result<Vec<UTXO>> { ) -> anyhow::Result<Vec<UTXO>> {
let mut notes = vec![]; let mut notes = vec![];
let mut statement = self.connection.prepare( 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 "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 = ( AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ?1
SELECT MAX(height) FROM sapling_witnesses WHERE height <= ?1 AND r.id_note = w.note")?;
) AND r.id_note = w.note")?; let rows = statement.query_map(params![checkpoint_height, account], |row| {
let rows = statement.query_map(params![anchor_height, account], |row| {
let id_note: u32 = row.get(0)?; let id_note: u32 = row.get(0)?;
let diversifier: Vec<u8> = row.get(1)?; let diversifier: Vec<u8> = row.get(1)?;
let amount: i64 = row.get(2)?; let amount: i64 = row.get(2)?;
@ -589,10 +624,9 @@ impl DbAdapter {
let mut statement = self.connection.prepare( 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 "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 = ( AND (r.excluded IS NULL OR NOT r.excluded) AND w.height = ?1
SELECT MAX(height) FROM orchard_witnesses WHERE height <= ?1 AND r.id_note = w.note")?;
) AND r.id_note = w.note")?; let rows = statement.query_map(params![checkpoint_height, account], |row| {
let rows = statement.query_map(params![anchor_height, account], |row| {
let id_note: u32 = row.get(0)?; let id_note: u32 = row.get(0)?;
let diversifier: Vec<u8> = row.get(1)?; let diversifier: Vec<u8> = row.get(1)?;
let amount: i64 = row.get(2)?; let amount: i64 = row.get(2)?;
@ -968,6 +1002,10 @@ impl DbAdapter {
"DELETE FROM orchard_addrs WHERE account = ?1", "DELETE FROM orchard_addrs WHERE account = ?1",
params![account], params![account],
)?; )?;
self.connection.execute(
"DELETE FROM ua_settings WHERE account = ?1",
params![account],
)?;
self.connection self.connection
.execute("DELETE FROM messages WHERE account = ?1", params![account])?; .execute("DELETE FROM messages WHERE account = ?1", params![account])?;
Ok(()) Ok(())
@ -1031,8 +1069,8 @@ impl DbAdapter {
} }
pub fn store_message(&self, account: u32, message: &ZMessage) -> anyhow::Result<()> { 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)", 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, false])?; params![account, message.id_tx, message.sender, message.recipient, message.subject, message.body, message.timestamp, message.height, message.incoming, false])?;
Ok(()) Ok(())
} }
@ -1119,18 +1157,20 @@ impl DbAdapter {
} }
pub fn get_txid_without_memo(&self) -> anyhow::Result<Vec<GetTransactionDetailRequest>> { pub fn get_txid_without_memo(&self) -> anyhow::Result<Vec<GetTransactionDetailRequest>> {
let mut stmt = self let mut stmt = self.connection.prepare(
.connection "SELECT account, id_tx, height, timestamp, txid FROM transactions WHERE memo IS NULL",
.prepare("SELECT account, id_tx, height, txid FROM transactions WHERE memo IS NULL")?; )?;
let rows = stmt.query_map([], |row| { let rows = stmt.query_map([], |row| {
let account: u32 = row.get(0)?; let account: u32 = row.get(0)?;
let id_tx: u32 = row.get(1)?; let id_tx: u32 = row.get(1)?;
let height: u32 = row.get(2)?; 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 { Ok(GetTransactionDetailRequest {
account, account,
id_tx, id_tx,
height, height,
timestamp,
txid: txid.try_into().unwrap(), txid: txid.try_into().unwrap(),
}) })
})?; })?;
@ -1204,6 +1244,13 @@ impl DbAdapter {
let chain = get_coin_chain(self.coin_type); let chain = get_coin_chain(self.coin_type);
chain.network() chain.network()
} }
pub fn sapling_activation_height(&self) -> u32 {
self.network()
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
.into()
}
} }
pub struct ZMessage { pub struct ZMessage {
@ -1214,6 +1261,7 @@ pub struct ZMessage {
pub body: String, pub body: String,
pub timestamp: u32, pub timestamp: u32,
pub height: u32, pub height: u32,
pub incoming: bool,
} }
impl ZMessage { impl ZMessage {

View File

@ -34,7 +34,7 @@ pub fn reset_db(connection: &Connection) -> anyhow::Result<()> {
Ok(()) 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( connection.execute(
"CREATE TABLE IF NOT EXISTS schema_version ( "CREATE TABLE IF NOT EXISTS schema_version (
id INTEGER PRIMARY KEY NOT NULL, id INTEGER PRIMARY KEY NOT NULL,
@ -195,7 +195,9 @@ pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()>
orchard BOOL NOT NULL)", orchard BOOL NOT NULL)",
[], [],
)?; )?;
upgrade_accounts(&connection, network)?; if has_ua {
upgrade_accounts(&connection, network)?;
}
connection.execute( connection.execute(
"CREATE TABLE sapling_tree( "CREATE TABLE sapling_tree(
height INTEGER PRIMARY KEY, 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)", "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 { if version != 5 {
update_schema_version(connection, 5)?; update_schema_version(connection, 5)?;
connection.cache_flush()?;
log::info!("Database migrated"); log::info!("Database migrated");
} }

View File

@ -289,15 +289,17 @@ pub async fn pay(payment: Json<Payment>, config: &State<Config>) -> Result<Strin
.iter() .iter()
.map(|p| RecipientMemo::from_recipient(&from, p)) .map(|p| RecipientMemo::from_recipient(&from, p))
.collect(); .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.coin,
c.id_account, c.id_account,
latest, latest,
&recipients, &recipients,
payment.confirmations, payment.confirmations,
Box::new(|_| {}),
) )
.await?; .await?;
let txid =
warp_api_ffi::api::payment_v2::sign_and_broadcast(c.coin, c.id_account, &tx_plan)
.await?;
Ok(txid) 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 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, OsRng).unwrap();
let tx = build_tx(c.chain.network(), &keys, &tx_plan, context, OsRng).unwrap();
let tx = hex::encode(&tx); let tx = hex::encode(&tx);
Ok(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::sapling::{SaplingHasher, SAPLING_ROOTS};
use crate::sync::tree::TreeCheckpoint; use crate::sync::tree::TreeCheckpoint;
use crate::sync::Witness; 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 anyhow::anyhow;
use jubjub::Fr; use jubjub::Fr;
use orchard::builder::Builder as OrchardBuilder; use orchard::builder::Builder as OrchardBuilder;
@ -75,7 +75,6 @@ pub fn build_tx(
network: &Network, network: &Network,
skeys: &SecretKeys, skeys: &SecretKeys,
plan: &TransactionPlan, plan: &TransactionPlan,
context: TxBuilderContext,
mut rng: impl RngCore + CryptoRng + Clone, mut rng: impl RngCore + CryptoRng + Clone,
) -> anyhow::Result<Vec<u8>> { ) -> anyhow::Result<Vec<u8>> {
let secp = Secp256k1::<All>::new(); let secp = Secp256k1::<All>::new();
@ -100,8 +99,9 @@ pub fn build_tx(
}; };
let mut has_orchard = false; let mut has_orchard = false;
let mut builder = Builder::new(*network, BlockHeight::from_u32(context.height)); let mut builder = Builder::new(*network, BlockHeight::from_u32(plan.height));
let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&context.orchard_anchor) log::info!("ANCHOR {}", hex::encode(&plan.orchard_anchor));
let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&plan.orchard_anchor)
.unwrap() .unwrap()
.into(); .into();
let mut orchard_builder = OrchardBuilder::new(Flags::from_parts(true, true), anchor); let mut orchard_builder = OrchardBuilder::new(Flags::from_parts(true, true), anchor);
@ -204,7 +204,7 @@ pub fn build_tx(
get_prover(), get_prover(),
&mut ctx, &mut ctx,
&mut rng, &mut rng,
BlockHeight::from_u32(context.height), BlockHeight::from_u32(plan.height),
None, None,
) )
.unwrap(); .unwrap();
@ -219,7 +219,7 @@ pub fn build_tx(
TxVersion::Zip225, TxVersion::Zip225,
BranchId::Nu5, BranchId::Nu5,
0, 0,
BlockHeight::from_u32(context.height + EXPIRY_HEIGHT), BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT),
transparent_bundle, transparent_bundle,
None, None,
sapling_bundle, sapling_bundle,
@ -261,7 +261,7 @@ pub fn build_tx(
TxVersion::Zip225, TxVersion::Zip225,
BranchId::Nu5, BranchId::Nu5,
0, 0,
BlockHeight::from_u32(context.height + EXPIRY_HEIGHT), BlockHeight::from_u32(plan.height + EXPIRY_HEIGHT),
transparent_bundle, transparent_bundle,
None, None,
sapling_bundle, sapling_bundle,
@ -347,7 +347,9 @@ async fn dummy_test() {
orders.push(Order::new(7, "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8", 70000, MemoBytes::empty())); orders.push(Order::new(7, "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8", 70000, MemoBytes::empty()));
log::info!("Building tx plan"); 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!("Plan: {}", serde_json::to_string(&tx_plan).unwrap());
log::info!("Building tx"); 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::ua::decode;
use crate::note_selection::TransactionBuilderError::TxTooComplex; use crate::note_selection::TransactionBuilderError::TxTooComplex;
use crate::note_selection::{TransactionBuilderError, MAX_ATTEMPTS}; use crate::note_selection::{TransactionBuilderError, MAX_ATTEMPTS};
use crate::Hash;
use anyhow::anyhow; use anyhow::anyhow;
use std::str::FromStr; use std::str::FromStr;
use zcash_primitives::memo::{Memo, MemoBytes}; use zcash_primitives::memo::{Memo, MemoBytes};
@ -282,6 +283,7 @@ pub fn outputs_for_change(
pub fn build_tx_plan<F: FeeCalculator>( pub fn build_tx_plan<F: FeeCalculator>(
fvk: &str, fvk: &str,
height: u32, height: u32,
orchard_anchor: &Hash,
utxos: &[UTXO], utxos: &[UTXO],
orders: &[Order], orders: &[Order],
config: &TransactionBuilderConfig, config: &TransactionBuilderConfig,
@ -312,6 +314,7 @@ pub fn build_tx_plan<F: FeeCalculator>(
let tx_plan = TransactionPlan { let tx_plan = TransactionPlan {
fvk: fvk.to_string(), fvk: fvk.to_string(),
height, height,
orchard_anchor: orchard_anchor.clone(),
spends: notes, spends: notes,
outputs: fills, outputs: fills,
net_chg, net_chg,

View File

@ -57,7 +57,7 @@ impl TransactionReport {
let net_sapling = outs[1] as i64 - spends[1] as i64; let net_sapling = outs[1] as i64 - spends[1] as i64;
let net_orchard = outs[2] as i64 - spends[2] 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 0 // very low privacy: t2t
} else if outs[0] != 0 || spends[0] != 0 { } else if outs[0] != 0 || spends[0] != 0 {
1 // low privacy: t2z or z2t 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::fee::{FeeCalculator, FeeZIP327};
use crate::note_selection::optimize::{outputs_for_change, select_inputs}; use crate::note_selection::optimize::{outputs_for_change, select_inputs};
use crate::note_selection::ua::decode; use crate::note_selection::ua::decode;
use crate::Hash;
use assert_matches::assert_matches; use assert_matches::assert_matches;
use serde::Serialize; use serde::Serialize;
use serde_json::Value; use serde_json::Value;
@ -13,6 +14,7 @@ use zcash_primitives::memo::MemoBytes;
macro_rules! utxo { macro_rules! utxo {
($id:expr, $q:expr) => { ($id:expr, $q:expr) => {
UTXO { UTXO {
id: $id,
amount: $q * 1000, amount: $q * 1000,
source: Source::Transparent { source: Source::Transparent {
txid: [0u8; 32], txid: [0u8; 32],
@ -25,6 +27,7 @@ macro_rules! utxo {
macro_rules! sapling { macro_rules! sapling {
($id:expr, $q:expr) => { ($id:expr, $q:expr) => {
UTXO { UTXO {
id: $id,
amount: $q * 1000, amount: $q * 1000,
source: Source::Sapling { source: Source::Sapling {
id_note: $id, id_note: $id,
@ -39,6 +42,7 @@ macro_rules! sapling {
macro_rules! orchard { macro_rules! orchard {
($id:expr, $q:expr) => { ($id:expr, $q:expr) => {
UTXO { UTXO {
id: $id,
amount: $q * 1000, amount: $q * 1000,
source: Source::Orchard { source: Source::Orchard {
id_note: $id, id_note: $id,
@ -731,6 +735,7 @@ fn test_tx_plan() {
let tx_plan = build_tx_plan::<FeeZIP327>( let tx_plan = build_tx_plan::<FeeZIP327>(
"", "",
0, 0,
&[Hash::default(); 2],
&utxos, &utxos,
&orders, &orders,
&TransactionBuilderConfig { &TransactionBuilderConfig {

View File

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

View File

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

View File

@ -16,6 +16,7 @@ pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH";
lazy_static! { lazy_static! {
pub static ref ORCHARD_ROOTS: Vec<Hash> = { pub static ref ORCHARD_ROOTS: Vec<Hash> = {
log::info!("Initialize Orchard Hasher");
let h = OrchardHasher::new(); let h = OrchardHasher::new();
h.empty_roots(32) h.empty_roots(32)
}; };
@ -37,7 +38,7 @@ impl OrchardHasher {
let mut acc = self.Q; let mut acc = self.Q;
let (S_x, S_y) = SINSEMILLA_S[depth as usize]; let (S_x, S_y) = SINSEMILLA_S[depth as usize];
let S_chunk = Affine::from_xy(S_x, S_y).unwrap(); 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 // Shift right by 1 bit and overwrite the 256th bit of left
let mut left = *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> { fn spends(&self, vtx: &CompactTx) -> Vec<Nf> {
vtx.actions vtx.actions
.iter() .iter()
.map(|co| { .filter_map(|co| {
let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap(); if !co.nullifier.is_empty() {
Nf(nf) let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap();
Some(Nf(nf))
} else {
None
}
}) })
.collect() .collect()
} }

View File

@ -5,7 +5,7 @@ use serde::Serialize;
use crate::chain::{download_chain, DecryptNode}; use crate::chain::{download_chain, DecryptNode};
use crate::transaction::{get_transaction_details, retrieve_tx_info, GetTransactionDetailRequest}; use crate::transaction::{get_transaction_details, retrieve_tx_info, GetTransactionDetailRequest};
use crate::{ use crate::{
connect_lightwalletd, CoinConfig, CompactBlock, CompactSaplingOutput, CompactTx, connect_lightwalletd, ChainError, CoinConfig, CompactBlock, CompactSaplingOutput, CompactTx,
DbAdapterBuilder, DbAdapterBuilder,
}; };
@ -78,8 +78,35 @@ type OrchardSynchronizer = Synchronizer<
pub async fn sync_async<'a>( pub async fn sync_async<'a>(
coin: u8, coin: u8,
_chunk_size: u32, get_tx: bool,
get_tx: bool, // TODO 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, target_height_offset: u32,
max_cost: u32, max_cost: u32,
progress_callback: AMProgressCallback, // TODO progress_callback: AMProgressCallback, // TODO
@ -109,7 +136,7 @@ pub async fn sync_async<'a>(
let mut height = start_height; let mut height = start_height;
let (blocks_tx, mut blocks_rx) = mpsc::channel::<Blocks>(1); let (blocks_tx, mut blocks_rx) = mpsc::channel::<Blocks>(1);
tokio::spawn(async move { let downloader = tokio::spawn(async move {
download_chain( download_chain(
&mut client, &mut client,
start_height, start_height,
@ -174,7 +201,9 @@ pub async fn sync_async<'a>(
"orchard".to_string(), "orchard".to_string(),
); );
synchronizer.initialize(height)?; synchronizer.initialize(height)?;
log::info!("Process orchard start");
progress.trial_decryptions += synchronizer.process(&blocks.0)? as u64; 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()); cb(progress.clone());
} }
downloader.await??;
if get_tx { if get_tx {
get_transaction_details(coin).await?; get_transaction_details(coin).await?;
} }

View File

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

View File

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

View File

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