Block downloading + storing (#4)

* Initial scaffold for Block Downloader

* (Failing) init Wallet test

* Ignore generated files

* Inject endpoint address on tests

* Simple Downloader + Tests

* CompactBlockDownloader latestBlockHeight, rewind + tests

* remover awful sync functions

* Compact Block processor scaffold

* Block Downloader + tests
[WIP] Block Processor

* Synchronous getBlockRange

* CompactBlock downloading sync + tests

* Sync CompactBlock Storage + Tests

* Rename Storage to FakeStorage

* WIP Blockdownloader test, block storage tests

* Fix carthage import for SQLite.framework

* SQLite Storage implementation for CompactBlocks + Tests

* Housekeeping, TestDbBuilder and other utils to their own place

* Integrated CompactBlockStorage to Downloader Tests

* Get latestBlockHeight from wallet

* move FakeStorage class to test utils

* Add ZcashLightClientKit import to moved file

* data db initialization + test

* ZcashOperation, CompactBlockDownloadOperation + tests. BlockDAO, latestScannedHeight

* Download and scan blocks. Download Operation. Scan Operation. Tests.

* cleanup test

* Updated readme
This commit is contained in:
Francisco Gindre 2019-10-18 15:45:19 -03:00 committed by GitHub
parent 8456ccc5b2
commit 60ea9d6737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 3873 additions and 71 deletions

4
.gitignore vendored
View File

@ -10,6 +10,8 @@ zcashlc.h
## These are backup files generated by rustfmt
**/*.rs.bk
# params files
*.params
# Xcode
## Build generated
@ -75,3 +77,5 @@ Pods
# do not commit generated libraries to this repo
lib
*.a
*.generated.swift
env-vars.sh

View File

@ -1 +1,2 @@
github "grpc/grpc-swift"
github "stephencelis/SQLite.swift"

View File

@ -1 +1,2 @@
github "grpc/grpc-swift" "0.9.1"
github "stephencelis/SQLite.swift" "0.12.2"

View File

@ -18,6 +18,32 @@ use_frameworks!
pod 'ZcashLightClientKit'
````
# Carthage support
Add ```ZcashLightClientKit``` to your Cartfile
# Testing
Currently tests depend on a ```lightwalletd``` server instance runnning locally or remotely to pass.
To know more about running ```lightwalletd```, refer to its repo https://github.com/zcash-hackworks/lightwalletd
## Pointing tests to a lightwalletd instance
Tests use ```Sourcery``` to generate a Constants file which injects the ```lightwalletd``` server address to the test themselves
### Installing sourcery
refer to the official repo https://github.com/krzysztofzablocki/Sourcery
### Setting env-var.sh file to run locally
create a file called ```env-var.sh``` on the project root to create the ```LIGHTWALLETD_ADDRESS``` environment variable on build time.
```
export LIGHTWALLETD_ADDRESS="localhost%3a9067"
```
### Integrating with CD/CI
The ```LIGHTWALLETD_ADDRESS``` environment variable can also be added to your shell of choice and ```xcodebuild``` will pick it up accordingly.
We advice setting this value as a secret variable on your CD/CI environment when possible
## Troubleshooting
#### _function_name referenced from...

View File

@ -10,4 +10,3 @@ framework module ZcashLightClientKit {
}
}

View File

@ -22,6 +22,8 @@ Pod::Spec.new do |s|
s.ios.deployment_target = '11.0'
s.dependency 'SwiftGRPC'
s.dependency 'SQLite.swift'
s.ios.vendored_libraries = 'lib/libzcashlc.a'

View File

@ -8,10 +8,55 @@
/* Begin PBXBuildFile section */
0B45933D22C612CB002A66BA /* ZcashRustBackendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B45933C22C612CB002A66BA /* ZcashRustBackendTests.swift */; };
0D866A522334208100960888 /* ResourceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D866A512334208000960888 /* ResourceProvider.swift */; };
0D9F5F1623537AD000D263C6 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D9F5F1523537AD000D263C6 /* Storage.swift */; };
0D9F5F192353821400D263C6 /* SQLDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D9F5F182353821400D263C6 /* SQLDatabase.swift */; };
0D9F5F1B235388CA00D263C6 /* CompactBlockStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D9F5F1A235388CA00D263C6 /* CompactBlockStorage.swift */; };
0D9F5F1D2353922500D263C6 /* CompactBlockStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D9F5F1C2353922500D263C6 /* CompactBlockStorageTests.swift */; };
0D9F5F1F2353C27100D263C6 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9F5F1E2353C27000D263C6 /* SQLite.framework */; };
0D9F5F202353C27F00D263C6 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9F5F1E2353C27000D263C6 /* SQLite.framework */; };
0DA33493232C0BC200CAC082 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA33492232C0BC200CAC082 /* Wallet.swift */; };
0DA33496232C11DF00CAC082 /* SeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA33495232C11DF00CAC082 /* SeedProvider.swift */; };
0DA33498232C1A8200CAC082 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA33497232C1A8200CAC082 /* Constants.swift */; };
0DA3349A232C1B6F00CAC082 /* WalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA33499232C1B6F00CAC082 /* WalletTests.swift */; };
0DA3DFAD2354BFC3008F8ECC /* TestDbBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA3DFAC2354BFC3008F8ECC /* TestDbBuilder.swift */; };
0DA3DFAF23551A4C008F8ECC /* StorageBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA3DFAE23551A4C008F8ECC /* StorageBuilder.swift */; };
0DA47A1623561FD700249DAF /* FakeStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB45704232AA06200057720 /* FakeStorage.swift */; };
0DA47A182356439400249DAF /* CompactBlockProcessingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA47A172356439400249DAF /* CompactBlockProcessingOperation.swift */; };
0DB456F4232A860200057720 /* ZcashCompactBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB456F3232A860200057720 /* ZcashCompactBlock.swift */; };
0DB456FD232A867800057720 /* service.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB456F8232A867800057720 /* service.pb.swift */; };
0DB456FE232A867800057720 /* service.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB456F9232A867800057720 /* service.grpc.swift */; };
0DB456FF232A867800057720 /* compact_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB456FA232A867800057720 /* compact_formats.pb.swift */; };
0DB45702232A86EF00057720 /* LightWalletGRPCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB45701232A86EF00057720 /* LightWalletGRPCService.swift */; };
0DB45709232AA81200057720 /* Protocolbuffer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB45708232AA81200057720 /* Protocolbuffer+Extensions.swift */; };
0DB4570D232ACD3100057720 /* ZcashRustBackendWelding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB4570C232ACD3100057720 /* ZcashRustBackendWelding.swift */; };
0DB45711232ADD4B00057720 /* CompactBlockStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB45710232ADD4B00057720 /* CompactBlockStoring.swift */; };
0DB45713232AEAF200057720 /* LightWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB45712232AEAF200057720 /* LightWalletService.swift */; };
0DB45714232B0AA800057720 /* BoringSSL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6823284A2C0053EFAC /* BoringSSL.framework */; };
0DB45715232B0AA800057720 /* CgRPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6623284A2C0053EFAC /* CgRPC.framework */; };
0DB45716232B0AA800057720 /* SwiftGRPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6723284A2C0053EFAC /* SwiftGRPC.framework */; };
0DB45717232B0AA800057720 /* SwiftProtobuf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6923284A2C0053EFAC /* SwiftProtobuf.framework */; };
0DBF2CE7233291530074C1BE /* LightWalletServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBF2CE6233291530074C1BE /* LightWalletServiceTests.swift */; };
0DBF2CEA233291D10074C1BE /* Tests+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBF2CE9233291D10074C1BE /* Tests+Utils.swift */; };
0DBF2CEC2332ACBD0074C1BE /* BlockDownloaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBF2CEB2332ACBD0074C1BE /* BlockDownloaderTests.swift */; };
0DBF2CEE2332B3090074C1BE /* CompactBlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBF2CED2332B3090074C1BE /* CompactBlockProcessor.swift */; };
0DBF2CF02332B8B70074C1BE /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBF2CEF2332B8B70074C1BE /* Stubs.swift */; };
0DC64E6A23284A2C0053EFAC /* CgRPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6623284A2C0053EFAC /* CgRPC.framework */; };
0DC64E6B23284A2C0053EFAC /* SwiftGRPC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6723284A2C0053EFAC /* SwiftGRPC.framework */; };
0DC64E6C23284A2C0053EFAC /* BoringSSL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6823284A2C0053EFAC /* BoringSSL.framework */; };
0DC64E6D23284A2C0053EFAC /* SwiftProtobuf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DC64E6923284A2C0053EFAC /* SwiftProtobuf.framework */; };
0DDC86232358C39000C50148 /* BlockScanOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDC86222358C39000C50148 /* BlockScanOperationTests.swift */; };
0DDC862A2358E50000C50148 /* sapling-output.params in Resources */ = {isa = PBXBuildFile; fileRef = 0DDC86282358E4FE00C50148 /* sapling-output.params */; };
0DDC862B2358E50000C50148 /* sapling-spend.params in Resources */ = {isa = PBXBuildFile; fileRef = 0DDC86292358E4FF00C50148 /* sapling-spend.params */; };
0DDC862D235905F600C50148 /* cache.db in Resources */ = {isa = PBXBuildFile; fileRef = 0DDC862C235905F600C50148 /* cache.db */; };
0DDF6378232FD02F000F9F01 /* Constants.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDF6377232FD02F000F9F01 /* Constants.generated.swift */; };
0DE3CD4523317A6F00FD1EFA /* BlockDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE3CD4423317A6F00FD1EFA /* BlockDownloader.swift */; };
0DF3154A2357A1CC0052E778 /* BlockRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF315492357A1CC0052E778 /* BlockRepository.swift */; };
0DF3154D2357A2790052E778 /* BlockDao.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF3154C2357A2790052E778 /* BlockDao.swift */; };
0DF3154F2357A81B0052E778 /* CompactBlockDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF3154E2357A81B0052E778 /* CompactBlockDAO.swift */; };
0DF315512357B2750052E778 /* CompactBlockDownloadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF315502357B2750052E778 /* CompactBlockDownloadOperation.swift */; };
0DF315532357BF060052E778 /* DownloadOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF315522357BF060052E778 /* DownloadOperationTests.swift */; };
0DF490DE23357A95005A4929 /* CompactBlockProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF490DD23357A95005A4929 /* CompactBlockProcessorTests.swift */; };
103AFE8F228312A30074BC98 /* ZcashLightClientKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 103AFE85228312A30074BC98 /* ZcashLightClientKit.framework */; };
103AFE94228312A30074BC98 /* ZcashLightClientKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 103AFE93228312A30074BC98 /* ZcashLightClientKitTests.swift */; };
103AFE96228312A30074BC98 /* ZcashLightClientKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 103AFE88228312A30074BC98 /* ZcashLightClientKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -32,10 +77,53 @@
/* Begin PBXFileReference section */
0B45933C22C612CB002A66BA /* ZcashRustBackendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashRustBackendTests.swift; sourceTree = "<group>"; };
0D866A512334208000960888 /* ResourceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceProvider.swift; sourceTree = "<group>"; };
0D9F5F1523537AD000D263C6 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
0D9F5F182353821400D263C6 /* SQLDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLDatabase.swift; sourceTree = "<group>"; };
0D9F5F1A235388CA00D263C6 /* CompactBlockStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactBlockStorage.swift; sourceTree = "<group>"; };
0D9F5F1C2353922500D263C6 /* CompactBlockStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactBlockStorageTests.swift; sourceTree = "<group>"; };
0D9F5F1E2353C27000D263C6 /* SQLite.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SQLite.framework; path = Carthage/Build/iOS/SQLite.framework; sourceTree = "<group>"; };
0DA33492232C0BC200CAC082 /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
0DA33495232C11DF00CAC082 /* SeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedProvider.swift; sourceTree = "<group>"; };
0DA33497232C1A8200CAC082 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
0DA33499232C1B6F00CAC082 /* WalletTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTests.swift; sourceTree = "<group>"; };
0DA3DFAC2354BFC3008F8ECC /* TestDbBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDbBuilder.swift; sourceTree = "<group>"; };
0DA3DFAE23551A4C008F8ECC /* StorageBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageBuilder.swift; sourceTree = "<group>"; };
0DA47A172356439400249DAF /* CompactBlockProcessingOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactBlockProcessingOperation.swift; sourceTree = "<group>"; };
0DB456F3232A860200057720 /* ZcashCompactBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashCompactBlock.swift; sourceTree = "<group>"; };
0DB456F7232A867800057720 /* service.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; path = service.proto; sourceTree = "<group>"; };
0DB456F8232A867800057720 /* service.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = service.pb.swift; sourceTree = "<group>"; };
0DB456F9232A867800057720 /* service.grpc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = service.grpc.swift; sourceTree = "<group>"; };
0DB456FA232A867800057720 /* compact_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = compact_formats.pb.swift; sourceTree = "<group>"; };
0DB456FB232A867800057720 /* compact_formats.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; path = compact_formats.proto; sourceTree = "<group>"; };
0DB45701232A86EF00057720 /* LightWalletGRPCService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightWalletGRPCService.swift; sourceTree = "<group>"; };
0DB45704232AA06200057720 /* FakeStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeStorage.swift; sourceTree = "<group>"; };
0DB45706232AA6AF00057720 /* ZcashLightClientKit.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = ZcashLightClientKit.modulemap; sourceTree = "<group>"; };
0DB45708232AA81200057720 /* Protocolbuffer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Protocolbuffer+Extensions.swift"; sourceTree = "<group>"; };
0DB4570C232ACD3100057720 /* ZcashRustBackendWelding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashRustBackendWelding.swift; sourceTree = "<group>"; };
0DB45710232ADD4B00057720 /* CompactBlockStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactBlockStoring.swift; sourceTree = "<group>"; };
0DB45712232AEAF200057720 /* LightWalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightWalletService.swift; sourceTree = "<group>"; };
0DBF2CE6233291530074C1BE /* LightWalletServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightWalletServiceTests.swift; sourceTree = "<group>"; };
0DBF2CE9233291D10074C1BE /* Tests+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tests+Utils.swift"; sourceTree = "<group>"; };
0DBF2CEB2332ACBD0074C1BE /* BlockDownloaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDownloaderTests.swift; sourceTree = "<group>"; };
0DBF2CED2332B3090074C1BE /* CompactBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactBlockProcessor.swift; sourceTree = "<group>"; };
0DBF2CEF2332B8B70074C1BE /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = "<group>"; };
0DC64E6623284A2C0053EFAC /* CgRPC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CgRPC.framework; path = Carthage/Build/iOS/CgRPC.framework; sourceTree = "<group>"; };
0DC64E6723284A2C0053EFAC /* SwiftGRPC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftGRPC.framework; path = Carthage/Build/iOS/SwiftGRPC.framework; sourceTree = "<group>"; };
0DC64E6823284A2C0053EFAC /* BoringSSL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BoringSSL.framework; path = Carthage/Build/iOS/BoringSSL.framework; sourceTree = "<group>"; };
0DC64E6923284A2C0053EFAC /* SwiftProtobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftProtobuf.framework; path = Carthage/Build/iOS/SwiftProtobuf.framework; sourceTree = "<group>"; };
0DDC86222358C39000C50148 /* BlockScanOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockScanOperationTests.swift; sourceTree = "<group>"; };
0DDC86282358E4FE00C50148 /* sapling-output.params */ = {isa = PBXFileReference; lastKnownFileType = file; path = "sapling-output.params"; sourceTree = "<group>"; };
0DDC86292358E4FF00C50148 /* sapling-spend.params */ = {isa = PBXFileReference; lastKnownFileType = file; path = "sapling-spend.params"; sourceTree = "<group>"; };
0DDC862C235905F600C50148 /* cache.db */ = {isa = PBXFileReference; lastKnownFileType = file; path = cache.db; sourceTree = "<group>"; };
0DDF6377232FD02F000F9F01 /* Constants.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.generated.swift; sourceTree = "<group>"; };
0DE3CD4423317A6F00FD1EFA /* BlockDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDownloader.swift; sourceTree = "<group>"; };
0DF315492357A1CC0052E778 /* BlockRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockRepository.swift; sourceTree = "<group>"; };
0DF3154C2357A2790052E778 /* BlockDao.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockDao.swift; sourceTree = "<group>"; };
0DF3154E2357A81B0052E778 /* CompactBlockDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = " CompactBlockDAO.swift"; sourceTree = "<group>"; };
0DF315502357B2750052E778 /* CompactBlockDownloadOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactBlockDownloadOperation.swift; sourceTree = "<group>"; };
0DF315522357BF060052E778 /* DownloadOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadOperationTests.swift; sourceTree = "<group>"; };
0DF490DD23357A95005A4929 /* CompactBlockProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactBlockProcessorTests.swift; sourceTree = "<group>"; };
103AFE85228312A30074BC98 /* ZcashLightClientKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ZcashLightClientKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
103AFE88228312A30074BC98 /* ZcashLightClientKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZcashLightClientKit.h; sourceTree = "<group>"; };
103AFE89228312A30074BC98 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -44,7 +132,6 @@
103AFE95228312A30074BC98 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
103AFEA12283166B0074BC98 /* libzcashlc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libzcashlc.a; path = target/universal/debug/libzcashlc.a; sourceTree = "<group>"; };
103AFEA322831BB00074BC98 /* ZcashRustBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashRustBackend.swift; sourceTree = "<group>"; };
103AFEA8228320F00074BC98 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
103AFEA9228320F00074BC98 /* zcashlc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zcashlc.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -56,6 +143,7 @@
0DC64E6A23284A2C0053EFAC /* CgRPC.framework in Frameworks */,
0DC64E6B23284A2C0053EFAC /* SwiftGRPC.framework in Frameworks */,
0DC64E6C23284A2C0053EFAC /* BoringSSL.framework in Frameworks */,
0D9F5F1F2353C27100D263C6 /* SQLite.framework in Frameworks */,
0DC64E6D23284A2C0053EFAC /* SwiftProtobuf.framework in Frameworks */,
103AFEA22283166B0074BC98 /* libzcashlc.a in Frameworks */,
);
@ -66,15 +154,157 @@
buildActionMask = 2147483647;
files = (
103AFE8F228312A30074BC98 /* ZcashLightClientKit.framework in Frameworks */,
0DB45714232B0AA800057720 /* BoringSSL.framework in Frameworks */,
0DB45715232B0AA800057720 /* CgRPC.framework in Frameworks */,
0D9F5F202353C27F00D263C6 /* SQLite.framework in Frameworks */,
0DB45716232B0AA800057720 /* SwiftGRPC.framework in Frameworks */,
0DB45717232B0AA800057720 /* SwiftProtobuf.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0D866A532334233500960888 /* Processor */ = {
isa = PBXGroup;
children = (
0DBF2CED2332B3090074C1BE /* CompactBlockProcessor.swift */,
0DA47A172356439400249DAF /* CompactBlockProcessingOperation.swift */,
0DF315502357B2750052E778 /* CompactBlockDownloadOperation.swift */,
);
path = Processor;
sourceTree = "<group>";
};
0D9F5F17235381D100D263C6 /* DatabaseStorage */ = {
isa = PBXGroup;
children = (
0D9F5F182353821400D263C6 /* SQLDatabase.swift */,
0D9F5F1A235388CA00D263C6 /* CompactBlockStorage.swift */,
0DA3DFAE23551A4C008F8ECC /* StorageBuilder.swift */,
);
path = DatabaseStorage;
sourceTree = "<group>";
};
0DA33494232C11C600CAC082 /* Providers */ = {
isa = PBXGroup;
children = (
0DA33495232C11DF00CAC082 /* SeedProvider.swift */,
0D866A512334208000960888 /* ResourceProvider.swift */,
);
path = Providers;
sourceTree = "<group>";
};
0DB456F2232A85D900057720 /* Model */ = {
isa = PBXGroup;
children = (
0DB456F3232A860200057720 /* ZcashCompactBlock.swift */,
);
path = Model;
sourceTree = "<group>";
};
0DB456F5232A863700057720 /* Service */ = {
isa = PBXGroup;
children = (
0DB45701232A86EF00057720 /* LightWalletGRPCService.swift */,
0DB456F2232A85D900057720 /* Model */,
0DB456F6232A864B00057720 /* ProtoBuf */,
0DB45712232AEAF200057720 /* LightWalletService.swift */,
);
path = Service;
sourceTree = "<group>";
};
0DB456F6232A864B00057720 /* ProtoBuf */ = {
isa = PBXGroup;
children = (
0DB45707232AA70900057720 /* Extensions */,
0DB456FA232A867800057720 /* compact_formats.pb.swift */,
0DB4570A232AA84000057720 /* proto */,
0DB456F9232A867800057720 /* service.grpc.swift */,
0DB456F8232A867800057720 /* service.pb.swift */,
);
path = ProtoBuf;
sourceTree = "<group>";
};
0DB45703232AA03C00057720 /* Storage */ = {
isa = PBXGroup;
children = (
0DF3154B2357A2680052E778 /* DAO */,
0DB45710232ADD4B00057720 /* CompactBlockStoring.swift */,
0D9F5F1523537AD000D263C6 /* Storage.swift */,
0DF315492357A1CC0052E778 /* BlockRepository.swift */,
);
path = Storage;
sourceTree = "<group>";
};
0DB45707232AA70900057720 /* Extensions */ = {
isa = PBXGroup;
children = (
0DB45708232AA81200057720 /* Protocolbuffer+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
0DB4570A232AA84000057720 /* proto */ = {
isa = PBXGroup;
children = (
0DB456FB232A867800057720 /* compact_formats.proto */,
0DB456F7232A867800057720 /* service.proto */,
);
path = proto;
sourceTree = "<group>";
};
0DB4570B232ACD1700057720 /* Rust */ = {
isa = PBXGroup;
children = (
103AFEA322831BB00074BC98 /* ZcashRustBackend.swift */,
0DB4570C232ACD3100057720 /* ZcashRustBackendWelding.swift */,
);
path = Rust;
sourceTree = "<group>";
};
0DBF2CE8233291C10074C1BE /* utils */ = {
isa = PBXGroup;
children = (
0DB45704232AA06200057720 /* FakeStorage.swift */,
0DBF2CEF2332B8B70074C1BE /* Stubs.swift */,
0DBF2CE9233291D10074C1BE /* Tests+Utils.swift */,
0DA3DFAC2354BFC3008F8ECC /* TestDbBuilder.swift */,
);
path = utils;
sourceTree = "<group>";
};
0DE3CD4223317A1200FD1EFA /* Block */ = {
isa = PBXGroup;
children = (
0D9F5F17235381D100D263C6 /* DatabaseStorage */,
0DE3CD4323317A3500FD1EFA /* Downloader */,
0DB45703232AA03C00057720 /* Storage */,
0D866A532334233500960888 /* Processor */,
);
path = Block;
sourceTree = "<group>";
};
0DE3CD4323317A3500FD1EFA /* Downloader */ = {
isa = PBXGroup;
children = (
0DE3CD4423317A6F00FD1EFA /* BlockDownloader.swift */,
);
path = Downloader;
sourceTree = "<group>";
};
0DF3154B2357A2680052E778 /* DAO */ = {
isa = PBXGroup;
children = (
0DF3154C2357A2790052E778 /* BlockDao.swift */,
0DF3154E2357A81B0052E778 /* CompactBlockDAO.swift */,
);
path = DAO;
sourceTree = "<group>";
};
103AFE7B228312A30074BC98 = {
isa = PBXGroup;
children = (
0DB45706232AA6AF00057720 /* ZcashLightClientKit.modulemap */,
103AFE87228312A30074BC98 /* ZcashLightClientKit */,
103AFE92228312A30074BC98 /* ZcashLightClientKitTests */,
103AFE86228312A30074BC98 /* Products */,
@ -94,10 +324,15 @@
103AFE87228312A30074BC98 /* ZcashLightClientKit */ = {
isa = PBXGroup;
children = (
0DE3CD4223317A1200FD1EFA /* Block */,
0DA33494232C11C600CAC082 /* Providers */,
0DB456F5232A863700057720 /* Service */,
103AFEA7228320F00074BC98 /* zcashlc */,
103AFE88228312A30074BC98 /* ZcashLightClientKit.h */,
103AFEA322831BB00074BC98 /* ZcashRustBackend.swift */,
0DB4570B232ACD1700057720 /* Rust */,
103AFE89228312A30074BC98 /* Info.plist */,
0DA33492232C0BC200CAC082 /* Wallet.swift */,
0DA33497232C1A8200CAC082 /* Constants.swift */,
);
path = ZcashLightClientKit;
sourceTree = "<group>";
@ -105,9 +340,21 @@
103AFE92228312A30074BC98 /* ZcashLightClientKitTests */ = {
isa = PBXGroup;
children = (
0DDC862C235905F600C50148 /* cache.db */,
0DDC86282358E4FE00C50148 /* sapling-output.params */,
0DDC86292358E4FF00C50148 /* sapling-spend.params */,
0DBF2CE8233291C10074C1BE /* utils */,
0DDF6377232FD02F000F9F01 /* Constants.generated.swift */,
103AFE93228312A30074BC98 /* ZcashLightClientKitTests.swift */,
0B45933C22C612CB002A66BA /* ZcashRustBackendTests.swift */,
0DA33499232C1B6F00CAC082 /* WalletTests.swift */,
103AFE95228312A30074BC98 /* Info.plist */,
0DBF2CE6233291530074C1BE /* LightWalletServiceTests.swift */,
0DBF2CEB2332ACBD0074C1BE /* BlockDownloaderTests.swift */,
0DF490DD23357A95005A4929 /* CompactBlockProcessorTests.swift */,
0D9F5F1C2353922500D263C6 /* CompactBlockStorageTests.swift */,
0DF315522357BF060052E778 /* DownloadOperationTests.swift */,
0DDC86222358C39000C50148 /* BlockScanOperationTests.swift */,
);
path = ZcashLightClientKitTests;
sourceTree = "<group>";
@ -115,6 +362,7 @@
103AFEA02283166B0074BC98 /* Frameworks */ = {
isa = PBXGroup;
children = (
0D9F5F1E2353C27000D263C6 /* SQLite.framework */,
0DC64E6823284A2C0053EFAC /* BoringSSL.framework */,
0DC64E6623284A2C0053EFAC /* CgRPC.framework */,
0DC64E6723284A2C0053EFAC /* SwiftGRPC.framework */,
@ -127,7 +375,6 @@
103AFEA7228320F00074BC98 /* zcashlc */ = {
isa = PBXGroup;
children = (
103AFEA8228320F00074BC98 /* module.modulemap */,
103AFEA9228320F00074BC98 /* zcashlc.h */,
);
path = zcashlc;
@ -172,9 +419,11 @@
isa = PBXNativeTarget;
buildConfigurationList = 103AFE9C228312A30074BC98 /* Build configuration list for PBXNativeTarget "ZcashLightClientKitTests" */;
buildPhases = (
0DDF6379232FD0B4000F9F01 /* ShellScript */,
103AFE8A228312A30074BC98 /* Sources */,
103AFE8B228312A30074BC98 /* Frameworks */,
103AFE8C228312A30074BC98 /* Resources */,
0D35E05E2330007B00D5901F /* ShellScript */,
);
buildRules = (
);
@ -235,12 +484,34 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0DDC862A2358E50000C50148 /* sapling-output.params in Resources */,
0DDC862D235905F600C50148 /* cache.db in Resources */,
0DDC862B2358E50000C50148 /* sapling-spend.params in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
0D35E05E2330007B00D5901F /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"$(SRCROOT)/filelists/input.xcfilelist",
);
inputPaths = (
);
outputFileListPaths = (
"$(SRCROOT)/filelists/output.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/usr/local/bin/carthage copy-frameworks\n";
};
0DC64E65232848F10053EFAC /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -260,6 +531,24 @@
shellPath = /bin/sh;
shellScript = "/usr/local/bin/carthage copy-frameworks\n";
};
0DDF6379232FD0B4000F9F01 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
"$(SRCROOT)/Constants.generated.swift",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#check if env-vars.sh exists\nif [ -f ${SRCROOT}/env-vars.sh ];then\nsource ${SRCROOT}/env-vars.sh\nfi\n\nexport ZCASH_TEST_SRC_PATH=\"${SRCROOT}/ZcashLightClientKitTests\"\nif ![ ${LIGHTWALLETD_ADDRESS} ]; then\n echo \"LIGHTWALLETD_ADDRESS VARIABLE NOT DEFINED\"\n exit 1\nfi\necho \"export ZCASH_TEST_SRC_PATH=$ZCASH_TEST_SRC_PATH\"\n#no `else` case needed if the CI works as expecteds\nsourcery --templates ${ZCASH_TEST_SRC_PATH} --sources ${ZCASH_TEST_SRC_PATH} --output ${ZCASH_TEST_SRC_PATH} --args addr=$LIGHTWALLETD_ADDRESS\n";
};
103AFE9F2283152F0074BC98 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -285,7 +574,31 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0DA33493232C0BC200CAC082 /* Wallet.swift in Sources */,
0DA3DFAF23551A4C008F8ECC /* StorageBuilder.swift in Sources */,
0DB45713232AEAF200057720 /* LightWalletService.swift in Sources */,
0DB456FD232A867800057720 /* service.pb.swift in Sources */,
0DB45702232A86EF00057720 /* LightWalletGRPCService.swift in Sources */,
0DE3CD4523317A6F00FD1EFA /* BlockDownloader.swift in Sources */,
0D9F5F1623537AD000D263C6 /* Storage.swift in Sources */,
0DA33496232C11DF00CAC082 /* SeedProvider.swift in Sources */,
0DB456FE232A867800057720 /* service.grpc.swift in Sources */,
0DA47A182356439400249DAF /* CompactBlockProcessingOperation.swift in Sources */,
103AFEA422831BB00074BC98 /* ZcashRustBackend.swift in Sources */,
0DBF2CEE2332B3090074C1BE /* CompactBlockProcessor.swift in Sources */,
0DB45709232AA81200057720 /* Protocolbuffer+Extensions.swift in Sources */,
0D9F5F192353821400D263C6 /* SQLDatabase.swift in Sources */,
0DB4570D232ACD3100057720 /* ZcashRustBackendWelding.swift in Sources */,
0DF3154F2357A81B0052E778 /* CompactBlockDAO.swift in Sources */,
0DB45711232ADD4B00057720 /* CompactBlockStoring.swift in Sources */,
0D866A522334208100960888 /* ResourceProvider.swift in Sources */,
0D9F5F1B235388CA00D263C6 /* CompactBlockStorage.swift in Sources */,
0DF315512357B2750052E778 /* CompactBlockDownloadOperation.swift in Sources */,
0DA33498232C1A8200CAC082 /* Constants.swift in Sources */,
0DF3154D2357A2790052E778 /* BlockDao.swift in Sources */,
0DB456FF232A867800057720 /* compact_formats.pb.swift in Sources */,
0DB456F4232A860200057720 /* ZcashCompactBlock.swift in Sources */,
0DF3154A2357A1CC0052E778 /* BlockRepository.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -293,8 +606,20 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0DA3DFAD2354BFC3008F8ECC /* TestDbBuilder.swift in Sources */,
0B45933D22C612CB002A66BA /* ZcashRustBackendTests.swift in Sources */,
0D9F5F1D2353922500D263C6 /* CompactBlockStorageTests.swift in Sources */,
0DF490DE23357A95005A4929 /* CompactBlockProcessorTests.swift in Sources */,
0DA47A1623561FD700249DAF /* FakeStorage.swift in Sources */,
0DBF2CEA233291D10074C1BE /* Tests+Utils.swift in Sources */,
0DDC86232358C39000C50148 /* BlockScanOperationTests.swift in Sources */,
0DBF2CEC2332ACBD0074C1BE /* BlockDownloaderTests.swift in Sources */,
0DBF2CE7233291530074C1BE /* LightWalletServiceTests.swift in Sources */,
0DA3349A232C1B6F00CAC082 /* WalletTests.swift in Sources */,
0DDF6378232FD02F000F9F01 /* Constants.generated.swift in Sources */,
103AFE94228312A30074BC98 /* ZcashLightClientKitTests.swift in Sources */,
0DBF2CF02332B8B70074C1BE /* Stubs.swift in Sources */,
0DF315532357BF060052E778 /* DownloadOperationTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -443,6 +768,10 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
HEADER_SEARCH_PATHS = "";
INFOPLIST_FILE = ZcashLightClientKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@ -479,6 +808,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/iOS";
GCC_PREPROCESSOR_DEFINITIONS = "";
HEADER_SEARCH_PATHS = "";
INFOPLIST_FILE = ZcashLightClientKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
@ -507,6 +837,10 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/iOS",
);
INFOPLIST_FILE = ZcashLightClientKitTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -525,6 +859,10 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/iOS",
);
INFOPLIST_FILE = ZcashLightClientKitTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",

View File

@ -0,0 +1,124 @@
//
// CompactBlockStorage.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/13/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SQLite
struct CompactBlockStorage: CompactBlockDAO {
var db: Connection
init(connection: Connection) {
self.db = connection
}
private func compactBlocksTable() -> Table {
Table("compactblocks")
}
private func heightColumn() -> Expression<Int64> {
Expression<Int64>("height")
}
private func dataColumn() -> Expression<Blob> {
Expression<Blob>("data")
}
func createTable() throws {
do {
let compactBlocks = compactBlocksTable()
let height = heightColumn()
let data = dataColumn()
try db.run(compactBlocks.create(ifNotExists: true) { t in
t.column(height, primaryKey: true)
t.column(data)
} )
try db.run(compactBlocks.createIndex(height, ifNotExists: true))
} catch {
throw StorageError.couldNotCreate
}
}
func insert(_ block: ZcashCompactBlock) throws {
try db.run(compactBlocksTable().insert(block))
}
func insert(_ blocks: [ZcashCompactBlock]) throws {
let compactBlocks = compactBlocksTable()
try db.transaction {
for block in blocks {
try db.run(compactBlocks.insert(block))
}
}
}
func latestBlockHeight() throws -> BlockHeight {
guard let maxHeight = try db.scalar(compactBlocksTable().select(heightColumn().max)) else {
return BlockHeight.empty()
}
guard let blockHeight = BlockHeight(exactly: maxHeight) else {
throw StorageError.operationFailed
}
return blockHeight
}
func rewind(to height: BlockHeight) throws {
try db.run(compactBlocksTable().filter(heightColumn() >= Int64(height)).delete())
}
}
extension CompactBlockStorage: CompactBlockStoring {
func latestHeight() throws -> BlockHeight {
try latestBlockHeight()
}
func latestHeight(result: @escaping (Swift.Result<BlockHeight, Error>) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
do {
result(.success(try self.latestBlockHeight()))
} catch {
result(.failure(error))
}
}
}
func write(blocks: [ZcashCompactBlock]) throws {
try insert(blocks)
}
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) {
DispatchQueue.global(qos: .userInitiated).async {
do {
try self.insert(blocks)
completion?(nil)
} catch {
completion?(error)
}
}
}
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) {
DispatchQueue.global(qos: .userInitiated).async {
do {
try self.rewind(to: height)
completion?(nil)
} catch {
completion?(error)
}
}
}
}

View File

@ -0,0 +1,49 @@
//
// SQLDatabase.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/13/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SQLite
class SQLiteStorage: Storage {
private var connection: Connection
var compactBlockDao: CompactBlockDAO
init(connection: Connection, compactBlockDAO: CompactBlockDAO) {
self.compactBlockDao = compactBlockDAO
self.connection = connection
}
func open(at path: String) throws {
do {
connection = try Connection(path)
} catch {
throw StorageError.openFailed
}
}
func createDatabase(at path: String) throws {
try compactBlockDao.createTable()
}
func closeDatabase() {}
}
/**
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

@ -0,0 +1,24 @@
//
// StorageBuilder.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/14/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SQLite
struct StorageBuilder {
static func cacheDb(at url: URL) -> Storage? {
do {
let connection = try Connection(url.absoluteString)
let blockDAO = CompactBlockStorage(connection: connection)
try blockDAO.createTable()
return SQLiteStorage(connection: connection, compactBlockDAO: blockDAO)
} catch {
return nil
}
}
}

View File

@ -0,0 +1,126 @@
//
// BlockDownloader.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 17/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
enum CompactBlockDownloadError: Error {
case timeout
case generalError(error: Error)
}
protocol CompactBlockDownloading {
/**
Downloads and stores the given block range.
Non-Blocking
*/
func downloadBlockRange(_ heightRange: CompactBlockRange,
completion: @escaping (Error?) -> Void)
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void)
func latestBlockHeight(result: @escaping (Result<BlockHeight,Error>) -> Void)
/**
Downloads and stores the given block range.
Blocking
*/
func downloadBlockRange(_ range: CompactBlockRange) throws
func rewind(to height: BlockHeight) throws
func latestBlockHeight() throws -> BlockHeight
}
/**
Serves as a source of compact blocks received from the light wallet server. Once started, it will use the given
lightwallet service to request all the appropriate blocks and compact block store to persist them. By delegating to
these dependencies, the downloader remains agnostic to the particular implementation of how to retrieve and store
data; although, by default the SDK uses gRPC and SQL.
- Property lightwalletService: the service used for requesting compact blocks
- Property storage: responsible for persisting the compact blocks that are received
*/
class CompactBlockDownloader {
fileprivate var lightwalletService: LightWalletService
fileprivate var storage: CompactBlockStoring
init(service: LightWalletService, storage: CompactBlockStoring) {
self.lightwalletService = service
self.storage = storage
}
}
extension CompactBlockDownloader: CompactBlockDownloading {
/**
Downloads and stores the given block range.
Non-Blocking
*/
func downloadBlockRange(_ heightRange: CompactBlockRange,
completion: @escaping (Error?) -> Void) {
lightwalletService.blockRange(heightRange) { [weak self] (result) in
guard let self = self else {
return
}
switch result{
case .failure(let error):
completion(error)
return
case .success(let compactBlocks):
self.storage.write(blocks: compactBlocks) { (storeError) in
completion(storeError)
}
}
}
}
func downloadBlockRange(_ range: CompactBlockRange) throws {
let blocks = try lightwalletService.blockRange(range)
try storage.write(blocks: blocks)
}
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void){
storage.rewind(to: height) { (e) in
completion(e)
}
}
func latestBlockHeight(result: @escaping (Result<BlockHeight,Error>) -> Void) {
storage.latestHeight { (r) in
switch r {
case .failure(let e):
result(.failure(CompactBlockDownloadError.generalError(error: e)))
return
case .success(let height):
result(.success(height))
}
}
}
func rewind(to height: BlockHeight) throws {
try self.storage.rewind(to: height)
}
func latestBlockHeight() throws -> BlockHeight {
try self.storage.latestHeight()
}
}

View File

@ -0,0 +1,63 @@
//
// CompactBlockDownloadOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
typealias ZcashOperationCompletionBlock = (_ finished: Bool, _ cancelled: Bool, _ error: Error?) -> Void
enum ZcashOperationError: Error {
case unknown
}
class ZcashOperation: Operation {
var error: Error?
var completionHandler: ZcashOperationCompletionBlock?
var completionDispatchQueue: DispatchQueue = DispatchQueue.main
override init() {
super.init()
completionBlock = { [weak self] in
guard let self = self else { return }
self.completionHandler?(self.isFinished, self.isCancelled, self.error)
}
}
convenience init(completionDispatchQueue: DispatchQueue = DispatchQueue.main) {
self.init()
self.completionDispatchQueue = completionDispatchQueue
}
}
class CompactBlockDownloadOperation: ZcashOperation {
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
private var downloader: CompactBlockDownloading
private var range: CompactBlockRange
required init(downloader: CompactBlockDownloading, range: CompactBlockRange) {
self.range = range
self.downloader = downloader
super.init()
self.name = "Download Operation: \(range)"
}
override func main() {
do {
try downloader.downloadBlockRange(range)
} catch {
self.error = error
self.cancel()
}
}
}

View File

@ -0,0 +1,36 @@
//
// CompactBlockProcessingOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/15/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
class CompactBlockScanningOperation: ZcashOperation {
override var isConcurrent: Bool { false }
override var isAsynchronous: Bool { false }
var rustBackend: ZcashRustBackendWelding.Type
private var cacheDb: URL
private var dataDb: URL
init(rustWelding: ZcashRustBackendWelding.Type, cacheDb: URL, dataDb:URL) {
rustBackend = rustWelding
self.cacheDb = cacheDb
self.dataDb = dataDb
super.init()
}
override func main() {
guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb) else {
self.error = self.rustBackend.lastError() ?? ZcashOperationError.unknown
print("block scanning failed with error: \(String(describing: self.error))")
self.cancel()
return
}
}
}

View File

@ -0,0 +1,277 @@
//
// CompactBlockProcessor.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 18/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
enum CompactBlockProcessorError: Error {
case invalidConfiguration
case missingDbPath(path: String)
}
extension Notification.Name {
static let blockProcessorUpdated = Notification.Name(rawValue: "CompactBlockProcessorUpdated")
static let blockProcessorStarted = Notification.Name(rawValue:"CompactBlockProcessorStarted")
static let blockProcessorStopped = Notification.Name(rawValue:"CompactBlockProcessorStopped")
static let blockProcessorFinished = Notification.Name(rawValue:"CompactBlockProcessorFinished")
static let blockProcessorFailed = Notification.Name(rawValue:"CompactBlockProcessorFinished")
}
class CompactBlockProcessor {
enum State {
/**
connected and downloading blocks
*/
case connected
/**
was doing something but was paused
*/
case stopped
/**
processor is scanning
*/
case scanning
/**
was processing but erred
*/
case error
/**
Just chillin'
*/
case idle
}
private(set) var state: State = .idle
private var downloader: CompactBlockDownloading
private var rustBackend: ZcashRustBackendWelding.Type
private var config: Configuration = Configuration.standard
private var service: LightWalletService
private var queue = DispatchQueue(label: "CompactBlockProcessor-Queue")
private var retryAttempts = 0
private var backoffTimer: Timer?
// convenience vars
private var maxAttempts: Int {
config.retries
}
private var batchSize: BlockHeight {
BlockHeight(self.config.downloadBatchSize)
}
private var processingError: Error?
init(downloader: CompactBlockDownloading, backend: ZcashRustBackendWelding.Type, config: Configuration, service: LightWalletService) {
self.downloader = downloader
self.rustBackend = backend
self.config = config
self.service = service
suscribeToSystemNotifications()
}
private func suscribeToSystemNotifications() {
// TODO: check system notifications for connections and application context changes
}
private func unsuscribeToSystemNotifications() {
}
deinit {
self.queue.suspend()
self.unsuscribeToSystemNotifications()
}
/**
Compact Block Processor configuration
Property: cacheDbPath absolute file path of the DB where raw, unprocessed compact blocks are stored.
Property: dataDbPath absolute file path of the DB where all information derived from the cache DB is stored.
*/
struct Configuration {
var cacheDbPath: String
var dataDbPath: String
var downloadBatchSize = DEFAULT_BATCH_SIZE
var blockPollInterval = DEFAULT_POLL_INTERVAL
var retries = DEFAULT_RETRIES
var maxBackoffInterval = DEFAULT_MAX_BACKOFF_INTERVAL
var rewindDistance = DEFAULT_REWIND_DISTANCE
}
private func validateConfiguration() throws {
guard FileManager.default.fileExists(atPath: config.cacheDbPath) else {
throw CompactBlockProcessorError.missingDbPath(path: config.cacheDbPath)
}
guard FileManager.default.fileExists(atPath: config.dataDbPath) else {
throw CompactBlockProcessorError.missingDbPath(path: config.dataDbPath)
}
}
func start() throws {
try validateConfiguration()
var latestDownloadedBlockHeight: BlockHeight = 0
var latestBlockHeight: BlockHeight = 0
let dispatchGroup = DispatchGroup()
let downloadedHeightItem = DispatchWorkItem {
dispatchGroup.enter()
self.downloader.latestBlockHeight { (heightResult) in
switch heightResult {
case .success(let downloadedHeight):
latestDownloadedBlockHeight = downloadedHeight
case .failure(let e):
DispatchQueue.main.async {
self.fail(e)
}
}
dispatchGroup.leave()
}
}
let latestBlockHeightItem = DispatchWorkItem {
dispatchGroup.enter()
self.service.latestBlockHeight { (result) in
switch result {
case .success(let blockHeight):
latestBlockHeight = blockHeight
case .failure(let e):
DispatchQueue.main.async {
self.fail(e)
}
}
dispatchGroup.leave()
}
}
queue.async(execute: downloadedHeightItem)
queue.async(execute: latestBlockHeightItem)
dispatchGroup.notify(queue: DispatchQueue.main) {
print("downloaded BlockHeight Value: \(latestDownloadedBlockHeight)")
print("latest BlockHeight Value:\(latestBlockHeight)")
guard self.state != .error else {
return
}
NotificationCenter.default.post(name: Notification.Name.blockProcessorStarted, object: self)
self.processNewBlocks(latestHeight: latestBlockHeight, latestDownloadedHeight: latestDownloadedBlockHeight)
}
}
func processNewBlocks(latestHeight: BlockHeight, latestDownloadedHeight: BlockHeight) {
let dispatchGroup = DispatchGroup()
let validateBlocksTask = DispatchWorkItem {
dispatchGroup.enter()
self.state = .scanning
}
let downloadTask = DispatchWorkItem {
dispatchGroup.enter()
self.state = .connected
self.downloader.downloadBlockRange(self.nextBatchBlockRange(latestHeight: latestHeight, latestDownloadedHeight: latestDownloadedHeight)) { (error) in
if let error = error {
validateBlocksTask.cancel()
self.fail(error)
}
dispatchGroup.leave()
}
}
queue.async(execute: downloadTask)
queue.async(execute: validateBlocksTask)
dispatchGroup.notify(queue: DispatchQueue.main) {
self.processBatchFinished(latestHeight: latestHeight, latestDownloadedHeight: latestDownloadedHeight)
}
}
private func processBatchFinished(latestHeight: BlockHeight, latestDownloadedHeight: BlockHeight) {
guard processingError == nil else {
retryProcessing(latestHeight: latestHeight, latestDownloadedHeight: latestDownloadedHeight)
return
}
retryAttempts = 0
guard latestDownloadedHeight < latestHeight else {
processingFinished()
return
}
}
private func processingFinished() {
self.state = .idle
self.backoffTimer = Timer(timeInterval: TimeInterval(self.config.blockPollInterval), repeats: true, block: { _ in
do {
try self.start()
} catch {
self.fail(error)
}
})
}
private func nextBatchBlockRange(latestHeight: BlockHeight, latestDownloadedHeight: BlockHeight) -> CompactBlockRange {
return CompactBlockRange(uncheckedBounds: (latestDownloadedHeight + 1, min(latestDownloadedHeight + BlockHeight(DEFAULT_BATCH_SIZE), latestDownloadedHeight)))
}
func retryProcessing(latestHeight: BlockHeight, latestDownloadedHeight: BlockHeight) {
processNewBlocks(latestHeight: latestHeight, latestDownloadedHeight: latestDownloadedHeight)
}
func stop() {
queue.suspend()
self.state = .stopped
}
func fail(_ error: Error) {
// todo specify: failure
print(error.localizedDescription)
self.processingError = error
self.state = .error
NotificationCenter.default.post(name: Notification.Name.blockProcessorFailed, object: self, userInfo: ["error": error ])
}
}
extension CompactBlockProcessor.Configuration {
static var standard: CompactBlockProcessor.Configuration {
let pathProvider = DefaultResourceProvider()
return CompactBlockProcessor.Configuration(cacheDbPath: pathProvider.cacheDbPath, dataDbPath: pathProvider.dataDbPath)
}
}

View File

@ -0,0 +1,14 @@
//
// BlockRepository.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
protocol BlockRepository {
func lastScannedBlockHeight() -> BlockHeight
}

View File

@ -0,0 +1,63 @@
//
// CompactBlockStoring.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
public struct ZcashCompactBlock {
var height: BlockHeight
var data: Data
}
extension ZcashCompactBlock: Encodable { }
protocol CompactBlockStoring {
/**
Gets the highest block that is currently stored.
*/
func latestHeight() throws -> BlockHeight
/**
Gets the highest block that is currently stored.
Non-Blocking
*/
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.
*/
func write(blocks: [ZcashCompactBlock]) throws -> Void
/**
Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
Non-Blocking
*/
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?)
/**
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.
Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49.
*/
func rewind(to height: BlockHeight) throws -> Void
/**
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.
Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49.
*/
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?)
}

View File

@ -0,0 +1,25 @@
//
// CompactBlockDAO.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
protocol CompactBlockDAO {
func createTable() throws
func insert(_ block: ZcashCompactBlock) throws
func insert(_ blocks: [ZcashCompactBlock]) throws
/**
Query the latest block height, returns -1 if no block is stored
*/
func latestBlockHeight() throws -> BlockHeight
func rewind(to height: BlockHeight) throws
}

View File

@ -0,0 +1,39 @@
//
// BlockDao.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SQLite
protocol BlockDao {
func latestBlockHeight() throws -> BlockHeight
}
class BlockSQLDAO: BlockDao {
var db: Connection
var table: Table
var height = Expression<Int>("height")
init(dataDb: URL) throws {
self.db = try Connection(dataDb.absoluteString, readonly: true)
self.table = Table("Blocks")
}
func latestBlockHeight() throws -> BlockHeight {
try db.scalar(table.select(height.max)) ?? BlockHeight.empty()
}
}
extension BlockSQLDAO: BlockRepository {
func lastScannedBlockHeight() -> BlockHeight {
(try? self.latestBlockHeight()) ?? BlockHeight.empty()
}
}

View File

@ -0,0 +1,28 @@
//
// Storage.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/13/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
protocol Storage {
func createDatabase(at path: String) throws
func closeDatabase()
var compactBlockDao: CompactBlockDAO { get }
}
enum StorageError: Error {
case couldNotCreate
case openFailed
case closeFailed
case operationFailed
}

View File

@ -0,0 +1,84 @@
//
// Constants.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 13/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
/**
* Miner's fee in zatoshi.
*/
public let MINERS_FEE_ZATOSHI: BlockHeight = 10_000
/**
* The number of zatoshi that equal 1 ZEC.
*/
public let ZATOSHI_PER_ZEC: BlockHeight = 100_000_000
/**
* The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks
* prior to this height, at all.
*/
public let SAPLING_ACTIVATION_HEIGHT: BlockHeight = 280_000
/**
* The theoretical maximum number of blocks in a reorg, due to other bottlenecks in the protocol design.
*/
public let MAX_REORG_SIZE = 100
/**
* The amount of blocks ahead of the current height where new transactions are set to expire. This value is controlled
* by the rust backend but it is helpful to know what it is set to and shdould be kept in sync.
*/
public let EXPIRY_OFFSET = 20
//
// Defaults
//
/**
* Default size of batches of blocks to request from the compact block service.
*/
public let DEFAULT_BATCH_SIZE = 100
/**
* Default amount of time, in milliseconds, to poll for new blocks. Typically, this should be about half the average
* block time.
*/
public let DEFAULT_POLL_INTERVAL: UInt64 = 75_000
/**
* Default attempts at retrying.
*/
public let DEFAULT_RETRIES = 5
/**
* The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer than
* this before retyring.
*/
public let DEFAULT_MAX_BACKOFF_INTERVAL: TimeInterval = 600
/**
* Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the
* reorg but smaller than the theoretical max reorg size of 100.
*/
public let DEFAULT_REWIND_DISTANCE = 10
/**
* The number of blocks to allow before considering our data to be stale. This usually helps with what to do when
* returning from the background and is exposed via the Synchronizer's isStale function.
*/
public let DEFAULT_STALE_TOLERANCE = 10
/**
Default Name for LibRustZcash data.db
*/
public let DEFAULT_DATA_DB_NAME = "data.db"
/**
Default Name for Compact Block caches db
*/
public let DEFAULT_CACHES_DB_NAME = "caches.db"

View File

@ -0,0 +1,43 @@
//
// ResourceProvider.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 19/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
public enum ResourceProviderError: Error {
case unavailableResource
}
public protocol ResourceProvider {
var dataDbPath: String { get }
var cacheDbPath: String { get }
}
public struct DefaultResourceProvider: ResourceProvider {
public var dataDbPath: String {
do {
let url = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
return url.appendingPathComponent(DEFAULT_DATA_DB_NAME).path
} catch {
return "file://\(DEFAULT_DATA_DB_NAME)"
}
}
public var cacheDbPath: String {
do {
let path = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
return path.appendingPathComponent(DEFAULT_CACHES_DB_NAME).path
} catch {
return "file://\(DEFAULT_CACHES_DB_NAME)"
}
}
}

View File

@ -0,0 +1,13 @@
//
// SeedProvider.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 13/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
public protocol SeedProvider {
func seed() -> [UInt8]
}

View File

@ -0,0 +1,9 @@
//
// ZcashRust+Utils.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation

View File

@ -7,15 +7,18 @@
//
import Foundation
enum RustWeldingError: Error {
case genericError(message: String)
}
public class ZcashRustBackend {
static func osStrFromURL(_ url: URL) -> (String, UInt) {
let path = url.absoluteString
return (path, UInt(path.lengthOfBytes(using: .utf8)))
class ZcashRustBackend: ZcashRustBackendWelding {
static func lastError() -> Error? {
guard let message = getLastError() else { return nil }
return RustWeldingError.genericError(message: message)
}
public static func getLastError() -> String? {
static func getLastError() -> String? {
let errorLen = zcashlc_last_error_length()
if errorLen > 0 {
let error = UnsafeMutablePointer<Int8>.allocate(capacity: Int(errorLen))
@ -26,14 +29,17 @@ public class ZcashRustBackend {
return nil
}
}
public static func initDataDb(dbData: URL) -> Bool {
let dbData = osStrFromURL(dbData)
/**
* Sets up the internal structure of the data database.
*/
static func initDataDb(dbData: URL) -> Bool {
let dbData = dbData.osStr()
return zcashlc_init_data_database(dbData.0, dbData.1) != 0
}
public static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]? {
let dbData = osStrFromURL(dbData)
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
@ -46,13 +52,13 @@ public class ZcashRustBackend {
return extsks
}
public static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) -> Bool {
let dbData = osStrFromURL(dbData)
static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) -> Bool {
let dbData = dbData.osStr()
return zcashlc_init_blocks_table(dbData.0, dbData.1, height, [CChar](hash.utf8CString), time, [CChar](saplingTree.utf8CString)) != 0
}
public static func getAddress(dbData: URL, account: Int32) -> String? {
let dbData = osStrFromURL(dbData)
static func getAddress(dbData: URL, account: Int32) -> String? {
let dbData = dbData.osStr()
let addressCStr = zcashlc_get_address(dbData.0, dbData.1, account)
if addressCStr == nil {
@ -64,18 +70,18 @@ public class ZcashRustBackend {
return address
}
public static func getBalance(dbData: URL, account: Int32) -> Int64 {
let dbData = osStrFromURL(dbData)
static func getBalance(dbData: URL, account: Int32) -> Int64 {
let dbData = dbData.osStr()
return zcashlc_get_balance(dbData.0, dbData.1, account)
}
public static func getVerifiedBalance(dbData: URL, account: Int32) -> Int64 {
let dbData = osStrFromURL(dbData)
static func getVerifiedBalance(dbData: URL, account: Int32) -> Int64 {
let dbData = dbData.osStr()
return zcashlc_get_verified_balance(dbData.0, dbData.1, account)
}
public static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
let dbData = osStrFromURL(dbData)
static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
let dbData = dbData.osStr()
let memoCStr = zcashlc_get_received_memo_as_utf8(dbData.0, dbData.1, idNote)
if memoCStr == nil {
@ -87,8 +93,8 @@ public class ZcashRustBackend {
return memo
}
public static func getSentMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
let dbData = osStrFromURL(dbData)
static func getSentMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
let dbData = dbData.osStr()
let memoCStr = zcashlc_get_sent_memo_as_utf8(dbData.0, dbData.1, idNote)
if memoCStr == nil {
@ -100,27 +106,36 @@ public class ZcashRustBackend {
return memo
}
public static func validateCombinedChain(dbCache: URL, dbData: URL) -> Int32 {
let dbCache = osStrFromURL(dbCache)
let dbData = osStrFromURL(dbData)
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)
}
public static func rewindToHeight(dbData: URL, height: Int32) -> Bool {
let dbData = osStrFromURL(dbData)
static func rewindToHeight(dbData: URL, height: Int32) -> Bool {
let dbData = dbData.osStr()
return zcashlc_rewind_to_height(dbData.0, dbData.1, height) != 0
}
public static func scanBlocks(dbCache: URL, dbData: URL) -> Bool {
let dbCache = osStrFromURL(dbCache)
let dbData = osStrFromURL(dbData)
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
}
public static func sendToAddress(dbData: URL, account: Int32, extsk: String, to: String, value: Int64, memo: String?, spendParams: URL, outputParams: URL) -> Int64 {
let dbData = osStrFromURL(dbData)
let spendParams = osStrFromURL(spendParams)
let outputParams = osStrFromURL(outputParams)
static func sendToAddress(dbData: URL, account: Int32, extsk: String, to: String, value: Int64, memo: String?, spendParams: URL, outputParams: URL) -> Int64 {
let dbData = dbData.osStr()
let spendParams = spendParams.osStr()
let outputParams = outputParams.osStr()
return zcashlc_send_to_address(dbData.0, dbData.1, account, extsk, to, value, memo, spendParams.0, spendParams.1, outputParams.0, outputParams.1)
}
}
private extension URL {
func osStr() -> (String, UInt) {
let path = self.absoluteString
return (path, UInt(path.lengthOfBytes(using: .utf8)))
}
}

View File

@ -0,0 +1,40 @@
//
// ZcashRustBackendWelding.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
protocol ZcashRustBackendWelding {
static func lastError() -> Error?
static func getLastError() -> String?
static func initDataDb(dbData: URL) -> Bool
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]?
static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) -> Bool
static func getAddress(dbData: URL, account: Int32) -> String?
static func getBalance(dbData: URL, account: Int32) -> Int64
static func getVerifiedBalance(dbData: URL, account: Int32) -> Int64
static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64) -> String?
static func getSentMemoAsUTF8(dbData: URL, idNote: Int64) -> String?
static func validateCombinedChain(dbCache: URL, dbData: URL) -> Int32
static func rewindToHeight(dbData: URL, height: Int32) -> Bool
static func scanBlocks(dbCache: URL, dbData: URL) -> Bool
static func sendToAddress(dbData: URL, account: Int32, extsk: String, to: String, value: Int64, memo: String?, spendParams: URL, outputParams: URL) -> Int64
}

View File

@ -0,0 +1,138 @@
//
// ServiceHelper.swift
// gRPC-PoC
//
// Created by Francisco Gindre on 29/08/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SwiftGRPC
class LightWalletGRPCService {
var queue = DispatchQueue.init(label: "LightWalletGRPCService")
let channel: Channel
let compactTxStreamer: CompactTxStreamerServiceClient
init(channel: Channel) {
self.channel = channel
compactTxStreamer = CompactTxStreamerServiceClient(channel: self.channel)
}
func blockRange(startHeight: BlockHeight, endHeight: BlockHeight? = nil, result: @escaping (CallResult)->()) throws -> CompactTxStreamerGetBlockRangeCall {
try compactTxStreamer.getBlockRange(BlockRange(startHeight: startHeight, endHeight: endHeight)) { result($0) }
}
func latestBlock() throws -> BlockID {
try compactTxStreamer.getLatestBlock(ChainSpec())
}
func getTx(hash:String) throws -> RawTransaction {
var filter = TxFilter()
filter.hash = Data(hash.utf8)
return try compactTxStreamer.getTransaction(filter)
}
func getAllBlocksSinceSaplingLaunch(_ result: @escaping (CallResult)->()) throws -> CompactTxStreamerGetBlockRangeCall {
try compactTxStreamer.getBlockRange(BlockRange.sinceSaplingActivation(), completion: result)
}
}
extension LightWalletGRPCService: LightWalletService {
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
var blocks = [ZcashCompactBlock]()
let call = try compactTxStreamer.getBlockRange(range.blockRange(), completion: { statusCode in
print("finished with statusCode: \(statusCode)")
})
while let block = try call.receive() {
if let compactBlock = ZcashCompactBlock(compactBlock: block) {
blocks.append(compactBlock)
} else {
throw LightWalletServiceError.invalidBlock
}
}
return blocks
}
func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> ()) {
do {
try compactTxStreamer.getLatestBlock(ChainSpec()) { (blockID, callResult) in
guard let rawHeight = blockID?.height, let blockHeight = Int(exactly: rawHeight) else {
result(.failure(LightWalletServiceError.generalError))
return
}
result(.success(blockHeight))
}
} catch {
// TODO: Handle Error
print(error.localizedDescription)
result(.failure(LightWalletServiceError.generalError))
}
}
// TODO: Make cancellable
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
queue.async {
var blocks = [CompactBlock]()
var isSyncing = true
guard let response = try? self.compactTxStreamer.getBlockRange(range.blockRange(),completion: { (callResult) in
isSyncing = false
if callResult.success {
let code = callResult.statusCode
switch code{
case .ok:
do {
result(.success(try blocks.asZcashCompactBlocks()))
} catch {
result(.failure(LightWalletServiceError.generalError))
}
default:
result(.failure(LightWalletServiceError.failed(statusCode: code)))
}
} else {
result(.failure(LightWalletServiceError.generalError))
return
}
}) else {
result(.failure(LightWalletServiceError.generalError))
return
}
do {
var element: CompactBlock?
repeat {
element = try response.receive()
if let e = element {
blocks.append(e)
}
} while isSyncing && element != nil
} catch {
result(.failure(LightWalletServiceError.generalError))
}
}
}
func latestBlockHeight() throws -> BlockHeight {
guard let height = try? latestBlock().compactBlockHeight() else {
throw LightWalletServiceError.invalidBlock
}
return height
}
}

View File

@ -0,0 +1,85 @@
//
// LightWalletService.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SwiftGRPC
public enum LightWalletServiceError: Error {
case generalError
case failed(statusCode: StatusCode)
case invalidBlock
}
extension LightWalletServiceError: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
switch lhs {
case .generalError:
switch rhs {
case .generalError:
return true
default:
return false
}
case .failed(let statusCode):
switch rhs {
case .failed(let anotherStatus):
return statusCode == anotherStatus
default:
return false
}
case .invalidBlock:
switch rhs {
case .invalidBlock:
return true
default:
return false
}
}
}
}
public protocol LightWalletService {
/**
Return the latest block height known to the service.
- Parameter result: a result containing the height or an Error
*/
func latestBlockHeight(result: @escaping (Result<BlockHeight,LightWalletServiceError>) -> ())
/**
Return the latest block height known to the service.
- Parameter result: a result containing the height or an Error
*/
func latestBlockHeight() throws -> BlockHeight
/**
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.
Non blocking
*/
func blockRange(_ range: Range<BlockHeight>, 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.
blocking
*/
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock]
}

View File

@ -0,0 +1,56 @@
//
// ZcashCompactBlock.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
public typealias BlockHeight = Int
public typealias CompactBlockRange = Range<BlockHeight>
enum ZcashCompactBlockError: Error {
case unreadableBlock(compactBlock: CompactBlock)
}
extension BlockHeight {
static func empty() -> BlockHeight {
BlockHeight(-1)
}
}
extension ZcashCompactBlock {
init?(compactBlock: CompactBlock) {
do {
// Safe to try: 32-bit systems will nil
guard let h = Int(exactly: compactBlock.height) else { return nil }
self.height = h
self.data = try compactBlock.serializedData()
} catch {
return nil
}
}
}
extension ZcashCompactBlock: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
if lhs.height != rhs.height {
return false
}
return lhs.data == rhs.data
}
}
extension ZcashCompactBlock: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(height)
hasher.combine(data)
}
}

View File

@ -0,0 +1,76 @@
//
// Protocolbuffer+Extensions.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
extension CompactBlockRange {
func blockRange() -> BlockRange {
BlockRange(startHeight: lowerBound, endHeight: upperBound)
}
}
extension BlockID {
static let saplingActivationHeight: UInt64 = 280_000
init(height: UInt64) {
self = BlockID()
self.height = height
}
static var saplingActivation: BlockID {
BlockID(height: saplingActivationHeight)
}
init(height: BlockHeight) {
self.init(height: UInt64(height))
}
func compactBlockHeight() -> BlockHeight? {
BlockHeight(exactly: self.height)
}
}
extension BlockRange {
init(startHeight: Int, endHeight: Int? = nil) {
self = BlockRange()
self.start = BlockID(height: UInt64(startHeight))
if let endHeight = endHeight {
self.end = BlockID(height: UInt64(endHeight))
}
}
static func sinceSaplingActivation(to height: UInt64? = nil) -> BlockRange {
var blockRange = BlockRange()
blockRange.start = BlockID.saplingActivation
if let height = height {
blockRange.end = BlockID.init(height: height)
}
return blockRange
}
}
extension Array where Element == CompactBlock {
func asZcashCompactBlocks() throws -> [ZcashCompactBlock] {
var result = [ZcashCompactBlock]()
for i in 0 ..< self.count {
guard let zBlock = ZcashCompactBlock(compactBlock: self[i]) else {
throw ZcashCompactBlockError.unreadableBlock(compactBlock: self[i])
}
result.append(zBlock)
}
return result
}
}

View File

@ -0,0 +1,300 @@
// DO NOT EDIT.
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: compact_formats.proto
//
// For information on using the generated types, please see the documenation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that your are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
/// CompactBlock is a packaging of ONLY the data from a block that's needed to:
/// 1. Detect a payment to your shielded Sapling address
/// 2. Detect a spend of your shielded Sapling notes
/// 3. Update your witnesses to generate new Sapling spend proofs.
struct CompactBlock {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// the version of this wire format, for storage
var protoVersion: UInt32 = 0
/// the height of this block
var height: UInt64 = 0
var hash: Data = SwiftProtobuf.Internal.emptyData
var prevHash: Data = SwiftProtobuf.Internal.emptyData
var time: UInt32 = 0
/// (hash, prevHash, and time) OR (full header)
var header: Data = SwiftProtobuf.Internal.emptyData
/// compact transactions from this block
var vtx: [CompactTx] = []
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
struct CompactTx {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// Index and hash will allow the receiver to call out to chain
/// explorers or other data structures to retrieve more information
/// about this transaction.
var index: UInt64 = 0
var hash: Data = SwiftProtobuf.Internal.emptyData
/// The transaction fee: present if server can provide. In the case of a
/// stateless server and a transaction with transparent inputs, this will be
/// unset because the calculation requires reference to prior transactions.
/// in a pure-Sapling context, the fee will be calculable as:
/// valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
var fee: UInt32 = 0
var spends: [CompactSpend] = []
var outputs: [CompactOutput] = []
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
struct CompactSpend {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var nf: Data = SwiftProtobuf.Internal.emptyData
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
struct CompactOutput {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var cmu: Data = SwiftProtobuf.Internal.emptyData
var epk: Data = SwiftProtobuf.Internal.emptyData
var ciphertext: Data = SwiftProtobuf.Internal.emptyData
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "cash.z.wallet.sdk.rpc"
extension CompactBlock: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".CompactBlock"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "protoVersion"),
2: .same(proto: "height"),
3: .same(proto: "hash"),
4: .same(proto: "prevHash"),
5: .same(proto: "time"),
6: .same(proto: "header"),
7: .same(proto: "vtx"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularUInt32Field(value: &self.protoVersion)
case 2: try decoder.decodeSingularUInt64Field(value: &self.height)
case 3: try decoder.decodeSingularBytesField(value: &self.hash)
case 4: try decoder.decodeSingularBytesField(value: &self.prevHash)
case 5: try decoder.decodeSingularUInt32Field(value: &self.time)
case 6: try decoder.decodeSingularBytesField(value: &self.header)
case 7: try decoder.decodeRepeatedMessageField(value: &self.vtx)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.protoVersion != 0 {
try visitor.visitSingularUInt32Field(value: self.protoVersion, fieldNumber: 1)
}
if self.height != 0 {
try visitor.visitSingularUInt64Field(value: self.height, fieldNumber: 2)
}
if !self.hash.isEmpty {
try visitor.visitSingularBytesField(value: self.hash, fieldNumber: 3)
}
if !self.prevHash.isEmpty {
try visitor.visitSingularBytesField(value: self.prevHash, fieldNumber: 4)
}
if self.time != 0 {
try visitor.visitSingularUInt32Field(value: self.time, fieldNumber: 5)
}
if !self.header.isEmpty {
try visitor.visitSingularBytesField(value: self.header, fieldNumber: 6)
}
if !self.vtx.isEmpty {
try visitor.visitRepeatedMessageField(value: self.vtx, fieldNumber: 7)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: CompactBlock, rhs: CompactBlock) -> Bool {
if lhs.protoVersion != rhs.protoVersion {return false}
if lhs.height != rhs.height {return false}
if lhs.hash != rhs.hash {return false}
if lhs.prevHash != rhs.prevHash {return false}
if lhs.time != rhs.time {return false}
if lhs.header != rhs.header {return false}
if lhs.vtx != rhs.vtx {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension CompactTx: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".CompactTx"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "index"),
2: .same(proto: "hash"),
3: .same(proto: "fee"),
4: .same(proto: "spends"),
5: .same(proto: "outputs"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularUInt64Field(value: &self.index)
case 2: try decoder.decodeSingularBytesField(value: &self.hash)
case 3: try decoder.decodeSingularUInt32Field(value: &self.fee)
case 4: try decoder.decodeRepeatedMessageField(value: &self.spends)
case 5: try decoder.decodeRepeatedMessageField(value: &self.outputs)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.index != 0 {
try visitor.visitSingularUInt64Field(value: self.index, fieldNumber: 1)
}
if !self.hash.isEmpty {
try visitor.visitSingularBytesField(value: self.hash, fieldNumber: 2)
}
if self.fee != 0 {
try visitor.visitSingularUInt32Field(value: self.fee, fieldNumber: 3)
}
if !self.spends.isEmpty {
try visitor.visitRepeatedMessageField(value: self.spends, fieldNumber: 4)
}
if !self.outputs.isEmpty {
try visitor.visitRepeatedMessageField(value: self.outputs, fieldNumber: 5)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: CompactTx, rhs: CompactTx) -> Bool {
if lhs.index != rhs.index {return false}
if lhs.hash != rhs.hash {return false}
if lhs.fee != rhs.fee {return false}
if lhs.spends != rhs.spends {return false}
if lhs.outputs != rhs.outputs {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension CompactSpend: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".CompactSpend"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "nf"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularBytesField(value: &self.nf)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.nf.isEmpty {
try visitor.visitSingularBytesField(value: self.nf, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: CompactSpend, rhs: CompactSpend) -> Bool {
if lhs.nf != rhs.nf {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension CompactOutput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".CompactOutput"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "cmu"),
2: .same(proto: "epk"),
3: .same(proto: "ciphertext"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularBytesField(value: &self.cmu)
case 2: try decoder.decodeSingularBytesField(value: &self.epk)
case 3: try decoder.decodeSingularBytesField(value: &self.ciphertext)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.cmu.isEmpty {
try visitor.visitSingularBytesField(value: self.cmu, fieldNumber: 1)
}
if !self.epk.isEmpty {
try visitor.visitSingularBytesField(value: self.epk, fieldNumber: 2)
}
if !self.ciphertext.isEmpty {
try visitor.visitSingularBytesField(value: self.ciphertext, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: CompactOutput, rhs: CompactOutput) -> Bool {
if lhs.cmu != rhs.cmu {return false}
if lhs.epk != rhs.epk {return false}
if lhs.ciphertext != rhs.ciphertext {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View File

@ -0,0 +1,48 @@
syntax = "proto3";
package cash.z.wallet.sdk.rpc;
option go_package = "walletrpc";
option swift_prefix = "";
// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value.
// bytes fields of hashes are in canonical little-endian format.
// CompactBlock is a packaging of ONLY the data from a block that's needed to:
// 1. Detect a payment to your shielded Sapling address
// 2. Detect a spend of your shielded Sapling notes
// 3. Update your witnesses to generate new Sapling spend proofs.
message CompactBlock {
uint32 protoVersion = 1; // the version of this wire format, for storage
uint64 height = 2; // the height of this block
bytes hash = 3;
bytes prevHash = 4;
uint32 time = 5;
bytes header = 6; // (hash, prevHash, and time) OR (full header)
repeated CompactTx vtx = 7; // compact transactions from this block
}
message CompactTx {
// Index and hash will allow the receiver to call out to chain
// explorers or other data structures to retrieve more information
// about this transaction.
uint64 index = 1;
bytes hash = 2;
// The transaction fee: present if server can provide. In the case of a
// stateless server and a transaction with transparent inputs, this will be
// unset because the calculation requires reference to prior transactions.
// in a pure-Sapling context, the fee will be calculable as:
// valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
uint32 fee = 3;
repeated CompactSpend spends = 4;
repeated CompactOutput outputs = 5;
}
message CompactSpend {
bytes nf = 1;
}
message CompactOutput {
bytes cmu = 1;
bytes epk = 2;
bytes ciphertext = 3;
}

View File

@ -0,0 +1,49 @@
syntax = "proto3";
package cash.z.wallet.sdk.rpc;
option go_package = "walletrpc";
option swift_prefix = "";
import "compact_formats.proto";
// A BlockID message contains identifiers to select a block: a height or a
// hash. If the hash is present it takes precedence.
message BlockID {
uint64 height = 1;
bytes hash = 2;
}
// BlockRange technically allows ranging from hash to hash etc but this is not
// currently intended for support, though there is no reason you couldn't do
// it. Further permutations are left as an exercise.
message BlockRange {
BlockID start = 1;
BlockID end = 2;
}
// A TxFilter contains the information needed to identify a particular
// transaction: either a block and an index, or a direct transaction hash.
message TxFilter {
BlockID block = 1;
uint64 index = 2;
bytes hash = 3;
}
// RawTransaction contains the complete transaction data.
message RawTransaction {
bytes data = 1;
}
message SendResponse {
int32 errorCode = 1;
string errorMessage = 2;
}
// Empty placeholder. Someday we may want to specify e.g. a particular chain fork.
message ChainSpec {}
service CompactTxStreamer {
rpc GetLatestBlock(ChainSpec) returns (BlockID) {}
rpc GetBlock(BlockID) returns (CompactBlock) {}
rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {}
rpc GetTransaction(TxFilter) returns (RawTransaction) {}
rpc SendTransaction(RawTransaction) returns (SendResponse) {}
}

View File

@ -0,0 +1,208 @@
//
// DO NOT EDIT.
//
// Generated by the protocol buffer compiler.
// Source: service.proto
//
//
// Copyright 2018, gRPC Authors All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Dispatch
import Foundation
import SwiftGRPC
import SwiftProtobuf
internal protocol CompactTxStreamerGetLatestBlockCall: ClientCallUnary {}
fileprivate final class CompactTxStreamerGetLatestBlockCallBase: ClientCallUnaryBase<ChainSpec, BlockID>, CompactTxStreamerGetLatestBlockCall {
override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock" }
}
internal protocol CompactTxStreamerGetBlockCall: ClientCallUnary {}
fileprivate final class CompactTxStreamerGetBlockCallBase: ClientCallUnaryBase<BlockID, CompactBlock>, CompactTxStreamerGetBlockCall {
override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock" }
}
internal protocol CompactTxStreamerGetBlockRangeCall: ClientCallServerStreaming {
/// Do not call this directly, call `receive()` in the protocol extension below instead.
func _receive(timeout: DispatchTime) throws -> CompactBlock?
/// Call this to wait for a result. Nonblocking.
func receive(completion: @escaping (ResultOrRPCError<CompactBlock?>) -> Void) throws
}
internal extension CompactTxStreamerGetBlockRangeCall {
/// Call this to wait for a result. Blocking.
func receive(timeout: DispatchTime = .distantFuture) throws -> CompactBlock? { return try self._receive(timeout: timeout) }
}
fileprivate final class CompactTxStreamerGetBlockRangeCallBase: ClientCallServerStreamingBase<BlockRange, CompactBlock>, CompactTxStreamerGetBlockRangeCall {
override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange" }
}
internal protocol CompactTxStreamerGetTransactionCall: ClientCallUnary {}
fileprivate final class CompactTxStreamerGetTransactionCallBase: ClientCallUnaryBase<TxFilter, RawTransaction>, CompactTxStreamerGetTransactionCall {
override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction" }
}
internal protocol CompactTxStreamerSendTransactionCall: ClientCallUnary {}
fileprivate final class CompactTxStreamerSendTransactionCallBase: ClientCallUnaryBase<RawTransaction, SendResponse>, CompactTxStreamerSendTransactionCall {
override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction" }
}
/// Instantiate CompactTxStreamerServiceClient, then call methods of this protocol to make API calls.
internal protocol CompactTxStreamerService: ServiceClient {
/// Synchronous. Unary.
func getLatestBlock(_ request: ChainSpec, metadata customMetadata: Metadata) throws -> BlockID
/// Asynchronous. Unary.
@discardableResult
func getLatestBlock(_ request: ChainSpec, metadata customMetadata: Metadata, completion: @escaping (BlockID?, CallResult) -> Void) throws -> CompactTxStreamerGetLatestBlockCall
/// Synchronous. Unary.
func getBlock(_ request: BlockID, metadata customMetadata: Metadata) throws -> CompactBlock
/// Asynchronous. Unary.
@discardableResult
func getBlock(_ request: BlockID, metadata customMetadata: Metadata, completion: @escaping (CompactBlock?, CallResult) -> Void) throws -> CompactTxStreamerGetBlockCall
/// Asynchronous. Server-streaming.
/// Send the initial message.
/// Use methods on the returned object to get streamed responses.
func getBlockRange(_ request: BlockRange, metadata customMetadata: Metadata, completion: ((CallResult) -> Void)?) throws -> CompactTxStreamerGetBlockRangeCall
/// Synchronous. Unary.
func getTransaction(_ request: TxFilter, metadata customMetadata: Metadata) throws -> RawTransaction
/// Asynchronous. Unary.
@discardableResult
func getTransaction(_ request: TxFilter, metadata customMetadata: Metadata, completion: @escaping (RawTransaction?, CallResult) -> Void) throws -> CompactTxStreamerGetTransactionCall
/// Synchronous. Unary.
func sendTransaction(_ request: RawTransaction, metadata customMetadata: Metadata) throws -> SendResponse
/// Asynchronous. Unary.
@discardableResult
func sendTransaction(_ request: RawTransaction, metadata customMetadata: Metadata, completion: @escaping (SendResponse?, CallResult) -> Void) throws -> CompactTxStreamerSendTransactionCall
}
internal extension CompactTxStreamerService {
/// Synchronous. Unary.
func getLatestBlock(_ request: ChainSpec) throws -> BlockID {
return try self.getLatestBlock(request, metadata: self.metadata)
}
/// Asynchronous. Unary.
@discardableResult
func getLatestBlock(_ request: ChainSpec, completion: @escaping (BlockID?, CallResult) -> Void) throws -> CompactTxStreamerGetLatestBlockCall {
return try self.getLatestBlock(request, metadata: self.metadata, completion: completion)
}
/// Synchronous. Unary.
func getBlock(_ request: BlockID) throws -> CompactBlock {
return try self.getBlock(request, metadata: self.metadata)
}
/// Asynchronous. Unary.
@discardableResult
func getBlock(_ request: BlockID, completion: @escaping (CompactBlock?, CallResult) -> Void) throws -> CompactTxStreamerGetBlockCall {
return try self.getBlock(request, metadata: self.metadata, completion: completion)
}
/// Asynchronous. Server-streaming.
func getBlockRange(_ request: BlockRange, completion: ((CallResult) -> Void)?) throws -> CompactTxStreamerGetBlockRangeCall {
return try self.getBlockRange(request, metadata: self.metadata, completion: completion)
}
/// Synchronous. Unary.
func getTransaction(_ request: TxFilter) throws -> RawTransaction {
return try self.getTransaction(request, metadata: self.metadata)
}
/// Asynchronous. Unary.
@discardableResult
func getTransaction(_ request: TxFilter, completion: @escaping (RawTransaction?, CallResult) -> Void) throws -> CompactTxStreamerGetTransactionCall {
return try self.getTransaction(request, metadata: self.metadata, completion: completion)
}
/// Synchronous. Unary.
func sendTransaction(_ request: RawTransaction) throws -> SendResponse {
return try self.sendTransaction(request, metadata: self.metadata)
}
/// Asynchronous. Unary.
@discardableResult
func sendTransaction(_ request: RawTransaction, completion: @escaping (SendResponse?, CallResult) -> Void) throws -> CompactTxStreamerSendTransactionCall {
return try self.sendTransaction(request, metadata: self.metadata, completion: completion)
}
}
internal final class CompactTxStreamerServiceClient: ServiceClientBase, CompactTxStreamerService {
/// Synchronous. Unary.
internal func getLatestBlock(_ request: ChainSpec, metadata customMetadata: Metadata) throws -> BlockID {
return try CompactTxStreamerGetLatestBlockCallBase(channel)
.run(request: request, metadata: customMetadata)
}
/// Asynchronous. Unary.
@discardableResult
internal func getLatestBlock(_ request: ChainSpec, metadata customMetadata: Metadata, completion: @escaping (BlockID?, CallResult) -> Void) throws -> CompactTxStreamerGetLatestBlockCall {
return try CompactTxStreamerGetLatestBlockCallBase(channel)
.start(request: request, metadata: customMetadata, completion: completion)
}
/// Synchronous. Unary.
internal func getBlock(_ request: BlockID, metadata customMetadata: Metadata) throws -> CompactBlock {
return try CompactTxStreamerGetBlockCallBase(channel)
.run(request: request, metadata: customMetadata)
}
/// Asynchronous. Unary.
@discardableResult
internal func getBlock(_ request: BlockID, metadata customMetadata: Metadata, completion: @escaping (CompactBlock?, CallResult) -> Void) throws -> CompactTxStreamerGetBlockCall {
return try CompactTxStreamerGetBlockCallBase(channel)
.start(request: request, metadata: customMetadata, completion: completion)
}
/// Asynchronous. Server-streaming.
/// Send the initial message.
/// Use methods on the returned object to get streamed responses.
internal func getBlockRange(_ request: BlockRange, metadata customMetadata: Metadata, completion: ((CallResult) -> Void)?) throws -> CompactTxStreamerGetBlockRangeCall {
return try CompactTxStreamerGetBlockRangeCallBase(channel)
.start(request: request, metadata: customMetadata, completion: completion)
}
/// Synchronous. Unary.
internal func getTransaction(_ request: TxFilter, metadata customMetadata: Metadata) throws -> RawTransaction {
return try CompactTxStreamerGetTransactionCallBase(channel)
.run(request: request, metadata: customMetadata)
}
/// Asynchronous. Unary.
@discardableResult
internal func getTransaction(_ request: TxFilter, metadata customMetadata: Metadata, completion: @escaping (RawTransaction?, CallResult) -> Void) throws -> CompactTxStreamerGetTransactionCall {
return try CompactTxStreamerGetTransactionCallBase(channel)
.start(request: request, metadata: customMetadata, completion: completion)
}
/// Synchronous. Unary.
internal func sendTransaction(_ request: RawTransaction, metadata customMetadata: Metadata) throws -> SendResponse {
return try CompactTxStreamerSendTransactionCallBase(channel)
.run(request: request, metadata: customMetadata)
}
/// Asynchronous. Unary.
@discardableResult
internal func sendTransaction(_ request: RawTransaction, metadata customMetadata: Metadata, completion: @escaping (SendResponse?, CallResult) -> Void) throws -> CompactTxStreamerSendTransactionCall {
return try CompactTxStreamerSendTransactionCallBase(channel)
.start(request: request, metadata: customMetadata, completion: completion)
}
}

View File

@ -0,0 +1,408 @@
// DO NOT EDIT.
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: service.proto
//
// For information on using the generated types, please see the documenation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that your are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
/// A BlockID message contains identifiers to select a block: a height or a
/// hash. If the hash is present it takes precedence.
struct BlockID {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var height: UInt64 = 0
var hash: Data = SwiftProtobuf.Internal.emptyData
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
/// BlockRange technically allows ranging from hash to hash etc but this is not
/// currently intended for support, though there is no reason you couldn't do
/// it. Further permutations are left as an exercise.
struct BlockRange {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var start: BlockID {
get {return _storage._start ?? BlockID()}
set {_uniqueStorage()._start = newValue}
}
/// Returns true if `start` has been explicitly set.
var hasStart: Bool {return _storage._start != nil}
/// Clears the value of `start`. Subsequent reads from it will return its default value.
mutating func clearStart() {_uniqueStorage()._start = nil}
var end: BlockID {
get {return _storage._end ?? BlockID()}
set {_uniqueStorage()._end = newValue}
}
/// Returns true if `end` has been explicitly set.
var hasEnd: Bool {return _storage._end != nil}
/// Clears the value of `end`. Subsequent reads from it will return its default value.
mutating func clearEnd() {_uniqueStorage()._end = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _storage = _StorageClass.defaultInstance
}
/// A TxFilter contains the information needed to identify a particular
/// transaction: either a block and an index, or a direct transaction hash.
struct TxFilter {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var block: BlockID {
get {return _storage._block ?? BlockID()}
set {_uniqueStorage()._block = newValue}
}
/// Returns true if `block` has been explicitly set.
var hasBlock: Bool {return _storage._block != nil}
/// Clears the value of `block`. Subsequent reads from it will return its default value.
mutating func clearBlock() {_uniqueStorage()._block = nil}
var index: UInt64 {
get {return _storage._index}
set {_uniqueStorage()._index = newValue}
}
var hash: Data {
get {return _storage._hash}
set {_uniqueStorage()._hash = newValue}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _storage = _StorageClass.defaultInstance
}
/// RawTransaction contains the complete transaction data.
struct RawTransaction {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var data: Data = SwiftProtobuf.Internal.emptyData
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
struct SendResponse {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var errorCode: Int32 = 0
var errorMessage: String = String()
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
/// Empty placeholder. Someday we may want to specify e.g. a particular chain fork.
struct ChainSpec {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "cash.z.wallet.sdk.rpc"
extension BlockID: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".BlockID"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "height"),
2: .same(proto: "hash"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularUInt64Field(value: &self.height)
case 2: try decoder.decodeSingularBytesField(value: &self.hash)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.height != 0 {
try visitor.visitSingularUInt64Field(value: self.height, fieldNumber: 1)
}
if !self.hash.isEmpty {
try visitor.visitSingularBytesField(value: self.hash, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: BlockID, rhs: BlockID) -> Bool {
if lhs.height != rhs.height {return false}
if lhs.hash != rhs.hash {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension BlockRange: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".BlockRange"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "start"),
2: .same(proto: "end"),
]
fileprivate class _StorageClass {
var _start: BlockID? = nil
var _end: BlockID? = nil
static let defaultInstance = _StorageClass()
private init() {}
init(copying source: _StorageClass) {
_start = source._start
_end = source._end
}
}
fileprivate mutating func _uniqueStorage() -> _StorageClass {
if !isKnownUniquelyReferenced(&_storage) {
_storage = _StorageClass(copying: _storage)
}
return _storage
}
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
_ = _uniqueStorage()
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularMessageField(value: &_storage._start)
case 2: try decoder.decodeSingularMessageField(value: &_storage._end)
default: break
}
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._start {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
if let v = _storage._end {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
}
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: BlockRange, rhs: BlockRange) -> Bool {
if lhs._storage !== rhs._storage {
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
let _storage = _args.0
let rhs_storage = _args.1
if _storage._start != rhs_storage._start {return false}
if _storage._end != rhs_storage._end {return false}
return true
}
if !storagesAreEqual {return false}
}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension TxFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".TxFilter"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "block"),
2: .same(proto: "index"),
3: .same(proto: "hash"),
]
fileprivate class _StorageClass {
var _block: BlockID? = nil
var _index: UInt64 = 0
var _hash: Data = SwiftProtobuf.Internal.emptyData
static let defaultInstance = _StorageClass()
private init() {}
init(copying source: _StorageClass) {
_block = source._block
_index = source._index
_hash = source._hash
}
}
fileprivate mutating func _uniqueStorage() -> _StorageClass {
if !isKnownUniquelyReferenced(&_storage) {
_storage = _StorageClass(copying: _storage)
}
return _storage
}
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
_ = _uniqueStorage()
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularMessageField(value: &_storage._block)
case 2: try decoder.decodeSingularUInt64Field(value: &_storage._index)
case 3: try decoder.decodeSingularBytesField(value: &_storage._hash)
default: break
}
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._block {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
if _storage._index != 0 {
try visitor.visitSingularUInt64Field(value: _storage._index, fieldNumber: 2)
}
if !_storage._hash.isEmpty {
try visitor.visitSingularBytesField(value: _storage._hash, fieldNumber: 3)
}
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: TxFilter, rhs: TxFilter) -> Bool {
if lhs._storage !== rhs._storage {
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
let _storage = _args.0
let rhs_storage = _args.1
if _storage._block != rhs_storage._block {return false}
if _storage._index != rhs_storage._index {return false}
if _storage._hash != rhs_storage._hash {return false}
return true
}
if !storagesAreEqual {return false}
}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension RawTransaction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".RawTransaction"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "data"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularBytesField(value: &self.data)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.data.isEmpty {
try visitor.visitSingularBytesField(value: self.data, fieldNumber: 1)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: RawTransaction, rhs: RawTransaction) -> Bool {
if lhs.data != rhs.data {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension SendResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".SendResponse"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "errorCode"),
2: .same(proto: "errorMessage"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularInt32Field(value: &self.errorCode)
case 2: try decoder.decodeSingularStringField(value: &self.errorMessage)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.errorCode != 0 {
try visitor.visitSingularInt32Field(value: self.errorCode, fieldNumber: 1)
}
if !self.errorMessage.isEmpty {
try visitor.visitSingularStringField(value: self.errorMessage, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SendResponse, rhs: SendResponse) -> Bool {
if lhs.errorCode != rhs.errorCode {return false}
if lhs.errorMessage != rhs.errorMessage {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension ChainSpec: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ChainSpec"
static let _protobuf_nameMap = SwiftProtobuf._NameMap()
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let _ = try decoder.nextFieldNumber() {
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: ChainSpec, rhs: ChainSpec) -> Bool {
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View File

@ -0,0 +1,91 @@
//
// Wallet.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 13/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
/**
Wrapper for the Rust backend. This class basically represents all the Rust-wallet
capabilities and the supporting data required to exercise those abilities.
*/
public enum WalletError: Error {
case cacheDbInitFailed
case dataDbInitFailed
}
public class Wallet {
private var rustBackend: ZcashRustBackendWelding.Type
private var dataDbURL: URL
private var cacheDbURL: URL
private var paramDestination: URL
private var accountIDs: [Int]
private var seedProvider: SeedProvider
private var walletBirthday: WalletBirthday
private var storage: Storage?
init(rustWelding: ZcashRustBackendWelding.Type, cacheDbURL:URL, dataDbURL: URL, paramDestination: URL, seedProvider: SeedProvider, walletBirthday: WalletBirthday, accountIDs: [Int] = [0]) {
self.rustBackend = rustWelding.self
self.dataDbURL = dataDbURL
self.paramDestination = paramDestination
self.accountIDs = accountIDs
self.seedProvider = seedProvider
self.cacheDbURL = cacheDbURL
self.walletBirthday = walletBirthday
}
public func initalize(firstRunStartHeight: BlockHeight = SAPLING_ACTIVATION_HEIGHT) throws {
guard let storage = StorageBuilder.cacheDb(at: cacheDbURL) else {
throw WalletError.cacheDbInitFailed
}
guard rustBackend.initDataDb(dbData: dataDbURL) else {
throw WalletError.dataDbInitFailed
}
self.storage = storage
}
public func latestBlockHeight() -> Int? {
try? self.storage?.compactBlockDao.latestBlockHeight()
}
}
/**
Represents the wallet's birthday which can be thought of as a checkpoint at the earliest moment in history where
transactions related to this wallet could exist. Ideally, this would correspond to the latest block height at the
time the wallet key was created. Worst case, the height of Sapling activation could be used (280000).
Knowing a wallet's birthday can significantly reduce the amount of data that it needs to download because none of
the data before that height needs to be scanned for transactions. However, we do need the Sapling tree data in
order to construct valid transactions from that point forward. This birthday contains that tree data, allowing us
to avoid downloading all the compact blocks required in order to generate it.
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
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: TimeInterval = -1
var tree: String = ""
}

View File

@ -0,0 +1,122 @@
//
// BlockDownloaderTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 18/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import XCTest
@testable import ZcashLightClientKit
class BlockDownloaderTests: XCTestCase {
var downloader: CompactBlockDownloading!
var service: LightWalletService!
var storage: CompactBlockStoring!
override func setUp() {
service = LightWalletGRPCService(channel: ChannelProvider().channel())
storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
downloader = CompactBlockDownloader(service: service, storage: storage)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
service = nil
storage = nil
downloader = nil
}
func testSmallDownloadAsync() {
let expect = XCTestExpectation(description: self.description)
expect.expectedFulfillmentCount = 3
let lowerRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT
let upperRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT + 99
let range = CompactBlockRange(uncheckedBounds: (lowerRange,upperRange))
downloader.downloadBlockRange(range) { (error) in
expect.fulfill()
XCTAssertNil(error)
// check what was 'stored'
self.storage.latestHeight { (result) in
expect.fulfill()
XCTAssertTrue(self.validate(result: result, against: upperRange))
self.downloader.latestBlockHeight { (resultHeight) in
expect.fulfill()
XCTAssertTrue(self.validate(result: resultHeight, against: upperRange))
}
}
}
wait(for: [expect], timeout: 2)
}
func testSmallDownload() {
let lowerRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT
let upperRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT + 99
let range = CompactBlockRange(uncheckedBounds: (lowerRange,upperRange))
var latest: BlockHeight = 0
do {
latest = try downloader.latestBlockHeight()
} catch {
XCTFail(error.localizedDescription)
}
XCTAssertEqual(latest, BlockHeight.empty())
XCTAssertNoThrow(try downloader.downloadBlockRange(range))
var currentLatest: BlockHeight = 0
do {
currentLatest = try downloader.latestBlockHeight()
} catch {
XCTFail("latest block failed")
return
}
XCTAssertEqual(currentLatest,upperRange )
}
func testFailure() {
let awfulDownloader = CompactBlockDownloader(service: AwfulLightWalletService(), storage: ZcashConsoleFakeStorage())
let expect = XCTestExpectation(description: self.description)
expect.expectedFulfillmentCount = 1
let lowerRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT
let upperRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT + 99
let range = CompactBlockRange(uncheckedBounds: (lowerRange,upperRange))
awfulDownloader.downloadBlockRange(range) { (error) in
expect.fulfill()
XCTAssertNotNil(error)
}
wait(for: [expect], timeout: 2)
}
}
/// Helper functions
extension BlockDownloaderTests {
func validate(result: Result<BlockHeight,Error> ,against height: BlockHeight) -> Bool {
switch result {
case .success(let resultHeight):
return resultHeight == height
default:
return false
}
}
}

View File

@ -0,0 +1,81 @@
//
// BlockScanOperationTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 10/17/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import XCTest
import SQLite
@testable import ZcashLightClientKit
class BlockScanOperationTests: XCTestCase {
var operationQueue = OperationQueue()
var cacheDbURL: URL!
var dataDbURL: URL!
let rustWelding = ZcashRustBackend.self
var blockRepository: BlockRepository!
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
self.cacheDbURL = try! __cacheDbURL()
self.dataDbURL = try! __dataDbURL()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
operationQueue.cancelAllOperations()
// try! FileManager.default.removeItem(at: cacheDbURL)
try! FileManager.default.removeItem(at: dataDbURL)
}
func testSingleDownloadAndScanOperation() {
guard rustWelding.initDataDb(dbData: dataDbURL) else {
XCTFail("could not initialize data DB")
return
}
blockRepository = try! BlockSQLDAO(dataDb: dataDbURL)
let downloadExpect = XCTestExpectation(description: self.description + "download")
let scanExpect = XCTestExpectation(description: self.description + "scan")
let service = LightWalletGRPCService(channel: ChannelProvider().channel())
let storage = try! TestDbBuilder.diskCompactBlockStorage(at: cacheDbURL)
let downloader = CompactBlockDownloader(service: service, storage: storage)
let blockCount = 100
let range = SAPLING_ACTIVATION_HEIGHT ..< SAPLING_ACTIVATION_HEIGHT + blockCount
let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range)
let scanOperation = CompactBlockScanningOperation(rustWelding: rustWelding, cacheDb: cacheDbURL, dataDb: dataDbURL)
downloadOperation.completionHandler = { (finished, cancelled, error) in
downloadExpect.fulfill()
XCTAssertNil(error)
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
scanOperation.completionHandler = { (finished, cancelled, error) in
scanExpect.fulfill()
XCTAssertNil(error)
XCTAssertFalse(cancelled)
XCTAssertTrue(finished)
}
scanOperation.addDependency(downloadOperation)
operationQueue.addOperation(downloadOperation)
operationQueue.addOperation(scanOperation)
wait(for: [downloadExpect, scanExpect], timeout: 5, enforceOrder: true)
XCTAssertEqual(try! storage.latestHeight(),range.endIndex)
XCTAssertEqual(blockRepository.lastScannedBlockHeight(), range.endIndex)
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -0,0 +1,61 @@
//
// CompactBlockProcessorTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 20/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import XCTest
@testable import ZcashLightClientKit
class CompactBlockProcessorTests: XCTestCase {
let processorConfig = CompactBlockProcessor.Configuration.standard
var processor: CompactBlockProcessor!
var expect: XCTestExpectation!
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
XCTAssertTrue(MockDbInit.emptyFile(at: processorConfig.cacheDbPath))
XCTAssertTrue(MockDbInit.emptyFile(at: processorConfig.dataDbPath))
let service = LightWalletGRPCService(channel: ChannelProvider().channel())
let storage = ZcashConsoleFakeStorage()
let downloader = CompactBlockDownloader(service: service, storage: storage)
expect = XCTestExpectation(description: self.description)
processor = CompactBlockProcessor(downloader: downloader,
backend: ZcashRustBackend.self,
config: processorConfig,
service: service)
}
override func tearDown() {
do {
try MockDbInit.destroy(at: processorConfig.cacheDbPath)
try MockDbInit.destroy(at: processorConfig.dataDbPath)
} catch {
XCTFail(error.localizedDescription)
}
expect.unsuscribeFromNotifications()
}
func testStartNotifiesSuscriptors() {
XCTAssertNotNil(processor)
expect.suscribe(to: Notification.Name.blockProcessorStarted, object: processor)
XCTAssertNoThrow(try processor.start())
wait(for: [expect], timeout: 5)
}
}

View File

@ -0,0 +1,89 @@
//
// CompactBlockStorageTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 10/13/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import XCTest
@testable import ZcashLightClientKit
class CompactBlockStorageTests: XCTestCase {
var storage: Storage = try! TestDbBuilder.inMemory()
func testEmptyStorage() {
XCTAssertEqual(try! storage.compactBlockDao.latestBlockHeight(), BlockHeight.empty())
}
func testStoreThousandBlocks() {
let initialHeight = try! storage.compactBlockDao.latestBlockHeight()
let startHeight = SAPLING_ACTIVATION_HEIGHT
let blockCount = Int(1_000)
let finalHeight = startHeight + blockCount
do {
try TestDbBuilder.seed(db: storage, with: CompactBlockRange(startHeight...finalHeight))
} catch {
XCTFail("seed faild with error: \(error)")
return
}
let latestHeight = try! storage.compactBlockDao.latestBlockHeight()
XCTAssertNotEqual(initialHeight, latestHeight)
XCTAssertEqual(latestHeight, finalHeight)
}
func testStoreOneBlockFromEmpty() {
let initialHeight = try! storage.compactBlockDao.latestBlockHeight()
guard initialHeight == BlockHeight.empty() else {
XCTFail("database not empty, latest height: \(initialHeight)")
return
}
let expectedHeight = BlockHeight(123_456)
guard let block = StubBlockCreator.createRandomDataBlock(with: expectedHeight) else {
XCTFail("could not create randem block with height: \(expectedHeight)")
return
}
XCTAssertNoThrow(try storage.compactBlockDao.insert(block))
do {
let result = try storage.compactBlockDao.latestBlockHeight()
XCTAssertEqual(result, expectedHeight)
} catch {
XCTFail("latestBlockHeight failed")
return
}
}
func testRewindTo() {
let startHeight = SAPLING_ACTIVATION_HEIGHT
let blockCount = Int(1_000)
let finalHeight = startHeight + blockCount
do {
try TestDbBuilder.seed(db: storage, with: CompactBlockRange(startHeight...finalHeight))
} catch {
XCTFail("seed faild with error: \(error)")
return
}
let rewindHeight = BlockHeight(finalHeight - 233)
XCTAssertNoThrow(try storage.compactBlockDao.rewind(to: rewindHeight))
do {
let latestHeight = try storage.compactBlockDao.latestBlockHeight()
XCTAssertEqual(latestHeight, rewindHeight - 1)
} catch {
XCTFail("Rewind latest block failed with error: \(error)")
}
}
}

View File

@ -0,0 +1,4 @@
public struct Constants {
static let address: String = "{{ argument.addr }}"
}

View File

@ -0,0 +1,46 @@
//
// DownloadOperationTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 10/16/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import XCTest
import SQLite
@testable import ZcashLightClientKit
class DownloadOperationTests: XCTestCase {
var operationQueue = OperationQueue()
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
operationQueue.cancelAllOperations()
}
func testSingleOperation() {
let expect = XCTestExpectation(description: self.description)
let service = LightWalletGRPCService(channel: ChannelProvider().channel())
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
let downloader = CompactBlockDownloader(service: service, storage: storage)
let blockCount = 100
let range = SAPLING_ACTIVATION_HEIGHT ..< SAPLING_ACTIVATION_HEIGHT + blockCount
let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range)
downloadOperation.completionHandler = { (finished, cancelled, error) in
expect.fulfill()
XCTAssertNil(error)
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
operationQueue.addOperation(downloadOperation)
wait(for: [expect], timeout: 5)
XCTAssertEqual(try! storage.latestHeight(),range.endIndex)
}
}

View File

@ -0,0 +1,92 @@
//
// LightWalletServiceTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 18/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import XCTest
@testable import ZcashLightClientKit
import SwiftGRPC
class LightWalletServiceTests: XCTestCase {
var service: LightWalletService!
var channel: Channel!
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
channel = ChannelProvider().channel()
service = LightWalletGRPCService(channel: channel)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
channel.shutdown()
}
func testFailure() {
let expect = XCTestExpectation(description: self.description)
let excessivelyHugeRange = Range<BlockHeight>(uncheckedBounds: (lower: 280_000, upper: 600_000))
service.blockRange(excessivelyHugeRange) { (result) in
XCTAssertEqual(result, .failure(LightWalletServiceError.failed(statusCode: SwiftGRPC.StatusCode.unknown)))
expect.fulfill()
}
wait(for: [expect], timeout: 5)
}
func testHundredBlocks() {
let expect = XCTestExpectation(description: self.description)
let lowerRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT
let upperRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT + 99
let blockRange = Range<BlockHeight>(uncheckedBounds: (lower: lowerRange, upper: upperRange))
service.blockRange(blockRange) { (result) in
expect.fulfill()
switch result {
case .failure(let error):
XCTFail("failed with error \(error)")
case .success(let blocks):
XCTAssertEqual(blocks.count, 100)
XCTAssertEqual(blocks[0].height, lowerRange)
}
}
wait(for: [expect], timeout: 5)
}
func testSyncBlockRange() {
let lowerRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT
let upperRange: BlockHeight = SAPLING_ACTIVATION_HEIGHT + 99
let blockRange = CompactBlockRange(uncheckedBounds: (lower: lowerRange, upper: upperRange))
do {
let blocks = try service.blockRange(blockRange)
XCTAssertEqual(blocks.count, blockRange.count + 1)
} catch {
XCTFail("\(error)")
}
}
func testLatestBlock(){
let expect = XCTestExpectation(description: self.description)
service.latestBlockHeight { (result) in
expect.fulfill()
switch result {
case .failure(let e):
XCTFail("error: \(e)")
case .success(let height):
XCTAssertTrue(height > SAPLING_ACTIVATION_HEIGHT)
}
}
wait(for: [expect], timeout: 5)
}
}

View File

@ -0,0 +1,62 @@
//
// WalletTests.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 13/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import XCTest
@testable import ZcashLightClientKit
class WalletTests: XCTestCase {
var dbData: URL! = nil
var paramDestination: URL! = nil
var cacheData: URL! = nil
override func setUp() {
let dataDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
dbData = dataDir.appendingPathComponent("data.db")
cacheData = dataDir.appendingPathComponent("cache.db")
paramDestination = dataDir.appendingPathComponent("parameters")
}
override func tearDown() {
if FileManager.default.fileExists(atPath: dbData.absoluteString) {
try! FileManager.default.trashItem(at: dbData, resultingItemURL: nil)
}
}
func testWalletInitialization() {
let wallet = Wallet(rustWelding: ZcashRustBackend.self, cacheDbURL: cacheData, dataDbURL: dbData, paramDestination: paramDestination, seedProvider: SampleSeedProvider(), walletBirthday: WalletBirthdayProvider.testBirthday)
XCTAssertNoThrow(try wallet.initalize())
// fileExists actually sucks, so attempting to delete the file and checking what happens is far better :)
XCTAssertNoThrow( try FileManager.default.removeItem(at: dbData!) )
guard let latestBlockHeight = wallet.latestBlockHeight() else {
XCTFail("should get BlockHeight.empty() from freshly initialized wallet")
return
}
XCTAssertEqual(latestBlockHeight, BlockHeight.empty())
XCTAssertNoThrow( try FileManager.default.removeItem(at: cacheData!) )
}
}
struct SampleSeedProvider: SeedProvider {
func seed() -> [UInt8] {
Array("seed".utf8)
}
}
struct WalletBirthdayProvider {
static var testBirthday: WalletBirthday {
WalletBirthday()
}
}

View File

@ -7,29 +7,93 @@
//
import XCTest
import SwiftGRPC
@testable import ZcashLightClientKit
class ZcashLightClientKitTests: XCTestCase {
var latestBlockHeight: BlockHeight!
var service: LightWalletGRPCService!
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
super.setUp()
service = LightWalletGRPCService(channel: ChannelProvider().channel())
latestBlockHeight = try! service.latestBlock().compactBlockHeight()!
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
service.channel.shutdown()
service = nil
latestBlockHeight = nil
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
XCTAssert(ZcashRustBackend.getLastError() == nil)
func testEnvironmentLaunch() {
let address = Constants.address
XCTAssertFalse(address.isEmpty, "Your \'\(Environment.lightwalletdKey)\' key is missing from your launch environment variables")
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
func testService() {
// and that it has a non-zero size
XCTAssert(latestBlockHeight > 0)
}
// /**
// LIGHTWALLETD KILLER TEST - DO NOT USE
// */
// func testBlockRangeService() {
//
// let expect = XCTestExpectation(description: self.debugDescription)
// let _ = try? service.getAllBlocksSinceSaplingLaunch(){ result in
// print(result)
// expect.fulfill()
// XCTAssert(result.success)
// XCTAssertNotNil(result.resultData)
// }
// wait(for: [expect], timeout: 10)
// }
func testBlockRangeServiceTilLastest() {
let expectedCount: BlockHeight = 99
var count: BlockHeight = 0
let startHeight = latestBlockHeight - expectedCount
let endHeight = latestBlockHeight!
guard let call = try? service!.blockRange(startHeight: startHeight, endHeight: endHeight,result: {
result in
XCTAssert(result.success)
}) else {
XCTFail("failed to create getBlockRange( \(startHeight) ..<= \(endHeight)")
return
}
var blocks = [CompactBlock]()
while true {
guard let block = try? call.receive() else {
break
}
blocks.append(block)
count += 1
}
XCTAssertEqual(expectedCount + 1, count)
}
}
class Environment {
static let lightwalletdKey = "LIGHTWALLETD_ADDRESS"
}

View File

@ -10,38 +10,56 @@ import XCTest
@testable import ZcashLightClientKit
class ZcashRustBackendTests: XCTestCase {
var dbData: URL? = nil
var dbData: URL!
override func setUp() {
let dataDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
dbData = dataDir.appendingPathComponent("data.db")
dbData = try! __dataDbURL()
}
override func tearDown() {
// Delete test database between runs
do {
try FileManager.default.removeItem(at: dbData!)
} catch {
}
try? FileManager.default.removeItem(at: dbData!)
}
func testInitAndGetAddress() {
let seed = "seed"
XCTAssert(ZcashRustBackend.initDataDb(dbData: dbData!))
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
let _ = ZcashRustBackend.initAccountsTable(dbData: dbData!, seed: Array(seed.utf8), accounts: 1)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
let addr = ZcashRustBackend.getAddress(dbData: dbData!, account: 0)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertEqual(addr, Optional("ztestsapling1meqz0cd598fw0jlq2htkuarg8gqv36fam83yxmu5mu3wgkx4khlttqhqaxvwf57urm3rqsq9t07"))
// Test invalid account
let addr2 = ZcashRustBackend.getAddress(dbData: dbData!, account: 1)
XCTAssert(ZcashRustBackend.getLastError() != nil)
XCTAssertEqual(addr2, nil)
}
func testInitAndScanBlocks() {
guard let cacheDb = Bundle(for: Self.self).url(forResource: "cache", withExtension: "db") else {
XCTFail("pre populated Db not present")
return
}
let seed = "seed"
XCTAssertTrue(ZcashRustBackend.initDataDb(dbData: dbData!))
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
let _ = ZcashRustBackend.initAccountsTable(dbData: dbData!, seed: Array(seed.utf8), accounts: 1)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
let addr = ZcashRustBackend.getAddress(dbData: dbData!, account: 0)
XCTAssertEqual(ZcashRustBackend.getLastError(), nil)
XCTAssertEqual(addr, Optional("ztestsapling1meqz0cd598fw0jlq2htkuarg8gqv36fam83yxmu5mu3wgkx4khlttqhqaxvwf57urm3rqsq9t07"))
XCTAssertTrue(ZcashRustBackend.scanBlocks(dbCache: cacheDb, dbData: dbData))
}
}

Binary file not shown.

View File

@ -0,0 +1,67 @@
//
// Storage.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
@testable import ZcashLightClientKit
class ZcashConsoleFakeStorage: CompactBlockStoring {
func latestHeight() throws -> Int {
return self.latestBlockHeight
}
func write(blocks: [ZcashCompactBlock]) throws {
fakeSave(blocks: blocks)
}
func rewind(to height: BlockHeight) throws {
fakeRewind(to: height)
}
var latestBlockHeight: BlockHeight = 0
var delay = DispatchTimeInterval.milliseconds(300)
init(latestBlockHeight: BlockHeight = 0) {
self.latestBlockHeight = latestBlockHeight
}
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
result(.success(self.latestBlockHeight))
}
}
fileprivate func fakeSave(blocks: [ZcashCompactBlock]) {
blocks.forEach {
print("saving block \($0)")
self.latestBlockHeight = $0.height
}
}
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.fakeSave(blocks: blocks)
completion?(nil)
}
}
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.fakeRewind(to: height)
completion?(nil)
}
}
private func fakeRewind(to height: BlockHeight) {
print("rewind to \(height)")
self.latestBlockHeight = min(self.latestBlockHeight, height)
}
}

View File

@ -0,0 +1,35 @@
//
// Stubs.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 18/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import ZcashLightClientKit
class AwfulLightWalletService: LightWalletService {
func latestBlockHeight() throws -> BlockHeight {
throw LightWalletServiceError.generalError
}
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
throw LightWalletServiceError.invalidBlock
}
func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
result(.failure(LightWalletServiceError.generalError))
}
}
func blockRange(_ range: Range<BlockHeight>, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
result(.failure(LightWalletServiceError.generalError))
}
}
}

View File

@ -0,0 +1,86 @@
//
// TestDbBuilder.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 10/14/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SQLite
@testable import ZcashLightClientKit
struct TestDbBuilder {
enum TestBuilderError: Error {
case generalError
}
static func inMemory() throws -> Storage {
let connection = try Connection(.inMemory, readonly: false)
let compactBlockDao = CompactBlockStorage(connection: connection)
try compactBlockDao.createTable()
return SQLiteStorage(connection: connection, compactBlockDAO: compactBlockDao)
}
static func inMemoryCompactBlockStorage() throws -> CompactBlockStorage {
let connection = try Connection(.inMemory, readonly: false)
let compactBlockDao = CompactBlockStorage(connection: connection)
try compactBlockDao.createTable()
return compactBlockDao
}
static func diskCompactBlockStorage(at url:URL) throws -> CompactBlockStorage {
let connection = try Connection(url.absoluteString)
let compactBlockDao = CompactBlockStorage(connection: connection)
try compactBlockDao.createTable()
return compactBlockDao
}
static func seed(db: Storage, with blockRange: CompactBlockRange) throws {
guard let blocks = StubBlockCreator.createBlockRange(blockRange) else {
throw TestBuilderError.generalError
}
try db.compactBlockDao.insert(blocks)
}
}
struct StubBlockCreator {
static func createRandomDataBlock(with height: BlockHeight) -> ZcashCompactBlock? {
guard let data = randomData(ofLength: 100) else {
print("error creating stub block")
return nil
}
return ZcashCompactBlock(height: height, data: data)
}
static func createBlockRange(_ range: CompactBlockRange) -> [ZcashCompactBlock]? {
var blocks = [ZcashCompactBlock]()
for height in range {
guard let block = createRandomDataBlock(with: height) else {
return nil
}
blocks.append(block)
}
return blocks
}
static func randomData(ofLength length: Int) -> Data? {
var bytes = [UInt8](repeating: 0, count: length)
let status = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
if status == errSecSuccess {
return Data(bytes: &bytes, count: bytes.count)
}
print("Status \(status)")
return nil
}
}

View File

@ -0,0 +1,71 @@
//
// Tests+Utils.swift
// ZcashLightClientKitTests
//
// Created by Francisco Gindre on 18/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
import SwiftGRPC
import XCTest
class ChannelProvider {
func channel() -> SwiftGRPC.Channel {
Channel(address: Constants.address, secure: false)
}
}
struct MockDbInit {
@discardableResult static func emptyFile(at path: String) -> Bool {
FileManager.default.createFile(atPath: path, contents: Data("".utf8), attributes: nil)
}
static func destroy(at path: String) throws {
try FileManager.default.removeItem(atPath: path)
}
}
extension XCTestExpectation {
func suscribe(to notification: Notification.Name, object: Any?) {
NotificationCenter.default.addObserver(self, selector: #selector(fulfill), name: notification, object: object)
}
func unsuscribe(from notification: Notification.Name) {
NotificationCenter.default.removeObserver(self, name: notification, object: nil)
}
func unsuscribeFromNotifications() {
NotificationCenter.default.removeObserver(self)
}
}
func __documentsDirectory() throws -> URL {
try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
}
func __cacheDbURL() throws -> URL {
try __documentsDirectory().appendingPathComponent("cache.db", isDirectory: false)
}
func __dataDbURL() throws -> URL {
try __documentsDirectory().appendingPathComponent("data.db", isDirectory: false)
}
func parametersReady() -> Bool {
guard let output = try? __documentsDirectory().appendingPathComponent("sapling-output.params", isDirectory: false),
let spend = try? __documentsDirectory().appendingPathComponent("sapling-spend.params", isDirectory: false),
FileManager.default.isReadableFile(atPath: output.absoluteString),
FileManager.default.isReadableFile(atPath: spend.absoluteString) else {
return false
}
return true
}

View File

@ -2,3 +2,4 @@ $(SRCROOT)/Carthage/Build/iOS/BoringSSL.framework
$(SRCROOT)/Carthage/Build/iOS/CgRPC.framework
$(SRCROOT)/Carthage/Build/iOS/SwiftGRPC.framework
$(SRCROOT)/Carthage/Build/iOS/SwiftProtobuf.framework
$(SRCROOT)/Carthage/Build/iOS/SQLite.framework

View File

@ -2,3 +2,4 @@ $(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/BoringSSL.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/CgRPC.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftGRPC.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftProtobuf.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SQLite.framework