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]]
|
||||
name = "equihash"
|
||||
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 = [
|
||||
"blake2b_simd",
|
||||
"byteorder",
|
||||
|
@ -512,9 +512,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
|
@ -626,9 +626,9 @@ checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
|
@ -1092,9 +1092,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.60"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
|
||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -1222,9 +1222,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.55"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
|
||||
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1462,7 +1462,7 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
|||
[[package]]
|
||||
name = "zcash_client_backend"
|
||||
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 = [
|
||||
"base64 0.12.3",
|
||||
"bech32",
|
||||
|
@ -1485,7 +1485,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_client_sqlite"
|
||||
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 = [
|
||||
"bech32",
|
||||
"bs58",
|
||||
|
@ -1503,7 +1503,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_primitives"
|
||||
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 = [
|
||||
"aes",
|
||||
"bitvec",
|
||||
|
@ -1531,7 +1531,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
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 = [
|
||||
"bellman",
|
||||
"blake2b_simd",
|
||||
|
|
|
@ -237,18 +237,6 @@ int32_t zcashlc_scan_blocks(const uint8_t *db_cache,
|
|||
const uint8_t *db_data,
|
||||
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,
|
||||
uintptr_t db_data_len,
|
||||
const uint8_t *db_cache,
|
||||
|
|
389
rust/src/lib.rs
389
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
|
||||
/// 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]
|
||||
pub extern "C" fn zcashlc_shield_funds(
|
||||
db_data: *const u8,
|
||||
|
@ -1076,192 +1276,7 @@ pub extern "C" fn zcashlc_shield_funds(
|
|||
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,
|
||||
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))
|
||||
shield_funds(&db_cache, &db_data, account, &tsk, &extsk, &memo, &spend_params, &output_params)
|
||||
});
|
||||
unwrap_exc_or(res, -1)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue