taddrs
This commit is contained in:
parent
7d40ad28c6
commit
cb7a2e04b9
|
@ -38,6 +38,10 @@ bls12_381 = "^0.4.0"
|
||||||
ff = "^0.9"
|
ff = "^0.9"
|
||||||
group = "^0.9"
|
group = "^0.9"
|
||||||
byteorder = "^1.4"
|
byteorder = "^1.4"
|
||||||
|
secp256k1 = "0.20.2"
|
||||||
|
tiny-hderive = "0.3.0"
|
||||||
|
ripemd160 = "0.9.1"
|
||||||
|
sha2 = "0.9.5"
|
||||||
|
|
||||||
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
|
# librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd
|
||||||
|
|
||||||
|
@ -46,6 +50,7 @@ path = "/home/hanh/projects/librustzcash/zcash_client_backend"
|
||||||
|
|
||||||
[dependencies.zcash_primitives]
|
[dependencies.zcash_primitives]
|
||||||
path = "/home/hanh/projects/librustzcash/zcash_primitives"
|
path = "/home/hanh/projects/librustzcash/zcash_primitives"
|
||||||
|
features = [ "transparent-inputs" ]
|
||||||
|
|
||||||
[dependencies.zcash_proofs]
|
[dependencies.zcash_proofs]
|
||||||
path = "/home/hanh/projects/librustzcash/zcash_proofs"
|
path = "/home/hanh/projects/librustzcash/zcash_proofs"
|
||||||
|
|
12
src/chain.rs
12
src/chain.rs
|
@ -1,7 +1,7 @@
|
||||||
use crate::commitment::{CTree, Witness};
|
use crate::commitment::{CTree, Witness};
|
||||||
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
|
||||||
use crate::lw_rpc::*;
|
use crate::lw_rpc::*;
|
||||||
use crate::{advance_tree, NETWORK, LWD_URL};
|
use crate::{advance_tree, NETWORK};
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
use group::GroupEncoding;
|
use group::GroupEncoding;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
@ -316,9 +316,9 @@ pub fn calculate_tree_state_v2(cbs: &[CompactBlock], blocks: &[DecryptedBlock])
|
||||||
new_witnesses
|
new_witnesses
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect_lightwalletd() -> anyhow::Result<CompactTxStreamerClient<Channel>> {
|
pub async fn connect_lightwalletd(url: &str) -> anyhow::Result<CompactTxStreamerClient<Channel>> {
|
||||||
let mut channel = tonic::transport::Channel::from_shared(LWD_URL)?;
|
let mut channel = tonic::transport::Channel::from_shared(url.to_owned())?;
|
||||||
if LWD_URL.starts_with("https") {
|
if url.starts_with("https") {
|
||||||
let pem = include_bytes!("ca.pem");
|
let pem = include_bytes!("ca.pem");
|
||||||
let ca = Certificate::from_pem(pem);
|
let ca = Certificate::from_pem(pem);
|
||||||
let tls = ClientTlsConfig::new().ca_certificate(ca);
|
let tls = ClientTlsConfig::new().ca_certificate(ca);
|
||||||
|
@ -328,7 +328,7 @@ pub async fn connect_lightwalletd() -> anyhow::Result<CompactTxStreamerClient<Ch
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sync(fvks: &HashMap<u32, String>) -> anyhow::Result<()> {
|
pub async fn sync(fvks: &HashMap<u32, String>, ld_url: &str) -> anyhow::Result<()> {
|
||||||
let fvks: HashMap<_, _> = fvks.iter().map(|(&account, fvk)| {
|
let fvks: HashMap<_, _> = fvks.iter().map(|(&account, fvk)| {
|
||||||
let fvk =
|
let fvk =
|
||||||
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
|
decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk)
|
||||||
|
@ -337,7 +337,7 @@ pub async fn sync(fvks: &HashMap<u32, String>) -> anyhow::Result<()> {
|
||||||
(account, fvk)
|
(account, fvk)
|
||||||
}).collect();
|
}).collect();
|
||||||
let decrypter = DecryptNode::new(fvks);
|
let decrypter = DecryptNode::new(fvks);
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(ld_url).await?;
|
||||||
let start_height: u32 = crate::NETWORK
|
let start_height: u32 = crate::NETWORK
|
||||||
.activation_height(NetworkUpgrade::Sapling)
|
.activation_height(NetworkUpgrade::Sapling)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
71
src/db.rs
71
src/db.rs
|
@ -6,6 +6,7 @@ use zcash_primitives::consensus::{NetworkUpgrade, Parameters};
|
||||||
use zcash_primitives::merkle_tree::IncrementalWitness;
|
use zcash_primitives::merkle_tree::IncrementalWitness;
|
||||||
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed};
|
use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed};
|
||||||
use zcash_primitives::zip32::{ExtendedFullViewingKey, DiversifierIndex};
|
use zcash_primitives::zip32::{ExtendedFullViewingKey, DiversifierIndex};
|
||||||
|
use crate::taddr::{derive_tkeys, BIP44_PATH};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const DEFAULT_DB_PATH: &str = "zec.db";
|
pub const DEFAULT_DB_PATH: &str = "zec.db";
|
||||||
|
@ -105,6 +106,14 @@ impl DbAdapter {
|
||||||
NO_PARAMS,
|
NO_PARAMS,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
self.connection.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS taddrs (
|
||||||
|
account INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
sk TEXT NOT NULL,
|
||||||
|
address TEXT NOT NULL)",
|
||||||
|
NO_PARAMS,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +456,20 @@ impl DbAdapter {
|
||||||
Ok((seed, sk, ivk))
|
Ok((seed, sk, ivk))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_seed(&self, account: u32) -> anyhow::Result<Option<String>> {
|
||||||
|
log::info!("+get_seed");
|
||||||
|
let seed = self.connection.query_row(
|
||||||
|
"SELECT seed FROM accounts WHERE id_account = ?1",
|
||||||
|
params![account],
|
||||||
|
|row| {
|
||||||
|
let sk: String = row.get(0)?;
|
||||||
|
Ok(sk)
|
||||||
|
},
|
||||||
|
).optional()?;
|
||||||
|
log::info!("-get_seed");
|
||||||
|
Ok(seed)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_sk(&self, account: u32) -> anyhow::Result<String> {
|
pub fn get_sk(&self, account: u32) -> anyhow::Result<String> {
|
||||||
log::info!("+get_sk");
|
log::info!("+get_sk");
|
||||||
let sk = self.connection.query_row(
|
let sk = self.connection.query_row(
|
||||||
|
@ -475,6 +498,20 @@ impl DbAdapter {
|
||||||
Ok(ivk)
|
Ok(ivk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_address(&self, account: u32) -> anyhow::Result<String> {
|
||||||
|
log::debug!("+get_address");
|
||||||
|
let address = self.connection.query_row(
|
||||||
|
"SELECT address FROM accounts WHERE id_account = ?1",
|
||||||
|
params![account],
|
||||||
|
|row| {
|
||||||
|
let address: String = row.get(0)?;
|
||||||
|
Ok(address)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
log::debug!("-get_address");
|
||||||
|
Ok(address)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_diversifier(&self, account: u32) -> anyhow::Result<DiversifierIndex> {
|
pub fn get_diversifier(&self, account: u32) -> anyhow::Result<DiversifierIndex> {
|
||||||
let diversifier_index = self.connection.query_row(
|
let diversifier_index = self.connection.query_row(
|
||||||
"SELECT diversifier_index FROM diversifiers WHERE account = ?1",
|
"SELECT diversifier_index FROM diversifiers WHERE account = ?1",
|
||||||
|
@ -484,7 +521,7 @@ impl DbAdapter {
|
||||||
let mut div = [0u8; 11];
|
let mut div = [0u8; 11];
|
||||||
div.copy_from_slice(&d);
|
div.copy_from_slice(&d);
|
||||||
Ok(div)
|
Ok(div)
|
||||||
}
|
},
|
||||||
).optional()?.unwrap_or_else(|| [0u8; 11]);
|
).optional()?.unwrap_or_else(|| [0u8; 11]);
|
||||||
Ok(DiversifierIndex(diversifier_index))
|
Ok(DiversifierIndex(diversifier_index))
|
||||||
}
|
}
|
||||||
|
@ -497,6 +534,38 @@ impl DbAdapter {
|
||||||
params![account, diversifier_bytes])?;
|
params![account, diversifier_bytes])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_taddr(&self, account: u32) -> anyhow::Result<Option<String>> {
|
||||||
|
let address = self.connection.query_row(
|
||||||
|
"SELECT address FROM taddrs WHERE account = ?1",
|
||||||
|
params![account],
|
||||||
|
|row| {
|
||||||
|
let address: String = row.get(0)?;
|
||||||
|
Ok(address)
|
||||||
|
}).optional()?;
|
||||||
|
Ok(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tsk(&self, account: u32) -> anyhow::Result<String> {
|
||||||
|
let sk = self.connection.query_row(
|
||||||
|
"SELECT sk FROM taddrs WHERE account = ?1",
|
||||||
|
params![account],
|
||||||
|
|row| {
|
||||||
|
let address: String = row.get(0)?;
|
||||||
|
Ok(address)
|
||||||
|
})?;
|
||||||
|
Ok(sk)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_taddr(&self, account: u32) -> anyhow::Result<()> {
|
||||||
|
let seed = self.get_seed(account)?;
|
||||||
|
if let Some(seed) = seed {
|
||||||
|
let (sk, address) = derive_tkeys(&seed, BIP44_PATH)?;
|
||||||
|
self.connection.execute("INSERT INTO taddrs(account, sk, address) VALUES (?1, ?2, ?3) \
|
||||||
|
ON CONFLICT DO NOTHING", params![account, &sk, &address])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -22,6 +22,7 @@ mod key;
|
||||||
mod mempool;
|
mod mempool;
|
||||||
mod print;
|
mod print;
|
||||||
mod scan;
|
mod scan;
|
||||||
|
mod taddr;
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
pub use crate::builder::advance_tree;
|
pub use crate::builder::advance_tree;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use bip39::{Language, Mnemonic};
|
use bip39::{Language, Mnemonic};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2};
|
use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2, LWD_URL};
|
||||||
use rusqlite::NO_PARAMS;
|
use rusqlite::NO_PARAMS;
|
||||||
|
|
||||||
const DB_NAME: &str = "zec.db";
|
const DB_NAME: &str = "zec.db";
|
||||||
|
@ -22,7 +22,7 @@ async fn test() -> anyhow::Result<()> {
|
||||||
let progress = |height| {
|
let progress = |height| {
|
||||||
log::info!("Height = {}", height);
|
log::info!("Height = {}", height);
|
||||||
};
|
};
|
||||||
let wallet = Wallet::new(DB_NAME);
|
let wallet = Wallet::new(DB_NAME, LWD_URL);
|
||||||
wallet.new_account_with_key("test", &seed).unwrap();
|
wallet.new_account_with_key("test", &seed).unwrap();
|
||||||
let res = wallet.sync(progress).await;
|
let res = wallet.sync(progress).await;
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
|
@ -34,7 +34,7 @@ async fn test() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tx_id = wallet
|
let tx_id = wallet
|
||||||
.send_payment(1, &address, 50000, u64::max_value(), move |progress| { println!("{}", progress.cur()); })
|
.send_payment(1, &address, 50000, u64::max_value(), 2, move |progress| { println!("{}", progress.cur()); })
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("TXID = {}", tx_id);
|
println!("TXID = {}", tx_id);
|
||||||
|
|
|
@ -27,10 +27,11 @@ pub struct MemPool {
|
||||||
transactions: HashMap<Vec<u8>, MemPoolTransacton>,
|
transactions: HashMap<Vec<u8>, MemPoolTransacton>,
|
||||||
nfs: HashMap<Vec<u8>, u64>,
|
nfs: HashMap<Vec<u8>, u64>,
|
||||||
balance: i64,
|
balance: i64,
|
||||||
|
ld_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemPool {
|
impl MemPool {
|
||||||
pub fn new(db_path: &str) -> MemPool {
|
pub fn new(db_path: &str, ld_url: &str) -> MemPool {
|
||||||
MemPool {
|
MemPool {
|
||||||
db_path: db_path.to_string(),
|
db_path: db_path.to_string(),
|
||||||
account: 0,
|
account: 0,
|
||||||
|
@ -39,6 +40,7 @@ impl MemPool {
|
||||||
transactions: HashMap::new(),
|
transactions: HashMap::new(),
|
||||||
nfs: HashMap::new(),
|
nfs: HashMap::new(),
|
||||||
balance: 0,
|
balance: 0,
|
||||||
|
ld_url: ld_url.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ impl MemPool {
|
||||||
pub async fn scan(&mut self) -> anyhow::Result<i64> {
|
pub async fn scan(&mut self) -> anyhow::Result<i64> {
|
||||||
if self.ivk.is_some() {
|
if self.ivk.is_some() {
|
||||||
let ivk = self.ivk.as_ref().unwrap().clone();
|
let ivk = self.ivk.as_ref().unwrap().clone();
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(&self.ld_url).await?;
|
||||||
let height = BlockHeight::from(get_latest_height(&mut client).await?);
|
let height = BlockHeight::from(get_latest_height(&mut client).await?);
|
||||||
if self.height != height {
|
if self.height != height {
|
||||||
// New blocks invalidate the mempool
|
// New blocks invalidate the mempool
|
||||||
|
@ -154,14 +156,14 @@ impl MemPool {
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::db::DEFAULT_DB_PATH;
|
use crate::db::DEFAULT_DB_PATH;
|
||||||
use crate::mempool::MemPool;
|
use crate::mempool::MemPool;
|
||||||
use crate::DbAdapter;
|
use crate::{DbAdapter, LWD_URL};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_mempool() {
|
async fn test_mempool() {
|
||||||
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||||
let ivk = db.get_ivk(1).unwrap();
|
let ivk = db.get_ivk(1).unwrap();
|
||||||
let mut mempool = MemPool::new("zec.db");
|
let mut mempool = MemPool::new("zec.db", LWD_URL);
|
||||||
mempool.set_ivk(&ivk);
|
mempool.set_ivk(&ivk);
|
||||||
loop {
|
loop {
|
||||||
mempool.scan().await.unwrap();
|
mempool.scan().await.unwrap();
|
||||||
|
|
10
src/scan.rs
10
src/scan.rs
|
@ -75,10 +75,12 @@ pub async fn sync_async(
|
||||||
db_path: &str,
|
db_path: &str,
|
||||||
target_height_offset: u32,
|
target_height_offset: u32,
|
||||||
progress_callback: ProgressCallback,
|
progress_callback: ProgressCallback,
|
||||||
|
ld_url: &str
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
let ld_url = ld_url.to_owned();
|
||||||
let db_path = db_path.to_string();
|
let db_path = db_path.to_string();
|
||||||
|
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(&ld_url).await?;
|
||||||
let (start_height, mut prev_hash, fvks) = {
|
let (start_height, mut prev_hash, fvks) = {
|
||||||
let db = DbAdapter::new(&db_path)?;
|
let db = DbAdapter::new(&db_path)?;
|
||||||
let height = db.get_db_height()?;
|
let height = db.get_db_height()?;
|
||||||
|
@ -102,7 +104,7 @@ pub async fn sync_async(
|
||||||
let (processor_tx, mut processor_rx) = mpsc::channel::<Blocks>(1);
|
let (processor_tx, mut processor_rx) = mpsc::channel::<Blocks>(1);
|
||||||
|
|
||||||
let downloader = tokio::spawn(async move {
|
let downloader = tokio::spawn(async move {
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(&ld_url).await?;
|
||||||
while let Some(range) = download_rx.recv().await {
|
while let Some(range) = download_rx.recv().await {
|
||||||
log::info!("+ {:?}", range);
|
log::info!("+ {:?}", range);
|
||||||
let blocks = download_chain(&mut client, range.start, range.end, prev_hash).await?;
|
let blocks = download_chain(&mut client, range.start, range.end, prev_hash).await?;
|
||||||
|
@ -302,8 +304,8 @@ pub async fn sync_async(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn latest_height() -> anyhow::Result<u32> {
|
pub async fn latest_height(ld_url: &str) -> anyhow::Result<u32> {
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(ld_url).await?;
|
||||||
let height = get_latest_height(&mut client).await?;
|
let height = get_latest_height(&mut client).await?;
|
||||||
Ok(height)
|
Ok(height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
use zcash_primitives::transaction::builder::Builder;
|
||||||
|
use crate::{CompactTxStreamerClient, AddressList, DbAdapter, NETWORK, connect_lightwalletd, get_latest_height, GetAddressUtxosArg};
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
use tonic::Request;
|
||||||
|
use zcash_primitives::consensus::{BlockHeight, Parameters, BranchId};
|
||||||
|
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;
|
||||||
|
use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use anyhow::Context;
|
||||||
|
use zcash_primitives::legacy::{Script, TransparentAddress};
|
||||||
|
use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_payment_address, encode_transparent_address};
|
||||||
|
use crate::chain::send_transaction;
|
||||||
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
use ripemd160::{Ripemd160, Digest};
|
||||||
|
use sha2::Sha256;
|
||||||
|
use secp256k1::{SecretKey, PublicKey, Secp256k1, All};
|
||||||
|
use tiny_hderive::bip32::ExtendedPrivKey;
|
||||||
|
use bip39::{Mnemonic, Language, Seed};
|
||||||
|
|
||||||
|
pub const BIP44_PATH: &str = "m/44'/133'/0'/0/0";
|
||||||
|
|
||||||
|
pub async fn get_taddr_balance(client: &mut CompactTxStreamerClient<Channel>, address: &str) -> anyhow::Result<u64> {
|
||||||
|
let req = AddressList {
|
||||||
|
addresses: vec![address.to_string()],
|
||||||
|
};
|
||||||
|
let rep = client.get_taddress_balance(Request::new(req)).await?.into_inner();
|
||||||
|
Ok(rep.value_zat as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn shield_taddr(db: &DbAdapter, account: u32, prover: &LocalTxProver, ld_url: &str) -> anyhow::Result<String> {
|
||||||
|
let mut client = connect_lightwalletd(ld_url).await?;
|
||||||
|
let last_height = get_latest_height(&mut client).await?;
|
||||||
|
let ivk = db.get_ivk(account)?;
|
||||||
|
let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)?.unwrap();
|
||||||
|
let z_address = db.get_address(account)?;
|
||||||
|
let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &z_address)?.unwrap();
|
||||||
|
let t_address = db.get_taddr(account)?;
|
||||||
|
if t_address.is_none() { anyhow::bail!("No transparent address"); }
|
||||||
|
let t_address = t_address.unwrap();
|
||||||
|
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
|
||||||
|
let amount = Amount::from_u64(get_taddr_balance(&mut client, &t_address).await?).unwrap();
|
||||||
|
if amount <= DEFAULT_FEE { anyhow::bail!("Not enough balance"); }
|
||||||
|
let amount = amount - DEFAULT_FEE;
|
||||||
|
|
||||||
|
let sk = db.get_tsk(account)?;
|
||||||
|
let seckey =
|
||||||
|
secp256k1::SecretKey::from_str(&sk).context("Cannot parse secret key")?;
|
||||||
|
|
||||||
|
let req = GetAddressUtxosArg {
|
||||||
|
addresses: vec![t_address.to_string()],
|
||||||
|
start_height: 0,
|
||||||
|
max_entries: 0,
|
||||||
|
};
|
||||||
|
let utxo_rep = client.get_address_utxos(Request::new(req)).await?.into_inner();
|
||||||
|
|
||||||
|
for utxo in utxo_rep.address_utxos.iter() {
|
||||||
|
let mut tx_hash = [0u8; 32];
|
||||||
|
tx_hash.copy_from_slice(&utxo.txid);
|
||||||
|
let op = OutPoint::new(tx_hash, utxo.index as u32);
|
||||||
|
let script = Script(utxo.script.clone());
|
||||||
|
let txout = TxOut {
|
||||||
|
value: Amount::from_i64(utxo.value_zat).unwrap(),
|
||||||
|
script_pubkey: script,
|
||||||
|
};
|
||||||
|
builder.add_transparent_input(seckey, op, txout)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ovk = fvk.fvk.ovk;
|
||||||
|
builder.add_sapling_output(Some(ovk), pa, amount, None)?;
|
||||||
|
let consensus_branch_id =
|
||||||
|
BranchId::for_height(&NETWORK, BlockHeight::from_u32(last_height));
|
||||||
|
let (tx, _) = builder.build(consensus_branch_id, prover)?;
|
||||||
|
let mut raw_tx: Vec<u8> = vec![];
|
||||||
|
tx.write(&mut raw_tx)?;
|
||||||
|
|
||||||
|
let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?;
|
||||||
|
log::info!("Tx ID = {}", tx_id);
|
||||||
|
|
||||||
|
Ok(tx_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn derive_tkeys(phrase: &str, path: &str) -> anyhow::Result<(String, String)> {
|
||||||
|
let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?;
|
||||||
|
let seed = Seed::new(&mnemonic, "");
|
||||||
|
let secp = Secp256k1::<All>::new();
|
||||||
|
let ext = ExtendedPrivKey::derive(&seed.as_bytes(), path).unwrap();
|
||||||
|
let secret_key = SecretKey::from_slice(&ext.secret()).unwrap();
|
||||||
|
let pub_key = PublicKey::from_secret_key(&secp, &secret_key);
|
||||||
|
let pub_key = pub_key.serialize();
|
||||||
|
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
|
||||||
|
let address = TransparentAddress::PublicKey(pub_key.into());
|
||||||
|
let address = encode_transparent_address(&NETWORK.b58_pubkey_address_prefix(), &NETWORK.b58_script_address_prefix(), &address);
|
||||||
|
let sk = secret_key.to_string();
|
||||||
|
Ok((sk, address))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{DbAdapter, LWD_URL};
|
||||||
|
use crate::db::DEFAULT_DB_PATH;
|
||||||
|
use crate::taddr::{shield_taddr, derive_tkeys};
|
||||||
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_shield_addr() {
|
||||||
|
let prover = LocalTxProver::with_default_location().unwrap();
|
||||||
|
let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap();
|
||||||
|
let txid = shield_taddr(&db, 1, &prover, LWD_URL).await.unwrap();
|
||||||
|
println!("{}", txid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_derive() {
|
||||||
|
let seed = dotenv::var("SEED").unwrap();
|
||||||
|
for i in 0..10 {
|
||||||
|
let (_sk, addr) = derive_tkeys(&seed, &format!("m/44'/133'/0'/0/{}", i)).unwrap();
|
||||||
|
println!("{}", addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ use std::sync::{mpsc, Arc};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
use zcash_client_backend::address::RecipientAddress;
|
use zcash_client_backend::address::RecipientAddress;
|
||||||
use zcash_client_backend::data_api::wallet::ANCHOR_OFFSET;
|
|
||||||
use zcash_client_backend::encoding::{decode_extended_spending_key, decode_extended_full_viewing_key, encode_payment_address};
|
use zcash_client_backend::encoding::{decode_extended_spending_key, decode_extended_full_viewing_key, encode_payment_address};
|
||||||
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
|
use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS};
|
||||||
use zcash_primitives::consensus::{BlockHeight, BranchId, Parameters};
|
use zcash_primitives::consensus::{BlockHeight, BranchId, Parameters};
|
||||||
|
@ -20,6 +19,7 @@ use zcash_primitives::transaction::components::amount::{DEFAULT_FEE, MAX_MONEY};
|
||||||
use zcash_primitives::transaction::components::Amount;
|
use zcash_primitives::transaction::components::Amount;
|
||||||
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
use crate::taddr::{get_taddr_balance, shield_taddr};
|
||||||
|
|
||||||
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
|
const DEFAULT_CHUNK_SIZE: u32 = 100_000;
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ pub struct Wallet {
|
||||||
pub db_path: String,
|
pub db_path: String,
|
||||||
db: DbAdapter,
|
db: DbAdapter,
|
||||||
prover: LocalTxProver,
|
prover: LocalTxProver,
|
||||||
|
pub ld_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
@ -47,7 +48,7 @@ impl Default for WalletBalance {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wallet {
|
impl Wallet {
|
||||||
pub fn new(db_path: &str) -> Wallet {
|
pub fn new(db_path: &str, ld_url: &str) -> Wallet {
|
||||||
let prover = LocalTxProver::from_bytes(SPEND_PARAMS, OUTPUT_PARAMS);
|
let prover = LocalTxProver::from_bytes(SPEND_PARAMS, OUTPUT_PARAMS);
|
||||||
let db = DbAdapter::new(db_path).unwrap();
|
let db = DbAdapter::new(db_path).unwrap();
|
||||||
db.init_db().unwrap();
|
db.init_db().unwrap();
|
||||||
|
@ -55,6 +56,7 @@ impl Wallet {
|
||||||
db_path: db_path.to_string(),
|
db_path: db_path.to_string(),
|
||||||
db,
|
db,
|
||||||
prover,
|
prover,
|
||||||
|
ld_url: ld_url.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +97,7 @@ impl Wallet {
|
||||||
let account = self
|
let account = self
|
||||||
.db
|
.db
|
||||||
.store_account(name, seed.as_deref(), sk.as_deref(), &ivk, &pa)?;
|
.store_account(name, seed.as_deref(), sk.as_deref(), &ivk, &pa)?;
|
||||||
|
self.db.create_taddr(account)?;
|
||||||
Ok(account)
|
Ok(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,12 +106,13 @@ impl Wallet {
|
||||||
chunk_size: u32,
|
chunk_size: u32,
|
||||||
target_height_offset: u32,
|
target_height_offset: u32,
|
||||||
progress_callback: ProgressCallback,
|
progress_callback: ProgressCallback,
|
||||||
|
ld_url: &str
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
crate::scan::sync_async(chunk_size, db_path, target_height_offset, progress_callback).await
|
crate::scan::sync_async(chunk_size, db_path, target_height_offset, progress_callback, ld_url).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_latest_height() -> anyhow::Result<u32> {
|
pub async fn get_latest_height(&self) -> anyhow::Result<u32> {
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(&self.ld_url).await?;
|
||||||
let last_height = get_latest_height(&mut client).await?;
|
let last_height = get_latest_height(&mut client).await?;
|
||||||
Ok(last_height)
|
Ok(last_height)
|
||||||
}
|
}
|
||||||
|
@ -117,10 +121,11 @@ impl Wallet {
|
||||||
pub async fn sync_ex(
|
pub async fn sync_ex(
|
||||||
db_path: &str,
|
db_path: &str,
|
||||||
progress_callback: impl Fn(u32) + Send + 'static,
|
progress_callback: impl Fn(u32) + Send + 'static,
|
||||||
|
ld_url: &str
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let cb = Arc::new(Mutex::new(progress_callback));
|
let cb = Arc::new(Mutex::new(progress_callback));
|
||||||
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 10, cb.clone()).await?;
|
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 10, cb.clone(), ld_url).await?;
|
||||||
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone()).await?;
|
Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,11 +133,11 @@ impl Wallet {
|
||||||
&self,
|
&self,
|
||||||
progress_callback: impl Fn(u32) + Send + 'static,
|
progress_callback: impl Fn(u32) + Send + 'static,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
Self::sync_ex(&self.db_path, progress_callback).await
|
Self::sync_ex(&self.db_path, progress_callback, &self.ld_url).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn skip_to_last_height(&self) -> anyhow::Result<()> {
|
pub async fn skip_to_last_height(&self) -> anyhow::Result<()> {
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(&self.ld_url).await?;
|
||||||
let last_height = get_latest_height(&mut client).await?;
|
let last_height = get_latest_height(&mut client).await?;
|
||||||
let block_id = BlockId {
|
let block_id = BlockId {
|
||||||
height: last_height as u64,
|
height: last_height as u64,
|
||||||
|
@ -160,6 +165,7 @@ impl Wallet {
|
||||||
to_address: &str,
|
to_address: &str,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
max_amount_per_note: u64,
|
max_amount_per_note: u64,
|
||||||
|
anchor_offset: u32,
|
||||||
progress_callback: impl Fn(Progress) + Send + 'static,
|
progress_callback: impl Fn(Progress) + Send + 'static,
|
||||||
) -> anyhow::Result<String> {
|
) -> anyhow::Result<String> {
|
||||||
let secret_key = self.db.get_sk(account)?;
|
let secret_key = self.db.get_sk(account)?;
|
||||||
|
@ -172,13 +178,13 @@ impl Wallet {
|
||||||
let extfvk = ExtendedFullViewingKey::from(&skey);
|
let extfvk = ExtendedFullViewingKey::from(&skey);
|
||||||
let (_, change_address) = extfvk.default_address().unwrap();
|
let (_, change_address) = extfvk.default_address().unwrap();
|
||||||
let ovk = extfvk.fvk.ovk;
|
let ovk = extfvk.fvk.ovk;
|
||||||
let last_height = Self::get_latest_height().await?;
|
let last_height = self.get_latest_height().await?;
|
||||||
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
|
let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height));
|
||||||
let anchor_height = self
|
let anchor_height = self
|
||||||
.db
|
.db
|
||||||
.get_last_sync_height()?
|
.get_last_sync_height()?
|
||||||
.ok_or_else(|| anyhow::anyhow!("No spendable notes"))?;
|
.ok_or_else(|| anyhow::anyhow!("No spendable notes"))?;
|
||||||
let anchor_height = anchor_height.min(last_height - ANCHOR_OFFSET);
|
let anchor_height = anchor_height.min(last_height - anchor_offset);
|
||||||
log::info!("Anchor = {}", anchor_height);
|
log::info!("Anchor = {}", anchor_height);
|
||||||
let mut notes = self
|
let mut notes = self
|
||||||
.db
|
.db
|
||||||
|
@ -252,7 +258,7 @@ impl Wallet {
|
||||||
let mut raw_tx: Vec<u8> = vec![];
|
let mut raw_tx: Vec<u8> = vec![];
|
||||||
tx.write(&mut raw_tx)?;
|
tx.write(&mut raw_tx)?;
|
||||||
|
|
||||||
let mut client = connect_lightwalletd().await?;
|
let mut client = connect_lightwalletd(&self.ld_url).await?;
|
||||||
let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?;
|
let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?;
|
||||||
log::info!("Tx ID = {}", tx_id);
|
log::info!("Tx ID = {}", tx_id);
|
||||||
|
|
||||||
|
@ -276,6 +282,25 @@ impl Wallet {
|
||||||
let pa = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
|
let pa = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa);
|
||||||
Ok(pa)
|
Ok(pa)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_taddr_balance(&self, account: u32) -> anyhow::Result<u64> {
|
||||||
|
let mut client = connect_lightwalletd(&self.ld_url).await?;
|
||||||
|
let address = self.db.get_taddr(account)?;
|
||||||
|
let balance = match address {
|
||||||
|
None => 0u64,
|
||||||
|
Some(address) => get_taddr_balance(&mut client, &address).await?,
|
||||||
|
};
|
||||||
|
Ok(balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn shield_taddr(&self, account: u32) -> anyhow::Result<String> {
|
||||||
|
shield_taddr(&self.db, account, &self.prover, &self.ld_url).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lwd_url(&mut self, ld_url: &str) -> anyhow::Result<()> {
|
||||||
|
self.ld_url = ld_url.to_string();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -283,6 +308,7 @@ mod tests {
|
||||||
use crate::key::derive_secret_key;
|
use crate::key::derive_secret_key;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
use bip39::{Language, Mnemonic};
|
use bip39::{Language, Mnemonic};
|
||||||
|
use crate::LWD_URL;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_wallet_seed() {
|
async fn test_wallet_seed() {
|
||||||
|
@ -290,7 +316,7 @@ mod tests {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let seed = dotenv::var("SEED").unwrap();
|
let seed = dotenv::var("SEED").unwrap();
|
||||||
let wallet = Wallet::new("zec.db");
|
let wallet = Wallet::new("zec.db", LWD_URL);
|
||||||
wallet.new_account_with_key("test", &seed).unwrap();
|
wallet.new_account_with_key("test", &seed).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +337,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_diversified_address() {
|
pub fn test_diversified_address() {
|
||||||
let wallet = Wallet::new("zec.db");
|
let wallet = Wallet::new("zec.db", LWD_URL);
|
||||||
let address = wallet.new_diversified_address(1).unwrap();
|
let address = wallet.new_diversified_address(1).unwrap();
|
||||||
println!("{}", address);
|
println!("{}", address);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue