split send_funds function into bindgen and pure rust impl
This commit is contained in:
parent
d1a099b26f
commit
228183af23
|
@ -429,7 +429,7 @@ checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equihash"
|
name = "equihash"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
|
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake2b_simd",
|
"blake2b_simd",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -512,9 +512,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "1.0.1"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
|
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
|
@ -626,9 +626,9 @@ checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
|
@ -1092,9 +1092,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.60"
|
version = "1.0.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
|
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -1222,9 +1222,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.55"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
|
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1462,7 +1462,7 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zcash_client_backend"
|
name = "zcash_client_backend"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
|
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.12.3",
|
"base64 0.12.3",
|
||||||
"bech32",
|
"bech32",
|
||||||
|
@ -1485,7 +1485,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zcash_client_sqlite"
|
name = "zcash_client_sqlite"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
|
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bech32",
|
"bech32",
|
||||||
"bs58",
|
"bs58",
|
||||||
|
@ -1503,7 +1503,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zcash_primitives"
|
name = "zcash_primitives"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
|
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bitvec",
|
"bitvec",
|
||||||
|
@ -1531,7 +1531,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zcash_proofs"
|
name = "zcash_proofs"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
|
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bellman",
|
"bellman",
|
||||||
"blake2b_simd",
|
"blake2b_simd",
|
||||||
|
|
|
@ -237,18 +237,6 @@ int32_t zcashlc_scan_blocks(const uint8_t *db_cache,
|
||||||
const uint8_t *db_data,
|
const uint8_t *db_data,
|
||||||
uintptr_t db_data_len);
|
uintptr_t db_data_len);
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* 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_shield_funds(const uint8_t *db_data,
|
int64_t zcashlc_shield_funds(const uint8_t *db_data,
|
||||||
uintptr_t db_data_len,
|
uintptr_t db_data_len,
|
||||||
const uint8_t *db_cache,
|
const uint8_t *db_cache,
|
||||||
|
|
387
rust/src/lib.rs
387
rust/src/lib.rs
|
@ -1042,6 +1042,206 @@ pub fn double_sha256(payload: &[u8]) -> Vec<u8> {
|
||||||
///
|
///
|
||||||
/// Do not call this multiple times in parallel, or you will generate transactions that
|
/// Do not call this multiple times in parallel, or you will generate transactions that
|
||||||
/// double-spend the same notes.
|
/// double-spend the same notes.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
|
fn shield_funds(
|
||||||
|
db_cache: &BlockDB,
|
||||||
|
db_data: &WalletDB,
|
||||||
|
account: u32,
|
||||||
|
tsk: &str,
|
||||||
|
extsk: &str,
|
||||||
|
memo: &str,
|
||||||
|
spend_params: &Path,
|
||||||
|
output_params: &Path,
|
||||||
|
) -> Result<i64,failure::Error> {
|
||||||
|
let anchor_and_height = match (&db_data).get_target_and_anchor_heights() {
|
||||||
|
Ok(Some(h)) => h,
|
||||||
|
Ok(None) => {
|
||||||
|
return Err(format_err!("No anchor and target heights found"));
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(format_err!("Invalid memo input"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// get UTXOs from DB
|
||||||
|
let utxos = match get_utxos(&NETWORK, &db_cache) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format_err!("Error getting UTXOs {}",e));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// verify that the addresses of the UTXOs are correspond to the given t-address
|
||||||
|
let distinct_addresses: Vec<&UnspentTransactionOutput> = utxos.iter().filter(|utxo| utxo.address != t_addr).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if distinct_addresses.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 = match Amount::from_i64(utxos.iter().map(|u| i64::from(u.value)).sum::<i64>()) {
|
||||||
|
Ok(a) => a,
|
||||||
|
_ => {
|
||||||
|
return Err(format_err!("error collecting total amount from UTXOs"));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
|
||||||
|
for utxo in utxos.iter() {
|
||||||
|
let outpoint = OutPoint::new(
|
||||||
|
utxo.txid.0,
|
||||||
|
utxo.index as u32
|
||||||
|
);
|
||||||
|
|
||||||
|
let coin = TxOut {
|
||||||
|
value: utxo.value.clone(),
|
||||||
|
script_pubkey: utxo.script.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match builder.add_transparent_input(sk.clone(), outpoint.clone(), coin.clone()) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format_err!("error adding transparent input {}",e));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are no sapling notes so we set the change manually
|
||||||
|
builder.send_change_to(ovk, z_address.clone());
|
||||||
|
|
||||||
|
// add the sapling output to shield the funds
|
||||||
|
match builder.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, Some(memo.clone())) {
|
||||||
|
Ok(_) =>(),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format_err!("Failed to add sapling output {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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 = match (&db_data).get_update_ops() {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format_err!("error updating database with created tx {}",e));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
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))
|
||||||
|
}
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn zcashlc_shield_funds(
|
pub extern "C" fn zcashlc_shield_funds(
|
||||||
db_data: *const u8,
|
db_data: *const u8,
|
||||||
|
@ -1076,192 +1276,7 @@ pub extern "C" fn zcashlc_shield_funds(
|
||||||
slice::from_raw_parts(output_params, output_params_len)
|
slice::from_raw_parts(output_params, output_params_len)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let anchor_and_height = match (&db_data).get_target_and_anchor_heights() {
|
shield_funds(&db_cache, &db_data, account, &tsk, &extsk, &memo, &spend_params, &output_params)
|
||||||
Ok(Some(h)) => h,
|
|
||||||
Ok(None) => {
|
|
||||||
return Err(format_err!("No anchor and target heights found"));
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
Err(_) => {
|
|
||||||
return Err(format_err!("Invalid memo input"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// get UTXOs from DB
|
|
||||||
let utxos = match get_utxos(&NETWORK, &db_cache) {
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format_err!("Error getting UTXOs {}",e));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// verify that the addresses of the UTXOs are correspond to the given t-address
|
|
||||||
let distinct_addresses: Vec<&UnspentTransactionOutput> = utxos.iter().filter(|utxo| utxo.address != t_addr).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if distinct_addresses.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 = match Amount::from_i64(utxos.iter().map(|u| i64::from(u.value)).sum::<i64>()) {
|
|
||||||
Ok(a) => a,
|
|
||||||
_ => {
|
|
||||||
return Err(format_err!("error collecting total amount from UTXOs"));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
|
|
||||||
for utxo in utxos.iter() {
|
|
||||||
let outpoint = OutPoint::new(
|
|
||||||
utxo.txid.0,
|
|
||||||
utxo.index as u32
|
|
||||||
);
|
|
||||||
|
|
||||||
let coin = TxOut {
|
|
||||||
value: utxo.value.clone(),
|
|
||||||
script_pubkey: utxo.script.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match builder.add_transparent_input(sk.clone(), outpoint.clone(), coin.clone()) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format_err!("error adding transparent input {}",e));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// there are no sapling notes so we set the change manually
|
|
||||||
builder.send_change_to(ovk, z_address.clone());
|
|
||||||
|
|
||||||
// add the sapling output to shield the funds
|
|
||||||
match builder.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, Some(memo.clone())) {
|
|
||||||
Ok(_) =>(),
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format_err!("Failed to add sapling output {}", e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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 = match (&db_data).get_update_ops() {
|
|
||||||
Ok(d) => d,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format_err!("error updating database with created tx {}",e));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
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)
|
unwrap_exc_or(res, -1)
|
||||||
}
|
}
|
Loading…
Reference in New Issue