diff --git a/Cargo.lock b/Cargo.lock index f51d21b..91941b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,11 @@ name = "bit-vec" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "blake2b_simd" version = "0.5.6" @@ -144,6 +149,11 @@ dependencies = [ "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cc" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cfg-if" version = "0.1.9" @@ -194,6 +204,16 @@ name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ff" version = "0.4.0" @@ -317,6 +337,34 @@ dependencies = [ "zcash_proofs 0.0.0", ] +[[package]] +name = "libsqlite3-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "nodrop" version = "0.1.13" @@ -372,6 +420,11 @@ dependencies = [ "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pkg-config" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ppv-lite86" version = "0.2.5" @@ -482,6 +535,33 @@ dependencies = [ "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rusqlite" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha2" version = "0.8.0" @@ -508,6 +588,29 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.61 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.61 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "typenum" version = "1.10.0" @@ -518,6 +621,11 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "vcpkg" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.3.7" @@ -554,6 +662,16 @@ dependencies = [ "zcash_primitives 0.0.0", ] +[[package]] +name = "zcash_client_sqlite" +version = "0.0.0" +dependencies = [ + "rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_client_backend 0.0.0", + "zcash_primitives 0.0.0", +] + [[package]] name = "zcash_primitives" version = "0.0.0" @@ -601,6 +719,7 @@ dependencies = [ "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" "checksum bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0089c35ab7c6f2bc55ab23f769913f0ac65b1023e7e74638a1f43128dd5df2" "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum blake2b_simd 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "461f4b879a8eb70c1debf7d0788a9a5ff15f1ea9d25925fea264ef4258bed6b2" "checksum blake2s_simd 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3a84d2614b18a5367d357331a90fd533d5ceb1e86abc319320df2104ab744c2a" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" @@ -609,6 +728,7 @@ dependencies = [ "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" +"checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" @@ -617,6 +737,8 @@ dependencies = [ "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72d337a64190607d4fcca2cb78982c5dd57f4916e19696b48a575fa746b6cb0f" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +"checksum fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" "checksum fpe 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "21988a326139165b75e3196bc6962ca638e5fb0c95102fbf152a3743174b01e4" "checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" @@ -627,12 +749,17 @@ dependencies = [ "checksum hex-literal-impl 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "520870c3213943eb8d7803e80180d12a6c7ceb4ae74602544529d1643dc4ddda" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.61 (registry+https://github.com/rust-lang/crates.io-index)" = "c665266eb592905e8503ba3403020f4b8794d26263f412ca33171600eca9a6fa" +"checksum libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25" +"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +"checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro-hack 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "463bf29e7f11344e58c9e01f171470ab15c925c6822ad75028cc1c0e1d1eb63b" "checksum proc-macro-hack-impl 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "38c47dcb1594802de8c02f3b899e2018c78291168a22c281be21ea0fb4796842" @@ -647,11 +774,17 @@ dependencies = [ "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum rand_os 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb525a78d3a0b0e05b6fe0f7df14d7a4dc957944c7b403911ba5a0f1c694967" "checksum rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "01f40907d9ffc762709e4ff3eb4a6f6b41b650375a3f09ac92b641942b7fb082" "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 69639cf..0eba4cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "librustzcash", "pairing", "zcash_client_backend", + "zcash_client_sqlite", "zcash_primitives", "zcash_proofs", ] diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml new file mode 100644 index 0000000..c7a9c64 --- /dev/null +++ b/zcash_client_sqlite/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "zcash_client_sqlite" +version = "0.0.0" +authors = [ + "Jack Grigg ", +] +edition = "2018" + +[dependencies] +rusqlite = { version = "0.20", features = ["bundled"] } +zcash_client_backend = { path = "../zcash_client_backend" } +zcash_primitives = { path = "../zcash_primitives" } + +[dev-dependencies] +tempfile = "3" diff --git a/zcash_client_sqlite/README.md b/zcash_client_sqlite/README.md new file mode 100644 index 0000000..2c244f8 --- /dev/null +++ b/zcash_client_sqlite/README.md @@ -0,0 +1,21 @@ +# zcash_client_sqlite + +This library contains APIs that collectively implement a Zcash light client in +an SQLite database. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. + diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs new file mode 100644 index 0000000..514b6f8 --- /dev/null +++ b/zcash_client_sqlite/src/error.rs @@ -0,0 +1,41 @@ +use std::error; +use std::fmt; + +#[derive(Debug)] +pub enum ErrorKind { + TableNotEmpty, + Database(rusqlite::Error), +} + +#[derive(Debug)] +pub struct Error(pub(crate) ErrorKind); + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.0 { + ErrorKind::TableNotEmpty => write!(f, "Table is not empty"), + ErrorKind::Database(e) => write!(f, "{}", e), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match &self.0 { + ErrorKind::Database(e) => Some(e), + _ => None, + } + } +} + +impl From for Error { + fn from(e: rusqlite::Error) -> Self { + Error(ErrorKind::Database(e)) + } +} + +impl Error { + pub fn kind(&self) -> &ErrorKind { + &self.0 + } +} diff --git a/zcash_client_sqlite/src/init.rs b/zcash_client_sqlite/src/init.rs new file mode 100644 index 0000000..c5ea793 --- /dev/null +++ b/zcash_client_sqlite/src/init.rs @@ -0,0 +1,286 @@ +//! Functions for initializing the various databases. + +use rusqlite::{types::ToSql, Connection, NO_PARAMS}; +use std::path::Path; +use zcash_client_backend::{ + constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + encoding::encode_extended_full_viewing_key, +}; +use zcash_primitives::{block::BlockHash, zip32::ExtendedFullViewingKey}; + +use crate::{ + address_from_extfvk, + error::{Error, ErrorKind}, +}; + +/// Sets up the internal structure of the cache database. +/// +/// # Examples +/// +/// ``` +/// use tempfile::NamedTempFile; +/// use zcash_client_sqlite::init::init_cache_database; +/// +/// let data_file = NamedTempFile::new().unwrap(); +/// let db_cache = data_file.path(); +/// init_cache_database(&db_cache).unwrap(); +/// ``` +pub fn init_cache_database>(db_cache: P) -> Result<(), Error> { + let cache = Connection::open(db_cache)?; + cache.execute( + "CREATE TABLE IF NOT EXISTS compactblocks ( + height INTEGER PRIMARY KEY, + data BLOB NOT NULL + )", + NO_PARAMS, + )?; + Ok(()) +} + +/// Sets up the internal structure of the data database. +/// +/// # Examples +/// +/// ``` +/// use tempfile::NamedTempFile; +/// use zcash_client_sqlite::init::init_data_database; +/// +/// let data_file = NamedTempFile::new().unwrap(); +/// let db_data = data_file.path(); +/// init_data_database(&db_data).unwrap(); +/// ``` +pub fn init_data_database>(db_data: P) -> Result<(), Error> { + let data = Connection::open(db_data)?; + data.execute( + "CREATE TABLE IF NOT EXISTS accounts ( + account INTEGER PRIMARY KEY, + extfvk TEXT NOT NULL, + address TEXT NOT NULL + )", + NO_PARAMS, + )?; + data.execute( + "CREATE TABLE IF NOT EXISTS blocks ( + height INTEGER PRIMARY KEY, + hash BLOB NOT NULL, + time INTEGER NOT NULL, + sapling_tree BLOB NOT NULL + )", + NO_PARAMS, + )?; + data.execute( + "CREATE TABLE IF NOT EXISTS transactions ( + id_tx INTEGER PRIMARY KEY, + txid BLOB NOT NULL UNIQUE, + created TEXT, + block INTEGER, + tx_index INTEGER, + expiry_height INTEGER, + raw BLOB, + 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, + account INTEGER NOT NULL, + diversifier BLOB NOT NULL, + value INTEGER NOT NULL, + rcm BLOB NOT NULL, + nf BLOB NOT NULL UNIQUE, + is_change BOOLEAN NOT NULL, + memo BLOB, + spent INTEGER, + FOREIGN KEY (tx) REFERENCES transactions(id_tx), + FOREIGN KEY (account) REFERENCES accounts(account), + FOREIGN KEY (spent) REFERENCES transactions(id_tx), + CONSTRAINT tx_output UNIQUE (tx, output_index) + )", + NO_PARAMS, + )?; + data.execute( + "CREATE TABLE IF NOT EXISTS sapling_witnesses ( + id_witness INTEGER PRIMARY KEY, + note INTEGER NOT NULL, + block INTEGER NOT NULL, + witness BLOB NOT NULL, + FOREIGN KEY (note) REFERENCES received_notes(id_note), + FOREIGN KEY (block) REFERENCES blocks(height), + CONSTRAINT witness_height UNIQUE (note, block) + )", + NO_PARAMS, + )?; + data.execute( + "CREATE TABLE IF NOT EXISTS sent_notes ( + id_note INTEGER PRIMARY KEY, + tx INTEGER NOT NULL, + output_index INTEGER NOT NULL, + from_account INTEGER NOT NULL, + address TEXT NOT NULL, + value INTEGER NOT NULL, + memo BLOB, + FOREIGN KEY (tx) REFERENCES transactions(id_tx), + FOREIGN KEY (from_account) REFERENCES accounts(account), + CONSTRAINT tx_output UNIQUE (tx, output_index) + )", + NO_PARAMS, + )?; + Ok(()) +} + +/// Initialises the data database with the given [`ExtendedFullViewingKey`]s. +/// +/// The [`ExtendedFullViewingKey`]s are stored internally and used by other APIs such as +/// [`get_address`], [`scan_cached_blocks`], and [`create_to_address`]. `extfvks` **MUST** +/// be arranged in account-order; that is, the [`ExtendedFullViewingKey`] for ZIP 32 +/// account `i` **MUST** be at `extfvks[i]`. +/// +/// # Examples +/// +/// ``` +/// use tempfile::NamedTempFile; +/// use zcash_client_sqlite::init::{init_accounts_table, init_data_database}; +/// use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; +/// +/// let data_file = NamedTempFile::new().unwrap(); +/// let db_data = data_file.path(); +/// init_data_database(&db_data).unwrap(); +/// +/// let extsk = ExtendedSpendingKey::master(&[]); +/// let extfvks = [ExtendedFullViewingKey::from(&extsk)]; +/// init_accounts_table(&db_data, &extfvks).unwrap(); +/// ``` +/// +/// [`get_address`]: crate::query::get_address +/// [`scan_cached_blocks`]: crate::scan::scan_cached_blocks +/// [`create_to_address`]: crate::transact::create_to_address +pub fn init_accounts_table>( + db_data: P, + extfvks: &[ExtendedFullViewingKey], +) -> Result<(), Error> { + let data = Connection::open(db_data)?; + + let mut empty_check = data.prepare("SELECT * FROM accounts LIMIT 1")?; + if empty_check.exists(NO_PARAMS)? { + return Err(Error(ErrorKind::TableNotEmpty)); + } + + // Insert accounts atomically + data.execute("BEGIN IMMEDIATE", NO_PARAMS)?; + for (account, extfvk) in extfvks.iter().enumerate() { + let address = address_from_extfvk(extfvk); + let extfvk = + encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, extfvk); + data.execute( + "INSERT INTO accounts (account, extfvk, address) + VALUES (?, ?, ?)", + &[ + (account as u32).to_sql()?, + extfvk.to_sql()?, + address.to_sql()?, + ], + )?; + } + data.execute("COMMIT", NO_PARAMS)?; + + Ok(()) +} + +/// Initialises the data database with the given block. +/// +/// This enables a newly-created database to be immediately-usable, without needing to +/// synchronise historic blocks. +/// +/// # Examples +/// +/// ``` +/// use zcash_client_sqlite::init::init_blocks_table; +/// use zcash_primitives::block::BlockHash; +/// +/// // The block height. +/// let height = 500_000; +/// // The hash of the block header. +/// let hash = BlockHash([0; 32]); +/// // The nTime field from the block header. +/// let time = 12_3456_7890; +/// // The serialized Sapling commitment tree as of this block. +/// // Pre-compute and hard-code, or obtain from a service. +/// let sapling_tree = &[]; +/// +/// init_blocks_table("/path/to/data.db", height, hash, time, sapling_tree); +/// ``` +pub fn init_blocks_table>( + db_data: P, + height: i32, + hash: BlockHash, + time: u32, + sapling_tree: &[u8], +) -> Result<(), Error> { + let data = Connection::open(db_data)?; + + let mut empty_check = data.prepare("SELECT * FROM blocks LIMIT 1")?; + if empty_check.exists(NO_PARAMS)? { + return Err(Error(ErrorKind::TableNotEmpty)); + } + + data.execute( + "INSERT INTO blocks (height, hash, time, sapling_tree) + VALUES (?, ?, ?, ?)", + &[ + height.to_sql()?, + hash.0.to_sql()?, + time.to_sql()?, + sapling_tree.to_sql()?, + ], + )?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use tempfile::NamedTempFile; + use zcash_primitives::{ + block::BlockHash, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, + }; + + use super::{init_accounts_table, init_blocks_table, init_data_database}; + + #[test] + fn init_accounts_table_only_works_once() { + let data_file = NamedTempFile::new().unwrap(); + let db_data = data_file.path(); + init_data_database(&db_data).unwrap(); + + // We can call the function as many times as we want with no data + init_accounts_table(&db_data, &[]).unwrap(); + init_accounts_table(&db_data, &[]).unwrap(); + + // First call with data should initialise the accounts table + let extfvks = [ExtendedFullViewingKey::from(&ExtendedSpendingKey::master( + &[], + ))]; + init_accounts_table(&db_data, &extfvks).unwrap(); + + // Subsequent calls should return an error + init_accounts_table(&db_data, &[]).unwrap_err(); + init_accounts_table(&db_data, &extfvks).unwrap_err(); + } + + #[test] + fn init_blocks_table_only_works_once() { + let data_file = NamedTempFile::new().unwrap(); + let db_data = data_file.path(); + init_data_database(&db_data).unwrap(); + + // First call with data should initialise the blocks table + init_blocks_table(&db_data, 1, BlockHash([1; 32]), 1, &[]).unwrap(); + + // Subsequent calls should return an error + init_blocks_table(&db_data, 2, BlockHash([2; 32]), 2, &[]).unwrap_err(); + } +} diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs new file mode 100644 index 0000000..767b582 --- /dev/null +++ b/zcash_client_sqlite/src/lib.rs @@ -0,0 +1,33 @@ +//! *An SQLite-based Zcash light client.* +//! +//! `zcash_client_backend` contains a set of APIs that collectively implement an +//! SQLite-based light client for the Zcash network. +//! +//! # Design +//! +//! The light client is built around two SQLite databases: +//! +//! - A cache database, used to inform the light client about new [`CompactBlock`]s. It is +//! read-only within all light client APIs *except* for [`init_cache_database`] which +//! can be used to initialize the database. +//! +//! - A data database, where the light client's state is stored. It is read-write within +//! the light client APIs, and **assumed to be read-only outside these APIs**. Callers +//! **MUST NOT** write to the database without using these APIs. Callers **MAY** read +//! the database directly in order to extract information for display to users. +//! +//! [`CompactBlock`]: zcash_client_backend::proto::compact_formats::CompactBlock +//! [`init_cache_database`]: crate::init::init_cache_database + +use zcash_client_backend::{ + constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, encoding::encode_payment_address, +}; +use zcash_primitives::zip32::ExtendedFullViewingKey; + +pub mod error; +pub mod init; + +fn address_from_extfvk(extfvk: &ExtendedFullViewingKey) -> String { + let addr = extfvk.default_address().unwrap().1; + encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &addr) +}