zcash-android-wallet-sdk/src/main/rust/lib.rs

221 lines
6.5 KiB
Rust
Raw Normal View History

#[macro_use]
extern crate log;
2018-11-22 05:54:25 -08:00
extern crate rusqlite;
2018-11-21 08:40:18 -08:00
extern crate zcash_client_backend;
extern crate zip32;
use rusqlite::{types::ToSql, Connection, NO_PARAMS};
2018-11-21 08:40:18 -08:00
use zcash_client_backend::{
address::encode_payment_address, constants::HRP_SAPLING_EXTENDED_SPENDING_KEY_TEST,
2018-11-22 05:54:25 -08:00
welding_rig::scan_block_from_bytes,
2018-11-21 08:40:18 -08:00
};
use zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey};
2018-11-21 08:40:18 -08:00
fn extfvk_from_seed(seed: &[u8]) -> ExtendedFullViewingKey {
2018-11-21 08:40:18 -08:00
let master = ExtendedSpendingKey::master(seed);
let extsk = ExtendedSpendingKey::from_path(
&master,
&[
ChildIndex::Hardened(32),
ChildIndex::Hardened(1),
ChildIndex::Hardened(0),
],
);
ExtendedFullViewingKey::from(&extsk)
}
fn address_from_extfvk(extfvk: &ExtendedFullViewingKey) -> String {
let addr = extfvk.default_address().unwrap().1;
2018-11-21 08:40:18 -08:00
encode_payment_address(HRP_SAPLING_EXTENDED_SPENDING_KEY_TEST, &addr)
}
fn init_data_database(db_data: &str) -> rusqlite::Result<()> {
let data = Connection::open(db_data)?;
data.execute(
"CREATE TABLE IF NOT EXISTS blocks (
height INTEGER PRIMARY KEY,
time INTEGER
)",
NO_PARAMS,
)?;
data.execute(
"CREATE TABLE IF NOT EXISTS transactions (
id_tx INTEGER PRIMARY KEY,
txid BLOB NOT NULL UNIQUE,
block INTEGER NOT NULL,
FOREIGN KEY (block) REFERENCES blocks(height)
)",
NO_PARAMS,
)?;
data.execute(
"CREATE TABLE IF NOT EXISTS received_notes (
id_note INTEGER PRIMARY KEY,
tx INTEGER NOT NULL,
output_index INTEGER NOT NULL,
value INTEGER NOT NULL,
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
CONSTRAINT tx_output UNIQUE (tx, output_index)
)",
NO_PARAMS,
)?;
Ok(())
}
2018-11-22 05:54:25 -08:00
struct CompactBlockRow {
height: i32,
data: Vec<u8>,
}
/// Scans new blocks added to the cache for any transactions received by the given
/// ExtendedFullViewingKeys.
///
/// Assumes that the caller is handling rollbacks.
2018-11-22 05:54:25 -08:00
fn scan_cached_blocks(
db_cache: &str,
db_data: &str,
2018-11-22 05:54:25 -08:00
extfvks: &[ExtendedFullViewingKey],
) -> rusqlite::Result<()> {
let cache = Connection::open(db_cache)?;
let data = Connection::open(db_data)?;
// Recall where we synced up to previously.
// If we have never synced, use 0 to select all cached CompactBlocks.
let mut last_height =
data.query_row(
"SELECT MAX(height) FROM blocks",
NO_PARAMS,
|row| match row.get_checked(0) {
Ok(h) => h,
Err(_) => 0,
},
)?;
// Prepare necessary SQL statements
let mut stmt_blocks = cache
.prepare("SELECT height, data FROM compactblocks WHERE height > ? ORDER BY height ASC")?;
let mut stmt_insert_block = data.prepare(
"INSERT INTO blocks (height)
VALUES (?)",
)?;
let mut stmt_insert_tx = data.prepare(
"INSERT INTO transactions (txid, block)
VALUES (?, ?)",
)?;
let mut stmt_insert_note = data.prepare(
"INSERT INTO received_notes (tx, output_index, value)
VALUES (?, ?, ?)",
)?;
// Fetch the CompactBlocks we need to scan
let rows = stmt_blocks.query_map(&[last_height], |row| CompactBlockRow {
height: row.get(0),
data: row.get(1),
})?;
2018-11-22 05:54:25 -08:00
for row in rows {
let row = row?;
// Scanned blocks MUST be height-seqential.
if row.height != (last_height + 1) {
error!(
"Expected height of next CompactBlock to be {}, but was {}",
last_height + 1,
row.height
);
// Nothing more we can do
break;
}
last_height = row.height;
// Insert the block into the database.
stmt_insert_block.execute(&[row.height.to_sql()?])?;
2018-11-22 05:54:25 -08:00
for tx in scan_block_from_bytes(&row.data, &extfvks) {
// Insert our transaction into the database.
stmt_insert_tx.execute(&[tx.txid.0.to_vec().to_sql()?, row.height.to_sql()?])?;
let tx_row = data.last_insert_rowid();
2018-11-22 05:54:25 -08:00
for output in tx.shielded_outputs {
// Insert received note into the database.
// Assumptions:
// - A transaction will not contain more than 2^63 shielded outputs.
// - A note value will never exceed 2^63 zatoshis.
stmt_insert_note.execute(&[tx_row, output.index as i64, output.value as i64])?;
2018-11-22 05:54:25 -08:00
}
}
}
Ok(())
2018-11-22 05:54:25 -08:00
}
/// JNI interface
2018-11-21 05:51:37 -08:00
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
pub mod android {
extern crate android_logger;
extern crate jni;
extern crate log_panics;
use log::Level;
2018-11-22 05:54:25 -08:00
use self::android_logger::Filter;
2018-11-22 05:54:25 -08:00
use self::jni::objects::{JClass, JString};
use self::jni::sys::{jbyteArray, jstring};
2018-11-21 05:51:37 -08:00
use self::jni::JNIEnv;
2018-11-22 05:54:25 -08:00
use super::{address_from_extfvk, extfvk_from_seed, scan_cached_blocks};
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initLogs(
_env: JNIEnv,
_: JClass,
) {
android_logger::init_once(
Filter::default().with_min_level(Level::Trace),
Some("cash.z.rust.logs"),
);
log_panics::init();
debug!("logs have been initialized successfully");
}
#[no_mangle]
2018-11-21 08:40:18 -08:00
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getAddress(
2018-11-21 05:51:37 -08:00
env: JNIEnv,
_: JClass,
2018-11-21 08:40:18 -08:00
seed: jbyteArray,
) -> jstring {
let seed = env.convert_byte_array(seed).unwrap();
let addr = address_from_extfvk(&extfvk_from_seed(&seed));
2018-11-21 08:40:18 -08:00
let output = env.new_string(addr).expect("Couldn't create Java string!");
output.into_inner()
}
2018-11-22 05:54:25 -08:00
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_scanBlocks(
env: JNIEnv,
_: JClass,
db_cache: JString,
db_data: JString,
2018-11-22 05:54:25 -08:00
seed: jbyteArray,
) {
let db_cache: String = env
.get_string(db_cache)
.expect("Couldn't get Java string!")
.into();
let db_data: String = env
.get_string(db_data)
2018-11-22 05:54:25 -08:00
.expect("Couldn't get Java string!")
.into();
let seed = env.convert_byte_array(seed).unwrap();
if let Err(e) = scan_cached_blocks(&db_cache, &db_data, &[extfvk_from_seed(&seed)]) {
error!("Error while scanning blocks: {}", e);
2018-11-22 05:54:25 -08:00
}
}
}