Implement remainder of FFI interface
This commit is contained in:
parent
bac9957f2b
commit
80ca214b25
|
@ -275,6 +275,15 @@ dependencies = [
|
|||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.5"
|
||||
|
@ -426,9 +435,11 @@ dependencies = [
|
|||
"cbindgen 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ffi_helpers 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zcash_client_backend 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
"zcash_client_sqlite 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
"zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
"zcash_proofs 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -972,6 +983,22 @@ dependencies = [
|
|||
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5#ae63116f639847e54716fbf47f19589d12038a16"
|
||||
dependencies = [
|
||||
"bellman 0.1.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
"blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
"pairing 0.14.2 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sapling-crypto 0.0.1 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
"zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum aes 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6fb1737cdc8da3db76e90ca817a194249a38fcb500c2e6ecec39b29448aa873"
|
||||
"checksum aes-soft 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67cc03b0a090a05cb01e96998a01905d7ceedce1bc23b756c0bb7faa0682ccb1"
|
||||
|
@ -1007,6 +1034,7 @@ dependencies = [
|
|||
"checksum crypto_api_chachapoly 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9ee35dbace0831b5fe7cb9b43eb029aa14a10f594a115025d4628a2baa63ab"
|
||||
"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
|
||||
"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
|
||||
"checksum directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72d337a64190607d4fcca2cb78982c5dd57f4916e19696b48a575fa746b6cb0f"
|
||||
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
|
||||
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
|
||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
@ -1088,3 +1116,4 @@ dependencies = [
|
|||
"checksum zcash_client_backend 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)" = "<none>"
|
||||
"checksum zcash_client_sqlite 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)" = "<none>"
|
||||
"checksum zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)" = "<none>"
|
||||
"checksum zcash_proofs 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v5)" = "<none>"
|
||||
|
|
|
@ -8,6 +8,7 @@ build = "rust/build.rs"
|
|||
[dependencies]
|
||||
failure = "0.1"
|
||||
ffi_helpers = "0.1"
|
||||
hex = "0.3"
|
||||
|
||||
[dependencies.zcash_client_backend]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
|
@ -21,6 +22,10 @@ branch = "note-spending-v5"
|
|||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "note-spending-v5"
|
||||
|
||||
[dependencies.zcash_proofs]
|
||||
git = "https://github.com/str4d/librustzcash.git"
|
||||
branch = "note-spending-v5"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.8"
|
||||
|
||||
|
|
341
rust/src/lib.rs
341
rust/src/lib.rs
|
@ -1,16 +1,27 @@
|
|||
use failure::format_err;
|
||||
use ffi_helpers::panic::catch_panic;
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::ffi::{CStr, CString, OsStr};
|
||||
use std::os::raw::c_char;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
use std::slice;
|
||||
use zcash_client_backend::{
|
||||
constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, encoding::encode_extended_spending_key,
|
||||
constants::{testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, SAPLING_CONSENSUS_BRANCH_ID},
|
||||
encoding::{decode_extended_spending_key, encode_extended_spending_key},
|
||||
keys::spending_key,
|
||||
};
|
||||
use zcash_client_sqlite::{get_address, init_accounts_table, init_data_database, ErrorKind};
|
||||
use zcash_primitives::zip32::ExtendedFullViewingKey;
|
||||
use zcash_client_sqlite::{
|
||||
address::RecipientAddress,
|
||||
chain::{rewind_to_height, validate_combined_chain},
|
||||
get_address, get_balance, get_received_memo_as_utf8, get_sent_memo_as_utf8,
|
||||
get_verified_balance, init_accounts_table, init_blocks_table, init_data_database,
|
||||
scan_cached_blocks, send_to_address, ErrorKind,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
block::BlockHash, note_encryption::Memo, transaction::components::Amount,
|
||||
zip32::ExtendedFullViewingKey,
|
||||
};
|
||||
use zcash_proofs::prover::LocalTxProver;
|
||||
|
||||
fn unwrap_exc_or<T>(exc: Result<T, ()>, def: T) -> T {
|
||||
match exc {
|
||||
|
@ -119,6 +130,39 @@ pub extern "C" fn zcashlc_init_accounts_table(
|
|||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// Initialises the data database with the given block.
|
||||
///
|
||||
/// This enables a newly-created database to be immediately-usable, without needing to
|
||||
/// synchronise historic blocks.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_init_blocks_table(
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
height: i32,
|
||||
hash_hex: *const c_char,
|
||||
time: u32,
|
||||
sapling_tree_hex: *const c_char,
|
||||
) -> i32 {
|
||||
let res = catch_panic(|| {
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
let hash = {
|
||||
let mut hash = hex::decode(unsafe { CStr::from_ptr(hash_hex) }.to_str()?).unwrap();
|
||||
hash.reverse();
|
||||
BlockHash::from_slice(&hash)
|
||||
};
|
||||
let sapling_tree =
|
||||
hex::decode(unsafe { CStr::from_ptr(sapling_tree_hex) }.to_str()?).unwrap();
|
||||
|
||||
match init_blocks_table(&db_data, height, hash, time, &sapling_tree) {
|
||||
Ok(()) => Ok(1),
|
||||
Err(e) => Err(format_err!("Error while initializing blocks table: {}", e)),
|
||||
}
|
||||
});
|
||||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// Returns the address for the account.
|
||||
///
|
||||
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
|
||||
|
@ -149,6 +193,295 @@ pub extern "C" fn zcashlc_get_address(
|
|||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let res = catch_panic(|| {
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
let account = if account >= 0 {
|
||||
account as u32
|
||||
} else {
|
||||
return Err(format_err!("account argument must be positive"));
|
||||
};
|
||||
|
||||
match get_balance(&db_data, account) {
|
||||
Ok(balance) => Ok(balance.0),
|
||||
Err(e) => Err(format_err!("Error while fetching balance: {}", e)),
|
||||
}
|
||||
});
|
||||
unwrap_exc_or(res, -1)
|
||||
}
|
||||
|
||||
/// Returns the verified balance for the account, which ignores notes that have been
|
||||
/// received too recently and are not yet deemed spendable.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_get_verified_balance(
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
account: i32,
|
||||
) -> i64 {
|
||||
let res = catch_panic(|| {
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
let account = if account >= 0 {
|
||||
account as u32
|
||||
} else {
|
||||
return Err(format_err!("account argument must be positive"));
|
||||
};
|
||||
|
||||
match get_verified_balance(&db_data, account) {
|
||||
Ok(balance) => Ok(balance.0),
|
||||
Err(e) => Err(format_err!("Error while fetching verified balance: {}", e)),
|
||||
}
|
||||
});
|
||||
unwrap_exc_or(res, -1)
|
||||
}
|
||||
|
||||
/// Returns the memo for a received note, if it is known and a valid UTF-8 string.
|
||||
///
|
||||
/// The note is identified by its row index in the `received_notes` table within the data
|
||||
/// database.
|
||||
///
|
||||
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_get_received_memo_as_utf8(
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
id_note: i64,
|
||||
) -> *mut c_char {
|
||||
let res = catch_panic(|| {
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
|
||||
let memo = match get_received_memo_as_utf8(db_data, id_note) {
|
||||
Ok(memo) => memo.unwrap_or_default(),
|
||||
Err(e) => return Err(format_err!("Error while fetching memo: {}", e)),
|
||||
};
|
||||
|
||||
Ok(CString::new(memo).unwrap().into_raw())
|
||||
});
|
||||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// Returns the memo for a sent note, if it is known and a valid UTF-8 string.
|
||||
///
|
||||
/// The note is identified by its row index in the `sent_notes` table within the data
|
||||
/// database.
|
||||
///
|
||||
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_get_sent_memo_as_utf8(
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
id_note: i64,
|
||||
) -> *mut c_char {
|
||||
let res = catch_panic(|| {
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
|
||||
let memo = match get_sent_memo_as_utf8(db_data, id_note) {
|
||||
Ok(memo) => memo.unwrap_or_default(),
|
||||
Err(e) => return Err(format_err!("Error while fetching memo: {}", e)),
|
||||
};
|
||||
|
||||
Ok(CString::new(memo).unwrap().into_raw())
|
||||
});
|
||||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// Checks that the scanned blocks in the data database, when combined with the recent
|
||||
/// `CompactBlock`s in the cache database, form a valid chain.
|
||||
///
|
||||
/// This function is built on the core assumption that the information provided in the
|
||||
/// cache database is more likely to be accurate than the previously-scanned information.
|
||||
/// This follows from the design (and trust) assumption that the `lightwalletd` server
|
||||
/// provides accurate block information as of the time it was requested.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `-1` if the combined chain is valid.
|
||||
/// - `upper_bound` if the combined chain is invalid.
|
||||
/// `upper_bound` is the height of the highest invalid block (on the assumption that the
|
||||
/// highest block in the cache database is correct).
|
||||
/// - `0` if there was an error during validation unrelated to chain validity.
|
||||
///
|
||||
/// This function does not mutate either of the databases.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_validate_combined_chain(
|
||||
db_cache: *const u8,
|
||||
db_cache_len: usize,
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
) -> i32 {
|
||||
let res = catch_panic(|| {
|
||||
let db_cache = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_cache, db_cache_len)
|
||||
}));
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
|
||||
if let Err(e) = validate_combined_chain(&db_cache, &db_data) {
|
||||
match e.kind() {
|
||||
ErrorKind::InvalidChain(upper_bound, _) => Ok(*upper_bound),
|
||||
_ => Err(format_err!("Error while validating chain: {}", e)),
|
||||
}
|
||||
} else {
|
||||
// All blocks are valid, so "highest invalid block height" is below genesis.
|
||||
Ok(-1)
|
||||
}
|
||||
});
|
||||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// Rewinds the data database to the given height.
|
||||
///
|
||||
/// If the requested height is greater than or equal to the height of the last scanned
|
||||
/// block, this function does nothing.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_rewind_to_height(
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
height: i32,
|
||||
) -> i32 {
|
||||
let res = catch_panic(|| {
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
|
||||
match rewind_to_height(&db_data, height) {
|
||||
Ok(()) => Ok(1),
|
||||
Err(e) => Err(format_err!(
|
||||
"Error while rewinding data DB to height {}: {}",
|
||||
height,
|
||||
e
|
||||
)),
|
||||
}
|
||||
});
|
||||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// Scans new blocks added to the cache for any transactions received by the tracked
|
||||
/// accounts.
|
||||
///
|
||||
/// This function pays attention only to cached blocks with heights greater than the
|
||||
/// highest scanned block in `db_data`. Cached blocks with lower heights are not verified
|
||||
/// against previously-scanned blocks. In particular, this function **assumes** that the
|
||||
/// caller is handling rollbacks.
|
||||
///
|
||||
/// For brand-new light client databases, this function starts scanning from the Sapling
|
||||
/// activation height. This height can be fast-forwarded to a more recent block by calling
|
||||
/// [`zcashlc_init_blocks_table`] before this function.
|
||||
///
|
||||
/// Scanned blocks are required to be height-sequential. If a block is missing from the
|
||||
/// cache, an error will be signalled.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_scan_blocks(
|
||||
db_cache: *const u8,
|
||||
db_cache_len: usize,
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
) -> i32 {
|
||||
let res = catch_panic(|| {
|
||||
let db_cache = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_cache, db_cache_len)
|
||||
}));
|
||||
let db_data = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
|
||||
match scan_cached_blocks(&db_cache, &db_data) {
|
||||
Ok(()) => Ok(1),
|
||||
Err(e) => Err(format_err!("Error while scanning blocks: {}", e)),
|
||||
}
|
||||
});
|
||||
unwrap_exc_or_null(res)
|
||||
}
|
||||
|
||||
/// 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_send_to_address(
|
||||
db_data: *const u8,
|
||||
db_data_len: usize,
|
||||
account: i32,
|
||||
extsk: *const c_char,
|
||||
to: *const c_char,
|
||||
value: i64,
|
||||
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 = Path::new(OsStr::from_bytes(unsafe {
|
||||
slice::from_raw_parts(db_data, db_data_len)
|
||||
}));
|
||||
let account = if account >= 0 {
|
||||
account as u32
|
||||
} else {
|
||||
return Err(format_err!("account argument must be positive"));
|
||||
};
|
||||
let extsk = unsafe { CStr::from_ptr(extsk) }.to_str()?;
|
||||
let to = unsafe { CStr::from_ptr(to) }.to_str()?;
|
||||
let value = Amount(value);
|
||||
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 extsk = match decode_extended_spending_key(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));
|
||||
}
|
||||
};
|
||||
|
||||
let to = match RecipientAddress::from_str(&to) {
|
||||
Ok(Some(to)) => to,
|
||||
Ok(None) => {
|
||||
return Err(format_err!("PaymentAddress is for the wrong network"));
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format_err!("Invalid address: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let memo = Memo::from_str(&memo);
|
||||
|
||||
let prover = LocalTxProver::new(spend_params, output_params);
|
||||
|
||||
send_to_address(
|
||||
&db_data,
|
||||
SAPLING_CONSENSUS_BRANCH_ID,
|
||||
prover,
|
||||
(account, &extsk),
|
||||
&to,
|
||||
value,
|
||||
memo,
|
||||
)
|
||||
.map_err(|e| format_err!("Error while sending funds: {}", e))
|
||||
});
|
||||
unwrap_exc_or(res, -1)
|
||||
}
|
||||
|
||||
/// Frees strings returned by other zcashlc functions.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcashlc_string_free(s: *mut c_char) {
|
||||
|
|
Loading…
Reference in New Issue