price API

This commit is contained in:
Hanh 2021-08-16 21:07:04 +08:00
parent a0104484e1
commit c99794214f
11 changed files with 332 additions and 161 deletions

View File

@ -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<Option<Quote>> {
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)]

View File

@ -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(())
}

View File

@ -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};

View File

@ -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)?;

View File

@ -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);

View File

@ -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<ExtendedSpendingKey>, diversifier: &Diversifier, fvk: &ExtendedFullViewingKey, amount: Amount, rseed: &[u8], witness: &[u8]) -> anyhow::Result<()> {
fn add_input(
&mut self,
_skey: Option<ExtendedSpendingKey>,
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<ExtendedSpendingKey>, diversifier: &Diversifier, fvk: &ExtendedFullViewingKey, amount: Amount, rseed: &[u8], witness: &[u8]) -> anyhow::Result<()> {
fn add_input(
&mut self,
skey: Option<ExtendedSpendingKey>,
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::<Node>::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<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKey>, notes: &[SpendableNote], target_amount: Amount, fvk: &ExtendedFullViewingKey, recipients: &[Recipient]) -> anyhow::Result<Vec<u32>> {
pub fn prepare_tx<B: TxBuilder>(
builder: &mut B,
skey: Option<ExtendedSpendingKey>,
notes: &[SpendableNote],
target_amount: Amount,
fvk: &ExtendedFullViewingKey,
recipients: &[Recipient],
) -> anyhow::Result<Vec<u32>> {
let mut amount = target_amount;
amount += DEFAULT_FEE;
let target_amount_with_fee = amount;
@ -157,13 +202,13 @@ pub fn prepare_tx<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKe
for n in notes.iter() {
if amount.is_positive() {
let a = amount.min(
Amount::from_u64(n.note.value)
.map_err(|_| anyhow::anyhow!("Invalid amount"))?,
Amount::from_u64(n.note.value).map_err(|_| anyhow::anyhow!("Invalid amount"))?,
);
amount -= a;
let mut witness_bytes: Vec<u8> = 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<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKe
if amount.is_positive() {
log::info!("Not enough balance");
anyhow::bail!(
"Not enough balance, need {} zats, missing {} zats",
u64::from(target_amount_with_fee),
u64::from(amount)
);
"Not enough balance, need {} zats, missing {} zats",
u64::from(target_amount_with_fee),
u64::from(amount)
);
}
log::info!("Preparing tx");
@ -197,16 +242,9 @@ pub fn prepare_tx<B: TxBuilder>(builder: &mut B, skey: Option<ExtendedSpendingKe
log::info!("Sapling output: {}", r.amount);
let memo_bytes = hex::decode(&r.memo).unwrap();
let memo = MemoBytes::from_bytes(&memo_bytes)?;
builder.add_z_output(
&r.address,
ovk,
amount,
&memo,
)
}
RecipientAddress::Transparent(_address) => {
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<Vec<
let mut diversifier = [0u8; 11];
hex::decode_to_slice(&txin.diversifier, &mut diversifier)?;
let diversifier = Diversifier(diversifier);
let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &txin.fvk)?.unwrap();
let fvk = decode_extended_full_viewing_key(
NETWORK.hrp_sapling_extended_full_viewing_key(),
&txin.fvk,
)?
.unwrap();
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
let mut rseed_bytes = [0u8; 32];
hex::decode_to_slice(&txin.rseed, &mut rseed_bytes)?;
let rseed = Fr::from_bytes(&rseed_bytes).unwrap();
let note = pa.create_note(txin.amount, Rseed::BeforeZip212(rseed)).unwrap();
let note = pa
.create_note(txin.amount, Rseed::BeforeZip212(rseed))
.unwrap();
let w = hex::decode(&txin.witness)?;
let witness = IncrementalWitness::<Node>::read(&*w)?;
let merkle_path = witness.path().unwrap();
@ -264,10 +308,13 @@ pub fn sign_offline_tx(tx: &Tx, sk: &ExtendedSpendingKey) -> anyhow::Result<Vec<
pub async fn broadcast_tx(tx: &[u8], ld_url: &str) -> anyhow::Result<String> {
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)
}

View File

@ -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<Vec<(i64, f64)>> {
if timestamps.is_empty() { return Ok(Vec::new()); }
let mut timestamps_map: HashMap<i64, Option<f64>> = 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(&params);
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<Vec<Quote>> {
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<Quote> = 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(&params);
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(&quotes, currency).unwrap();
}
}
}

View File

@ -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,

View File

@ -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};

View File

@ -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<Contact> = 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<Option<Contact>> {
fn decode_contact(
account: u32,
index: u32,
address: &str,
memo: &str,
) -> anyhow::Result<Option<Contact>> {
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);
}
}

View File

@ -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<u32> {
@ -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<String> {
pub async fn send_multi_payment(
&self,
account: u32,
recipients_json: &str,
anchor_offset: u32,
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<String> {
let recipients: Vec<Recipient> = 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<Recipient>, anchor_offset: u32) -> anyhow::Result<Tx> {
fn _prepare_payment(
&self,
account: u32,
amount: u64,
last_height: u32,
recipients: &Vec<Recipient>,
anchor_offset: u32,
) -> anyhow::Result<Tx> {
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, &notes, amount, &extfvk, recipients)?;
@ -246,7 +265,8 @@ impl Wallet {
progress_callback: impl Fn(Progress) + Send + 'static,
) -> anyhow::Result<String> {
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()), &notes, target_amount, &extfvk, recipients)?;
let selected_notes = prepare_tx(
&mut builder,
Some(skey.clone()),
&notes,
target_amount,
&extfvk,
recipients,
)?;
let (progress_tx, progress_rx) = mpsc::channel::<Progress>();
@ -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<Vec<SpendableNote>> {
fn _get_spendable_notes(
&self,
account: u32,
extfvk: &ExtendedFullViewingKey,
last_height: u32,
anchor_offset: u32,
) -> anyhow::Result<Vec<SpendableNote>> {
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<Vec<Recipient>> {
fn _build_recipients(
to_address: &str,
amount: u64,
max_amount_per_note: u64,
memo: &str,
) -> anyhow::Result<Vec<Recipient>> {
let mut recipients: Vec<Recipient> = 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<u32> {
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<u32> {
let quotes = fetch_historical_prices(now, days, currency, &self.db)
.await
.unwrap();
self.db.store_historical_prices(&quotes, currency).unwrap();
Ok(quotes.len() as u32)
}
}