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:
parent
8456ccc5b2
commit
60ea9d6737
|
@ -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
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
github "grpc/grpc-swift" "0.9.1"
|
||||
github "stephencelis/SQLite.swift" "0.12.2"
|
||||
|
|
26
README.md
26
README.md
|
@ -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...
|
||||
|
|
|
@ -10,4 +10,3 @@ framework module ZcashLightClientKit {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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)")}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)?)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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"
|
|
@ -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)"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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
|
|
@ -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)))
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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]
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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) {}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 = ""
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
public struct Constants {
|
||||
static let address: String = "{{ argument.addr }}"
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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.
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue