From c99794214febff196030d459ab84f1e16c0f112b Mon Sep 17 00:00:00 2001 From: Hanh Date: Mon, 16 Aug 2021 21:07:04 +0800 Subject: [PATCH] price API --- src/db.rs | 29 ++++++-- src/db/migration.rs | 13 ++-- src/lib.rs | 18 ++--- src/main/sign.rs | 5 +- src/main/warp_cli.rs | 8 +-- src/pay.rs | 153 ++++++++++++++++++++++++++++--------------- src/prices.rs | 111 ++++++++++++++++++++----------- src/scan.rs | 11 ++-- src/taddr.rs | 5 +- src/transaction.rs | 40 ++++++++--- src/wallet.rs | 100 ++++++++++++++++++++-------- 11 files changed, 332 insertions(+), 161 deletions(-) diff --git a/src/db.rs b/src/db.rs index 9adec2d..326a214 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,7 +1,9 @@ use crate::chain::{Nf, NfRef}; +use crate::prices::Quote; use crate::taddr::{derive_tkeys, BIP44_PATH}; use crate::transaction::{Contact, TransactionInfo}; use crate::{CTree, Witness, NETWORK}; +use chrono::NaiveDateTime; use rusqlite::{params, Connection, OptionalExtension, NO_PARAMS}; use std::collections::HashMap; use zcash_client_backend::encoding::decode_extended_full_viewing_key; @@ -9,7 +11,6 @@ use zcash_primitives::consensus::{NetworkUpgrade, Parameters}; use zcash_primitives::merkle_tree::IncrementalWitness; use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed, SaplingIvk}; use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey}; -use chrono::NaiveDateTime; mod migration; @@ -660,17 +661,35 @@ impl DbAdapter { Ok(timestamps) } - pub fn store_historical_prices(&mut self, prices: Vec<(i64, f64)>, currency: &str) -> anyhow::Result<()> { + pub fn store_historical_prices( + &mut self, + prices: &[Quote], + currency: &str, + ) -> anyhow::Result<()> { let db_transaction = self.connection.transaction()?; { - let mut statement = db_transaction.prepare("INSERT INTO historical_prices(timestamp, price, currency) VALUES (?1, ?2, ?3)")?; - for (ts, px) in prices { - statement.execute(params![ts, px, currency])?; + let mut statement = db_transaction.prepare( + "INSERT INTO historical_prices(timestamp, price, currency) VALUES (?1, ?2, ?3)", + )?; + for q in prices { + statement.execute(params![q.timestamp, q.price, currency])?; } } db_transaction.commit()?; Ok(()) } + + pub fn get_latest_quote(&self, currency: &str) -> anyhow::Result> { + let quote = self.connection.query_row( + "SELECT timestamp, price FROM historical_prices WHERE currency = ?1 ORDER BY timestamp DESC", + params![currency], + |row| { + let timestamp: i64 = row.get(0)?; + let price: f64 = row.get(1)?; + Ok(Quote { timestamp, price }) + }).optional()?; + Ok(quote) + } } #[cfg(test)] diff --git a/src/db/migration.rs b/src/db/migration.rs index 6114fac..d2c9468 100644 --- a/src/db/migration.rs +++ b/src/db/migration.rs @@ -110,8 +110,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> { } if version < 2 { - connection - .execute("ALTER TABLE received_notes ADD excluded BOOL", NO_PARAMS)?; + connection.execute("ALTER TABLE received_notes ADD excluded BOOL", NO_PARAMS)?; } if version < 3 { @@ -136,8 +135,14 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> { )?; } - update_schema_version(&connection, 4)?; + if version < 5 { + connection.execute( + "DELETE FROM historical_prices", + NO_PARAMS, + )?; + } + + update_schema_version(&connection, 5)?; Ok(()) } - diff --git a/src/lib.rs b/src/lib.rs index f4eb950..55f7512 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ #[path = "generated/cash.z.wallet.sdk.rpc.rs"] pub mod lw_rpc; -#[cfg(feature="ycash")] +#[cfg(feature = "ycash")] mod coin { - use zcash_primitives::consensus::{Network, BranchId}; + use zcash_primitives::consensus::{BranchId, Network}; pub const NETWORK: Network = Network::YCashMainNetwork; pub const TICKER: &str = "ycash"; @@ -12,9 +12,9 @@ mod coin { } } -#[cfg(not(feature="ycash"))] +#[cfg(not(feature = "ycash"))] mod coin { - use zcash_primitives::consensus::{Network, BranchId, BlockHeight}; + use zcash_primitives::consensus::{BlockHeight, BranchId, Network}; pub const NETWORK: Network = Network::MainNetwork; pub const TICKER: &str = "zcash"; @@ -23,7 +23,7 @@ mod coin { } } -pub use coin::{NETWORK, TICKER, get_branch}; +pub use coin::{get_branch, NETWORK, TICKER}; // Mainnet // pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067"; @@ -43,13 +43,13 @@ mod db; mod hash; mod key; mod mempool; +mod pay; +mod prices; mod print; mod scan; mod taddr; mod transaction; -mod pay; mod wallet; -mod prices; pub use crate::builder::advance_tree; pub use crate::chain::{ @@ -59,11 +59,11 @@ pub use crate::chain::{ pub use crate::commitment::{CTree, Witness}; pub use crate::db::DbAdapter; pub use crate::hash::pedersen_hash; -pub use crate::key::{is_valid_key, decode_key}; +pub use crate::key::{decode_key, is_valid_key}; pub use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient; pub use crate::lw_rpc::*; pub use crate::mempool::MemPool; +pub use crate::pay::{broadcast_tx, sign_offline_tx, Tx}; pub use crate::print::*; pub use crate::scan::{latest_height, scan_all, sync_async}; pub use crate::wallet::{Wallet, WalletBalance}; -pub use crate::pay::{sign_offline_tx, broadcast_tx, Tx}; diff --git a/src/main/sign.rs b/src/main/sign.rs index d9bf9f9..3a30b0d 100644 --- a/src/main/sign.rs +++ b/src/main/sign.rs @@ -1,7 +1,7 @@ use clap::Clap; -use sync::{decode_key, Tx, sign_offline_tx, NETWORK}; use std::fs::File; use std::io::{Read, Write}; +use sync::{decode_key, sign_offline_tx, Tx, NETWORK}; use zcash_client_backend::encoding::decode_extended_spending_key; use zcash_primitives::consensus::Parameters; @@ -17,7 +17,8 @@ fn main() -> anyhow::Result<()> { let opts: SignArgs = SignArgs::parse(); let sk = sk.unwrap(); - let sk = decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &sk)?.unwrap(); + let sk = + decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &sk)?.unwrap(); let file_name = opts.tx_filename; let mut file = File::open(file_name)?; diff --git a/src/main/warp_cli.rs b/src/main/warp_cli.rs index 85f2bd5..2af2f3e 100644 --- a/src/main/warp_cli.rs +++ b/src/main/warp_cli.rs @@ -57,13 +57,7 @@ async fn test() -> anyhow::Result<()> { // println!("TXID = {}", tx_id); let tx = wallet - .prepare_payment( - 1, - &address, - 50000, - "test memo", - 0, - 2) + .prepare_payment(1, &address, 50000, "test memo", 0, 2) .await .unwrap(); println!("TX = {}", tx); diff --git a/src/pay.rs b/src/pay.rs index dda4473..f9c82ee 100644 --- a/src/pay.rs +++ b/src/pay.rs @@ -1,22 +1,24 @@ -use crate::{NETWORK, connect_lightwalletd, RawTransaction, get_latest_height}; -use zcash_primitives::zip32::{ExtendedSpendingKey, ExtendedFullViewingKey}; -use zcash_primitives::sapling::{Diversifier, Rseed, Node}; -use zcash_primitives::transaction::components::Amount; -use zcash_primitives::sapling::keys::OutgoingViewingKey; -use zcash_primitives::memo::MemoBytes; -use zcash_client_backend::encoding::{encode_extended_full_viewing_key, decode_extended_full_viewing_key}; -use zcash_primitives::consensus::{Parameters, Network, BlockHeight, BranchId}; -use zcash_primitives::transaction::builder::Builder; -use rand::rngs::OsRng; -use jubjub::Fr; -use zcash_primitives::merkle_tree::IncrementalWitness; -use zcash_client_backend::address::RecipientAddress; use crate::db::SpendableNote; use crate::wallet::Recipient; -use zcash_primitives::transaction::components::amount::DEFAULT_FEE; -use serde::{Serialize, Deserialize}; -use zcash_proofs::prover::LocalTxProver; +use crate::{connect_lightwalletd, get_latest_height, RawTransaction, NETWORK}; +use jubjub::Fr; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; use tonic::Request; +use zcash_client_backend::address::RecipientAddress; +use zcash_client_backend::encoding::{ + decode_extended_full_viewing_key, encode_extended_full_viewing_key, +}; +use zcash_primitives::consensus::{BlockHeight, BranchId, Network, Parameters}; +use zcash_primitives::memo::MemoBytes; +use zcash_primitives::merkle_tree::IncrementalWitness; +use zcash_primitives::sapling::keys::OutgoingViewingKey; +use zcash_primitives::sapling::{Diversifier, Node, Rseed}; +use zcash_primitives::transaction::builder::Builder; +use zcash_primitives::transaction::components::amount::DEFAULT_FEE; +use zcash_primitives::transaction::components::Amount; +use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; +use zcash_proofs::prover::LocalTxProver; #[derive(Serialize, Deserialize, Debug)] pub struct Tx { @@ -60,10 +62,16 @@ pub trait TxBuilder { fvk: &ExtendedFullViewingKey, amount: Amount, rseed: &[u8], - witness: &[u8] + witness: &[u8], ) -> anyhow::Result<()>; fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()>; - fn add_z_output(&mut self, address: &str, ovk: &OutgoingViewingKey, amount: Amount, memo: &MemoBytes) -> anyhow::Result<()>; + fn add_z_output( + &mut self, + address: &str, + ovk: &OutgoingViewingKey, + amount: Amount, + memo: &MemoBytes, + ) -> anyhow::Result<()>; } pub struct ColdTxBuilder { @@ -79,10 +87,21 @@ impl ColdTxBuilder { } impl TxBuilder for ColdTxBuilder { - fn add_input(&mut self, _skey: Option, diversifier: &Diversifier, fvk: &ExtendedFullViewingKey, amount: Amount, rseed: &[u8], witness: &[u8]) -> anyhow::Result<()> { + fn add_input( + &mut self, + _skey: Option, + diversifier: &Diversifier, + fvk: &ExtendedFullViewingKey, + amount: Amount, + rseed: &[u8], + witness: &[u8], + ) -> anyhow::Result<()> { let tx_in = TxIn { diversifier: hex::encode(diversifier.0), - fvk: encode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk), + fvk: encode_extended_full_viewing_key( + NETWORK.hrp_sapling_extended_full_viewing_key(), + &fvk, + ), amount: u64::from(amount), rseed: hex::encode(rseed), witness: hex::encode(witness), @@ -102,7 +121,13 @@ impl TxBuilder for ColdTxBuilder { Ok(()) } - fn add_z_output(&mut self, address: &str, ovk: &OutgoingViewingKey, amount: Amount, memo: &MemoBytes) -> anyhow::Result<()> { + fn add_z_output( + &mut self, + address: &str, + ovk: &OutgoingViewingKey, + amount: Amount, + memo: &MemoBytes, + ) -> anyhow::Result<()> { let tx_out = TxOut { addr: address.to_string(), amount: u64::from(amount), @@ -115,12 +140,22 @@ impl TxBuilder for ColdTxBuilder { } impl TxBuilder for Builder<'_, Network, OsRng> { - fn add_input(&mut self, skey: Option, diversifier: &Diversifier, fvk: &ExtendedFullViewingKey, amount: Amount, rseed: &[u8], witness: &[u8]) -> anyhow::Result<()> { + fn add_input( + &mut self, + skey: Option, + diversifier: &Diversifier, + fvk: &ExtendedFullViewingKey, + amount: Amount, + rseed: &[u8], + witness: &[u8], + ) -> anyhow::Result<()> { let pa = fvk.fvk.vk.to_payment_address(diversifier.clone()).unwrap(); let mut rseed_bytes = [0u8; 32]; rseed_bytes.copy_from_slice(rseed); let fr = Fr::from_bytes(&rseed_bytes).unwrap(); - let note = pa.create_note(u64::from(amount), Rseed::BeforeZip212(fr)).unwrap(); + let note = pa + .create_note(u64::from(amount), Rseed::BeforeZip212(fr)) + .unwrap(); let witness = IncrementalWitness::::read(&*witness).unwrap(); let merkle_path = witness.path().unwrap(); self.add_sapling_spend(skey.unwrap(), diversifier.clone(), note, merkle_path)?; @@ -128,28 +163,38 @@ impl TxBuilder for Builder<'_, Network, OsRng> { } fn add_t_output(&mut self, address: &str, amount: Amount) -> anyhow::Result<()> { - let to_addr = RecipientAddress::decode(&NETWORK, address).ok_or(anyhow::anyhow!("Not a valid address"))?; + let to_addr = RecipientAddress::decode(&NETWORK, address) + .ok_or(anyhow::anyhow!("Not a valid address"))?; if let RecipientAddress::Transparent(t_address) = to_addr { self.add_transparent_output(&t_address, amount)?; } Ok(()) } - fn add_z_output(&mut self, address: &str, ovk: &OutgoingViewingKey, amount: Amount, memo: &MemoBytes) -> anyhow::Result<()> { - let to_addr = RecipientAddress::decode(&NETWORK, address).ok_or(anyhow::anyhow!("Not a valid address"))?; + fn add_z_output( + &mut self, + address: &str, + ovk: &OutgoingViewingKey, + amount: Amount, + memo: &MemoBytes, + ) -> anyhow::Result<()> { + let to_addr = RecipientAddress::decode(&NETWORK, address) + .ok_or(anyhow::anyhow!("Not a valid address"))?; if let RecipientAddress::Shielded(pa) = to_addr { - self.add_sapling_output( - Some(ovk.clone()), - pa.clone(), - amount, - Some(memo.clone()), - )?; + self.add_sapling_output(Some(ovk.clone()), pa.clone(), amount, Some(memo.clone()))?; } Ok(()) } } -pub fn prepare_tx(builder: &mut B, skey: Option, notes: &[SpendableNote], target_amount: Amount, fvk: &ExtendedFullViewingKey, recipients: &[Recipient]) -> anyhow::Result> { +pub fn prepare_tx( + builder: &mut B, + skey: Option, + notes: &[SpendableNote], + target_amount: Amount, + fvk: &ExtendedFullViewingKey, + recipients: &[Recipient], +) -> anyhow::Result> { let mut amount = target_amount; amount += DEFAULT_FEE; let target_amount_with_fee = amount; @@ -157,13 +202,13 @@ pub fn prepare_tx(builder: &mut B, skey: Option = vec![]; n.witness.write(&mut witness_bytes)?; - if let Rseed::BeforeZip212(rseed) = n.note.rseed { // rseed are stored as pre-zip212 + if let Rseed::BeforeZip212(rseed) = n.note.rseed { + // rseed are stored as pre-zip212 builder.add_input( skey.clone(), &n.diversifier, @@ -179,10 +224,10 @@ pub fn prepare_tx(builder: &mut B, skey: Option(builder: &mut B, skey: Option { - builder.add_t_output(&r.address, amount) + builder.add_z_output(&r.address, ovk, amount, &memo) } + RecipientAddress::Transparent(_address) => builder.add_t_output(&r.address, amount), }?; } @@ -220,12 +258,18 @@ pub fn sign_offline_tx(tx: &Tx, sk: &ExtendedSpendingKey) -> anyhow::Result::read(&*w)?; let merkle_path = witness.path().unwrap(); @@ -264,10 +308,13 @@ pub fn sign_offline_tx(tx: &Tx, sk: &ExtendedSpendingKey) -> anyhow::Result anyhow::Result { let mut client = connect_lightwalletd(ld_url).await?; let latest_height = get_latest_height(&mut client).await?; - let raw_tx = RawTransaction { + let raw_tx = RawTransaction { data: tx.to_vec(), height: latest_height as u64, }; - let rep = client.send_transaction(Request::new(raw_tx)).await?.into_inner(); + let rep = client + .send_transaction(Request::new(raw_tx)) + .await? + .into_inner(); Ok(rep.error_message) } diff --git a/src/prices.rs b/src/prices.rs index 38d7dd1..04fd876 100644 --- a/src/prices.rs +++ b/src/prices.rs @@ -1,53 +1,88 @@ +use crate::{DbAdapter, TICKER}; use chrono::NaiveDateTime; -use std::collections::HashMap; -use crate::TICKER; -const DAY_SEC: i64 = 24*3600; +const DAY_SEC: i64 = 24 * 3600; -pub async fn retrieve_historical_prices(timestamps: &[i64], currency: &str) -> anyhow::Result> { - if timestamps.is_empty() { return Ok(Vec::new()); } - let mut timestamps_map: HashMap> = HashMap::new(); - for ts in timestamps { - timestamps_map.insert(*ts, None); - } - let client = reqwest::Client::new(); - let start = timestamps.first().unwrap(); - let end = timestamps.last().unwrap() + DAY_SEC; - let url = format!("https://api.coingecko.com/api/v3/coins/{}/market_chart/range", TICKER); - let params = [("from", start.to_string()), ("to", end.to_string()), ("vs_currency", currency.to_string())]; - let req = client.get(url).query(¶ms); - let res = req.send().await?; - let r: serde_json::Value = res.json().await?; - let prices = r["prices"].as_array().unwrap(); - for p in prices.iter() { - let p = p.as_array().unwrap(); - let ts = p[0].as_i64().unwrap() / 1000; - let px = p[1].as_f64().unwrap(); - // rounded to daily - let date = NaiveDateTime::from_timestamp(ts, 0).date().and_hms(0, 0, 0); - let ts = date.timestamp(); - if let Some(None) = timestamps_map.get(&ts) { - timestamps_map.insert(ts, Some(px)); +#[derive(Debug)] +pub struct Quote { + pub timestamp: i64, + pub price: f64, +} + +pub async fn fetch_historical_prices( + now: i64, + days: u32, + currency: &str, + db: &DbAdapter, +) -> anyhow::Result> { + let today = now / DAY_SEC; + let from_day = today - days as i64; + let latest_quote = db.get_latest_quote(currency)?; + let latest_day = if let Some(latest_quote) = latest_quote { + latest_quote.timestamp / DAY_SEC + } else { + 0 + }; + let latest_day = latest_day.max(from_day); + + let mut quotes: Vec = vec![]; + if latest_day < today { + let from = (latest_day + 1) * DAY_SEC; + let to = today * DAY_SEC; + let client = reqwest::Client::new(); + let url = format!( + "https://api.coingecko.com/api/v3/coins/{}/market_chart/range", + TICKER + ); + let params = [ + ("from", from.to_string()), + ("to", to.to_string()), + ("vs_currency", currency.to_string()), + ]; + let req = client.get(url).query(¶ms); + let res = req.send().await?; + let r: serde_json::Value = res.json().await?; + let prices = r["prices"].as_array().unwrap(); + let mut prev_timestamp = 0i64; + for p in prices.iter() { + let p = p.as_array().unwrap(); + let ts = p[0].as_i64().unwrap() / 1000; + let price = p[1].as_f64().unwrap(); + // rounded to daily + let date = NaiveDateTime::from_timestamp(ts, 0).date().and_hms(0, 0, 0); + let timestamp = date.timestamp(); + if timestamp != prev_timestamp { + let quote = Quote { timestamp, price }; + quotes.push(quote); + } + prev_timestamp = timestamp; } } - let prices: Vec<_> = timestamps_map.iter().map(|(k, v)| { - (*k, v.expect(&format!("missing price for ts {}", *k))) - }).collect(); - Ok(prices) + + Ok(quotes) } #[cfg(test)] mod tests { - use crate::DbAdapter; use crate::db::DEFAULT_DB_PATH; - use crate::prices::retrieve_historical_prices; + use crate::prices::fetch_historical_prices; + use crate::DbAdapter; + use std::time::SystemTime; #[tokio::test] - async fn test() { + async fn test_fetch_quotes() { let currency = "EUR"; let mut db = DbAdapter::new(DEFAULT_DB_PATH).unwrap(); - let ts = db.get_missing_prices_timestamp("USD").unwrap(); - let prices = retrieve_historical_prices(&ts, currency).await.unwrap(); - db.store_historical_prices(prices, currency).unwrap(); + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as i64; + let quotes = fetch_historical_prices(now, 365, currency, &db) + .await + .unwrap(); + for q in quotes.iter() { + println!("{:?}", q); + } + db.store_historical_prices("es, currency).unwrap(); } -} \ No newline at end of file +} diff --git a/src/scan.rs b/src/scan.rs index a2657d8..fdd7a8c 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -184,10 +184,13 @@ pub async fn sync_async( b.compact_block.time, n.tx_index as u32, )?; - my_tx_ids.insert(TxIdHeight { - height: n.height, - index: n.tx_index as u32, - }, id_tx); + my_tx_ids.insert( + TxIdHeight { + height: n.height, + index: n.tx_index as u32, + }, + id_tx, + ); let id_note = db.store_received_note( &ReceivedNote { account: n.account, diff --git a/src/taddr.rs b/src/taddr.rs index b378b4e..4502fc7 100644 --- a/src/taddr.rs +++ b/src/taddr.rs @@ -1,5 +1,8 @@ use crate::chain::send_transaction; -use crate::{connect_lightwalletd, get_latest_height, AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, NETWORK, get_branch}; +use crate::{ + connect_lightwalletd, get_branch, get_latest_height, AddressList, CompactTxStreamerClient, + DbAdapter, GetAddressUtxosArg, NETWORK, +}; use anyhow::Context; use bip39::{Language, Mnemonic, Seed}; use ripemd160::{Digest, Ripemd160}; diff --git a/src/transaction.rs b/src/transaction.rs index ba8c523..da25952 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,6 +1,9 @@ use crate::{CompactTxStreamerClient, DbAdapter, TxFilter, NETWORK}; +use futures::StreamExt; use std::collections::HashMap; use std::convert::TryFrom; +use std::sync::mpsc; +use std::sync::mpsc::SyncSender; use tonic::transport::Channel; use tonic::Request; use zcash_client_backend::encoding::{ @@ -13,9 +16,6 @@ use zcash_primitives::sapling::note_encryption::{ }; use zcash_primitives::transaction::Transaction; use zcash_primitives::zip32::ExtendedFullViewingKey; -use futures::StreamExt; -use std::sync::mpsc; -use std::sync::mpsc::SyncSender; #[derive(Debug)] pub struct TransactionInfo { @@ -167,7 +167,18 @@ pub async fn retrieve_tx_info( } let res = tokio_stream::iter(decode_tx_params).for_each_concurrent(None, |mut p| async move { - if let Ok(tx_info) = decode_transaction(&mut p.client, p.nf_map, p.id_tx, p.account, &p.fvk, &p.tx_hash, p.height, p.index).await { + if let Ok(tx_info) = decode_transaction( + &mut p.client, + p.nf_map, + p.id_tx, + p.account, + &p.fvk, + &p.tx_hash, + p.height, + p.index, + ) + .await + { p.tx.send(tx_info).unwrap(); drop(p.tx); } @@ -177,7 +188,12 @@ pub async fn retrieve_tx_info( let mut contacts: Vec = vec![]; while let Ok(tx_info) = rx.recv() { if !tx_info.address.is_empty() && !tx_info.memo.is_empty() { - if let Some(contact) = decode_contact(tx_info.account, tx_info.index, &tx_info.address, &tx_info.memo)? { + if let Some(contact) = decode_contact( + tx_info.account, + tx_info.index, + &tx_info.address, + &tx_info.memo, + )? { contacts.push(contact); } } @@ -199,7 +215,12 @@ pub async fn retrieve_tx_info( Ok(()) } -fn decode_contact(account: u32, index: u32, address: &str, memo: &str) -> anyhow::Result> { +fn decode_contact( + account: u32, + index: u32, + address: &str, + memo: &str, +) -> anyhow::Result> { let res = if let Some(memo_line) = memo.lines().next() { let name = memo_line.strip_prefix("Contact:"); name.map(|name| Contact { @@ -242,9 +263,10 @@ mod tests { decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk) .unwrap() .unwrap(); - let tx_info = decode_transaction(&mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1) - .await - .unwrap(); + let tx_info = + decode_transaction(&mut client, &nf_map, 1, account, &fvk, &tx_hash, 1313212, 1) + .await + .unwrap(); println!("{:?}", tx_info); } } diff --git a/src/wallet.rs b/src/wallet.rs index af07b9d..d64cc15 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,12 +1,21 @@ use crate::chain::send_transaction; +use crate::db::SpendableNote; use crate::key::{decode_key, is_valid_key}; +use crate::pay::prepare_tx; +use crate::pay::{ColdTxBuilder, Tx}; +use crate::prices::fetch_historical_prices; use crate::scan::ProgressCallback; use crate::taddr::{get_taddr_balance, shield_taddr}; -use crate::{connect_lightwalletd, get_latest_height, BlockId, CTree, DbAdapter, NETWORK, get_branch}; +use crate::{ + connect_lightwalletd, get_branch, get_latest_height, BlockId, CTree, DbAdapter, NETWORK, +}; use bip39::{Language, Mnemonic}; use rand::prelude::SliceRandom; use rand::rngs::OsRng; use rand::RngCore; +use serde::Deserialize; +use std::convert::TryFrom; +use std::str::FromStr; use std::sync::{mpsc, Arc}; use tokio::sync::Mutex; use tonic::Request; @@ -16,19 +25,12 @@ use zcash_client_backend::encoding::{ }; use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS}; use zcash_primitives::consensus::{BlockHeight, Parameters}; +use zcash_primitives::memo::{Memo, MemoBytes}; use zcash_primitives::transaction::builder::{Builder, Progress}; use zcash_primitives::transaction::components::amount::MAX_MONEY; use zcash_primitives::transaction::components::Amount; use zcash_primitives::zip32::ExtendedFullViewingKey; use zcash_proofs::prover::LocalTxProver; -use serde::Deserialize; -use crate::pay::prepare_tx; -use zcash_primitives::memo::{Memo, MemoBytes}; -use std::convert::TryFrom; -use std::str::FromStr; -use crate::db::SpendableNote; -use crate::pay::{ColdTxBuilder, Tx}; -use crate::prices::retrieve_historical_prices; const DEFAULT_CHUNK_SIZE: u32 = 100_000; @@ -133,7 +135,7 @@ impl Wallet { progress_callback, ld_url, ) - .await + .await } pub async fn get_latest_height(&self) -> anyhow::Result { @@ -159,7 +161,7 @@ impl Wallet { cb.clone(), ld_url, ) - .await?; + .await?; Self::scan_async(get_tx, db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?; Ok(()) } @@ -177,7 +179,7 @@ impl Wallet { progress_callback, &self.ld_url, ) - .await + .await } pub async fn skip_to_last_height(&self) -> anyhow::Result<()> { @@ -203,10 +205,16 @@ impl Wallet { self.db.trim_to_height(height) } - pub async fn send_multi_payment(&self, account: u32, recipients_json: &str, anchor_offset: u32, - progress_callback: impl Fn(Progress) + Send + 'static) -> anyhow::Result { + pub async fn send_multi_payment( + &self, + account: u32, + recipients_json: &str, + anchor_offset: u32, + progress_callback: impl Fn(Progress) + Send + 'static, + ) -> anyhow::Result { let recipients: Vec = serde_json::from_str(recipients_json)?; - self._send_payment(account, &recipients, anchor_offset, progress_callback).await + self._send_payment(account, &recipients, anchor_offset, progress_callback) + .await } pub async fn prepare_payment( @@ -225,10 +233,21 @@ impl Wallet { Ok(tx_str) } - fn _prepare_payment(&self, account: u32, amount: u64, last_height: u32, recipients: &Vec, anchor_offset: u32) -> anyhow::Result { + fn _prepare_payment( + &self, + account: u32, + amount: u64, + last_height: u32, + recipients: &Vec, + anchor_offset: u32, + ) -> anyhow::Result { let amount = Amount::from_u64(amount).unwrap(); let ivk = self.db.get_ivk(account)?; - let extfvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)?.unwrap(); + let extfvk = decode_extended_full_viewing_key( + NETWORK.hrp_sapling_extended_full_viewing_key(), + &ivk, + )? + .unwrap(); let notes = self._get_spendable_notes(account, &extfvk, last_height, anchor_offset)?; let mut builder = ColdTxBuilder::new(last_height); prepare_tx(&mut builder, None, ¬es, amount, &extfvk, recipients)?; @@ -246,7 +265,8 @@ impl Wallet { progress_callback: impl Fn(Progress) + Send + 'static, ) -> anyhow::Result { let recipients = Self::_build_recipients(to_address, amount, max_amount_per_note, memo)?; - self._send_payment(account, &recipients, anchor_offset, progress_callback).await + self._send_payment(account, &recipients, anchor_offset, progress_callback) + .await } async fn _send_payment( @@ -268,7 +288,14 @@ impl Wallet { let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height)); log::info!("Preparing tx"); - let selected_notes = prepare_tx(&mut builder, Some(skey.clone()), ¬es, target_amount, &extfvk, recipients)?; + let selected_notes = prepare_tx( + &mut builder, + Some(skey.clone()), + ¬es, + target_amount, + &extfvk, + recipients, + )?; let (progress_tx, progress_rx) = mpsc::channel::(); @@ -306,7 +333,7 @@ impl Wallet { NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk, )? - .unwrap(); + .unwrap(); let mut diversifier_index = self.db.get_diversifier(account)?; diversifier_index.increment().unwrap(); let (new_diversifier_index, pa) = fvk @@ -336,7 +363,13 @@ impl Wallet { Ok(()) } - fn _get_spendable_notes(&self, account: u32, extfvk: &ExtendedFullViewingKey, last_height: u32, anchor_offset: u32) -> anyhow::Result> { + fn _get_spendable_notes( + &self, + account: u32, + extfvk: &ExtendedFullViewingKey, + last_height: u32, + anchor_offset: u32, + ) -> anyhow::Result> { let anchor_height = self .db .get_last_sync_height()? @@ -352,7 +385,12 @@ impl Wallet { Ok(notes) } - fn _build_recipients(to_address: &str, amount: u64, max_amount_per_note: u64, memo: &str) -> anyhow::Result> { + fn _build_recipients( + to_address: &str, + amount: u64, + max_amount_per_note: u64, + memo: &str, + ) -> anyhow::Result> { let mut recipients: Vec = vec![]; let target_amount = Amount::from_u64(amount).unwrap(); let max_amount_per_note = if max_amount_per_note != 0 { @@ -376,13 +414,17 @@ impl Wallet { Ok(recipients) } - pub async fn sync_historical_prices(&mut self, currency: &str) -> anyhow::Result { - let ts = self.db.get_missing_prices_timestamp(currency)?; - if !ts.is_empty() { - let prices = retrieve_historical_prices(&ts, currency).await?; - self.db.store_historical_prices(prices, currency)?; - } - Ok(ts.len() as u32) + pub async fn sync_historical_prices( + &mut self, + now: i64, + days: u32, + currency: &str, + ) -> anyhow::Result { + let quotes = fetch_historical_prices(now, days, currency, &self.db) + .await + .unwrap(); + self.db.store_historical_prices("es, currency).unwrap(); + Ok(quotes.len() as u32) } }