wip
This commit is contained in:
parent
56b70bcceb
commit
5ddacf524c
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) =
|
||||
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 !exists {
|
||||
if c.chain.has_transparent() {
|
||||
db.create_taddr(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)?;
|
||||
db.store_ua_settings(account, false, true, c.chain.has_unified())?;
|
||||
account
|
||||
}
|
||||
};
|
||||
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 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(¬e_ids)?;
|
||||
let mut mempool = c.mempool.lock().unwrap();
|
||||
mempool.clear()?;
|
||||
Ok(tx_id)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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
134
src/db.rs
|
@ -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 {
|
||||
|
|
|
@ -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)",
|
||||
[],
|
||||
)?;
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -96,9 +96,13 @@ impl<N: Parameters> TrialDecrypter<N, OrchardDomain, OrchardViewKey, DecryptedOr
|
|||
fn spends(&self, vtx: &CompactTx) -> Vec<Nf> {
|
||||
vtx.actions
|
||||
.iter()
|
||||
.map(|co| {
|
||||
.filter_map(|co| {
|
||||
if !co.nullifier.is_empty() {
|
||||
let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap();
|
||||
Nf(nf)
|
||||
Some(Nf(nf))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
39
src/scan.rs
39
src/scan.rs
|
@ -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?;
|
||||
}
|
||||
|
|
15
src/sync.rs
15
src/sync.rs
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,24 +42,18 @@ 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() {
|
||||
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
|
||||
};
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
|
|
88
src/ua.rs
88
src/ua.rs
|
@ -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
|
Loading…
Reference in New Issue