Implement remainder of FFI interface

This commit is contained in:
Jack Grigg 2019-05-08 14:10:11 +01:00
parent bac9957f2b
commit 80ca214b25
3 changed files with 371 additions and 4 deletions

29
Cargo.lock generated
View File

@ -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>"

View File

@ -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"

View File

@ -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) {