mirror of
https://github.com/Qortal/piratewallet-light-cli.git
synced 2025-01-30 18:42:15 +00:00
Light Client Support for Memos (#1)
* Dependency to note-spending-v6 branch * Spend funds * Add outgoing memos * cleanup * Incoming memo support * Refactor to LightClient * Abstract grpc client creation * TLS example * Add address command * Run blocking IO * Store memos in wallet Tx * Fetch the full txns only for new transactions * Comments * Filter first * More comments * Add commands
This commit is contained in:
parent
698c65a284
commit
67864dfdb6
@ -24,34 +24,32 @@ rustyline = "5.0.2"
|
||||
|
||||
[dependencies.bellman]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
branch = "note-spending-v6"
|
||||
default-features = false
|
||||
features = ["groth16"]
|
||||
|
||||
[dependencies.pairing]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
|
||||
[dependencies.sapling-crypto]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
default-features = false
|
||||
branch = "note-spending-v6"
|
||||
|
||||
[dependencies.zcash_client_backend]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
branch = "note-spending-v6"
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_primitives]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
branch = "note-spending-v6"
|
||||
default-features = false
|
||||
|
||||
[dependencies.zcash_proofs]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "demo-wasm"
|
||||
branch = "note-spending-v6"
|
||||
default-features = false
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc", features = ["tower-hyper"] }
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
@ -1,7 +1,7 @@
|
||||
//! Structs for handling supported address types.
|
||||
|
||||
use pairing::bls12_381::Bls12;
|
||||
use sapling_crypto::primitives::PaymentAddress;
|
||||
use zcash_primitives::primitives::PaymentAddress;
|
||||
use zcash_client_backend::encoding::{decode_payment_address, decode_transparent_address};
|
||||
use zcash_primitives::legacy::TransparentAddress;
|
||||
|
||||
|
80
rust-lightclient/src/commands.rs
Normal file
80
rust-lightclient/src/commands.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::LightClient;
|
||||
|
||||
pub trait Command {
|
||||
fn help(&self);
|
||||
|
||||
fn short_help(&self) -> String;
|
||||
|
||||
fn exec(&self, args: &[String], lightclient: &LightClient);
|
||||
}
|
||||
|
||||
struct SyncCommand {}
|
||||
|
||||
impl Command for SyncCommand {
|
||||
fn help(&self) {
|
||||
println!("Type sync for syncing");
|
||||
}
|
||||
|
||||
fn short_help(&self) -> String {
|
||||
"Download CompactBlocks and sync to the server".to_string()
|
||||
}
|
||||
|
||||
fn exec(&self, args: &[String], lightclient: &LightClient) {
|
||||
lightclient.do_sync();
|
||||
}
|
||||
}
|
||||
|
||||
struct HelpCommand {}
|
||||
|
||||
impl Command for HelpCommand {
|
||||
fn help(&self) {
|
||||
println!("Lists all available commands");
|
||||
}
|
||||
|
||||
fn short_help(&self) -> String {
|
||||
"Lists all available commands".to_string()
|
||||
}
|
||||
|
||||
fn exec(&self, args: &[String], _: &LightClient) {
|
||||
// Print a list of all commands
|
||||
get_commands().iter().for_each(| (cmd, obj) | {
|
||||
println!("{} - {}", cmd, obj.short_help());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct AddressCommand {}
|
||||
impl Command for AddressCommand {
|
||||
fn help(&self) {
|
||||
println!("Show my addresses");
|
||||
}
|
||||
|
||||
fn short_help(&self) -> String {
|
||||
"List all current addresses".to_string()
|
||||
}
|
||||
|
||||
fn exec(&self, args: &[String], lightclient: &LightClient) {
|
||||
lightclient.do_address();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_commands() -> Box<HashMap<String, Box<dyn Command>>> {
|
||||
let mut map: HashMap<String, Box<dyn Command>> = HashMap::new();
|
||||
|
||||
map.insert("sync".to_string(), Box::new(SyncCommand{}));
|
||||
map.insert("help".to_string(), Box::new(HelpCommand{}));
|
||||
map.insert("address".to_string(), Box::new(AddressCommand{}));
|
||||
|
||||
Box::new(map)
|
||||
}
|
||||
|
||||
pub fn do_user_command(cmd: String, lightclient: &LightClient) {
|
||||
match get_commands().get(&cmd) {
|
||||
Some(cmd) => cmd.exec(&[], lightclient),
|
||||
None => {
|
||||
println!("Unknown command : {}. Type 'help' for a list of commands", cmd);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,526 +1,477 @@
|
||||
use std::time::SystemTime;
|
||||
use crate::lightwallet::LightWallet;
|
||||
|
||||
use pairing::bls12_381::Bls12;
|
||||
use sapling_crypto::primitives::{Diversifier, Note, PaymentAddress};
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use protobuf::*;
|
||||
use zcash_client_backend::{
|
||||
constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, encoding::encode_payment_address,
|
||||
proto::compact_formats::CompactBlock, welding_rig::scan_block,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
sapling::Node,
|
||||
transaction::{
|
||||
builder::{Builder, DEFAULT_FEE},
|
||||
components::Amount,
|
||||
TxId,
|
||||
},
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
JUBJUB,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||
|
||||
use crate::address;
|
||||
use crate::prover;
|
||||
use std::error::Error;
|
||||
use std::io::prelude::*;
|
||||
use std::fs::File;
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
// allocator.
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
use zcash_primitives::transaction::{TxId, Transaction};
|
||||
use zcash_primitives::note_encryption::Memo;
|
||||
|
||||
const ANCHOR_OFFSET: u32 = 10;
|
||||
use futures::Future;
|
||||
use hyper::client::connect::{Destination, HttpConnector};
|
||||
use tower_grpc::Request;
|
||||
use tower_hyper::{client, util};
|
||||
use tower_util::MakeService;
|
||||
use futures::stream::Stream;
|
||||
|
||||
const SAPLING_ACTIVATION_HEIGHT: i32 = 280_000;
|
||||
use crate::grpc_client::{ChainSpec, BlockId, BlockRange, RawTransaction, TxFilter};
|
||||
use crate::grpc_client::client::CompactTxStreamer;
|
||||
|
||||
// Used below to return the grpc "Client" type to calling methods
|
||||
type Client = crate::grpc_client::client::CompactTxStreamer<tower_request_modifier::RequestModifier<tower_hyper::client::Connection<tower_grpc::BoxBody>, tower_grpc::BoxBody>>;
|
||||
|
||||
|
||||
fn now() -> f64 {
|
||||
// web_sys::window()
|
||||
// .expect("should have a Window")
|
||||
// .performance()
|
||||
// .expect("should have a Performance")
|
||||
// .now()
|
||||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as f64
|
||||
pub struct LightClient {
|
||||
pub wallet : Arc<LightWallet>,
|
||||
pub sapling_output : Vec<u8>,
|
||||
pub sapling_spend : Vec<u8>,
|
||||
}
|
||||
|
||||
struct BlockData {
|
||||
height: i32,
|
||||
hash: BlockHash,
|
||||
tree: CommitmentTree<Node>,
|
||||
}
|
||||
|
||||
struct SaplingNoteData {
|
||||
account: usize,
|
||||
diversifier: Diversifier,
|
||||
note: Note<Bls12>,
|
||||
witnesses: Vec<IncrementalWitness<Node>>,
|
||||
nullifier: [u8; 32],
|
||||
spent: Option<TxId>,
|
||||
}
|
||||
|
||||
impl SaplingNoteData {
|
||||
fn new(
|
||||
extfvk: &ExtendedFullViewingKey,
|
||||
output: zcash_client_backend::wallet::WalletShieldedOutput,
|
||||
witness: IncrementalWitness<Node>,
|
||||
) -> Self {
|
||||
let nf = {
|
||||
let mut nf = [0; 32];
|
||||
nf.copy_from_slice(
|
||||
&output
|
||||
.note
|
||||
.nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB),
|
||||
);
|
||||
nf
|
||||
};
|
||||
|
||||
SaplingNoteData {
|
||||
account: output.account,
|
||||
diversifier: output.to.diversifier,
|
||||
note: output.note,
|
||||
witnesses: vec![witness],
|
||||
nullifier: nf,
|
||||
spent: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WalletTx {
|
||||
block: i32,
|
||||
notes: Vec<SaplingNoteData>,
|
||||
}
|
||||
|
||||
struct SpendableNote {
|
||||
txid: TxId,
|
||||
nullifier: [u8; 32],
|
||||
diversifier: Diversifier,
|
||||
note: Note<Bls12>,
|
||||
witness: IncrementalWitness<Node>,
|
||||
}
|
||||
|
||||
impl SpendableNote {
|
||||
fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize) -> Option<Self> {
|
||||
if nd.spent.is_none() {
|
||||
let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1);
|
||||
|
||||
witness.map(|w| SpendableNote {
|
||||
txid,
|
||||
nullifier: nd.nullifier,
|
||||
diversifier: nd.diversifier,
|
||||
note: nd.note.clone(),
|
||||
witness: w.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
extsks: [ExtendedSpendingKey; 1],
|
||||
extfvks: [ExtendedFullViewingKey; 1],
|
||||
address: PaymentAddress<Bls12>,
|
||||
blocks: Arc<RwLock<Vec<BlockData>>>,
|
||||
txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
|
||||
}
|
||||
|
||||
/// Public methods, exported to JavaScript.
|
||||
impl Client {
|
||||
impl LightClient {
|
||||
pub fn new() -> Self {
|
||||
|
||||
let extsk = ExtendedSpendingKey::master(&[0; 32]);
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
let address = extfvk.default_address().unwrap().1;
|
||||
|
||||
Client {
|
||||
extsks: [extsk],
|
||||
extfvks: [extfvk],
|
||||
address,
|
||||
blocks: Arc::new(RwLock::new(vec![])),
|
||||
txs: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool {
|
||||
let mut blocks = self.blocks.write().unwrap();
|
||||
if !blocks.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let hash = match hex::decode(hash) {
|
||||
Ok(hash) => {
|
||||
let mut r = hash;
|
||||
r.reverse();
|
||||
BlockHash::from_slice(&r)
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
return false;
|
||||
}
|
||||
let mut w = LightClient {
|
||||
wallet : Arc::new(LightWallet::new()),
|
||||
sapling_output : vec![],
|
||||
sapling_spend : vec![]
|
||||
};
|
||||
|
||||
let sapling_tree = match hex::decode(sapling_tree) {
|
||||
Ok(tree) => tree,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
return false;
|
||||
// Read Sapling Params
|
||||
let mut f = File::open("/home/adityapk/.zcash-params/sapling-output.params").unwrap();
|
||||
f.read_to_end(&mut w.sapling_output).unwrap();
|
||||
let mut f = File::open("/home/adityapk/.zcash-params/sapling-spend.params").unwrap();
|
||||
f.read_to_end(&mut w.sapling_spend).unwrap();
|
||||
|
||||
w.wallet.set_initial_block(500000,
|
||||
"004fada8d4dbc5e80b13522d2c6bd0116113c9b7197f0c6be69bc7a62f2824cd",
|
||||
"01b733e839b5f844287a6a491409a991ec70277f39a50c99163ed378d23a829a0700100001916db36dfb9a0cf26115ed050b264546c0fa23459433c31fd72f63d188202f2400011f5f4e3bd18da479f48d674dbab64454f6995b113fa21c9d8853a9e764fb3e1f01df9d2c233ca60360e3c2bb73caf5839a1be634c8b99aea22d02abda2e747d9100001970d41722c078288101acd0a75612acfb4c434f2a55aab09fb4e812accc2ba7301485150f0deac7774dcd0fe32043bde9ba2b6bbfff787ad074339af68e88ee70101601324f1421e00a43ef57f197faf385ee4cac65aab58048016ecbd94e022973701e1b17f4bd9d1b6ca1107f619ac6d27b53dd3350d5be09b08935923cbed97906c0000000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39");
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
pub fn last_scanned_height(&self) -> u64 {
|
||||
self.wallet.last_scanned_height() as u64
|
||||
}
|
||||
|
||||
pub fn do_address(&self) {
|
||||
println!("Address: {}", self.wallet.address());
|
||||
println!("Balance: {}", self.wallet.balance());
|
||||
}
|
||||
|
||||
pub fn do_sync(&self) {
|
||||
// Sync is 3 parts
|
||||
// 1. Get the latest block
|
||||
// 2. Get all the blocks that we don't have
|
||||
// 3. Find all new Txns that don't have the full Tx, and get them as full transactions
|
||||
// and scan them, mainly to get the memos
|
||||
let mut last_scanned_height = self.wallet.last_scanned_height() as u64;
|
||||
let mut end_height = last_scanned_height + 1000;
|
||||
|
||||
// This will hold the latest block fetched from the RPC
|
||||
let latest_block_height = Arc::new(AtomicU64::new(0));
|
||||
// TODO: this could be a oneshot channel
|
||||
let latest_block_height_clone = latest_block_height.clone();
|
||||
self.fetch_latest_block(move |block: BlockId| {
|
||||
latest_block_height_clone.store(block.height, Ordering::SeqCst);
|
||||
});
|
||||
let last_block = latest_block_height.load(Ordering::SeqCst);
|
||||
|
||||
let bytes_downloaded = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
// Fetch CompactBlocks in increments
|
||||
loop {
|
||||
let local_light_wallet = self.wallet.clone();
|
||||
let local_bytes_downloaded = bytes_downloaded.clone();
|
||||
|
||||
let simple_callback = move |encoded_block: &[u8]| {
|
||||
local_light_wallet.scan_block(encoded_block);
|
||||
local_bytes_downloaded.fetch_add(encoded_block.len(), Ordering::SeqCst);
|
||||
};
|
||||
|
||||
if let Ok(tree) = CommitmentTree::read(&sapling_tree[..]) {
|
||||
blocks.push(BlockData { height, hash, tree });
|
||||
true
|
||||
} else {
|
||||
false
|
||||
print!("Syncing {}/{}, Balance = {} \r",
|
||||
last_scanned_height, last_block, self.wallet.balance());
|
||||
|
||||
self.fetch_blocks(last_scanned_height, end_height, simple_callback);
|
||||
|
||||
last_scanned_height = end_height + 1;
|
||||
end_height = last_scanned_height + 1000 - 1;
|
||||
|
||||
if last_scanned_height > last_block {
|
||||
break;
|
||||
} else if end_height > last_block {
|
||||
end_height = last_block;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_scanned_height(&self) -> i32 {
|
||||
self.blocks
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.map(|block| block.height)
|
||||
.unwrap_or(SAPLING_ACTIVATION_HEIGHT - 1)
|
||||
}
|
||||
println!("Synced to {}, Downloaded {} kB \r",
|
||||
last_block, bytes_downloaded.load(Ordering::SeqCst) / 1024);
|
||||
|
||||
/// Determines the target height for a transaction, and the offset from which to
|
||||
/// select anchors, based on the current synchronised block chain.
|
||||
fn get_target_height_and_anchor_offset(&self) -> Option<(u32, usize)> {
|
||||
match {
|
||||
let blocks = self.blocks.read().unwrap();
|
||||
(
|
||||
blocks.first().map(|block| block.height as u32),
|
||||
blocks.last().map(|block| block.height as u32),
|
||||
)
|
||||
} {
|
||||
(Some(min_height), Some(max_height)) => {
|
||||
let target_height = max_height + 1;
|
||||
|
||||
// Select an anchor ANCHOR_OFFSET back from the target block,
|
||||
// unless that would be before the earliest block we have.
|
||||
let anchor_height =
|
||||
cmp::max(target_height.saturating_sub(ANCHOR_OFFSET), min_height);
|
||||
// Get the Raw transaction for all the wallet transactions
|
||||
|
||||
Some((target_height, (target_height - anchor_height) as usize))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address(&self) -> String {
|
||||
encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &self.address)
|
||||
}
|
||||
|
||||
pub fn balance(&self) -> u64 {
|
||||
self.txs
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.map(|tx| {
|
||||
tx.notes
|
||||
.iter()
|
||||
.map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 })
|
||||
.sum::<u64>()
|
||||
})
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
pub fn verified_balance(&self) -> u64 {
|
||||
let anchor_height = match self.get_target_height_and_anchor_offset() {
|
||||
Some((height, anchor_offset)) => height - anchor_offset as u32,
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
self.txs
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.map(|tx| {
|
||||
if tx.block as u32 <= anchor_height {
|
||||
tx.notes
|
||||
.iter()
|
||||
.map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 })
|
||||
.sum::<u64>()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
pub fn scan_block(&self, block: &[u8]) -> bool {
|
||||
let block: CompactBlock = match parse_from_bytes(block) {
|
||||
Ok(block) => block,
|
||||
Err(e) => {
|
||||
eprintln!("Could not parse CompactBlock from bytes: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Scanned blocks MUST be height-sequential.
|
||||
let height = block.get_height() as i32;
|
||||
if height == self.last_scanned_height() {
|
||||
// If the last scanned block is rescanned, check it still matches.
|
||||
if let Some(hash) = self.blocks.read().unwrap().last().map(|block| block.hash) {
|
||||
if block.hash() != hash {
|
||||
eprintln!("Block hash does not match for block {}. {} vs {}", height, block.hash(), hash);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if height != (self.last_scanned_height() + 1) {
|
||||
eprintln!(
|
||||
"Block is not height-sequential (expected {}, found {})",
|
||||
self.last_scanned_height() + 1,
|
||||
height
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the most recent scanned data.
|
||||
let mut block_data = BlockData {
|
||||
height,
|
||||
hash: block.hash(),
|
||||
tree: self
|
||||
.blocks
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.map(|block| block.tree.clone())
|
||||
.unwrap_or(CommitmentTree::new()),
|
||||
};
|
||||
let mut txs = self.txs.write().unwrap();
|
||||
|
||||
// Create a Vec containing all unspent nullifiers.
|
||||
let nfs: Vec<_> = txs
|
||||
.iter()
|
||||
.map(|(txid, tx)| {
|
||||
let txid = *txid;
|
||||
tx.notes.iter().filter_map(move |nd| {
|
||||
if nd.spent.is_none() {
|
||||
Some((nd.nullifier, nd.account, txid))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
// Prepare the note witnesses for updating
|
||||
for tx in txs.values_mut() {
|
||||
for nd in tx.notes.iter_mut() {
|
||||
// Duplicate the most recent witness
|
||||
if let Some(witness) = nd.witnesses.last() {
|
||||
nd.witnesses.push(witness.clone());
|
||||
}
|
||||
// Trim the oldest witnesses
|
||||
nd.witnesses = nd
|
||||
.witnesses
|
||||
.split_off(nd.witnesses.len().saturating_sub(100));
|
||||
}
|
||||
}
|
||||
|
||||
let new_txs = {
|
||||
let nf_refs: Vec<_> = nfs.iter().map(|(nf, acc, _)| (&nf[..], *acc)).collect();
|
||||
|
||||
// Create a single mutable slice of all the newly-added witnesses.
|
||||
let mut witness_refs: Vec<_> = txs
|
||||
.values_mut()
|
||||
.map(|tx| tx.notes.iter_mut().filter_map(|nd| nd.witnesses.last_mut()))
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
scan_block(
|
||||
block,
|
||||
&self.extfvks,
|
||||
&nf_refs[..],
|
||||
&mut block_data.tree,
|
||||
&mut witness_refs[..],
|
||||
)
|
||||
};
|
||||
|
||||
for (tx, new_witnesses) in new_txs {
|
||||
// Mark notes as spent.
|
||||
for spend in &tx.shielded_spends {
|
||||
let txid = nfs
|
||||
.iter()
|
||||
.find(|(nf, _, _)| &nf[..] == &spend.nf[..])
|
||||
.unwrap()
|
||||
.2;
|
||||
let mut spent_note = txs
|
||||
.get_mut(&txid)
|
||||
.unwrap()
|
||||
.notes
|
||||
.iter_mut()
|
||||
.find(|nd| &nd.nullifier[..] == &spend.nf[..])
|
||||
.unwrap();
|
||||
spent_note.spent = Some(tx.txid);
|
||||
}
|
||||
|
||||
// Find the existing transaction entry, or create a new one.
|
||||
if !txs.contains_key(&tx.txid) {
|
||||
let tx_entry = WalletTx {
|
||||
block: block_data.height,
|
||||
notes: vec![],
|
||||
};
|
||||
txs.insert(tx.txid, tx_entry);
|
||||
}
|
||||
let tx_entry = txs.get_mut(&tx.txid).unwrap();
|
||||
|
||||
// Save notes.
|
||||
for (output, witness) in tx
|
||||
.shielded_outputs
|
||||
.into_iter()
|
||||
.zip(new_witnesses.into_iter())
|
||||
// We need to first copy over the Txids from the wallet struct, because
|
||||
// we need to free the read lock from here (Because we'll self.wallet.txs later)
|
||||
let txids_to_fetch: Vec<TxId>;
|
||||
{
|
||||
tx_entry.notes.push(SaplingNoteData::new(
|
||||
&self.extfvks[output.account],
|
||||
output,
|
||||
witness,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Store scanned data for this block.
|
||||
self.blocks.write().unwrap().push(block_data);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn send_to_address(
|
||||
&self,
|
||||
consensus_branch_id: u32,
|
||||
spend_params: &[u8],
|
||||
output_params: &[u8],
|
||||
to: &str,
|
||||
value: u32,
|
||||
) -> Option<Box<[u8]>> {
|
||||
let start_time = now();
|
||||
println!(
|
||||
"0: Creating transaction sending {} tazoshis to {}",
|
||||
value,
|
||||
to
|
||||
);
|
||||
|
||||
let extsk = &self.extsks[0];
|
||||
let extfvk = &self.extfvks[0];
|
||||
let ovk = extfvk.fvk.ovk;
|
||||
|
||||
let to = match address::RecipientAddress::from_str(to) {
|
||||
Some(to) => to,
|
||||
None => {
|
||||
eprintln!("Invalid recipient address");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let value = Amount(value as i64);
|
||||
|
||||
// Target the next block, assuming we are up-to-date.
|
||||
let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
eprintln!("Cannot send funds before scanning any blocks");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Select notes to cover the target value
|
||||
println!("{}: Selecting notes", now() - start_time);
|
||||
let target_value = value.0 + DEFAULT_FEE.0;
|
||||
let notes: Vec<_> = self
|
||||
.txs
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note)))
|
||||
.flatten()
|
||||
.filter_map(|(txid, note)| SpendableNote::from(txid, note, anchor_offset))
|
||||
.scan(0, |running_total, spendable| {
|
||||
let value = spendable.note.value;
|
||||
let ret = if *running_total < target_value as u64 {
|
||||
Some(spendable)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
*running_total = *running_total + value;
|
||||
ret
|
||||
// First, build a list of all the TxIDs and Memos that we need
|
||||
// to fetch.
|
||||
// 1. Get all (txid, Option<Memo>)
|
||||
// 2. Filter out all txids where the Memo is None
|
||||
// (Which means that particular txid was never fetched. Remember
|
||||
// that when memos are fetched, if they are empty, they become
|
||||
// Some(f60000...)
|
||||
let txids_and_memos = self.wallet.txs.read().unwrap().iter()
|
||||
.flat_map( |(txid, wtx)| { // flat_map because we're collecting vector of vectors
|
||||
wtx.notes.iter()
|
||||
.filter( |nd| nd.memo.is_none()) // only get if memo is None (i.e., it has not been fetched)
|
||||
.map( |nd| (txid.clone(), nd.memo.clone()) ) // collect (txid, memo) Clone everything because we want copies, so we can release the read lock
|
||||
.collect::<Vec<(TxId, Option<Memo>)>>() // convert to vector
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<(TxId, Option<Memo>)>>();
|
||||
|
||||
// Confirm we were able to select sufficient value
|
||||
let selected_value = notes
|
||||
.iter()
|
||||
.map(|selected| selected.note.value)
|
||||
.sum::<u64>();
|
||||
if selected_value < target_value as u64 {
|
||||
eprintln!(
|
||||
"Insufficient funds (have {}, need {})",
|
||||
selected_value, target_value
|
||||
);
|
||||
return None;
|
||||
println!("{:?}", txids_and_memos);
|
||||
// TODO: Assert that all the memos here are None
|
||||
|
||||
txids_to_fetch = txids_and_memos.iter()
|
||||
.map( | (txid, _) | txid.clone() ) // We're only interested in the txids, so drop the Memo, which is None anyway
|
||||
.collect::<Vec<TxId>>(); // and convert into Vec
|
||||
}
|
||||
|
||||
// Create the transaction
|
||||
println!("{}: Adding {} inputs", now() - start_time, notes.len());
|
||||
let mut builder = Builder::new(height);
|
||||
for selected in notes.iter() {
|
||||
if let Err(e) = builder.add_sapling_spend(
|
||||
extsk.clone(),
|
||||
selected.diversifier,
|
||||
selected.note.clone(),
|
||||
selected.witness.clone(),
|
||||
) {
|
||||
eprintln!("Error adding note: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
println!("{}: Adding output", now() - start_time);
|
||||
if let Err(e) = match to {
|
||||
address::RecipientAddress::Shielded(to) => {
|
||||
builder.add_sapling_output(ovk, to.clone(), value, None)
|
||||
}
|
||||
address::RecipientAddress::Transparent(to) => {
|
||||
builder.add_transparent_output(&to, value)
|
||||
}
|
||||
} {
|
||||
eprintln!("Error adding output: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
println!("{}: Building transaction", now() - start_time);
|
||||
let (tx, _) = match builder.build(
|
||||
consensus_branch_id,
|
||||
prover::InMemTxProver::new(spend_params, output_params),
|
||||
) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
eprintln!("Error creating transaction: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
// And go and fetch the txids, getting the full transaction, so we can
|
||||
// read the memos
|
||||
for txid in txids_to_fetch {
|
||||
let light_wallet_clone = self.wallet.clone();
|
||||
println!("Scanning txid {}", txid);
|
||||
|
||||
self.fetch_full_tx(txid, move |tx_bytes: &[u8] | {
|
||||
let tx = Transaction::read(tx_bytes).unwrap();
|
||||
|
||||
light_wallet_clone.scan_full_tx(&tx);
|
||||
});
|
||||
};
|
||||
println!("{}: Transaction created", now() - start_time);
|
||||
println!("Transaction ID: {}", tx.txid());
|
||||
|
||||
// Mark notes as spent.
|
||||
let mut txs = self.txs.write().unwrap();
|
||||
for selected in notes {
|
||||
let mut spent_note = txs
|
||||
.get_mut(&selected.txid)
|
||||
.unwrap()
|
||||
.notes
|
||||
.iter_mut()
|
||||
.find(|nd| &nd.nullifier[..] == &selected.nullifier[..])
|
||||
.unwrap();
|
||||
spent_note.spent = Some(tx.txid());
|
||||
// Print all the memos for fun.
|
||||
let memos = self.wallet.txs.read().unwrap()
|
||||
.values().flat_map(|wtx| {
|
||||
wtx.notes.iter().map(|nd| nd.memo.clone() ).collect::<Vec<Option<Memo>>>()
|
||||
})
|
||||
.map( |m| match m {
|
||||
Some(memo) => {
|
||||
match memo.to_utf8() {
|
||||
Some(Ok(memo_str)) => Some(memo_str),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
_ => None
|
||||
})
|
||||
.collect::<Vec<Option<String>>>();
|
||||
|
||||
println!("All Wallet Txns {:?}", memos);
|
||||
}
|
||||
|
||||
// Return the encoded transaction, so the caller can send it.
|
||||
let mut raw_tx = vec![];
|
||||
tx.write(&mut raw_tx).unwrap();
|
||||
Some(raw_tx.into_boxed_slice())
|
||||
pub fn do_send(&self, addr: String, value: u64, memo: Option<String>) {
|
||||
let rawtx = self.wallet.send_to_address(
|
||||
u32::from_str_radix("2bb40e60", 16).unwrap(), // Blossom ID
|
||||
&self.sapling_spend, &self.sapling_output,
|
||||
&addr, value, memo
|
||||
);
|
||||
|
||||
match rawtx {
|
||||
Some(txbytes) => self.broadcast_raw_tx(txbytes),
|
||||
None => eprintln!("No Tx to broadcast")
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fetch_blocks<F : 'static + std::marker::Send>(&self, start_height: u64, end_height: u64, c: F)
|
||||
where F : Fn(&[u8]) {
|
||||
// Fetch blocks
|
||||
let uri: http::Uri = format!("http://127.0.0.1:9067").parse().unwrap();
|
||||
|
||||
let dst = Destination::try_from_uri(uri.clone()).unwrap();
|
||||
let connector = util::Connector::new(HttpConnector::new(4));
|
||||
let settings = client::Builder::new().http2_only(true).clone();
|
||||
let mut make_client = client::Connect::with_builder(connector, settings);
|
||||
|
||||
let say_hello = make_client
|
||||
.make_service(dst)
|
||||
.map_err(|e| panic!("connect error: {:?}", e))
|
||||
.and_then(move |conn| {
|
||||
|
||||
let conn = tower_request_modifier::Builder::new()
|
||||
.set_origin(uri)
|
||||
.build(conn)
|
||||
.unwrap();
|
||||
|
||||
// Wait until the client is ready...
|
||||
CompactTxStreamer::new(conn)
|
||||
.ready()
|
||||
.map_err(|e| eprintln!("streaming error {:?}", e))
|
||||
})
|
||||
.and_then(move |mut client| {
|
||||
let bs = BlockId{ height: start_height, hash: vec!()};
|
||||
let be = BlockId{ height: end_height, hash: vec!()};
|
||||
|
||||
let br = Request::new(BlockRange{ start: Some(bs), end: Some(be)});
|
||||
client
|
||||
.get_block_range(br)
|
||||
.map_err(|e| {
|
||||
eprintln!("RouteChat request failed; err={:?}", e);
|
||||
})
|
||||
.and_then(move |response| {
|
||||
let inbound = response.into_inner();
|
||||
inbound.for_each(move |b| {
|
||||
use prost::Message;
|
||||
let mut encoded_buf = vec![];
|
||||
|
||||
b.encode(&mut encoded_buf).unwrap();
|
||||
c(&encoded_buf);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| eprintln!("gRPC inbound stream error: {:?}", e))
|
||||
})
|
||||
});
|
||||
|
||||
tokio::runtime::current_thread::Runtime::new().unwrap().block_on(say_hello).unwrap();
|
||||
}
|
||||
|
||||
|
||||
pub fn fetch_full_tx<F : 'static + std::marker::Send>(&self, txid: TxId, c: F)
|
||||
where F : Fn(&[u8]) {
|
||||
let uri: http::Uri = format!("http://127.0.0.1:9067").parse().unwrap();
|
||||
|
||||
let say_hello = self.make_grpc_client(uri).unwrap()
|
||||
.and_then(move |mut client| {
|
||||
let txfilter = TxFilter { block: None, index: 0, hash: txid.0.to_vec() };
|
||||
client.get_transaction(Request::new(txfilter))
|
||||
})
|
||||
.and_then(move |response| {
|
||||
//let tx = Transaction::read(&response.into_inner().data[..]).unwrap();
|
||||
c(&response.into_inner().data);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| {
|
||||
println!("ERR = {:?}", e);
|
||||
});
|
||||
|
||||
tokio::runtime::current_thread::Runtime::new().unwrap().block_on(say_hello).unwrap()
|
||||
}
|
||||
|
||||
pub fn broadcast_raw_tx(&self, tx_bytes: Box<[u8]>) {
|
||||
let uri: http::Uri = format!("http://127.0.0.1:9067").parse().unwrap();
|
||||
|
||||
let say_hello = self.make_grpc_client(uri).unwrap()
|
||||
.and_then(move |mut client| {
|
||||
client.send_transaction(Request::new(RawTransaction {data: tx_bytes.to_vec()}))
|
||||
})
|
||||
.and_then(move |response| {
|
||||
println!("{:?}", response.into_inner());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| {
|
||||
println!("ERR = {:?}", e);
|
||||
});
|
||||
|
||||
tokio::runtime::current_thread::Runtime::new().unwrap().block_on(say_hello).unwrap()
|
||||
}
|
||||
|
||||
pub fn fetch_latest_block<F : 'static + std::marker::Send>(&self, mut c : F)
|
||||
where F : FnMut(BlockId) {
|
||||
let uri: http::Uri = format!("http://127.0.0.1:9067").parse().unwrap();
|
||||
|
||||
let say_hello = self.make_grpc_client(uri).unwrap()
|
||||
.and_then(|mut client| {
|
||||
client.get_latest_block(Request::new(ChainSpec {}))
|
||||
})
|
||||
.and_then(move |response| {
|
||||
c(response.into_inner());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| {
|
||||
println!("ERR = {:?}", e);
|
||||
});
|
||||
|
||||
tokio::runtime::current_thread::Runtime::new().unwrap().block_on(say_hello).unwrap()
|
||||
}
|
||||
|
||||
fn make_grpc_client(&self, uri: http::Uri) -> Result<Box<dyn Future<Item=Client, Error=tower_grpc::Status> + Send>, Box<dyn Error>> {
|
||||
let dst = Destination::try_from_uri(uri.clone())?;
|
||||
let connector = util::Connector::new(HttpConnector::new(4));
|
||||
let settings = client::Builder::new().http2_only(true).clone();
|
||||
let mut make_client = client::Connect::with_builder(connector, settings);
|
||||
|
||||
let say_hello = make_client
|
||||
.make_service(dst)
|
||||
.map_err(|e| panic!("connect error: {:?}", e))
|
||||
.and_then(move |conn| {
|
||||
|
||||
let conn = tower_request_modifier::Builder::new()
|
||||
.set_origin(uri)
|
||||
.build(conn)
|
||||
.unwrap();
|
||||
|
||||
// Wait until the client is ready...
|
||||
CompactTxStreamer::new(conn).ready()
|
||||
});
|
||||
Ok(Box::new(say_hello))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
TLS Example https://gist.github.com/kiratp/dfcbcf0aa713a277d5d53b06d9db9308
|
||||
|
||||
// [dependencies]
|
||||
// futures = "0.1.27"
|
||||
// http = "0.1.17"
|
||||
// tokio = "0.1.21"
|
||||
// tower-request-modifier = { git = "https://github.com/tower-rs/tower-http" }
|
||||
// tower-grpc = { version = "0.1.0", features = ["tower-hyper"] }
|
||||
// tower-service = "0.2"
|
||||
// tower-util = "0.1"
|
||||
// tokio-rustls = "0.10.0-alpha.3"
|
||||
// webpki = "0.19.1"
|
||||
// webpki-roots = "0.16.0"
|
||||
// tower-h2 = { git = "https://github.com/tower-rs/tower-h2" }
|
||||
// openssl = "*"
|
||||
// openssl-probe = "*"
|
||||
|
||||
use std::thread;
|
||||
use std::sync::{Arc};
|
||||
use futures::{future, Future};
|
||||
use tower_util::MakeService;
|
||||
|
||||
use tokio_rustls::client::TlsStream;
|
||||
use tokio_rustls::{rustls::ClientConfig, TlsConnector};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio::executor::DefaultExecutor;
|
||||
use tokio::net::tcp::TcpStream;
|
||||
use tower_h2;
|
||||
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
|
||||
|
||||
struct Dst(SocketAddr);
|
||||
|
||||
|
||||
impl tower_service::Service<()> for Dst {
|
||||
type Response = TlsStream<TcpStream>;
|
||||
type Error = ::std::io::Error;
|
||||
type Future = Box<dyn Future<Item = TlsStream<TcpStream>, Error = ::std::io::Error> + Send>;
|
||||
|
||||
fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> {
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
fn call(&mut self, _: ()) -> Self::Future {
|
||||
println!("{:?}", self.0);
|
||||
let mut config = ClientConfig::new();
|
||||
|
||||
config.alpn_protocols.push(b"h2".to_vec());
|
||||
config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
let config = Arc::new(config);
|
||||
let tls_connector = TlsConnector::from(config);
|
||||
|
||||
let addr_string_local = "mydomain.com";
|
||||
|
||||
let domain = webpki::DNSNameRef::try_from_ascii_str(addr_string_local).unwrap();
|
||||
let domain_local = domain.to_owned();
|
||||
|
||||
let stream = TcpStream::connect(&self.0).and_then(move |sock| {
|
||||
sock.set_nodelay(true).unwrap();
|
||||
tls_connector.connect(domain_local.as_ref(), sock)
|
||||
})
|
||||
.map(move |tcp| tcp);
|
||||
|
||||
Box::new(stream)
|
||||
}
|
||||
}
|
||||
|
||||
// Same implementation but without TLS. Should make it straightforward to run without TLS
|
||||
// when testing on local machine
|
||||
|
||||
// impl tower_service::Service<()> for Dst {
|
||||
// type Response = TcpStream;
|
||||
// type Error = ::std::io::Error;
|
||||
// type Future = Box<dyn Future<Item = TcpStream, Error = ::std::io::Error> + Send>;
|
||||
|
||||
// fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> {
|
||||
// Ok(().into())
|
||||
// }
|
||||
|
||||
// fn call(&mut self, _: ()) -> Self::Future {
|
||||
// let mut config = ClientConfig::new();
|
||||
// config.alpn_protocols.push(b"h2".to_vec());
|
||||
// config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
|
||||
// let addr_string_local = "mydomain.com".to_string();
|
||||
// let addr = addr_string_local.as_str();
|
||||
|
||||
// let stream = TcpStream::connect(&self.0)
|
||||
// .and_then(move |sock| {
|
||||
// sock.set_nodelay(true).unwrap();
|
||||
// Ok(sock)
|
||||
// });
|
||||
// Box::new(stream)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
fn connect() {
|
||||
let keepalive = future::loop_fn((), move |_| {
|
||||
let uri: http::Uri = "https://mydomain.com".parse().unwrap();
|
||||
println!("Connecting to network at: {:?}", uri);
|
||||
|
||||
let addr = "https://mydomain.com:443"
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
let h2_settings = Default::default();
|
||||
let mut make_client = tower_h2::client::Connect::new(Dst {0: addr}, h2_settings, DefaultExecutor::current());
|
||||
|
||||
make_client
|
||||
.make_service(())
|
||||
.map_err(|e| {
|
||||
eprintln!("HTTP/2 connection failed; err={:?}", e);
|
||||
})
|
||||
.and_then(move |conn| {
|
||||
let conn = tower_request_modifier::Builder::new()
|
||||
.set_origin(uri)
|
||||
.build(conn)
|
||||
.unwrap();
|
||||
|
||||
MyGrpcService::new(conn)
|
||||
// Wait until the client is ready...
|
||||
.ready()
|
||||
.map_err(|e| eprintln!("client closed: {:?}", e))
|
||||
})
|
||||
.and_then(move |mut client| {
|
||||
// do stuff
|
||||
})
|
||||
.then(|e| {
|
||||
eprintln!("Reopening client connection to network: {:?}", e);
|
||||
let retry_sleep = std::time::Duration::from_secs(1);
|
||||
|
||||
thread::sleep(retry_sleep);
|
||||
Ok(future::Loop::Continue(()))
|
||||
})
|
||||
});
|
||||
|
||||
thread::spawn(move || tokio::run(keepalive));
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
connect();
|
||||
}
|
||||
|
||||
*/
|
561
rust-lightclient/src/lightwallet.rs
Normal file
561
rust-lightclient/src/lightwallet.rs
Normal file
@ -0,0 +1,561 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use pairing::bls12_381::Bls12;
|
||||
use zcash_primitives::primitives::{Diversifier, Note, PaymentAddress};
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use protobuf::*;
|
||||
use zcash_client_backend::{
|
||||
constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, encoding::encode_payment_address,
|
||||
proto::compact_formats::CompactBlock, welding_rig::scan_block,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
sapling::Node,
|
||||
transaction::{
|
||||
builder::{Builder},
|
||||
components::Amount, components::amount::DEFAULT_FEE,
|
||||
TxId, Transaction
|
||||
},
|
||||
note_encryption::{Memo, try_sapling_note_decryption},
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
JUBJUB,
|
||||
};
|
||||
|
||||
use crate::address;
|
||||
use crate::prover;
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
// allocator.
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
const ANCHOR_OFFSET: u32 = 10;
|
||||
|
||||
const SAPLING_ACTIVATION_HEIGHT: i32 = 280_000;
|
||||
|
||||
|
||||
fn now() -> f64 {
|
||||
// web_sys::window()
|
||||
// .expect("should have a Window")
|
||||
// .performance()
|
||||
// .expect("should have a Performance")
|
||||
// .now()
|
||||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as f64
|
||||
}
|
||||
|
||||
struct BlockData {
|
||||
height: i32,
|
||||
hash: BlockHash,
|
||||
tree: CommitmentTree<Node>,
|
||||
}
|
||||
|
||||
pub struct SaplingNoteData {
|
||||
account: usize,
|
||||
diversifier: Diversifier,
|
||||
note: Note<Bls12>,
|
||||
witnesses: Vec<IncrementalWitness<Node>>,
|
||||
nullifier: [u8; 32],
|
||||
spent: Option<TxId>,
|
||||
pub memo: Option<Memo>
|
||||
}
|
||||
|
||||
impl SaplingNoteData {
|
||||
fn new(
|
||||
extfvk: &ExtendedFullViewingKey,
|
||||
output: zcash_client_backend::wallet::WalletShieldedOutput
|
||||
) -> Self {
|
||||
let witness = output.witness;
|
||||
let nf = {
|
||||
let mut nf = [0; 32];
|
||||
nf.copy_from_slice(
|
||||
&output
|
||||
.note
|
||||
.nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB),
|
||||
);
|
||||
nf
|
||||
};
|
||||
|
||||
SaplingNoteData {
|
||||
account: output.account,
|
||||
diversifier: output.to.diversifier,
|
||||
note: output.note,
|
||||
witnesses: vec![witness],
|
||||
nullifier: nf,
|
||||
spent: None,
|
||||
memo: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WalletTx {
|
||||
block: i32,
|
||||
pub notes: Vec<SaplingNoteData>,
|
||||
}
|
||||
|
||||
struct SpendableNote {
|
||||
txid: TxId,
|
||||
nullifier: [u8; 32],
|
||||
diversifier: Diversifier,
|
||||
note: Note<Bls12>,
|
||||
witness: IncrementalWitness<Node>,
|
||||
}
|
||||
|
||||
impl SpendableNote {
|
||||
fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize) -> Option<Self> {
|
||||
if nd.spent.is_none() {
|
||||
let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1);
|
||||
|
||||
witness.map(|w| SpendableNote {
|
||||
txid,
|
||||
nullifier: nd.nullifier,
|
||||
diversifier: nd.diversifier,
|
||||
note: nd.note.clone(),
|
||||
witness: w.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LightWallet {
|
||||
extsks: [ExtendedSpendingKey; 1],
|
||||
extfvks: [ExtendedFullViewingKey; 1],
|
||||
address: PaymentAddress<Bls12>,
|
||||
blocks: Arc<RwLock<Vec<BlockData>>>,
|
||||
pub txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
|
||||
}
|
||||
|
||||
impl LightWallet {
|
||||
pub fn new() -> Self {
|
||||
|
||||
let extsk = ExtendedSpendingKey::master(&[1; 32]); // New key
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
let address = extfvk.default_address().unwrap().1;
|
||||
|
||||
LightWallet {
|
||||
extsks: [extsk],
|
||||
extfvks: [extfvk],
|
||||
address,
|
||||
blocks: Arc::new(RwLock::new(vec![])),
|
||||
txs: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool {
|
||||
let mut blocks = self.blocks.write().unwrap();
|
||||
if !blocks.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let hash = match hex::decode(hash) {
|
||||
Ok(hash) => {
|
||||
let mut r = hash;
|
||||
r.reverse();
|
||||
BlockHash::from_slice(&r)
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let sapling_tree = match hex::decode(sapling_tree) {
|
||||
Ok(tree) => tree,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(tree) = CommitmentTree::read(&sapling_tree[..]) {
|
||||
blocks.push(BlockData { height, hash, tree });
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_scanned_height(&self) -> i32 {
|
||||
self.blocks
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.map(|block| block.height)
|
||||
.unwrap_or(SAPLING_ACTIVATION_HEIGHT - 1)
|
||||
}
|
||||
|
||||
/// Determines the target height for a transaction, and the offset from which to
|
||||
/// select anchors, based on the current synchronised block chain.
|
||||
fn get_target_height_and_anchor_offset(&self) -> Option<(u32, usize)> {
|
||||
match {
|
||||
let blocks = self.blocks.read().unwrap();
|
||||
(
|
||||
blocks.first().map(|block| block.height as u32),
|
||||
blocks.last().map(|block| block.height as u32),
|
||||
)
|
||||
} {
|
||||
(Some(min_height), Some(max_height)) => {
|
||||
let target_height = max_height + 1;
|
||||
|
||||
// Select an anchor ANCHOR_OFFSET back from the target block,
|
||||
// unless that would be before the earliest block we have.
|
||||
let anchor_height =
|
||||
cmp::max(target_height.saturating_sub(ANCHOR_OFFSET), min_height);
|
||||
|
||||
Some((target_height, (target_height - anchor_height) as usize))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn address(&self) -> String {
|
||||
encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &self.address)
|
||||
}
|
||||
|
||||
pub fn balance(&self) -> u64 {
|
||||
self.txs
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.map(|tx| {
|
||||
tx.notes
|
||||
.iter()
|
||||
.map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 })
|
||||
.sum::<u64>()
|
||||
})
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
pub fn verified_balance(&self) -> u64 {
|
||||
let anchor_height = match self.get_target_height_and_anchor_offset() {
|
||||
Some((height, anchor_offset)) => height - anchor_offset as u32,
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
self.txs
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.map(|tx| {
|
||||
if tx.block as u32 <= anchor_height {
|
||||
tx.notes
|
||||
.iter()
|
||||
.map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 })
|
||||
.sum::<u64>()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
pub fn scan_full_tx(&self, tx: &Transaction) {
|
||||
for output in tx.shielded_outputs.iter() {
|
||||
|
||||
let ivks: Vec<_> = self.extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect();
|
||||
|
||||
let cmu = output.cmu;
|
||||
let ct = output.enc_ciphertext;
|
||||
|
||||
for (_account, ivk) in ivks.iter().enumerate() {
|
||||
let epk_prime = output.ephemeral_key.as_prime_order(&JUBJUB).unwrap();
|
||||
|
||||
let (note, _to, memo) = match try_sapling_note_decryption(ivk, &epk_prime, &cmu, &ct) {
|
||||
Some(ret) => ret,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
{
|
||||
// Update the WalletTx
|
||||
// Do it in a short scope because of the write lock.
|
||||
let mut txs = self.txs.write().unwrap();
|
||||
txs.get_mut(&tx.txid()).unwrap()
|
||||
.notes.iter_mut()
|
||||
.find(|nd| nd.note == note).unwrap()
|
||||
.memo = Some(memo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan_block(&self, block: &[u8]) -> bool {
|
||||
let block: CompactBlock = match parse_from_bytes(block) {
|
||||
Ok(block) => block,
|
||||
Err(e) => {
|
||||
eprintln!("Could not parse CompactBlock from bytes: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Scanned blocks MUST be height-sequential.
|
||||
let height = block.get_height() as i32;
|
||||
if height == self.last_scanned_height() {
|
||||
// If the last scanned block is rescanned, check it still matches.
|
||||
if let Some(hash) = self.blocks.read().unwrap().last().map(|block| block.hash) {
|
||||
if block.hash() != hash {
|
||||
eprintln!("Block hash does not match for block {}. {} vs {}", height, block.hash(), hash);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if height != (self.last_scanned_height() + 1) {
|
||||
eprintln!(
|
||||
"Block is not height-sequential (expected {}, found {})",
|
||||
self.last_scanned_height() + 1,
|
||||
height
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the most recent scanned data.
|
||||
let mut block_data = BlockData {
|
||||
height,
|
||||
hash: block.hash(),
|
||||
tree: self
|
||||
.blocks
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.map(|block| block.tree.clone())
|
||||
.unwrap_or(CommitmentTree::new()),
|
||||
};
|
||||
let mut txs = self.txs.write().unwrap();
|
||||
|
||||
// Create a Vec containing all unspent nullifiers.
|
||||
let nfs: Vec<_> = txs
|
||||
.iter()
|
||||
.map(|(txid, tx)| {
|
||||
let txid = *txid;
|
||||
tx.notes.iter().filter_map(move |nd| {
|
||||
if nd.spent.is_none() {
|
||||
Some((nd.nullifier, nd.account, txid))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
// Prepare the note witnesses for updating
|
||||
for tx in txs.values_mut() {
|
||||
for nd in tx.notes.iter_mut() {
|
||||
// Duplicate the most recent witness
|
||||
if let Some(witness) = nd.witnesses.last() {
|
||||
let clone = witness.clone();
|
||||
nd.witnesses.push(clone);
|
||||
}
|
||||
// Trim the oldest witnesses
|
||||
nd.witnesses = nd
|
||||
.witnesses
|
||||
.split_off(nd.witnesses.len().saturating_sub(100));
|
||||
}
|
||||
}
|
||||
|
||||
let new_txs = {
|
||||
let nf_refs: Vec<_> = nfs.iter().map(|(nf, acc, _)| (&nf[..], *acc)).collect();
|
||||
|
||||
// Create a single mutable slice of all the newly-added witnesses.
|
||||
let mut witness_refs: Vec<_> = txs
|
||||
.values_mut()
|
||||
.map(|tx| tx.notes.iter_mut().filter_map(|nd| nd.witnesses.last_mut()))
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
scan_block(
|
||||
block,
|
||||
&self.extfvks,
|
||||
&nf_refs[..],
|
||||
&mut block_data.tree,
|
||||
&mut witness_refs[..],
|
||||
)
|
||||
};
|
||||
|
||||
for tx in new_txs {
|
||||
// Mark notes as spent.
|
||||
for spend in &tx.shielded_spends {
|
||||
let txid = nfs
|
||||
.iter()
|
||||
.find(|(nf, _, _)| &nf[..] == &spend.nf[..])
|
||||
.unwrap()
|
||||
.2;
|
||||
let mut spent_note = txs
|
||||
.get_mut(&txid)
|
||||
.unwrap()
|
||||
.notes
|
||||
.iter_mut()
|
||||
.find(|nd| &nd.nullifier[..] == &spend.nf[..])
|
||||
.unwrap();
|
||||
spent_note.spent = Some(tx.txid);
|
||||
}
|
||||
|
||||
// Find the existing transaction entry, or create a new one.
|
||||
if !txs.contains_key(&tx.txid) {
|
||||
let tx_entry = WalletTx {
|
||||
block: block_data.height,
|
||||
notes: vec![],
|
||||
};
|
||||
txs.insert(tx.txid, tx_entry);
|
||||
}
|
||||
let tx_entry = txs.get_mut(&tx.txid).unwrap();
|
||||
|
||||
// Save notes.
|
||||
for output in tx
|
||||
.shielded_outputs
|
||||
.into_iter()
|
||||
{
|
||||
tx_entry.notes.push(SaplingNoteData::new(
|
||||
&self.extfvks[output.account],
|
||||
output
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Store scanned data for this block.
|
||||
self.blocks.write().unwrap().push(block_data);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn send_to_address(
|
||||
&self,
|
||||
consensus_branch_id: u32,
|
||||
spend_params: &[u8],
|
||||
output_params: &[u8],
|
||||
to: &str,
|
||||
value: u64,
|
||||
memo: Option<String>,
|
||||
) -> Option<Box<[u8]>> {
|
||||
let start_time = now();
|
||||
println!(
|
||||
"0: Creating transaction sending {} tazoshis to {}",
|
||||
value,
|
||||
to
|
||||
);
|
||||
|
||||
let extsk = &self.extsks[0];
|
||||
let extfvk = &self.extfvks[0];
|
||||
let ovk = extfvk.fvk.ovk;
|
||||
|
||||
let to = match address::RecipientAddress::from_str(to) {
|
||||
Some(to) => to,
|
||||
None => {
|
||||
eprintln!("Invalid recipient address");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let value = Amount::from_u64(value).unwrap();
|
||||
|
||||
// Target the next block, assuming we are up-to-date.
|
||||
let (height, anchor_offset) = match self.get_target_height_and_anchor_offset() {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
eprintln!("Cannot send funds before scanning any blocks");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Select notes to cover the target value
|
||||
println!("{}: Selecting notes", now() - start_time);
|
||||
let target_value = value + DEFAULT_FEE ;
|
||||
let notes: Vec<_> = self
|
||||
.txs
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note)))
|
||||
.flatten()
|
||||
.filter_map(|(txid, note)| SpendableNote::from(txid, note, anchor_offset))
|
||||
.scan(0, |running_total, spendable| {
|
||||
let value = spendable.note.value;
|
||||
let ret = if *running_total < u64::from(target_value) {
|
||||
Some(spendable)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
*running_total = *running_total + value;
|
||||
ret
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Confirm we were able to select sufficient value
|
||||
let selected_value = notes
|
||||
.iter()
|
||||
.map(|selected| selected.note.value)
|
||||
.sum::<u64>();
|
||||
if selected_value < u64::from(target_value) {
|
||||
eprintln!(
|
||||
"Insufficient funds (have {}, need {:?})",
|
||||
selected_value, target_value
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create the transaction
|
||||
println!("{}: Adding {} inputs", now() - start_time, notes.len());
|
||||
let mut builder = Builder::new(height);
|
||||
for selected in notes.iter() {
|
||||
if let Err(e) = builder.add_sapling_spend(
|
||||
extsk.clone(),
|
||||
selected.diversifier,
|
||||
selected.note.clone(),
|
||||
selected.witness.clone(),
|
||||
) {
|
||||
eprintln!("Error adding note: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute memo if it exists
|
||||
let encoded_memo = memo.map(|s| Memo::from_str(&s).unwrap() );
|
||||
|
||||
println!("{}: Adding output", now() - start_time);
|
||||
if let Err(e) = match to {
|
||||
address::RecipientAddress::Shielded(to) => {
|
||||
builder.add_sapling_output(ovk, to.clone(), value, encoded_memo)
|
||||
}
|
||||
address::RecipientAddress::Transparent(to) => {
|
||||
builder.add_transparent_output(&to, value)
|
||||
}
|
||||
} {
|
||||
eprintln!("Error adding output: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
println!("{}: Building transaction", now() - start_time);
|
||||
let (tx, _) = match builder.build(
|
||||
consensus_branch_id,
|
||||
prover::InMemTxProver::new(spend_params, output_params),
|
||||
) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
eprintln!("Error creating transaction: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
println!("{}: Transaction created", now() - start_time);
|
||||
println!("Transaction ID: {}", tx.txid());
|
||||
|
||||
// Mark notes as spent.
|
||||
let mut txs = self.txs.write().unwrap();
|
||||
for selected in notes {
|
||||
let mut spent_note = txs
|
||||
.get_mut(&selected.txid)
|
||||
.unwrap()
|
||||
.notes
|
||||
.iter_mut()
|
||||
.find(|nd| &nd.nullifier[..] == &selected.nullifier[..])
|
||||
.unwrap();
|
||||
spent_note.spent = Some(tx.txid());
|
||||
}
|
||||
|
||||
// Return the encoded transaction, so the caller can send it.
|
||||
let mut raw_tx = vec![];
|
||||
tx.write(&mut raw_tx).unwrap();
|
||||
Some(raw_tx.into_boxed_slice())
|
||||
}
|
||||
}
|
@ -1,24 +1,14 @@
|
||||
|
||||
use futures::Future;
|
||||
use hyper::client::connect::{Destination, HttpConnector};
|
||||
use tower_grpc::Request;
|
||||
use tower_hyper::{client, util};
|
||||
use tower_util::MakeService;
|
||||
use futures::stream::Stream;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
|
||||
mod lightclient;
|
||||
mod lightwallet;
|
||||
mod address;
|
||||
mod prover;
|
||||
mod commands;
|
||||
|
||||
use lightclient::LightClient;
|
||||
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::Editor;
|
||||
|
||||
use crate::grpc_client::{ChainSpec, BlockId, BlockRange};
|
||||
|
||||
pub mod grpc_client {
|
||||
include!(concat!(env!("OUT_DIR"), "/cash.z.wallet.sdk.rpc.rs"));
|
||||
}
|
||||
@ -26,17 +16,19 @@ pub mod grpc_client {
|
||||
|
||||
|
||||
pub fn main() {
|
||||
let light_client = LightClient::new();
|
||||
|
||||
// `()` can be used when no completer is required
|
||||
let mut rl = Editor::<()>::new();
|
||||
if rl.load_history("history.txt").is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
loop {
|
||||
let readline = rl.readline(">> ");
|
||||
let readline = rl.readline(&format!("Block:{} (h for help) >> ", light_client.last_scanned_height()));
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
rl.add_history_entry(line.as_str());
|
||||
do_user_command(line);
|
||||
commands::do_user_command(line, &light_client);
|
||||
},
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
println!("CTRL-C");
|
||||
@ -54,143 +46,3 @@ pub fn main() {
|
||||
}
|
||||
rl.save_history("history.txt").unwrap();
|
||||
}
|
||||
|
||||
pub fn do_user_command(cmd: String) {
|
||||
match cmd.as_ref() {
|
||||
"sync" => { do_sync() }
|
||||
_ => { println!("Unknown command {}", cmd); }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_sync() {
|
||||
let lightclient = Arc::new(lightclient::Client::new());
|
||||
lightclient.set_initial_block(500000,
|
||||
"004fada8d4dbc5e80b13522d2c6bd0116113c9b7197f0c6be69bc7a62f2824cd",
|
||||
"01b733e839b5f844287a6a491409a991ec70277f39a50c99163ed378d23a829a0700100001916db36dfb9a0cf26115ed050b264546c0fa23459433c31fd72f63d188202f2400011f5f4e3bd18da479f48d674dbab64454f6995b113fa21c9d8853a9e764fb3e1f01df9d2c233ca60360e3c2bb73caf5839a1be634c8b99aea22d02abda2e747d9100001970d41722c078288101acd0a75612acfb4c434f2a55aab09fb4e812accc2ba7301485150f0deac7774dcd0fe32043bde9ba2b6bbfff787ad074339af68e88ee70101601324f1421e00a43ef57f197faf385ee4cac65aab58048016ecbd94e022973701e1b17f4bd9d1b6ca1107f619ac6d27b53dd3350d5be09b08935923cbed97906c0000000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39");
|
||||
|
||||
let mut last_scanned_height = lightclient.last_scanned_height() as u64;
|
||||
let mut end_height = last_scanned_height + 1000;
|
||||
|
||||
let latest_block_height = Arc::new(AtomicU64::new(0));
|
||||
|
||||
let latest_block_height_clone = latest_block_height.clone();
|
||||
let latest_block = move |block: BlockId| {
|
||||
latest_block_height_clone.store(block.height, Ordering::SeqCst);
|
||||
};
|
||||
get_latest_block(latest_block);
|
||||
let last_block = latest_block_height.load(Ordering::SeqCst);
|
||||
println!("Latest block = {}", last_block);
|
||||
|
||||
loop {
|
||||
let local_lightclient = lightclient.clone();
|
||||
|
||||
let simple_callback = move |encoded_block: &[u8]| {
|
||||
local_lightclient.scan_block(encoded_block);
|
||||
|
||||
print!("Block Height: {}, Balance = {}\r", local_lightclient.last_scanned_height(), local_lightclient.balance());
|
||||
};
|
||||
|
||||
read_blocks(last_scanned_height, end_height, simple_callback);
|
||||
|
||||
last_scanned_height = end_height + 1;
|
||||
end_height = last_scanned_height + 1000 - 1;
|
||||
|
||||
if last_scanned_height > last_block {
|
||||
break;
|
||||
} else if end_height > last_block {
|
||||
end_height = last_block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_blocks<F : 'static + std::marker::Send>(start_height: u64, end_height: u64, c: F)
|
||||
where F : Fn(&[u8]) {
|
||||
// Fetch blocks
|
||||
let uri: http::Uri = format!("http://127.0.0.1:9067").parse().unwrap();
|
||||
|
||||
let dst = Destination::try_from_uri(uri.clone()).unwrap();
|
||||
let connector = util::Connector::new(HttpConnector::new(4));
|
||||
let settings = client::Builder::new().http2_only(true).clone();
|
||||
let mut make_client = client::Connect::with_builder(connector, settings);
|
||||
|
||||
let say_hello = make_client
|
||||
.make_service(dst)
|
||||
.map_err(|e| panic!("connect error: {:?}", e))
|
||||
.and_then(move |conn| {
|
||||
use crate::grpc_client::client::CompactTxStreamer;
|
||||
|
||||
let conn = tower_request_modifier::Builder::new()
|
||||
.set_origin(uri)
|
||||
.build(conn)
|
||||
.unwrap();
|
||||
|
||||
// Wait until the client is ready...
|
||||
CompactTxStreamer::new(conn)
|
||||
.ready()
|
||||
.map_err(|e| eprintln!("streaming error {:?}", e))
|
||||
})
|
||||
.and_then(move |mut client| {
|
||||
let bs = BlockId{ height: start_height, hash: vec!()};
|
||||
let be = BlockId{ height: end_height, hash: vec!()};
|
||||
|
||||
let br = Request::new(BlockRange{ start: Some(bs), end: Some(be)});
|
||||
client
|
||||
.get_block_range(br)
|
||||
.map_err(|e| {
|
||||
eprintln!("RouteChat request failed; err={:?}", e);
|
||||
})
|
||||
.and_then(move |response| {
|
||||
let inbound = response.into_inner();
|
||||
inbound.for_each(move |b| {
|
||||
use prost::Message;
|
||||
let mut encoded_buf = vec![];
|
||||
|
||||
b.encode(&mut encoded_buf).unwrap();
|
||||
c(&encoded_buf);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| eprintln!("gRPC inbound stream error: {:?}", e))
|
||||
})
|
||||
});
|
||||
|
||||
tokio::run(say_hello);
|
||||
}
|
||||
|
||||
pub fn get_latest_block<F : 'static + std::marker::Send>(mut c : F)
|
||||
where F : FnMut(BlockId) {
|
||||
let uri: http::Uri = format!("http://127.0.0.1:9067").parse().unwrap();
|
||||
|
||||
let dst = Destination::try_from_uri(uri.clone()).unwrap();
|
||||
let connector = util::Connector::new(HttpConnector::new(4));
|
||||
let settings = client::Builder::new().http2_only(true).clone();
|
||||
let mut make_client = client::Connect::with_builder(connector, settings);
|
||||
|
||||
let say_hello = make_client
|
||||
.make_service(dst)
|
||||
.map_err(|e| panic!("connect error: {:?}", e))
|
||||
.and_then(move |conn| {
|
||||
use crate::grpc_client::client::CompactTxStreamer;
|
||||
|
||||
let conn = tower_request_modifier::Builder::new()
|
||||
.set_origin(uri)
|
||||
.build(conn)
|
||||
.unwrap();
|
||||
|
||||
// Wait until the client is ready...
|
||||
CompactTxStreamer::new(conn).ready()
|
||||
})
|
||||
.and_then(|mut client| {
|
||||
|
||||
client.get_latest_block(Request::new(ChainSpec {}))
|
||||
})
|
||||
.and_then(move |response| {
|
||||
c(response.into_inner());
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| {
|
||||
println!("ERR = {:?}", e);
|
||||
});
|
||||
|
||||
tokio::run(say_hello);
|
||||
}
|
@ -2,10 +2,11 @@
|
||||
|
||||
use bellman::groth16::{prepare_verifying_key, Parameters, PreparedVerifyingKey};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use sapling_crypto::{
|
||||
use zcash_primitives::{
|
||||
jubjub::{edwards, fs::Fs, Unknown},
|
||||
primitives::{Diversifier, PaymentAddress, ProofGenerationKey},
|
||||
redjubjub::{PublicKey, Signature},
|
||||
transaction::components::Amount
|
||||
};
|
||||
use zcash_primitives::{
|
||||
merkle_tree::CommitmentTreeWitness, prover::TxProver, sapling::Node,
|
||||
@ -114,7 +115,7 @@ impl TxProver for InMemTxProver {
|
||||
fn binding_sig(
|
||||
&self,
|
||||
ctx: &mut Self::SaplingProvingContext,
|
||||
value_balance: i64,
|
||||
value_balance: Amount,
|
||||
sighash: &[u8; 32],
|
||||
) -> Result<Signature, ()> {
|
||||
ctx.binding_sig(value_balance, sighash, &JUBJUB)
|
||||
|
Loading…
Reference in New Issue
Block a user