[WIP] shield funds PoC

This commit is contained in:
Francisco Gindre 2020-12-22 11:37:11 -03:00
parent 0c9f3cb5ef
commit d75d5cb3ee
5 changed files with 373 additions and 74 deletions

89
Cargo.lock generated
View File

@ -49,9 +49,9 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6789e291be47ace86a60303502173d84af8327e3627ecf334356ee0f87a164c"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "ansi_term"
@ -340,9 +340,9 @@ dependencies = [
[[package]]
name = "const_fn"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
[[package]]
name = "constant_time_eq"
@ -429,7 +429,7 @@ checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "equihash"
version = "0.1.0"
source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
dependencies = [
"blake2b_simd",
"byteorder",
@ -597,14 +597,14 @@ dependencies = [
"lazy_static",
"rand",
"ring",
"secp256k1",
"secp256k1 0.17.2",
]
[[package]]
name = "heck"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
dependencies = [
"unicode-segmentation",
]
@ -674,9 +674,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]]
name = "libsqlite3-sys"
@ -701,8 +701,9 @@ dependencies = [
"hdwallet",
"hex",
"ripemd160",
"secp256k1",
"secp256k1 0.17.2",
"sha2 0.9.2",
"time",
"zcash_client_backend",
"zcash_client_sqlite",
"zcash_primitives",
@ -863,9 +864,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
dependencies = [
"proc-macro2",
]
@ -971,9 +972,9 @@ dependencies = [
[[package]]
name = "rusqlite"
version = "0.24.1"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3d4791ab5517217f51216a84a688b53c1ebf7988736469c538d02f46ddba68"
checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112"
dependencies = [
"bitflags",
"fallible-iterator",
@ -1024,7 +1025,16 @@ version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2932dc07acd2066ff2e3921a4419606b220ba6cd03a9935123856cc534877056"
dependencies = [
"secp256k1-sys",
"secp256k1-sys 0.1.2",
]
[[package]]
name = "secp256k1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6179428c22c73ac0fbb7b5579a56353ce78ba29759b3b8575183336ea74cdfb"
dependencies = [
"secp256k1-sys 0.3.0",
]
[[package]]
@ -1036,6 +1046,15 @@ dependencies = [
"cc",
]
[[package]]
name = "secp256k1-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11553d210db090930f4432bea123b31f70bbf693ace14504ea2a35e796c28dd2"
dependencies = [
"cc",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -1053,18 +1072,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.117"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.117"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2",
"quote",
@ -1115,9 +1134,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]]
name = "spin"
@ -1197,15 +1216,15 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "subtle"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.53"
version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
dependencies = [
"proc-macro2",
"quote",
@ -1287,9 +1306,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.7"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
@ -1326,9 +1345,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "vcpkg"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
[[package]]
name = "vec_map"
@ -1443,7 +1462,7 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "zcash_client_backend"
version = "0.4.0"
source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
dependencies = [
"base64 0.12.3",
"bech32",
@ -1466,7 +1485,7 @@ dependencies = [
[[package]]
name = "zcash_client_sqlite"
version = "0.2.1"
source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
dependencies = [
"bech32",
"bs58",
@ -1484,7 +1503,7 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.4.0"
source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
dependencies = [
"aes",
"bitvec",
@ -1503,6 +1522,8 @@ dependencies = [
"log",
"rand",
"rand_core",
"ripemd160",
"secp256k1 0.19.0",
"sha2 0.9.2",
"subtle",
]
@ -1510,7 +1531,7 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.4.0"
source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
dependencies = [
"bellman",
"blake2b_simd",

View File

@ -13,7 +13,7 @@ ffi_helpers = "0.2"
hex = "0.4"
zcash_client_backend = "0.4"
zcash_client_sqlite = "0.2.1"
zcash_primitives = "0.4"
zcash_primitives = { version = "0.4", features = ["transparent-inputs"] }
#### Temporary additions: ####################################
base58 = "0.1.0"
@ -22,8 +22,10 @@ bs58 = { version = "0.3", features = ["check"] }
hdwallet = "0.2.2"
ripemd160 = "0.9"
secp256k1 = "0.17.2"
time = "0.2"
##############################################################
[dependencies.zcash_proofs]
version = "0.4"
default-features = false
@ -41,10 +43,10 @@ crate-type = ["staticlib"]
lto = true
[patch.crates-io]
zcash_client_backend = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"}
zcash_client_sqlite = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"}
zcash_primitives = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"}
zcash_proofs = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"}
zcash_client_backend = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" }
zcash_client_sqlite = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" }
zcash_primitives = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" }
zcash_proofs = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" }
[features]
mainnet = ["zcash_client_sqlite/mainnet"]

View File

@ -3,6 +3,31 @@
#include <stdint.h>
#include <stdlib.h>
/**
*
* psst, hey I have a Major in Social Sciences. Consider using something else
* Creates a transaction paying the specified address from the given account.
*
* Returns the row index of the newly-created transaction in the `transactions` table
* within the data database. The caller can read the raw transaction bytes from the `raw`
* column in order to broadcast the transaction to the network.
*
* Do not call this multiple times in parallel, or you will generate transactions that
* double-spend the same notes.
*/
int64_t zcashlc_autoshield_funds(const uint8_t *db_data,
uintptr_t db_data_len,
const uint8_t *db_cache,
uintptr_t db_cache_len,
int32_t account,
const char *tsk,
const char *extsk,
const char *memo,
const uint8_t *spend_params,
uintptr_t spend_params_len,
const uint8_t *output_params,
uintptr_t output_params_len);
int32_t zcashlc_branch_id_for_height(int32_t height);
/**

View File

@ -5,10 +5,11 @@ use std::env;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new()
if let Ok(b) = cbindgen::Builder::new()
.with_crate(crate_dir)
.with_language(cbindgen::Language::C)
.generate()
.expect("Unable to generate bindings")
.write_to_file("ZcashLightClientKit/zcashlc/zcashlc.h");
.generate() {
b.write_to_file("ZcashLightClientKit/zcashlc/zcashlc.h");
}
}

View File

@ -25,13 +25,22 @@ use zcash_client_backend::{
use zcash_client_sqlite::{
wallet::init::{init_accounts_table, init_blocks_table, init_data_database},
BlockDB, NoteId, WalletDB,
wallet::{UnspentTransactionOutput, get_utxos},
};
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, BranchId, Parameters},
note_encryption::Memo,
transaction::{components::Amount, Transaction},
zip32::ExtendedFullViewingKey,
transaction::components::{
amount::DEFAULT_FEE,
Amount,
OutPoint,
TxOut
},
legacy::Script,
transaction::builder::Builder,
transaction::Transaction,
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
};
#[cfg(feature = "mainnet")]
@ -49,7 +58,10 @@ use base58::ToBase58;
use sha2::{Digest, Sha256};
// use zcash_primitives::legacy::TransparentAddress;
use hdwallet::{ExtendedPrivKey, KeyIndex};
use secp256k1::{PublicKey, Secp256k1};
use secp256k1::{
Secp256k1,
key::{PublicKey, SecretKey},
};
// use crate::extended_key::{key_index::KeyIndex, ExtendedPrivKey, ExtendedPubKey, KeySeed};
@ -314,15 +326,25 @@ pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_seed(
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let address = spending_key(&seed, NETWORK.coin_type(), account_index)
.default_address()
.unwrap()
.1;
let address_str = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &address);
let address_str = deriveShieldedAddressFromSeed(&NETWORK, &seed, account_index);
Ok(CString::new(address_str).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
fn deriveShieldedAddressFromSeed<P: Parameters>(params: &P, seed: &[u8], account_index: u32) -> String {
let address = spending_key(&seed, NETWORK.coin_type(), account_index)
.default_address()
.unwrap()
.1;
encode_payment_address(params.hrp_sapling_payment_address(), &address)
}
fn deriveShieldedAddressFromSpendingKey<P: Parameters>(params: &P, extsk: &ExtendedSpendingKey) -> String {
let address = extsk.default_address().unwrap().1;
encode_payment_address(params.hrp_sapling_payment_address(), &address)
}
/// derives a shielded address from the given viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
@ -366,17 +388,16 @@ pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key(
Ok(Some(extsk)) => ExtendedFullViewingKey::from(&extsk),
Ok(None) => {
return Err(format_err!("Deriving viewing key from spending key returned no results. Encoding was valid but type was incorrect."));
}
},
Err(e) => {
return Err(format_err!(
"Error while deriving viewing key from spending key: {}",
e
));
}
},
};
let encoded =
encode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &extfvk);
let encoded = encode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &extfvk);
Ok(CString::new(encoded).unwrap().into_raw())
});
@ -456,32 +477,28 @@ pub extern "C" fn zcashlc_get_address(
pub unsafe extern "C" fn zcashlc_is_valid_shielded_address(address: *const c_char) -> bool {
let res = catch_panic(|| {
let addr = CStr::from_ptr(address).to_str()?;
match RecipientAddress::decode(&NETWORK, &addr) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => Ok(true),
RecipientAddress::Transparent(_) => Ok(false),
},
None => Err(format_err!("Address is for the wrong network")),
}
Ok(is_valid_shielded_address(&addr))
});
unwrap_exc_or(res, false)
}
fn is_valid_shielded_address(address: &str) -> bool {
match RecipientAddress::decode(&NETWORK, &address) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => true,
RecipientAddress::Transparent(_) => false,
},
None => false,
}
}
/// Returns true when the address is valid and transparent.
/// Returns false in any other case
#[no_mangle]
pub unsafe extern "C" fn zcashlc_is_valid_transparent_address(address: *const c_char) -> bool {
let res = catch_panic(|| {
let addr = CStr::from_ptr(address).to_str()?;
match RecipientAddress::decode(&NETWORK, &addr) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => Ok(false),
RecipientAddress::Transparent(_) => Ok(true),
},
None => Err(format_err!("Address is for the wrong network")),
}
Ok(is_valid_transparent_address(&addr))
});
unwrap_exc_or(res, false)
}
@ -501,6 +518,16 @@ pub unsafe extern "C" fn zcashlc_is_valid_viewing_key(key: *const c_char) -> boo
});
unwrap_exc_or(res, false)
}
fn is_valid_transparent_address(address: &str) -> bool {
match RecipientAddress::decode(&NETWORK, &address) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => false,
RecipientAddress::Transparent(_) => true,
},
None => false,
}
}
/// Returns the balance for the account, including all unspent notes that we know about.
#[no_mangle]
pub extern "C" fn zcashlc_get_balance(db_data: *const u8, db_data_len: usize, account: i32) -> i64 {
@ -785,17 +812,17 @@ pub extern "C" fn zcashlc_create_to_address(
Ok(Some(extsk)) => extsk,
Ok(None) => {
return Err(format_err!("ExtendedSpendingKey is for the wrong network"));
}
},
Err(e) => {
return Err(format_err!("Invalid ExtendedSpendingKey: {}", e));
}
},
};
let to = match RecipientAddress::decode(&NETWORK, &to) {
Some(to) => to,
None => {
return Err(format_err!("PaymentAddress is for the wrong network"));
}
},
};
let memo = Memo::from_str(&memo).map_err(|_| format_err!("Invalid memo"))?;
@ -931,6 +958,17 @@ pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_seed(
unwrap_exc_or_null(res)
}
fn derive_transparent_address_from_secret_key(secret_key: SecretKey) -> String {
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &secret_key);
let mut hash160 = ripemd160::Ripemd160::new();
hash160.update(Sha256::digest(&pk.serialize()[..].to_vec()));
hash160
.finalize()
.to_base58check(&NETWORK.b58_pubkey_address_prefix(), &[])
}
//
// Helper code from: https://github.com/adityapk00/zecwallet-light-cli/blob/master/lib/src/lightwallet.rs
//
@ -959,4 +997,216 @@ pub fn double_sha256(payload: &[u8]) -> Vec<u8> {
let h1 = Sha256::digest(&payload);
let h2 = Sha256::digest(&h1);
h2.to_vec()
}
//// Extremely Experimental: I'm not even a rust developer
///
/// psst, hey I have a Major in Social Sciences. Consider using something else
/// Creates a transaction paying the specified address from the given account.
///
/// Returns the row index of the newly-created transaction in the `transactions` table
/// within the data database. The caller can read the raw transaction bytes from the `raw`
/// column in order to broadcast the transaction to the network.
///
/// Do not call this multiple times in parallel, or you will generate transactions that
/// double-spend the same notes.
#[no_mangle]
pub extern "C" fn zcashlc_autoshield_funds(
db_data: *const u8,
db_data_len: usize,
db_cache: *const u8,
db_cache_len: usize,
account: i32,
tsk: *const c_char,
extsk: *const c_char,
memo: *const c_char,
spend_params: *const u8,
spend_params_len: usize,
output_params: *const u8,
output_params_len: usize,
) -> i64 {
let res = catch_panic(|| {
let db_data = wallet_db(db_data, db_data_len)?;
let db_cache = block_db(db_cache, db_cache_len)?;
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
let tsk = unsafe { CStr::from_ptr(tsk) }.to_str()?;
let extsk = unsafe { CStr::from_ptr(extsk) }.to_str()?;
let memo = unsafe { CStr::from_ptr(memo) }.to_str()?;
let spend_params = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(spend_params, spend_params_len)
}));
let output_params = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(output_params, output_params_len)
}));
let anchor_and_height = match (&db_data).get_target_and_anchor_heights() {
Ok(Some(h)) => h,
Err(e) => {
return Err(format_err!("Error fetching anchor and target heights: {}", e));
},
};
// grab secret private key for t-funds
let sk = match secp256k1::key::SecretKey::from_str(&tsk) {
Ok(sk) => sk,
Err(e) => {
return Err(format_err!("Invalid Transparent Secret key: {}", e));
},
};
// derive the corresponding t-address
let t_addr_str = derive_transparent_address_from_secret_key(sk);
let t_addr = match RecipientAddress::decode(&NETWORK, &t_addr_str) {
Some(to) => to,
None => {
return Err(format_err!("PaymentAddress is for the wrong network"));
},
};
let extsk =
match decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &extsk)
{
Ok(Some(extsk)) => extsk,
Ok(None) => {
return Err(format_err!("ExtendedSpendingKey is for the wrong network"));
},
Err(e) => {
return Err(format_err!("Invalid ExtendedSpendingKey: {}", e));
},
};
// derive own shielded address from the provided extended spending key
let z_address = extsk.default_address().unwrap().1;
let exfvk = ExtendedFullViewingKey::from(&extsk);
let ovk = exfvk.fvk.ovk;
let memo = match Memo::from_str(&memo) {
Ok(memo) => memo,
};
// get UTXOs from DB
let utxos = match get_utxos(&NETWORK, &db_cache) {
Ok(u) => u
};
// verify that the addresses of the UTXOs are correspond to the given t-address
let distinctAddresses: Vec<UnspentTransactionOutput> = utxos.into_iter().filter(|utxo| utxo.address != t_addr).collect();
if distinctAddresses.len() > 0 {
return Err(format_err!("one or more UTXOs correspond to other addresses that don't match the provided SecretKey"));
}
// check that the utxos are confirmed
let latest_scanned_height = anchor_and_height.1;
let latest_anchor = anchor_and_height.0;
let unconfirmed_funds: Vec<BlockHeight> = utxos.iter().map(|u| u.height).filter(|h| h > &latest_anchor).collect();
if unconfirmed_funds.len() > 0 {
return Err(format_err!("one or more UTXOs are unconfirmed "));
}
let total_amount = utxos.iter().map(|u| u.value).sum();
let fee = DEFAULT_FEE;
let target_value = fee + total_amount;
if fee >= total_amount {
return Err(format_err!("Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.",
u64::from(total_amount), target_value, anchor_and_height.0 + 1));
}
let amount_to_shield = total_amount - fee;
let prover = LocalTxProver::new(spend_params, output_params);
let mut builder = Builder::new(NETWORK, latest_scanned_height);
utxos.iter().map(|utxo| {
let outpoint = OutPoint::new(
utxo.txid.0,
utxo.index as u32
);
let coin = TxOut {
value: utxo.value.clone(),
script_pubkey: utxo.script.clone(),
};
builder.add_transparent_input(sk.clone(), outpoint.clone(), coin.clone())
})
.collect()
.map_err(|e| format_err!("Error adding transparent output {}",e));
// there are no sapling notes so we set the change manually
builder.send_change_to(ovk, z_address);
let recipient = RecipientAddress::Shielded(z_address);
// add the sapling output to shield the funds
builder.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, Some(memo));
let consensus_branch_id = BranchId::for_height(&NETWORK, anchor_and_height.1);
let (tx, tx_metadata) = match builder
.build(consensus_branch_id, &prover) {
Ok(t) => t,
Err(e) => {
return Err(format_err!("Error building transaction {}",e));
},
};
// We only called add_sapling_output() once.
let output_index = match tx_metadata.output_index(0) {
Some(idx) => idx as i64,
None => {
return Err(format_err!("Output 0 should exist in the transaction"));
},
};
// Update the database atomically, to ensure the result is internally consistent.
let mut db_update = (&db_data).get_update_ops().map_err(|e| e.into())?;
db_update
.transactionally(|up| {
let created = time::OffsetDateTime::now_utc();
let tx_ref = up.put_tx_data(&tx, Some(created))?;
// Mark notes as spent.
//
// This locks the notes so they aren't selected again by a subsequent call to
// create_spend_to_address() before this transaction has been mined (at which point the notes
// get re-marked as spent).
//
// Assumes that create_spend_to_address() will never be called in parallel, which is a
// reasonable assumption for a light client such as a mobile phone.
for spend in &tx.shielded_spends {
up.mark_spent(tx_ref, &spend.nullifier)?;
}
up.insert_sent_note(
&NETWORK,
tx_ref,
output_index as usize,
AccountId(account),
&RecipientAddress::from(z_address),
amount_to_shield,
Some(memo),
)?;
// Return the row number of the transaction, so the caller can fetch it for sending.
Ok(tx_ref)
})
.map_err(|e| format_err!("error building updating data_db {}",e))
});
unwrap_exc_or(res, -1)
}