Support Note Spending v7

* upgraded to note-spending-v7

* use "zip32 compliant" seed on demo app

* Fixes to initializer, added v7 methods, documented API. Fixed compact block processor not initializing correctly upon new wallets.

* documentation

* Change u8 return value to bool + tests

* add address validation functionality to Initializer

* renamed functions to a C style. Exposed deriving functions in rust backend. Added more tests

* fixed bug where compact block processor wouldn't reschedule

* Fixed capture blocks retaining references

* fixed memory cycles and leaks

* fixed memory leak and blockrange error

* fixed error on confirmed transactions and added blockheight to progress notifications
This commit is contained in:
Francisco Gindre 2020-02-26 13:54:48 -03:00 committed by Linda Naeun Lee
parent 5ba21fab99
commit 5bc3764f9e
42 changed files with 1353 additions and 344 deletions

143
Cargo.lock generated
View File

@ -88,16 +88,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bellman"
version = "0.1.0"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.2.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2s_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"group 0.1.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"pairing 0.14.2 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"group 0.2.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"pairing 0.15.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -160,7 +160,7 @@ dependencies = [
[[package]]
name = "bs58"
version = "0.2.5"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -297,25 +297,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ff"
version = "0.4.0"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.5.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ff_derive 0.3.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"ff_derive 0.4.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ff_derive"
version = "0.3.0"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.4.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -365,10 +365,10 @@ dependencies = [
[[package]]
name = "group"
version = "0.1.0"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.2.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -405,16 +405,18 @@ dependencies = [
[[package]]
name = "libzcashlc"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"cbindgen 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"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-v6)",
"zcash_client_sqlite 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"zcash_proofs 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"pairing 0.15.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"zcash_client_backend 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"zcash_client_sqlite 0.0.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"zcash_primitives 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"zcash_proofs 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
]
[[package]]
@ -477,12 +479,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pairing"
version = "0.14.2"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.15.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"group 0.1.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"group 0.2.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -587,15 +589,6 @@ dependencies = [
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_os"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_xorshift"
version = "0.2.0"
@ -690,16 +683,6 @@ name = "subtle"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.15.44"
@ -826,70 +809,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "zcash_client_backend"
version = "0.0.0"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.1.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bs58 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"bs58 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pairing 0.14.2 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"pairing 0.15.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"zcash_primitives 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
]
[[package]]
name = "zcash_client_sqlite"
version = "0.0.0"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bs58 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"pairing 0.14.2 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"bs58 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"pairing 0.15.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (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-v6)",
"zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"zcash_client_backend 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"zcash_primitives 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
]
[[package]]
name = "zcash_primitives"
version = "0.0.0"
source = "git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.1.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2s_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crypto_api_chachapoly 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"fpe 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pairing 0.14.2 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"pairing 0.15.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"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-v6#9e659d6db74380a732e36dfea915f9f7c6809431"
version = "0.1.0"
source = "git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925#f3f5338282eeda6d9f5bff69f6930a5473c95925"
dependencies = [
"bellman 0.1.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"bellman 0.2.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"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-v6)",
"pairing 0.14.2 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)",
"ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"pairing 0.15.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"zcash_primitives 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)",
]
[metadata]
@ -904,7 +887,7 @@ dependencies = [
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0089c35ab7c6f2bc55ab23f769913f0ac65b1023e7e74638a1f43128dd5df2"
"checksum bellman 0.1.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum bellman 0.2.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0"
@ -912,7 +895,7 @@ dependencies = [
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
"checksum bs58 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c95ee6bba9d950218b6cc910cf62bc9e0a171d0f4537e3627b0f54d08549b188"
"checksum bs58 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b170cd256a3f9fa6b9edae3e44a7dfdfc77e8124dbc3e2612d75f9c3e2396dae"
"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.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
@ -930,14 +913,14 @@ dependencies = [
"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 ff 0.4.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum ff_derive 0.3.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum ff 0.5.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum ff_derive 0.4.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum ffi_helpers 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "545c5db858a1f8749e81b22cb9e10a5412c9775441a53c4bd1d1f28531edc843"
"checksum fpe 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "21988a326139165b75e3196bc6962ca638e5fb0c95102fbf152a3743174b01e4"
"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
"checksum group 0.1.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum group 0.2.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
@ -951,7 +934,7 @@ dependencies = [
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
"checksum pairing 0.14.2 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum pairing 0.15.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
@ -965,7 +948,6 @@ dependencies = [
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a"
"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"
@ -978,7 +960,6 @@ dependencies = [
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
@ -996,7 +977,7 @@ dependencies = [
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"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"
"checksum zcash_client_backend 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum zcash_client_sqlite 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum zcash_primitives 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum zcash_proofs 0.0.0 (git+https://github.com/str4d/librustzcash.git?branch=note-spending-v6)" = "<none>"
"checksum zcash_client_backend 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum zcash_client_sqlite 0.0.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum zcash_primitives 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"
"checksum zcash_proofs 0.1.0 (git+https://github.com/str4d/librustzcash.git?rev=f3f5338282eeda6d9f5bff69f6930a5473c95925)" = "<none>"

View File

@ -1,7 +1,9 @@
[package]
name = "libzcashlc"
version = "0.0.1"
authors = ["Jack Grigg <jack@z.cash>"]
version = "0.0.2"
authors = ["Jack Grigg <jack@z.cash>",
"Francisco Gindre <francisco@z.cash>",
]
edition = "2018"
build = "rust/build.rs"
@ -10,21 +12,29 @@ failure = "0.1"
ffi_helpers = "0.1"
hex = "0.3"
[dependencies.ff]
git = "https://github.com/str4d/librustzcash.git"
rev = "f3f5338282eeda6d9f5bff69f6930a5473c95925"
[dependencies.pairing]
git = "https://github.com/str4d/librustzcash.git"
rev = "f3f5338282eeda6d9f5bff69f6930a5473c95925"
[dependencies.zcash_client_backend]
git = "https://github.com/str4d/librustzcash.git"
branch = "note-spending-v6"
rev = "f3f5338282eeda6d9f5bff69f6930a5473c95925"
[dependencies.zcash_client_sqlite]
git = "https://github.com/str4d/librustzcash.git"
branch = "note-spending-v6"
rev = "f3f5338282eeda6d9f5bff69f6930a5473c95925"
[dependencies.zcash_primitives]
git = "https://github.com/str4d/librustzcash.git"
branch = "note-spending-v6"
rev = "f3f5338282eeda6d9f5bff69f6930a5473c95925"
[dependencies.zcash_proofs]
git = "https://github.com/str4d/librustzcash.git"
branch = "note-spending-v6"
rev = "f3f5338282eeda6d9f5bff69f6930a5473c95925"
default-features = false
features = ["local-prover"]

View File

@ -14,7 +14,7 @@ struct DemoAppConfig {
static var port = "9067"
static var birthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 643_500 : 620_000
static var network = ZcashSDK.isMainnet ? ZcashNetwork.mainNet : ZcashNetwork.testNet
static var seed = ZcashSDK.isMainnet ? Array("testreferencealice".utf8) : Array("testreferencealice".utf8)
static var seed = ZcashSDK.isMainnet ? Array("testreferencealicetestreferencealice".utf8) : Array("testreferencealicetestreferencealice".utf8)
static var address: String {
"\(host):\(port)"
}

View File

@ -69,14 +69,3 @@ struct SimpleConnectionProvider: ConnectionProvider {
}
}
/**
Set schema version
*/
// TODO: define a better way to do this
//extension Connection {
// public var userVersion: Int32 {
// get { return Int32(try scalar("PRAGMA user_version") as Int64)}
// set { try! run("PRAGMA user_version = \(newValue)")}
// }
//}

View File

@ -12,7 +12,9 @@ enum CompactBlockDownloadError: Error {
case timeout
case generalError(error: Error)
}
/**
Represents what a compact block downloaded should provide to its clients
*/
public protocol CompactBlockDownloading {
/**
Downloads and stores the given block range.
@ -21,6 +23,13 @@ public protocol CompactBlockDownloading {
func downloadBlockRange(_ heightRange: CompactBlockRange,
completion: @escaping (Error?) -> Void)
/**
Remove newer blocks and go back to the given height
- Parameters:
- height: the given height to rewind to
- completion: block to be executed after completing rewind
*/
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void)
/**
@ -42,6 +51,9 @@ public protocol CompactBlockDownloading {
*/
func downloadBlockRange(_ range: CompactBlockRange) throws
/**
Restore the download progress up to the given height.
*/
func rewind(to height: BlockHeight) throws
/**
returns the height of the latest compact block stored locally.
@ -68,7 +80,7 @@ public protocol CompactBlockDownloading {
class CompactBlockDownloader {
fileprivate var lightwalletService: LightWalletService
fileprivate var storage: CompactBlockRepository
private(set) var storage: CompactBlockRepository
init(service: LightWalletService, storage: CompactBlockRepository) {
self.lightwalletService = service
@ -123,7 +135,7 @@ extension CompactBlockDownloader: CompactBlockDownloading {
try storage.write(blocks: blocks)
}
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void){
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void) {
storage.rewind(to: height) { (e) in
completion(e)

View File

@ -7,37 +7,84 @@
//
import Foundation
/**
Errors thrown by CompactBlock Processor
*/
public enum CompactBlockProcessorError: Error {
case invalidConfiguration
case missingDbPath(path: String)
case dataDbInitFailed(path: String)
}
/**
CompactBlockProcessor notification userInfo object keys.
check Notification.Name extensions for more details.
*/
public struct CompactBlockProcessorNotificationKey {
public static let progress = "CompactBlockProcessorNotificationKey.progress"
public static let progressHeight = "CompactBlockProcessorNotificationKey.progressHeight"
public static let reorgHeight = "CompactBlockProcessorNotificationKey.reorgHeight"
public static let latestScannedBlockHeight = "CompactBlockProcessorNotificationKey.latestScannedBlockHeight"
public static let rewindHeight = "CompactBlockProcessorNotificationKey.rewindHeight"
public static let error = "error"
}
public extension Notification.Name {
/**
Processing progress update. usertInfo["progress"]
Processing progress update
Query the userInfo object for the key CompactBlockProcessorNotificationKey.progress and CompactBlockProcessorNotificationKey.progressheight for more information on progress % and height
*/
static let blockProcessorUpdated = Notification.Name(rawValue: "CompactBlockProcessorUpdated")
/**
Notification sent when a compact block processor starts downloading
*/
static let blockProcessorStartedDownloading = Notification.Name(rawValue: "CompactBlockProcessorStartedDownloading")
/**
Notification sent when the compact block processor starts validating the chain state
*/
static let blockProcessorStartedValidating = Notification.Name(rawValue: "CompactBlockProcessorStartedValidating")
/**
Notification sent when the compact block processor starts scanning blocks from the cache
*/
static let blockProcessorStartedScanning = Notification.Name(rawValue: "CompactBlockProcessorStartedScanning")
/**
Notification sent when the compact block processsor stop() method is called
*/
static let blockProcessorStopped = Notification.Name(rawValue: "CompactBlockProcessorStopped")
/**
Notification sent when the compact block processsor presented an error.
Query userInfo object on the key CompactBlockProcessorNotificationKey.error
*/
static let blockProcessorFailed = Notification.Name(rawValue: "CompactBlockProcessorFailed")
/**
Notification sent when the compact block processsor has finished syncing the blockchain to latest height
*/
static let blockProcessorFinished = Notification.Name(rawValue: "CompactBlockProcessorFinished")
/**
Notification sent when the compact block processsor is doing nothing
*/
static let blockProcessorIdle = Notification.Name(rawValue: "CompactBlockProcessorIdle")
/**
Notification sent when something odd happened. probably going from a state to another state that shouldn't be the next state.
*/
static let blockProcessorUnknownTransition = Notification.Name(rawValue: "CompactBlockProcessorTransitionUnknown")
/**
Notification sent when the compact block processsor handled a ReOrg.
Query the userInfo object on the key CompactBlockProcessorNotificationKey.reorgHeight for the height on which the reorg was detected. CompactBlockProcessorNotificationKey.rewindHeight for the height that the processor backed to in order to solve the Reorg
*/
static let blockProcessorHandledReOrg = Notification.Name(rawValue: "CompactBlockProcessorHandledReOrg")
}
/**
The compact block processor is in charge of orchestrating the download and caching of compact blocks from a LightWalletEndpoint
when started the processor downloads does a download - validate - scan cycle until it reaches latest height on the blockchain.
*/
public class CompactBlockProcessor {
/**
Compact Block Processor configuration
@ -46,13 +93,12 @@ public class CompactBlockProcessor {
Property: dataDbPath absolute file path of the DB where all information derived from the cache DB is stored.
*/
// TODO: make internal again
public struct Configuration {
public var cacheDb: URL
public var dataDb: URL
public var downloadBatchSize = ZcashSDK.DEFAULT_BATCH_SIZE
public var blockPollInterval: TimeInterval {
TimeInterval.random(in: ZcashSDK.DEFAULT_POLL_INTERVAL/2 ... ZcashSDK.DEFAULT_POLL_INTERVAL * 1.5) //
TimeInterval.random(in: ZcashSDK.DEFAULT_POLL_INTERVAL / 2 ... ZcashSDK.DEFAULT_POLL_INTERVAL * 1.5)
}
public var retries = ZcashSDK.DEFAULT_RETRIES
@ -66,7 +112,9 @@ public class CompactBlockProcessor {
self.walletBirthday = walletBirthday
}
}
/**
Represents the possible states of a CompactBlockProcessor
*/
public enum State {
/**
@ -129,6 +177,12 @@ public class CompactBlockProcessor {
BlockHeight(self.config.downloadBatchSize)
}
/**
Initializes a CompactBlockProcessor instance
- Parameters:
- downloader: an instance that complies to CompactBlockDownloading protocol
- backend: a class that complies to ZcashRustBackendWelding
*/
public init(downloader: CompactBlockDownloading, backend: ZcashRustBackendWelding.Type, config: Configuration) {
self.downloader = downloader
self.rustBackend = backend
@ -140,6 +194,16 @@ public class CompactBlockProcessor {
self.queue.cancelAllOperations()
}
var shouldStart: Bool {
switch self.state {
case .stopped, .synced, .error(_):
return self.retryAttempts < self.config.retries
default:
return false
}
}
private func validateConfiguration() throws {
guard FileManager.default.isReadableFile(atPath: config.cacheDb.absoluteString) else {
throw CompactBlockProcessorError.missingDbPath(path: config.cacheDb.absoluteString)
@ -149,7 +213,14 @@ public class CompactBlockProcessor {
throw CompactBlockProcessorError.missingDbPath(path: config.dataDb.absoluteString)
}
}
/**
Starts the CompactBlockProcessor instance and starts downloading and processing blocks
triggers the blockProcessorStartedDownloading notification
- Important: subscribe to the notifications before calling this method
*/
public func start() throws {
// TODO: check if this validation makes sense at all
@ -160,10 +231,13 @@ public class CompactBlockProcessor {
return
}
guard let birthday = WalletBirthday.birthday(with: config.walletBirthday) else {
throw CompactBlockProcessorError.invalidConfiguration
guard shouldStart else {
print("Warning: compact block processor was started while busy!!!!")
return
}
let birthday = WalletBirthday.birthday(with: config.walletBirthday)
do {
try rustBackend.initDataDb(dbData: config.dataDb)
try rustBackend.initBlocksTable(dbData: config.dataDb, height: Int32(birthday.height), hash: birthday.hash, time: birthday.time, saplingTree: birthday.tree)
@ -177,6 +251,13 @@ public class CompactBlockProcessor {
}
/**
Stops the CompactBlockProcessor
Note: retry count is reset
- Parameter cancelTasks: cancel the pending tasks. Defaults to true
*/
public func stop(cancelTasks: Bool = true) {
self.backoffTimer?.invalidate()
self.backoffTimer = nil
@ -202,18 +283,23 @@ public class CompactBlockProcessor {
if self.latestBlockHeight > latestDownloadedBlockHeight {
self.processNewBlocks(range: self.nextBatchBlockRange(latestHeight: self.latestBlockHeight, latestDownloadedHeight: latestDownloadedBlockHeight))
} else {
self.downloader.latestBlockHeight { (result) in
self.downloader.latestBlockHeight { [weak self] (result) in
guard let self = self else { return }
switch result {
case .success(let blockHeight):
self.latestBlockHeight = blockHeight
if self.latestBlockHeight == latestDownloadedBlockHeight {
self.processingFinished(height: blockHeight)
} else {
self.processNewBlocks(range: self.nextBatchBlockRange(latestHeight: self.latestBlockHeight, latestDownloadedHeight: latestDownloadedBlockHeight))
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.latestBlockHeight = blockHeight
if self.latestBlockHeight == latestDownloadedBlockHeight {
self.processingFinished(height: blockHeight)
} else {
self.processNewBlocks(range: self.nextBatchBlockRange(latestHeight: self.latestBlockHeight, latestDownloadedHeight: latestDownloadedBlockHeight))
}
}
case .failure(let e):
DispatchQueue.main.async {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.fail(e)
}
}
@ -222,22 +308,23 @@ public class CompactBlockProcessor {
}
func processNewBlocks(range: CompactBlockRange) {
// guard !range.isEmpty else {
// processingFinished(height: range.upperBound)
// return
// }
let cfg = self.config
let downloadBlockOperation = CompactBlockDownloadOperation(downloader: self.downloader, range: range)
downloadBlockOperation.startedHandler = {
self.state = .downloading
downloadBlockOperation.startedHandler = { [weak self] in
self?.state = .downloading
}
downloadBlockOperation.errorHandler = { (error) in
self.processingError = error
self.fail(error)
downloadBlockOperation.errorHandler = { [weak self] (error) in
guard let self = self else { return }
self.processingError = error
self.fail(error)
}
let validateChainOperation = CompactBlockValidationOperation(rustWelding: self.rustBackend, cacheDb: cfg.cacheDb, dataDb: cfg.dataDb)
@ -251,43 +338,51 @@ public class CompactBlockProcessor {
print("validateChainFinished")
}
validateChainOperation.errorHandler = { (error) in
guard let validationError = error as? CompactBlockValidationError else {
print("Warning: validateChain operation returning generic error: \(error)")
return
}
validateChainOperation.errorHandler = { [weak self] (error) in
guard let self = self else { return }
guard let validationError = error as? CompactBlockValidationError else {
print("Warning: validateChain operation returning generic error: \(error)")
return
}
switch validationError {
case .validationFailed(let height):
print("chain validation at height: \(height)")
self.validationFailed(at: height)
}
switch validationError {
case .validationFailed(let height):
print("chain validation at height: \(height)")
self.validationFailed(at: height)
}
}
validateChainOperation.startedHandler = {
self.state = .validating
validateChainOperation.startedHandler = { [weak self] in
self?.state = .validating
}
validateChainOperation.addDependency(downloadBlockOperation)
let scanBlocksOperation = CompactBlockScanningOperation(rustWelding: self.rustBackend, cacheDb: cfg.cacheDb, dataDb: cfg.dataDb)
scanBlocksOperation.startedHandler = {
self.state = .scanning
scanBlocksOperation.startedHandler = { [weak self] in
self?.state = .scanning
}
scanBlocksOperation.completionHandler = { (finished, cancelled) in
scanBlocksOperation.completionHandler = { [weak self] (finished, cancelled) in
guard !cancelled else {
print("Warning: operation cancelled")
return
}
print("scan operation completed: \(scanBlocksOperation)")
self.processBatchFinished(range: range)
self?.processBatchFinished(range: range)
}
scanBlocksOperation.errorHandler = { (error) in
self.processingError = error
self.fail(error)
scanBlocksOperation.errorHandler = { [weak self] (error) in
guard let self = self else { return }
self.processingError = error
self.fail(error)
}
scanBlocksOperation.addDependency(downloadBlockOperation)
@ -309,7 +404,8 @@ public class CompactBlockProcessor {
print("\(self) progress: \(progress)")
NotificationCenter.default.post(name: Notification.Name.blockProcessorUpdated,
object: self,
userInfo: [ CompactBlockProcessorNotificationKey.progress : progress ])
userInfo: [ CompactBlockProcessorNotificationKey.progress : progress,
CompactBlockProcessorNotificationKey.progressHeight : self.latestBlockHeight])
}
private func validationFailed(at height: BlockHeight) {
@ -374,19 +470,30 @@ public class CompactBlockProcessor {
private func processingFinished(height: BlockHeight) {
self.state = .synced
NotificationCenter.default.post(name: Notification.Name.blockProcessorFinished, object: self, userInfo: [CompactBlockProcessorNotificationKey.latestScannedBlockHeight : height])
self.backoffTimer = Timer(timeInterval: self.config.blockPollInterval, repeats: true, block: { _ in
do {
try self.start()
} catch {
self.fail(error)
let interval = self.config.blockPollInterval
self.backoffTimer?.invalidate()
let timer = Timer(timeInterval: interval, repeats: true, block: { [weak self] _ in
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
do {
try self.start()
} catch {
self.fail(error)
}
}
})
RunLoop.main.add(timer, forMode: .default)
self.backoffTimer = timer
}
func nextBatchBlockRange(latestHeight: BlockHeight, latestDownloadedHeight: BlockHeight) -> CompactBlockRange {
let lowerBound = latestDownloadedHeight <= config.walletBirthday ? config.walletBirthday : latestDownloadedHeight + 1
return CompactBlockRange(uncheckedBounds: (lowerBound, min(lowerBound + BlockHeight(config.downloadBatchSize - 1), latestHeight)))
let upperBound = BlockHeight(min(lowerBound + BlockHeight(config.downloadBatchSize - 1), latestHeight))
return lowerBound ... upperBound
}
func retryProcessing(range: CompactBlockRange) {
@ -406,6 +513,7 @@ public class CompactBlockProcessor {
// todo specify: failure
print(error)
queue.cancelAllOperations()
self.retryAttempts = self.retryAttempts + 1
self.processingError = error
self.state = .error(error)
@ -422,7 +530,7 @@ public class CompactBlockProcessor {
case .synced:
NotificationCenter.default.post(name: Notification.Name.blockProcessorIdle, object: self)
case .error(let err):
NotificationCenter.default.post(name: Notification.Name.blockProcessorFailed, object: self, userInfo: ["error": err])
NotificationCenter.default.post(name: Notification.Name.blockProcessorFailed, object: self, userInfo: [CompactBlockProcessorNotificationKey.error: err])
case .scanning:
NotificationCenter.default.post(name: Notification.Name.blockProcessorStartedScanning, object: self)
case .stopped:
@ -434,6 +542,9 @@ public class CompactBlockProcessor {
}
public extension CompactBlockProcessor.Configuration {
/**
Standard configuration for most compact block processors
*/
static var standard: CompactBlockProcessor.Configuration {
let pathProvider = DefaultResourceProvider()
return CompactBlockProcessor.Configuration(cacheDb: pathProvider.cacheDbURL, dataDb: pathProvider.dataDbURL)

View File

@ -61,8 +61,8 @@ class ZcashOperation: Operation {
return
}
self.handlerDispatchQueue.async {
errorHandler(self.error ?? ZcashOperationError.unknown)
self.handlerDispatchQueue.async { [weak self] in
errorHandler(self?.error ?? ZcashOperationError.unknown)
}
}

View File

@ -70,7 +70,22 @@ extension PendingTransaction {
// TODO: Handle Memo
init(value: Int, toAddress: String, memo: String?, account index: Int) {
self = PendingTransaction(toAddress: toAddress, accountIndex: index, minedHeight: -1, expiryHeight: -1, cancelled: 0, encodeAttempts: 0, submitAttempts: 0, errorMessage: nil, errorCode: nil, createTime: Date().timeIntervalSince1970, raw: nil, id: nil, value: Int(value), memo: nil, rawTransactionId: nil)
self = PendingTransaction(toAddress: toAddress,
accountIndex: index,
minedHeight: -1,
expiryHeight: -1,
cancelled: 0,
encodeAttempts: 0,
submitAttempts: 0,
errorMessage: nil,
errorCode: nil,
createTime: Date().timeIntervalSince1970,
raw: nil,
id: nil,
value: Int(value),
memo: memo?.encodeAsZcashTransactionMemo(),
rawTransactionId: nil)
}
}

View File

@ -192,9 +192,9 @@ class TransactionSQLDAO: TransactionRepository {
blocktimeinseconds DESC,
id DESC
LIMIT \(limit) OFFSET \(offset)
""").map({ (bindings) -> ConfirmedTransactionEntity in
""").compactMap({ (bindings) -> ConfirmedTransactionEntity? in
guard let tx = TransactionBuilder.createConfirmedTransaction(from: bindings) else {
throw TransactionRepositoryError.malformedTransaction
return nil
}
return tx
})

View File

@ -1,5 +1,5 @@
//
// File.swift
// AccountEntity.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 11/14/19.
@ -7,13 +7,13 @@
import Foundation
public protocol AccountEntity: Hashable {
protocol AccountEntity: Hashable {
var account: Int { get set }
var extfvk: String { get set }
var address: String { get set }
}
public extension AccountEntity {
extension AccountEntity {
func hash(into hasher: inout Hasher) {
hasher.combine(account)
hasher.combine(extfvk)

View File

@ -7,7 +7,7 @@
import Foundation
public protocol CompactBlockEntity {
protocol CompactBlockEntity {
var height: BlockHeight { get set }
var data: Data { get set }
}

View File

@ -6,29 +6,98 @@
//
import Foundation
/**
Represents a sent transaction that has not been confirmed yet on the blockchain
*/
public protocol PendingTransactionEntity: SignedTransactionEntity, AbstractTransaction, RawIdentifiable {
/**
recipient address
*/
var toAddress: String { get set }
/**
index of the account from which the funds were sent
*/
var accountIndex: Int { get set }
/**
height which the block was mined at.
-1 when block has not been mined yet
*/
var minedHeight: BlockHeight { get set }
/**
height for which the represented transaction would be considered expired
*/
var expiryHeight: BlockHeight { get set }
/**
value is 1 if the transaction was cancelled
*/
var cancelled: Int { get set }
/**
how many times this transaction encoding was attempted
*/
var encodeAttempts: Int { get set }
/**
How many attempts to send this transaction have been done
*/
var submitAttempts: Int { get set }
/**
Error message if available.
*/
var errorMessage: String? { get set }
/**
error code, if available
*/
var errorCode: Int? { get set }
/**
create time of the represented transaction
- Note: represented in timeIntervalySince1970
*/
var createTime: TimeInterval { get set }
/**
Checks whether this transaction is the same as the given transaction
*/
func isSameTransactionId<T: RawIdentifiable> (other: T) -> Bool
/**
returns whether the represented transaction is pending based on the provided block height
*/
func isPending(currentHeight: Int) -> Bool
/**
if the represented transaction is being created
*/
var isCreating: Bool { get }
/**
returns whether the represented transaction has failed to be encoded
*/
var isFailedEncoding: Bool { get }
/**
returns whether the represented transaction has failed to be submitted
*/
var isFailedSubmit: Bool { get }
/**
returns whether the represented transaction presents some kind of error
*/
var isFailure: Bool { get }
/**
returns whether the represented transaction has been cancelled by the user
*/
var isCancelled: Bool { get }
/**
returns whether the represented transaction has been succesfully mined
*/
var isMined: Bool { get }
/**
returns whether the represented transaction has been submitted
*/
var isSubmitted: Bool { get }
/**
returns whether the represented transaction has been submitted successfully
*/
var isSubmitSuccess: Bool { get }
}
@ -77,12 +146,18 @@ public extension PendingTransactionEntity {
}
public extension PendingTransactionEntity {
/**
TransactionEntity representation of this PendingTransactionEntity transaction
*/
var transactionEntity: TransactionEntity {
Transaction(id: self.id ?? -1, transactionId: self.rawTransactionId ?? Data(), created: Date(timeIntervalSince1970: self.createTime).description, transactionIndex: -1, expiryHeight: self.expiryHeight, minedHeight: self.minedHeight, raw: self.raw)
}
}
public extension ConfirmedTransactionEntity {
/**
TransactionEntity representation of this ConfirmedTransactionEntity transaction
*/
var transactionEntity: TransactionEntity {
Transaction(id: self.id ?? -1, transactionId: self.rawTransactionId ?? Data(), created: Date(timeIntervalSince1970: self.blockTimeInSeconds).description, transactionIndex: self.transactionIndex, expiryHeight: self.expiryHeight, minedHeight: self.minedHeight, raw: self.raw)
}

View File

@ -6,17 +6,33 @@
//
import Foundation
/**
convenience representation of all transaction types
*/
public protocol TransactionEntity {
/**
Internal transactio id
*/
var id: Int? { get set }
/**
Blockchain transaction id
*/
var transactionId: Data { get set }
/**
String representing the date of creation
format is yyyy-MM-dd'T'HH:MM:ss.SSSSSSSSSZ
- Example: 2019-12-04T17:49:10.636624000Z
*/
var created: String? { get set }
var transactionIndex: Int? { get set }
var expiryHeight: BlockHeight? { get set }
var minedHeight: BlockHeight? { get set }
var raw: Data? { get set }
}
/**
Hashable extension default implementation
*/
public extension TransactionEntity {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
@ -41,28 +57,68 @@ public extension TransactionEntity {
return true
}
}
/**
Abstract representation of all transaction types
*/
public protocol AbstractTransaction {
/**
internal id for this transaction
*/
var id: Int? { get set }
/**
value in zatoshi
*/
var value: Int { get set }
/**
data containing the memo if any
*/
var memo: Data? { get set }
}
/**
Capabilites of a signed transaction
*/
public protocol SignedTransactionEntity {
var raw: Data? { get set }
}
/**
capabilities of an entity that can be uniquely identified by a raw transaction id
*/
public protocol RawIdentifiable {
var rawTransactionId: Data? { get set }
}
/**
Attributes that a Mined transaction must have
*/
public protocol MinedTransactionEntity: AbstractTransaction, RawIdentifiable {
/**
height on which this transaction was mined at. Convention is that -1 is retuned when it has not been mined yet
*/
var minedHeight: Int { get set }
/**
internal note id that is involved on this transaction
*/
var noteId: Int { get set }
/**
block time in in reference since 1970
*/
var blockTimeInSeconds: TimeInterval { get set }
/**
internal index for this transaction
*/
var transactionIndex: Int { get set }
}
public protocol ConfirmedTransactionEntity: MinedTransactionEntity, SignedTransactionEntity {
/**
recipient address if available
*/
var toAddress: String? { get set }
/**
expiration height for this transaction
*/
var expiryHeight: BlockHeight? { get set }
}

View File

@ -8,7 +8,9 @@
import Foundation
public extension Data {
/**
Transforms the data info bytes into a Zcash hex transaction id
*/
func toHexStringTxId() -> String {
self.hexEncodedString().toTxIdString()
}

View File

@ -19,6 +19,9 @@ public enum InitializerError: Error {
case falseStart
}
/**
Represents a lightwallet instance endpoint to connect to
*/
public struct LightWalletEndpoint {
public var address: String
public var port: String
@ -27,7 +30,13 @@ public struct LightWalletEndpoint {
public var host: String {
"\(address):\(port)"
}
/**
initializes a LightWalletEndpoint
- Parameters:
- address: a String containing the host address
- port: string with the port of the host address
- secure: true if connecting through TLS. Default value is true
*/
public init(address: String, port: String, secure: Bool = true) {
self.address = address
self.port = port
@ -51,9 +60,24 @@ public class Initializer {
private(set) var spendParamsURL: URL
private(set) var outputParamsURL: URL
private var walletBirthday: WalletBirthday?
private(set) var lightWalletService: LightWalletService
private(set) var transactionRepository: TransactionRepository
private(set) var downloader: CompactBlockDownloader
private(set) var processor: CompactBlockProcessor?
/**
the LightWalletEndpoint that this initializer is connecting to
*/
public private(set) var endpoint: LightWalletEndpoint
/**
Constructs the Initializer
- Parameters:
- cacheDbURL: location of the compact blocks cache db
- dataDbURL: Location of the data db
- pendingDbURL: location of the pending transactions database
- endpoint: the endpoint representing the lightwalletd instance you want to point to
- spendParamsURL: location of the spend parameters
- outputParamsURL: location of the output parameters
*/
public init (cacheDbURL: URL, dataDbURL: URL, pendingDbURL: URL, endpoint: LightWalletEndpoint, spendParamsURL: URL, outputParamsURL: URL) {
self.cacheDbURL = cacheDbURL
self.dataDbURL = dataDbURL
@ -61,6 +85,13 @@ public class Initializer {
self.pendingDbURL = pendingDbURL
self.spendParamsURL = spendParamsURL
self.outputParamsURL = outputParamsURL
self.lightWalletService = LightWalletGRPCService(endpoint: endpoint)
self.transactionRepository = TransactionRepositoryBuilder.build(dataDbURL: dataDbURL)
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
try? storage.createTable()
self.downloader = CompactBlockDownloader(service: lightWalletService, storage: storage)
}
/**
@ -74,10 +105,10 @@ public class Initializer {
'compactBlockCache.db' and 'transactionData.db' files are created by this function (if they
do not already exist). These files can be given a prefix for scenarios where multiple wallets
operate in one app--for instance, when sweeping funds from another wallet seed.
- Parameter seedProvider the seed to use for initializing this wallet.
- Parameter walletBirthdayHeight the height corresponding to when the wallet seed was created. If null,
this signals that the wallet is being born.
- Parameter numberOfAccounts the number of accounts to create from this seed.
- Parameters:
- seedProvider: the seed to use for initializing this wallet.
- walletBirthdayHeight: the height corresponding to when the wallet seed was created. If null, this signals that the wallet is being born.
- numberOfAccounts: the number of accounts to create from this seed.
*/
public func initialize(seedProvider: SeedProvider, walletBirthdayHeight: BlockHeight, numberOfAccounts: Int = 1) throws -> [String]? {
@ -90,12 +121,11 @@ public class Initializer {
throw InitializerError.dataDbInitFailed
}
guard let birthday = WalletBirthday.birthday(with: walletBirthdayHeight) else {
self.walletBirthday = WalletBirthday.birthday(with: walletBirthdayHeight)
guard let birthday = self.walletBirthday else {
throw InitializerError.falseStart
}
self.walletBirthday = birthday
do {
try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(birthday.height), hash: birthday.hash, time: birthday.time, saplingTree: birthday.tree)
} catch RustWeldingError.dataDbNotEmpty {
@ -104,12 +134,12 @@ public class Initializer {
throw InitializerError.dataDbInitFailed
}
let downloader = CompactBlockStorage(url: cacheDbURL, readonly: true)
let lastDownloaded = (try? downloader.latestHeight()) ?? ZcashSDK.SAPLING_ACTIVATION_HEIGHT
let lastDownloaded = (try? downloader.storage.latestHeight()) ?? self.walletBirthday?.height ?? ZcashSDK.SAPLING_ACTIVATION_HEIGHT
// resume from last downloaded block
lowerBoundHeight = max(birthday.height, lastDownloaded)
self.processor = CompactBlockProcessorBuilder.buildProcessor(configuration: CompactBlockProcessor.Configuration(cacheDb: cacheDbURL, dataDb: dataDbURL, walletBirthday: walletBirthday?.height ?? self.lowerBoundHeight), downloader: self.downloader, backend: rustBackend)
guard let accounts = rustBackend.initAccountsTable(dbData: dataDbURL, seed: seedProvider.seed(), accounts: Int32(numberOfAccounts)) else {
throw rustBackend.lastError() ?? InitializerError.accountInitFailed
}
@ -117,29 +147,57 @@ public class Initializer {
return accounts
}
/**
get address from the given account index
- Parameter account: the index of the account
*/
public func getAddress(index account: Int = 0) -> String? {
rustBackend.getAddress(dbData: dataDbURL, account: Int32(account))
}
/**
get (unverified) balance from the given account index
- Parameter account: the index of the account
*/
public func getBalance(account index: Int = 0) -> Int64 {
rustBackend.getBalance(dbData: dataDbURL, account: Int32(index))
}
/**
get verified balance from the given account index
- Parameter account: the index of the account
*/
public func getVerifiedBalance(account index: Int = 0) -> Int64 {
rustBackend.getVerifiedBalance(dbData: dataDbURL, account: Int32(index))
}
// TODO: make internal
/**
checks if the provided address is a valid shielded zAddress
*/
public func isValidShieldedAddress(_ address: String) -> Bool {
(try? rustBackend.isValidShieldedAddress(address)) ?? false
}
/**
checks if the provided address is a transparent zAddress
*/
public func isValidTransparentAddress(_ address: String) -> Bool {
(try? rustBackend.isValidTransparentAddress(address)) ?? false
}
/**
underlying CompactBlockProcessor for this initializer
Although it is recommended to always use the higher abstraction first, if you need a more fine grained control over synchronization, you can use a CompactBlockProcessor instead of a Synchronizer.
*/
public func blockProcessor() -> CompactBlockProcessor? {
var configuration = CompactBlockProcessor.Configuration(cacheDb: cacheDbURL, dataDb: dataDbURL)
configuration.walletBirthday = walletBirthday?.height ?? self.lowerBoundHeight // check if this make sense
guard let downloader = CompactBlockDownloader.sqlDownloader(service: LightWalletGRPCService(endpoint: endpoint), at: self.cacheDbURL) else {
return nil
}
return CompactBlockProcessor(downloader: downloader, backend: self.rustBackend, config: configuration)
}
self.processor
}
}
class CompactBlockProcessorBuilder {
static func buildProcessor(configuration: CompactBlockProcessor.Configuration, downloader: CompactBlockDownloader, backend: ZcashRustBackendWelding.Type) -> CompactBlockProcessor {
return CompactBlockProcessor(downloader: downloader, backend: backend, config: configuration)
}
}
/**
@ -154,16 +212,17 @@ public class Initializer {
New wallets can ignore any blocks created before their birthday.
- Parameter height the height at the time the wallet was born
- Parameter hash the block hash corresponding to the given height
- Parameter time the time the wallet was born, in seconds
- Parameter tree the sapling tree corresponding to the given height. This takes around 15 minutes of processing to
- Parameters:
- height: the height at the time the wallet was born
- hash: the block hash corresponding to the given height
- time: the time the wallet was born, in seconds
- tree: the sapling tree corresponding to the given height. This takes around 15 minutes of processing to
generate from scratch because all blocks since activation need to be considered. So when it is calculated in
advance it can save the user a lot of time.
*/
public struct WalletBirthday {
var height: BlockHeight = -1
var hash: String = ""
var time: UInt32 = 0
var tree: String = ""
public private(set) var height: BlockHeight = -1
public private(set) var hash: String = ""
public private(set) var time: UInt32 = 0
public private(set) var tree: String = ""
}

View File

@ -17,7 +17,9 @@ public protocol ResourceProvider {
var cacheDbURL: URL { get }
}
/**
Convenience provider for a data db and cache db resources.
*/
public struct DefaultResourceProvider: ResourceProvider {
public var dataDbURL: URL {

View File

@ -7,7 +7,9 @@
//
import Foundation
/**
Describes an interface for an entity that provides seed bytes
*/
public protocol SeedProvider {
func seed() -> [UInt8]
}

View File

@ -7,7 +7,9 @@
//
import Foundation
/**
A Zcash compact block to store on cache DB
*/
public struct ZcashCompactBlock: CompactBlockEntity {
public var height: BlockHeight
public var data: Data
@ -26,18 +28,27 @@ protocol CompactBlockRepository {
/**
Gets the highest block that is currently stored.
Non-Blocking
- Parameter result: closure resulting on either the latest height or an error
*/
func latestHeight(result: @escaping (Result<BlockHeight,Error>) -> Void)
/**
Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
Blocking
- Parameter blocks: the compact blocks that will be written to storage
- Throws: an error when there's a failure
*/
func write(blocks: [ZcashCompactBlock]) throws
/**
Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
Non-Blocking
- Parameters:
- Parameter blocks: array of blocks to be written to storage
- Parameter completion: a closure that will be called after storing the blocks
*/
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?)
@ -45,8 +56,10 @@ protocol CompactBlockRepository {
/**
Remove every block above and including the given height.
After this operation, the data store will look the same as one that has not yet stored the given block height.
After this operation, the data store will look the same as one that has not yet stored the given block height.
Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49.
- Parameter height: the height to rewind to
*/
func rewind(to height: BlockHeight) throws
@ -57,6 +70,10 @@ protocol CompactBlockRepository {
After this operation, the data store will look the same as one that has not yet stored the given block height.
Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49.
- Parameters:
- Parameter height: the height to rewind to
- Parameter completion: a closure that will be called after storing the blocks
*/
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?)
}

View File

@ -11,12 +11,16 @@ class TransactionRepositoryBuilder {
static func build(initializer: Initializer) -> TransactionRepository {
TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.dataDbURL.path, readonly: true))
}
static func build(dataDbURL: URL) -> TransactionRepository {
TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: true))
}
}
class PagedTransactionRepositoryBuilder {
static func build(initializer: Initializer, kind: TransactionKind = .all) -> PaginatedTransactionRepository {
let txRepository = TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.dataDbURL.path, readonly: true))
return PagedTransactionDAO(repository: txRepository, kind: kind)
return PagedTransactionDAO(repository: initializer.transactionRepository, kind: kind)
}
}

View File

@ -10,12 +10,12 @@ import Foundation
extension Data {
func asZcashTransactionMemo() -> String? {
return nil
return String(bytes: self, encoding: .utf8)
}
}
extension String {
func encodeAsZcashTransactionMemo() -> Data? {
return nil
return self.data(using: .utf8)
}
}

View File

@ -12,7 +12,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
static func lastError() -> RustWeldingError? {
guard let message = getLastError() else { return nil }
zcashlc_clear_last_error()
if message.contains("couldn't load Sapling spend parameters") {
return RustWeldingError.saplingSpendParametersNotFound
}
@ -32,8 +32,8 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
/**
* Sets up the internal structure of the data database.
*/
* Sets up the internal structure of the data database.
*/
static func initDataDb(dbData: URL) throws {
let dbData = dbData.osStr()
guard zcashlc_init_data_database(dbData.0, dbData.1) != 0 else {
@ -43,14 +43,34 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.dataDbInitFailed(message: "unknown error")
}
}
static func isValidShieldedAddress(_ address: String) throws -> Bool {
guard zcashlc_is_valid_shielded_address([CChar](address.utf8CString)) else {
if let error = lastError() {
throw error
}
return false
}
return true
}
static func isValidTransparentAddress(_ address: String) throws -> Bool {
guard zcashlc_is_valid_transparent_address([CChar](address.utf8CString)) else {
if let error = lastError() {
throw error
}
return false
}
return true
}
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]? {
let dbData = dbData.osStr()
let extsksCStr = zcashlc_init_accounts_table(dbData.0, dbData.1, seed, UInt(seed.count), accounts)
if extsksCStr == nil {
return nil
}
let extsks = UnsafeBufferPointer(start: extsksCStr, count: Int(accounts)).compactMap({ (cStr) -> String? in
guard let str = cStr else { return nil }
return String(cString: str)
@ -58,7 +78,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
zcashlc_vec_string_free(extsksCStr, UInt(accounts))
return extsks
}
static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) throws {
let dbData = dbData.osStr()
guard zcashlc_init_blocks_table(dbData.0, dbData.1, height, [CChar](hash.utf8CString), time, [CChar](saplingTree.utf8CString)) != 0 else {
@ -68,64 +88,64 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.dataDbInitFailed(message: "Unknown Error")
}
}
static func getAddress(dbData: URL, account: Int32) -> String? {
let dbData = dbData.osStr()
guard let addressCStr = zcashlc_get_address(dbData.0, dbData.1, account) else { return nil }
let address = String(validatingUTF8: addressCStr)
zcashlc_string_free(addressCStr)
return address
}
static func getBalance(dbData: URL, account: Int32) -> Int64 {
let dbData = dbData.osStr()
return zcashlc_get_balance(dbData.0, dbData.1, account)
}
static func getVerifiedBalance(dbData: URL, account: Int32) -> Int64 {
let dbData = dbData.osStr()
return zcashlc_get_verified_balance(dbData.0, dbData.1, account)
}
static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
let dbData = dbData.osStr()
guard let memoCStr = zcashlc_get_received_memo_as_utf8(dbData.0, dbData.1, idNote) else { return nil }
let memo = String(validatingUTF8: memoCStr)
zcashlc_string_free(memoCStr)
return memo
}
static func getSentMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
let dbData = dbData.osStr()
guard let memoCStr = zcashlc_get_sent_memo_as_utf8(dbData.0, dbData.1, idNote) else { return nil }
let memo = String(validatingUTF8: memoCStr)
zcashlc_string_free(memoCStr)
return memo
}
static func validateCombinedChain(dbCache: URL, dbData: URL) -> Int32 {
let dbCache = dbCache.osStr()
let dbData = dbData.osStr()
return zcashlc_validate_combined_chain(dbCache.0, dbCache.1, dbData.0, dbData.1)
}
static func rewindToHeight(dbData: URL, height: Int32) -> Bool {
let dbData = dbData.osStr()
return zcashlc_rewind_to_height(dbData.0, dbData.1, height) != 0
}
static func scanBlocks(dbCache: URL, dbData: URL) -> Bool {
let dbCache = dbCache.osStr()
let dbData = dbData.osStr()
return zcashlc_scan_blocks(dbCache.0, dbCache.1, dbData.0, dbData.1) != 0
}
static func createToAddress(dbData: URL, account: Int32, extsk: String, to: String, value: Int64, memo: String?, spendParamsPath: String, outputParamsPath: String) -> Int64 {
let dbData = dbData.osStr()
let memoBytes = memo ?? ""
@ -133,6 +153,53 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return zcashlc_create_to_address(dbData.0, dbData.1, account, extsk, to, value, memoBytes, spendParamsPath, UInt(spendParamsPath.lengthOfBytes(using: .utf8)), outputParamsPath, UInt(outputParamsPath.lengthOfBytes(using: .utf8)))
}
static func deriveExtendedFullViewingKey(_ spendingKey: String) throws -> String? {
guard let extsk = zcashlc_derive_extended_full_viewing_key([CChar](spendingKey.utf8CString)) else {
if let error = lastError() {
throw error
}
return nil
}
let derived = String(validatingUTF8: extsk)
zcashlc_string_free(extsk)
return derived
}
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32) throws -> [String]? {
guard let extsksCStr = zcashlc_derive_extended_full_viewing_keys(seed, UInt(seed.lengthOfBytes(using: .utf8)), accounts) else {
if let error = lastError() {
throw error
}
return nil
}
let extsks = UnsafeBufferPointer(start: extsksCStr, count: Int(accounts)).compactMap({ (cStr) -> String? in
guard let str = cStr else { return nil }
return String(cString: str)
})
zcashlc_vec_string_free(extsksCStr, UInt(accounts))
return extsks
}
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32) throws -> [String]? {
guard let extsksCStr = zcashlc_derive_extended_spending_keys(seed, UInt(seed.lengthOfBytes(using: .utf8)), accounts) else {
if let error = lastError() {
throw error
}
return nil
}
let extsks = UnsafeBufferPointer(start: extsksCStr, count: Int(accounts)).compactMap({ (cStr) -> String? in
guard let str = cStr else { return nil }
return String(cString: str)
})
zcashlc_vec_string_free(extsksCStr, UInt(accounts))
return extsks
}
}
private extension ZcashRustBackend {

View File

@ -20,43 +20,111 @@ public struct ZcashRustBackendWeldingConstants {
}
public protocol ZcashRustBackendWelding {
/**
gets the latest error if available
*/
static func lastError() -> RustWeldingError?
/**
gets the latest error message from librustzcash
*/
static func getLastError() -> String?
/**
initializes the data db
- Parameter dbData: location of the data db sql file
*/
static func initDataDb(dbData: URL) throws
/**
- Returns: true when the address is valid and shielded. Returns false in any other case
- Throws: Error when the provided address belongs to another network
*/
static func isValidShieldedAddress(_ address: String) throws -> Bool
/**
- Returns: true when the address is valid and transparent. false in any other case
- Throws: Error when the provided address belongs to another network
*/
static func isValidTransparentAddress(_ address: String) throws -> Bool
/**
initialize the blocks table from a given checkpoint (birthday)
- Parameters:
- dbData: location of the data db
- seed: byte array of the zip32 seed
- accounts: how many accounts you want to have
*/
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]?
/**
initialize the blocks table from a given checkpoint (birthday)
- Parameters:
- dbData: location of the data db
- height: represents the block height of the given checkpoint
- hash: hash of the merkle tree
- time: in milliseconds from reference
- saplingTree: hash of the sapling tree
*/
static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) throws
/**
gets the address from data db from the given account
- Parameters:
- dbData: location of the data db
- account: index of the given account
- Returns: an optional string with the address if found
*/
static func getAddress(dbData: URL, account: Int32) -> String?
/**
get the (unverified) balance from the given account
- Parameters:
- dbData: location of the data db
- account: index of the given account
*/
static func getBalance(dbData: URL, account: Int32) -> Int64
/**
get the verified balance from the given account
- Parameters:
- dbData: location of the data db
- account: index of the given account
*/
static func getVerifiedBalance(dbData: URL, account: Int32) -> Int64
/**
get received memo from note
- Parameters:
- dbData: location of the data db file
- idNote: note_id of note where the memo is located
*/
static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64) -> String?
/**
get sent memo from note
- Parameters:
- dbData: location of the data db file
- idNote: note_id of note where the memo is located
*/
static func getSentMemoAsUTF8(dbData: URL, idNote: Int64) -> String?
/**
* 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.
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.
- Important: This function does not mutate either of the databases.
*/
static func validateCombinedChain(dbCache: URL, dbData: URL) -> Int32
/**
rewinds the compact block storage to the given height. clears up all derived data as well
- Parameters:
- dbData: location of the data db file
- height: height to rewind to
*/
static func rewindToHeight(dbData: URL, height: Int32) -> Bool
/**
@ -71,6 +139,11 @@ public protocol ZcashRustBackendWelding {
[`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.
- Parameters:
- dbCache: location of the compact block cache db
- dbData: location of the data db file
returns false if fails to scan.
*/
static func scanBlocks(dbCache: URL, dbData: URL) -> Bool
@ -87,5 +160,30 @@ public protocol ZcashRustBackendWelding {
- outputParamsPath: path escaped String for the filesystem locations where the output paremeters are located
*/
static func createToAddress(dbData: URL, account: Int32, extsk: String, to: String, value: Int64, memo: String?, spendParamsPath: String, outputParamsPath: String) -> Int64
/**
Derives a full viewing key from a seed
- Parameter spendingKey: a string containing the spending key
- Returns: the derived key
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedFullViewingKey(_ spendingKey: String) throws -> String?
/**
Derives a set of full viewing keys from a seed
- Parameter spendingKey: a string containing the spending key
- Parameter accounts: the number of accounts you want to derive from this seed
- Returns: an array containing the derived keys
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32) throws -> [String]?
/**
Derives a set of full viewing keys from a seed
- Parameter seed: a string containing the seed
- Parameter accounts: the number of accounts you want to derive from this seed
- Returns: an array containing the spending keys
- Throws: RustBackendError if fatal error occurs
*/
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32) throws -> [String]?
}

View File

@ -9,6 +9,8 @@
import Foundation
import SwiftGRPC
/**
Swift GRPC implementation of Lightwalletd service */
public class LightWalletGRPCService {
var queue = DispatchQueue.init(label: "LightWalletGRPCService")
@ -111,7 +113,10 @@ extension LightWalletGRPCService: LightWalletService {
// TODO: Make cancellable
public func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
queue.async {
queue.async { [weak self] in
guard let self = self else { return }
var blocks = [CompactBlock]()
var isSyncing = true
guard let response = try? self.compactTxStreamer.getBlockRange(range.blockRange(),completion: { (callResult) in

View File

@ -9,6 +9,10 @@
import Foundation
import SwiftGRPC
import SwiftProtobuf
/**
Wrapper for errors received from a Lightwalletd endpoint
*/
public enum LightWalletServiceError: Error {
case generalError
case failed(statusCode: StatusCode, message: String)
@ -27,9 +31,9 @@ extension LightWalletServiceError: Equatable {
default:
return false
}
case .failed(let statusCode):
case .failed(let statusCode, _):
switch rhs {
case .failed(let anotherStatus):
case .failed(let anotherStatus, _):
return statusCode == anotherStatus
default:
return false
@ -86,13 +90,14 @@ public protocol LightWalletService {
For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5.
Non blocking
*/
func blockRange(_ range: Range<BlockHeight>, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void )
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void )
/**
Return the given range of blocks.
- Parameter range: the inclusive range to fetch.
For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5.
For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5.
blocking
*/
@ -100,11 +105,16 @@ public protocol LightWalletService {
/**
Submits a raw transaction over lightwalletd. Non-Blocking
- Parameter spendTransaction: data representing the transaction to be sent
- Parameter result: escaping closure that takes a result containing either LightWalletServiceResponse or LightWalletServiceError
*/
func submit(spendTransaction: Data, result: @escaping(Result<LightWalletServiceResponse,LightWalletServiceError>) -> Void)
/**
Submits a raw transaction over lightwalletd. Blocking
- Parameter spendTransaction: data representing the transaction to be sent
- Throws: LightWalletServiceError
- Returns: LightWalletServiceResponse
*/
func submit(spendTransaction: Data) throws -> LightWalletServiceResponse

View File

@ -9,7 +9,7 @@
import Foundation
public typealias BlockHeight = Int
public typealias CompactBlockRange = Range<BlockHeight>
public typealias CompactBlockRange = ClosedRange<BlockHeight>
enum ZcashCompactBlockError: Error {
case unreadableBlock(compactBlock: CompactBlock)

View File

@ -6,9 +6,19 @@ public extension WalletBirthday {
static func birthday(with height: BlockHeight) -> WalletBirthday? {
switch height {
case 419_200:
return WalletBirthday(height: 419_200, hash: "00000000025a57200d898ac7f21e26bf29028bbe96ec46e05b2c17cc9db9e4f3", time: 1540779337, tree: "000000")
return WalletBirthday(
height: 419_200,
hash: "00000000025a57200d898ac7f21e26bf29028bbe96ec46e05b2c17cc9db9e4f3",
time: 1540779337,
tree: "000000"
)
case 643_500:
return WalletBirthday(height: 643_500, hash: "000000000041005fd724ff6e29bd1738bed69a4d9ca028e124029525350bd789", time: 1574579149, tree: "01999fc372390699b15f71d41745abe6a2ea0db4ffa8894d3c5fe30b9261a1a43a01585112668685bd6783cb01b72d17dc86c6d740c27cccf66b75e959e4e4f5ea3710019b7f6b4457a97eadbe1a39bfcc6ba0a56d37010d0d799e1e652fc29733103e04016a0b4d2705e1feb2021d80e5785608536dde05aea5ef676a5427244228b19e2d00010973d03ad5f79fcac64ab3ffbdaaac1a24b74a3617770bf960fb004cbd422439000001984bfce9361025cc38574f944a3ed7b074b3bf88cfce6f14c4a9be4d91d6dc730105871ec1e3737a39bceb00b0c2d253ff36f472e92c361e7ef360d49ea8dc4c4200000001c145105e1bf401668a8f23ca70c47ee92d23bd366072020c83d26b855eeafd6d0001fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13")
return WalletBirthday(
height: 643_500,
hash: "000000000041005fd724ff6e29bd1738bed69a4d9ca028e124029525350bd789",
time: 1574579149,
tree: "01999fc372390699b15f71d41745abe6a2ea0db4ffa8894d3c5fe30b9261a1a43a01585112668685bd6783cb01b72d17dc86c6d740c27cccf66b75e959e4e4f5ea3710019b7f6b4457a97eadbe1a39bfcc6ba0a56d37010d0d799e1e652fc29733103e04016a0b4d2705e1feb2021d80e5785608536dde05aea5ef676a5427244228b19e2d00010973d03ad5f79fcac64ab3ffbdaaac1a24b74a3617770bf960fb004cbd422439000001984bfce9361025cc38574f944a3ed7b074b3bf88cfce6f14c4a9be4d91d6dc730105871ec1e3737a39bceb00b0c2d253ff36f472e92c361e7ef360d49ea8dc4c4200000001c145105e1bf401668a8f23ca70c47ee92d23bd366072020c83d26b855eeafd6d0001fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13"
)
default:
return nil
}
@ -16,22 +26,103 @@ public extension WalletBirthday {
}
{% else %}
public extension WalletBirthday {
static func birthday(with height: BlockHeight) -> WalletBirthday? {
static func birthday(with height: BlockHeight) -> WalletBirthday {
switch height {
case 280_000:
return WalletBirthday(height: 280000, hash: "000420e7fcc3a49d729479fb0b560dd7b8617b178a08e9e389620a9d1dd6361a", time: 1535262293, tree: "000000")
case 421720:
return WalletBirthday(height: 421720, hash: "001ede53476a31a91da3313eddf4e41409fb7f4e003840700557b576024d09b4", time: 1550762014, tree: "015495a30aef9e18b9c774df6a9fcd583748c8bba1a6348e70f59bc9f0c2bc673b000f00000000018054b75173b577dc36f2c80dfc41f83d6716557597f74ec54436df32d4466d57000120f1825067a52ca973b07431199d5866a0d46ef231d08aa2f544665936d5b4520168d782e3d028131f59e9296c75de5a101898c5e53108e45baa223c608d6c3d3d01fb0a8d465b57c15d793c742df9470b116ddf06bd30d42123fdb7becef1fd63640001a86b141bdb55fd5f5b2e880ea4e07caf2bbf1ac7b52a9f504977913068a917270001dd960b6c11b157d1626f0768ec099af9385aea3f31c91111a8c5b899ffb99e6b0192acd61b1853311b0bf166057ca433e231c93ab5988844a09a91c113ebc58e18019fbfd76ad6d98cafa0174391546e7022afe62e870e20e16d57c4c419a5c2bb69")
case 425865:
return WalletBirthday(height: 425865, hash: "0011c4de26004e564347b8af218ca16cd07b08c4159b1cc9c43afa6cb8807bed", time: 1551215770, tree: "01881e4da7e4767ee8a144a32ab8a5719a513bb05854477773bb55e6cd7f15055201f8a99a3a5ae3528ec2fc0bda9652b6728aecb08bf364e06ac511fd6654d782720f019ef0b9bdd075c38519fa4ab8210fe7e94c609f52672796e33e3cab58b1602831000001f803bf338ff1526b2ca527288974cb9be3fe240a2eadb7507e46ba59eaddb9320129fc0148ac088a6aa509f8f64ef79fda92232020369b58a12b32c05b6f428f22015e3dd0950c442940bd015c2176f7c817f22104f54c61159727483188c539dc13000000013589be9e2d9e9e38fd78b1e8eaec5b5f5167bf7fd2b1c95c316fa366a24cac4c01a86b141bdb55fd5f5b2e880ea4e07caf2bbf1ac7b52a9f504977913068a917270001dd960b6c11b157d1626f0768ec099af9385aea3f31c91111a8c5b899ffb99e6b0192acd61b1853311b0bf166057ca433e231c93ab5988844a09a91c113ebc58e18019fbfd76ad6d98cafa0174391546e7022afe62e870e20e16d57c4c419a5c2bb69")
case 518000:
return WalletBirthday(height: 518000, hash: "000ba586d734c295f0bc034be229b1c96cb040f9d4929efdb5d2b187eeb238fb", time: 1560645743, tree: "01a4f5240a88a6eb4ffbda7961a1430506aad1a50ba011593f02c243d968feb0550010000140f91773b4ab669846e5bcb96f60e68256c49a27872a98e9d5ce50b30a0c434e0000018968663d6a7b444591de83f8a07223113f5de7e8203807adacc7677c3bcd4f420194c7ecac0ef6d702d475680ec32051fdf6368af0c459ab450009c001bcbf7a5300000001f0eead5192c3b3ab7208429877570676647e448210332c6da7e18660b142b80e01b98b14cab05247195b3b3be3dd8639bae99a0dd10bed1282ac25b62a134afd7200000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39")
case 523240:
return WalletBirthday(height: 523240, hash: "00000c33da2196f0ed1bda71043f671fc69a0212e01f892653e212ab358f6b79", time: 1561002603, tree: "01d3e02bc1c2d66762f370b329a3063067701ad66c44b40285686bc8ff25f5616f00100154bff87bd0bda3b70a6d7754eca261de15fee3cd9bc53073a232e07fc3261e27000001a54dcaccb4c5e578aef89f2a3b4e3c3d8a487e6e904c5da5916118d721948d07000000000118fa9c6fef4963049dc7002a13bb0021d5e950591e48c9e5f2cbd1199429b80401f0eead5192c3b3ab7208429877570676647e448210332c6da7e18660b142b80e01b98b14cab05247195b3b3be3dd8639bae99a0dd10bed1282ac25b62a134afd7200000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39")
case 620000:
return WalletBirthday(height: 620000, hash: "005f97953c8e1265d6b45f4435ffa32918e53e8f0025c286a4080c3eab167197", time: 1569572035, tree: "0170cf036ea1ea3c6e08432e18b6a372ca0b8b83671cc13ab0cf9e28c182f6c36f00100000013f3fc2c16ac4780f1c472ca65534ab08911f325a9edde5ea7f24364b47c9a95300017621b12e518cbbbdb7511ab423e0bddda412ed61ed3cff5be2140de65d6a0069010576153a5a2098812e7a028c37c3398e186f398c9b07bc199784ab97e5535c3e0000019a6ce2f0f7dbb2de493a315abf62d8ca96ccc701f116b6ddfae33870a2183d3c01c9d3564eff54ebc328eab2e4f1150c3637f4f47516f879a0cfebdf49fe7b1d5201c104705fac60a85596010e41260d07f3a64f38f37a112eaef41cd9d736edc5270145e3d4899fcd7f0f1236ae31eafb3f4b65ad6b11a17eae1729cec09bd3afa01a000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39")
case 280_000 ..< 421720:
return WalletBirthday(
height: 280000,
hash: "000420e7fcc3a49d729479fb0b560dd7b8617b178a08e9e389620a9d1dd6361a",
time: 1535262293,
tree: "000000"
)
case 421720 ..< 425865:
return WalletBirthday(
height: 421720,
hash: "001ede53476a31a91da3313eddf4e41409fb7f4e003840700557b576024d09b4",
time: 1550762014,
tree: "015495a30aef9e18b9c774df6a9fcd583748c8bba1a6348e70f59bc9f0c2bc673b000f00000000018054b75173b577dc36f2c80dfc41f83d6716557597f74ec54436df32d4466d57000120f1825067a52ca973b07431199d5866a0d46ef231d08aa2f544665936d5b4520168d782e3d028131f59e9296c75de5a101898c5e53108e45baa223c608d6c3d3d01fb0a8d465b57c15d793c742df9470b116ddf06bd30d42123fdb7becef1fd63640001a86b141bdb55fd5f5b2e880ea4e07caf2bbf1ac7b52a9f504977913068a917270001dd960b6c11b157d1626f0768ec099af9385aea3f31c91111a8c5b899ffb99e6b0192acd61b1853311b0bf166057ca433e231c93ab5988844a09a91c113ebc58e18019fbfd76ad6d98cafa0174391546e7022afe62e870e20e16d57c4c419a5c2bb69"
)
case 425865 ..< 518000:
return WalletBirthday(
height: 425865,
hash: "0011c4de26004e564347b8af218ca16cd07b08c4159b1cc9c43afa6cb8807bed",
time: 1551215770,
tree: "01881e4da7e4767ee8a144a32ab8a5719a513bb05854477773bb55e6cd7f15055201f8a99a3a5ae3528ec2fc0bda9652b6728aecb08bf364e06ac511fd6654d782720f019ef0b9bdd075c38519fa4ab8210fe7e94c609f52672796e33e3cab58b1602831000001f803bf338ff1526b2ca527288974cb9be3fe240a2eadb7507e46ba59eaddb9320129fc0148ac088a6aa509f8f64ef79fda92232020369b58a12b32c05b6f428f22015e3dd0950c442940bd015c2176f7c817f22104f54c61159727483188c539dc13000000013589be9e2d9e9e38fd78b1e8eaec5b5f5167bf7fd2b1c95c316fa366a24cac4c01a86b141bdb55fd5f5b2e880ea4e07caf2bbf1ac7b52a9f504977913068a917270001dd960b6c11b157d1626f0768ec099af9385aea3f31c91111a8c5b899ffb99e6b0192acd61b1853311b0bf166057ca433e231c93ab5988844a09a91c113ebc58e18019fbfd76ad6d98cafa0174391546e7022afe62e870e20e16d57c4c419a5c2bb69"
)
case 518000 ..< 523240:
return WalletBirthday(
height: 518000,
hash: "000ba586d734c295f0bc034be229b1c96cb040f9d4929efdb5d2b187eeb238fb",
time: 1560645743,
tree: "01a4f5240a88a6eb4ffbda7961a1430506aad1a50ba011593f02c243d968feb0550010000140f91773b4ab669846e5bcb96f60e68256c49a27872a98e9d5ce50b30a0c434e0000018968663d6a7b444591de83f8a07223113f5de7e8203807adacc7677c3bcd4f420194c7ecac0ef6d702d475680ec32051fdf6368af0c459ab450009c001bcbf7a5300000001f0eead5192c3b3ab7208429877570676647e448210332c6da7e18660b142b80e01b98b14cab05247195b3b3be3dd8639bae99a0dd10bed1282ac25b62a134afd7200000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 523240 ..< 620000:
return WalletBirthday(
height: 523240,
hash: "00000c33da2196f0ed1bda71043f671fc69a0212e01f892653e212ab358f6b79",
time: 1561002603,
tree: "01d3e02bc1c2d66762f370b329a3063067701ad66c44b40285686bc8ff25f5616f00100154bff87bd0bda3b70a6d7754eca261de15fee3cd9bc53073a232e07fc3261e27000001a54dcaccb4c5e578aef89f2a3b4e3c3d8a487e6e904c5da5916118d721948d07000000000118fa9c6fef4963049dc7002a13bb0021d5e950591e48c9e5f2cbd1199429b80401f0eead5192c3b3ab7208429877570676647e448210332c6da7e18660b142b80e01b98b14cab05247195b3b3be3dd8639bae99a0dd10bed1282ac25b62a134afd7200000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 620000 ..< 680000:
return WalletBirthday(
height: 620000,
hash: "005f97953c8e1265d6b45f4435ffa32918e53e8f0025c286a4080c3eab167197",
time: 1569572035,
tree: "0170cf036ea1ea3c6e08432e18b6a372ca0b8b83671cc13ab0cf9e28c182f6c36f00100000013f3fc2c16ac4780f1c472ca65534ab08911f325a9edde5ea7f24364b47c9a95300017621b12e518cbbbdb7511ab423e0bddda412ed61ed3cff5be2140de65d6a0069010576153a5a2098812e7a028c37c3398e186f398c9b07bc199784ab97e5535c3e0000019a6ce2f0f7dbb2de493a315abf62d8ca96ccc701f116b6ddfae33870a2183d3c01c9d3564eff54ebc328eab2e4f1150c3637f4f47516f879a0cfebdf49fe7b1d5201c104705fac60a85596010e41260d07f3a64f38f37a112eaef41cd9d736edc5270145e3d4899fcd7f0f1236ae31eafb3f4b65ad6b11a17eae1729cec09bd3afa01a000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 680000 ..< 720000:
return WalletBirthday(
height: 680000,
hash: "00a4fbf54597d2f474f999576affad63f0ba2daa14c6fcd55c7eeec700107270",
time: 1573569367,
tree: "010a57f939a267f8b1e8b77288c783432e48fa95f7b22ead5e8ff46a788181453801d6457d98d3698a367aef4a2fe5675a575790d5d8081b731f979f0e64043fb7351001afaaf81d6d982b401444dbcf89e63c2583d234c1a60de17940a9b3a15f3494660001ba7acc730584a689413c44781d3b13cd497bfdca3fe27fb78cd9b50e9929906300000000000129e195df514840a20b95200b92d5b8d196b119cd6887508d8de077beffdbfe68016482af04b979e08e4e5760d55832292e55dbdd88143992f123840c8983db7b3f000001e3ec5d790cc9acc2586fc6e9ce5aae5f5aba32d33e386165c248c4a03ec8ed670000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 720000 ..< 740000:
return WalletBirthday(
height: 720000,
hash: "018b71dde8c9b1ee3e79961c6e3536c79226f5d6e79bb35c9ed28dcb8cb78b48",
time: 1575943651,
tree: "012cfde48dff4f20ddc50a7aca3746f7d77920eebc8cf4ef53feac34cb8719c03a012c668d234aaa485862e1d06e46d6d7093c2581e2b9cda90aafd691d6e325410610014f3a875476cb8159d46fec1aba18c201c268cd61b01811b7e5bf83998fb8222500011f19160cc75325c090f3eb3fa0cff2d94e43e2713c89e7b02a34f6ed08fbcd26000001edc05305223f7f2839abc1dc7d900468349577d6d6f5c182ef3a81a848753b5b0000000129cbee0c11a827718f126d9e037155a9e173ee2d2ecf57dc68f7b66437d44f7301a113bcf163405e4286bc080ac55aa68555d2c9e63334e7b9a5eb756872f14c470001e3ec5d790cc9acc2586fc6e9ce5aae5f5aba32d33e386165c248c4a03ec8ed670000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 740000 ..< 760000:
return WalletBirthday(
height: 740000,
hash: "00163c394a1e545b4c777fc91474d92becdd0fe9300d38fb3563997d026a54ea",
time: 1577389695,
tree: "0135ee37f83d0b924eed58bcceb249a4977dcb21495561b97bc747a272ebe5d6580010000001fe65cfbb8a76e0d29ac94e5ada80c80b607d1addfc287754f37ddef531eb122500013a1d604d978f716fcb2887cdaa3c582a608b795ed1b3c57998cbede2be479931000108ffb0fbc6b653305b37cd5568b85112b996cd514fe97ec73f7169dae0cef05101baf5541e853483315273c25c1956d233513c3dabd8741972136b2f8f8cd89035000129cbee0c11a827718f126d9e037155a9e173ee2d2ecf57dc68f7b66437d44f7301a113bcf163405e4286bc080ac55aa68555d2c9e63334e7b9a5eb756872f14c470001e3ec5d790cc9acc2586fc6e9ce5aae5f5aba32d33e386165c248c4a03ec8ed670000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 760000 ..< 780000:
return WalletBirthday(
height: 760000,
hash: "005ee769f3adbd0b24a63e8e4047200e038c38df277544d40c23ac1a88c1f37b",
time: 1578766903,
tree: "01470a1a4d29374e074f07a646b95dd89892d9b84d235a89ecc9d5a52beadce901001001311898ce56df0ddb10dc573a54ea06d11415e72602daa80c01f888fbd4a9a734018610afa4925cdb8bd4dd75a53ab69a74080322cae53c630ac02ce00b09d3171601bdfbbde5011bd6c0620ed2db3e01d5daa2ff8bb5f3b58687d265dd33a5681d530197e268c82e56dfc62aeb54586a2000766da8078f09a2d15fb558ad05664b4c5301aa204407034ba59fce0eee6518688585e96b0f10befd595b8e68a8ae15328a51017389398c5634242b03ef811f6abf224df9e6fc6d4393139e526dca4cf44dcc2c0000017de4c2c210c617dc61a43e124cab93e4f6143e1e9e46c2e55a541a9781cde43c0129cbee0c11a827718f126d9e037155a9e173ee2d2ecf57dc68f7b66437d44f7301a113bcf163405e4286bc080ac55aa68555d2c9e63334e7b9a5eb756872f14c470001e3ec5d790cc9acc2586fc6e9ce5aae5f5aba32d33e386165c248c4a03ec8ed670000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 780000 ..< 798181:
return WalletBirthday(
height: 780000,
hash: "0104238cc440b6bf05b86a1b00d794c6d88fc61b8c416124a971fb5ce94b91e6",
time: 1580205978,
tree: "011ed883b7eddb4783eee5b73ceee4c78413e1f6f9db3d88d1007f5fa62292955c001001861feaeab59bc31cd97ffc89467877abf8b9fa157bc875907eb90d6e8c723325000000000001ccc21a1d581eb6d3f35729f202f0014a59b4f9d41d92d44316b381f57dc8356b01ec0f418f21af87c0e0846b318be838bd181f25b708ee2b2fa030468399fb7932017de4c2c210c617dc61a43e124cab93e4f6143e1e9e46c2e55a541a9781cde43c0129cbee0c11a827718f126d9e037155a9e173ee2d2ecf57dc68f7b66437d44f7301a113bcf163405e4286bc080ac55aa68555d2c9e63334e7b9a5eb756872f14c470001e3ec5d790cc9acc2586fc6e9ce5aae5f5aba32d33e386165c248c4a03ec8ed670000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
case 798181 ... BlockHeight.max:
return WalletBirthday(
height: 798181,
hash: "02936aa4f8b6b2caad7cca19a866158656e37edfb0f32e2a473dc278fa634b71",
time: 1581521064,
tree: "01901ef0f4221f8a02d5896fda6f78cb5578fb8e9a5361d5689bfb3b6ab92072320010000101c5d791d8748f0ab0ecdb48764de29c470cc74f5d95b5a1f8cf0830bb059b66015af654e998c75460e9c1f5ba185589c53c383167e66951cfe7684321b7a77e5c013faf6b3f21f03982ada477bfddc95b18e63683c497b2f4630ab453623c28974c01aff3253410a6601ab4b5b3626f2121cb77399c634b1774a4c63f415598d16b0c01a6158d0a1a1bec9dadc601b140a9fdfc7bdab7e232739a3cc0e0e46bbe3ef44301ccc21a1d581eb6d3f35729f202f0014a59b4f9d41d92d44316b381f57dc8356b01ec0f418f21af87c0e0846b318be838bd181f25b708ee2b2fa030468399fb7932017de4c2c210c617dc61a43e124cab93e4f6143e1e9e46c2e55a541a9781cde43c0129cbee0c11a827718f126d9e037155a9e173ee2d2ecf57dc68f7b66437d44f7301a113bcf163405e4286bc080ac55aa68555d2c9e63334e7b9a5eb756872f14c470001e3ec5d790cc9acc2586fc6e9ce5aae5f5aba32d33e386165c248c4a03ec8ed670000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
default:
return nil
return WalletBirthday(
height: 798181,
hash: "02936aa4f8b6b2caad7cca19a866158656e37edfb0f32e2a473dc278fa634b71",
time: 1581521064,
tree: "01901ef0f4221f8a02d5896fda6f78cb5578fb8e9a5361d5689bfb3b6ab92072320010000101c5d791d8748f0ab0ecdb48764de29c470cc74f5d95b5a1f8cf0830bb059b66015af654e998c75460e9c1f5ba185589c53c383167e66951cfe7684321b7a77e5c013faf6b3f21f03982ada477bfddc95b18e63683c497b2f4630ab453623c28974c01aff3253410a6601ab4b5b3626f2121cb77399c634b1774a4c63f415598d16b0c01a6158d0a1a1bec9dadc601b140a9fdfc7bdab7e232739a3cc0e0e46bbe3ef44301ccc21a1d581eb6d3f35729f202f0014a59b4f9d41d92d44316b381f57dc8356b01ec0f418f21af87c0e0846b318be838bd181f25b708ee2b2fa030468399fb7932017de4c2c210c617dc61a43e124cab93e4f6143e1e9e46c2e55a541a9781cde43c0129cbee0c11a827718f126d9e037155a9e173ee2d2ecf57dc68f7b66437d44f7301a113bcf163405e4286bc080ac55aa68555d2c9e63334e7b9a5eb756872f14c470001e3ec5d790cc9acc2586fc6e9ce5aae5f5aba32d33e386165c248c4a03ec8ed670000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
)
}
}
}

View File

@ -8,6 +8,9 @@
import Foundation
/**
Represents errors thrown by a Synchronizer
*/
public enum SynchronizerError: Error {
case initFailed
case syncFailed
@ -16,15 +19,13 @@ public enum SynchronizerError: Error {
/**
Primary interface for interacting with the SDK. Defines the contract that specific
implementations like [MockSynchronizer] and [SdkSynchronizer] fulfill. Given the language-level
support for coroutines, we favor their use in the SDK and incorporate that choice into this
contract.
implementations like SdkSynchronizer fulfill.
*/
public protocol Synchronizer {
/**
Starts this synchronizer within the given scope.
*
Implementations should leverage structured concurrency and
cancel all jobs when this scope completes.
*/
@ -37,40 +38,39 @@ public protocol Synchronizer {
func stop() throws
/**
Value representing the [Status] of this Synchronizer. As the status changes, a new
Value representing the Status of this Synchronizer. As the status changes, a new
value will be emitted by KVO
*/
var status: Status { get }
/**
A flow of progress values, typically corresponding to this Synchronizer downloading blocks.
Typically, any non- zero value below 1.0 indicates that progress indicators can be shown and
Typically, any non-zero value below 1.0 indicates that progress indicators can be shown and
a value of 1.0 signals that progress is complete and any progress indicators can be hidden. KVO Compliant
*/
var progress: Float { get }
/**
Gets the address for the given account.
- Parameter accountIndex the optional accountId whose address is of interest. By default, the first account is used.
- Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
*/
func getAddress(accountIndex: Int) -> String
/**
Sends zatoshi.
- Parameter spendingKey the key that allows spends to occur.
- Parameter zatoshi the amount of zatoshi to send.
- Parameter toAddress the recipient's address.
- Parameter memo the optional memo to include as part of the transaction.
- Parameter accountIndex the optional account id to use. By default, the first account is used.
- Parameter spendingKey: the key that allows spends to occur.
- Parameter zatoshi: the amount of zatoshi to send.
- Parameter toAddress: the recipient's address.
- Parameter memo: the optional memo to include as part of the transaction.
- Parameter accountIndex: the optional account id to use. By default, the first account is used.
*/
func sendToAddress(spendingKey: String, zatoshi: Int64, toAddress: String, memo: String?, from accountIndex: Int, resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void)
/**
Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
an option if the transaction has not yet been submitted to the server.
- Parameter transaction the transaction to cancel.
- Returns true when the cancellation request was successful. False when it is too late.
- Parameter transaction: the transaction to cancel.
- Returns: true when the cancellation request was successful. False when it is too late.
*/
func cancelSpend(transaction: PendingTransactionEntity) -> Bool
@ -94,11 +94,15 @@ public protocol Synchronizer {
/**
a repository serving transactions in a paginated manner
- Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository
*/
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
}
/**
The Status of the synchronizer
*/
public enum Status {
/**
@ -126,6 +130,9 @@ public enum Status {
case synced
}
/**
Kind of transactions handled by a Synchronizer
*/
public enum TransactionKind {
case sent
case received

View File

@ -69,7 +69,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
result(.failure(TransactionManagerError.notPending(tx: pendingTransaction)))// this transaction is not stored
return
}
// FIX: change to async when librustzcash is updated to v6
queue.async { [weak self] in
guard let self = self else { return }
@ -189,7 +189,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
class OutboundTransactionManagerBuilder {
static func build(initializer: Initializer) throws -> OutboundTransactionManager {
return PersistentTransactionManager(encoder: TransactionEncoderbuilder.build(initializer: initializer), service: LightWalletGRPCService(endpoint: initializer.endpoint), repository: try PendingTransactionRepositoryBuilder.build(initializer: initializer))
return PersistentTransactionManager(encoder: TransactionEncoderbuilder.build(initializer: initializer), service: initializer.lightWalletService, repository: try PendingTransactionRepositoryBuilder.build(initializer: initializer))
}
}

View File

@ -23,6 +23,15 @@ protocol TransactionEncoder {
doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
double-bangs for things).
Blocking
- Parameters:
- Parameter spendingKey: a string containing the spending key
- Parameter zatoshi: the amount to send in zatoshis
- Parameter to: string containing the recipient address
- Parameter memo: string containin the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds
- Throws: a TransactionEncoderError
*/
func createTransaction(spendingKey: String, zatoshi: Int, to: String, memo: String?, from accountIndex: Int) throws -> EncodedTransaction
@ -31,6 +40,14 @@ protocol TransactionEncoder {
doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
double-bangs for things).
Non-blocking
- Parameters:
- Parameter spendingKey: a string containing the spending key
- Parameter zatoshi: the amount to send in zatoshis
- Parameter to: string containing the recipient address
- Parameter memo: string containin the memo (optional)
- Parameter accountIndex: index of the account that will be used to send the funds
- Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or an TransactionEncoderError
*/
func createTransaction(spendingKey: String, zatoshi: Int, to: String, memo: String?, from accountIndex: Int, result: @escaping TransactionEncoderResultBlock)

View File

@ -35,7 +35,7 @@ class WalletTransactionEncoder: TransactionEncoder {
convenience init(initializer: Initializer) {
self.init(rust: initializer.rustBackend,
dataDb: initializer.dataDbURL,
repository: TransactionRepositoryBuilder.build(initializer: initializer),
repository: initializer.transactionRepository,
outputParams: initializer.outputParamsURL,
spendParams: initializer.spendParamsURL)

View File

@ -10,14 +10,52 @@ import Foundation
import UIKit
public extension Notification.Name {
static let transactionsUpdated = Notification.Name("SDKSyncronizerTransactionUpdates")
/**
Notification is posted whenever transactions are updated
- Important: not yet posted
*/
static let transactionsUpdated = Notification.Name("SDKSyncronizerTransactionUpdated")
/**
Posted when the synchronizer is started.
*/
static let synchronizerStarted = Notification.Name("SDKSyncronizerStarted")
/**
Posted when there are progress updates.
- Note: Query userInfo object for NotificationKeys.progress for Float progress percentage and NotificationKeys.blockHeight for the current progress height
*/
static let synchronizerProgressUpdated = Notification.Name("SDKSyncronizerProgressUpdated")
/**
Posted when the synchronizer is synced to latest height
*/
static let synchronizerSynced = Notification.Name("SDKSyncronizerSynced")
/**
Posted when the synchronizer is stopped
*/
static let synchronizerStopped = Notification.Name("SDKSyncronizerStopped")
/**
Posted when the synchronizer loses connection
*/
static let synchronizerDisconnected = Notification.Name("SDKSyncronizerDisconnected")
/**
Posted when the synchronizer starts syncing
*/
static let synchronizerSyncing = Notification.Name("SDKSyncronizerSyncing")
/**
Posted when the synchronizer finds a mined transaction
- Note: query userInfo on NotificationKeys.minedTransaction for the transaction
*/
static let synchronizerMinedTransaction = Notification.Name("synchronizerMinedTransaction")
/**
Posted when the synchronizer presents an error
- Note: query userInfo on NotificationKeys.error for an error
*/
static let synchronizerFailed = Notification.Name("SDKSynchronizerFailed")
}
@ -56,11 +94,15 @@ public class SDKSynchronizer: Synchronizer {
}
}
/**
Creates an SDKSynchronizer instance
- Parameter initializer: a wallet Initializer object
*/
public init(initializer: Initializer) throws {
self.status = .disconnected
self.initializer = initializer
self.transactionManager = try OutboundTransactionManagerBuilder.build(initializer: initializer)
self.transactionRepository = TransactionRepositoryBuilder.build(initializer: initializer)
self.transactionRepository = initializer.transactionRepository
}
deinit {
@ -69,7 +111,10 @@ public class SDKSynchronizer: Synchronizer {
self.blockProcessor = nil
self.taskIdentifier = .invalid
}
/**
Starts the synchronizer
- Throws: CompactBlockProcessorError when failures occur
*/
public func start() throws {
guard let processor = initializer.blockProcessor() else {
@ -88,6 +133,10 @@ public class SDKSynchronizer: Synchronizer {
}
/**
Stops the synchronizer
- Throws: CompactBlockProcessorError when failures occur
*/
public func stop() throws {
guard status != .stopped, status != .disconnected else { return }
@ -96,6 +145,7 @@ public class SDKSynchronizer: Synchronizer {
processor.stop(cancelTasks: true)
}
// MARK: event subscription
private func subscribeToAppDelegateNotifications() {
// todo: ios 13 platform specific
@ -223,31 +273,50 @@ public class SDKSynchronizer: Synchronizer {
}
@objc func processorStartedDownloading(_ notification: Notification) {
DispatchQueue.main.async { self.status = .syncing }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.status = .syncing
}
}
@objc func processorStartedValidating(_ notification: Notification) {
DispatchQueue.main.async { self.status = .syncing }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.status = .syncing
}
}
@objc func processorStartedScanning(_ notification: Notification) {
DispatchQueue.main.async { self.status = .syncing }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.status = .syncing
}
}
@objc func processorStopped(_ notification: Notification) {
DispatchQueue.main.async { self.status = .stopped }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.status = .stopped
}
}
@objc func processorFailed(_ notification: Notification) {
DispatchQueue.main.async { self.status = .disconnected }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.status = .disconnected
}
}
@objc func processorIdle(_ notification: Notification) {
DispatchQueue.main.async { self.status = .disconnected }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.status = .disconnected
}
}
@objc func processorFinished(_ notification: Notification) {
DispatchQueue.global().async {
DispatchQueue.global().async {[ weak self ] in
guard let self = self else { return }
self.refreshPendingTransactions()
DispatchQueue.main.async {
self.status = .synced
@ -358,7 +427,7 @@ public class SDKSynchronizer: Synchronizer {
}
public func allSentTransactions() throws -> [ConfirmedTransactionEntity] {
try transactionRepository.findAllReceivedTransactions(offset: 0, limit: Int.max) ?? [ConfirmedTransactionEntity]()
try transactionRepository.findAllSentTransactions(offset: 0, limit: Int.max) ?? [ConfirmedTransactionEntity]()
}
public func paginatedTransactions(of kind: TransactionKind = .all) -> PaginatedTransactionRepository {
@ -405,7 +474,7 @@ public class SDKSynchronizer: Synchronizer {
private func removeConfirmedTransactions() throws {
let latestHeight = try transactionRepository.lastScannedHeight()
try transactionManager.allPendingTransactions()?.filter( { abs($0.minedHeight - latestHeight) >= ZcashSDK.DEFAULT_REWIND_DISTANCE } ).forEach( { try transactionManager.delete(pendingTransaction: $0) } )
try transactionManager.allPendingTransactions()?.filter( { $0.minedHeight > 0 && abs($0.minedHeight - latestHeight) >= ZcashSDK.DEFAULT_REWIND_DISTANCE } ).forEach( { try transactionManager.delete(pendingTransaction: $0) } )
}
private func refreshPendingTransactions() {
@ -419,12 +488,18 @@ public class SDKSynchronizer: Synchronizer {
private func notifyMinedTransaction(_ tx: PendingTransactionEntity) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else { return }
NotificationCenter.default.post(name: Notification.Name.synchronizerMinedTransaction, object: self, userInfo: [NotificationKeys.minedTransaction : tx])
}
}
private func notifyFailure(_ error: Error) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else { return }
NotificationCenter.default.post(name: Notification.Name.synchronizerFailed, object: self, userInfo: [NotificationKeys.error : error])
}
}
@ -448,4 +523,3 @@ extension SDKSynchronizer {
(try? self.allReceivedTransactions()) ?? [ConfirmedTransactionEntity]()
}
}

View File

@ -28,6 +28,16 @@ int64_t zcashlc_create_to_address(const uint8_t *db_data,
const uint8_t *output_params,
uintptr_t output_params_len);
char *zcashlc_derive_extended_full_viewing_key(const char *extsk);
char **zcashlc_derive_extended_full_viewing_keys(const uint8_t *seed,
uintptr_t seed_len,
int32_t accounts);
char **zcashlc_derive_extended_spending_keys(const uint8_t *seed,
uintptr_t seed_len,
int32_t accounts);
/**
* Copies the last error message into the provided allocated buffer.
*/
@ -99,6 +109,19 @@ int32_t zcashlc_init_blocks_table(const uint8_t *db_data,
*/
int32_t zcashlc_init_data_database(const uint8_t *db_data, uintptr_t db_data_len);
/**
* Returns true when the address is valid and shielded.
* Returns false in any other case
* Errors when the provided address belongs to another network
*/
bool zcashlc_is_valid_shielded_address(const char *address);
/**
* Returns true when the address is valid and transparent.
* Returns false in any other case
*/
bool zcashlc_is_valid_transparent_address(const char *address);
/**
* Returns the length of the last error message to be logged.
*/

View File

@ -47,7 +47,7 @@ class BlockScanOperationTests: XCTestCase {
let latestScannedBlockExpect = XCTestExpectation(description: self.description + "latestScannedHeight")
let service = LightWalletGRPCService(channel: ChannelProvider().channel())
let blockCount = 100
let range = ZcashSDK.SAPLING_ACTIVATION_HEIGHT ..< ZcashSDK.SAPLING_ACTIVATION_HEIGHT + blockCount
let range = ZcashSDK.SAPLING_ACTIVATION_HEIGHT ... ZcashSDK.SAPLING_ACTIVATION_HEIGHT + blockCount
let downloadOperation = CompactBlockDownloadOperation(downloader: CompactBlockDownloader.sqlDownloader(service: service, at: cacheDbURL)!, range: range)
let scanOperation = CompactBlockScanningOperation(rustWelding: rustWelding, cacheDb: cacheDbURL, dataDb: dataDbURL)
@ -88,7 +88,7 @@ class BlockScanOperationTests: XCTestCase {
latestScannedBlockOperation.completionBlock = {
latestScannedBlockExpect.fulfill()
XCTAssertEqual(latestScannedheight, range.endIndex)
XCTAssertEqual(latestScannedheight, range.upperBound)
}
latestScannedBlockOperation.addDependency(scanOperation)

View File

@ -24,7 +24,7 @@ class CompactBlockStorageTests: XCTestCase {
let finalHeight = startHeight + blockCount
do {
try TestDbBuilder.seed(db: compactBlockDao, with: CompactBlockRange(startHeight...finalHeight))
try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
} catch {
XCTFail("seed faild with error: \(error)")
return
@ -67,7 +67,7 @@ class CompactBlockStorageTests: XCTestCase {
let finalHeight = startHeight + blockCount
do {
try TestDbBuilder.seed(db: compactBlockDao, with: CompactBlockRange(startHeight...finalHeight))
try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
} catch {
XCTFail("seed faild with error: \(error)")
return

View File

@ -25,7 +25,7 @@ class DownloadOperationTests: XCTestCase {
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let downloader = CompactBlockDownloader(service: service, storage: storage)
let blockCount = 100
let range = ZcashSDK.SAPLING_ACTIVATION_HEIGHT ..< ZcashSDK.SAPLING_ACTIVATION_HEIGHT + blockCount
let range = ZcashSDK.SAPLING_ACTIVATION_HEIGHT ... ZcashSDK.SAPLING_ACTIVATION_HEIGHT + blockCount
let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range)
downloadOperation.completionHandler = { (finished, cancelled) in
@ -42,7 +42,7 @@ class DownloadOperationTests: XCTestCase {
wait(for: [expect], timeout: 10)
XCTAssertEqual(try! storage.latestHeight(),range.endIndex)
XCTAssertEqual(try! storage.latestHeight(),range.upperBound)
}
}

View File

@ -43,7 +43,7 @@ class LightWalletServiceTests: XCTestCase {
let count = 99
let lowerRange: BlockHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT
let upperRange: BlockHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT + count
let blockRange = Range<BlockHeight>(uncheckedBounds: (lower: lowerRange, upper: upperRange))
let blockRange = lowerRange ... upperRange
service.blockRange(blockRange) { (result) in
expect.fulfill()
@ -52,8 +52,9 @@ class LightWalletServiceTests: XCTestCase {
XCTFail("failed with error \(error)")
case .success(let blocks):
XCTAssertEqual(blocks.count, count)
XCTAssertEqual(blocks.count, blockRange.count)
XCTAssertEqual(blocks[0].height, lowerRange)
XCTAssertEqual(blocks.last!.height, upperRange)
}
}
@ -63,11 +64,11 @@ class LightWalletServiceTests: XCTestCase {
func testSyncBlockRange() {
let lowerRange: BlockHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT
let upperRange: BlockHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 99
let blockRange = CompactBlockRange(uncheckedBounds: (lower: lowerRange, upper: upperRange))
let blockRange = lowerRange ... upperRange
do {
let blocks = try service.blockRange(blockRange)
XCTAssertEqual(blocks.count, blockRange.count + 1)
XCTAssertEqual(blocks.count, blockRange.count)
} catch {
XCTFail("\(error)")
}

View File

@ -44,7 +44,7 @@ class WalletTests: XCTestCase {
struct SampleSeedProvider: SeedProvider {
func seed() -> [UInt8] {
Array("seed".utf8)
Array("testreferencealicetestreferencealice".utf8)
}
}

View File

@ -118,8 +118,8 @@ class WalletTransactionEncoderTests: XCTestCase {
to: self.recipientAddress,
value: Int64(self.zpend),
memo: nil,
spendParams: try! __spendParamsURL(),
outputParams: try! __outputParamsURL())
spendParamsPath: try! __spendParamsURL().path,
outputParamsPath: try! __outputParamsURL().path)
expectation.fulfill()
}
wait(for: [expectation], timeout: 240)
@ -162,7 +162,15 @@ class SpendOperation: Operation {
override func main() {
txId = rustBackend.createToAddress(dbData: dataDbURL, account: Int32(fromAccount), extsk: spendingKey, to: recipient, value: Int64(zatoshi), memo: memo, spendParams: spendURL, outputParams: outputURL)
txId = rustBackend.createToAddress(
dbData: dataDbURL,
account: Int32(fromAccount),
extsk: spendingKey,
to: recipient,
value: Int64(zatoshi),
memo: memo,
spendParamsPath: spendURL.path,
outputParamsPath: outputURL.path)
}
@ -201,7 +209,7 @@ class CreateToAddressThread: Thread {
override func main() {
self._working = true
txId = rustBackend.createToAddress(dbData: dataDbURL, account: Int32(fromAccount), extsk: spendingKey, to: recipient, value: Int64(zatoshi), memo: memo, spendParams: spendURL, outputParams: outputURL)
txId = rustBackend.createToAddress(dbData: dataDbURL, account: Int32(fromAccount), extsk: spendingKey, to: recipient, value: Int64(zatoshi), memo: memo, spendParamsPath: spendURL.path, outputParamsPath: outputURL.path)
self._working = false
}
}

View File

@ -27,22 +27,57 @@ class ZcashRustBackendTests: XCTestCase {
dataDbHandle.dispose()
}
func testInitAndGetAddress() {
let seed = "seed"
func testInitWithShortSeedAndFail() {
let seed = "testreferencealice"
XCTAssertNoThrow(try ZcashRustBackend.initDataDb(dbData: dbData!))
let _ = ZcashRustBackend.initAccountsTable(dbData: dbData!, seed: Array(seed.utf8), accounts: 1)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertNotNil(ZcashRustBackend.getLastError())
}
func testDeriveExtendedSpendingKeys() {
let seed = "testreferencealicetestreferencealice"
let addr = ZcashRustBackend.getAddress(dbData: dbData!, account: 0)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertEqual(addr, Optional("ztestsapling1meqz0cd598fw0jlq2htkuarg8gqv36fam83yxmu5mu3wgkx4khlttqhqaxvwf57urm3rqsq9t07"))
var spendingKeys: [String]? = nil
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveExtendedSpendingKeys(seed: seed, accounts: 1) }())
// Test invalid account
let addr2 = ZcashRustBackend.getAddress(dbData: dbData!, account: 1)
XCTAssert(ZcashRustBackend.getLastError() != nil)
XCTAssertEqual(addr2, nil)
XCTAssertNotNil(spendingKeys)
XCTAssertFalse(spendingKeys?.first?.isEmpty ?? true)
}
func testDeriveExtendedFullViewingKeys() {
let seed = "testreferencealicetestreferencealice"
var fullViewingKeys: [String]? = nil
XCTAssertNoThrow(try { fullViewingKeys = try ZcashRustBackend.deriveExtendedFullViewingKeys(seed: seed, accounts: 1) }())
XCTAssertNotNil(fullViewingKeys)
XCTAssertFalse(fullViewingKeys?.first?.isEmpty ?? true)
}
func testDeriveExtendedFullViewingKey() {
let seed = "testreferencealicetestreferencealice"
var fullViewingKey: String? = nil
var spendingKeys: [String]? = nil
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveExtendedSpendingKeys(seed: seed, accounts: 1) }())
XCTAssertNotNil(spendingKeys)
XCTAssertFalse(spendingKeys?.first?.isEmpty ?? true)
guard let spendingKey = spendingKeys?.first else {
XCTFail("no spending key generated")
return
}
XCTAssertNoThrow(try { fullViewingKey = try ZcashRustBackend.deriveExtendedFullViewingKey(spendingKey) }())
XCTAssertNotNil(fullViewingKey)
XCTAssertFalse(fullViewingKey?.isEmpty ?? true)
}
func testInitAndScanBlocks() {
@ -50,7 +85,7 @@ class ZcashRustBackendTests: XCTestCase {
XCTFail("pre populated Db not present")
return
}
let seed = "testreferencealice"
let seed = "testreferencealicetestreferencealice"
XCTAssertNoThrow(try ZcashRustBackend.initDataDb(dbData: dbData!))
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
@ -59,7 +94,7 @@ class ZcashRustBackendTests: XCTestCase {
let addr = ZcashRustBackend.getAddress(dbData: dbData!, account: 0)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertEqual(addr, Optional("ztestsapling12pxv67r0kdw58q8tcn8kxhfy9n4vgaa7q8vp0dg24aueuz2mpgv2x7mw95yetcc37efc6q3hewn"))
XCTAssertEqual(addr, Optional("ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc"))
XCTAssertTrue(ZcashRustBackend.scanBlocks(dbCache: cacheDb, dbData: dbData))
@ -67,8 +102,59 @@ class ZcashRustBackendTests: XCTestCase {
func testSendToAddress() {
let tx = try! ZcashRustBackend.createToAddress(dbData: dataDbHandle.readWriteDb, account: 0, extsk: spendingKey, to: recipientAddress, value: Int64(zpend), memo: nil, spendParams: URL(string: __spendParamsURL().path)!, outputParams: URL(string: __outputParamsURL().path)!)
let tx = try! ZcashRustBackend.createToAddress(dbData: dataDbHandle.readWriteDb, account: 0, extsk: spendingKey, to: recipientAddress, value: Int64(zpend), memo: nil, spendParamsPath: __spendParamsURL().path, outputParamsPath: __outputParamsURL().path)
XCTAssert(tx > 0)
XCTAssertNil(ZcashRustBackend.lastError())
}
func testIsValidTransparentAddressFalse() {
var isValid: Bool? = nil
XCTAssertNoThrow(try { isValid = try ZcashRustBackend.isValidTransparentAddress("ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc") }())
if let valid = isValid {
XCTAssertFalse(valid)
} else {
XCTFail()
}
}
func testIsValidTransparentAddressTrue() {
var isValid: Bool? = nil
XCTAssertNoThrow(try { isValid = try ZcashRustBackend.isValidTransparentAddress("tmSwpioc7reeoNrYB9SKpWkurJz3yEj3ee7") }())
if let valid = isValid {
XCTAssertTrue(valid)
} else {
XCTFail()
}
}
func testIsValidShieldedAddressTrue() {
var isValid: Bool? = nil
XCTAssertNoThrow(try { isValid = try ZcashRustBackend.isValidShieldedAddress("ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc") }())
if let valid = isValid {
XCTAssertTrue(valid)
} else {
XCTFail()
}
}
func testIsValidShieldedAddressFalse() {
var isValid: Bool? = nil
XCTAssertNoThrow(try { isValid = try ZcashRustBackend.isValidShieldedAddress("tmSwpioc7reeoNrYB9SKpWkurJz3yEj3ee7") }())
if let valid = isValid {
XCTAssertFalse(valid)
} else {
XCTFail()
}
}
}

View File

@ -34,7 +34,7 @@ class MockLightWalletService: LightWalletService {
return self.latestHeight
}
func blockRange(_ range: Range<BlockHeight>, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
self.service.blockRange(range, result: result)
}

View File

@ -27,7 +27,7 @@ class AwfulLightWalletService: MockLightWalletService {
}
override func blockRange(_ range: Range<BlockHeight>, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
override func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
result(.failure(LightWalletServiceError.generalError))
}
@ -77,6 +77,7 @@ extension LightWalletServiceMockResponse {
}
class MockRustBackend: ZcashRustBackendWelding {
static var mockDataDb = false
static var mockAcounts = false
static var mockError: RustWeldingError?
@ -104,6 +105,14 @@ class MockRustBackend: ZcashRustBackendWelding {
mockLastError ?? rustBackend.getLastError()
}
static func isValidShieldedAddress(_ address: String) throws -> Bool {
true
}
static func isValidTransparentAddress(_ address: String) throws -> Bool {
true
}
static func initDataDb(dbData: URL) throws {
if !mockDataDb {
try rustBackend.initDataDb(dbData: dbData)
@ -189,8 +198,8 @@ class MockRustBackend: ZcashRustBackendWelding {
return rustBackend.scanBlocks(dbCache: dbCache, dbData: dbData)
}
static func createToAddress(dbData: URL, account: Int32, extsk: String, to: String, value: Int64, memo: String?, spendParams: URL, outputParams: URL) -> Int64 {
mockCreateToAddress ?? rustBackend.createToAddress(dbData: dbData, account: account, extsk: extsk, to: to, value: value, memo: memo, spendParams: spendParams, outputParams: outputParams)
static func createToAddress(dbData: URL, account: Int32, extsk: String, to: String, value: Int64, memo: String?, spendParamsPath: String, outputParamsPath: String) -> Int64 {
mockCreateToAddress ?? rustBackend.createToAddress(dbData: dbData, account: account, extsk: extsk, to: to, value: value, memo: memo, spendParamsPath: spendParamsPath, outputParamsPath: outputParamsPath)
}
static func shouldSucceed(successRate: Float) -> Bool {
@ -198,4 +207,16 @@ class MockRustBackend: ZcashRustBackendWelding {
return random <= successRate
}
static func deriveExtendedFullViewingKey(_ spendingKey: String) throws -> String? {
nil
}
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32) throws -> [String]? {
nil
}
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32) throws -> [String]? {
nil
}
}

View File

@ -5,9 +5,12 @@ use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::slice;
use std::str::FromStr;
use zcash_client_backend::{
constants::{testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY},
encoding::{decode_extended_spending_key, encode_extended_spending_key},
encoding::{
decode_extended_spending_key, encode_extended_full_viewing_key,
encode_extended_spending_key,
},
keys::spending_key,
};
use zcash_client_sqlite::{
@ -23,11 +26,26 @@ use zcash_client_sqlite::{
transact::create_to_address,
};
use zcash_primitives::{
block::BlockHash, note_encryption::Memo, transaction::components::Amount,
zip32::ExtendedFullViewingKey,
block::BlockHash,
consensus::BranchId,
note_encryption::Memo,
transaction::components::Amount,
zip32::{ExtendedFullViewingKey},
};
use zcash_proofs::prover::LocalTxProver;
#[cfg(feature = "mainnet")]
use zcash_client_backend::constants::mainnet::{
COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY
};
#[cfg(not(feature = "mainnet"))]
use zcash_client_backend::constants::testnet::{
COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY
};
fn unwrap_exc_or<T>(exc: Result<T, ()>, def: T) -> T {
match exc {
Ok(value) => value,
@ -168,6 +186,111 @@ pub extern "C" fn zcashlc_init_blocks_table(
unwrap_exc_or_null(res)
}
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys(
seed: *const u8,
seed_len: usize,
accounts: i32,
) -> *mut *mut c_char {
let res = catch_panic(|| {
let seed = slice::from_raw_parts(seed, seed_len);
let accounts = if accounts > 0 {
accounts as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let extsks: Vec<_> = (0..accounts)
.map(|account| spending_key(&seed, COIN_TYPE, account))
.collect();
// Return the ExtendedSpendingKeys for the created accounts.
let mut v: Vec<_> = extsks
.iter()
.map(|extsk| {
let encoded =
encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, extsk);
CString::new(encoded).unwrap().into_raw()
})
.collect();
assert!(v.len() == v.capacity());
let p = v.as_mut_ptr();
std::mem::forget(v);
Ok(p)
});
unwrap_exc_or_null(res)
}
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys(
seed: *const u8,
seed_len: usize,
accounts: i32,
) -> *mut *mut c_char {
let res = catch_panic(|| {
let seed = slice::from_raw_parts(seed, seed_len);
let accounts = if accounts > 0 {
accounts as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let extsks: Vec<_> = (0..accounts)
.map(|account| ExtendedFullViewingKey::from(&spending_key(&seed, COIN_TYPE, account)))
.collect();
// Return the ExtendedSpendingKeys for the created accounts.
let mut v: Vec<_> = extsks
.iter()
.map(|extsk| {
let encoded =
encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, extsk);
CString::new(encoded).unwrap().into_raw()
})
.collect();
assert!(v.len() == v.capacity());
let p = v.as_mut_ptr();
std::mem::forget(v);
Ok(p)
});
unwrap_exc_or_null(res)
}
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key(
extsk: *const c_char,
) -> *mut c_char {
let res = catch_panic(|| {
let extsk = CStr::from_ptr(extsk).to_str()?;
let extfvk = match decode_extended_spending_key(
HRP_SAPLING_EXTENDED_SPENDING_KEY,
&extsk,
) {
Ok(Some(extsk)) => ExtendedFullViewingKey::from(&extsk),
Ok(None) => {
return Err(format_err!("Deriving viewing key from spending key returned no results. Encoding was valid but type was incorrect."));
}
Err(e) => {
return Err(format_err!(
"Error while deriving viewing key from spending key: {}",
e
));
}
};
let encoded = encode_extended_full_viewing_key(
HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
&extfvk,
);
Ok(CString::new(encoded).unwrap().into_raw())
});
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.
@ -219,6 +342,49 @@ pub extern "C" fn zcashlc_get_balance(db_data: *const u8, db_data_len: usize, ac
unwrap_exc_or(res, -1)
}
/// Returns true when the address is valid and shielded.
/// Returns false in any other case
/// Errors when the provided address belongs to another network
#[no_mangle]
pub unsafe extern "C" fn zcashlc_is_valid_shielded_address(
address: *const c_char,
) -> bool {
let res = catch_panic(|| {
let addr = CStr::from_ptr(address).to_str()?;
match RecipientAddress::from_str(&addr) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => Ok(true),
RecipientAddress::Transparent(_) => Ok(false),
},
None => Err(format_err!("Address is for the wrong network")),
}
});
unwrap_exc_or(res, false)
}
/// Returns true when the address is valid and transparent.
/// Returns false in any other case
#[no_mangle]
pub unsafe extern "C" fn zcashlc_is_valid_transparent_address(
address: *const c_char,
) -> bool {
let res = catch_panic(|| {
let addr = CStr::from_ptr(address).to_str()?;
match RecipientAddress::from_str(&addr) {
Some(addr) => match addr {
RecipientAddress::Shielded(_) => Ok(false),
RecipientAddress::Transparent(_) => Ok(true),
},
None => Err(format_err!("Address is for the wrong network")),
}
});
unwrap_exc_or(res, false)
}
/// Returns the verified balance for the account, which ignores notes that have been
/// received too recently and are not yet deemed spendable.
#[no_mangle]
@ -470,18 +636,18 @@ pub extern "C" fn zcashlc_create_to_address(
}
};
let memo = Memo::from_str(&memo);
let memo = Memo::from_str(&memo).map_err(|_| format_err!("Invalid memo"))?;
let prover = LocalTxProver::new(spend_params, output_params);
create_to_address(
&db_data,
0x2bb4_0e60, // BLOSSOM_CONSENSUS_BRANCH_ID
BranchId::Blossom,
prover,
(account, &extsk),
&to,
value,
memo,
Some(memo),
)
.map_err(|e| format_err!("Error while sending funds: {}", e))
});