From f6d951a8a9fb001c9f1fcc615aceff93ac5893e3 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Sun, 27 Oct 2019 11:08:22 -0700 Subject: [PATCH 01/15] Build armv7 and aaarch64 --- mkrelease.sh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/mkrelease.sh b/mkrelease.sh index 134569f..3b17bba 100755 --- a/mkrelease.sh +++ b/mkrelease.sh @@ -43,7 +43,7 @@ mkdir -p target/macOS-zecwallet-cli-v$APP_VERSION cp target/release/zecwallet-cli target/macOS-zecwallet-cli-v$APP_VERSION/ # For Windows and Linux, build via docker -docker run --rm -v $(pwd)/:/opt/zecwallet-light-cli rustbuild:latest bash -c "cd /opt/zecwallet-light-cli && cargo build --release && SODIUM_LIB_DIR='/opt/libsodium-win64/lib/' cargo build --release --target x86_64-pc-windows-gnu" +docker run --rm -v $(pwd)/:/opt/zecwallet-light-cli rustbuild:latest bash -c "cd /opt/zecwallet-light-cli && cargo build --release && cargo build --release --target armv7-unknown-linux-gnueabihf && cargo build --release --target aarch64-unknown-linux-gnu && SODIUM_LIB_DIR='/opt/libsodium-win64/lib/' cargo build --release --target x86_64-pc-windows-gnu" # Now sign and zip the binaries # macOS @@ -82,4 +82,27 @@ zip -r Windows-zecwallet-cli-v$APP_VERSION.zip Windows-zecwallet-cli-v$APP_VERSI cd .. +#Armv7 +rm -rf target/Armv7-zecwallet-cli-v$APP_VERSION +mkdir -p target/Armv7-zecwallet-cli-v$APP_VERSION +cp target/armv7-unknown-linux-gnueabihf/release/zecwallet-cli target/Armv7-zecwallet-cli-v$APP_VERSION/ +gpg --batch --output target/Armv7-zecwallet-cli-v$APP_VERSION/zecwallet-cli.sig --detach-sig target/Armv7-zecwallet-cli-v$APP_VERSION/zecwallet-cli +cd target +cd Armv7-zecwallet-cli-v$APP_VERSION +gsha256sum zecwallet-cli > sha256sum.txt +cd .. +zip -r Armv7-zecwallet-cli-v$APP_VERSION.zip Armv7-zecwallet-cli-v$APP_VERSION +cd .. + +#AARCH64 +rm -rf target/aarch64-zecwallet-cli-v$APP_VERSION +mkdir -p target/aarch64-zecwallet-cli-v$APP_VERSION +cp target/aarch64-unknown-linux-gnu/release/zecwallet-cli target/aarch64-zecwallet-cli-v$APP_VERSION/ +gpg --batch --output target/aarch64-zecwallet-cli-v$APP_VERSION/zecwallet-cli.sig --detach-sig target/aarch64-zecwallet-cli-v$APP_VERSION/zecwallet-cli +cd target +cd aarch64-zecwallet-cli-v$APP_VERSION +gsha256sum zecwallet-cli > sha256sum.txt +cd .. +zip -r aarch64-zecwallet-cli-v$APP_VERSION.zip aarch64-zecwallet-cli-v$APP_VERSION +cd .. From a8ede64e55f6a2dc05685a7e7fa94cf1f0b33c9d Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Sun, 27 Oct 2019 19:24:08 -0700 Subject: [PATCH 02/15] Store outgoing mempool txns in wallet --- lib/src/lightclient.rs | 25 +++- lib/src/lightwallet.rs | 233 +++++++++++++++++++++++------------ lib/src/lightwallet/tests.rs | 68 +++++++++- 3 files changed, 245 insertions(+), 81 deletions(-) diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index fc8fea2..a3c7f80 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -641,7 +641,8 @@ impl LightClient { pub fn do_list_transactions(&self) -> JsonValue { let wallet = self.wallet.read().unwrap(); - // Create a list of TransactionItems + + // Create a list of TransactionItems from wallet txns let mut tx_list = wallet.txs.read().unwrap().iter() .flat_map(| (_k, v) | { let mut txns: Vec = vec![]; @@ -709,6 +710,28 @@ impl LightClient { }) .collect::>(); + // Add in all mempool txns + tx_list.extend(wallet.mempool_txs.read().unwrap().iter().map( |(_, wtx)| { + let amount = wtx.outgoing_metadata.iter().map(|om| om.value).sum::(); + + // Collect outgoing metadata + let outgoing_json = wtx.outgoing_metadata.iter() + .map(|om| + object!{ + "address" => om.address.clone(), + "value" => om.value, + "memo" => LightWallet::memo_str(&Some(om.memo.clone())), + }).collect::>(); + + object! { + "block_height" => wtx.block, + "datetime" => wtx.datetime, + "txid" => format!("{}", wtx.txid), + "amount" => -1 * amount as i64, + "outgoing_metadata" => outgoing_json, + } + })); + tx_list.sort_by( |a, b| if a["block_height"] == b["block_height"] { a["txid"].as_str().cmp(&b["txid"].as_str()) } else { diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 4376ccd..a9db4c0 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -117,6 +117,10 @@ pub struct LightWallet { blocks: Arc>>, pub txs: Arc>>, + // Transactions that are only in the mempool, but haven't been confirmed yet. + // This is not stored to disk. + pub mempool_txs: Arc>>, + // The block at which this wallet was born. Rescans // will start from here. birthday: u64, @@ -197,20 +201,21 @@ impl LightWallet { = LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0); Ok(LightWallet { - encrypted: false, - unlocked: true, - enc_seed: [0u8; 48], - nonce: vec![], - seed: seed_bytes, - extsks: Arc::new(RwLock::new(vec![extsk])), - extfvks: Arc::new(RwLock::new(vec![extfvk])), - zaddress: Arc::new(RwLock::new(vec![address])), - tkeys: Arc::new(RwLock::new(vec![tpk])), - taddresses: Arc::new(RwLock::new(vec![taddr])), - blocks: Arc::new(RwLock::new(vec![])), - txs: Arc::new(RwLock::new(HashMap::new())), - config: config.clone(), - birthday: latest_block, + encrypted: false, + unlocked: true, + enc_seed: [0u8; 48], + nonce: vec![], + seed: seed_bytes, + extsks: Arc::new(RwLock::new(vec![extsk])), + extfvks: Arc::new(RwLock::new(vec![extfvk])), + zaddress: Arc::new(RwLock::new(vec![address])), + tkeys: Arc::new(RwLock::new(vec![tpk])), + taddresses: Arc::new(RwLock::new(vec![taddr])), + blocks: Arc::new(RwLock::new(vec![])), + txs: Arc::new(RwLock::new(HashMap::new())), + mempool_txs: Arc::new(RwLock::new(HashMap::new())), + config: config.clone(), + birthday: latest_block, }) } @@ -295,19 +300,20 @@ impl LightWallet { let birthday = reader.read_u64::()?; Ok(LightWallet{ - encrypted: encrypted, - unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked. - enc_seed: enc_seed, - nonce: nonce, - seed: seed_bytes, - extsks: Arc::new(RwLock::new(extsks)), - extfvks: Arc::new(RwLock::new(extfvks)), - zaddress: Arc::new(RwLock::new(addresses)), - tkeys: Arc::new(RwLock::new(tkeys)), - taddresses: Arc::new(RwLock::new(taddresses)), - blocks: Arc::new(RwLock::new(blocks)), - txs: Arc::new(RwLock::new(txs)), - config: config.clone(), + encrypted: encrypted, + unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked. + enc_seed: enc_seed, + nonce: nonce, + seed: seed_bytes, + extsks: Arc::new(RwLock::new(extsks)), + extfvks: Arc::new(RwLock::new(extfvks)), + zaddress: Arc::new(RwLock::new(addresses)), + tkeys: Arc::new(RwLock::new(tkeys)), + taddresses: Arc::new(RwLock::new(taddresses)), + blocks: Arc::new(RwLock::new(blocks)), + txs: Arc::new(RwLock::new(txs)), + mempool_txs: Arc::new(RwLock::new(HashMap::new())), + config: config.clone(), birthday, }) } @@ -468,6 +474,7 @@ impl LightWallet { pub fn clear_blocks(&self) { self.blocks.write().unwrap().clear(); self.txs.write().unwrap().clear(); + self.mempool_txs.write().unwrap().clear(); } pub fn set_initial_block(&self, height: i32, hash: &str, sapling_tree: &str) -> bool { @@ -1170,62 +1177,68 @@ impl LightWallet { .map(|block| block.tree.clone()) .unwrap_or(CommitmentTree::new()), }; + + // These are filled in inside the block + let new_txs; + let nfs: Vec<_>; + { + // Create a write lock + let mut txs = self.txs.write().unwrap(); - // Create a write lock that will last for the rest of the function. - let mut txs = self.txs.write().unwrap(); - - // Create a Vec containing all unspent nullifiers. - // Include only the confirmed spent nullifiers, since unconfirmed ones still need to be included - // during scan_block below. - 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 - } + // Create a Vec containing all unspent nullifiers. + // Include only the confirmed spent nullifiers, since unconfirmed ones still need to be included + // during scan_block below. + nfs = 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(); + .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); + // 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)); } - // 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.clone(), - &self.extfvks.read().unwrap(), - &nf_refs[..], - &mut block_data.tree, - &mut witness_refs[..], - ) - }; + 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.clone(), + &self.extfvks.read().unwrap(), + &nf_refs[..], + &mut block_data.tree, + &mut witness_refs[..], + ) + }; + } + // If this block had any new Txs, return the list of ALL txids in this block, // so the wallet can fetch them all as a decoy. let all_txs = if !new_txs.is_empty() { @@ -1239,6 +1252,9 @@ impl LightWallet { }; for tx in new_txs { + // Create a write lock + let mut txs = self.txs.write().unwrap(); + // Mark notes as spent. let mut total_shielded_value_spent: u64 = 0; @@ -1301,6 +1317,11 @@ impl LightWallet { } } + { + // Cleanup mempool tx after adding a block, to remove all txns that got mined + self.cleanup_mempool(); + } + // Print info about the block every 10,000 blocks if height % 10_000 == 0 { match self.get_sapling_tree() { @@ -1309,7 +1330,6 @@ impl LightWallet { } } - Ok(all_txs) } @@ -1347,7 +1367,7 @@ impl LightWallet { ); // Convert address (str) to RecepientAddress and value to Amount - let tos = tos.iter().map(|to| { + let recepients = tos.iter().map(|to| { let ra = match address::RecipientAddress::from_str(to.0, self.config.hrp_sapling_address(), self.config.base58_pubkey_address(), @@ -1482,7 +1502,7 @@ impl LightWallet { // TODO: We're using the first ovk to encrypt outgoing Txns. Is that Ok? let ovk = self.extfvks.read().unwrap()[0].fvk.ovk; - for (to, value, memo) in tos { + for (to, value, memo) in recepients { // Compute memo if it exists let encoded_memo = memo.map(|s| Memo::from_str(&s).unwrap()); @@ -1539,11 +1559,66 @@ impl LightWallet { } } + // Add this Tx to the mempool structure + { + let mut mempool_txs = self.mempool_txs.write().unwrap(); + + match mempool_txs.get_mut(&tx.txid()) { + None => { + // Collect the outgoing metadata + let outgoing_metadata = tos.iter().map(|(addr, amt, maybe_memo)| { + OutgoingTxMetadata { + address: addr.to_string(), + value: *amt, + memo: match maybe_memo { + None => Memo::default(), + Some(s) => Memo::from_str(&s).unwrap(), + }, + } + }).collect::>(); + + // Create a new WalletTx + let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid()); + wtx.outgoing_metadata = outgoing_metadata; + + // Add it into the mempool + mempool_txs.insert(tx.txid(), wtx); + }, + Some(_) => { + warn!("A newly created Tx was already in the mempool! How's that possible? Txid: {}", tx.txid()); + } + } + } + // Return the encoded transaction, so the caller can send it. let mut raw_tx = vec![]; tx.write(&mut raw_tx).unwrap(); Ok(raw_tx.into_boxed_slice()) } + + // After some blocks have been mined, we need to remove the Txns from the mempool_tx structure + // if they : + // 1. Have expired + // 2. The Tx has been added to the wallet via a mined block + pub fn cleanup_mempool(&self) { + const DEFAULT_TX_EXPIRY_DELTA: i32 = 20; + + let current_height = self.blocks.read().unwrap().last().map(|b| b.height).unwrap_or(0); + + { + // Remove all expired Txns + self.mempool_txs.write().unwrap().retain( | _, wtx| { + current_height < (wtx.block + DEFAULT_TX_EXPIRY_DELTA) + }); + } + + { + // Remove all txns where the txid is added to the wallet directly + self.mempool_txs.write().unwrap().retain ( |txid, _| { + self.txs.read().unwrap().get(txid).is_none() + }); + } + } } #[cfg(test)] diff --git a/lib/src/lightwallet/tests.rs b/lib/src/lightwallet/tests.rs index 469d022..2997694 100644 --- a/lib/src/lightwallet/tests.rs +++ b/lib/src/lightwallet/tests.rs @@ -690,7 +690,7 @@ fn get_test_wallet(amount: u64) -> (LightWallet, TxId, BlockHash) { } #[test] -fn test_z_spend() { +fn test_z_spend_to_z() { const AMOUNT1: u64 = 50000; let (wallet, txid1, block_hash) = get_test_wallet(AMOUNT1); @@ -723,6 +723,19 @@ fn test_z_spend() { assert_eq!(txs[&txid1].notes[0].unconfirmed_spent, Some(sent_txid)); } + // It should also be in the mempool structure + { + let mem = wallet.mempool_txs.read().unwrap(); + + assert_eq!(mem[&sent_txid].block, 2); // block number is next block + assert! (mem[&sent_txid].datetime > 0); + assert_eq!(mem[&sent_txid].txid, sent_txid); + assert_eq!(mem[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].address, ext_address); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + let mut cb3 = FakeCompactBlock::new(2, block_hash); cb3.add_tx(&sent_tx); wallet.scan_block(&cb3.as_bytes()).unwrap(); @@ -743,6 +756,12 @@ fn test_z_spend() { assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); } + { + // And the mempool tx should disappear + let mem = wallet.mempool_txs.read().unwrap(); + assert!(mem.get(&sent_txid).is_none()); + } + // Now, full scan the Tx, which should populate the Outgoing Meta data wallet.scan_full_tx(&sent_tx, 2, 0); @@ -1448,6 +1467,53 @@ fn add_blocks(wallet: &LightWallet, start: i32, num: i32, mut prev_hash: BlockHa Ok(new_blk.hash()) } +#[test] +fn test_z_mempool_expiry() { + const AMOUNT1: u64 = 50000; + let (wallet, _, block_hash) = get_test_wallet(AMOUNT1); + + let fvk = ExtendedFullViewingKey::from(&ExtendedSpendingKey::master(&[1u8; 32])); + let ext_address = encode_payment_address(wallet.config.hrp_sapling_address(), + &fvk.default_address().unwrap().1); + + const AMOUNT_SENT: u64 = 20; + + let outgoing_memo = "Outgoing Memo".to_string(); + + let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); + let (ss, so) = get_sapling_params().unwrap(); + + // Create a tx and send to address + let raw_tx = wallet.send_to_address(branch_id, &ss, &so, + vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))]).unwrap(); + + let sent_tx = Transaction::read(&raw_tx[..]).unwrap(); + let sent_txid = sent_tx.txid(); + + // It should also be in the mempool structure + { + let mem = wallet.mempool_txs.read().unwrap(); + + assert_eq!(mem[&sent_txid].block, 2); // block number is next block + assert! (mem[&sent_txid].datetime > 0); + assert_eq!(mem[&sent_txid].txid, sent_txid); + assert_eq!(mem[&sent_txid].outgoing_metadata.len(), 1); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].address, ext_address); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].value, AMOUNT_SENT); + assert_eq!(mem[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); + } + + // Don't mine the Tx, but just add several blocks + add_blocks(&wallet, 2, 21, block_hash).unwrap(); + + // After 21 blocks, it should disappear (expiry is 20 blocks) since it was not mined + { + let mem = wallet.mempool_txs.read().unwrap(); + + assert!(mem.get(&sent_txid).is_none()); + } +} + #[test] fn test_block_limit() { const AMOUNT: u64 = 500000; From 01cd0269d558f8755ddc720dc26b81767a773cde Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Sun, 27 Oct 2019 20:53:38 -0700 Subject: [PATCH 03/15] Add fee to mempool tx --- lib/src/lightclient.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index a3c7f80..a3c280e 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -712,7 +712,11 @@ impl LightClient { // Add in all mempool txns tx_list.extend(wallet.mempool_txs.read().unwrap().iter().map( |(_, wtx)| { - let amount = wtx.outgoing_metadata.iter().map(|om| om.value).sum::(); + use zcash_primitives::transaction::components::amount::DEFAULT_FEE; + use std::convert::TryInto; + + let amount: u64 = wtx.outgoing_metadata.iter().map(|om| om.value).sum::(); + let fee: u64 = DEFAULT_FEE.try_into().unwrap(); // Collect outgoing metadata let outgoing_json = wtx.outgoing_metadata.iter() @@ -727,7 +731,8 @@ impl LightClient { "block_height" => wtx.block, "datetime" => wtx.datetime, "txid" => format!("{}", wtx.txid), - "amount" => -1 * amount as i64, + "amount" => -1 * (fee + amount) as i64, + "unconfirmed" => true, "outgoing_metadata" => outgoing_json, } })); From c6631b658af284cdf860e021dd9987b5b5d1bff1 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Mon, 28 Oct 2019 10:41:46 -0700 Subject: [PATCH 04/15] Remove pending spent amount from balance --- lib/src/lightwallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index a9db4c0..71d1005 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -767,7 +767,7 @@ impl LightWallet { None => true } }) - .map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 }) + .map(|nd| if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { nd.note.value } else { 0 }) .sum::() }) .sum::() From 05bf4a645959e8cafef6581fa6b4f9add538cb55 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Tue, 29 Oct 2019 10:13:34 -0700 Subject: [PATCH 05/15] Add check for balance/zbalance --- lib/src/lightwallet/tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/src/lightwallet/tests.rs b/lib/src/lightwallet/tests.rs index 2997694..69fdbae 100644 --- a/lib/src/lightwallet/tests.rs +++ b/lib/src/lightwallet/tests.rs @@ -706,6 +706,12 @@ fn test_z_spend_to_z() { let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); let (ss, so) = get_sapling_params().unwrap(); + // Make sure that the balance exists + { + assert_eq!(wallet.zbalance(None), AMOUNT1); + assert_eq!(wallet.verified_zbalance(None), AMOUNT1); + } + // Create a tx and send to address let raw_tx = wallet.send_to_address(branch_id, &ss, &so, vec![(&ext_address, AMOUNT_SENT, Some(outgoing_memo.clone()))]).unwrap(); @@ -736,6 +742,12 @@ fn test_z_spend_to_z() { assert_eq!(mem[&sent_txid].outgoing_metadata[0].memo.to_utf8().unwrap().unwrap(), outgoing_memo); } + { + // The wallet should deduct this from the balance and verified balance + assert_eq!(wallet.zbalance(None), 0); + assert_eq!(wallet.verified_zbalance(None), 0); + } + let mut cb3 = FakeCompactBlock::new(2, block_hash); cb3.add_tx(&sent_tx); wallet.scan_block(&cb3.as_bytes()).unwrap(); From 25c1e351920963a823cb541fad89c19610de95d6 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Tue, 29 Oct 2019 10:13:48 -0700 Subject: [PATCH 06/15] Build docker image on ubuntu 1604 --- docker/Dockerfile | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index abe4b05..00c4027 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,35 +1,41 @@ -FROM rust:1.38 +FROM ubuntu:16.04 LABEL Description="Rust compile env for Linux + Windows (cross)" RUN apt update -RUN apt install -y build-essential mingw-w64 gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf +RUN apt install -y build-essential mingw-w64 gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf curl vim wget + +# Get Rust +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" RUN rustup target add x86_64-pc-windows-gnu RUN rustup target add aarch64-unknown-linux-gnu RUN rustup target add armv7-unknown-linux-gnueabihf # Append the linker to the cargo config for Windows cross compile -RUN echo "[target.x86_64-pc-windows-gnu]" >> /usr/local/cargo/config && \ - echo "linker = '/usr/bin/x86_64-w64-mingw32-gcc'" >> /usr/local/cargo/config +RUN echo "[target.x86_64-pc-windows-gnu]" >> /root/.cargo/config && \ + echo "linker = '/usr/bin/x86_64-w64-mingw32-gcc'" >> /root/.cargo/config -RUN echo "[target.aarch64-unknown-linux-gnu]" >> /usr/local/cargo/config && \ - echo "linker = '/usr/bin/aarch64-linux-gnu-gcc'" >> /usr/local/cargo/config +RUN echo "[target.aarch64-unknown-linux-gnu]" >> /root/.cargo/config && \ + echo "linker = '/usr/bin/aarch64-linux-gnu-gcc'" >> /root/.cargo/config -RUN echo "[target.armv7-unknown-linux-gnueabihf]" >> /usr/local/cargo/config && \ - echo "linker = '/usr/bin/arm-linux-gnueabihf-gcc'" >> /usr/local/cargo/config +RUN echo "[target.armv7-unknown-linux-gnueabihf]" >> /root/.cargo/config && \ + echo "linker = '/usr/bin/arm-linux-gnueabihf-gcc'" >> /root/.cargo/config ENV CC_x86_64_unknown_linux_musl="gcc" ENV CC_aarch64_unknown_linux_gnu="aarch64-linux-gnu-gcc" ENV CC_armv7_unknown_linux_gnueabhihf="arm-linux-gnueabihf-gcc" # This is a bug fix for the windows cross compiler for Rust. -RUN cp /usr/x86_64-w64-mingw32/lib/crt2.o /usr/local/rustup/toolchains/1.38.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o +RUN cp /usr/x86_64-w64-mingw32/lib/crt2.o /root/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o # For windows cross compilation, use a pre-build binary. Remember to set the # SODIUM_LIB_DIR for windows cross compilation RUN cd /opt && wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.17-mingw.tar.gz && \ tar xvf libsodium-1.0.17-mingw.tar.gz +RUN apt install -y git + # Cargo fetch the dependencies so we don't download them over and over again RUN cd /tmp && git clone https://github.com/adityapk00/zecwallet-light-cli.git && \ cd zecwallet-light-cli && \ From 8504a61225fd6d9bf326e0ef11389abda7683738 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Tue, 29 Oct 2019 14:44:59 -0700 Subject: [PATCH 07/15] Allow escaped chars --- lib/src/commands.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/commands.rs b/lib/src/commands.rs index da91bde..46f2635 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -440,9 +440,7 @@ impl Command for SendCommand { // Check for a single argument that can be parsed as JSON let send_args = if args.len() == 1 { - // Sometimes on the command line, people use "'" for the quotes, which json::parse doesn't - // understand. So replace it with double-quotes - let arg_list = args[0].replace("'", "\""); + let arg_list = args[0]; let json_args = match json::parse(&arg_list) { Ok(j) => j, From 0a1f672ebc3cb6753c9e36171c54a364dd2794a4 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 13:52:13 -0700 Subject: [PATCH 08/15] Get wallet encryption status --- lib/src/commands.rs | 63 ++++++++++++++++++++++++++++-------------- lib/src/lightclient.rs | 8 ++++++ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/lib/src/commands.rs b/lib/src/commands.rs index da91bde..b7f1ea7 100644 --- a/lib/src/commands.rs +++ b/lib/src/commands.rs @@ -35,6 +35,26 @@ impl Command for SyncCommand { } } +struct EncryptionStatusCommand {} +impl Command for EncryptionStatusCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Check if the wallet is encrypted and if it is locked"); + h.push("Usage:"); + h.push("encryptionstatus"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Check if the wallet is encrypted and if it is locked".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + lightclient.do_encryption_status().pretty(2) + } +} struct SyncStatusCommand {} impl Command for SyncStatusCommand { @@ -731,27 +751,28 @@ impl Command for QuitCommand { pub fn get_commands() -> Box>> { let mut map: HashMap> = HashMap::new(); - map.insert("sync".to_string(), Box::new(SyncCommand{})); - map.insert("syncstatus".to_string(), Box::new(SyncStatusCommand{})); - map.insert("rescan".to_string(), Box::new(RescanCommand{})); - map.insert("help".to_string(), Box::new(HelpCommand{})); - map.insert("balance".to_string(), Box::new(BalanceCommand{})); - map.insert("addresses".to_string(), Box::new(AddressCommand{})); - map.insert("height".to_string(), Box::new(HeightCommand{})); - map.insert("export".to_string(), Box::new(ExportCommand{})); - map.insert("info".to_string(), Box::new(InfoCommand{})); - map.insert("send".to_string(), Box::new(SendCommand{})); - map.insert("save".to_string(), Box::new(SaveCommand{})); - map.insert("quit".to_string(), Box::new(QuitCommand{})); - map.insert("list".to_string(), Box::new(TransactionsCommand{})); - map.insert("notes".to_string(), Box::new(NotesCommand{})); - map.insert("new".to_string(), Box::new(NewAddressCommand{})); - map.insert("seed".to_string(), Box::new(SeedCommand{})); - map.insert("encrypt".to_string(), Box::new(EncryptCommand{})); - map.insert("decrypt".to_string(), Box::new(DecryptCommand{})); - map.insert("unlock".to_string(), Box::new(UnlockCommand{})); - map.insert("lock".to_string(), Box::new(LockCommand{})); - map.insert("fixbip39bug".to_string(), Box::new(FixBip39BugCommand{})); + map.insert("sync".to_string(), Box::new(SyncCommand{})); + map.insert("syncstatus".to_string(), Box::new(SyncStatusCommand{})); + map.insert("encryptionstatus".to_string(), Box::new(EncryptionStatusCommand{})); + map.insert("rescan".to_string(), Box::new(RescanCommand{})); + map.insert("help".to_string(), Box::new(HelpCommand{})); + map.insert("balance".to_string(), Box::new(BalanceCommand{})); + map.insert("addresses".to_string(), Box::new(AddressCommand{})); + map.insert("height".to_string(), Box::new(HeightCommand{})); + map.insert("export".to_string(), Box::new(ExportCommand{})); + map.insert("info".to_string(), Box::new(InfoCommand{})); + map.insert("send".to_string(), Box::new(SendCommand{})); + map.insert("save".to_string(), Box::new(SaveCommand{})); + map.insert("quit".to_string(), Box::new(QuitCommand{})); + map.insert("list".to_string(), Box::new(TransactionsCommand{})); + map.insert("notes".to_string(), Box::new(NotesCommand{})); + map.insert("new".to_string(), Box::new(NewAddressCommand{})); + map.insert("seed".to_string(), Box::new(SeedCommand{})); + map.insert("encrypt".to_string(), Box::new(EncryptCommand{})); + map.insert("decrypt".to_string(), Box::new(DecryptCommand{})); + map.insert("unlock".to_string(), Box::new(UnlockCommand{})); + map.insert("lock".to_string(), Box::new(LockCommand{})); + map.insert("fixbip39bug".to_string(), Box::new(FixBip39BugCommand{})); Box::new(map) } diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index a3c280e..823720d 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -639,6 +639,14 @@ impl LightClient { res } + pub fn do_encryption_status(&self) -> JsonValue { + let wallet = self.wallet.read().unwrap(); + object!{ + "encrypted" => wallet.is_encrypted(), + "locked" => !wallet.is_unlocked_for_spending() + } + } + pub fn do_list_transactions(&self) -> JsonValue { let wallet = self.wallet.read().unwrap(); From 50d331b0cfe1b3c4b81e33fd6febb4b24264627a Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 17:08:58 -0700 Subject: [PATCH 09/15] Don't remove unconfirmed txs from balance --- lib/src/lightwallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 71d1005..a9db4c0 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -767,7 +767,7 @@ impl LightWallet { None => true } }) - .map(|nd| if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { nd.note.value } else { 0 }) + .map(|nd| if nd.spent.is_none() { nd.note.value } else { 0 }) .sum::() }) .sum::() From cacccf752d6260250e0b5de6b3ea8112950cda06 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 17:21:58 -0700 Subject: [PATCH 10/15] Update num confirmations to 5 --- README.md | 2 +- lib/src/lightwallet.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7072890..e8f81db 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Run `zecwallet-cli help` to see a list of all commands. ### Note Management Zecwallet-CLI does automatic note and utxo management, which means it doesn't allow you to manually select which address to send outgoing transactions from. It follows these principles: * Defaults to sending shielded transactions, even if you're sending to a transparent address -* Sapling funds need at least 4 confirmations before they can be spent +* Sapling funds need at least 5 confirmations before they can be spent * Can select funds from multiple shielded addresses in the same transaction * Will automatically shield your transparent funds at the first opportunity * When sending an outgoing transaction to a shielded address, Zecwallet-CLI can decide to use the transaction to additionally shield your transparent funds (i.e., send your transparent funds to your own shielded address in the same transaction) diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index a9db4c0..ec1f91c 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -1468,7 +1468,7 @@ impl LightWallet { if selected_value < u64::from(target_value) { let e = format!( "Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.", - selected_value, target_value, self.config.anchor_offset + selected_value, target_value, self.config.anchor_offset + 1 ); error!("{}", e); return Err(e); From 3e1c61a4b0589be1ff7590cf4ddf025a9160c631 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Wed, 30 Oct 2019 17:38:18 -0700 Subject: [PATCH 11/15] Fix tests --- lib/src/lightwallet/tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/lightwallet/tests.rs b/lib/src/lightwallet/tests.rs index 69fdbae..ac8b5ff 100644 --- a/lib/src/lightwallet/tests.rs +++ b/lib/src/lightwallet/tests.rs @@ -743,8 +743,8 @@ fn test_z_spend_to_z() { } { - // The wallet should deduct this from the balance and verified balance - assert_eq!(wallet.zbalance(None), 0); + // The wallet should deduct this from the verified balance. The zbalance still includes it + assert_eq!(wallet.zbalance(None), AMOUNT1); assert_eq!(wallet.verified_zbalance(None), 0); } @@ -763,6 +763,7 @@ fn test_z_spend_to_z() { // The sent tx should generate change assert_eq!(txs[&sent_txid].notes.len(), 1); assert_eq!(txs[&sent_txid].notes[0].note.value, AMOUNT1 - AMOUNT_SENT - fee); + assert_eq!(wallet.zbalance(None), AMOUNT1 - AMOUNT_SENT - fee); assert_eq!(txs[&sent_txid].notes[0].is_change, true); assert_eq!(txs[&sent_txid].notes[0].spent, None); assert_eq!(txs[&sent_txid].notes[0].unconfirmed_spent, None); From 143651cec1b8574c5332f726b4d5b18ecd5b4832 Mon Sep 17 00:00:00 2001 From: DenioD Date: Thu, 31 Oct 2019 15:36:43 +0100 Subject: [PATCH 12/15] change conf to 2 --- lib/src/lightwallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 5bd8862..b76e242 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -1471,7 +1471,7 @@ impl LightWallet { if selected_value < u64::from(target_value) { let e = format!( "Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.", - selected_value, target_value, self.config.anchor_offset + 1 + selected_value, target_value, self.config.anchor_offset - 2 ); error!("{}", e); return Err(e); From 54143f63b1c1db3e2bbf14177fe2720f5c1a7ca0 Mon Sep 17 00:00:00 2001 From: DenioD Date: Thu, 31 Oct 2019 16:15:23 +0100 Subject: [PATCH 13/15] add +2 conf in readme. Set anchor_offset to 2 --- README.md | 2 +- lib/src/lib.rs | 2 +- lib/src/lightwallet.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e87dfc..3b4dd1e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Run `silentdragonlite-cli help` to see a list of all commands. ### Note Management silentdragonlite does automatic note and utxo management, which means it doesn't allow you to manually select which address to send outgoing transactions from. It follows these principles: * Defaults to sending shielded transactions, even if you're sending to a transparent address -* Sapling funds need at least 5 confirmations before they can be spent +* Sapling funds need at least 2 confirmations before they can be spent * Can select funds from multiple shielded addresses in the same transaction * Will automatically shield your transparent funds at the first opportunity * When sending an outgoing transaction to a shielded address, silentdragonlite can decide to use the transaction to additionally shield your transparent funds (i.e., send your transparent funds to your own shielded address in the same transaction) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 3e4fe27..5728ae7 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -11,7 +11,7 @@ pub mod commands; #[folder = "zcash-params/"] pub struct SaplingParams; -pub const ANCHOR_OFFSET: u32 = 4; +pub const ANCHOR_OFFSET: u32 = 2; pub mod grpc_client { diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index b76e242..6eb81f8 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -1471,7 +1471,7 @@ impl LightWallet { if selected_value < u64::from(target_value) { let e = format!( "Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.", - selected_value, target_value, self.config.anchor_offset - 2 + selected_value, target_value, self.config.anchor_offset ); error!("{}", e); return Err(e); From 01da2e08d62c1c56b4954da4481878e6de337819 Mon Sep 17 00:00:00 2001 From: DenioD Date: Thu, 31 Oct 2019 17:16:57 +0100 Subject: [PATCH 14/15] set z_verified to anchor_offset --- lib/src/lightwallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 6eb81f8..929ecb1 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -802,7 +802,7 @@ impl LightWallet { pub fn verified_zbalance(&self, addr: Option) -> u64 { let anchor_height = match self.get_target_height_and_anchor_offset() { - Some((height, anchor_offset)) => height - anchor_offset as u32 - 1, + Some((height, anchor_offset)) => height - anchor_offset as u32 , None => return 0, }; From bc9bef6ec16b9c852134b1f3f54f53ed49114a86 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Thu, 31 Oct 2019 14:56:45 -0700 Subject: [PATCH 15/15] Handle resolution errors at startup --- lib/src/lightclient.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 823720d..1fd62a1 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -75,6 +75,13 @@ impl LightClientConfig { } pub fn create(server: http::Uri, dangerous: bool) -> io::Result<(LightClientConfig, u64)> { + use std::net::ToSocketAddrs; + // Test for a connection first + format!("{}:{}", server.host().unwrap(), server.port_part().unwrap()) + .to_socket_addrs()? + .next() + .ok_or(std::io::Error::new(ErrorKind::ConnectionRefused, "Couldn't resolve server!"))?; + // Do a getinfo first, before opening the wallet let info = grpcconnector::get_info(server.clone(), dangerous) .map_err(|e| std::io::Error::new(ErrorKind::ConnectionRefused, e))?;