wip
This commit is contained in:
parent
56b70bcceb
commit
5ddacf524c
|
@ -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"
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)?;
|
||||||
|
let account = match account {
|
||||||
|
Some(account) => account,
|
||||||
|
None => {
|
||||||
|
let account =
|
||||||
db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?;
|
db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?;
|
||||||
if !exists {
|
|
||||||
if c.chain.has_transparent() {
|
if c.chain.has_transparent() {
|
||||||
db.create_taddr(account)?;
|
db.create_taddr(account)?;
|
||||||
}
|
}
|
||||||
if c.chain.has_unified() {
|
if c.chain.has_unified() {
|
||||||
db.create_orchard(account)?;
|
db.create_orchard(account)?;
|
||||||
}
|
}
|
||||||
db.store_ua_settings(account, true, true, true)?;
|
db.store_ua_settings(account, false, true, c.chain.has_unified())?;
|
||||||
} else {
|
account
|
||||||
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 address = if c.chain.has_unified() {
|
||||||
let t = address_type & 1 != 0;
|
let t = address_type & 1 != 0;
|
||||||
let s = address_type & 2 != 0;
|
let s = address_type & 2 != 0;
|
||||||
let o = address_type & 4 != 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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::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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
134
src/db.rs
|
@ -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 {
|
||||||
|
|
|
@ -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)",
|
||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
if has_ua {
|
||||||
upgrade_accounts(&connection, network)?;
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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| {
|
||||||
|
if !co.nullifier.is_empty() {
|
||||||
let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap();
|
let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap();
|
||||||
Nf(nf)
|
Some(Nf(nf))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
39
src/scan.rs
39
src/scan.rs
|
@ -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?;
|
||||||
}
|
}
|
||||||
|
|
15
src/sync.rs
15
src/sync.rs
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,24 +42,18 @@ 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() {
|
||||||
|
if !keys.contains_key(&req.account) {
|
||||||
let decryption_keys = get_decryption_keys(network, req.account, &db)?;
|
let decryption_keys = get_decryption_keys(network, req.account, &db)?;
|
||||||
keys.insert(req.account, decryption_keys);
|
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
|
||||||
};
|
};
|
||||||
|
|
||||||
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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