[#209] Add support for multiple instances of the SDKSynchronizer

Closes #209.

[#845] Introduce ZcashSynchronizerAlias enum

Closes #845.

[#852] SDKSynchronizer using queues label based on the alias

Closes #852.

[#847] Remove posibility to use DatabaseStorageManager as singleton

Closes #847.

[#850] Remove synchronizerConnectionStateChanged notification

Closes #850.

[#855] Add check if the Alias is already used

Closes #855

- Added `UsedAliasesChecker` utility which is used to register aliases
  that are in use.
- `prepare()` and `wipe()` methods now check if the current alias can't
  be used and if not then `InitializerError.aliasAlreadyInUse` is
  thrown/emitted.
- Some public methods that could cause harm if used with the Alias that
  is already in use now throw `SynchronizerError.notPrepared`. Thanks to
  this the client app is forced to call `prepare()` first. And
  `prepare()` does check for the Alias.
- Added tests for new conditions.

[#849] Make InternalSyncProgress aware of the Alias

Closes #849.

[#853] Only instance with default Alias migrates legacy cache DB

Closes #853.

[#851] Apply the Alias to the URLs

Closes #851.

- `Initializer` now updates paths according to alias before paths are
  used anywhere in the SDK.
- Paths update can fail. It would be incovenient for the client apps to
  handle errors thrown from `Initiliazer` constructor. So the error is
  then handled in `SDKSynchronizer.prepare()` or `SDKSynchronizer.wipe()`.

[#846] Stop using SDKMetrics as singleton (#862)

- metrics are not longer a singleton
- tests fixed
- metrics outside init of the synchronizer

[#848] Make logger aware of the alias

- logger is now an instance passed throughout the sdk instead of a static proxy

[#848] Make logger aware of the alias (#868)

- comments addressed

[#848] Make logger aware of the alias (#868)

- returning protocol back

Fix typos

[#856] Add possibility to test multiple synchronizers in the sample app

Closes #856.

- Added `alias` property to `Synchronizer`.
- Added `SyncBlocksListViewController` which provides UI to use multiple
  synchronizers at once.

[#209] Add changelog

- Add changelog for #209.
- Overall improve readability of the rendered changelog. Tickets
  references are now prefixed with `###` instead of `- `.

Fix compilation
This commit is contained in:
Michal Fousek 2023-03-22 13:47:32 +01:00
parent 2ff6b81b4b
commit 5c979f42e6
74 changed files with 2237 additions and 663 deletions

View File

@ -1,7 +1,30 @@
# unreleased
- [#831] Add support for alternative APIs
### [#209] Support Initializer Aliases
There are two new protocols (`ClosureSynchronizer` and `CombineSynchronizer`). And there are two new
Added `ZcashSynchronizerAlias` enum which is used to identify an instance of the `SDKSynchronizer`. All the paths
to all resources (databases, filesystem block storage...) are updated automatically inside the SDK according to the
alias. So it's safe to create multiple instances of the `SDKSynchronizer`. Each instance must have unique Alias. If
the `default` alias is used then the SDK works the same as before this change was introduced.
The SDK now also checks which aliases are used and it prevents situations when two instances of the `SDKSynchronizer`
has the same alias. Methods `prepare()` and `wipe()` do checks for used alias. And those methods fail
with `InitializerError.aliasAlreadyInUse` if the alias is already used.
If the alias check fails in the `prepare()` method then the status of the `SDKSynchronizer` isn't switched from `unprepared`.
These methods newly throw `SynchronizerError.notPrepared` error when the status is `unprepared`:
- `sendToAddress(spendingKey:zatoshi:toAddress:memo:) async throws -> PendingTransactionEntity`
- `shieldFundsspendingKey:memo:shieldingThreshold:) async throws -> PendingTransactionEntity`
- `latestUTXOs(address:) async throws -> [UnspentTransactionOutputEntity]`
- `refreshUTXOs(address:from:) async throws -> RefreshedUTXOs`
- `rewind(policy:) -> AnyPublisher<Void, Error>`
Provided file URLs to resources (databases, filesystem block storage...) are now parsed inside the SDK and updated
according to the alias. If some error during this happens then `SDKSynchronzer.prepare()` method throws
`InitializerError.cantUpdateURLWithAlias` error.
### [#831] Add support for alternative APIs
There are two new protocols (`ClosureSynchronizer` and `CombineSynchronizer`). And there are two new
objects which conform to respective protocols (`ClosureSDKSynchronizer` and `CombineSDKSynchronizer`). These
new objects offer alternative API for the `SDKSynchronizer`. Now the client app can choose which technology
it wants to use to communicate with Zcash SDK and it isn't forced to use async.
@ -13,25 +36,24 @@ These methods in the `SDKSynchronizer` are now async:
Non async `SDKsynchronizer.latestHeight(result:)` were moved to `ClosureSDKSynchronizer`.
- [#724] Switch from event based notifications to state based notifications
### [#724] Switch from event based notifications to state based notifications
The `SDKSynchronizer` no longer uses `NotificationCenter` to send notifications.
The `SDKSynchronizer` no longer uses `NotificationCenter` to send notifications.
Notifications are replaced with `Combine` publishers. Check the migrating document and
documentation in the code to get more information.
- [#826] Change how the SDK is initialized
### [#826] Change how the SDK is initialized
- `viewingKeys` and `walletBirthday` are removed from `Initializer` constuctor. These parameters
are moved to `SDKSynchronizer.prepare` function.
- Constructor of the `SDKSynchronizer` no longer throws exception.
- Any value emitted from `lastState` stream before `SDKSynchronizer.prepare` is called has
`latestScannedHeight` set to 0.
- `Initializer.initialize` function isn't public anymore. To initialize SDK call `SDKSynchronizer.prepare`
- `viewingKeys` and `walletBirthday` are removed from `Initializer` constuctor. These parameters moved to
`SDKSynchronizer.prepare` function.
- Constructor of the `SDKSynchronizer` no longer throws exception.
- Any value emitted from `lastState` stream before `SDKSynchronizer.prepare` is called has `latestScannedHeight` set to 0.
- `Initializer.initialize` function isn't public anymore. To initialize SDK call `SDKSynchronizer.prepare`
instead.
# 0.19.1-beta
## Checkpoints added
### Checkpoints added
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/2002500.json
@ -45,7 +67,7 @@ Testnet
Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2250000.json
````
## Fixed
- [#821] `failedToWriteMetadata` at sync startup
### [#821] `failedToWriteMetadata` at sync startup
contains no public API changes.
Adds `func shouldClearBlockCacheAndUpdateInternalState() -> BlockHeight?` to `SyncRanges`
so that the compact block processor can advert internal states that are not consistent and
@ -59,51 +81,50 @@ For concrete examples check out the tests in:
Removed linter binary from repository
# 0.19.0-beta
- [#816] Improve how rewind call can be used
### [#816] Improve how rewind call can be used
`SDKSynchronizer.rewind(policy:)` function can be now called anytime. It returns `AnyPublisher` which
`SDKSynchronizer.rewind(policy:)` function can be now called anytime. It returns `AnyPublisher` which
completes or fails when the rewind is done. For more details read the documentation for this method
in the code.
- [#801] Improve how wipe call can be used
### [#801] Improve how wipe call can be used
`SDKSynchronizer.wipe()` function can be now called anytime. It returns `AnyPublisher` which
`SDKSynchronizer.wipe()` function can be now called anytime. It returns `AnyPublisher` which
completes or fails when the wipe is done. For more details read the documentation for this method
in the code.
- [#793] Send synchronizerStopped notification only when sync process stops
### [#793] Send synchronizerStopped notification only when sync process stops
`synchronizerStopped` notification is now sent after the sync process stops. It's
`synchronizerStopped` notification is now sent after the sync process stops. It's
not sent right when `stop()` method is called.
- [#795] Include sapling-spend file into bundle for tests
### [#795] Include sapling-spend file into bundle for tests
This is only an internal change and doesn't change the behavior of the SDK. `Initializer`'s
This is only an internal change and doesn't change the behavior of the SDK. `Initializer`'s
constructor has a new parameter `saplingParamsSourceURL`. Use `SaplingParamsSourceURL.default`
value for this parameter.
- [#764] Refactor communication between components inside th SDK
### [#764] Refactor communication between components inside th SDK
This is mostly an internal change. A consequence of this change is that all the notifications
This is mostly an internal change. A consequence of this change is that all the notifications
delivered via `NotificationCenter` with the prefix `blockProcessor` are now gone. If affected
notifications were used in your code use notifications with the prefix `synchronizer` now.
These notifications are defined in `SDKSynchronizer.swift`.
- [#759] Remove Jazz-generated HTML docs
### [#759] Remove Jazz-generated HTML docs
We remove these documents since they are outdated and we rely on the docs in the
code itself.
We remove these documents since they are outdated and we rely on the docs in the code itself.
- [#726] Modularize GRPC layer
### [#726] Modularize GRPC layer
This is mostly internal change. `LightWalletService` is no longer public. If it
This is mostly internal change. `LightWalletService` is no longer public. If it
is used in your code replace it by using `SDKSynchronizer` API.
- [#770] Update GRPC swift library
This updates to GRPC-Swift 1.14.0.
### [#770] Update GRPC swift library
This updates to GRPC-Swift 1.14.0.
Checkpoints added:
### Checkpoints added:
Mainnet:
````
@ -175,32 +196,32 @@ to move away from cacheDb.
## Other Issues Fixed by this PR:
- [#587] ShieldFundsTests:
### [#587] ShieldFundsTests:
- https://github.com/zcash/ZcashLightClientKit/issues/720
- https://github.com/zcash/ZcashLightClientKit/issues/587
- https://github.com/zcash/ZcashLightClientKit/issues/667
- [#443] Delete blocks from cache after processing them
### [#443] Delete blocks from cache after processing them
Closes https://github.com/zcash/ZcashLightClientKit/issues/443
- [#754] adopt name change in libzashlc package that fixes a deprecation in SPM
### [#754] adopt name change in libzashlc package that fixes a deprecation in SPM
Closes https://github.com/zcash/ZcashLightClientKit/issues/754
# 0.18.1-beta
- [#767] implement getRecipients() for Synchronizer.
### [#767] implement getRecipients() for Synchronizer.
This implements `getRecipients()` function which retrieves the possible
recipients from a sent transaction. These can either be addresses or
internal accounts depending on the transaction being a shielding tx
or a regular outgoing transaction.
Other changes:
- Fix version of zcash-light-client-ffi to 0.1.1
- Enhance error reporting on a test make Mock comply with protocol
Other changes:
- Fix version of zcash-light-client-ffi to 0.1.1
- Enhance error reporting on a test make Mock comply with protocol
# 0.18.0-beta
## Farewell Cocoapods.
- [#612] Remove Support for Cocoapods (#706)
### [#612] Remove Support for Cocoapods (#706)
It wouldn't have been possible to release an SDK without you, pal.
@ -211,7 +232,7 @@ We've been communicating this for a long time. Although, if you really need Coco
please let us know by opening an issue in our repo and we'll talk about it.
## New Checkpoints
### New Checkpoints
Checkpoints
Mainnet
@ -238,67 +259,67 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2200000.json
## Bugfixes
- [#645] Default rewind after ReOrg is 20 blocks when it should be 10
This fixes an issue where the default reorg was 20 blocks rewind instead of 10. The
### [#645] Default rewind after ReOrg is 20 blocks when it should be 10
This fixes an issue where the default reorg was 20 blocks rewind instead of 10. The
reorg count was incremented before calling the rewind height computing function.
## Use Librustzcash database views to query and represent transactions
- [#556] Change data structures which represent transactions.
### [#556] Change data structures which represent transactions.
These data types are gone: `Transaction`, `TransactionEntity`, `ConfirmedTransaction`,
These data types are gone: `Transaction`, `TransactionEntity`, `ConfirmedTransaction`,
`ConfirmedTransactionEntity`. And these data types were added: `ZcashTransaction.Overview`,
`ZcashTransaction.Received`, `ZcashTransaction.Sent`.
New data structures are very similar to the old ones. Although there many breaking changes.
New data structures are very similar to the old ones. Although there many breaking changes.
The APIs of the `SDKSynchronizer` remain unchanged in their behavior. They return different
data types. **When adopting this change, you should check which data types are used by methods
of the `SDKSynchronizer` in your code and change them accordingly.**
New transaction structures no longer have a `memo` property. This responds to the fact that
New transaction structures no longer have a `memo` property. This responds to the fact that
Zcash transactions can have either none or multiple memos. To get memos for the transaction
the `SDKSynchronizer` has now new methods to fetch those:
- `func getMemos(for transaction: ZcashTransaction.Overview) throws -> [Memo]`,
- `func getMemos(for receivedTransaction: ZcashTransaction.Received) throws -> [Memo]`
- `func getMemos(for sentTransaction: ZcashTransaction.Sent) throws -> [Memo]`
- `func getMemos(for transaction: ZcashTransaction.Overview) throws -> [Memo]`,
- `func getMemos(for receivedTransaction: ZcashTransaction.Received) throws -> [Memo]`
- `func getMemos(for sentTransaction: ZcashTransaction.Sent) throws -> [Memo]`
## CompactBlockProcessor is now internal
- [#671] Make CompactBlockProcessor Internal.
### [#671] Make CompactBlockProcessor Internal.
The CompactBlockProcessor is no longer a public class/API. Any direct access will
The CompactBlockProcessor is no longer a public class/API. Any direct access will
end up as a compiler error. Recommended way how to handle things is via `SDKSynchronizer`
from now on. The Demo app has been updated accordingly as well.
## We've changed how we download and scan blocks. Status reporting has changed.
- [#657] Change how blocks are downloaded and scanned.
### [#657] Change how blocks are downloaded and scanned.
In previous versions, the SDK first downloaded all the blocks and then it
In previous versions, the SDK first downloaded all the blocks and then it
scanned all the blocks. This approach requires a lot of disk space. The SDK now
behaves differently. It downloads a batch of blocks (100 by default), scans those, and
removes those blocks from the disk. And repeats this until all the blocks are processed.
`SyncStatus` was changed. `.downloading`, `.validating`, and `.scanning` symbols
`SyncStatus` was changed. `.downloading`, `.validating`, and `.scanning` symbols
were removed. And the `.scanning` symbol was added. The removed phases of the sync
process are now reported as one phase.
Notifications were also changed similarly. These notifications were
Notifications were also changed similarly. These notifications were
removed: `SDKSynchronizerDownloading`, `SDKSyncronizerValidating`, and `SDKSyncronizerScanning`.
And the `SDKSynchronizerSyncing` notification was added. The added notification replaces
the removed notifications.
## New Wipe Method to delete wallet information. Use with care.
- [#677] Add support for wallet wipe into SDK. Add new method `Synchronizer.wipe()`.
### [#677] Add support for wallet wipe into SDK. Add new method `Synchronizer.wipe()`.
## Benchmarking APIs: A primer
- [#663] Foundations for the benchmarking/performance testing in the SDK.
### [#663] Foundations for the benchmarking/performance testing in the SDK.
This change presents 2 building blocks for the future automated tests, consisting
This change presents 2 building blocks for the future automated tests, consisting
of a new SDKMetrics interface to control flow of the data in the SDK and
new performance (unit) test measuring synchronization of 100 mainnet blocks.
# 0.17.6-beta
- [#756] 0.17.5-beta updates to libzcashlc 0.2.0 when it shouldn't
### [#756] 0.17.5-beta updates to libzcashlc 0.2.0 when it shouldn't
Updated checkpoints to the ones present in 0.18.0-beta
# 0.17.5-beta
@ -328,11 +349,12 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2170000.json
````
# 0.17.4-beta
- [#665] Fix testShieldFunds() `get_transparent_balance` error
### [#665] Fix testShieldFunds() `get_transparent_balance` error
updates `libzcashlc` to `0.1.1` to fix an error where getting a
transparent balance on an empty database would fail.
# 0.17.3-beta
- [#646] SDK sync process resumes to previously saved block height
### [#646] SDK sync process resumes to previously saved block height
This change adds an internal storage test on UserDefaults that tells the
SDK where sync was left off when cancelled whatever the reason for it
to restart on a later attempt. This fixes some issues around syncing
@ -367,23 +389,23 @@ Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1631000.json
```
# 0.17.2-beta
- [#660] Fix the situation when any rewind causes full rescan
### [#660] Fix the situation when any rewind causes full rescan
# 0.17.1-beta
- [#651] Change the rewind behavior. Now if the rewind is used while the sync process is in progress then an exception is thrown.
- [#616] Download Stream generates too many updates on the main thread
### [#651] Change the rewind behavior. Now if the rewind is used while the sync process is in progress then an exception is thrown.
### [#616] Download Stream generates too many updates on the main thread
> **WARNING**: Notifications from SDK are no longer delivered on main thread.
- [#585] Fix RewindRescanTests (#656)
- Cleanup warnings (#655)
- [#637] Make sapling parameter download part of processing blocks (#650)
- [#631] Verify SHA1 correctness of Sapling files after downloading (#643)
- Add benchmarking info to SyncBlocksViewController (#649)
- [#639] Provide an API to estimate TextMemo length limit correctly (#640)
- [#597] Bump up SQLite Swift to 0.14.1 (#638)
- [#488] Delete cache db when sync ends
### [#585] Fix RewindRescanTests (#656)
### Cleanup warnings (#655)
### [#637] Make sapling parameter download part of processing blocks (#650)
### [#631] Verify SHA1 correctness of Sapling files after downloading (#643)
### Add benchmarking info to SyncBlocksViewController (#649)
### [#639] Provide an API to estimate TextMemo length limit correctly (#640)
### [#597] Bump up SQLite Swift to 0.14.1 (#638)
### [#488] Delete cache db when sync ends
- Added Checkpoints
### Added Checkpoints
Mainnet
````
@ -403,45 +425,44 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2130000.json
# Summary of 0.17.0-beta
- [#321] Validate UA
- [#384] Adopt Type Safe Memos in the FFI and SDK
- [#355] Update lib.rs to lastest librustzcash master
- [#373] Demo App shows ZEC balances in scientific notation
- [#380] One of the initAccountsTable() is dead code (except for tests)
- [#374] XCTest don't load Resources from the module's bundle
- [#375] User can't go twice in a row to SendFundsViewController
- [#490] Rebase long dated PRs on top of the feature branches
- [#510] Change references of Shielded address to Sapling Address
- [#511] Derivation functions should only return a single resul
- [#512] Remove derivation of t-address from pubkey
- [#520] Use UA Test Vector for Recipient Test
- [#544] Change Demo App to use USK and new rolling addresses
- [#602] Improve error logging for InitializerError and RustWeldingError
- [#579] Fix database lock
- [#595] Update Travis to use Xcode 14
- [#592] Fix various tests and deleted some that are not useful anymore
- [#523] Make a CompactBlockProcessor an Actor
- [#593] Fix testSmallDownloadAsync test
- [#577] Fix: reduce batch size when reaching increased load part of the chain
- [#575] make Memo and MemoBytes parameters nullable so they can be omitted
when sending to transparent receivers.
- commit `1979e41` Fix pre populated Db to have transactions from darksidewalletd seed
- commit `a483537` Ensure that the persisted test database has had migrations applied.
- commit `1273d30` Clarify & make regular how migrations are applied.
- commit `78856c6` Fix: successive launches of the application fail because the closed range of the migrations to apply would be invalid (lower range > that upper range)
- commit `7847a71` Fix incorrect encoding of optional strings in PendingTransaction.
- commit `789cf01` Add Fee field to Transaction, ConfirmedTransaction, ReceivedTransactions and Pen dingTransactions. Update Notes DAOs with new fields
- commit `849083f` Fix UInt32 conversions to SQL in PendingTransactionDao
- commit `fae15ce` Fix sent_notes.to_address column reference.
- commit `23f1f5d` Merge pull request #562 from zcash/fix_UnifiedTypecodesTests
- commit `30a9c06` Replace `db.run` with `db.execute` to fix migration issues
- commit `0fbf90d` Add migration to re-create pending_transactions table with nullable columns.
- commit `36932a2` Use PendingTransactionEntity.internalAccount for shielding.
- commit `f5d7aa0` Modify PendingTransactionEntity to be able to represent internal shielding tx.
- [#561] Fix unified typecodes tests
- [#530] Implement ability to extract available typecodes from UA
### [#321] Validate UA
### [#384] Adopt Type Safe Memos in the FFI and SDK
### [#355] Update lib.rs to lastest librustzcash master
### [#373] Demo App shows ZEC balances in scientific notation
### [#380] One of the initAccountsTable() is dead code (except for tests)
### [#374] XCTest don't load Resources from the module's bundle
### [#375] User can't go twice in a row to SendFundsViewController
### [#490] Rebase long dated PRs on top of the feature branches
### [#510] Change references of Shielded address to Sapling Address
### [#511] Derivation functions should only return a single resul
### [#512] Remove derivation of t-address from pubkey
### [#520] Use UA Test Vector for Recipient Test
### [#544] Change Demo App to use USK and new rolling addresses
### [#602] Improve error logging for InitializerError and RustWeldingError
### [#579] Fix database lock
### [#595] Update Travis to use Xcode 14
### [#592] Fix various tests and deleted some that are not useful anymore
### [#523] Make a CompactBlockProcessor an Actor
### [#593] Fix testSmallDownloadAsync test
### [#577] Fix: reduce batch size when reaching increased load part of the chain
### [#575] make Memo and MemoBytes parameters nullable so they can be omitted when sending to transparent receivers.
### commit `1979e41` Fix pre populated Db to have transactions from darksidewalletd seed
### commit `a483537` Ensure that the persisted test database has had migrations applied.
### commit `1273d30` Clarify & make regular how migrations are applied.
### commit `78856c6` Fix: successive launches of the application fail because the closed range of the migrations to apply would be invalid (lower range > that upper range)
### commit `7847a71` Fix incorrect encoding of optional strings in PendingTransaction.
### commit `789cf01` Add Fee field to Transaction, ConfirmedTransaction, ReceivedTransactions and Pen dingTransactions. Update Notes DAOs with new fields
### commit `849083f` Fix UInt32 conversions to SQL in PendingTransactionDao
### commit `fae15ce` Fix sent_notes.to_address column reference.
### commit `23f1f5d` Merge pull request #562 from zcash/fix_UnifiedTypecodesTests
### commit `30a9c06` Replace `db.run` with `db.execute` to fix migration issues
### commit `0fbf90d` Add migration to re-create pending_transactions table with nullable columns.
### commit `36932a2` Use PendingTransactionEntity.internalAccount for shielding.
### commit `f5d7aa0` Modify PendingTransactionEntity to be able to represent internal shielding tx.
### [#561] Fix unified typecodes tests
### [#530] Implement ability to extract available typecodes from UA
- Added Checkpoints
### Added Checkpoints
Mainnet
````
@ -457,7 +478,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2110000.json
````
# 0.17.0-beta.rc1
- Added Checkpoints
### Added Checkpoints
Mainnet
````
@ -484,25 +505,25 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2100000.json
````
# 0.17.0-alpha.5
- point to libzcashlc 0.1.0-beta.3. This fixes an issue spending change notes
### point to libzcashlc 0.1.0-beta.3. This fixes an issue spending change notes
# 0.17.0-alpha.4
- point to libzcashlc 0.1.0-beta.2
### point to libzcashlc 0.1.0-beta.2
# 0.17.0-alpha.3
- [#602] Improve error logging for InitializerError and RustWeldingError
### [#602] Improve error logging for InitializerError and RustWeldingError
# 0.17.0-alpha.2
- [#579] Fix database lock
- [#592] Fix various tests and deleted some that are not useful anymore
- [#581] getTransparentBalanceForAccount error not handled
### [#579] Fix database lock
### [#592] Fix various tests and deleted some that are not useful anymore
### [#581] getTransparentBalanceForAccount error not handled
# 0.17.0-alpha.1
See MIGRATING.md
# 0.16-13-beta
- [#597] SDK does not build with SQLite 0.14
### [#597] SDK does not build with SQLite 0.14
# 0.16.12-beta
Checkpoints added:
### Checkpoints added:
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1832500.json
@ -515,7 +536,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1847500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1850000.json
````
# 0.16.11-beta
Checkpoints added:
### Checkpoints added:
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1812500.json
@ -528,7 +549,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1827500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1830000.json
````
# 0.16.10-beta
- [#532] [0.16.x-beta] Download does not stop correctly
### [#532] [0.16.x-beta] Download does not stop correctly
Issue Reported:
@ -551,7 +572,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json
````
# 0.16.9-beta
Checkpoints added:
### Checkpoints added:
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json
@ -564,7 +585,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json
````
# 0.16.8-beta
Checkpoints added:
### Checkpoints added:
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1775000.json
@ -581,7 +602,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2010000.json
````
# 0.16.7-beta
- [#455] revert queue priority downgrade changes from [#435] (#456)
### [#455] revert queue priority downgrade changes from [#435] (#456)
This reverts queue priority changes from commit `a5d0e447748257d2af5c9101391dd05a5ce929a2` since we detected it might prevent downloads to be scheduled in a timely fashion

View File

@ -49,6 +49,10 @@
0DDFB33C236B743000AED892 /* LatestHeightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33B236B743000AED892 /* LatestHeightViewController.swift */; };
0DDFB33E236B844900AED892 /* DemoAppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33D236B844900AED892 /* DemoAppConfig.swift */; };
0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */; };
343CB69729CDC05D0063BF41 /* SyncBlocksListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 343CB69629CDC05D0063BF41 /* SyncBlocksListViewController.swift */; };
343CB69829CDC05D0063BF41 /* SyncBlocksListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 343CB69629CDC05D0063BF41 /* SyncBlocksListViewController.swift */; };
34CC8FD029CDC212001E06E9 /* SynchronizerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CC8FCF29CDC212001E06E9 /* SynchronizerCell.swift */; };
34CC8FD129CDC212001E06E9 /* SynchronizerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CC8FCF29CDC212001E06E9 /* SynchronizerCell.swift */; };
F94912632790D7C4004BB3DE /* ZcashLightClientSample-Mainnet-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F94912622790D7C4004BB3DE /* ZcashLightClientSample-Mainnet-Info.plist */; };
F9D63D1F27CD114A00F4DC5F /* ZcashLightClientKit in Frameworks */ = {isa = PBXBuildFile; productRef = F9D63D1E27CD114A00F4DC5F /* ZcashLightClientKit */; };
F9D63D2227CD125300F4DC5F /* KRProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = F9D63D2127CD125300F4DC5F /* KRProgressHUD */; };
@ -95,6 +99,8 @@
0DDFB33D236B844900AED892 /* DemoAppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoAppConfig.swift; sourceTree = "<group>"; };
0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedTransactionsViewController.swift; sourceTree = "<group>"; };
22AE1E6F756FD3EA41A397FC /* Pods_ZcashLightClientSampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
343CB69629CDC05D0063BF41 /* SyncBlocksListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncBlocksListViewController.swift; sourceTree = "<group>"; };
34CC8FCF29CDC212001E06E9 /* SynchronizerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizerCell.swift; sourceTree = "<group>"; };
372CC57DB80CC242BA556A30 /* Pods_ZcashLightClientSampleUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSampleUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
541D36743362A797BF085D14 /* Pods_ZcashLightClientSample_Mainnet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample_Mainnet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
EBA186B246C4205F9BF71EED /* Pods_ZcashLightClientSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -182,6 +188,8 @@
isa = PBXGroup;
children = (
0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */,
343CB69629CDC05D0063BF41 /* SyncBlocksListViewController.swift */,
34CC8FCF29CDC212001E06E9 /* SynchronizerCell.swift */,
);
path = "Sync Blocks";
sourceTree = "<group>";
@ -518,6 +526,8 @@
0D1BE47F2581937100F78BE3 /* GetUTXOsViewController.swift in Sources */,
0DA58B962397F2CB004596EA /* TransactionsDataSource.swift in Sources */,
0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */,
34CC8FD029CDC212001E06E9 /* SynchronizerCell.swift in Sources */,
343CB69729CDC05D0063BF41 /* SyncBlocksListViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -548,6 +558,8 @@
0DA1C4C827D11D9500E5006E /* GetUTXOsViewController.swift in Sources */,
0DA1C4CC27D11D9500E5006E /* TransactionsDataSource.swift in Sources */,
0DA1C4CD27D11D9500E5006E /* PaginatedTransactionsViewController.swift in Sources */,
34CC8FD129CDC212001E06E9 /* SynchronizerCell.swift in Sources */,
343CB69829CDC05D0063BF41 /* SyncBlocksListViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -710,7 +722,7 @@
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited)";
INFOPLIST_FILE = ZcashLightClientSample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -733,7 +745,7 @@
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited)";
INFOPLIST_FILE = ZcashLightClientSample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -796,7 +808,7 @@
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited)";
INFOPLIST_FILE = "ZcashLightClientSample-Mainnet-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -819,7 +831,7 @@
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited)";
INFOPLIST_FILE = "ZcashLightClientSample-Mainnet-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View File

@ -33,7 +33,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var sharedViewingKey: UnifiedFullViewingKey {
return try! DerivationTool(networkType: kZcashNetwork.networkType)
.deriveUnifiedSpendingKey(seed: DemoAppConfig.seed, accountIndex: 0)
.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0)
.deriveFullViewingKey()
}
@ -42,6 +42,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return wallet
} else {
let wallet = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: try! fsBlockDbRootURLHelper(),
dataDbURL: try! dataDbURLHelper(),
pendingDbURL: try! pendingDbURLHelper(),
@ -49,8 +50,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
network: kZcashNetwork,
spendParamsURL: try! spendParamsURLHelper(),
outputParamsURL: try! outputParamsURLHelper(),
saplingParamsSourceURL: SaplingParamsSourceURL.default,
loggerProxy: loggerProxy
saplingParamsSourceURL: SaplingParamsSourceURL.default
)
self.wallet = wallet

View File

@ -77,11 +77,31 @@
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="eja-yc-RHW" kind="show" id="fZ3-Vb-Oxe"/>
<segue destination="eja-yc-RHW" kind="show" id="Lk2-gP-A8l"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="aXz-Nu-7Iz" style="IBUITableViewCellStyleDefault" id="UML-hy-jTe">
<rect key="frame" x="0.0" y="181.00000381469727" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="UML-hy-jTe" id="6nt-IH-7Kp">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Sync multiple wallets" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="aXz-Nu-7Iz">
<rect key="frame" x="20" y="0.0" width="350" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="TrV-Ny-JYh" kind="show" id="ItX-87-rau"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="v8L-AQ-cAg" style="IBUITableViewCellStyleDefault" id="RKO-CX-5oF">
<rect key="frame" x="0.0" y="181.00000381469727" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="224.66667175292969" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="RKO-CX-5oF" id="WdD-zf-ng7">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -101,7 +121,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="CS3-Hm-JPK" style="IBUITableViewCellStyleDefault" id="lX6-BP-STv">
<rect key="frame" x="0.0" y="224.66667175292969" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="268.33333969116211" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="lX6-BP-STv" id="uYl-mR-smC">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -121,7 +141,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="hfc-dh-b2p" style="IBUITableViewCellStyleDefault" id="U6y-0k-TWn">
<rect key="frame" x="0.0" y="268.33333969116211" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="312.00000762939453" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="U6y-0k-TWn" id="7aj-Lt-9o9">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -141,7 +161,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="oRc-aQ-3vN" style="IBUITableViewCellStyleDefault" id="rBT-Qj-4GX">
<rect key="frame" x="0.0" y="312.00000762939453" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="355.66667556762695" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rBT-Qj-4GX" id="7t0-PZ-KW4">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -161,7 +181,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="mo0-CA-kMx" style="IBUITableViewCellStyleDefault" id="cYW-no-fyA">
<rect key="frame" x="0.0" y="355.66667556762695" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="399.33334350585938" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="cYW-no-fyA" id="uu3-KF-LDB">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -181,7 +201,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="3ht-A5-ABT" style="IBUITableViewCellStyleDefault" id="Bvc-KS-SGJ">
<rect key="frame" x="0.0" y="399.33334350585938" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="443.0000114440918" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Bvc-KS-SGJ" id="Hsp-0f-x3X">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -201,7 +221,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="Hl7-xV-yrM" style="IBUITableViewCellStyleDefault" id="6sF-sD-nYj">
<rect key="frame" x="0.0" y="443.0000114440918" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="486.66667938232422" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="6sF-sD-nYj" id="Uwh-62-xaj">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -221,7 +241,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="HyY-vt-5nR" style="IBUITableViewCellStyleDefault" id="KkJ-uD-G5q">
<rect key="frame" x="0.0" y="486.66667938232422" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="530.33334732055664" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="KkJ-uD-G5q" id="VL6-QM-S0g">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -241,7 +261,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="XCC-pZ-eel" style="IBUITableViewCellStyleDefault" id="dgB-c9-cv9">
<rect key="frame" x="0.0" y="530.33334732055664" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="574.00001525878906" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dgB-c9-cv9" id="C46-42-3yU">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -261,7 +281,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="B3i-Nh-NA5" style="IBUITableViewCellStyleDefault" id="VPb-7U-IKD">
<rect key="frame" x="0.0" y="574.00001525878906" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="617.66668319702148" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VPb-7U-IKD" id="IWX-T9-THE">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -278,7 +298,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="qHq-xq-jFS" style="IBUITableViewCellStyleDefault" id="XHY-aU-r1N">
<rect key="frame" x="0.0" y="617.66668319702148" width="390" height="43.666667938232422"/>
<rect key="frame" x="0.0" y="661.33335113525391" width="390" height="43.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XHY-aU-r1N" id="fbk-CU-wgr">
<rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
@ -938,7 +958,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="vQP-aT-BeF" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="889" y="983"/>
<point key="canvasLocation" x="1165" y="1125"/>
</scene>
<!--Sync Blocks-->
<scene sceneID="mqi-cb-0xH">
@ -959,16 +979,16 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="246" verticalCompressionResistancePriority="250" ambiguous="YES" alignment="center" spacing="13" translatesAutoresizingMaskIntoConstraints="NO" id="IIl-kO-2M8">
<rect key="frame" x="0.0" y="79" width="374" height="114"/>
<rect key="frame" x="0.0" y="79.000000000000014" width="374" height="201.33333333333337"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" text="Status:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3t3-Tr-UlI">
<rect key="frame" x="0.0" y="38" width="95.666666666666671" height="38.333333333333343"/>
<rect key="frame" x="0.0" y="81.666666666666657" width="95.666666666666671" height="38.333333333333343"/>
<fontDescription key="fontDescription" type="system" pointSize="32"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jj9-7r-s2Y">
<rect key="frame" x="108.66666666666666" y="43.333333333333343" width="265.33333333333337" height="27.666666666666671"/>
<rect key="frame" x="108.66666666666666" y="87" width="265.33333333333337" height="27.666666666666671"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="23"/>
<color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/>
@ -979,22 +999,22 @@
</constraints>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Progress" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JPx-ol-2nc">
<rect key="frame" x="0.0" y="217" width="366" height="43"/>
<rect key="frame" x="0.0" y="304.33333333333331" width="366" height="43"/>
<fontDescription key="fontDescription" type="system" pointSize="36"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" ambiguous="YES" progressViewStyle="bar" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="oKg-9s-8Ym">
<rect key="frame" x="0.0" y="284" width="366" height="2.6666666666666856"/>
<rect key="frame" x="0.0" y="371.33333333333331" width="366" height="2.6666666666666856"/>
</progressView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="0%" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kXE-5z-HN2">
<rect key="frame" x="0.0" y="309.66666666666669" width="374" height="39.666666666666686"/>
<rect key="frame" x="0.0" y="397" width="374" height="39.666666666666686"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G5M-gm-1ux">
<rect key="frame" x="0.0" y="373.33333333333331" width="374" height="45"/>
<rect key="frame" x="0.0" y="460.66666666666663" width="374" height="45"/>
<fontDescription key="fontDescription" type="system" pointSize="27"/>
<state key="normal" title="Start"/>
<connections>
@ -1002,19 +1022,19 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XK2-GA-Ufv">
<rect key="frame" x="0.0" y="442.33333333333337" width="374" height="14.333333333333314"/>
<rect key="frame" x="0.0" y="529.66666666666663" width="374" height="14.333333333333371"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ROk-70-c41">
<rect key="frame" x="0.0" y="480.66666666666663" width="374" height="14.333333333333314"/>
<rect key="frame" x="0.0" y="568" width="374" height="14.333333333333371"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" verticalHuggingPriority="750" verticalCompressionResistancePriority="737" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fB9-xh-4fl" userLabel="Trailing View">
<rect key="frame" x="0.0" y="519" width="374" height="200"/>
<rect key="frame" x="0.0" y="606.33333333333337" width="374" height="112.66666666666663"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="20" id="Bpt-XM-IZA"/>
@ -1059,7 +1079,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="K0g-OR-DhR" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1800.0000000000002" y="948.88392857142856"/>
<point key="canvasLocation" x="-911" y="1125"/>
</scene>
<!--Latest Height View Controller-->
<scene sceneID="4pw-mR-qER">
@ -1120,6 +1140,104 @@
</objects>
<point key="canvasLocation" x="-92" y="40"/>
</scene>
<!--Sync Blocks List View Controller-->
<scene sceneID="n6m-C7-arI">
<objects>
<viewController id="TrV-Ny-JYh" customClass="SyncBlocksListViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Mvd-Eh-7re">
<rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Building synchronizers..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="L9B-eh-47V">
<rect key="frame" x="102.33333333333333" y="411.66666666666669" width="185.33333333333337" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="8fp-u6-7zg">
<rect key="frame" x="185" y="442.66666666666669" width="20" height="20"/>
</activityIndicatorView>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="yV0-C4-Vmo">
<rect key="frame" x="0.0" y="91" width="390" height="719"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="SynchronizerCell" id="Fmb-3y-8Me" customClass="SynchronizerCell" customModule="ZcashLightClientSample" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="390" height="56.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Fmb-3y-8Me" id="8Cl-PW-Fub">
<rect key="frame" x="0.0" y="0.0" width="390" height="56.666667938232422"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wz9-uv-BrL">
<rect key="frame" x="20" y="5" width="365" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="35e-K9-Fhw">
<rect key="frame" x="20" y="31" width="300.33333333333331" height="20.666666666666671"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="22h-cY-kOF">
<rect key="frame" x="330.33333333333331" y="11.333333333333332" width="49.666666666666686" height="34.333333333333343"/>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="gray" image="play.circle" catalog="system"/>
<connections>
<action selector="buttonTap" destination="Fmb-3y-8Me" eventType="touchUpInside" id="oOi-6s-EPR"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="35e-K9-Fhw" secondAttribute="bottom" constant="5" id="6QT-Qu-mm2"/>
<constraint firstItem="35e-K9-Fhw" firstAttribute="leading" secondItem="8Cl-PW-Fub" secondAttribute="leadingMargin" id="Id9-Nz-Z5B"/>
<constraint firstAttribute="trailing" secondItem="22h-cY-kOF" secondAttribute="trailing" constant="10" id="Lum-bA-kNs"/>
<constraint firstItem="35e-K9-Fhw" firstAttribute="top" secondItem="wz9-uv-BrL" secondAttribute="bottom" constant="5" id="Zcv-wy-n4H"/>
<constraint firstItem="wz9-uv-BrL" firstAttribute="leading" secondItem="8Cl-PW-Fub" secondAttribute="leadingMargin" id="Zfy-iO-eYD"/>
<constraint firstItem="22h-cY-kOF" firstAttribute="centerY" secondItem="8Cl-PW-Fub" secondAttribute="centerY" id="hhI-ZR-t3b"/>
<constraint firstItem="wz9-uv-BrL" firstAttribute="top" secondItem="8Cl-PW-Fub" secondAttribute="top" constant="5" id="w9f-8m-CAD"/>
<constraint firstItem="22h-cY-kOF" firstAttribute="leading" secondItem="35e-K9-Fhw" secondAttribute="trailing" constant="10" id="yDL-hS-Qfd"/>
<constraint firstAttribute="trailing" secondItem="wz9-uv-BrL" secondAttribute="trailing" constant="5" id="yGT-mO-DCE"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="alias" destination="wz9-uv-BrL" id="oIj-s1-ybD"/>
<outlet property="button" destination="22h-cY-kOF" id="fpV-mN-dOf"/>
<outlet property="status" destination="35e-K9-Fhw" id="qJO-c2-ToS"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="TrV-Ny-JYh" id="vKf-b7-Da8"/>
<outlet property="delegate" destination="TrV-Ny-JYh" id="Mvs-lm-Gda"/>
</connections>
</tableView>
</subviews>
<viewLayoutGuide key="safeArea" id="JgX-JR-Ucx"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="8fp-u6-7zg" firstAttribute="centerX" secondItem="Mvd-Eh-7re" secondAttribute="centerX" id="Je0-TJ-wAB"/>
<constraint firstItem="yV0-C4-Vmo" firstAttribute="leading" secondItem="JgX-JR-Ucx" secondAttribute="leading" id="QVk-ab-40B"/>
<constraint firstItem="L9B-eh-47V" firstAttribute="centerX" secondItem="Mvd-Eh-7re" secondAttribute="centerX" id="SOe-AL-Kyw"/>
<constraint firstItem="yV0-C4-Vmo" firstAttribute="top" secondItem="JgX-JR-Ucx" secondAttribute="top" id="d0x-3f-cNh"/>
<constraint firstItem="L9B-eh-47V" firstAttribute="centerY" secondItem="Mvd-Eh-7re" secondAttribute="centerY" id="hjx-M6-GUF"/>
<constraint firstItem="JgX-JR-Ucx" firstAttribute="bottom" secondItem="yV0-C4-Vmo" secondAttribute="bottom" id="o8t-uP-lYc"/>
<constraint firstItem="JgX-JR-Ucx" firstAttribute="trailing" secondItem="yV0-C4-Vmo" secondAttribute="trailing" id="rLz-fK-IRg"/>
<constraint firstItem="8fp-u6-7zg" firstAttribute="top" secondItem="L9B-eh-47V" secondAttribute="bottom" constant="10" id="yCs-fV-jVm"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="YVw-fR-qFq"/>
<connections>
<outlet property="loadingIndicator" destination="8fp-u6-7zg" id="Clc-vR-dAx"/>
<outlet property="loadingLabel" destination="L9B-eh-47V" id="JjL-VT-mgg"/>
<outlet property="table" destination="yV0-C4-Vmo" id="nNW-NA-pUM"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="mjJ-pc-PRf" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-240" y="1125"/>
</scene>
<!--Get Balance View Controller-->
<scene sceneID="jf5-6v-Lfw">
<objects>
@ -1185,7 +1303,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="R73-h3-yvk" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1830" y="1623"/>
<point key="canvasLocation" x="440" y="1125"/>
</scene>
<!--Get UTXOs for tAddr-->
<scene sceneID="lqr-tV-bOy">
@ -1284,7 +1402,7 @@
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="-370" y="1329"/>
<point key="canvasLocation" x="1880" y="1126"/>
</scene>
</scenes>
<inferredMetricsTieBreakers>
@ -1292,6 +1410,7 @@
<segue reference="snP-Bc-obL"/>
</inferredMetricsTieBreakers>
<resources>
<image name="play.circle" catalog="system" width="128" height="123"/>
<systemColor name="labelColor">
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>

View File

@ -12,14 +12,39 @@ import MnemonicSwift
// swiftlint:disable force_try
enum DemoAppConfig {
struct SynchronizerInitData {
let alias: ZcashSynchronizerAlias
let birthday: BlockHeight
let seed: [UInt8]
}
static var host = ZcashSDK.isMainnet ? "lightwalletd.electriccoin.co" : "lightwalletd.testnet.electriccoin.co"
static var port: Int = 9067
static var birthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000
static var seed = try! Mnemonic.deterministicSeedBytes(from: """
static var defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000
static var defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """
live combine flight accident slow soda mind bright absent bid hen shy decade biology amazing mix enlist ensure biology rhythm snap duty soap armor
""")
static let otherSynchronizers: [SynchronizerInitData] = [
SynchronizerInitData(
alias: .custom("alt-sync-1"),
birthday: 2270000,
seed: try! Mnemonic.deterministicSeedBytes(from: """
celery very reopen verify cook page cricket shield guilt picnic survey doctor include choice they stairs breeze sort route mask carpet \
coral clinic glass
""")
),
SynchronizerInitData(
alias: .custom("alt-sync-2"),
birthday: 2270000,
seed: try! Mnemonic.deterministicSeedBytes(from: """
horse museum parrot simple scissors head baby december tool donor impose job draw outer photo much minimum door gun vessel matrix vacant \
magnet lumber
""")
)
]
static var address: String {
"\(host):\(port)"
}

View File

@ -42,7 +42,7 @@ class GetUTXOsViewController: UIViewController {
do {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
let usk = try derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.seed, accountIndex: 0)
let usk = try derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0)
KRProgressHUD.showMessage("🛡 Shielding 🛡")

View File

@ -86,7 +86,8 @@ class SaplingParametersViewController: UIViewController {
spendURL: spendParameter,
spendSourceURL: SaplingParamsSourceURL.default.spendParamFileURL,
outputURL: outputParameter,
outputSourceURL: SaplingParamsSourceURL.default.outputParamFileURL
outputSourceURL: SaplingParamsSourceURL.default.outputParamFileURL,
logger: loggerProxy
)
spendPath.text = urls.spend.path
outputPath.text = urls.output.path

View File

@ -44,9 +44,9 @@ class SendViewController: UIViewController {
setUp()
closureSynchronizer.prepare(
with: DemoAppConfig.seed,
with: DemoAppConfig.defaultSeed,
viewingKeys: [AppDelegate.shared.sharedViewingKey],
walletBirthday: DemoAppConfig.birthdayHeight
walletBirthday: DemoAppConfig.defaultBirthdayHeight
) { result in
loggerProxy.debug("Prepare result: \(result)")
}
@ -209,7 +209,7 @@ class SendViewController: UIViewController {
networkType: kZcashNetwork.networkType
)
.deriveUnifiedSpendingKey(
seed: DemoAppConfig.seed,
seed: DemoAppConfig.defaultSeed,
accountIndex: 0
)
else {

View File

@ -0,0 +1,194 @@
//
// SyncBlocksListViewController.swift
// ZcashLightClientSample
//
// Created by Michal Fousek on 24.03.2023.
// Copyright © 2023 Electric Coin Company. All rights reserved.
//
import Combine
import Foundation
import UIKit
import ZcashLightClientKit
// swiftlint:disable force_try force_cast
class SyncBlocksListViewController: UIViewController {
@IBOutlet var table: UITableView!
@IBOutlet var loadingLabel: UILabel!
@IBOutlet var loadingIndicator: UIActivityIndicatorView!
var synchronizers: [Synchronizer] = []
var synchronizerData: [DemoAppConfig.SynchronizerInitData] = []
var cancellables: [AnyCancellable] = []
override func viewDidLoad() {
super.viewDidLoad()
table.isHidden = true
loadingLabel.isHidden = false
loadingIndicator.startAnimating()
navigationItem.title = "List of synchronizers"
synchronizerData = [
DemoAppConfig.SynchronizerInitData(alias: .default, birthday: DemoAppConfig.defaultBirthdayHeight, seed: DemoAppConfig.defaultSeed)
] + DemoAppConfig.otherSynchronizers
makeSynchronizers() { [weak self] synchronizers in
self?.synchronizers = synchronizers
self?.table.reloadData()
self?.loadingLabel.isHidden = true
self?.loadingIndicator.stopAnimating()
self?.table.isHidden = false
self?.subscribeToSynchronizers()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
cancellables = []
Task(priority: .userInitiated) {
for synchronizer in synchronizers {
await synchronizer.stop()
}
}
}
private func didTapOnButton(index: Int) async {
let synchronizerData = synchronizerData[index]
let synchronizer = synchronizers[index]
let syncStatus = synchronizer.latestState.syncStatus
loggerProxy.debug("Processing synchronizer with alias \(synchronizer.alias.description) \(index)")
switch syncStatus {
case .stopped, .unprepared, .synced, .disconnected, .error:
do {
if syncStatus == .unprepared {
let viewingKey = try! DerivationTool(networkType: kZcashNetwork.networkType)
.deriveUnifiedSpendingKey(seed: synchronizerData.seed, accountIndex: 0)
.deriveFullViewingKey()
_ = try! await synchronizer.prepare(
with: synchronizerData.seed,
viewingKeys: [viewingKey],
walletBirthday: synchronizerData.birthday
)
}
try await synchronizer.start(retry: false)
} catch {
loggerProxy.error("Can't start synchronizer: \(error)")
}
case .syncing, .enhancing, .fetching:
await synchronizer.stop()
}
}
private func makeSynchronizers(completion: @escaping ([Synchronizer]) -> Void) {
DispatchQueue.global().async { [weak self] in
guard let self else { return completion([]) }
let otherSynchronizers = DemoAppConfig.otherSynchronizers.map { self.makeSynchronizer(from: $0) }
let synchronizers = [AppDelegate.shared.sharedSynchronizer] + otherSynchronizers
DispatchQueue.main.async {
completion(synchronizers)
}
}
}
private func makeSynchronizer(from data: DemoAppConfig.SynchronizerInitData) -> Synchronizer {
let initializer = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: try! fsBlockDbRootURLHelper(),
dataDbURL: try! dataDbURLHelper(),
pendingDbURL: try! pendingDbURLHelper(),
endpoint: DemoAppConfig.endpoint,
network: kZcashNetwork,
spendParamsURL: try! spendParamsURLHelper(),
outputParamsURL: try! outputParamsURLHelper(),
saplingParamsSourceURL: SaplingParamsSourceURL.default,
alias: data.alias,
logLevel: .debug
)
return SDKSynchronizer(initializer: initializer)
}
private func subscribeToSynchronizers() {
for (index, synchronizer) in synchronizers.enumerated() {
synchronizer.stateStream
.throttle(for: .seconds(0.3), scheduler: DispatchQueue.main, latest: true)
.sink(
receiveValue: { [weak self] _ in
self?.table.reloadRows(at: [IndexPath(item: index, section: 0)], with: .none)
}
)
.store(in: &cancellables)
}
}
}
extension SyncBlocksListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return synchronizers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCell(withIdentifier: "SynchronizerCell", for: indexPath) as! SynchronizerCell
let synchronizer = synchronizers[indexPath.row]
let synchronizerStatus = synchronizer.latestState.syncStatus
cell.alias.text = synchronizer.alias.description
cell.status.text = synchronizerStatus.text
let image: UIImage?
switch synchronizerStatus {
case .unprepared, .synced, .stopped, .disconnected, .error:
image = UIImage(systemName: "play.circle")
case .syncing, .enhancing, .fetching:
image = UIImage(systemName: "stop.circle")
}
cell.button.setTitle("", for: .normal)
cell.button.setTitle("", for: .highlighted)
cell.button.setImage(image, for: .normal)
cell.button.setImage(image, for: .highlighted)
cell.indexPath = indexPath
cell.didTapOnButton = { [weak self] indexPath in
Task {
await self?.didTapOnButton(index: indexPath.row)
}
}
return cell
}
}
extension SyncStatus {
var text: String {
switch self {
case let .syncing(progress):
return "Syncing 🤖 \(floor(progress.progress * 1000) / 10)%"
case let .error(error):
return "error 💔 \(error.localizedDescription)"
case .stopped:
return "Stopped 🚫"
case .synced:
return "Synced 😎"
case let .enhancing(progress):
return "Enhancing 🤖 \(floor(progress.progress * 1000) / 10)%"
case .fetching:
return "Fetching UTXOs"
case .unprepared:
return "Unprepared"
case .disconnected:
return "Disconnected"
}
}
}

View File

@ -82,7 +82,7 @@ class SyncBlocksViewController: UIViewController {
progressLabel.text = "\(floor(progress.progress * 1000) / 10)%"
if let currentMetric {
let report = SDKMetrics.shared.popBlock(operation: currentMetric)?.last
let report = synchronizer.metrics.popBlock(operation: currentMetric)?.last
metricLabel.text = currentMetricName + report.debugDescription
}
@ -114,7 +114,7 @@ class SyncBlocksViewController: UIViewController {
func accumulateMetrics() {
guard let currentMetric else { return }
if let reports = SDKMetrics.shared.popBlock(operation: currentMetric) {
if let reports = synchronizer.metrics.popBlock(operation: currentMetric) {
for report in reports {
accumulatedMetrics = .accumulate(accumulatedMetrics, current: report)
}
@ -122,7 +122,7 @@ class SyncBlocksViewController: UIViewController {
}
func overallSummary() {
let cumulativeSummary = SDKMetrics.shared.cumulativeSummary()
let cumulativeSummary = synchronizer.metrics.cumulativeSummary()
let downloadedBlocksReport = cumulativeSummary.downloadedBlocksReport ?? SDKMetrics.ReportSummary.zero
let validatedBlocksReport = cumulativeSummary.validatedBlocksReport ?? SDKMetrics.ReportSummary.zero
@ -157,13 +157,13 @@ class SyncBlocksViewController: UIViewController {
if syncStatus == .unprepared {
// swiftlint:disable:next force_try
_ = try! await synchronizer.prepare(
with: DemoAppConfig.seed,
with: DemoAppConfig.defaultSeed,
viewingKeys: [AppDelegate.shared.sharedViewingKey],
walletBirthday: DemoAppConfig.birthdayHeight
walletBirthday: DemoAppConfig.defaultBirthdayHeight
)
}
SDKMetrics.shared.enableMetrics()
synchronizer.metrics.enableMetrics()
try await synchronizer.start()
updateUI()
} catch {
@ -172,7 +172,7 @@ class SyncBlocksViewController: UIViewController {
}
default:
await synchronizer.stop()
SDKMetrics.shared.disableMetrics()
synchronizer.metrics.disableMetrics()
updateUI()
}

View File

@ -0,0 +1,24 @@
//
// SynchronizerCell.swift
// ZcashLightClientSample
//
// Created by Michal Fousek on 24.03.2023.
// Copyright © 2023 Electric Coin Company. All rights reserved.
//
import Foundation
import UIKit
class SynchronizerCell: UITableViewCell {
@IBOutlet var alias: UILabel!
@IBOutlet var status: UILabel!
@IBOutlet var button: UIButton!
var indexPath: IndexPath?
var didTapOnButton: ((IndexPath) -> Void)?
@IBAction func buttonTap() {
guard let indexPath else { return }
didTapOnButton?(indexPath)
}
}

View File

@ -154,6 +154,7 @@ actor CompactBlockProcessor {
/// - parameter spendParamsURL: absolute file path of the sapling-spend.params file
/// - parameter outputParamsURL: absolute file path of the sapling-output.params file
struct Configuration {
let alias: ZcashSynchronizerAlias
let saplingParamsSourceURL: SaplingParamsSourceURL
public var fsBlockCacheRoot: URL
public var dataDb: URL
@ -176,6 +177,7 @@ actor CompactBlockProcessor {
}
init (
alias: ZcashSynchronizerAlias,
cacheDbURL: URL? = nil,
fsBlockCacheRoot: URL,
dataDb: URL,
@ -190,6 +192,7 @@ actor CompactBlockProcessor {
saplingActivation: BlockHeight,
network: ZcashNetwork
) {
self.alias = alias
self.fsBlockCacheRoot = fsBlockCacheRoot
self.dataDb = dataDb
self.spendParamsURL = spendParamsURL
@ -207,6 +210,7 @@ actor CompactBlockProcessor {
}
init(
alias: ZcashSynchronizerAlias,
fsBlockCacheRoot: URL,
dataDb: URL,
spendParamsURL: URL,
@ -215,6 +219,7 @@ actor CompactBlockProcessor {
walletBirthdayProvider: @escaping () -> BlockHeight,
network: ZcashNetwork
) {
self.alias = alias
self.fsBlockCacheRoot = fsBlockCacheRoot
self.dataDb = dataDb
self.spendParamsURL = spendParamsURL
@ -269,6 +274,9 @@ actor CompactBlockProcessor {
private var afterSyncHooksManager = AfterSyncHooksManager()
let metrics: SDKMetrics
let logger: Logger
/// Don't update this variable directly. Use `updateState()` method.
var state: State = .stopped
@ -325,7 +333,7 @@ actor CompactBlockProcessor {
private var cancelableTask: Task<Void, Error>?
let internalSyncProgress = InternalSyncProgress(storage: UserDefaults.standard)
private let internalSyncProgress: InternalSyncProgress
/// Initializes a CompactBlockProcessor instance
/// - Parameters:
@ -337,7 +345,9 @@ actor CompactBlockProcessor {
service: LightWalletService,
storage: CompactBlockRepository,
backend: ZcashRustBackendWelding.Type,
config: Configuration
config: Configuration,
metrics: SDKMetrics,
logger: Logger
) {
self.init(
service: service,
@ -347,19 +357,27 @@ actor CompactBlockProcessor {
repository: TransactionRepositoryBuilder.build(
dataDbURL: config.dataDb
),
accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true)
accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: logger),
metrics: metrics,
logger: logger
)
}
/// Initializes a CompactBlockProcessor instance from an Initialized object
/// - Parameters:
/// - initializer: an instance that complies to CompactBlockDownloading protocol
init(initializer: Initializer, walletBirthdayProvider: @escaping () -> BlockHeight) {
init(
initializer: Initializer,
metrics: SDKMetrics,
logger: Logger,
walletBirthdayProvider: @escaping () -> BlockHeight
) {
self.init(
service: initializer.lightWalletService,
storage: initializer.storage,
backend: initializer.rustBackend,
config: Configuration(
alias: initializer.alias,
fsBlockCacheRoot: initializer.fsBlockDbRoot,
dataDb: initializer.dataDbURL,
spendParamsURL: initializer.spendParamsURL,
@ -369,7 +387,9 @@ actor CompactBlockProcessor {
network: initializer.network
),
repository: initializer.transactionRepository,
accountRepository: initializer.accountRepository
accountRepository: initializer.accountRepository,
metrics: metrics,
logger: logger
)
}
@ -379,14 +399,22 @@ actor CompactBlockProcessor {
backend: ZcashRustBackendWelding.Type,
config: Configuration,
repository: TransactionRepository,
accountRepository: AccountRepository
accountRepository: AccountRepository,
metrics: SDKMetrics,
logger: Logger
) {
self.metrics = metrics
self.logger = logger
let internalSyncProgress = InternalSyncProgress(alias: config.alias, storage: UserDefaults.standard, logger: logger)
self.internalSyncProgress = internalSyncProgress
let blockDownloaderService = BlockDownloaderServiceImpl(service: service, storage: storage)
let blockDownloader = BlockDownloaderImpl(
service: service,
downloaderService: blockDownloaderService,
storage: storage,
internalSyncProgress: internalSyncProgress
internalSyncProgress: internalSyncProgress,
metrics: metrics,
logger: logger
)
self.blockDownloaderService = blockDownloaderService
@ -397,7 +425,12 @@ actor CompactBlockProcessor {
dataDB: config.dataDb,
networkType: config.network.networkType
)
self.blockValidator = BlockValidatorImpl(config: blockValidatorConfig, rustBackend: backend)
self.blockValidator = BlockValidatorImpl(
config: blockValidatorConfig,
rustBackend: backend,
metrics: metrics,
logger: logger
)
let blockScannerConfig = BlockScannerConfig(
fsBlockCacheRoot: config.fsBlockCacheRoot,
@ -405,7 +438,13 @@ actor CompactBlockProcessor {
networkType: config.network.networkType,
scanningBatchSize: config.scanningBatchSize
)
self.blockScanner = BlockScannerImpl(config: blockScannerConfig, rustBackend: backend, transactionRepository: repository)
self.blockScanner = BlockScannerImpl(
config: blockScannerConfig,
rustBackend: backend,
transactionRepository: repository,
metrics: metrics,
logger: logger
)
let blockEnhancerConfig = BlockEnhancerConfig(dataDb: config.dataDb, networkType: config.network.networkType)
self.blockEnhancer = BlockEnhancerImpl(
@ -413,7 +452,9 @@ actor CompactBlockProcessor {
config: blockEnhancerConfig,
internalSyncProgress: internalSyncProgress,
rustBackend: backend,
transactionRepository: repository
transactionRepository: repository,
metrics: metrics,
logger: logger
)
let utxoFetcherConfig = UTXOFetcherConfig(
@ -426,7 +467,9 @@ actor CompactBlockProcessor {
blockDownloaderService: blockDownloaderService,
config: utxoFetcherConfig,
internalSyncProgress: internalSyncProgress,
rustBackend: backend
rustBackend: backend,
metrics: metrics,
logger: logger
)
let saplingParametersHandlerConfig = SaplingParametersHandlerConfig(
@ -436,7 +479,11 @@ actor CompactBlockProcessor {
spendParamsURL: config.spendParamsURL,
saplingParamsSourceURL: config.saplingParamsSourceURL
)
self.saplingParametersHandler = SaplingParametersHandlerImpl(config: saplingParametersHandlerConfig, rustBackend: backend)
self.saplingParametersHandler = SaplingParametersHandlerImpl(
config: saplingParametersHandlerConfig,
rustBackend: backend,
logger: logger
)
self.service = service
self.rustBackend = backend
@ -517,19 +564,19 @@ actor CompactBlockProcessor {
switch self.state {
case .error(let error):
// max attempts have been reached
LoggerProxy.info("max retry attempts reached with error: \(error)")
logger.info("max retry attempts reached with error: \(error)")
await notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
await updateState(.stopped)
case .stopped:
// max attempts have been reached
LoggerProxy.info("max retry attempts reached")
logger.info("max retry attempts reached")
await notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
case .synced:
// max attempts have been reached
LoggerProxy.warn("max retry attempts reached on synced state, this indicates malfunction")
logger.warn("max retry attempts reached on synced state, this indicates malfunction")
await notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
LoggerProxy.debug("Warning: compact block processor was started while busy!!!!")
logger.debug("Warning: compact block processor was started while busy!!!!")
afterSyncHooksManager.insert(hook: .anotherSync)
}
return
@ -567,21 +614,21 @@ actor CompactBlockProcessor {
///
/// - Note: If this is called while sync is in progress then the sync process is stopped first and then rewind is executed.
func rewind(context: AfterSyncHooksManager.RewindContext) async {
LoggerProxy.debug("Starting rewid")
logger.debug("Starting rewind")
switch self.state {
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
LoggerProxy.debug("Stopping sync because of rewind")
logger.debug("Stopping sync because of rewind")
afterSyncHooksManager.insert(hook: .rewind(context))
stop()
case .stopped, .error, .synced:
LoggerProxy.debug("Sync doesn't run. Executing rewind.")
logger.debug("Sync doesn't run. Executing rewind.")
await doRewind(context: context)
}
}
private func doRewind(context: AfterSyncHooksManager.RewindContext) async {
LoggerProxy.debug("Executing rewind.")
logger.debug("Executing rewind.")
let lastDownloaded = await internalSyncProgress.latestDownloadedBlockHeight
let height = Int32(context.height ?? lastDownloaded)
let nearestHeight = rustBackend.getNearestRewindHeight(
@ -623,21 +670,21 @@ actor CompactBlockProcessor {
// MARK: Wipe
func wipe(context: AfterSyncHooksManager.WipeContext) async {
LoggerProxy.debug("Starting wipe")
logger.debug("Starting wipe")
switch self.state {
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
LoggerProxy.debug("Stopping sync because of wipe")
logger.debug("Stopping sync because of wipe")
afterSyncHooksManager.insert(hook: .wipe(context))
stop()
case .stopped, .error, .synced:
LoggerProxy.debug("Sync doesn't run. Executing wipe.")
logger.debug("Sync doesn't run. Executing wipe.")
await doWipe(context: context)
}
}
private func doWipe(context: AfterSyncHooksManager.WipeContext) async {
LoggerProxy.debug("Executing wipe.")
logger.debug("Executing wipe.")
context.prewipe()
await updateState(.stopped)
@ -691,7 +738,7 @@ actor CompactBlockProcessor {
do {
let totalProgressRange = computeTotalProgressRange(from: ranges)
LoggerProxy.debug("""
logger.debug("""
Syncing with ranges:
downloaded but not scanned: \
\(ranges.downloadedButUnscannedRange?.lowerBound ?? -1)...\(ranges.downloadedButUnscannedRange?.upperBound ?? -1)
@ -715,7 +762,7 @@ actor CompactBlockProcessor {
}
if let range = ranges.downloadedButUnscannedRange {
LoggerProxy.debug("Starting scan with downloaded but not scanned blocks with range: \(range.lowerBound)...\(range.upperBound)")
logger.debug("Starting scan with downloaded but not scanned blocks with range: \(range.lowerBound)...\(range.upperBound)")
try await blockScanner.scanBlocks(at: range, totalProgressRange: totalProgressRange) { [weak self] lastScannedHeight in
let progress = BlockProgress(
startHeight: totalProgressRange.lowerBound,
@ -727,13 +774,13 @@ actor CompactBlockProcessor {
}
if let range = ranges.downloadAndScanRange {
LoggerProxy.debug("Starting sync with range: \(range.lowerBound)...\(range.upperBound)")
logger.debug("Starting sync with range: \(range.lowerBound)...\(range.upperBound)")
try await downloadAndScanBlocks(at: range, totalProgressRange: totalProgressRange)
}
if let range = ranges.enhanceRange {
anyActionExecuted = true
LoggerProxy.debug("Enhancing with range: \(range.lowerBound)...\(range.upperBound)")
logger.debug("Enhancing with range: \(range.lowerBound)...\(range.upperBound)")
await updateState(.enhancing)
let transactions = try await blockEnhancer.enhance(at: range) { [weak self] progress in
await self?.notifyProgress(.enhance(progress))
@ -743,34 +790,34 @@ actor CompactBlockProcessor {
if let range = ranges.fetchUTXORange {
anyActionExecuted = true
LoggerProxy.debug("Fetching UTXO with range: \(range.lowerBound)...\(range.upperBound)")
logger.debug("Fetching UTXO with range: \(range.lowerBound)...\(range.upperBound)")
await updateState(.fetching)
let result = try await utxoFetcher.fetch(at: range)
await send(event: .storedUTXOs(result))
}
LoggerProxy.debug("Fetching sapling parameters")
logger.debug("Fetching sapling parameters")
await updateState(.handlingSaplingFiles)
try await saplingParametersHandler.handleIfNeeded()
LoggerProxy.debug("Clearing cache")
logger.debug("Clearing cache")
try await clearCompactBlockCache()
if !Task.isCancelled {
await processBatchFinished(height: anyActionExecuted ? ranges.latestBlockHeight : nil)
}
} catch {
LoggerProxy.error("Sync failed with error: \(error)")
logger.error("Sync failed with error: \(error)")
if Task.isCancelled {
LoggerProxy.info("Processing cancelled.")
logger.info("Processing cancelled.")
await updateState(.stopped)
await handleAfterSyncHooks()
} else {
if case BlockValidatorError.validationFailed(let height) = error {
await validationFailed(at: height)
} else {
LoggerProxy.error("processing failed with error: \(error)")
logger.error("processing failed with error: \(error)")
await fail(error)
}
}
@ -787,7 +834,7 @@ actor CompactBlockProcessor {
} else if let rewindContext = afterSyncHooksManager.shouldExecuteRewindHook() {
await doRewind(context: rewindContext)
} else if afterSyncHooksManager.shouldExecuteAnotherSyncHook() {
LoggerProxy.debug("Starting new sync.")
logger.debug("Starting new sync.")
await nextBatch()
}
}
@ -809,7 +856,7 @@ actor CompactBlockProcessor {
for i in 0..<loopsCount {
let processingRange = computeSingleLoopDownloadRange(fullRange: range, loopCounter: i, batchSize: batchSize)
LoggerProxy.debug("Sync loop #\(i + 1) range: \(processingRange.lowerBound)...\(processingRange.upperBound)")
logger.debug("Sync loop #\(i + 1) range: \(processingRange.lowerBound)...\(processingRange.upperBound)")
try await blockDownloader.downloadAndStoreBlocks(
using: downloadStream,
@ -822,7 +869,7 @@ actor CompactBlockProcessor {
try await blockValidator.validate()
} catch {
guard let validationError = error as? BlockValidatorError else {
LoggerProxy.error("Block validation failed with generic error: \(error)")
logger.error("Block validation failed with generic error: \(error)")
throw error
}
@ -834,7 +881,7 @@ actor CompactBlockProcessor {
throw genericError
case .failedWithUnknownError:
LoggerProxy.error("validation failed without a specific error")
logger.error("validation failed without a specific error")
throw CompactBlockProcessorError.generalError(message: "validation failed without a specific error")
}
}
@ -849,7 +896,7 @@ actor CompactBlockProcessor {
await self?.notifyProgress(.syncing(progress))
}
} catch {
LoggerProxy.error("Scanning failed with error: \(error)")
logger.error("Scanning failed with error: \(error)")
throw error
}
@ -898,7 +945,7 @@ actor CompactBlockProcessor {
}
func notifyProgress(_ progress: CompactBlockProgress) async {
LoggerProxy.debug("progress: \(progress)")
logger.debug("progress: \(progress)")
await send(event: .progressUpdated(progress))
}
@ -917,7 +964,7 @@ actor CompactBlockProcessor {
func severeFailure(_ error: Error) async {
cancelableTask?.cancel()
LoggerProxy.error("show stopper failure: \(error)")
logger.error("show stopper failure: \(error)")
self.backoffTimer?.invalidate()
self.retryAttempts = config.retries
self.processingError = error
@ -927,7 +974,7 @@ actor CompactBlockProcessor {
func fail(_ error: Error) async {
// TODO: [#713] specify: failure. https://github.com/zcash/ZcashLightClientKit/issues/713
LoggerProxy.error("\(error)")
logger.error("\(error)")
cancelableTask?.cancel()
self.retryAttempts += 1
self.processingError = error
@ -981,7 +1028,7 @@ actor CompactBlockProcessor {
await self.processNewBlocks(ranges: ranges)
case let .wait(latestHeight, latestDownloadHeight):
// Lightwalletd might be syncing
LoggerProxy.info(
logger.info(
"Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight) " +
"while latest blockheight is reported at: \(latestHeight)"
)
@ -1045,7 +1092,7 @@ actor CompactBlockProcessor {
private func clearCompactBlockCache() async throws {
try await storage.clear()
LoggerProxy.info("Cache removed")
logger.info("Cache removed")
}
private func setTimer() async {
@ -1058,7 +1105,7 @@ actor CompactBlockProcessor {
Task { [self] in
guard let self else { return }
if await self.shouldStart {
LoggerProxy.debug(
self.logger.debug(
"""
Timer triggered: Starting compact Block processor!.
Processor State: \(await self.state)
@ -1230,7 +1277,7 @@ extension CompactBlockProcessor {
skipped.append(utxo)
}
} catch {
LoggerProxy.info("failed to put utxo - error: \(error)")
logger.info("failed to put utxo - error: \(error)")
skipped.append(utxo)
}
}
@ -1394,6 +1441,13 @@ extension CompactBlockProcessor {
throw CacheDbMigrationError.fsCacheMigrationFailedSameURL
}
// Instance with alias `default` is same as instance before the Alias was introduced. So it makes sense that only this instance handles
// legacy cache DB. Any instance with different than `default` alias was created after the Alias was introduced and at this point legacy
// cache DB is't anymore. So there is nothing to migrate for instances with not default Alias.
guard config.alias == .default else {
return
}
// if the URL provided is not readable, it means that the client has a reference
// to the cacheDb file but it has been deleted in a prior sync cycle. there's
// nothing to do here.

View File

@ -20,13 +20,16 @@ class MigrationManager {
var pendingDb: ConnectionProvider
var network: NetworkType
let logger: Logger
init(
pendingDbConnection: ConnectionProvider,
networkType: NetworkType
networkType: NetworkType,
logger: Logger
) {
self.pendingDb = pendingDbConnection
self.network = networkType
self.logger = logger
}
func performMigration() throws {
@ -39,7 +42,7 @@ private extension MigrationManager {
// getUserVersion returns a default value of zero for an unmigrated database.
let currentPendingDbVersion = try pendingDb.connection().getUserVersion()
LoggerProxy.debug(
logger.debug(
"Attempting to perform migration for pending Db - currentVersion: \(currentPendingDbVersion)." +
"Latest version is: \(Self.nextPendingDbMigration.rawValue - 1)"
)

View File

@ -10,8 +10,6 @@ import Foundation
import SQLite
class DatabaseStorageManager {
static var shared = DatabaseStorageManager()
private var readOnly: [URL: Connection] = [:]
private var readWrite: [URL: Connection] = [:]

View File

@ -46,6 +46,8 @@ struct BlockDownloaderImpl {
let downloaderService: BlockDownloaderService
let storage: CompactBlockRepository
let internalSyncProgress: InternalSyncProgress
let metrics: SDKMetrics
let logger: Logger
}
extension BlockDownloaderImpl: BlockDownloader {
@ -62,14 +64,14 @@ extension BlockDownloaderImpl: BlockDownloader {
totalProgressRange: CompactBlockRange
) async throws {
var buffer: [ZcashCompactBlock] = []
LoggerProxy.debug("Downloading blocks in range: \(range.lowerBound)...\(range.upperBound)")
logger.debug("Downloading blocks in range: \(range.lowerBound)...\(range.upperBound)")
var startTime = Date()
var counter = 0
var lastDownloadedBlockHeight = -1
let pushMetrics: (BlockHeight, Date, Date) -> Void = { lastDownloadedBlockHeight, startTime, finishTime in
SDKMetrics.shared.pushProgressReport(
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: totalProgressRange.lowerBound,
targetHeight: totalProgressRange.upperBound,

View File

@ -29,15 +29,17 @@ struct BlockEnhancerImpl {
let internalSyncProgress: InternalSyncProgress
let rustBackend: ZcashRustBackendWelding.Type
let transactionRepository: TransactionRepository
let metrics: SDKMetrics
let logger: Logger
private func enhance(transaction: ZcashTransaction.Overview) async throws -> ZcashTransaction.Overview {
LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.rawID.toHexStringTxId())")
logger.debug("Zoom.... Enhance... Tx: \(transaction.rawID.toHexStringTxId())")
let fetchedTransaction = try await blockDownloaderService.fetchTransaction(txId: transaction.rawID)
let transactionID = fetchedTransaction.rawID.toHexStringTxId()
let block = String(describing: transaction.minedHeight)
LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
logger.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
let decryptionResult = rustBackend.decryptAndStoreTransaction(
dbData: config.dataDb,
@ -78,7 +80,7 @@ extension BlockEnhancerImpl: BlockEnhancer {
func enhance(at range: CompactBlockRange, didEnhance: (EnhancementProgress) async -> Void) async throws -> [ZcashTransaction.Overview] {
try Task.checkCancellation()
LoggerProxy.debug("Started Enhancing range: \(range)")
logger.debug("Started Enhancing range: \(range)")
var retries = 0
let maxRetries = 5
@ -90,7 +92,7 @@ extension BlockEnhancerImpl: BlockEnhancer {
guard !transactions.isEmpty else {
await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
LoggerProxy.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
logger.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
return []
}
@ -116,7 +118,7 @@ extension BlockEnhancerImpl: BlockEnhancer {
}
} catch {
retries += 1
LoggerProxy.error("could not enhance txId \(transaction.rawID.toHexStringTxId()) - Error: \(error)")
logger.error("could not enhance txId \(transaction.rawID.toHexStringTxId()) - Error: \(error)")
if retries > maxRetries {
throw error
}
@ -124,7 +126,7 @@ extension BlockEnhancerImpl: BlockEnhancer {
}
}
SDKMetrics.shared.pushProgressReport(
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: range.lowerBound,
targetHeight: range.upperBound,
@ -136,14 +138,14 @@ extension BlockEnhancerImpl: BlockEnhancer {
operation: .enhancement
)
} catch {
LoggerProxy.error("error enhancing transactions! \(error)")
logger.error("error enhancing transactions! \(error)")
throw error
}
await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
if Task.isCancelled {
LoggerProxy.debug("Warning: compactBlockEnhancement on range \(range) cancelled")
logger.debug("Warning: compactBlockEnhancement on range \(range) cancelled")
}
return (try? transactionRepository.find(in: range, limit: Int.max, kind: .all)) ?? []

View File

@ -28,6 +28,8 @@ struct UTXOFetcherImpl {
let config: UTXOFetcherConfig
let internalSyncProgress: InternalSyncProgress
let rustBackend: ZcashRustBackendWelding.Type
let metrics: SDKMetrics
let logger: Logger
}
extension UTXOFetcherImpl: UTXOFetcher {
@ -77,12 +79,12 @@ extension UTXOFetcherImpl: UTXOFetcher {
await internalSyncProgress.set(utxo.height, .latestUTXOFetchedHeight)
} catch {
LoggerProxy.error("failed to put utxo - error: \(error)")
logger.error("failed to put utxo - error: \(error)")
skipped.append(utxo)
}
}
SDKMetrics.shared.pushProgressReport(
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: range.lowerBound,
targetHeight: range.upperBound,
@ -99,7 +101,7 @@ extension UTXOFetcherImpl: UTXOFetcher {
await internalSyncProgress.set(range.upperBound, .latestUTXOFetchedHeight)
if Task.isCancelled {
LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
logger.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
}
return result

View File

@ -13,6 +13,7 @@ class FSCompactBlockRepository {
let contentProvider: SortedDirectoryListing
let fileWriter: FSBlockFileWriter
let metadataStore: FSMetadataStore
let logger: Logger
private let fileManager = FileManager()
private let storageBatchSize = 10
@ -33,13 +34,15 @@ class FSCompactBlockRepository {
metadataStore: FSMetadataStore,
blockDescriptor: ZcashCompactBlockDescriptor,
contentProvider: SortedDirectoryListing,
fileWriter: FSBlockFileWriter = .atomic
fileWriter: FSBlockFileWriter = .atomic,
logger: Logger
) {
self.fsBlockDbRoot = fsBlockDbRoot
self.metadataStore = metadataStore
self.blockDescriptor = blockDescriptor
self.contentProvider = contentProvider
self.fileWriter = fileWriter
self.logger = logger
}
}
@ -75,7 +78,7 @@ extension FSCompactBlockRepository: CompactBlockRepository {
do {
try self.fileWriter.writeToURL(block.data, blockURL)
} catch {
LoggerProxy.error("Failed to write block: \(block.height) to path: \(blockURL.path) with error: \(error)")
logger.error("Failed to write block: \(block.height) to path: \(blockURL.path) with error: \(error)")
throw CompactBlockRepositoryError.failedToWriteBlock(block)
}
@ -90,7 +93,7 @@ extension FSCompactBlockRepository: CompactBlockRepository {
// if there are any remaining blocks on the cache store them
try await self.metadataStore.saveBlocksMeta(savedBlocks)
} catch {
LoggerProxy.error("failed to Block save to cache error: \(error.localizedDescription)")
logger.error("failed to Block save to cache error: \(error.localizedDescription)")
throw error
}
}
@ -115,7 +118,9 @@ extension FSCompactBlockRepository: CompactBlockRepository {
}
func clear() async throws {
try self.fileManager.removeItem(at: self.fsBlockDbRoot)
if self.fileManager.fileExists(atPath: self.fsBlockDbRoot.path) {
try self.fileManager.removeItem(at: self.fsBlockDbRoot)
}
try create()
}
}
@ -210,12 +215,13 @@ struct FSMetadataStore {
}
extension FSMetadataStore {
static func live(fsBlockDbRoot: URL, rustBackend: ZcashRustBackendWelding.Type) -> FSMetadataStore {
static func live(fsBlockDbRoot: URL, rustBackend: ZcashRustBackendWelding.Type, logger: Logger) -> FSMetadataStore {
FSMetadataStore { blocks in
try await FSMetadataStore.saveBlocksMeta(
blocks,
fsBlockDbRoot: fsBlockDbRoot,
rustBackend: rustBackend
rustBackend: rustBackend,
logger: logger
)
} rewindToHeight: { height in
guard rustBackend.rewindCacheToHeight(fsBlockDbRoot: fsBlockDbRoot, height: Int32(height)) else {
@ -235,7 +241,12 @@ extension FSMetadataStore {
/// - Throws `CompactBlockRepositoryError.failedToWriteMetadata` if the
/// operation fails. the underlying error is logged through `LoggerProxy`
/// - Note: This shouldn't be called in parallel by many threads or workers. Won't do anything if `blocks` is empty
static func saveBlocksMeta(_ blocks: [ZcashCompactBlock], fsBlockDbRoot: URL, rustBackend: ZcashRustBackendWelding.Type) async throws {
static func saveBlocksMeta(
_ blocks: [ZcashCompactBlock],
fsBlockDbRoot: URL,
rustBackend: ZcashRustBackendWelding.Type,
logger: Logger
) async throws {
guard !blocks.isEmpty else { return }
do {
@ -243,7 +254,7 @@ extension FSMetadataStore {
throw CompactBlockRepositoryError.failedToWriteMetadata
}
} catch {
LoggerProxy.error("Failed to write metadata with error: \(error)")
logger.error("Failed to write metadata with error: \(error)")
throw CompactBlockRepositoryError.failedToWriteMetadata
}
}

View File

@ -22,6 +22,7 @@ protocol SaplingParametersHandler {
struct SaplingParametersHandlerImpl {
let config: SaplingParametersHandlerConfig
let rustBackend: ZcashRustBackendWelding.Type
let logger: Logger
}
extension SaplingParametersHandlerImpl: SaplingParametersHandler {
@ -46,7 +47,7 @@ extension SaplingParametersHandlerImpl: SaplingParametersHandler {
// if sapling balance can't be detected of we fail to obtain the balance
// for some reason we shall not proceed to download the parameters and
// retry in the following attempt to sync.
LoggerProxy.error("Couldn't Fetch shielded balance. Won't attempt to download sapling parameters")
logger.error("Couldn't Fetch shielded balance. Won't attempt to download sapling parameters")
return
}
@ -54,7 +55,8 @@ extension SaplingParametersHandlerImpl: SaplingParametersHandler {
spendURL: config.spendParamsURL,
spendSourceURL: config.saplingParamsSourceURL.spendParamFileURL,
outputURL: config.outputParamsURL,
outputSourceURL: config.saplingParamsSourceURL.outputParamFileURL
outputSourceURL: config.saplingParamsSourceURL.outputParamFileURL,
logger: logger
)
}
}

View File

@ -22,6 +22,8 @@ struct BlockScannerImpl {
let config: BlockScannerConfig
let rustBackend: ZcashRustBackendWelding.Type
let transactionRepository: TransactionRepository
let metrics: SDKMetrics
let logger: Logger
}
extension BlockScannerImpl: BlockScanner {
@ -50,7 +52,7 @@ extension BlockScannerImpl: BlockScanner {
networkType: config.networkType
) else {
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
logger.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
let scanFinishTime = Date()
@ -67,7 +69,7 @@ extension BlockScannerImpl: BlockScanner {
progressHeight: lastScannedHeight
)
SDKMetrics.shared.pushProgressReport(
metrics.pushProgressReport(
progress: progress,
start: scanStartTime,
end: scanFinishTime,
@ -77,7 +79,7 @@ extension BlockScannerImpl: BlockScanner {
let heightCount = lastScannedHeight - previousScannedHeight
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds")
logger.debug("Scanned \(heightCount) blocks in \(seconds) seconds")
}
await Task.yield()

View File

@ -41,24 +41,37 @@ actor InternalSyncProgress {
case latestDownloadedBlockHeight
case latestEnhancedHeight
case latestUTXOFetchedHeight
func with(_ alias: ZcashSynchronizerAlias) -> String {
switch alias {
case .`default`:
return self.rawValue
case let .custom(rawAlias):
return "\(self.rawValue)_\(rawAlias)"
}
}
}
private let alias: ZcashSynchronizerAlias
private let storage: InternalSyncProgressStorage
let logger: Logger
var latestDownloadedBlockHeight: BlockHeight { load(.latestDownloadedBlockHeight) }
var latestEnhancedHeight: BlockHeight { load(.latestEnhancedHeight) }
var latestUTXOFetchedHeight: BlockHeight { load(.latestUTXOFetchedHeight) }
init(storage: InternalSyncProgressStorage) {
init(alias: ZcashSynchronizerAlias, storage: InternalSyncProgressStorage, logger: Logger) {
self.alias = alias
self.storage = storage
self.logger = logger
}
func load(_ key: Key) -> BlockHeight {
storage.integer(forKey: key.rawValue)
storage.integer(forKey: key.with(alias))
}
func set(_ value: BlockHeight, _ key: Key) {
storage.set(value, forKey: key.rawValue)
storage.set(value, forKey: key.with(alias))
storage.synchronize()
}
@ -103,7 +116,7 @@ actor InternalSyncProgress {
latestScannedHeight: BlockHeight,
walletBirthday: BlockHeight
) throws -> CompactBlockProcessor.NextState {
LoggerProxy.debug("""
logger.debug("""
Init numbers:
latestBlockHeight: \(latestBlockHeight)
latestDownloadedHeight: \(latestDownloadedBlockHeight)
@ -147,7 +160,7 @@ actor InternalSyncProgress {
}
if latestScannedHeight > latestDownloadedBlockHeight {
LoggerProxy.warn("""
logger.warn("""
InternalSyncProgress found inconsistent state.
latestBlockHeight: \(latestBlockHeight)
--> latestDownloadedHeight: \(latestDownloadedBlockHeight)

View File

@ -28,6 +28,8 @@ protocol BlockValidator {
struct BlockValidatorImpl {
let config: BlockValidatorConfig
let rustBackend: ZcashRustBackendWelding.Type
let metrics: SDKMetrics
let logger: Logger
}
extension BlockValidatorImpl: BlockValidator {
@ -43,7 +45,7 @@ extension BlockValidatorImpl: BlockValidator {
)
let finishTime = Date()
SDKMetrics.shared.pushProgressReport(
metrics.pushProgressReport(
progress: BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0),
start: startTime,
end: finishTime,
@ -54,7 +56,7 @@ extension BlockValidatorImpl: BlockValidator {
switch result {
case 0:
let rustError = rustBackend.lastError()
LoggerProxy.debug("Block validation failed with error: \(String(describing: rustError))")
logger.debug("Block validation failed with error: \(String(describing: rustError))")
if let rustError {
throw BlockValidatorError.failedWithError(rustError)
} else {
@ -62,11 +64,11 @@ extension BlockValidatorImpl: BlockValidator {
}
case ZcashRustBackendWeldingConstants.validChain:
LoggerProxy.debug("validateChainFinished")
logger.debug("validateChainFinished")
return
default:
LoggerProxy.debug("Block validation failed at height: \(result)")
logger.debug("Block validation failed at height: \(result)")
throw BlockValidatorError.validationFailed(height: BlockHeight(result))
}
}

View File

@ -14,6 +14,8 @@ import Foundation
///
/// If you are looking for documentation for a specific method or property look for it in the `Synchronizer` protocol.
public protocol ClosureSynchronizer {
var alias: ZcashSynchronizerAlias { get }
var latestState: SynchronizerState { get }
var connectionState: ConnectionState { get }

View File

@ -21,6 +21,8 @@ public typealias Single = AnyPublisher
///
/// If you are looking for documentation for a specific method or property look for it in the `Synchronizer` protocol.
public protocol CombineSynchronizer {
var alias: ZcashSynchronizerAlias { get }
var latestState: SynchronizerState { get }
var connectionState: ConnectionState { get }

View File

@ -7,6 +7,7 @@
import Foundation
import SQLite
struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable {
enum CodingKeys: String, CodingKey {
case toAddress = "to_address"
@ -230,9 +231,11 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
static let table = Table("pending_transactions")
var dbProvider: ConnectionProvider
let logger: Logger
init(dbProvider: ConnectionProvider) {
init(dbProvider: ConnectionProvider, logger: Logger) {
self.dbProvider = dbProvider
self.logger = logger
}
func closeDBConnection() {
@ -253,7 +256,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
let updatedRows = try dbProvider.connection().run(Self.table.filter(TableColumns.id == id).update(pendingTx))
if updatedRows == 0 {
LoggerProxy.error("attempted to update pending transactions but no rows were updated")
logger.error("attempted to update pending transactions but no rows were updated")
}
}
@ -308,7 +311,7 @@ class PendingTransactionSQLDAO: PendingTransactionRepository {
.run(transaction.update([TableColumns.minedHeight <- height]))
if updatedRows == 0 {
LoggerProxy.error("attempted to update a row but none was updated")
logger.error("attempted to update a row but none was updated")
}
}
}

View File

@ -55,9 +55,11 @@ class AccountSQDAO: AccountRepository {
let table = Table("accounts")
var dbProvider: ConnectionProvider
let logger: Logger
init (dbProvider: ConnectionProvider) {
init (dbProvider: ConnectionProvider, logger: Logger) {
self.dbProvider = dbProvider
self.logger = logger
}
func getAll() throws -> [AccountEntity] {
@ -79,7 +81,7 @@ class AccountSQDAO: AccountRepository {
}
let updatedRows = try dbProvider.connection().run(table.filter(TableColums.account == acc.account).update(acc))
if updatedRows == 0 {
LoggerProxy.error("attempted to update pending transactions but no rows were updated")
logger.error("attempted to update pending transactions but no rows were updated")
throw DatabaseStorageError.updateFailed
}
}
@ -135,11 +137,19 @@ class CachingAccountDao: AccountRepository {
}
enum AccountRepositoryBuilder {
static func build(dataDbURL: URL, readOnly: Bool = false, caching: Bool = false) -> AccountRepository {
static func build(dataDbURL: URL, readOnly: Bool = false, caching: Bool = false, logger: Logger) -> AccountRepository {
if caching {
return CachingAccountDao(dao: AccountSQDAO(dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly)))
return CachingAccountDao(
dao: AccountSQDAO(
dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly),
logger: logger
)
)
} else {
return AccountSQDAO(dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly))
return AccountSQDAO(
dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly),
logger: logger
)
}
}
}

View File

@ -40,8 +40,6 @@ extension SynchronizerError: LocalizedError {
return "We couldn't find this account number."
case .lightwalletdValidationFailed(underlyingError: let underlyingError):
return "We connected to the network but we couldn't verify the server. `lightwalletdValidationFailed` error: \(underlyingError)."
case .wipeAttemptWhileProcessing:
return "Can't execute wipe while sync process is in progress."
}
}
}

View File

@ -0,0 +1,36 @@
//
// File.swift
//
//
// Created by Michal Fousek on 23.03.2023.
//
import Foundation
extension URL {
/// Try to update URLs with `alias`.
///
/// If the `default` alias is used then the URL isn't changed at all.
/// If the `custom("anotherInstance")` is used then last path component or the URL is updated like this:
/// - /some/path/to.file -> /some/path/c_anotherInstance_to.file
/// - /some/path/to/directory -> /some/path/to/c_anotherInstance_directory
///
/// If the URLs can't be parsed then `nil` is returned.
func updateLastPathComponent(with alias: ZcashSynchronizerAlias) -> URL? {
let lastPathComponent = self.lastPathComponent
guard !lastPathComponent.isEmpty else {
return nil
}
switch alias {
case .`default`:
// When using default alias everything should work as before aliases to be backwards compatible.
return self
case .custom:
let newLastPathComponent = "\(alias.description)_\(lastPathComponent)"
return self.deletingLastPathComponent()
.appendingPathComponent(newLastPathComponent)
}
}
}

View File

@ -17,6 +17,8 @@ public enum InitializerError: Error {
case dataDbInitFailed(Error)
case accountInitFailed(Error)
case invalidViewingKey(key: String)
case aliasAlreadyInUse(ZcashSynchronizerAlias)
case cantUpdateURLWithAlias(URL)
}
/**
@ -68,20 +70,58 @@ public struct SaplingParamsSourceURL {
}
}
/// This identifies different instances of the synchronizer. It is usefull when the client app wants to support multiple wallets (with different
/// seeds) in one app. If the client app support only one wallet then it doesn't have to care about alias atall.
///
/// When custom alias is used to create instance of the synchronizer then paths to all resources (databases, storages...) are updated accordingly to
/// be sure that each instance is using unique paths to resources.
///
/// Custom alias identifiers shouldn't contain any confidential information because it may be logged. It also should have a reasonable length and
/// form. It will be part of the paths to the files (databases, storage...)
///
/// IMPORTANT: Always use `default` alias for one of the instances of the synchronizer.
public enum ZcashSynchronizerAlias: Hashable {
case `default`
case custom(String)
}
extension ZcashSynchronizerAlias: CustomStringConvertible {
public var description: String {
switch self {
case .`default`:
return "default"
case let .custom(alias):
return "c_\(alias)"
}
}
}
/**
Wrapper for all the Rust backend functionality that does not involve processing blocks. This
class initializes the Rust backend and the supporting data required to exercise those abilities.
The [cash.z.wallet.sdk.block.CompactBlockProcessor] handles all the remaining Rust backend
functionality, related to processing blocks.
*/
// swiftlint:disable type_body_length
public class Initializer {
struct URLs {
let fsBlockDbRoot: URL
let dataDbURL: URL
let pendingDbURL: URL
let spendParamsURL: URL
let outputParamsURL: URL
}
public enum InitializationResult {
case success
case seedRequired
}
// This is used to uniquely identify instance of the SDKSynchronizer. It's used when checking if the Alias is already used or not.
let id = UUID()
let rustBackend: ZcashRustBackendWelding.Type
let alias: String
let alias: ZcashSynchronizerAlias
let endpoint: LightWalletEndpoint
let fsBlockDbRoot: URL
let dataDbURL: URL
@ -95,6 +135,7 @@ public class Initializer {
let storage: CompactBlockRepository
let blockDownloaderService: BlockDownloaderService
let network: ZcashNetwork
let logger: Logger
/// The effective birthday of the wallet based on the height provided when initializing and the checkpoints available on this SDK.
///
@ -104,71 +145,20 @@ public class Initializer {
/// The purpose of this to migrate from cacheDb to fsBlockDb
private var cacheDbURL: URL?
/// Constructs the Initializer
/// - Parameters:
/// - fsBlockDbRoot: location of the compact blocks cache
/// - dataDbURL: Location of the data db
/// - pendingDbURL: location of the pending transactions database
/// - endpoint: the endpoint representing the lightwalletd instance you want to point to
/// - spendParamsURL: location of the spend parameters
/// - outputParamsURL: location of the output parameters
convenience public init (
fsBlockDbRoot: URL,
dataDbURL: URL,
pendingDbURL: URL,
endpoint: LightWalletEndpoint,
network: ZcashNetwork,
spendParamsURL: URL,
outputParamsURL: URL,
saplingParamsSourceURL: SaplingParamsSourceURL,
alias: String = "",
loggerProxy: Logger? = nil
) {
self.init(
rustBackend: ZcashRustBackend.self,
network: network,
cacheDbURL: nil,
fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: Self.makeLightWalletServiceFactory(endpoint: endpoint).make(),
repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL),
accountRepository: AccountRepositoryBuilder.build(
dataDbURL: dataDbURL,
readOnly: true,
caching: true
),
storage: FSCompactBlockRepository(
fsBlockDbRoot: fsBlockDbRoot,
metadataStore: .live(
fsBlockDbRoot: fsBlockDbRoot,
rustBackend: ZcashRustBackend.self
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
),
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
saplingParamsSourceURL: saplingParamsSourceURL,
alias: alias,
loggerProxy: loggerProxy
)
}
/// Error that can be created when updating URLs according to alias. If this error is created then it is thrown from `SDKSynchronizer.prepare()`
/// or `SDKSynchronizer.wipe()`.
var urlsParsingError: InitializerError?
/// Constructs the Initializer and migrates an old cacheDb to the new file system block cache if
/// a `cacheDbURL` is provided.
/// Constructs the Initializer and migrates an old cacheDb to the new file system block cache if a `cacheDbURL` is provided.
/// - Parameters:
/// - cacheDbURL: previous location of the cacheDb. Use this constru
/// - cacheDbURL: previous location of the cacheDb. If you don't know what a cacheDb is and you are adopting this SDK for the first time then
/// just pass `nil` here.
/// - fsBlockDbRoot: location of the compact blocks cache
/// - dataDbURL: Location of the data db
/// - pendingDbURL: location of the pending transactions database
/// - endpoint: the endpoint representing the lightwalletd instance you want to point to
/// - spendParamsURL: location of the spend parameters
/// - outputParamsURL: location of the output parameters
///
/// - note: If you don't know what a cacheDb is and you are adopting
/// this SDK for the first time then you just need to invoke `convenience init(fsBlockDbRoot: URL, dataDbURL: URL, pendingDbURL: URL, endpoint: LightWalletEndpoint, network: ZcashNetwork, spendParamsURL: URL, outputParamsURL: URL, alias: String = "", loggerProxy: Logger? = nil)` instead
convenience public init (
cacheDbURL: URL?,
fsBlockDbRoot: URL,
@ -179,71 +169,80 @@ public class Initializer {
spendParamsURL: URL,
outputParamsURL: URL,
saplingParamsSourceURL: SaplingParamsSourceURL,
alias: String = "",
loggerProxy: Logger? = nil
alias: ZcashSynchronizerAlias = .default,
logLevel: OSLogger.LogLevel = .debug
) {
let urls = URLs(
fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL
)
// It's not possible to fail from constructor. Technically it's possible but it can be pain for the client apps to handle errors thrown
// from constructor. So `parsingError` is just stored in initializer and `SDKSynchronizer.prepare()` throw this error if it exists.
let (updatedURLs, parsingError) = Self.tryToUpdateURLs(with: alias, urls: urls)
let logger = OSLogger(logLevel: logLevel, alias: alias)
self.init(
rustBackend: ZcashRustBackend.self,
network: network,
cacheDbURL: cacheDbURL,
fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
urls: updatedURLs,
endpoint: endpoint,
service: Self.makeLightWalletServiceFactory(endpoint: endpoint).make(),
repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL),
accountRepository: AccountRepositoryBuilder.build(
dataDbURL: dataDbURL,
dataDbURL: updatedURLs.dataDbURL,
readOnly: true,
caching: true
caching: true,
logger: logger
),
storage: FSCompactBlockRepository(
fsBlockDbRoot: fsBlockDbRoot,
fsBlockDbRoot: updatedURLs.fsBlockDbRoot,
metadataStore: .live(
fsBlockDbRoot: fsBlockDbRoot,
rustBackend: ZcashRustBackend.self
fsBlockDbRoot: updatedURLs.fsBlockDbRoot,
rustBackend: ZcashRustBackend.self,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
),
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
saplingParamsSourceURL: saplingParamsSourceURL,
alias: alias,
loggerProxy: loggerProxy
urlsParsingError: parsingError,
logger: logger
)
}
/**
Internal for dependency injection purposes
*/
/// Internal for dependency injection purposes.
///
/// !!! It's expected that URLs put here are already update with the Alias.
init(
rustBackend: ZcashRustBackendWelding.Type,
network: ZcashNetwork,
cacheDbURL: URL?,
fsBlockDbRoot: URL,
dataDbURL: URL,
pendingDbURL: URL,
urls: URLs,
endpoint: LightWalletEndpoint,
service: LightWalletService,
repository: TransactionRepository,
accountRepository: AccountRepository,
storage: CompactBlockRepository,
spendParamsURL: URL,
outputParamsURL: URL,
saplingParamsSourceURL: SaplingParamsSourceURL,
alias: String = "",
loggerProxy: Logger? = nil
alias: ZcashSynchronizerAlias,
urlsParsingError: InitializerError?,
logger: Logger
) {
logger = loggerProxy
self.cacheDbURL = cacheDbURL
self.rustBackend = rustBackend
self.fsBlockDbRoot = fsBlockDbRoot
self.dataDbURL = dataDbURL
self.pendingDbURL = pendingDbURL
self.fsBlockDbRoot = urls.fsBlockDbRoot
self.dataDbURL = urls.dataDbURL
self.pendingDbURL = urls.pendingDbURL
self.endpoint = endpoint
self.spendParamsURL = spendParamsURL
self.outputParamsURL = outputParamsURL
self.spendParamsURL = urls.spendParamsURL
self.outputParamsURL = urls.outputParamsURL
self.saplingParamsSourceURL = saplingParamsSourceURL
self.alias = alias
self.lightWalletService = service
@ -253,21 +252,76 @@ public class Initializer {
self.blockDownloaderService = BlockDownloaderServiceImpl(service: service, storage: storage)
self.network = network
self.walletBirthday = Checkpoint.birthday(with: 0, network: network).height
self.urlsParsingError = urlsParsingError
self.logger = logger
}
private static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory {
return LightWalletServiceFactory(
endpoint: endpoint,
connectionStateChange: { oldState, newState in
NotificationSender.default.post(
name: .synchronizerConnectionStateChanged,
object: self,
userInfo: [
SDKSynchronizer.NotificationKeys.previousConnectionState: oldState,
SDKSynchronizer.NotificationKeys.currentConnectionState: newState
]
)
}
return LightWalletServiceFactory(endpoint: endpoint)
}
/// Try to update URLs with `alias`.
///
/// If the `default` alias is used then the URLs are changed at all.
/// If the `custom("anotherInstance")` is used then last path component or the URL is updated like this:
/// - /some/path/to.file -> /some/path/c_anotherInstance_to.file
/// - /some/path/to/directory -> /some/path/to/c_anotherInstance_directory
///
/// If any of the URLs can't be parsed then returned error isn't nil.
private static func tryToUpdateURLs(
with alias: ZcashSynchronizerAlias,
urls: URLs
) -> (URLs, InitializerError?) {
let updatedURLsResult = Self.updateURLs(with: alias, urls: urls)
let parsingError: InitializerError?
let updatedURLs: URLs
switch updatedURLsResult {
case let .success(updated):
parsingError = nil
updatedURLs = updated
case let .failure(error):
parsingError = error
// When failure happens just use original URLs because something must be used. But this shouldn't be a problem because
// `SDKSynchronizer.prepare()` handles this error. And the SDK won't work if it isn't switched from `unprepared` state.
updatedURLs = urls
}
return (updatedURLs, parsingError)
}
private static func updateURLs(
with alias: ZcashSynchronizerAlias,
urls: URLs
) -> Result<URLs, InitializerError> {
guard let updatedFsBlockDbRoot = urls.fsBlockDbRoot.updateLastPathComponent(with: alias) else {
return .failure(.cantUpdateURLWithAlias(urls.fsBlockDbRoot))
}
guard let updatedDataDbURL = urls.dataDbURL.updateLastPathComponent(with: alias) else {
return .failure(.cantUpdateURLWithAlias(urls.dataDbURL))
}
guard let updatedPendingDbURL = urls.pendingDbURL.updateLastPathComponent(with: alias) else {
return .failure(.cantUpdateURLWithAlias(urls.pendingDbURL))
}
guard let updatedSpendParamsURL = urls.spendParamsURL.updateLastPathComponent(with: alias) else {
return .failure(.cantUpdateURLWithAlias(urls.spendParamsURL))
}
guard let updateOutputParamsURL = urls.outputParamsURL.updateLastPathComponent(with: alias) else {
return .failure(.cantUpdateURLWithAlias(urls.outputParamsURL))
}
return .success(
URLs(
fsBlockDbRoot: updatedFsBlockDbRoot,
dataDbURL: updatedDataDbURL,
pendingDbURL: updatedPendingDbURL,
spendParamsURL: updatedSpendParamsURL,
outputParamsURL: updateOutputParamsURL
)
)
}
@ -333,7 +387,8 @@ public class Initializer {
let migrationManager = MigrationManager(
pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path),
networkType: self.network.networkType
networkType: self.network.networkType,
logger: logger
)
try migrationManager.performMigration()
@ -415,6 +470,13 @@ extension InitializerError: LocalizedError {
return "account table init failed with error: \(error.localizedDescription)"
case .fsCacheInitFailed(let error):
return "Compact Block Cache failed to initialize with error: \(error.localizedDescription)"
case let .aliasAlreadyInUse(alias):
return """
The Alias \(alias) used for this instance of the SDKSynchronizer is already in use. Each instance of the SDKSynchronizer must use unique \
Alias.
"""
case .cantUpdateURLWithAlias(let url):
return "Can't update path URL with alias. \(url)"
}
}
}

View File

@ -31,8 +31,6 @@ import Foundation
///
/// We encourage you to check`SDKMetricsTests` and other tests in the Test/PerformanceTests/ folder.
public class SDKMetrics {
public static let shared = SDKMetrics()
public struct BlockMetricReport: Equatable {
public let startHeight: BlockHeight
public let progressHeight: BlockHeight
@ -62,6 +60,8 @@ public class SDKMetrics {
var isEnabled = false
var reports: [Operation: [BlockMetricReport]] = [:]
public init() { }
/// `SDKMetrics` is disabled by default. Any pushed data are simply ignored until `enableMetrics()` is called.
public func enableMetrics() {
isEnabled = true
@ -183,7 +183,7 @@ extension SDKMetrics {
let fetchUTXOsReport = summaryFor(reports: reports[.fetchUTXOs])
var totalSyncReport: ReportSummary?
if let duration = SDKMetrics.shared.syncReport?.duration {
if let duration = syncReport?.duration {
totalSyncReport = ReportSummary(minTime: duration, maxTime: duration, avgTime: duration)
}

View File

@ -61,16 +61,20 @@ class LightWalletGRPCService {
let streamingCallTimeout: TimeLimit
var latestBlockHeightProvider: LatestBlockHeightProvider = LiveLatestBlockHeightProvider()
var connectionStateChange: ((_ from: ConnectionState, _ to: ConnectionState) -> Void)? {
get { connectionManager.connectionStateChange }
set { connectionManager.connectionStateChange = newValue }
}
var queue: DispatchQueue
convenience init(endpoint: LightWalletEndpoint, connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void) {
convenience init(endpoint: LightWalletEndpoint) {
self.init(
host: endpoint.host,
port: endpoint.port,
secure: endpoint.secure,
singleCallTimeout: endpoint.singleCallTimeoutInMillis,
streamingCallTimeout: endpoint.streamingCallTimeoutInMillis,
connectionStateChange: connectionStateChange
streamingCallTimeout: endpoint.streamingCallTimeoutInMillis
)
}
@ -86,10 +90,9 @@ class LightWalletGRPCService {
port: Int = 9067,
secure: Bool = true,
singleCallTimeout: Int64,
streamingCallTimeout: Int64,
connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void
streamingCallTimeout: Int64
) {
self.connectionManager = ConnectionStatusManager(connectionStateChange: connectionStateChange)
self.connectionManager = ConnectionStatusManager()
self.queue = DispatchQueue.init(label: "LightWalletGRPCService")
self.streamingCallTimeout = TimeLimit.timeout(.milliseconds(streamingCallTimeout))
self.singleCallTimeout = TimeLimit.timeout(.milliseconds(singleCallTimeout))
@ -344,12 +347,10 @@ extension LightWalletServiceError {
}
class ConnectionStatusManager: ConnectivityStateDelegate {
let connectionStateChange: (_ from: ConnectionState, _ to: ConnectionState) -> Void
init(connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void) {
self.connectionStateChange = connectionStateChange
}
var connectionStateChange: ((_ from: ConnectionState, _ to: ConnectionState) -> Void)?
init() { }
func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) {
connectionStateChange(oldState.toConnectionState(), newState.toConnectionState())
connectionStateChange?(oldState.toConnectionState(), newState.toConnectionState())
}
}

View File

@ -136,19 +136,20 @@ protocol LightWalletServiceResponse {
struct LightWalletServiceFactory {
let endpoint: LightWalletEndpoint
let connectionStateChange: (_ from: ConnectionState, _ to: ConnectionState) -> Void
init(endpoint: LightWalletEndpoint, connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void) {
init(endpoint: LightWalletEndpoint) {
self.endpoint = endpoint
self.connectionStateChange = connectionStateChange
}
func make() -> LightWalletService {
return LightWalletGRPCService(endpoint: endpoint, connectionStateChange: connectionStateChange)
return LightWalletGRPCService(endpoint: endpoint)
}
}
protocol LightWalletService {
protocol LightWalletService: AnyObject {
/// Closure which is called when connection state changes.
var connectionStateChange: ((_ from: ConnectionState, _ to: ConnectionState) -> Void)? { get set }
/// Returns the info for this lightwalletd server
func getInfo() async throws -> LightWalletdInfo

View File

@ -26,7 +26,6 @@ public enum SynchronizerError: Error {
case rewindErrorUnknownArchorHeight // ZcashLightClientKit.SynchronizerError error 13.
case invalidAccount // ZcashLightClientKit.SynchronizerError error 14.
case lightwalletdValidationFailed(underlyingError: Error) // ZcashLightClientKit.SynchronizerError error 8.
case wipeAttemptWhileProcessing
}
public enum ShieldFundsError: Error {
@ -90,12 +89,15 @@ public enum SynchronizerEvent {
// Sent when the synchronizer fetched utxos from lightwalletd attempted to store them.
case storedUTXOs(_ inserted: [UnspentTransactionOutputEntity], _ skipped: [UnspentTransactionOutputEntity])
// Connection state to LightwalletEndpoint changed.
case connectionStateChanged
case connectionStateChanged(ConnectionState)
}
/// Primary interface for interacting with the SDK. Defines the contract that specific
/// implementations like SdkSynchronizer fulfill.
public protocol Synchronizer: AnyObject {
/// Alias used for this instance.
var alias: ZcashSynchronizerAlias { get }
/// Latest state of the SDK which can be get in synchronous manner.
var latestState: SynchronizerState { get }
@ -112,6 +114,9 @@ public protocol Synchronizer: AnyObject {
/// This stream is backed by `PassthroughSubject`. Check `SynchronizerEvent` to see which events may be emitted.
var eventStream: AnyPublisher<SynchronizerEvent, Never> { get }
/// An object that when enabled collects mertrics from the synchronizer
var metrics: SDKMetrics { get }
/// Initialize the wallet. The ZIP-32 seed bytes can optionally be passed to perform
/// database migrations. most of the times the seed won't be needed. If they do and are
/// not provided this will fail with `InitializationResult.seedRequired`. It could
@ -126,8 +131,13 @@ public protocol Synchronizer: AnyObject {
/// - seed: ZIP-32 Seed bytes for the wallet that will be initialized.
/// - viewingKeys: Viewing key derived from seed.
/// - walletBirthday: Birthday of wallet.
/// - Throws: `InitializerError.dataDbInitFailed` if the creation of the dataDb fails
/// - Throws:
/// `InitializerError.dataDbInitFailed` if the creation of the dataDb fails
/// `InitializerError.accountInitFailed` if the account table can't be initialized.
/// `InitializerError.aliasAlreadyInUse` if the Alias used to create this instance is already used by other instance
/// `InitializerError.cantUpdateURLWithAlias` if the updating of paths in `Initilizer` according to alias fails. When this happens it means that
/// some path passed to `Initializer` is invalid. The SDK can't recover from this and this instance
/// won't do anything.
func prepare(
with seed: [UInt8]?,
viewingKeys: [UnifiedFullViewingKey],
@ -163,6 +173,9 @@ public protocol Synchronizer: AnyObject {
/// - Parameter zatoshi: the amount to send in Zatoshi.
/// - Parameter toAddress: the recipient's address.
/// - Parameter memo: an `Optional<Memo>`with the memo to include as part of the transaction. send `nil` when sending to transparent receivers otherwise the function will throw an error
///
/// If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func sendToAddress(
spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
@ -173,6 +186,9 @@ public protocol Synchronizer: AnyObject {
/// Shields transparent funds from the given private key into the best shielded pool of the account associated to the given `UnifiedSpendingKey`.
/// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds
/// - Parameter memo: the optional memo to include as part of the transaction.
///
/// If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func shieldFunds(
spendingKey: UnifiedSpendingKey,
memo: Memo,
@ -232,8 +248,11 @@ public protocol Synchronizer: AnyObject {
/// Returns the latest block height from the provided Lightwallet endpoint
/// Blocking
func latestHeight() async throws -> BlockHeight
/// Returns the latests UTXOs for the given address from the specified height on
///
/// If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs
/// Returns the last stored transparent balance
@ -266,7 +285,10 @@ public protocol Synchronizer: AnyObject {
/// - Emits rewindError for other errors
///
/// `rewind(policy:)` itself doesn't start the sync process when it's done and it doesn't trigger notifications as regorg would. After it is done
/// you have start the sync process by calling `start()`.
/// you have start the sync process by calling `start()`
///
/// If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then returned publisher emits
/// `SynchronizerErrors.notPrepared` error.
///
/// - Parameter policy: the rewind policy
func rewind(_ policy: RewindPolicy) -> AnyPublisher<Void, Error>
@ -284,6 +306,12 @@ public protocol Synchronizer: AnyObject {
/// fails then something is seriously wrong. If the wipe fails then the SDK may be in inconsistent state. It's suggested to call wipe again until
/// it succeed.
///
/// Returned publisher emits `InitializerError.aliasAlreadyInUse` error if the Alias used to create this instance is already used by other
/// instance.
///
/// Returned publisher emits `InitializerError.cantUpdateURLWithAlias` if the updating of paths in `Initilizer` according to alias fails. When
/// this happens it means that some path passed to `Initializer` is invalid. The SDK can't recover from this and this instance won't do anything.
///
func wipe() -> AnyPublisher<Void, Error>
}
@ -331,6 +359,14 @@ public enum SyncStatus: Equatable {
}
}
public var isPrepared: Bool {
if case .unprepared = self {
return false
} else {
return true
}
}
public var briefDebugDescription: String {
switch self {
case .unprepared: return "unprepared"

View File

@ -23,6 +23,8 @@ public struct ClosureSDKSynchronizer {
}
extension ClosureSDKSynchronizer: ClosureSynchronizer {
public var alias: ZcashSynchronizerAlias { synchronizer.alias }
public var latestState: SynchronizerState { synchronizer.latestState }
public var connectionState: ConnectionState { synchronizer.connectionState }

View File

@ -23,6 +23,8 @@ public struct CombineSDKSynchronizer {
}
extension CombineSDKSynchronizer: CombineSynchronizer {
public var alias: ZcashSynchronizerAlias { synchronizer.alias }
public var latestState: SynchronizerState { synchronizer.latestState }
public var connectionState: ConnectionState { synchronizer.connectionState }

View File

@ -9,19 +9,12 @@
import Foundation
import Combine
extension Notification.Name {
static let synchronizerConnectionStateChanged = Notification.Name("SynchronizerConnectionStateChanged")
}
/// Synchronizer implementation for UIKit and iOS 13+
// swiftlint:disable type_body_length
public class SDKSynchronizer: Synchronizer {
public enum NotificationKeys {
public static let currentConnectionState = "SDKSynchronizer.currentConnectionState"
public static let previousConnectionState = "SDKSynchronizer.previousConnectionState"
}
public var alias: ZcashSynchronizerAlias { initializer.alias }
private let streamsUpdateQueue = DispatchQueue(label: "streamsUpdateQueue")
private lazy var streamsUpdateQueue = { DispatchQueue(label: "streamsUpdateQueue_\(initializer.alias.description)") }()
private let stateSubject = CurrentValueSubject<SynchronizerState, Never>(.zero)
public var stateStream: AnyPublisher<SynchronizerState, Never> { stateSubject.eraseToAnyPublisher() }
public private(set) var latestState: SynchronizerState = .zero
@ -29,6 +22,9 @@ public class SDKSynchronizer: Synchronizer {
private let eventSubject = PassthroughSubject<SynchronizerEvent, Never>()
public var eventStream: AnyPublisher<SynchronizerEvent, Never> { eventSubject.eraseToAnyPublisher() }
public let metrics: SDKMetrics
public let logger: Logger
// Don't read this variable directly. Use `status` instead. And don't update this variable directly use `updateStatus()` methods instead.
private var underlyingStatus: GenericActor<SyncStatus>
var status: SyncStatus {
@ -36,7 +32,7 @@ public class SDKSynchronizer: Synchronizer {
}
let blockProcessor: CompactBlockProcessor
let blockProcessorEventProcessingQueue = DispatchQueue(label: "blockProcessorEventProcessingQueue")
lazy var blockProcessorEventProcessingQueue = { DispatchQueue(label: "blockProcessorEventProcessingQueue_\(initializer.alias.description)") }()
public private(set) var initializer: Initializer
// Valid value is stored here after `prepare` is called.
@ -52,6 +48,7 @@ public class SDKSynchronizer: Synchronizer {
/// Creates an SDKSynchronizer instance
/// - Parameter initializer: a wallet Initializer object
public convenience init(initializer: Initializer) {
let metrics = SDKMetrics()
self.init(
status: .unprepared,
initializer: initializer,
@ -60,8 +57,11 @@ public class SDKSynchronizer: Synchronizer {
utxoRepository: UTXORepositoryBuilder.build(initializer: initializer),
blockProcessor: CompactBlockProcessor(
initializer: initializer,
metrics: metrics,
logger: initializer.logger,
walletBirthdayProvider: { initializer.walletBirthday }
)
),
metrics: metrics
)
}
@ -71,7 +71,8 @@ public class SDKSynchronizer: Synchronizer {
transactionManager: OutboundTransactionManager,
transactionRepository: TransactionRepository,
utxoRepository: UnspentTransactionOutputRepository,
blockProcessor: CompactBlockProcessor
blockProcessor: CompactBlockProcessor,
metrics: SDKMetrics
) {
self.connectionState = .idle
self.underlyingStatus = GenericActor(status)
@ -81,13 +82,18 @@ public class SDKSynchronizer: Synchronizer {
self.utxoRepository = utxoRepository
self.blockProcessor = blockProcessor
self.network = initializer.network
self.metrics = metrics
self.logger = initializer.logger
subscribeToProcessorNotifications(blockProcessor)
initializer.lightWalletService.connectionStateChange = { [weak self] oldState, newState in
self?.connectivityStateChanged(oldState: oldState, newState: newState)
}
Task(priority: .high) { [weak self] in await self?.subscribeToProcessorEvents(blockProcessor) }
}
deinit {
UsedAliasesChecker.stopUsing(alias: initializer.alias, id: initializer.id)
NotificationCenter.default.removeObserver(self)
Task { [blockProcessor] in
await blockProcessor.stop()
@ -99,6 +105,24 @@ public class SDKSynchronizer: Synchronizer {
await notify(oldStatus: oldValue, newStatus: newValue)
}
func throwIfUnprepared() throws {
if !latestState.syncStatus.isPrepared {
throw SynchronizerError.notPrepared
}
}
func checkIfCanContinueInitialisation() -> InitializerError? {
if let initialisationError = initializer.urlsParsingError {
return initialisationError
}
if !UsedAliasesChecker.tryToUse(alias: initializer.alias, id: initializer.id) {
return InitializerError.aliasAlreadyInUse(initializer.alias)
}
return nil
}
public func prepare(
with seed: [UInt8]?,
viewingKeys: [UnifiedFullViewingKey],
@ -106,6 +130,10 @@ public class SDKSynchronizer: Synchronizer {
) async throws -> Initializer.InitializationResult {
guard await status == .unprepared else { return .success }
if let error = checkIfCanContinueInitialisation() {
throw error
}
try utxoRepository.initialise()
if case .seedRequired = try self.initializer.initialize(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday) {
@ -127,7 +155,7 @@ public class SDKSynchronizer: Synchronizer {
throw SynchronizerError.notPrepared
case .syncing, .enhancing, .fetching:
LoggerProxy.warn("warning: Synchronizer started when already running. Next sync process will be started when the current one stops.")
logger.warn("warning: Synchronizer started when already running. Next sync process will be started when the current one stops.")
/// This may look strange but `CompactBlockProcessor` has mechanisms which can handle this situation. So we are fine with calling
/// it's start here.
await blockProcessor.start(retry: retry)
@ -143,41 +171,19 @@ public class SDKSynchronizer: Synchronizer {
public func stop() async {
let status = await self.status
guard status != .stopped, status != .disconnected else {
LoggerProxy.info("attempted to stop when status was: \(status)")
logger.info("attempted to stop when status was: \(status)")
return
}
await blockProcessor.stop()
}
private func subscribeToProcessorNotifications(_ processor: CompactBlockProcessor) {
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(connectivityStateChanged(_:)),
name: Notification.Name.synchronizerConnectionStateChanged,
object: nil
)
}
// MARK: Connectivity State
@objc func connectivityStateChanged(_ notification: Notification) {
guard
let userInfo = notification.userInfo,
let current = userInfo[NotificationKeys.currentConnectionState] as? ConnectionState
else {
LoggerProxy.error(
"Found \(notification.name) but lacks dictionary information." +
"This is probably a programming error"
)
return
}
connectionState = current
func connectivityStateChanged(oldState: ConnectionState, newState: ConnectionState) {
connectionState = newState
streamsUpdateQueue.async { [weak self] in
self?.eventSubject.send(.connectionStateChanged)
self?.eventSubject.send(.connectionStateChanged(newState))
}
}
@ -232,7 +238,7 @@ public class SDKSynchronizer: Synchronizer {
await updateStatus(.synced)
if let syncStartDate {
SDKMetrics.shared.pushSyncReport(
metrics.pushSyncReport(
start: syncStartDate,
end: Date()
)
@ -246,12 +252,12 @@ public class SDKSynchronizer: Synchronizer {
}
private func handledReorg(reorgHeight: BlockHeight, rewindHeight: BlockHeight) {
LoggerProxy.debug("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)")
logger.debug("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)")
do {
try transactionManager.handleReorg(at: rewindHeight)
} catch {
LoggerProxy.debug("error handling reorg: \(error)")
logger.debug("error handling reorg: \(error)")
}
}
@ -274,12 +280,15 @@ public class SDKSynchronizer: Synchronizer {
toAddress: Recipient,
memo: Memo?
) async throws -> PendingTransactionEntity {
try throwIfUnprepared()
do {
try await SaplingParameterDownloader.downloadParamsIfnotPresent(
spendURL: initializer.spendParamsURL,
spendSourceURL: initializer.saplingParamsSourceURL.spendParamFileURL,
outputURL: initializer.outputParamsURL,
outputSourceURL: initializer.saplingParamsSourceURL.outputParamFileURL
outputSourceURL: initializer.saplingParamsSourceURL.outputParamFileURL,
logger: logger
)
} catch {
throw SynchronizerError.parameterMissing(underlyingError: error)
@ -302,6 +311,8 @@ public class SDKSynchronizer: Synchronizer {
memo: Memo,
shieldingThreshold: Zatoshi
) async throws -> PendingTransactionEntity {
try throwIfUnprepared()
// let's see if there are funds to shield
let accountIndex = Int(spendingKey.account)
do {
@ -410,6 +421,8 @@ public class SDKSynchronizer: Synchronizer {
}
public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] {
try throwIfUnprepared()
guard initializer.isValidTransparentAddress(address) else {
throw SynchronizerError.generalError(message: "invalid t-address")
}
@ -431,7 +444,8 @@ public class SDKSynchronizer: Synchronizer {
}
public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs {
try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
try throwIfUnprepared()
return try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
}
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
public func getShieldedBalance(accountIndex: Int = 0) -> Int64 {
@ -472,7 +486,12 @@ public class SDKSynchronizer: Synchronizer {
public func rewind(_ policy: RewindPolicy) -> AnyPublisher<Void, Error> {
let subject = PassthroughSubject<Void, Error>()
Task {
Task(priority: .high) {
if !latestState.syncStatus.isPrepared {
subject.send(completion: .failure(SynchronizerError.notPrepared))
return
}
let height: BlockHeight?
switch policy {
@ -521,6 +540,11 @@ public class SDKSynchronizer: Synchronizer {
public func wipe() -> AnyPublisher<Void, Error> {
let subject = PassthroughSubject<Void, Error>()
Task(priority: .high) {
if let error = checkIfCanContinueInitialisation() {
subject.send(completion: .failure(error))
return
}
let context = AfterSyncHooksManager.WipeContext(
pendingDbURL: initializer.pendingDbURL,
prewipe: { [weak self] in
@ -633,7 +657,7 @@ public class SDKSynchronizer: Synchronizer {
try updateMinedTransactions()
try removeConfirmedTransactions()
} catch {
LoggerProxy.debug("error refreshing pending transactions: \(error)")
logger.debug("error refreshing pending transactions: \(error)")
}
}

View File

@ -25,18 +25,21 @@ class PersistentTransactionManager: OutboundTransactionManager {
var service: LightWalletService
var queue: DispatchQueue
var network: NetworkType
let logger: Logger
init(
encoder: TransactionEncoder,
service: LightWalletService,
repository: PendingTransactionRepository,
networkType: NetworkType
networkType: NetworkType,
logger: Logger
) {
self.repository = repository
self.encoder = encoder
self.service = service
self.network = networkType
self.queue = DispatchQueue.init(label: "PersistentTransactionManager.serial.queue", qos: .userInitiated)
self.logger = logger
}
func initSpend(
@ -61,7 +64,7 @@ class PersistentTransactionManager: OutboundTransactionManager {
zatoshi: zatoshi
)
}
LoggerProxy.debug("pending transaction \(String(describing: insertedTx.id)) created")
logger.debug("pending transaction \(String(describing: insertedTx.id)) created")
return insertedTx
}
@ -162,12 +165,12 @@ class PersistentTransactionManager: OutboundTransactionManager {
}
guard !storedTx.isCancelled else {
LoggerProxy.debug("ignoring cancelled transaction \(storedTx)")
logger.debug("ignoring cancelled transaction \(storedTx)")
throw TransactionManagerError.cancelled(storedTx)
}
guard let raw = storedTx.raw else {
LoggerProxy.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data")
logger.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data")
throw TransactionManagerError.internalInconsistency(storedTx)
}
@ -273,14 +276,18 @@ enum OutboundTransactionManagerBuilder {
encoder: TransactionEncoderbuilder.build(initializer: initializer),
service: initializer.lightWalletService,
repository: PendingTransactionRepositoryBuilder.build(initializer: initializer),
networkType: initializer.network.networkType
networkType: initializer.network.networkType,
logger: initializer.logger
)
}
}
enum PendingTransactionRepositoryBuilder {
static func build(initializer: Initializer) -> PendingTransactionRepository {
PendingTransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.pendingDbURL.path, readonly: false))
PendingTransactionSQLDAO(
dbProvider: SimpleConnectionProvider(path: initializer.pendingDbURL.path, readonly: false),
logger: initializer.logger
)
}
}

View File

@ -10,6 +10,7 @@ import Foundation
class WalletTransactionEncoder: TransactionEncoder {
var rustBackend: ZcashRustBackendWelding.Type
var repository: TransactionRepository
let logger: Logger
private var outputParamsURL: URL
private var spendParamsURL: URL
@ -24,7 +25,8 @@ class WalletTransactionEncoder: TransactionEncoder {
repository: TransactionRepository,
outputParams: URL,
spendParams: URL,
networkType: NetworkType
networkType: NetworkType,
logger: Logger
) {
self.rustBackend = rust
self.dataDbURL = dataDb
@ -33,6 +35,7 @@ class WalletTransactionEncoder: TransactionEncoder {
self.outputParamsURL = outputParams
self.spendParamsURL = spendParams
self.networkType = networkType
self.logger = logger
}
convenience init(initializer: Initializer) {
@ -43,7 +46,8 @@ class WalletTransactionEncoder: TransactionEncoder {
repository: initializer.transactionRepository,
outputParams: initializer.outputParamsURL,
spendParams: initializer.spendParamsURL,
networkType: initializer.network.networkType
networkType: initializer.network.networkType,
logger: initializer.logger
)
}
@ -63,7 +67,7 @@ class WalletTransactionEncoder: TransactionEncoder {
)
do {
LoggerProxy.debug("transaction id: \(txId)")
logger.debug("transaction id: \(txId)")
return try repository.find(id: txId)
} catch {
throw TransactionEncoderError.notFound(transactionId: txId)
@ -113,7 +117,7 @@ class WalletTransactionEncoder: TransactionEncoder {
)
do {
LoggerProxy.debug("transaction id: \(txId)")
logger.debug("transaction id: \(txId)")
return try repository.find(id: txId)
} catch {
throw TransactionEncoderError.notFound(transactionId: txId)

View File

@ -22,26 +22,24 @@ public protocol Logger {
func error(_ message: String, file: StaticString, function: StaticString, line: Int)
}
var logger: Logger?
enum LoggerProxy {
static func debug(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger?.debug(message, file: file, function: function, line: line)
extension Logger {
func debug(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
debug(message, file: file, function: function, line: line)
}
static func info(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger?.info(message, file: file, function: function, line: line)
func info(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
info(message, file: file, function: function, line: line)
}
static func event(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger?.event(message, file: file, function: function, line: line)
func event(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
event(message, file: file, function: function, line: line)
}
static func warn(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger?.warn(message, file: file, function: function, line: line)
func warn(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
warn(message, file: file, function: function, line: line)
}
static func error(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger?.error(message, file: file, function: function, line: line)
func error(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
error(message, file: file, function: function, line: line)
}
}

View File

@ -9,6 +9,8 @@ import Foundation
import os
public class OSLogger: Logger {
public var alias: ZcashSynchronizerAlias?
public enum LogLevel: Int {
case debug
case error
@ -21,10 +23,13 @@ public class OSLogger: Logger {
var level: LogLevel
public init(logLevel: LogLevel, category: String = "logs") {
public init(logLevel: LogLevel, category: String = "logs", alias: ZcashSynchronizerAlias? = nil) {
self.alias = alias
self.level = logLevel
if let bundleName = Bundle.main.bundleIdentifier {
self.oslog = OSLog(subsystem: bundleName, category: category)
var postfix = ""
if let alias { postfix = "_\(alias.description)" }
self.oslog = OSLog(subsystem: bundleName, category: "\(category)\(postfix)")
}
}

View File

@ -30,8 +30,8 @@ public enum SaplingParameterDownloader {
/// - Parameters:
/// - at: The destination URL for the download
@discardableResult
public static func downloadSpendParameter(_ at: URL, sourceURL: URL) async throws -> URL {
let resultURL = try await downloadFileWithRequestWithContinuation(URLRequest(url: sourceURL), at: at)
public static func downloadSpendParameter(_ at: URL, sourceURL: URL, logger: Logger) async throws -> URL {
let resultURL = try await downloadFileWithRequestWithContinuation(URLRequest(url: sourceURL), logger: logger, at: at)
try isSpendParamsSHA1Valid(url: resultURL)
return resultURL
}
@ -40,8 +40,8 @@ public enum SaplingParameterDownloader {
/// - Parameters:
/// - at: The destination URL for the download
@discardableResult
public static func downloadOutputParameter(_ at: URL, sourceURL: URL) async throws -> URL {
let resultURL = try await downloadFileWithRequestWithContinuation(URLRequest(url: sourceURL), at: at)
public static func downloadOutputParameter(_ at: URL, sourceURL: URL, logger: Logger) async throws -> URL {
let resultURL = try await downloadFileWithRequestWithContinuation(URLRequest(url: sourceURL), logger: logger, at: at)
try isOutputParamsSHA1Valid(url: resultURL)
return resultURL
}
@ -55,11 +55,12 @@ public enum SaplingParameterDownloader {
spendURL: URL,
spendSourceURL: URL,
outputURL: URL,
outputSourceURL: URL
outputSourceURL: URL,
logger: Logger
) async throws -> (spend: URL, output: URL) {
do {
async let spendResultURL = ensureSpendParameter(at: spendURL, sourceURL: spendSourceURL)
async let outputResultURL = ensureOutputParameter(at: outputURL, sourceURL: outputSourceURL)
async let spendResultURL = ensureSpendParameter(at: spendURL, sourceURL: spendSourceURL, logger: logger)
async let outputResultURL = ensureOutputParameter(at: outputURL, sourceURL: outputSourceURL, logger: logger)
let results = try await [spendResultURL, outputResultURL]
return (spend: results[0], output: results[1])
@ -68,21 +69,21 @@ public enum SaplingParameterDownloader {
}
}
static func ensureSpendParameter(at url: URL, sourceURL: URL) async throws -> URL {
static func ensureSpendParameter(at url: URL, sourceURL: URL, logger: Logger) async throws -> URL {
if isFilePresent(url: url) {
try isSpendParamsSHA1Valid(url: url)
return url
} else {
return try await downloadSpendParameter(url, sourceURL: sourceURL)
return try await downloadSpendParameter(url, sourceURL: sourceURL, logger: logger)
}
}
static func ensureOutputParameter(at url: URL, sourceURL: URL) async throws -> URL {
static func ensureOutputParameter(at url: URL, sourceURL: URL, logger: Logger) async throws -> URL {
if isFilePresent(url: url) {
try isOutputParamsSHA1Valid(url: url)
return url
} else {
return try await downloadOutputParameter(url, sourceURL: sourceURL)
return try await downloadOutputParameter(url, sourceURL: sourceURL, logger: logger)
}
}
@ -113,10 +114,11 @@ private extension SaplingParameterDownloader {
static func downloadFileWithRequestWithContinuation(
_ request: URLRequest,
logger: Logger,
at destination: URL
) async throws -> URL {
return try await withCheckedThrowingContinuation { continuation in
downloadFileWithRequest(request, at: destination) { result in
downloadFileWithRequest(request, at: destination, logger: logger) { result in
switch result {
case .success(let outputResultURL):
continuation.resume(returning: outputResultURL)
@ -130,9 +132,10 @@ private extension SaplingParameterDownloader {
static func downloadFileWithRequest(
_ request: URLRequest,
at destination: URL,
logger: Logger,
result: @escaping (Result<URL, Error>) -> Void
) {
LoggerProxy.debug("Downloading sapling file from \(String(describing: request.url))")
logger.debug("Downloading sapling file from \(String(describing: request.url))")
let task = URLSession.shared.downloadTask(with: request) { url, _, error in
if let error {
result(.failure(Errors.failed(error: error)))

View File

@ -0,0 +1,41 @@
//
// UsedAliasesChecker.swift
//
//
// Created by Michal Fousek on 23.03.2023.
//
import Foundation
private var usedAliases: [ZcashSynchronizerAlias: UUID] = [:]
private let usedAliasesLock = NSLock()
// Utility used to track which SDKSynchronizer aliases are already in use.
enum UsedAliasesChecker {
static func tryToUse(alias: ZcashSynchronizerAlias, id: UUID) -> Bool {
usedAliasesLock.lock()
defer { usedAliasesLock.unlock() }
// Using of `id` allows one instance of the SDKSynchronizer to call is any time it wants and still pass the check. When `wipe()` is called it
// starts using the alias. Then when `prepare()` is also registers alias. If the check for `id` wouldn't be here one instance of the
// `SDKSynchronizer` couldn't call `prepare()` after wipe because this check wouldn't pass.
//
// `id` is uniquely generated for each instance of the `SDKSynchronizer`.
if let idUsingAlias = usedAliases[alias] {
return idUsingAlias == id
} else {
usedAliases[alias] = id
return true
}
}
static func stopUsing(alias: ZcashSynchronizerAlias, id: UUID) {
usedAliasesLock.lock()
defer { usedAliasesLock.unlock() }
// When instance "owns" the alias the alias is removed and it's no longer registered as used.
if let idUsingAlias = usedAliases[alias], idUsingAlias == id {
usedAliases.removeValue(forKey: alias)
}
}
}

View File

@ -48,7 +48,7 @@ class AdvancedReOrgTests: XCTestCase {
func handleReorg(event: CompactBlockProcessor.Event) {
guard case let .handledReorg(reorgHeight, rewindHeight) = event else { return XCTFail("empty reorg event") }
logger!.debug("--- REORG DETECTED \(reorgHeight)--- RewindHeight: \(rewindHeight)", file: #file, function: #function, line: #line)
logger.debug("--- REORG DETECTED \(reorgHeight)--- RewindHeight: \(rewindHeight)", file: #file, function: #function, line: #line)
XCTAssertEqual(reorgHeight, expectedReorgHeight)
reorgExpectation.fulfill()

View File

@ -1088,7 +1088,8 @@ class BalanceTests: XCTestCase {
let pendingRepo = PendingTransactionSQLDAO(
dbProvider: SimpleConnectionProvider(
path: coordinator.synchronizer.initializer.pendingDbURL.absoluteString
)
),
logger: logger
)
guard

View File

@ -27,13 +27,18 @@ class BlockDownloaderTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self),
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()

View File

@ -82,7 +82,11 @@ final class InternalStateConsistencyTests: XCTestCase {
XCTAssertFalse(isSyncing, "SDKSynchronizer shouldn't be syncing")
XCTAssertEqual(status, .stopped)
let internalSyncState = InternalSyncProgress(storage: UserDefaults.standard)
let internalSyncState = InternalSyncProgress(
alias: .default,
storage: UserDefaults.standard,
logger: logger
)
let latestDownloadHeight = await internalSyncState.latestDownloadedBlockHeight
let latestScanHeight = try coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()

View File

@ -58,7 +58,7 @@ final class SynchronizerTests: XCTestCase {
func handleReorg(event: CompactBlockProcessor.Event) {
guard case let .handledReorg(reorgHeight, rewindHeight) = event else { return XCTFail("empty reorg notification") }
logger!.debug("--- REORG DETECTED \(reorgHeight)--- RewindHeight: \(rewindHeight)", file: #file, function: #function, line: #line)
logger.debug("--- REORG DETECTED \(reorgHeight)--- RewindHeight: \(rewindHeight)", file: #file, function: #function, line: #line)
XCTAssertEqual(reorgHeight, expectedReorgHeight)
reorgExpectation.fulfill()
@ -227,7 +227,11 @@ final class SynchronizerTests: XCTestCase {
XCTAssertTrue(fm.fileExists(atPath: storage.blocksDirectory.path), "FS Cache directory should exist")
XCTAssertEqual(try fm.contentsOfDirectory(atPath: storage.blocksDirectory.path), [], "FS Cache directory should be empty")
let internalSyncProgress = InternalSyncProgress(storage: UserDefaults.standard)
let internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: UserDefaults.standard,
logger: logger
)
let latestDownloadedBlockHeight = await internalSyncProgress.load(.latestDownloadedBlockHeight)
let latestEnhancedHeight = await internalSyncProgress.load(.latestEnhancedHeight)

View File

@ -43,7 +43,13 @@ class TransactionEnhancementTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
XCTestCase.wait { await InternalSyncProgress(storage: UserDefaults.standard).rewind(to: 0) }
XCTestCase.wait {
await InternalSyncProgress(
alias: .default,
storage: UserDefaults.standard,
logger: logger
).rewind(to: 0)
}
logger = OSLogger(logLevel: .debug)
@ -62,6 +68,7 @@ class TransactionEnhancementTests: XCTestCase {
let pathProvider = DefaultResourceProvider(network: network)
processorConfig = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL,
spendParamsURL: pathProvider.spendParamsURL,
@ -116,10 +123,12 @@ class TransactionEnhancementTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: rustBackend
rustBackend: rustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try! storage.create()
@ -128,7 +137,9 @@ class TransactionEnhancementTests: XCTestCase {
service: service,
storage: storage,
backend: rustBackend,
config: processorConfig
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in

View File

@ -71,7 +71,7 @@ class BlockScanTests: XCTestCase {
XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType))
let endpoint = LightWalletEndpoint(address: "lightwalletd.testnet.electriccoin.co", port: 9067)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let service = LightWalletServiceFactory(endpoint: endpoint).make()
let blockCount = 100
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
@ -82,15 +82,18 @@ class BlockScanTests: XCTestCase {
fsBlockDbRoot: fsDbRootURL,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: fsDbRootURL,
rustBackend: rustBackend
rustBackend: rustBackend,
logger: logger
),
blockDescriptor: ZcashCompactBlockDescriptor.live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try fsBlockRepository.create()
let processorConfig = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: fsDbRootURL,
dataDb: dataDbURL,
spendParamsURL: spendParamsURL,
@ -104,7 +107,9 @@ class BlockScanTests: XCTestCase {
service: service,
storage: fsBlockRepository,
backend: rustBackend,
config: processorConfig
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true))
@ -118,8 +123,8 @@ class BlockScanTests: XCTestCase {
XCTAssertEqual(latestScannedheight, range.upperBound)
}
func observeBenchmark() {
let reports = SDKMetrics.shared.popAllBlockReports(flush: true)
func observeBenchmark(_ metrics: SDKMetrics) {
let reports = metrics.popAllBlockReports(flush: true)
reports.forEach {
print("observed benchmark: \($0)")
@ -131,7 +136,8 @@ class BlockScanTests: XCTestCase {
logger = OSLogger(logLevel: .debug)
SDKMetrics.shared.enableMetrics()
let metrics = SDKMetrics()
metrics.enableMetrics()
guard try self.rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType) == .success else {
XCTFail("Seed should not be required for this test")
@ -163,7 +169,7 @@ class BlockScanTests: XCTestCase {
networkType: network.networkType
)
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
let fsDbRootURL = self.testTempDirectory
@ -171,15 +177,18 @@ class BlockScanTests: XCTestCase {
fsBlockDbRoot: fsDbRootURL,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: fsDbRootURL,
rustBackend: rustWelding
rustBackend: rustWelding,
logger: logger
),
blockDescriptor: ZcashCompactBlockDescriptor.live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try fsBlockRepository.create()
var processorConfig = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: fsDbRootURL,
dataDb: dataDbURL,
spendParamsURL: spendParamsURL,
@ -194,12 +203,14 @@ class BlockScanTests: XCTestCase {
service: service,
storage: fsBlockRepository,
backend: rustWelding,
config: processorConfig
config: processorConfig,
metrics: metrics,
logger: logger
)
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
switch event {
case .progressUpdated: self?.observeBenchmark()
case .progressUpdated: self?.observeBenchmark(metrics)
default: break
}
}
@ -242,6 +253,6 @@ class BlockScanTests: XCTestCase {
}
}
SDKMetrics.shared.disableMetrics()
metrics.disableMetrics()
}
}

View File

@ -37,7 +37,7 @@ class BlockStreamingTest: XCTestCase {
singleCallTimeoutInMillis: 1000,
streamingCallTimeoutInMillis: 100000
)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let service = LightWalletServiceFactory(endpoint: endpoint).make()
let latestHeight = try await service.latestBlockHeight()
@ -66,7 +66,7 @@ class BlockStreamingTest: XCTestCase {
singleCallTimeoutInMillis: 10000,
streamingCallTimeoutInMillis: 10000
)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let service = LightWalletServiceFactory(endpoint: endpoint).make()
let realRustBackend = ZcashRustBackend.self
@ -74,10 +74,12 @@ class BlockStreamingTest: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -93,7 +95,9 @@ class BlockStreamingTest: XCTestCase {
service: service,
storage: storage,
backend: realRustBackend,
config: processorConfig
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
let cancelableTask = Task {
@ -126,7 +130,7 @@ class BlockStreamingTest: XCTestCase {
singleCallTimeoutInMillis: 1000,
streamingCallTimeoutInMillis: 1000
)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let service = LightWalletServiceFactory(endpoint: endpoint).make()
let realRustBackend = ZcashRustBackend.self
@ -134,10 +138,12 @@ class BlockStreamingTest: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -155,7 +161,9 @@ class BlockStreamingTest: XCTestCase {
service: service,
storage: storage,
backend: realRustBackend,
config: processorConfig
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
let date = Date()

View File

@ -15,6 +15,7 @@ class CompactBlockProcessorTests: XCTestCase {
lazy var processorConfig = {
let pathProvider = DefaultResourceProvider(network: network)
return CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL,
spendParamsURL: pathProvider.spendParamsURL,
@ -46,9 +47,15 @@ class CompactBlockProcessorTests: XCTestCase {
logger = OSLogger(logLevel: .debug)
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
XCTestCase.wait { await InternalSyncProgress(storage: UserDefaults.standard).rewind(to: 0) }
XCTestCase.wait {
await InternalSyncProgress(
alias: .default,
storage: UserDefaults.standard,
logger: logger
).rewind(to: 0)
}
let liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
let service = MockLightWalletService(
latestBlockHeight: mockLatestHeight,
service: liveService
@ -71,10 +78,12 @@ class CompactBlockProcessorTests: XCTestCase {
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -83,7 +92,9 @@ class CompactBlockProcessorTests: XCTestCase {
service: service,
storage: storage,
backend: realRustBackend,
config: processorConfig
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
let dbInit = try realRustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: .testnet)
@ -185,7 +196,11 @@ class CompactBlockProcessorTests: XCTestCase {
latestDownloadedBlockHeight: latestDownloadedHeight
)
var internalSyncProgress = InternalSyncProgress(storage: InternalSyncProgressMemoryStorage())
var internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight)
var syncRanges = await internalSyncProgress.computeSyncRanges(
@ -214,7 +229,11 @@ class CompactBlockProcessorTests: XCTestCase {
latestDownloadedBlockHeight: latestDownloadedHeight
)
internalSyncProgress = InternalSyncProgress(storage: InternalSyncProgressMemoryStorage())
internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight)
syncRanges = await internalSyncProgress.computeSyncRanges(
@ -244,7 +263,11 @@ class CompactBlockProcessorTests: XCTestCase {
latestDownloadedBlockHeight: latestDownloadedHeight
)
internalSyncProgress = InternalSyncProgress(storage: InternalSyncProgressMemoryStorage())
internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight)
syncRanges = await internalSyncProgress.computeSyncRanges(

View File

@ -15,6 +15,7 @@ class CompactBlockReorgTests: XCTestCase {
lazy var processorConfig = {
let pathProvider = DefaultResourceProvider(network: network)
return CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL,
spendParamsURL: pathProvider.spendParamsURL,
@ -48,9 +49,15 @@ class CompactBlockReorgTests: XCTestCase {
logger = OSLogger(logLevel: .debug)
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
XCTestCase.wait { await InternalSyncProgress(storage: UserDefaults.standard).rewind(to: 0) }
XCTestCase.wait {
await InternalSyncProgress(
alias: .default,
storage: UserDefaults.standard,
logger: logger
).rewind(to: 0)
}
let liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
let service = MockLightWalletService(
latestBlockHeight: mockLatestHeight,
service: liveService
@ -74,10 +81,12 @@ class CompactBlockReorgTests: XCTestCase {
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try realCache.create()
@ -96,7 +105,9 @@ class CompactBlockReorgTests: XCTestCase {
service: service,
storage: realCache,
backend: mockBackend,
config: processorConfig
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
syncStartedExpect = XCTestExpectation(description: "\(self.description) syncStartedExpect")

View File

@ -33,7 +33,7 @@ class DownloadTests: XCTestCase {
}
func testSingleDownload() async throws {
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
let realRustBackend = ZcashRustBackend.self
@ -41,10 +41,12 @@ class DownloadTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -62,7 +64,9 @@ class DownloadTests: XCTestCase {
service: service,
storage: storage,
backend: realRustBackend,
config: processorConfig
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
do {

View File

@ -19,7 +19,7 @@ class LightWalletServiceTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
super.setUp()
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
}
override func tearDownWithError() throws {

View File

@ -31,7 +31,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let realRustBackend = ZcashRustBackend.self
@ -40,10 +40,12 @@ class BlockBatchValidationTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -51,6 +53,7 @@ class BlockBatchValidationTests: XCTestCase {
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: try! __dataDbURL(),
spendParamsURL: try! __spendParamsURL(),
@ -81,7 +84,9 @@ class BlockBatchValidationTests: XCTestCase {
service: service,
storage: storage,
backend: mockRust,
config: config
config: config,
metrics: SDKMetrics(),
logger: logger
)
do {
@ -101,7 +106,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let realRustBackend = ZcashRustBackend.self
@ -110,10 +115,12 @@ class BlockBatchValidationTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -121,6 +128,7 @@ class BlockBatchValidationTests: XCTestCase {
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: try! __dataDbURL(),
spendParamsURL: try! __spendParamsURL(),
@ -151,7 +159,9 @@ class BlockBatchValidationTests: XCTestCase {
service: service,
storage: storage,
backend: mockRust,
config: config
config: config,
metrics: SDKMetrics(),
logger: logger
)
do {
@ -171,7 +181,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .testnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let realRustBackend = ZcashRustBackend.self
@ -180,10 +190,12 @@ class BlockBatchValidationTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -191,6 +203,7 @@ class BlockBatchValidationTests: XCTestCase {
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: try! __dataDbURL(),
spendParamsURL: try! __spendParamsURL(),
@ -221,7 +234,9 @@ class BlockBatchValidationTests: XCTestCase {
service: service,
storage: storage,
backend: mockRust,
config: config
config: config,
metrics: SDKMetrics(),
logger: logger
)
do {
@ -241,7 +256,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let realRustBackend = ZcashRustBackend.self
@ -250,10 +265,12 @@ class BlockBatchValidationTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try storage.create()
@ -261,6 +278,7 @@ class BlockBatchValidationTests: XCTestCase {
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: try! __dataDbURL(),
spendParamsURL: try! __spendParamsURL(),
@ -292,7 +310,9 @@ class BlockBatchValidationTests: XCTestCase {
service: service,
storage: storage,
backend: mockRust,
config: config
config: config,
metrics: SDKMetrics(),
logger: logger
)
do {
@ -317,7 +337,7 @@ class BlockBatchValidationTests: XCTestCase {
let expectedLatestHeight = BlockHeight(1210000)
let service = MockLightWalletService(
latestBlockHeight: expectedLatestHeight,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let expectedStoredLatestHeight = BlockHeight(1220000)
let expectedResult = CompactBlockProcessor.NextState.wait(
@ -329,6 +349,7 @@ class BlockBatchValidationTests: XCTestCase {
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: try! __dataDbURL(),
spendParamsURL: try! __spendParamsURL(),
@ -372,7 +393,11 @@ class BlockBatchValidationTests: XCTestCase {
transactionRepository: transactionRepository,
config: config,
rustBackend: mockRust,
internalSyncProgress: InternalSyncProgress(storage: InternalSyncProgressMemoryStorage())
internalSyncProgress: InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
)
XCTAssertFalse(Task.isCancelled)
} catch {
@ -402,7 +427,7 @@ class BlockBatchValidationTests: XCTestCase {
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(
latestBlockHeight: expectedLatestHeight,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let expectedStoreLatestHeight = BlockHeight(1220000)
let walletBirthday = BlockHeight(1210000)
@ -421,6 +446,7 @@ class BlockBatchValidationTests: XCTestCase {
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: try! __dataDbURL(),
spendParamsURL: try! __spendParamsURL(),
@ -464,7 +490,11 @@ class BlockBatchValidationTests: XCTestCase {
transactionRepository: transactionRepository,
config: config,
rustBackend: mockRust,
internalSyncProgress: InternalSyncProgress(storage: InternalSyncProgressMemoryStorage())
internalSyncProgress: InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
)
XCTAssertFalse(Task.isCancelled)
} catch {
@ -494,7 +524,7 @@ class BlockBatchValidationTests: XCTestCase {
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(
latestBlockHeight: expectedLatestHeight,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
)
let expectedStoreLatestHeight = BlockHeight(1230000)
let walletBirthday = BlockHeight(1210000)
@ -502,6 +532,7 @@ class BlockBatchValidationTests: XCTestCase {
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloaderService = BlockDownloaderServiceImpl(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: try! __dataDbURL(),
spendParamsURL: try! __spendParamsURL(),
@ -516,7 +547,11 @@ class BlockBatchValidationTests: XCTestCase {
network: network
)
let internalSyncProgress = InternalSyncProgress(storage: InternalSyncProgressMemoryStorage())
let internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: InternalSyncProgressMemoryStorage(),
logger: logger
)
await internalSyncProgress.set(expectedStoreLatestHeight, .latestEnhancedHeight)
await internalSyncProgress.set(expectedStoreLatestHeight, .latestUTXOFetchedHeight)

View File

@ -33,6 +33,11 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
data = nil
}
func testAliasIsAsExpected() {
synchronizerMock.underlyingAlias = .custom("some_alias")
XCTAssertEqual(synchronizer.alias, .custom("some_alias"))
}
func testStateStreamEmitsAsExpected() {
let state = SynchronizerState(
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
@ -76,7 +81,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
}
func testEventStreamEmitsAsExpected() {
synchronizerMock.underlyingEventStream = Just(.connectionStateChanged).eraseToAnyPublisher()
synchronizerMock.underlyingEventStream = Just(.connectionStateChanged(.connecting)).eraseToAnyPublisher()
let expectation = XCTestExpectation()

View File

@ -31,6 +31,11 @@ class CombineSynchronizerOfflineTests: XCTestCase {
data = nil
}
func testAliasIsAsExpected() {
synchronizerMock.underlyingAlias = .custom("some_alias")
XCTAssertEqual(synchronizer.alias, .custom("some_alias"))
}
func testStateStreamEmitsAsExpected() {
let state = SynchronizerState(
shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)),
@ -74,7 +79,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
}
func testEventStreamEmitsAsExpected() {
synchronizerMock.underlyingEventStream = Just(.connectionStateChanged).eraseToAnyPublisher()
synchronizerMock.underlyingEventStream = Just(.connectionStateChanged(.connecting)).eraseToAnyPublisher()
let expectation = XCTestExpectation()

View File

@ -37,20 +37,29 @@ class CompactBlockProcessorOfflineTests: XCTestCase {
let service = MockLightWalletService(
latestBlockHeight: 690000,
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
)
let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
let processor = CompactBlockProcessor(service: service, storage: storage, backend: ZcashRustBackend.self, config: processorConfig)
let processor = CompactBlockProcessor(
service: service,
storage: storage,
backend: ZcashRustBackend.self,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
let fullRange = 0...1000

View File

@ -36,16 +36,17 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self
rustBackend: ZcashRustBackend.self,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try compactBlockRepository.create()
let latestHeight = await compactBlockRepository.latestHeight()
XCTAssertEqual(latestHeight, BlockHeight.empty())
}
@ -54,10 +55,12 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self
rustBackend: ZcashRustBackend.self,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try compactBlockRepository.create()
@ -79,10 +82,12 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self
rustBackend: ZcashRustBackend.self,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try compactBlockRepository.create()
@ -103,10 +108,12 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self
rustBackend: ZcashRustBackend.self,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try compactBlockRepository.create()

View File

@ -61,7 +61,8 @@ final class FsBlockStorageTests: XCTestCase {
describe: { _ in blockNameFixture },
compare: { _, _ in nil }
),
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try freshCache.create()
@ -82,7 +83,8 @@ final class FsBlockStorageTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: .mock,
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try freshCache.create()
@ -107,7 +109,8 @@ final class FsBlockStorageTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: .mock,
blockDescriptor: .live,
contentProvider: contentProvider
contentProvider: contentProvider,
logger: logger
)
try freshCache.create()
@ -167,9 +170,10 @@ final class FsBlockStorageTests: XCTestCase {
func testGetLatestHeight() async throws {
let freshCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self),
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self, logger: logger),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try freshCache.create()
@ -278,9 +282,10 @@ final class FsBlockStorageTests: XCTestCase {
func testClearTheCache() async throws {
let fsBlockCache = FSCompactBlockRepository(
fsBlockDbRoot: self.testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self),
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self, logger: logger),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.naive
contentProvider: DirectoryListingProviders.naive,
logger: logger
)
try fsBlockCache.create()
@ -305,7 +310,8 @@ final class FsBlockStorageTests: XCTestCase {
fsBlockDbRoot: testTempDirectory,
metadataStore: .mock,
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try freshCache.create()
@ -313,14 +319,15 @@ final class FsBlockStorageTests: XCTestCase {
XCTAssertNoThrow(try freshCache.create())
}
func testStoringTenSandblastedBlocks() async throws {
func disabled_testStoringTenSandblastedBlocks() async throws {
let realRustBackend = ZcashRustBackend.self
let realCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend),
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend, logger: logger),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try realCache.create()
@ -348,10 +355,11 @@ final class FsBlockStorageTests: XCTestCase {
let realCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend),
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend, logger: logger),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted,
fileWriter: FSBlockFileWriter(writeToURL: { _, _ in throw FixtureError.arbitraryError })
fileWriter: FSBlockFileWriter(writeToURL: { _, _ in throw FixtureError.arbitraryError }),
logger: logger
)
try realCache.create()
@ -374,9 +382,10 @@ final class FsBlockStorageTests: XCTestCase {
let realCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend),
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend, logger: logger),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try realCache.create()
@ -421,7 +430,12 @@ final class FsBlockStorageTests: XCTestCase {
MockRustBackend.writeBlocksMetadataResult = { false }
do {
try await FSMetadataStore.saveBlocksMeta(sandblastedBlocks, fsBlockDbRoot: testTempDirectory, rustBackend: MockRustBackend.self)
try await FSMetadataStore.saveBlocksMeta(
sandblastedBlocks,
fsBlockDbRoot: testTempDirectory,
rustBackend: MockRustBackend.self,
logger: logger
)
} catch CompactBlockRepositoryError.failedToWriteMetadata {
// this is fine
} catch {
@ -438,7 +452,12 @@ final class FsBlockStorageTests: XCTestCase {
MockRustBackend.writeBlocksMetadataResult = { throw RustWeldingError.genericError(message: "oops") }
do {
try await FSMetadataStore.saveBlocksMeta(sandblastedBlocks, fsBlockDbRoot: testTempDirectory, rustBackend: MockRustBackend.self)
try await FSMetadataStore.saveBlocksMeta(
sandblastedBlocks,
fsBlockDbRoot: testTempDirectory,
rustBackend: MockRustBackend.self,
logger: logger
)
} catch CompactBlockRepositoryError.failedToWriteMetadata {
// this is fine
} catch {
@ -453,7 +472,8 @@ final class FsBlockStorageTests: XCTestCase {
XCTAssertThrowsError(
try FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory,
rustBackend: MockRustBackend.self
rustBackend: MockRustBackend.self,
logger: logger
)
.rewindToHeight(expectedHeight)
) { error in
@ -476,9 +496,10 @@ final class FsBlockStorageTests: XCTestCase {
// NOTE: performance tests don't work with async code. Thanks Apple!
let freshCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self),
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self, logger: logger),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try freshCache.create()
@ -516,7 +537,8 @@ extension FSCompactBlockRepository {
contentProvider: SortedDirectoryContentProvider(
fileManager: FileManager.default,
sorting: { _, _ in false }
)
),
logger: OSLogger(logLevel: .debug)
)
}
}

View File

@ -0,0 +1,231 @@
//
// InitializerOfflineTests.swift
//
//
// Created by Michal Fousek on 24.03.2023.
//
import Foundation
@testable import TestUtils
import XCTest
@testable import ZcashLightClientKit
class InitializerOfflineTests: XCTestCase {
let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file")
let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory")
let invalidPathURL = URL(string: "https://whatever")!
// MARK: - Utils
private func makeInitializer(
fsBlockDbRoot: URL,
dataDbURL: URL,
pendingDbURL: URL,
spendParamsURL: URL,
outputParamsURL: URL,
alias: ZcashSynchronizerAlias
) -> Initializer {
return Initializer(
cacheDbURL: nil,
fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
saplingParamsSourceURL: .default,
alias: alias,
logLevel: .debug
)
}
private func update(url: URL, with alias: ZcashSynchronizerAlias) -> URL {
guard alias != .default else { return url }
let lastPathComponent = url.lastPathComponent
guard !lastPathComponent.isEmpty else { return url }
return url
.deletingLastPathComponent()
.appendingPathComponent("\(alias.description)_\(lastPathComponent)")
}
// MARK: - Tests
private func genericTestForURLsParsingFailures(
fsBlockDbRoot: URL,
dataDbURL: URL,
pendingDbURL: URL,
spendParamsURL: URL,
outputParamsURL: URL,
alias: ZcashSynchronizerAlias,
function: String = #function
) {
let initializer = makeInitializer(
fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
alias: alias
)
if let error = initializer.urlsParsingError, case let .cantUpdateURLWithAlias(failedURL) = error {
XCTAssertEqual(failedURL, invalidPathURL, "Failing \(function)")
} else {
XCTFail("URLs parsing error expected. Failing \(function)")
}
XCTAssertEqual(initializer.fsBlockDbRoot, fsBlockDbRoot, "Failing \(function)")
XCTAssertEqual(initializer.dataDbURL, dataDbURL, "Failing \(function)")
XCTAssertEqual(initializer.pendingDbURL, pendingDbURL, "Failing \(function)")
XCTAssertEqual(initializer.spendParamsURL, spendParamsURL, "Failing \(function)")
XCTAssertEqual(initializer.outputParamsURL, outputParamsURL, "Failing \(function)")
}
func test__defaultAlias__validURLs__updatedURLsAreBackwardsCompatible() {
let initializer = makeInitializer(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .default
)
XCTAssertNil(initializer.urlsParsingError)
XCTAssertEqual(initializer.fsBlockDbRoot, validDirectoryURL)
XCTAssertEqual(initializer.dataDbURL, validFileURL)
XCTAssertEqual(initializer.pendingDbURL, validFileURL)
XCTAssertEqual(initializer.spendParamsURL, validFileURL)
XCTAssertEqual(initializer.outputParamsURL, validFileURL)
}
func test__defaultAlias__invalidFsBlockDbRootURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: invalidPathURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .default
)
}
func test__defaultAlias__invalidDataDbURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: invalidPathURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .default
)
}
func test__defaultAlias__invalidPendingDbURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: invalidPathURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .default
)
}
func test__defaultAlias__invalidSpendParamsURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: invalidPathURL,
outputParamsURL: validFileURL,
alias: .default
)
}
func test__defaultAlias__invalidOutputParamsURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: invalidPathURL,
alias: .default
)
}
func test__customAlias__validURLs__updatedURLsAreAsExpected() {
let alias: ZcashSynchronizerAlias = .custom("alias")
let initializer = makeInitializer(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: alias
)
XCTAssertNil(initializer.urlsParsingError)
XCTAssertEqual(initializer.fsBlockDbRoot, update(url: validDirectoryURL, with: alias))
XCTAssertEqual(initializer.dataDbURL, update(url: validFileURL, with: alias))
XCTAssertEqual(initializer.pendingDbURL, update(url: validFileURL, with: alias))
XCTAssertEqual(initializer.spendParamsURL, update(url: validFileURL, with: alias))
XCTAssertEqual(initializer.outputParamsURL, update(url: validFileURL, with: alias))
}
func test__customAlias__invalidFsBlockDbRootURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: invalidPathURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .custom("alias")
)
}
func test__customAlias__invalidDataDbURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: invalidPathURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .custom("alias")
)
}
func test__customAlias__invalidPendingDbURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: invalidPathURL,
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
alias: .custom("alias")
)
}
func test__customAlias__invalidSpendParamsURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: invalidPathURL,
outputParamsURL: validFileURL,
alias: .custom("alias")
)
}
func test__customAlias__invalidOutputParamsURL__errorIsGenerated() {
genericTestForURLsParsingFailures(
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: validFileURL,
spendParamsURL: validFileURL,
outputParamsURL: invalidPathURL,
alias: .custom("alias")
)
}
}

View File

@ -10,13 +10,13 @@ import XCTest
@testable import ZcashLightClientKit
class InternalSyncProgressTests: XCTestCase {
var storage: InternalSyncProgressStorage!
var storage: InternalSyncProgressMemoryStorage!
var internalSyncProgress: InternalSyncProgress!
override func setUp() {
super.setUp()
storage = InternalSyncProgressMemoryStorage()
internalSyncProgress = InternalSyncProgress(storage: storage)
internalSyncProgress = InternalSyncProgress(alias: .default, storage: storage, logger: logger)
}
override func tearDownWithError() throws {
@ -129,4 +129,40 @@ class InternalSyncProgressTests: XCTestCase {
await internalSyncProgress.set(519000, .latestUTXOFetchedHeight)
XCTAssertEqual(storage.integer(forKey: "latestUTXOFetchedHeight"), 519000)
}
func test__whenUsingDefaultAliasKeysAreBackwardsCompatible() async {
await internalSyncProgress.set(630000, .latestDownloadedBlockHeight)
await internalSyncProgress.set(630000, .latestUTXOFetchedHeight)
await internalSyncProgress.set(630000, .latestEnhancedHeight)
XCTAssertEqual(storage.integer(forKey: InternalSyncProgress.Key.latestDownloadedBlockHeight.rawValue), 630000)
XCTAssertEqual(storage.integer(forKey: InternalSyncProgress.Key.latestUTXOFetchedHeight.rawValue), 630000)
XCTAssertEqual(storage.integer(forKey: InternalSyncProgress.Key.latestEnhancedHeight.rawValue), 630000)
}
func test__usingDifferentAliasesStoreValuesIndependently() async {
let internalSyncProgress1 = InternalSyncProgress(alias: .custom("alias1"), storage: storage, logger: logger)
await internalSyncProgress1.set(121000, .latestDownloadedBlockHeight)
await internalSyncProgress1.set(121000, .latestUTXOFetchedHeight)
await internalSyncProgress1.set(121000, .latestEnhancedHeight)
let internalSyncProgress2 = InternalSyncProgress(alias: .custom("alias2"), storage: storage, logger: logger)
await internalSyncProgress2.set(630000, .latestDownloadedBlockHeight)
await internalSyncProgress2.set(630000, .latestUTXOFetchedHeight)
await internalSyncProgress2.set(630000, .latestEnhancedHeight)
let latestDownloadedBlockHeight1 = await internalSyncProgress1.load(.latestDownloadedBlockHeight)
let latestUTXOFetchedHeigh1 = await internalSyncProgress1.load(.latestUTXOFetchedHeight)
let latestEnhancedHeight1 = await internalSyncProgress1.load(.latestEnhancedHeight)
XCTAssertEqual(latestDownloadedBlockHeight1, 121000)
XCTAssertEqual(latestUTXOFetchedHeigh1, 121000)
XCTAssertEqual(latestEnhancedHeight1, 121000)
let latestDownloadedBlockHeight2 = await internalSyncProgress2.load(.latestDownloadedBlockHeight)
let latestUTXOFetchedHeigh2 = await internalSyncProgress2.load(.latestUTXOFetchedHeight)
let latestEnhancedHeight2 = await internalSyncProgress2.load(.latestEnhancedHeight)
XCTAssertEqual(latestDownloadedBlockHeight2, 630000)
XCTAssertEqual(latestUTXOFetchedHeigh2, 630000)
XCTAssertEqual(latestEnhancedHeight2, 630000)
}
}

View File

@ -19,9 +19,9 @@ class PendingTransactionRepositoryTests: XCTestCase {
super.setUp()
cleanUpDb()
let pendingDbProvider = SimpleConnectionProvider(path: try! TestDbBuilder.pendingTransactionsDbURL().absoluteString)
let migrations = MigrationManager(pendingDbConnection: pendingDbProvider, networkType: .testnet)
let migrations = MigrationManager(pendingDbConnection: pendingDbProvider, networkType: .testnet, logger: logger)
try! migrations.performMigration()
pendingRepository = PendingTransactionSQLDAO(dbProvider: pendingDbProvider)
pendingRepository = PendingTransactionSQLDAO(dbProvider: pendingDbProvider, logger: logger)
}
override func tearDown() {

View File

@ -0,0 +1,322 @@
//
// SynchronizerOfflineTests.swift
//
//
// Created by Michal Fousek on 23.03.2023.
//
import Combine
import Foundation
@testable import TestUtils
import XCTest
@testable import ZcashLightClientKit
class SynchronizerOfflineTests: XCTestCase {
let data = AlternativeSynchronizerAPITestsData()
var network: ZcashNetwork!
var cancellables: [AnyCancellable] = []
override func setUp() async throws {
try await super.setUp()
network = ZcashNetworkBuilder.network(for: .testnet)
cancellables = []
}
override func tearDown() async throws {
try await super.tearDown()
network = nil
cancellables = []
}
func testCallPrepareWithAlreadyUsedAliasThrowsError() async throws {
let firstTestCoordinator = try await TestCoordinator(
alias: .custom("alias"),
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
let secondTestCoordinator = try await TestCoordinator(
alias: .custom("alias"),
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await firstTestCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Unpected fail. Prepare should succeed. \(error)")
}
do {
_ = try await secondTestCoordinator.prepare(seed: Environment.seedBytes)
XCTFail("Prepare should fail.")
} catch { }
}
func testWhenSynchronizerIsDeallocatedAliasIsntUsedAnymore() async throws {
var testCoordinator: TestCoordinator! = try await TestCoordinator(
alias: .default,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await testCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Unpected fail. Prepare should succeed. \(error)")
}
testCoordinator = try await TestCoordinator(
alias: .default,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await testCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Unpected fail. Prepare should succeed. \(error)")
}
}
func testCallWipeWithAlreadyUsedAliasThrowsError() async throws {
let firstTestCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false)
let secondTestCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false)
let firstWipeExpectation = XCTestExpectation(description: "First wipe expectation")
firstTestCoordinator.synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
firstWipeExpectation.fulfill()
case let .failure(error):
XCTFail("Unexpected error when calling wipe \(error)")
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
wait(for: [firstWipeExpectation], timeout: 1)
let secondWipeExpectation = XCTestExpectation(description: "Second wipe expectation")
secondTestCoordinator.synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
XCTFail("Second wipe should fail with error.")
case let .failure(error):
if let error = error as? InitializerError, case .aliasAlreadyInUse = error {
secondWipeExpectation.fulfill()
} else {
XCTFail("Wipe failed with unexpected error: \(error)")
}
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
wait(for: [secondWipeExpectation], timeout: 1)
}
func testPrepareCanBeCalledAfterWipeWithSameInstanceOfSDKSynchronizer() async throws {
let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false)
let expectation = XCTestExpectation(description: "Wipe expectation")
testCoordinator.synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
expectation.fulfill()
case let .failure(error):
XCTFail("Unexpected error when calling wipe \(error)")
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 1)
do {
_ = try await testCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Prepare after wipe should succeed.")
}
}
func testSendToAddressCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false)
do {
_ = try await testCoordinator.synchronizer.sendToAddress(
spendingKey: testCoordinator.spendingKey,
zatoshi: Zatoshi(1),
toAddress: .transparent(data.transparentAddress),
memo: nil
)
XCTFail("Send to address should fail.")
} catch {
if let error = error as? SynchronizerError, case .notPrepared = error {
} else {
XCTFail("Send to address failed with unexpected error: \(error)")
}
}
}
func testShieldFundsCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false)
do {
_ = try await testCoordinator.synchronizer.shieldFunds(
spendingKey: testCoordinator.spendingKey,
memo: Memo(string: "memo"),
shieldingThreshold: Zatoshi(1)
)
XCTFail("Shield funds should fail.")
} catch {
if let error = error as? SynchronizerError, case .notPrepared = error {
} else {
XCTFail("Shield funds failed with unexpected error: \(error)")
}
}
}
func testRefreshUTXOCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false)
do {
_ = try await testCoordinator.synchronizer.refreshUTXOs(address: data.transparentAddress, from: 1)
XCTFail("Shield funds should fail.")
} catch {
if let error = error as? SynchronizerError, case .notPrepared = error {
} else {
XCTFail("Shield funds failed with unexpected error: \(error)")
}
}
}
func testRewindCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(alias: .default, walletBirthday: 10, network: network, callPrepareInConstructor: false)
let expectation = XCTestExpectation()
testCoordinator.synchronizer.rewind(.quick)
.sink(
receiveCompletion: { result in
switch result {
case .finished:
XCTFail("Rewind should fail with error.")
case let .failure(error):
if let error = error as? SynchronizerError, case .notPrepared = error {
expectation.fulfill()
} else {
XCTFail("Rewind failed with unexpected error: \(error)")
}
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 1)
}
func testURLsParsingFailsInInitialierPrepareThenThrowsError() async throws {
let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file")
let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory")
let invalidPathURL = URL(string: "https://whatever")!
let initializer = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: invalidPathURL,
endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
saplingParamsSourceURL: .default,
alias: .default,
logLevel: .debug
)
XCTAssertNotNil(initializer.urlsParsingError)
let synchronizer = SDKSynchronizer(initializer: initializer)
do {
let derivationTool = DerivationTool(networkType: network.networkType)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(
seed: Environment.seedBytes,
accountIndex: 0
)
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
_ = try await synchronizer.prepare(with: Environment.seedBytes, viewingKeys: [viewingKey], walletBirthday: 123000)
XCTFail("Failure of prepare is expected.")
} catch {
if let error = error as? InitializerError, case let .cantUpdateURLWithAlias(failedURL) = error {
XCTAssertEqual(failedURL, invalidPathURL)
} else {
XCTFail("Failed with unexpected error: \(error)")
}
}
}
func testURLsParsingFailsInInitialierWipeThenThrowsError() async throws {
let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file")
let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory")
let invalidPathURL = URL(string: "https://whatever")!
let initializer = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: validDirectoryURL,
dataDbURL: validFileURL,
pendingDbURL: invalidPathURL,
endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
saplingParamsSourceURL: .default,
alias: .default,
logLevel: .debug
)
XCTAssertNotNil(initializer.urlsParsingError)
let synchronizer = SDKSynchronizer(initializer: initializer)
let expectation = XCTestExpectation()
synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
XCTFail("Failure of wipe is expected.")
case let .failure(error):
if let error = error as? InitializerError, case let .cantUpdateURLWithAlias(failedURL) = error {
XCTAssertEqual(failedURL, invalidPathURL)
expectation.fulfill()
} else {
XCTFail("Failed with unexpected error: \(error)")
}
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
wait(for: [expectation], timeout: 1)
}
}

View File

@ -45,6 +45,7 @@ class WalletTests: XCTestCase {
.map({ try derivationTool.deriveUnifiedFullViewingKey(from: $0) })
let wallet = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: self.testTempDirectory,
dataDbURL: try __dataDbURL(),
pendingDbURL: try TestDbBuilder.pendingTransactionsDbURL(),
@ -64,7 +65,7 @@ class WalletTests: XCTestCase {
} catch {
XCTFail("shouldn't fail here. Got error: \(error)")
}
// fileExists actually sucks, so attempting to delete the file and checking what happens is far better :)
XCTAssertNoThrow( try FileManager.default.removeItem(at: dbData!) )
}

View File

@ -93,6 +93,8 @@ class ZcashRustBackendTests: XCTestCase {
let tempDBs = TemporaryDbBuilder.build()
let seed = testVector[0].root_seed!
try? FileManager.default.removeItem(at: tempDBs.dataDB)
XCTAssertEqual(
try ZcashRustBackend.initDataDb(
dbData: tempDBs.dataDB,
@ -159,8 +161,6 @@ class ZcashRustBackendTests: XCTestCase {
expectedReceivers.sorted(),
actualReceivers.sorted()
)
try? FileManager.default.removeItem(at: tempDBs.dataDB)
}
func testGetMetadataFromAddress() throws {

View File

@ -10,9 +10,10 @@ import XCTest
final class SDKMetricsTests: XCTestCase {
func testPushDownloadBlocksReport() throws {
SDKMetrics.shared.enableMetrics()
let metrics = SDKMetrics()
metrics.enableMetrics()
SDKMetrics.shared.pushProgressReport(
metrics.pushProgressReport(
progress: BlockProgress(
startHeight: 1_730_000,
targetHeight: 1_730_099,
@ -24,35 +25,37 @@ final class SDKMetricsTests: XCTestCase {
operation: .downloadBlocks
)
XCTAssertTrue(SDKMetrics.shared.popBlock(operation: .downloadBlocks)?.count == 1)
XCTAssertTrue(metrics.popBlock(operation: .downloadBlocks)?.count == 1)
if let reports = SDKMetrics.shared.reports[.downloadBlocks], let report = reports.first {
if let reports = metrics.reports[.downloadBlocks], let report = reports.first {
XCTAssertEqual(report, SDKMetrics.BlockMetricReport.placeholderA)
} else {
XCTFail("Not expected to fail.")
}
SDKMetrics.shared.disableMetrics()
metrics.disableMetrics()
}
func testPopDownloadBlocksReport() throws {
SDKMetrics.shared.enableMetrics()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
let metrics = SDKMetrics()
metrics.enableMetrics()
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
if let reports = SDKMetrics.shared.popBlock(operation: .downloadBlocks), let report = reports.first {
if let reports = metrics.popBlock(operation: .downloadBlocks), let report = reports.first {
XCTAssertEqual(report, SDKMetrics.BlockMetricReport.placeholderA)
} else {
XCTFail("Not expected to fail.")
}
SDKMetrics.shared.disableMetrics()
metrics.disableMetrics()
}
func testCumulativeSummary() throws {
SDKMetrics.shared.enableMetrics()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
let metrics = SDKMetrics()
metrics.enableMetrics()
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
let summary = SDKMetrics.CumulativeSummary(
downloadedBlocksReport: SDKMetrics.ReportSummary(minTime: 1.0, maxTime: 1.0, avgTime: 1.0),
@ -63,24 +66,25 @@ final class SDKMetricsTests: XCTestCase {
totalSyncReport: nil
)
XCTAssertEqual(summary, SDKMetrics.shared.cumulativeSummary())
XCTAssertEqual(summary, metrics.cumulativeSummary())
SDKMetrics.shared.disableMetrics()
metrics.disableMetrics()
}
func testCumulateAndStartNewSet() throws {
SDKMetrics.shared.enableMetrics()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
SDKMetrics.shared.cumulateReportsAndStartNewSet()
let metrics = SDKMetrics()
metrics.enableMetrics()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
SDKMetrics.shared.cumulateReportsAndStartNewSet()
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
metrics.cumulateReportsAndStartNewSet()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
SDKMetrics.shared.cumulateReportsAndStartNewSet()
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
metrics.cumulateReportsAndStartNewSet()
XCTAssertTrue(SDKMetrics.shared.cumulativeSummaries.count == 3)
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
metrics.cumulateReportsAndStartNewSet()
XCTAssertTrue(metrics.cumulativeSummaries.count == 3)
let summary = SDKMetrics.CumulativeSummary(
downloadedBlocksReport: SDKMetrics.ReportSummary(minTime: 1.0, maxTime: 1.0, avgTime: 1.0),
@ -92,15 +96,16 @@ final class SDKMetricsTests: XCTestCase {
)
let summaries = [summary, summary, summary]
XCTAssertEqual(summaries, SDKMetrics.shared.cumulativeSummaries)
XCTAssertEqual(summaries, metrics.cumulativeSummaries)
SDKMetrics.shared.disableMetrics()
metrics.disableMetrics()
}
func testCumulativeSummaryMinMaxAvg() throws {
SDKMetrics.shared.enableMetrics()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA, SDKMetrics.BlockMetricReport.placeholderB]
let metrics = SDKMetrics()
metrics.enableMetrics()
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA, SDKMetrics.BlockMetricReport.placeholderB]
let summary = SDKMetrics.CumulativeSummary(
downloadedBlocksReport: SDKMetrics.ReportSummary(minTime: 1.0, maxTime: 6.0, avgTime: 3.5),
@ -111,19 +116,20 @@ final class SDKMetricsTests: XCTestCase {
totalSyncReport: nil
)
XCTAssertEqual(summary, SDKMetrics.shared.cumulativeSummary())
XCTAssertEqual(summary, metrics.cumulativeSummary())
SDKMetrics.shared.disableMetrics()
metrics.disableMetrics()
}
func testSummarizedCumulativeReports() throws {
SDKMetrics.shared.enableMetrics()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
SDKMetrics.shared.cumulateReportsAndStartNewSet()
let metrics = SDKMetrics()
metrics.enableMetrics()
SDKMetrics.shared.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderB]
SDKMetrics.shared.cumulateReportsAndStartNewSet()
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderA]
metrics.cumulateReportsAndStartNewSet()
metrics.reports[.downloadBlocks] = [SDKMetrics.BlockMetricReport.placeholderB]
metrics.cumulateReportsAndStartNewSet()
let summary = SDKMetrics.CumulativeSummary(
downloadedBlocksReport: SDKMetrics.ReportSummary(minTime: 1.0, maxTime: 6.0, avgTime: 3.5),
@ -134,9 +140,9 @@ final class SDKMetricsTests: XCTestCase {
totalSyncReport: nil
)
XCTAssertEqual(SDKMetrics.shared.summarizedCumulativeReports(), summary)
XCTAssertEqual(metrics.summarizedCumulativeReports(), summary)
SDKMetrics.shared.disableMetrics()
metrics.disableMetrics()
}
}

View File

@ -56,11 +56,11 @@ class SynchronizerTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let endpoint = LightWalletEndpoint(address: "lightwalletd.electriccoin.co", port: 9067, secure: true)
SDKMetrics.shared.enableMetrics()
var synchronizer: SDKSynchronizer?
for _ in 1...5 {
let databases = TemporaryDbBuilder.build()
let initializer = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: databases.fsCacheDbRoot,
dataDbURL: databases.dataDB,
pendingDbURL: databases.pendingDB,
@ -69,21 +69,29 @@ class SynchronizerTests: XCTestCase {
spendParamsURL: try __spendParamsURL(),
outputParamsURL: try __outputParamsURL(),
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
alias: "",
loggerProxy: OSLogger(logLevel: .debug)
alias: .default,
logLevel: .debug
)
try? FileManager.default.removeItem(at: databases.fsCacheDbRoot)
try? FileManager.default.removeItem(at: databases.dataDB)
try? FileManager.default.removeItem(at: databases.pendingDB)
let synchronizer = SDKSynchronizer(initializer: initializer)
synchronizer = SDKSynchronizer(initializer: initializer)
guard let synchronizer else { fatalError("Synchronizer not initialized.") }
synchronizer.metrics.enableMetrics()
_ = try await synchronizer.prepare(with: seedBytes, viewingKeys: [ufvk], walletBirthday: birthday)
let syncSyncedExpectation = XCTestExpectation(description: "synchronizerSynced Expectation")
sdkSynchronizerSyncStatusHandler.subscribe(to: synchronizer.stateStream, expectations: [.synced: syncSyncedExpectation])
let internalSyncProgress = InternalSyncProgress(storage: UserDefaults.standard)
let internalSyncProgress = InternalSyncProgress(
alias: .default,
storage: UserDefaults.standard,
logger: logger
)
await internalSyncProgress.rewind(to: birthday)
await (synchronizer.blockProcessor.service as? LightWalletGRPCService)?.latestBlockHeightProvider = MockLatestBlockHeightProvider(
birthday: self.birthday + 99
@ -93,10 +101,10 @@ class SynchronizerTests: XCTestCase {
wait(for: [syncSyncedExpectation], timeout: 100)
SDKMetrics.shared.cumulateReportsAndStartNewSet()
synchronizer.metrics.cumulateReportsAndStartNewSet()
}
if let cumulativeSummary = SDKMetrics.shared.summarizedCumulativeReports() {
if let cumulativeSummary = synchronizer?.metrics.summarizedCumulativeReports() {
let downloadedBlocksReport = cumulativeSummary.downloadedBlocksReport ?? .zero
let validatedBlocksReport = cumulativeSummary.validatedBlocksReport ?? .zero
let scannedBlocksReport = cumulativeSummary.scannedBlocksReport ?? .zero
@ -116,6 +124,6 @@ class SynchronizerTests: XCTestCase {
""")
}
SDKMetrics.shared.disableMetrics()
synchronizer?.metrics.disableMetrics()
}
}

View File

@ -38,13 +38,17 @@ enum DarksideDataset: String {
}
class DarksideWalletService: LightWalletService {
var connectionStateChange: ((ZcashLightClientKit.ConnectionState, ZcashLightClientKit.ConnectionState) -> Void)? {
get { service.connectionStateChange }
set { service.connectionStateChange = newValue }
}
var channel: Channel
var service: LightWalletService
var darksideService: DarksideStreamerClient
init(endpoint: LightWalletEndpoint) {
self.channel = ChannelProvider().channel()
self.service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
self.service = LightWalletServiceFactory(endpoint: endpoint).make()
self.darksideService = DarksideStreamerClient(channel: channel)
}

View File

@ -21,6 +21,10 @@ struct MockCancellable: CancellableCall {
}
class MockLightWalletService: LightWalletService {
var connectionStateChange: ((ZcashLightClientKit.ConnectionState, ZcashLightClientKit.ConnectionState) -> Void)? {
get { service.connectionStateChange }
set { service.connectionStateChange = newValue }
}
var mockLightDInfo: LightWalletdInfo?
var queue = DispatchQueue(label: "mock service queue")

View File

@ -0,0 +1,33 @@
//
// LoggerProxy.swift
//
//
// Created by Lukáš Korba on 27.03.2023.
//
import Foundation
import ZcashLightClientKit
var logger = OSLogger(logLevel: .debug)
enum LoggerProxy {
static func debug(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger.debug(message, file: file, function: function, line: line)
}
static func info(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger.info(message, file: file, function: function, line: line)
}
static func event(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger.event(message, file: file, function: function, line: line)
}
static func warn(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger.warn(message, file: file, function: function, line: line)
}
static func error(_ message: String, file: StaticString = #file, function: StaticString = #function, line: Int = #line) {
logger.error(message, file: file, function: function, line: line)
}
}

View File

@ -404,9 +404,14 @@ extension SaplingParamsSourceURL {
extension CompactBlockProcessor.Configuration {
/// Standard configuration for most compact block processors
static func standard(for network: ZcashNetwork, walletBirthday: BlockHeight) -> CompactBlockProcessor.Configuration {
static func standard(
alias: ZcashSynchronizerAlias = .default,
for network: ZcashNetwork,
walletBirthday: BlockHeight
) -> CompactBlockProcessor.Configuration {
let pathProvider = DefaultResourceProvider(network: network)
return CompactBlockProcessor.Configuration(
alias: alias,
fsBlockCacheRoot: pathProvider.fsCacheURL,
dataDb: pathProvider.dataDbURL,
spendParamsURL: pathProvider.spendParamsURL,

View File

@ -12,6 +12,9 @@ import Foundation
class SynchronizerMock: Synchronizer {
init() { }
var underlyingAlias: ZcashSynchronizerAlias! = nil
var alias: ZcashLightClientKit.ZcashSynchronizerAlias { underlyingAlias }
var underlyingStateStream: AnyPublisher<SynchronizerState, Never>! = nil
var stateStream: AnyPublisher<SynchronizerState, Never> { underlyingStateStream }
@ -24,6 +27,8 @@ class SynchronizerMock: Synchronizer {
var underlyingConnectionState: ConnectionState! = nil
var connectionState: ConnectionState { underlyingConnectionState }
let metrics = SDKMetrics()
var prepareWithSeedViewingKeysWalletBirthdayClosure: (
([UInt8]?, [UnifiedFullViewingKey], BlockHeight) async throws -> Initializer.InitializationResult
)! = nil

View File

@ -45,35 +45,51 @@ class TestCoordinator {
var databases: TemporaryTestDatabases
let network: ZcashNetwork
static func make(walletBirthday: BlockHeight, network: ZcashNetwork) -> TestCoordinator {
static func make(
alias: ZcashSynchronizerAlias = .default,
walletBirthday: BlockHeight,
network: ZcashNetwork,
callPrepareInConstructor: Bool = true
) -> TestCoordinator {
var coordinator: TestCoordinator!
XCTestCase.wait {
coordinator = try await TestCoordinator(walletBirthday: walletBirthday, network: network)
coordinator = try await TestCoordinator(
alias: alias,
walletBirthday: walletBirthday,
network: network,
callPrepareInConstructor: callPrepareInConstructor
)
}
return coordinator
}
static func make(
alias: ZcashSynchronizerAlias = .default,
spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight,
network: ZcashNetwork
network: ZcashNetwork,
callPrepareInConstructor: Bool = true
) -> TestCoordinator {
var coordinator: TestCoordinator!
XCTestCase.wait {
coordinator = try await TestCoordinator(
alias: alias,
spendingKey: spendingKey,
unifiedFullViewingKey: unifiedFullViewingKey,
walletBirthday: walletBirthday,
network: network
network: network,
callPrepareInConstructor: callPrepareInConstructor
)
}
return coordinator
}
convenience init(
alias: ZcashSynchronizerAlias = .default,
walletBirthday: BlockHeight,
network: ZcashNetwork
network: ZcashNetwork,
callPrepareInConstructor: Bool = true
) async throws {
let derivationTool = DerivationTool(networkType: network.networkType)
@ -85,20 +101,24 @@ class TestCoordinator {
let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
try await self.init(
alias: alias,
spendingKey: spendingKey,
unifiedFullViewingKey: ufvk,
walletBirthday: walletBirthday,
network: network
network: network,
callPrepareInConstructor: callPrepareInConstructor
)
}
required init(
alias: ZcashSynchronizerAlias = .default,
spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight,
network: ZcashNetwork
network: ZcashNetwork,
callPrepareInConstructor: Bool = true
) async throws {
await InternalSyncProgress(storage: UserDefaults.standard).rewind(to: 0)
await InternalSyncProgress(alias: alias, storage: UserDefaults.standard, logger: logger).rewind(to: 0)
self.spendingKey = spendingKey
self.viewingKey = unifiedFullViewingKey
@ -113,7 +133,7 @@ class TestCoordinator {
singleCallTimeoutInMillis: 10000,
streamingCallTimeoutInMillis: 1000000
)
let liveService = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let liveService = LightWalletServiceFactory(endpoint: endpoint).make()
self.service = DarksideWalletService(service: liveService)
let realRustBackend = ZcashRustBackend.self
@ -122,13 +142,16 @@ class TestCoordinator {
fsBlockDbRoot: self.databases.fsCacheDbRoot,
metadataStore: .live(
fsBlockDbRoot: self.databases.fsCacheDbRoot,
rustBackend: ZcashRustBackend.self
rustBackend: ZcashRustBackend.self,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
let synchronizer = TestSynchronizerBuilder.build(
alias: alias,
rustBackend: realRustBackend,
fsBlockDbRoot: databases.fsCacheDbRoot,
dataDbURL: databases.dataDB,
@ -138,19 +161,23 @@ class TestCoordinator {
repository: TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: databases.dataDB.absoluteString)),
accountRepository: AccountRepositoryBuilder.build(
dataDbURL: databases.dataDB,
readOnly: true
readOnly: true,
logger: logger
),
storage: storage,
spendParamsURL: try __spendParamsURL(),
outputParamsURL: try __outputParamsURL(),
network: network,
loggerProxy: OSLogger(logLevel: .debug)
logLevel: .debug
)
self.synchronizer = synchronizer
subscribeToState(synchronizer: self.synchronizer)
if case .seedRequired = try await prepare(seed: Environment.seedBytes) {
throw TestCoordinator.CoordinatorError.seedRequiredForMigration
if callPrepareInConstructor {
if case .seedRequired = try await prepare(seed: Environment.seedBytes) {
throw TestCoordinator.CoordinatorError.seedRequiredForMigration
}
}
}
@ -269,6 +296,7 @@ extension TestCoordinator {
let config = await self.synchronizer.blockProcessor.config
let newConfig = CompactBlockProcessor.Configuration(
alias: config.alias,
fsBlockCacheRoot: config.fsBlockCacheRoot,
dataDb: config.dataDb,
spendParamsURL: config.spendParamsURL,
@ -315,6 +343,7 @@ enum TemporaryDbBuilder {
enum TestSynchronizerBuilder {
static func build(
alias: ZcashSynchronizerAlias = .default,
rustBackend: ZcashRustBackendWelding.Type,
fsBlockDbRoot: URL,
dataDbURL: URL,
@ -327,9 +356,10 @@ enum TestSynchronizerBuilder {
spendParamsURL: URL,
outputParamsURL: URL,
network: ZcashNetwork,
loggerProxy: Logger? = nil
logLevel: OSLogger.LogLevel
) -> SDKSynchronizer {
let initializer = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: fsBlockDbRoot,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
@ -338,8 +368,8 @@ enum TestSynchronizerBuilder {
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
alias: "",
loggerProxy: loggerProxy
alias: alias,
logLevel: logLevel
)
return SDKSynchronizer(initializer: initializer)