[#888] Make actor from ZcashRustBackendWelding

Closes #888.

- `ZcashRustBackend` is actor now. So majority of methods in this actor
  are now async.
- Some methods stayed `static` in `ZcashRustBackend`. It would be hard
  to pass instance of the `ZcashRustBackend` to the places where these
  methods are used in static manner. And it would change lot of APIs.
  But it isn't problem from technical perspective because these methods
  would be `nonisolated` otherwise.
- Methods `lastError()` and `getLastError()` in `ZcashRustBackend` are
  now private. This makes sure that ther won't be aby race condition
  between other methods and these two error methods.
- All the methods for which was `lastError()` used in code now throw
  error. So `lastError()` is no longer needed outside of the
  `ZcashRustBackend`.
- There are in the public API related to `DerivationTool`.
- `DerivationTool` now requires instance of the `ZcashRustBackend`. And
  `ZcashRustBackend` isn't public type. So `DerivationTool` doesn't have
  any public constructor now. It can be created only via
  `Initializer.makeDerivationTool()` instance method.
- `deriveUnifiedSpendingKey()` and `deriveUnifiedFullViewingKey()` in
  `DerivationTool` are now async. It is because these are using
  `ZcashRustBackend` inside. `DerivationTool` offers alternative
  (closure and combine) APIs. But downside is that there is no sync API
  to dervie spending key or viewing key.
- Some methods of the `DerivationTool` are now static. These methods
  don't use anything that requires instance of the `DerivationTool`
  inside.

[#888] Use Sourcery to generate mocks

- I wrote mock for `Synchronizer` manually. And it's tedious and long
  and boring work.
- Now `ZcashRustBackendWelding` is changed a lot so it means
  `MockRustBackend` must be changed a lot. So I decided to introduce
  `sourcery` to generate mocks from protocols so we don't have to do it
  manually ever.
- To generate mocks go to `ZcashLightClientKit/Tests/TestUtils/Sourcery`
  directory and run `generateMocks.sh` script.
- Your protocol must be mentioned in `AutoMockable.swift` file.
  Generated mocks are in `AutoMockable.generated.swift` file.

[#888] Fix Offline tests

- Offline tests target now runs and tests are green.
- There is log of changes in tests. But logic is not changed.
- Updated `AutoMockable.stencil` so sourcery is able to generate mock as
  actor when protocol is marked with: `// sourcery: mockActor`.
- Last few updates in `ZcashRustBackendWelding`. In previous PR `rewindCacheToHeight`
  methods was overlooked and it didn't throw error.
- Removed `MockRustBackend` and using generated
  `ZCashRustBackendWeldingMock` instead.
- Using generated `SynchronizerMock`.

[#888] Fix NetworkTests

- Changed a bit how rust backend mock is used in the tests. Introduced
  `RustBackendMockHelper`. There are some state variables that must be
  preserved within one instance of the mock. This helper does exactly
  this. It keeps this state variables in the memory and helping mock to
  work as expected.

[#888] Fix Darkside tests

Create ZcashKeyDeriving internal protocol

Use New DerivationTool that does not require RustBackend

Remove duplicated methods that had been copied over

[#888] Fix potentially broken tests

I broke the tests because I moved `testTempDirectory` from each
`TestCase` to the `Environment`. By this I caused that each tests uses
exactly same URL. Which is directly against purpose of
`testTempDirectory`.

So now each test calls this one and store it to local variable. So each
test has unique URL.

[#888] Add ability to mock nonisolated methods to AutoMockable.stencil

[#888] Add changelog and fix the documentation in ZcashRustBackendWelding

[#888] Rename derivation rust backend protocol and remove static methods

- Renamed `ZcashKeyDeriving` to `ZcashKeyDerivationBackendWelding`. So
  the naming scheme is same as for `ZcashRustBackendWelding`.
- `ZcashKeyDerivationBackend` is now struct instead of enum.
- Methods in `ZcashKeyDerivationBackendWelding` (except one) are no
  longer static. Because of this the respective methods in
  `DerivationTool` aren't also static anymore.
This commit is contained in:
Michal Fousek 2023-03-31 19:10:35 +02:00
parent 6b7fbdd908
commit 2bbc5800bd
72 changed files with 3122 additions and 2197 deletions

1
.gitignore vendored
View File

@ -76,7 +76,6 @@ Pods
# do not commit generated libraries to this repo # do not commit generated libraries to this repo
lib lib
*.a *.a
*.generated.swift
env-vars.sh env-vars.sh
.vscode/ .vscode/

View File

@ -15,6 +15,7 @@ excluded:
- ZcashLightClientKitTests/Constants.generated.swift - ZcashLightClientKitTests/Constants.generated.swift
- build/ - build/
- docs/ - docs/
- Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift
disabled_rules: disabled_rules:
- notification_center_detachment - notification_center_detachment

View File

@ -11,6 +11,7 @@ excluded:
- ZcashLightClientKitTests/Constants.generated.swift - ZcashLightClientKitTests/Constants.generated.swift
- build/ - build/
- docs/ - docs/
- Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift
disabled_rules: disabled_rules:
- notification_center_detachment - notification_center_detachment

View File

@ -1,4 +1,12 @@
# unreleased # unreleased
### [#888] Updates to layer between Swift and Rust
This is mostly internal change. But it also touches the public API.
`KeyDeriving` protocol is changed. And therefore `DerivationTool` is changed. `deriveUnifiedSpendingKey(seed:accountIndex:)` and
`deriveUnifiedFullViewingKey(from:)` methods are now async. `DerivationTool` offers alternatives for these methods. Alternatives are using either
closures or Combine.
### [#469] ZcashRustBackendWelding to Async ### [#469] ZcashRustBackendWelding to Async
This is mostly internal change. But it also touches the public API. This is mostly internal change. But it also touches the public API.

View File

@ -104,6 +104,20 @@
ReferencedContainer = "container:../.."> ReferencedContainer = "container:../..">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0DA1C4A627D11B2900E5006E"
BuildableName = "ZcashLightClientSampleTests.xctest"
BlueprintName = "ZcashLightClientSampleTests"
ReferencedContainer = "container:ZcashLightClientSample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction

View File

@ -32,9 +32,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
} }
var sharedViewingKey: UnifiedFullViewingKey { var sharedViewingKey: UnifiedFullViewingKey {
return try! DerivationTool(networkType: kZcashNetwork.networkType) get async {
.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0) let derivationTool = sharedWallet.makeDerivationTool()
.deriveFullViewingKey() let spendingKey = try! await derivationTool
.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0)
return try! await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
}
} }
var sharedWallet: Initializer { var sharedWallet: Initializer {

View File

@ -39,24 +39,26 @@ class GetUTXOsViewController: UIViewController {
} }
@IBAction func shieldFunds(_ sender: Any) { @IBAction func shieldFunds(_ sender: Any) {
do { Task { @MainActor in
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType) do {
let derivationTool = AppDelegate.shared.sharedWallet.makeDerivationTool()
let usk = try derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0) let usk = try await derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0)
KRProgressHUD.showMessage("🛡 Shielding 🛡") KRProgressHUD.showMessage("🛡 Shielding 🛡")
Task { @MainActor in Task { @MainActor in
let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds( let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds(
spendingKey: usk, spendingKey: usk,
memo: try Memo(string: "shielding is fun!"), memo: try Memo(string: "shielding is fun!"),
shieldingThreshold: Zatoshi(10000) shieldingThreshold: Zatoshi(10000)
) )
KRProgressHUD.dismiss() KRProgressHUD.dismiss()
self.messageLabel.text = "funds shielded \(transaction)" self.messageLabel.text = "funds shielded \(transaction)"
}
} catch {
self.messageLabel.text = "Shielding failed \(error)"
} }
} catch {
self.messageLabel.text = "Shielding failed \(error)"
} }
} }
} }

View File

@ -42,13 +42,15 @@ class SendViewController: UIViewController {
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:))) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:)))
self.view.addGestureRecognizer(tapRecognizer) self.view.addGestureRecognizer(tapRecognizer)
setUp() setUp()
closureSynchronizer.prepare( Task { @MainActor in
with: DemoAppConfig.defaultSeed, closureSynchronizer.prepare(
viewingKeys: [AppDelegate.shared.sharedViewingKey], with: DemoAppConfig.defaultSeed,
walletBirthday: DemoAppConfig.defaultBirthdayHeight viewingKeys: [await AppDelegate.shared.sharedViewingKey],
) { result in walletBirthday: DemoAppConfig.defaultBirthdayHeight
loggerProxy.debug("Prepare result: \(result)") ) { result in
loggerProxy.debug("Prepare result: \(result)")
}
} }
} }
@ -216,14 +218,8 @@ class SendViewController: UIViewController {
return return
} }
guard let spendingKey = try? DerivationTool( let derivationTool = AppDelegate.shared.sharedWallet.makeDerivationTool()
networkType: kZcashNetwork.networkType guard let spendingKey = try? await derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0) else {
)
.deriveUnifiedSpendingKey(
seed: DemoAppConfig.defaultSeed,
accountIndex: 0
)
else {
loggerProxy.error("NO SPENDING KEY") loggerProxy.error("NO SPENDING KEY")
return return
} }

View File

@ -58,33 +58,35 @@ class SyncBlocksListViewController: UIViewController {
} }
private func didTapOnButton(index: Int) async { private func didTapOnButton(index: Int) async {
let synchronizerData = synchronizerData[index] Task { @MainActor in
let synchronizer = synchronizers[index] let synchronizerData = synchronizerData[index]
let syncStatus = synchronizer.latestState.syncStatus let synchronizer = synchronizers[index]
let syncStatus = synchronizer.latestState.syncStatus
loggerProxy.debug("Processing synchronizer with alias \(synchronizer.alias.description) \(index)") loggerProxy.debug("Processing synchronizer with alias \(synchronizer.alias.description) \(index)")
switch syncStatus { switch syncStatus {
case .stopped, .unprepared, .synced, .disconnected, .error: case .stopped, .unprepared, .synced, .disconnected, .error:
do { do {
if syncStatus == .unprepared { if syncStatus == .unprepared {
let viewingKey = try! DerivationTool(networkType: kZcashNetwork.networkType) let derivationTool = AppDelegate.shared.sharedWallet.makeDerivationTool()
.deriveUnifiedSpendingKey(seed: synchronizerData.seed, accountIndex: 0) let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: synchronizerData.seed, accountIndex: 0)
.deriveFullViewingKey() let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
_ = try! await synchronizer.prepare( _ = try! await synchronizer.prepare(
with: synchronizerData.seed, with: synchronizerData.seed,
viewingKeys: [viewingKey], viewingKeys: [viewingKey],
walletBirthday: synchronizerData.birthday walletBirthday: synchronizerData.birthday
) )
}
try await synchronizer.start(retry: false)
} catch {
loggerProxy.error("Can't start synchronizer: \(error)")
} }
case .syncing, .enhancing, .fetching:
try await synchronizer.start(retry: false) await synchronizer.stop()
} catch {
loggerProxy.error("Can't start synchronizer: \(error)")
} }
case .syncing, .enhancing, .fetching:
await synchronizer.stop()
} }
} }

View File

@ -39,7 +39,9 @@ let package = Package(
dependencies: ["ZcashLightClientKit"], dependencies: ["ZcashLightClientKit"],
path: "Tests/TestUtils", path: "Tests/TestUtils",
exclude: [ exclude: [
"proto/darkside.proto" "proto/darkside.proto",
"Sourcery/AutoMockable.stencil",
"Sourcery/generateMocks"
], ],
resources: [ resources: [
.copy("Resources/test_data.db"), .copy("Resources/test_data.db"),

View File

@ -316,7 +316,7 @@ actor CompactBlockProcessor {
var storage: CompactBlockRepository var storage: CompactBlockRepository
var transactionRepository: TransactionRepository var transactionRepository: TransactionRepository
var accountRepository: AccountRepository var accountRepository: AccountRepository
var rustBackend: ZcashRustBackendWelding.Type var rustBackend: ZcashRustBackendWelding
private var retryAttempts: Int = 0 private var retryAttempts: Int = 0
private var backoffTimer: Timer? private var backoffTimer: Timer?
private var lastChainValidationFailure: BlockHeight? private var lastChainValidationFailure: BlockHeight?
@ -344,7 +344,7 @@ actor CompactBlockProcessor {
init( init(
service: LightWalletService, service: LightWalletService,
storage: CompactBlockRepository, storage: CompactBlockRepository,
backend: ZcashRustBackendWelding.Type, rustBackend: ZcashRustBackendWelding,
config: Configuration, config: Configuration,
metrics: SDKMetrics, metrics: SDKMetrics,
logger: Logger logger: Logger
@ -352,11 +352,9 @@ actor CompactBlockProcessor {
self.init( self.init(
service: service, service: service,
storage: storage, storage: storage,
backend: backend, rustBackend: rustBackend,
config: config, config: config,
repository: TransactionRepositoryBuilder.build( repository: TransactionRepositoryBuilder.build(dataDbURL: config.dataDb),
dataDbURL: config.dataDb
),
accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: logger), accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: logger),
metrics: metrics, metrics: metrics,
logger: logger logger: logger
@ -375,7 +373,7 @@ actor CompactBlockProcessor {
self.init( self.init(
service: initializer.lightWalletService, service: initializer.lightWalletService,
storage: initializer.storage, storage: initializer.storage,
backend: initializer.rustBackend, rustBackend: initializer.rustBackend,
config: Configuration( config: Configuration(
alias: initializer.alias, alias: initializer.alias,
fsBlockCacheRoot: initializer.fsBlockDbRoot, fsBlockCacheRoot: initializer.fsBlockDbRoot,
@ -396,7 +394,7 @@ actor CompactBlockProcessor {
internal init( internal init(
service: LightWalletService, service: LightWalletService,
storage: CompactBlockRepository, storage: CompactBlockRepository,
backend: ZcashRustBackendWelding.Type, rustBackend: ZcashRustBackendWelding,
config: Configuration, config: Configuration,
repository: TransactionRepository, repository: TransactionRepository,
accountRepository: AccountRepository, accountRepository: AccountRepository,
@ -420,73 +418,57 @@ actor CompactBlockProcessor {
self.blockDownloaderService = blockDownloaderService self.blockDownloaderService = blockDownloaderService
self.blockDownloader = blockDownloader self.blockDownloader = blockDownloader
let blockValidatorConfig = BlockValidatorConfig(
fsBlockCacheRoot: config.fsBlockCacheRoot,
dataDB: config.dataDb,
networkType: config.network.networkType
)
self.blockValidator = BlockValidatorImpl( self.blockValidator = BlockValidatorImpl(
config: blockValidatorConfig, rustBackend: rustBackend,
rustBackend: backend,
metrics: metrics, metrics: metrics,
logger: logger logger: logger
) )
let blockScannerConfig = BlockScannerConfig( let blockScannerConfig = BlockScannerConfig(
fsBlockCacheRoot: config.fsBlockCacheRoot,
dataDB: config.dataDb,
networkType: config.network.networkType, networkType: config.network.networkType,
scanningBatchSize: config.scanningBatchSize scanningBatchSize: config.scanningBatchSize
) )
self.blockScanner = BlockScannerImpl( self.blockScanner = BlockScannerImpl(
config: blockScannerConfig, config: blockScannerConfig,
rustBackend: backend, rustBackend: rustBackend,
transactionRepository: repository, transactionRepository: repository,
metrics: metrics, metrics: metrics,
logger: logger logger: logger
) )
let blockEnhancerConfig = BlockEnhancerConfig(dataDb: config.dataDb, networkType: config.network.networkType)
self.blockEnhancer = BlockEnhancerImpl( self.blockEnhancer = BlockEnhancerImpl(
blockDownloaderService: blockDownloaderService, blockDownloaderService: blockDownloaderService,
config: blockEnhancerConfig,
internalSyncProgress: internalSyncProgress, internalSyncProgress: internalSyncProgress,
rustBackend: backend, rustBackend: rustBackend,
transactionRepository: repository, transactionRepository: repository,
metrics: metrics, metrics: metrics,
logger: logger logger: logger
) )
let utxoFetcherConfig = UTXOFetcherConfig( let utxoFetcherConfig = UTXOFetcherConfig(walletBirthdayProvider: config.walletBirthdayProvider)
dataDb: config.dataDb,
networkType: config.network.networkType,
walletBirthdayProvider: config.walletBirthdayProvider
)
self.utxoFetcher = UTXOFetcherImpl( self.utxoFetcher = UTXOFetcherImpl(
accountRepository: accountRepository, accountRepository: accountRepository,
blockDownloaderService: blockDownloaderService, blockDownloaderService: blockDownloaderService,
config: utxoFetcherConfig, config: utxoFetcherConfig,
internalSyncProgress: internalSyncProgress, internalSyncProgress: internalSyncProgress,
rustBackend: backend, rustBackend: rustBackend,
metrics: metrics, metrics: metrics,
logger: logger logger: logger
) )
let saplingParametersHandlerConfig = SaplingParametersHandlerConfig( let saplingParametersHandlerConfig = SaplingParametersHandlerConfig(
dataDb: config.dataDb,
networkType: config.network.networkType,
outputParamsURL: config.outputParamsURL, outputParamsURL: config.outputParamsURL,
spendParamsURL: config.spendParamsURL, spendParamsURL: config.spendParamsURL,
saplingParamsSourceURL: config.saplingParamsSourceURL saplingParamsSourceURL: config.saplingParamsSourceURL
) )
self.saplingParametersHandler = SaplingParametersHandlerImpl( self.saplingParametersHandler = SaplingParametersHandlerImpl(
config: saplingParametersHandlerConfig, config: saplingParametersHandlerConfig,
rustBackend: backend, rustBackend: rustBackend,
logger: logger logger: logger
) )
self.service = service self.service = service
self.rustBackend = backend self.rustBackend = rustBackend
self.storage = storage self.storage = storage
self.config = config self.config = config
self.transactionRepository = repository self.transactionRepository = repository
@ -517,8 +499,8 @@ actor CompactBlockProcessor {
_ info: LightWalletdInfo, _ info: LightWalletdInfo,
saplingActivation: BlockHeight, saplingActivation: BlockHeight,
localNetwork: ZcashNetwork, localNetwork: ZcashNetwork,
rustBackend: ZcashRustBackendWelding.Type rustBackend: ZcashRustBackendWelding
) throws { ) async throws {
// check network types // check network types
guard let remoteNetworkType = NetworkType.forChainName(info.chainName) else { guard let remoteNetworkType = NetworkType.forChainName(info.chainName) else {
throw CompactBlockProcessorError.generalError( throw CompactBlockProcessorError.generalError(
@ -536,7 +518,7 @@ actor CompactBlockProcessor {
} }
// check branch id // check branch id
let localBranch = try rustBackend.consensusBranchIdFor(height: Int32(info.blockHeight), networkType: localNetwork.networkType) let localBranch = try rustBackend.consensusBranchIdFor(height: Int32(info.blockHeight))
guard let remoteBranchID = ConsensusBranchID.fromString(info.consensusBranchID) else { guard let remoteBranchID = ConsensusBranchID.fromString(info.consensusBranchID) else {
throw CompactBlockProcessorError.generalError(message: "Consensus BranchIDs don't match this is probably an API or programming error") throw CompactBlockProcessorError.generalError(message: "Consensus BranchIDs don't match this is probably an API or programming error")
@ -631,24 +613,21 @@ actor CompactBlockProcessor {
logger.debug("Executing rewind.") logger.debug("Executing rewind.")
let lastDownloaded = await internalSyncProgress.latestDownloadedBlockHeight let lastDownloaded = await internalSyncProgress.latestDownloadedBlockHeight
let height = Int32(context.height ?? lastDownloaded) let height = Int32(context.height ?? lastDownloaded)
let nearestHeight = await rustBackend.getNearestRewindHeight(
dbData: config.dataDb,
height: height,
networkType: self.config.network.networkType
)
guard nearestHeight > 0 else { let nearestHeight: Int32
let error = rustBackend.lastError() ?? RustWeldingError.genericError( do {
message: "unknown error getting nearest rewind height for height: \(height)" nearestHeight = try await rustBackend.getNearestRewindHeight(height: height)
) } catch {
await fail(error) await fail(error)
return await context.completion(.failure(error)) return await context.completion(.failure(error))
} }
// FIXME: [#719] this should be done on the rust layer, https://github.com/zcash/ZcashLightClientKit/issues/719 // FIXME: [#719] this should be done on the rust layer, https://github.com/zcash/ZcashLightClientKit/issues/719
let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday)) let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday))
guard await rustBackend.rewindToHeight(dbData: config.dataDb, height: rewindHeight, networkType: self.config.network.networkType) else {
let error = rustBackend.lastError() ?? RustWeldingError.genericError(message: "unknown error rewinding to height \(height)") do {
try await rustBackend.rewindToHeight(height: rewindHeight)
} catch {
await fail(error) await fail(error)
return await context.completion(.failure(error)) return await context.completion(.failure(error))
} }
@ -715,7 +694,7 @@ actor CompactBlockProcessor {
func validateServer() async { func validateServer() async {
do { do {
let info = try await self.service.getInfo() let info = try await self.service.getInfo()
try Self.validateServerInfo( try await Self.validateServerInfo(
info, info,
saplingActivation: self.config.saplingActivation, saplingActivation: self.config.saplingActivation,
localNetwork: self.config.network, localNetwork: self.config.network,
@ -1055,14 +1034,10 @@ actor CompactBlockProcessor {
self.consecutiveChainValidationErrors += 1 self.consecutiveChainValidationErrors += 1
let rewindResult = await rustBackend.rewindToHeight( do {
dbData: config.dataDb, try await rustBackend.rewindToHeight(height: Int32(rewindHeight))
height: Int32(rewindHeight), } catch {
networkType: self.config.network.networkType await fail(error)
)
guard rewindResult else {
await fail(rustBackend.lastError() ?? RustWeldingError.genericError(message: "unknown error rewinding to height \(height)"))
return return
} }
@ -1205,11 +1180,7 @@ extension CompactBlockProcessor.State: Equatable {
extension CompactBlockProcessor { extension CompactBlockProcessor {
func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress { func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress {
try await rustBackend.getCurrentAddress( try await rustBackend.getCurrentAddress(account: Int32(accountIndex))
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType
)
} }
func getSaplingAddress(accountIndex: Int) async throws -> SaplingAddress { func getSaplingAddress(accountIndex: Int) async throws -> SaplingAddress {
@ -1227,18 +1198,10 @@ extension CompactBlockProcessor {
return WalletBalance( return WalletBalance(
verified: Zatoshi( verified: Zatoshi(
try await rustBackend.getVerifiedTransparentBalance( try await rustBackend.getVerifiedTransparentBalance(account: Int32(accountIndex))
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType
)
), ),
total: Zatoshi( total: Zatoshi(
try await rustBackend.getTransparentBalance( try await rustBackend.getTransparentBalance(account: Int32(accountIndex))
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType
)
) )
) )
} }
@ -1269,19 +1232,15 @@ extension CompactBlockProcessor {
var skipped: [UnspentTransactionOutputEntity] = [] var skipped: [UnspentTransactionOutputEntity] = []
for utxo in utxos { for utxo in utxos {
do { do {
if try await rustBackend.putUnspentTransparentOutput( try await rustBackend.putUnspentTransparentOutput(
dbData: dataDb,
txid: utxo.txid.bytes, txid: utxo.txid.bytes,
index: utxo.index, index: utxo.index,
script: utxo.script.bytes, script: utxo.script.bytes,
value: Int64(utxo.valueZat), value: Int64(utxo.valueZat),
height: utxo.height, height: utxo.height
networkType: self.config.network.networkType )
) {
refreshed.append(utxo) refreshed.append(utxo)
} else {
skipped.append(utxo)
}
} catch { } catch {
logger.info("failed to put utxo - error: \(error)") logger.info("failed to put utxo - error: \(error)")
skipped.append(utxo) skipped.append(utxo)
@ -1389,7 +1348,7 @@ extension CompactBlockProcessor {
downloaderService: BlockDownloaderService, downloaderService: BlockDownloaderService,
transactionRepository: TransactionRepository, transactionRepository: TransactionRepository,
config: Configuration, config: Configuration,
rustBackend: ZcashRustBackendWelding.Type, rustBackend: ZcashRustBackendWelding,
internalSyncProgress: InternalSyncProgress internalSyncProgress: InternalSyncProgress
) async throws -> CompactBlockProcessor.NextState { ) async throws -> CompactBlockProcessor.NextState {
// It should be ok to not create new Task here because this method is already async. But for some reason something not good happens // It should be ok to not create new Task here because this method is already async. But for some reason something not good happens
@ -1397,7 +1356,7 @@ extension CompactBlockProcessor {
let task = Task(priority: .userInitiated) { let task = Task(priority: .userInitiated) {
let info = try await service.getInfo() let info = try await service.getInfo()
try CompactBlockProcessor.validateServerInfo( try await CompactBlockProcessor.validateServerInfo(
info, info,
saplingActivation: config.saplingActivation, saplingActivation: config.saplingActivation,
localNetwork: config.network, localNetwork: config.network,

View File

@ -14,20 +14,14 @@ enum BlockEnhancerError: Error {
case txIdNotFound(txId: Data) case txIdNotFound(txId: Data)
} }
struct BlockEnhancerConfig {
let dataDb: URL
let networkType: NetworkType
}
protocol BlockEnhancer { protocol BlockEnhancer {
func enhance(at range: CompactBlockRange, didEnhance: (EnhancementProgress) async -> Void) async throws -> [ZcashTransaction.Overview] func enhance(at range: CompactBlockRange, didEnhance: (EnhancementProgress) async -> Void) async throws -> [ZcashTransaction.Overview]
} }
struct BlockEnhancerImpl { struct BlockEnhancerImpl {
let blockDownloaderService: BlockDownloaderService let blockDownloaderService: BlockDownloaderService
let config: BlockEnhancerConfig
let internalSyncProgress: InternalSyncProgress let internalSyncProgress: InternalSyncProgress
let rustBackend: ZcashRustBackendWelding.Type let rustBackend: ZcashRustBackendWelding
let transactionRepository: TransactionRepository let transactionRepository: TransactionRepository
let metrics: SDKMetrics let metrics: SDKMetrics
let logger: Logger let logger: Logger
@ -41,17 +35,13 @@ struct BlockEnhancerImpl {
let block = String(describing: transaction.minedHeight) let block = String(describing: transaction.minedHeight)
logger.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)") logger.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
let decryptionResult = await rustBackend.decryptAndStoreTransaction( do {
dbData: config.dataDb, try await rustBackend.decryptAndStoreTransaction(
txBytes: fetchedTransaction.raw.bytes, txBytes: fetchedTransaction.raw.bytes,
minedHeight: Int32(fetchedTransaction.minedHeight), minedHeight: Int32(fetchedTransaction.minedHeight)
networkType: config.networkType
)
guard decryptionResult else {
throw BlockEnhancerError.decryptError(
error: rustBackend.lastError() ?? .genericError(message: "`decryptAndStoreTransaction` failed. No message available")
) )
} catch {
throw BlockEnhancerError.decryptError(error: error)
} }
let confirmedTx: ZcashTransaction.Overview let confirmedTx: ZcashTransaction.Overview

View File

@ -13,8 +13,6 @@ enum UTXOFetcherError: Error {
} }
struct UTXOFetcherConfig { struct UTXOFetcherConfig {
let dataDb: URL
let networkType: NetworkType
let walletBirthdayProvider: () async -> BlockHeight let walletBirthdayProvider: () async -> BlockHeight
} }
@ -27,7 +25,7 @@ struct UTXOFetcherImpl {
let blockDownloaderService: BlockDownloaderService let blockDownloaderService: BlockDownloaderService
let config: UTXOFetcherConfig let config: UTXOFetcherConfig
let internalSyncProgress: InternalSyncProgress let internalSyncProgress: InternalSyncProgress
let rustBackend: ZcashRustBackendWelding.Type let rustBackend: ZcashRustBackendWelding
let metrics: SDKMetrics let metrics: SDKMetrics
let logger: Logger let logger: Logger
} }
@ -41,11 +39,7 @@ extension UTXOFetcherImpl: UTXOFetcher {
var tAddresses: [TransparentAddress] = [] var tAddresses: [TransparentAddress] = []
for account in accounts { for account in accounts {
tAddresses += try await rustBackend.listTransparentReceivers( tAddresses += try await rustBackend.listTransparentReceivers(account: Int32(account))
dbData: config.dataDb,
account: Int32(account),
networkType: config.networkType
)
} }
var utxos: [UnspentTransactionOutputEntity] = [] var utxos: [UnspentTransactionOutputEntity] = []
@ -64,19 +58,15 @@ extension UTXOFetcherImpl: UTXOFetcher {
let startTime = Date() let startTime = Date()
for utxo in utxos { for utxo in utxos {
do { do {
if try await rustBackend.putUnspentTransparentOutput( try await rustBackend.putUnspentTransparentOutput(
dbData: config.dataDb,
txid: utxo.txid.bytes, txid: utxo.txid.bytes,
index: utxo.index, index: utxo.index,
script: utxo.script.bytes, script: utxo.script.bytes,
value: Int64(utxo.valueZat), value: Int64(utxo.valueZat),
height: utxo.height, height: utxo.height
networkType: config.networkType )
) {
refreshed.append(utxo) refreshed.append(utxo)
} else {
skipped.append(utxo)
}
await internalSyncProgress.set(utxo.height, .latestUTXOFetchedHeight) await internalSyncProgress.set(utxo.height, .latestUTXOFetchedHeight)
} catch { } catch {

View File

@ -52,7 +52,10 @@ extension FSCompactBlockRepository: CompactBlockRepository {
try fileManager.createDirectory(at: blocksDirectory, withIntermediateDirectories: true) try fileManager.createDirectory(at: blocksDirectory, withIntermediateDirectories: true)
} }
guard try await self.metadataStore.initFsBlockDbRoot(self.fsBlockDbRoot) else { do {
try await self.metadataStore.initFsBlockDbRoot()
} catch {
logger.error("Blocks metadata store init failed with error: \(error)")
throw CompactBlockRepositoryError.failedToInitializeCache throw CompactBlockRepositoryError.failedToInitializeCache
} }
} }
@ -210,12 +213,12 @@ extension FSBlockFileWriter {
struct FSMetadataStore { struct FSMetadataStore {
var saveBlocksMeta: ([ZcashCompactBlock]) async throws -> Void var saveBlocksMeta: ([ZcashCompactBlock]) async throws -> Void
var rewindToHeight: (BlockHeight) async throws -> Void var rewindToHeight: (BlockHeight) async throws -> Void
var initFsBlockDbRoot: (URL) async throws -> Bool var initFsBlockDbRoot: () async throws -> Void
var latestHeight: () async -> BlockHeight var latestHeight: () async -> BlockHeight
} }
extension FSMetadataStore { extension FSMetadataStore {
static func live(fsBlockDbRoot: URL, rustBackend: ZcashRustBackendWelding.Type, logger: Logger) -> FSMetadataStore { static func live(fsBlockDbRoot: URL, rustBackend: ZcashRustBackendWelding, logger: Logger) -> FSMetadataStore {
FSMetadataStore { blocks in FSMetadataStore { blocks in
try await FSMetadataStore.saveBlocksMeta( try await FSMetadataStore.saveBlocksMeta(
blocks, blocks,
@ -224,13 +227,15 @@ extension FSMetadataStore {
logger: logger logger: logger
) )
} rewindToHeight: { height in } rewindToHeight: { height in
guard await rustBackend.rewindCacheToHeight(fsBlockDbRoot: fsBlockDbRoot, height: Int32(height)) else { do {
try await rustBackend.rewindCacheToHeight(height: Int32(height))
} catch {
throw CompactBlockRepositoryError.failedToRewind(height) throw CompactBlockRepositoryError.failedToRewind(height)
} }
} initFsBlockDbRoot: { dbRootURL in } initFsBlockDbRoot: {
try await rustBackend.initBlockMetadataDb(fsBlockDbRoot: dbRootURL) try await rustBackend.initBlockMetadataDb()
} latestHeight: { } latestHeight: {
await rustBackend.latestCachedBlockHeight(fsBlockDbRoot: fsBlockDbRoot) await rustBackend.latestCachedBlockHeight()
} }
} }
} }
@ -244,15 +249,13 @@ extension FSMetadataStore {
static func saveBlocksMeta( static func saveBlocksMeta(
_ blocks: [ZcashCompactBlock], _ blocks: [ZcashCompactBlock],
fsBlockDbRoot: URL, fsBlockDbRoot: URL,
rustBackend: ZcashRustBackendWelding.Type, rustBackend: ZcashRustBackendWelding,
logger: Logger logger: Logger
) async throws { ) async throws {
guard !blocks.isEmpty else { return } guard !blocks.isEmpty else { return }
do { do {
guard try await rustBackend.writeBlocksMetadata(fsBlockDbRoot: fsBlockDbRoot, blocks: blocks) else { try await rustBackend.writeBlocksMetadata(blocks: blocks)
throw CompactBlockRepositoryError.failedToWriteMetadata
}
} catch { } catch {
logger.error("Failed to write metadata with error: \(error)") logger.error("Failed to write metadata with error: \(error)")
throw CompactBlockRepositoryError.failedToWriteMetadata throw CompactBlockRepositoryError.failedToWriteMetadata

View File

@ -8,8 +8,6 @@
import Foundation import Foundation
struct SaplingParametersHandlerConfig { struct SaplingParametersHandlerConfig {
let dataDb: URL
let networkType: NetworkType
let outputParamsURL: URL let outputParamsURL: URL
let spendParamsURL: URL let spendParamsURL: URL
let saplingParamsSourceURL: SaplingParamsSourceURL let saplingParamsSourceURL: SaplingParamsSourceURL
@ -21,7 +19,7 @@ protocol SaplingParametersHandler {
struct SaplingParametersHandlerImpl { struct SaplingParametersHandlerImpl {
let config: SaplingParametersHandlerConfig let config: SaplingParametersHandlerConfig
let rustBackend: ZcashRustBackendWelding.Type let rustBackend: ZcashRustBackendWelding
let logger: Logger let logger: Logger
} }
@ -30,16 +28,8 @@ extension SaplingParametersHandlerImpl: SaplingParametersHandler {
try Task.checkCancellation() try Task.checkCancellation()
do { do {
let totalShieldedBalance = try await rustBackend.getBalance( let totalShieldedBalance = try await rustBackend.getBalance(account: Int32(0))
dbData: config.dataDb, let totalTransparentBalance = try await rustBackend.getTransparentBalance(account: Int32(0))
account: Int32(0),
networkType: config.networkType
)
let totalTransparentBalance = try await rustBackend.getTransparentBalance(
dbData: config.dataDb,
account: Int32(0),
networkType: config.networkType
)
// Download Sapling parameters only if sapling funds are detected. // Download Sapling parameters only if sapling funds are detected.
guard totalShieldedBalance > 0 || totalTransparentBalance > 0 else { return } guard totalShieldedBalance > 0 || totalTransparentBalance > 0 else { return }

View File

@ -8,8 +8,6 @@
import Foundation import Foundation
struct BlockScannerConfig { struct BlockScannerConfig {
let fsBlockCacheRoot: URL
let dataDB: URL
let networkType: NetworkType let networkType: NetworkType
let scanningBatchSize: Int let scanningBatchSize: Int
} }
@ -20,7 +18,7 @@ protocol BlockScanner {
struct BlockScannerImpl { struct BlockScannerImpl {
let config: BlockScannerConfig let config: BlockScannerConfig
let rustBackend: ZcashRustBackendWelding.Type let rustBackend: ZcashRustBackendWelding
let transactionRepository: TransactionRepository let transactionRepository: TransactionRepository
let metrics: SDKMetrics let metrics: SDKMetrics
let logger: Logger let logger: Logger
@ -42,19 +40,16 @@ extension BlockScannerImpl: BlockScanner {
let previousScannedHeight = lastScannedHeight let previousScannedHeight = lastScannedHeight
// TODO: [#576] remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576 // TODO: [#576] remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576
let batchSize = scanBatchSize(startScanHeight: previousScannedHeight + 1, network: self.config.networkType) let batchSize = scanBatchSize(startScanHeight: previousScannedHeight + 1, network: config.networkType)
let scanStartTime = Date() let scanStartTime = Date()
guard await self.rustBackend.scanBlocks( do {
fsBlockDbRoot: config.fsBlockCacheRoot, try await self.rustBackend.scanBlocks(limit: batchSize)
dbData: config.dataDB, } catch {
limit: batchSize,
networkType: config.networkType
) else {
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
logger.debug("block scanning failed with error: \(String(describing: error))") logger.debug("block scanning failed with error: \(String(describing: error))")
throw error throw error
} }
let scanFinishTime = Date() let scanFinishTime = Date()
lastScannedHeight = try await transactionRepository.lastScannedHeight() lastScannedHeight = try await transactionRepository.lastScannedHeight()

View File

@ -14,20 +14,13 @@ enum BlockValidatorError: Error {
case failedWithUnknownError case failedWithUnknownError
} }
struct BlockValidatorConfig {
let fsBlockCacheRoot: URL
let dataDB: URL
let networkType: NetworkType
}
protocol BlockValidator { protocol BlockValidator {
/// Validate all the downloaded blocks that haven't been yet validated. /// Validate all the downloaded blocks that haven't been yet validated.
func validate() async throws func validate() async throws
} }
struct BlockValidatorImpl { struct BlockValidatorImpl {
let config: BlockValidatorConfig let rustBackend: ZcashRustBackendWelding
let rustBackend: ZcashRustBackendWelding.Type
let metrics: SDKMetrics let metrics: SDKMetrics
let logger: Logger let logger: Logger
} }
@ -37,14 +30,24 @@ extension BlockValidatorImpl: BlockValidator {
try Task.checkCancellation() try Task.checkCancellation()
let startTime = Date() let startTime = Date()
let result = await rustBackend.validateCombinedChain( do {
fsBlockDbRoot: config.fsBlockCacheRoot, try await rustBackend.validateCombinedChain(limit: 0)
dbData: config.dataDB, pushProgressReport(startTime: startTime, finishTime: Date())
networkType: config.networkType, logger.debug("validateChainFinished")
limit: 0 } catch {
) pushProgressReport(startTime: startTime, finishTime: Date())
let finishTime = Date()
switch error {
case let RustWeldingError.invalidChain(upperBound):
throw BlockValidatorError.validationFailed(height: BlockHeight(upperBound))
default:
throw BlockValidatorError.failedWithError(error)
}
}
}
private func pushProgressReport(startTime: Date, finishTime: Date) {
metrics.pushProgressReport( metrics.pushProgressReport(
progress: BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0), progress: BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0),
start: startTime, start: startTime,
@ -52,24 +55,5 @@ extension BlockValidatorImpl: BlockValidator {
batchSize: 0, batchSize: 0,
operation: .validateBlocks operation: .validateBlocks
) )
switch result {
case 0:
let rustError = rustBackend.lastError()
logger.debug("Block validation failed with error: \(String(describing: rustError))")
if let rustError {
throw BlockValidatorError.failedWithError(rustError)
} else {
throw BlockValidatorError.failedWithUnknownError
}
case ZcashRustBackendWeldingConstants.validChain:
logger.debug("validateChainFinished")
return
default:
logger.debug("Block validation failed at height: \(result)")
throw BlockValidatorError.validationFailed(height: BlockHeight(result))
}
} }
} }

View File

@ -8,13 +8,6 @@
import Combine import Combine
import Foundation import Foundation
/* These aliases are here to just make the API easier to read. */
// Publisher which emitts completed or error. No value is emitted.
public typealias CompletablePublisher<E: Error> = AnyPublisher<Void, E>
// Publisher that either emits one value and then finishes or it emits error.
public typealias SinglePublisher = AnyPublisher
/// This defines a Combine-based API for the SDK. It's expected that the implementation of this protocol is only a very thin layer that translates /// This defines a Combine-based API for the SDK. It's expected that the implementation of this protocol is only a very thin layer that translates
/// async API defined in `Synchronizer` to Combine-based API. And it doesn't do anything else. It's here so each client can choose the API that suits /// async API defined in `Synchronizer` to Combine-based API. And it doesn't do anything else. It's here so each client can choose the API that suits
/// its case the best. /// its case the best.

View File

@ -28,7 +28,7 @@ struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable {
case rawTransactionId = "txid" case rawTransactionId = "txid"
case fee case fee
} }
var recipient: PendingTransactionRecipient var recipient: PendingTransactionRecipient
var accountIndex: Int var accountIndex: Int
var minedHeight: BlockHeight var minedHeight: BlockHeight

View File

@ -115,7 +115,6 @@ public class Initializer {
// This is used to uniquely identify instance of the SDKSynchronizer. It's used when checking if the Alias is already used or not. // 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 id = UUID()
let rustBackend: ZcashRustBackendWelding.Type
let alias: ZcashSynchronizerAlias let alias: ZcashSynchronizerAlias
let endpoint: LightWalletEndpoint let endpoint: LightWalletEndpoint
let fsBlockDbRoot: URL let fsBlockDbRoot: URL
@ -131,6 +130,7 @@ public class Initializer {
let blockDownloaderService: BlockDownloaderService let blockDownloaderService: BlockDownloaderService
let network: ZcashNetwork let network: ZcashNetwork
let logger: Logger let logger: Logger
let rustBackend: ZcashRustBackendWelding
/// The effective birthday of the wallet based on the height provided when initializing and the checkpoints available on this SDK. /// The effective birthday of the wallet based on the height provided when initializing and the checkpoints available on this SDK.
/// ///
@ -180,8 +180,16 @@ public class Initializer {
let (updatedURLs, parsingError) = Self.tryToUpdateURLs(with: alias, urls: urls) let (updatedURLs, parsingError) = Self.tryToUpdateURLs(with: alias, urls: urls)
let logger = OSLogger(logLevel: logLevel, alias: alias) let logger = OSLogger(logLevel: logLevel, alias: alias)
let rustBackend = ZcashRustBackend(
dbData: updatedURLs.dataDbURL,
fsBlockDbRoot: updatedURLs.fsBlockDbRoot,
spendParamsPath: updatedURLs.spendParamsURL,
outputParamsPath: updatedURLs.outputParamsURL,
networkType: network.networkType
)
self.init( self.init(
rustBackend: ZcashRustBackend.self, rustBackend: rustBackend,
network: network, network: network,
cacheDbURL: cacheDbURL, cacheDbURL: cacheDbURL,
urls: updatedURLs, urls: updatedURLs,
@ -198,7 +206,7 @@ public class Initializer {
fsBlockDbRoot: updatedURLs.fsBlockDbRoot, fsBlockDbRoot: updatedURLs.fsBlockDbRoot,
metadataStore: .live( metadataStore: .live(
fsBlockDbRoot: updatedURLs.fsBlockDbRoot, fsBlockDbRoot: updatedURLs.fsBlockDbRoot,
rustBackend: ZcashRustBackend.self, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -216,7 +224,7 @@ public class Initializer {
/// ///
/// !!! It's expected that URLs put here are already update with the Alias. /// !!! It's expected that URLs put here are already update with the Alias.
init( init(
rustBackend: ZcashRustBackendWelding.Type, rustBackend: ZcashRustBackendWelding,
network: ZcashNetwork, network: ZcashNetwork,
cacheDbURL: URL?, cacheDbURL: URL?,
urls: URLs, urls: URLs,
@ -341,7 +349,7 @@ public class Initializer {
} }
do { do {
if case .seedRequired = try await rustBackend.initDataDb(dbData: dataDbURL, seed: seed, networkType: network.networkType) { if case .seedRequired = try await rustBackend.initDataDb(seed: seed) {
return .seedRequired return .seedRequired
} }
} catch { } catch {
@ -351,12 +359,10 @@ public class Initializer {
let checkpoint = Checkpoint.birthday(with: walletBirthday, network: network) let checkpoint = Checkpoint.birthday(with: walletBirthday, network: network)
do { do {
try await rustBackend.initBlocksTable( try await rustBackend.initBlocksTable(
dbData: dataDbURL,
height: Int32(checkpoint.height), height: Int32(checkpoint.height),
hash: checkpoint.hash, hash: checkpoint.hash,
time: checkpoint.time, time: checkpoint.time,
saplingTree: checkpoint.saplingTree, saplingTree: checkpoint.saplingTree
networkType: network.networkType
) )
} catch RustWeldingError.dataDbNotEmpty { } catch RustWeldingError.dataDbNotEmpty {
// this is fine // this is fine
@ -367,11 +373,7 @@ public class Initializer {
self.walletBirthday = checkpoint.height self.walletBirthday = checkpoint.height
do { do {
try await rustBackend.initAccountsTable( try await rustBackend.initAccountsTable(ufvks: viewingKeys)
dbData: dataDbURL,
ufvks: viewingKeys,
networkType: network.networkType
)
} catch RustWeldingError.dataDbNotEmpty { } catch RustWeldingError.dataDbNotEmpty {
// this is fine // this is fine
} catch RustWeldingError.malformedStringInput { } catch RustWeldingError.malformedStringInput {
@ -395,14 +397,18 @@ public class Initializer {
checks if the provided address is a valid sapling address checks if the provided address is a valid sapling address
*/ */
public func isValidSaplingAddress(_ address: String) -> Bool { public func isValidSaplingAddress(_ address: String) -> Bool {
rustBackend.isValidSaplingAddress(address, networkType: network.networkType) DerivationTool(networkType: network.networkType).isValidSaplingAddress(address)
} }
/** /**
checks if the provided address is a transparent zAddress checks if the provided address is a transparent zAddress
*/ */
public func isValidTransparentAddress(_ address: String) -> Bool { public func isValidTransparentAddress(_ address: String) -> Bool {
rustBackend.isValidTransparentAddress(address, networkType: network.networkType) DerivationTool(networkType: network.networkType).isValidTransparentAddress(address)
}
public func makeDerivationTool() -> DerivationTool {
return DerivationTool(networkType: network.networkType)
} }
} }

View File

@ -63,7 +63,7 @@ public struct UnifiedFullViewingKey: Equatable, StringEncoded, Undescribable {
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is /// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid /// found to be invalid
public init(encoding: String, account: UInt32, network: NetworkType) throws { public init(encoding: String, account: UInt32, network: NetworkType) throws {
guard DerivationTool.rustwelding.isValidUnifiedFullViewingKey(encoding, networkType: network) else { guard DerivationTool(networkType: network).isValidUnifiedFullViewingKey(encoding) else {
throw KeyEncodingError.invalidEncoding throw KeyEncodingError.invalidEncoding
} }
@ -85,7 +85,7 @@ public struct SaplingExtendedFullViewingKey: Equatable, StringEncoded, Undescrib
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is /// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid /// found to be invalid
public init(encoding: String, network: NetworkType) throws { public init(encoding: String, network: NetworkType) throws {
guard DerivationTool.rustwelding.isValidSaplingExtendedFullViewingKey(encoding, networkType: network) else { guard ZcashKeyDerivationBackend(networkType: network).isValidSaplingExtendedFullViewingKey(encoding) else {
throw KeyEncodingError.invalidEncoding throw KeyEncodingError.invalidEncoding
} }
self.encoding = encoding self.encoding = encoding
@ -174,6 +174,8 @@ public struct SaplingAddress: Equatable, StringEncoded {
} }
public struct UnifiedAddress: Equatable, StringEncoded { public struct UnifiedAddress: Equatable, StringEncoded {
let networkType: NetworkType
public enum Errors: Error { public enum Errors: Error {
case couldNotExtractTypecodes case couldNotExtractTypecodes
} }
@ -212,6 +214,7 @@ public struct UnifiedAddress: Equatable, StringEncoded {
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is /// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid /// found to be invalid
public init(encoding: String, network: NetworkType) throws { public init(encoding: String, network: NetworkType) throws {
networkType = network
guard DerivationTool(networkType: network).isValidUnifiedAddress(encoding) else { guard DerivationTool(networkType: network).isValidUnifiedAddress(encoding) else {
throw KeyEncodingError.invalidEncoding throw KeyEncodingError.invalidEncoding
} }
@ -224,7 +227,7 @@ public struct UnifiedAddress: Equatable, StringEncoded {
/// couldn't be extracted /// couldn't be extracted
public func availableReceiverTypecodes() throws -> [UnifiedAddress.ReceiverTypecodes] { public func availableReceiverTypecodes() throws -> [UnifiedAddress.ReceiverTypecodes] {
do { do {
return try DerivationTool.receiverTypecodesFromUnifiedAddress(self) return try DerivationTool(networkType: networkType).receiverTypecodesFromUnifiedAddress(self)
} catch { } catch {
throw Errors.couldNotExtractTypecodes throw Errors.couldNotExtractTypecodes
} }
@ -272,7 +275,7 @@ public enum Recipient: Equatable, StringEncoded {
metadata.networkType) metadata.networkType)
case .p2sh: return (.transparent(TransparentAddress(validatedEncoding: encoded)), metadata.networkType) case .p2sh: return (.transparent(TransparentAddress(validatedEncoding: encoded)), metadata.networkType)
case .sapling: return (.sapling(SaplingAddress(validatedEncoding: encoded)), metadata.networkType) case .sapling: return (.sapling(SaplingAddress(validatedEncoding: encoded)), metadata.networkType)
case .unified: return (.unified(UnifiedAddress(validatedEncoding: encoded)), metadata.networkType) case .unified: return (.unified(UnifiedAddress(validatedEncoding: encoded, networkType: metadata.networkType)), metadata.networkType)
} }
} }
} }

View File

@ -0,0 +1,225 @@
//
// ZcashKeyDerivationBackend.swift
//
//
// Created by Francisco Gindre on 4/7/23.
//
import Foundation
import libzcashlc
struct ZcashKeyDerivationBackend: ZcashKeyDerivationBackendWelding {
let networkType: NetworkType
init(networkType: NetworkType) {
self.networkType = networkType
}
// MARK: Address metadata and validation
static func getAddressMetadata(_ address: String) -> AddressMetadata? {
var networkId: UInt32 = 0
var addrId: UInt32 = 0
guard zcashlc_get_address_metadata(
[CChar](address.utf8CString),
&networkId,
&addrId
) else {
return nil
}
guard
let network = NetworkType.forNetworkId(networkId),
let addrType = AddressType.forId(addrId)
else {
return nil
}
return AddressMetadata(network: network, addrType: addrType)
}
func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32] {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.invalidInput(message: "`address` contains null bytes.")
}
var len = UInt(0)
guard let typecodesPointer = zcashlc_get_typecodes_for_unified_address_receivers(
[CChar](address.utf8CString),
&len
), len > 0
else {
throw RustWeldingError.malformedStringInput
}
var typecodes: [UInt32] = []
for typecodeIndex in 0 ..< Int(len) {
let pointer = typecodesPointer.advanced(by: typecodeIndex)
typecodes.append(pointer.pointee)
}
defer {
zcashlc_free_typecodes(typecodesPointer, len)
}
return typecodes
}
func isValidSaplingAddress(_ address: String) -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_shielded_address([CChar](address.utf8CString), networkType.networkId)
}
func isValidSaplingExtendedFullViewingKey(_ key: String) -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_viewing_key([CChar](key.utf8CString), networkType.networkId)
}
func isValidSaplingExtendedSpendingKey(_ key: String) -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_sapling_extended_spending_key([CChar](key.utf8CString), networkType.networkId)
}
func isValidTransparentAddress(_ address: String) -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_transparent_address([CChar](address.utf8CString), networkType.networkId)
}
func isValidUnifiedAddress(_ address: String) -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_unified_address([CChar](address.utf8CString), networkType.networkId)
}
func isValidUnifiedFullViewingKey(_ key: String) -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_unified_full_viewing_key([CChar](key.utf8CString), networkType.networkId)
}
// MARK: Address Derivation
func deriveUnifiedSpendingKey(
from seed: [UInt8],
accountIndex: Int32
) async throws -> UnifiedSpendingKey {
let binaryKeyPtr = seed.withUnsafeBufferPointer { seedBufferPtr in
return zcashlc_derive_spending_key(
seedBufferPtr.baseAddress,
UInt(seed.count),
accountIndex,
networkType.networkId
)
}
defer { zcashlc_free_binary_key(binaryKeyPtr) }
guard let binaryKey = binaryKeyPtr?.pointee else {
throw lastError() ?? .genericError(message: "No error message available")
}
return binaryKey.unsafeToUnifiedSpendingKey(network: networkType)
}
func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) async throws -> UnifiedFullViewingKey {
let extfvk = try spendingKey.bytes.withUnsafeBufferPointer { uskBufferPtr -> UnsafeMutablePointer<CChar> in
guard let extfvk = zcashlc_spending_key_to_full_viewing_key(
uskBufferPtr.baseAddress,
UInt(spendingKey.bytes.count),
networkType.networkId
) else {
throw lastError() ?? .genericError(message: "No error message available")
}
return extfvk
}
defer { zcashlc_string_free(extfvk) }
guard let derived = String(validatingUTF8: extfvk) else {
throw RustWeldingError.unableToDeriveKeys
}
return UnifiedFullViewingKey(validatedEncoding: derived, account: spendingKey.account)
}
func getSaplingReceiver(for uAddr: UnifiedAddress) throws -> SaplingAddress {
guard let saplingCStr = zcashlc_get_sapling_receiver_for_unified_address(
[CChar](uAddr.encoding.utf8CString)
) else {
throw KeyDerivationErrors.invalidUnifiedAddress
}
defer { zcashlc_string_free(saplingCStr) }
guard let saplingReceiverStr = String(validatingUTF8: saplingCStr) else {
throw KeyDerivationErrors.receiverNotFound
}
return SaplingAddress(validatedEncoding: saplingReceiverStr)
}
func getTransparentReceiver(for uAddr: UnifiedAddress) throws -> TransparentAddress {
guard let transparentCStr = zcashlc_get_transparent_receiver_for_unified_address(
[CChar](uAddr.encoding.utf8CString)
) else {
throw KeyDerivationErrors.invalidUnifiedAddress
}
defer { zcashlc_string_free(transparentCStr) }
guard let transparentReceiverStr = String(validatingUTF8: transparentCStr) else {
throw KeyDerivationErrors.receiverNotFound
}
return TransparentAddress(validatedEncoding: transparentReceiverStr)
}
// MARK: Error Handling
private func lastError() -> RustWeldingError? {
defer { zcashlc_clear_last_error() }
guard let message = getLastError() else {
return nil
}
if message.contains("couldn't load Sapling spend parameters") {
return RustWeldingError.saplingSpendParametersNotFound
} else if message.contains("is not empty") {
return RustWeldingError.dataDbNotEmpty
}
return RustWeldingError.genericError(message: message)
}
private func getLastError() -> String? {
let errorLen = zcashlc_last_error_length()
if errorLen > 0 {
let error = UnsafeMutablePointer<Int8>.allocate(capacity: Int(errorLen))
zcashlc_error_message_utf8(error, errorLen)
zcashlc_clear_last_error()
return String(validatingUTF8: error)
} else {
return nil
}
}
}

View File

@ -0,0 +1,82 @@
//
// ZcashKeyDerivationBackendWelding.swift
//
//
// Created by Michal Fousek on 11.04.2023.
//
import Foundation
protocol ZcashKeyDerivationBackendWelding {
/// The network type this `ZcashKeyDerivationBackendWelding` implementation is for
var networkType: NetworkType { get }
/// Returns the network and address type for the given Zcash address string,
/// if the string represents a valid Zcash address.
/// - Note: not `NetworkType` bound
static func getAddressMetadata(_ address: String) -> AddressMetadata?
/// Obtains the available receiver typecodes for the given String encoded Unified Address
/// - Parameter address: public key represented as a String
/// - Returns the `[UInt32]` that compose the given UA
/// - Throws `RustWeldingError.invalidInput(message: String)` when the UA is either invalid or malformed
/// - Note: not `NetworkType` bound
func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32]
/// Validates the if the given string is a valid Sapling Address
/// - Parameter address: UTF-8 encoded String to validate
/// - Returns: true when the address is valid. Returns false in any other case
/// - Throws: Error when the provided address belongs to another network
func isValidSaplingAddress(_ address: String) -> Bool
/// Validates the if the given string is a valid Sapling Extended Full Viewing Key
/// - Parameter key: UTF-8 encoded String to validate
/// - Returns: `true` when the Sapling Extended Full Viewing Key is valid. `false` in any other case
/// - Throws: Error when there's another problem not related to validity of the string in question
func isValidSaplingExtendedFullViewingKey(_ key: String) -> Bool
/// Validates the if the given string is a valid Sapling Extended Spending Key
/// - Returns: `true` when the Sapling Extended Spending Key is valid, false in any other case.
/// - Throws: Error when the key is semantically valid but it belongs to another network
/// - parameter key: String encoded Extended Spending Key
func isValidSaplingExtendedSpendingKey(_ key: String) -> Bool
/// Validates the if the given string is a valid Transparent Address
/// - Parameter address: UTF-8 encoded String to validate
/// - Returns: true when the address is valid and transparent. false in any other case
func isValidTransparentAddress(_ address: String) -> Bool
/// validates whether a string encoded address is a valid Unified Address.
/// - Parameter address: UTF-8 encoded String to validate
/// - Returns: true when the address is valid and transparent. false in any other case
func isValidUnifiedAddress(_ address: String) -> Bool
/// verifies that the given string-encoded `UnifiedFullViewingKey` is valid.
/// - Parameter ufvk: UTF-8 encoded String to validate
/// - Returns: true when the encoded string is a valid UFVK. false in any other case
func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool
/// Derives and returns a unified spending key from the given seed for the given account ID.
/// Returns the binary encoding of the spending key. The caller should manage the memory of (and store, if necessary) the returned spending key in a secure fashion.
/// - Parameter seed: a Byte Array with the seed
/// - Parameter accountIndex:account index that the key can spend from
func deriveUnifiedSpendingKey(from seed: [UInt8], accountIndex: Int32) async throws -> UnifiedSpendingKey
/// Derives a `UnifiedFullViewingKey` from a `UnifiedSpendingKey`
/// - Parameter spendingKey: the `UnifiedSpendingKey` to derive from
/// - Throws: `RustWeldingError.unableToDeriveKeys` if the SDK couldn't derive the UFVK.
/// - Returns: the derived `UnifiedFullViewingKey`
func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) async throws -> UnifiedFullViewingKey
/// Returns the Sapling receiver within the given Unified Address, if any.
/// - Parameter uAddr: a `UnifiedAddress`
/// - Returns a `SaplingAddress` if any
/// - Throws `receiverNotFound` when the receiver is not found. `invalidUnifiedAddress` if the UA provided is not valid
func getSaplingReceiver(for uAddr: UnifiedAddress) throws -> SaplingAddress
/// Returns the transparent receiver within the given Unified Address, if any.
/// - parameter uAddr: a `UnifiedAddress`
/// - Returns a `TransparentAddress` if any
/// - Throws `receiverNotFound` when the receiver is not found. `invalidUnifiedAddress` if the UA provided is not valid
func getTransparentReceiver(for uAddr: UnifiedAddress) throws -> TransparentAddress
}

View File

@ -9,12 +9,37 @@
import Foundation import Foundation
import libzcashlc import libzcashlc
class ZcashRustBackend: ZcashRustBackendWelding { actor ZcashRustBackend: ZcashRustBackendWelding {
static let minimumConfirmations: UInt32 = 10 let minimumConfirmations: UInt32 = 10
static let useZIP317Fees = false let useZIP317Fees = false
static func createAccount(dbData: URL, seed: [UInt8], networkType: NetworkType) async throws -> UnifiedSpendingKey {
let dbData = dbData.osStr()
let dbData: (String, UInt)
let fsBlockDbRoot: (String, UInt)
let spendParamsPath: (String, UInt)
let outputParamsPath: (String, UInt)
let keyDeriving: ZcashKeyDerivationBackendWelding
nonisolated let networkType: NetworkType
/// Creates instance of `ZcashRustBackend`.
/// - Parameters:
/// - dbData: `URL` pointing to file where data database will be.
/// - fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is.
/// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename
/// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions.
/// - spendParamsPath: `URL` pointing to spend parameters file.
/// - outputParamsPath: `URL` pointing to output parameters file.
/// - networkType: Network type to use.
init(dbData: URL, fsBlockDbRoot: URL, spendParamsPath: URL, outputParamsPath: URL, networkType: NetworkType) {
self.dbData = dbData.osStr()
self.fsBlockDbRoot = fsBlockDbRoot.osPathStr()
self.spendParamsPath = spendParamsPath.osPathStr()
self.outputParamsPath = outputParamsPath.osPathStr()
self.networkType = networkType
self.keyDeriving = ZcashKeyDerivationBackend(networkType: networkType)
}
func createAccount(seed: [UInt8]) async throws -> UnifiedSpendingKey {
guard let ffiBinaryKeyPtr = zcashlc_create_account( guard let ffiBinaryKeyPtr = zcashlc_create_account(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -30,20 +55,13 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return ffiBinaryKeyPtr.pointee.unsafeToUnifiedSpendingKey(network: networkType) return ffiBinaryKeyPtr.pointee.unsafeToUnifiedSpendingKey(network: networkType)
} }
// swiftlint:disable function_parameter_count func createToAddress(
static func createToAddress(
dbData: URL,
usk: UnifiedSpendingKey, usk: UnifiedSpendingKey,
to address: String, to address: String,
value: Int64, value: Int64,
memo: MemoBytes?, memo: MemoBytes?
spendParamsPath: String, ) async throws -> Int64 {
outputParamsPath: String, let result = usk.bytes.withUnsafeBufferPointer { uskPtr in
networkType: NetworkType
) async -> Int64 {
let dbData = dbData.osStr()
return usk.bytes.withUnsafeBufferPointer { uskPtr in
zcashlc_create_to_address( zcashlc_create_to_address(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -52,55 +70,39 @@ class ZcashRustBackend: ZcashRustBackendWelding {
[CChar](address.utf8CString), [CChar](address.utf8CString),
value, value,
memo?.bytes, memo?.bytes,
spendParamsPath, spendParamsPath.0,
UInt(spendParamsPath.lengthOfBytes(using: .utf8)), spendParamsPath.1,
outputParamsPath, outputParamsPath.0,
UInt(outputParamsPath.lengthOfBytes(using: .utf8)), outputParamsPath.1,
networkType.networkId, networkType.networkId,
minimumConfirmations, minimumConfirmations,
useZIP317Fees useZIP317Fees
) )
} }
guard result > 0 else {
throw lastError() ?? .genericError(message: "No error message available")
}
return result
} }
static func decryptAndStoreTransaction(dbData: URL, txBytes: [UInt8], minedHeight: Int32, networkType: NetworkType) async -> Bool { func decryptAndStoreTransaction(txBytes: [UInt8], minedHeight: Int32) async throws {
let dbData = dbData.osStr() let result = zcashlc_decrypt_and_store_transaction(
return zcashlc_decrypt_and_store_transaction(
dbData.0, dbData.0,
dbData.1, dbData.1,
txBytes, txBytes,
UInt(txBytes.count), UInt(txBytes.count),
UInt32(minedHeight), UInt32(minedHeight),
networkType.networkId networkType.networkId
) != 0 )
}
static func deriveUnifiedSpendingKey( guard result != 0 else {
from seed: [UInt8],
accountIndex: Int32,
networkType: NetworkType
) throws -> UnifiedSpendingKey {
let binaryKeyPtr = seed.withUnsafeBufferPointer { seedBufferPtr in
return zcashlc_derive_spending_key(
seedBufferPtr.baseAddress,
UInt(seed.count),
accountIndex,
networkType.networkId
)
}
defer { zcashlc_free_binary_key(binaryKeyPtr) }
guard let binaryKey = binaryKeyPtr?.pointee else {
throw lastError() ?? .genericError(message: "No error message available") throw lastError() ?? .genericError(message: "No error message available")
} }
return binaryKey.unsafeToUnifiedSpendingKey(network: networkType)
} }
static func getBalance(dbData: URL, account: Int32, networkType: NetworkType) async throws -> Int64 { func getBalance(account: Int32) async throws -> Int64 {
let dbData = dbData.osStr()
let balance = zcashlc_get_balance(dbData.0, dbData.1, account, networkType.networkId) let balance = zcashlc_get_balance(dbData.0, dbData.1, account, networkType.networkId)
guard balance >= 0 else { guard balance >= 0 else {
@ -110,13 +112,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance return balance
} }
static func getCurrentAddress( func getCurrentAddress(account: Int32) async throws -> UnifiedAddress {
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> UnifiedAddress {
let dbData = dbData.osStr()
guard let addressCStr = zcashlc_get_current_address( guard let addressCStr = zcashlc_get_current_address(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -132,31 +128,25 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.unableToDeriveKeys throw RustWeldingError.unableToDeriveKeys
} }
return UnifiedAddress(validatedEncoding: address) return UnifiedAddress(validatedEncoding: address, networkType: networkType)
} }
static func getNearestRewindHeight( func getNearestRewindHeight(height: Int32) async throws -> Int32 {
dbData: URL, let result = zcashlc_get_nearest_rewind_height(
height: Int32,
networkType: NetworkType
) async -> Int32 {
let dbData = dbData.osStr()
return zcashlc_get_nearest_rewind_height(
dbData.0, dbData.0,
dbData.1, dbData.1,
height, height,
networkType.networkId networkType.networkId
) )
guard result > 0 else {
throw lastError() ?? .genericError(message: "No error message available")
}
return result
} }
static func getNextAvailableAddress( func getNextAvailableAddress(account: Int32) async throws -> UnifiedAddress {
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> UnifiedAddress {
let dbData = dbData.osStr()
guard let addressCStr = zcashlc_get_next_available_address( guard let addressCStr = zcashlc_get_next_available_address(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -172,16 +162,10 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.unableToDeriveKeys throw RustWeldingError.unableToDeriveKeys
} }
return UnifiedAddress(validatedEncoding: address) return UnifiedAddress(validatedEncoding: address, networkType: networkType)
} }
static func getReceivedMemo( func getReceivedMemo(idNote: Int64) async -> Memo? {
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo? {
let dbData = dbData.osStr()
var contiguousMemoBytes = ContiguousArray<UInt8>(MemoBytes.empty().bytes) var contiguousMemoBytes = ContiguousArray<UInt8>(MemoBytes.empty().bytes)
var success = false var success = false
@ -194,29 +178,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() } return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() }
} }
static func getSaplingReceiver(for uAddr: UnifiedAddress) throws -> SaplingAddress { func getSentMemo(idNote: Int64) async -> Memo? {
guard let saplingCStr = zcashlc_get_sapling_receiver_for_unified_address(
[CChar](uAddr.encoding.utf8CString)
) else {
throw KeyDerivationErrors.invalidUnifiedAddress
}
defer { zcashlc_string_free(saplingCStr) }
guard let saplingReceiverStr = String(validatingUTF8: saplingCStr) else {
throw KeyDerivationErrors.receiverNotFound
}
return SaplingAddress(validatedEncoding: saplingReceiverStr)
}
static func getSentMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo? {
let dbData = dbData.osStr()
var contiguousMemoBytes = ContiguousArray<UInt8>(MemoBytes.empty().bytes) var contiguousMemoBytes = ContiguousArray<UInt8>(MemoBytes.empty().bytes)
var success = false var success = false
@ -229,16 +191,11 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() } return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() }
} }
static func getTransparentBalance( func getTransparentBalance(account: Int32) async throws -> Int64 {
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64 {
guard account >= 0 else { guard account >= 0 else {
throw RustWeldingError.invalidInput(message: "Account index must be non-negative") throw RustWeldingError.invalidInput(message: "Account index must be non-negative")
} }
let dbData = dbData.osStr()
let balance = zcashlc_get_total_transparent_balance_for_account( let balance = zcashlc_get_total_transparent_balance_for_account(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -253,8 +210,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance return balance
} }
static func getVerifiedBalance(dbData: URL, account: Int32, networkType: NetworkType) async throws -> Int64 { func getVerifiedBalance(account: Int32) async throws -> Int64 {
let dbData = dbData.osStr()
let balance = zcashlc_get_verified_balance( let balance = zcashlc_get_verified_balance(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -270,17 +226,11 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance return balance
} }
static func getVerifiedTransparentBalance( func getVerifiedTransparentBalance(account: Int32) async throws -> Int64 {
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64 {
guard account >= 0 else { guard account >= 0 else {
throw RustWeldingError.invalidInput(message: "`account` must be non-negative") throw RustWeldingError.invalidInput(message: "`account` must be non-negative")
} }
let dbData = dbData.osStr()
let balance = zcashlc_get_verified_transparent_balance_for_account( let balance = zcashlc_get_verified_transparent_balance_for_account(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -299,8 +249,8 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance return balance
} }
static func lastError() -> RustWeldingError? { private nonisolated func lastError() -> RustWeldingError? {
defer { zcashlc_clear_last_error() } defer { zcashlc_clear_last_error() }
guard let message = getLastError() else { guard let message = getLastError() else {
@ -316,43 +266,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return RustWeldingError.genericError(message: message) return RustWeldingError.genericError(message: message)
} }
static func getAddressMetadata(_ address: String) -> AddressMetadata? { private nonisolated func getLastError() -> String? {
var networkId: UInt32 = 0
var addrId: UInt32 = 0
guard zcashlc_get_address_metadata(
[CChar](address.utf8CString),
&networkId,
&addrId
) else {
return nil
}
guard let network = NetworkType.forNetworkId(networkId),
let addrType = AddressType.forId(addrId)
else {
return nil
}
return AddressMetadata(network: network, addrType: addrType)
}
static func getTransparentReceiver(for uAddr: UnifiedAddress) throws -> TransparentAddress {
guard let transparentCStr = zcashlc_get_transparent_receiver_for_unified_address(
[CChar](uAddr.encoding.utf8CString)
) else {
throw KeyDerivationErrors.invalidUnifiedAddress
}
defer { zcashlc_string_free(transparentCStr) }
guard let transparentReceiverStr = String(validatingUTF8: transparentCStr) else {
throw KeyDerivationErrors.receiverNotFound
}
return TransparentAddress(validatedEncoding: transparentReceiverStr)
}
static func getLastError() -> String? {
let errorLen = zcashlc_last_error_length() let errorLen = zcashlc_last_error_length()
if errorLen > 0 { if errorLen > 0 {
let error = UnsafeMutablePointer<Int8>.allocate(capacity: Int(errorLen)) let error = UnsafeMutablePointer<Int8>.allocate(capacity: Int(errorLen))
@ -364,8 +278,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
} }
} }
static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: NetworkType) async throws -> DbInitResult { func initDataDb(seed: [UInt8]?) async throws -> DbInitResult {
let dbData = dbData.osStr()
switch zcashlc_init_data_database(dbData.0, dbData.1, seed, UInt(seed?.count ?? 0), networkType.networkId) { switch zcashlc_init_data_database(dbData.0, dbData.1, seed, UInt(seed?.count ?? 0), networkType.networkId) {
case 0: // ok case 0: // ok
return DbInitResult.success return DbInitResult.success
@ -375,74 +288,20 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw throwDataDbError(lastError() ?? .genericError(message: "No error message found")) throw throwDataDbError(lastError() ?? .genericError(message: "No error message found"))
} }
} }
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_shielded_address([CChar](address.utf8CString), networkType.networkId)
}
static func isValidTransparentAddress(_ address: String, networkType: NetworkType) -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_transparent_address([CChar](address.utf8CString), networkType.networkId) func initAccountsTable(ufvks: [UnifiedFullViewingKey]) async throws {
}
static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_viewing_key([CChar](key.utf8CString), networkType.networkId)
}
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: NetworkType) -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_sapling_extended_spending_key([CChar](key.utf8CString), networkType.networkId)
}
static func isValidUnifiedAddress(_ address: String, networkType: NetworkType) -> Bool {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_unified_address([CChar](address.utf8CString), networkType.networkId)
}
static func isValidUnifiedFullViewingKey(_ key: String, networkType: NetworkType) -> Bool {
guard !key.containsCStringNullBytesBeforeStringEnding() else {
return false
}
return zcashlc_is_valid_unified_full_viewing_key([CChar](key.utf8CString), networkType.networkId)
}
static func initAccountsTable(
dbData: URL,
ufvks: [UnifiedFullViewingKey],
networkType: NetworkType
) async throws {
let dbData = dbData.osStr()
var ffiUfvks: [FFIEncodedKey] = [] var ffiUfvks: [FFIEncodedKey] = []
for ufvk in ufvks { for ufvk in ufvks {
guard !ufvk.encoding.containsCStringNullBytesBeforeStringEnding() else { guard !ufvk.encoding.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.invalidInput(message: "`UFVK` contains null bytes.") throw RustWeldingError.invalidInput(message: "`UFVK` contains null bytes.")
} }
guard self.isValidUnifiedFullViewingKey(ufvk.encoding, networkType: networkType) else { guard self.keyDeriving.isValidUnifiedFullViewingKey(ufvk.encoding) else {
throw RustWeldingError.invalidInput(message: "UFVK is invalid.") throw RustWeldingError.invalidInput(message: "UFVK is invalid.")
} }
let ufvkCStr = [CChar](String(ufvk.encoding).utf8CString) let ufvkCStr = [CChar](String(ufvk.encoding).utf8CString)
let ufvkPtr = UnsafeMutablePointer<CChar>.allocate(capacity: ufvkCStr.count) let ufvkPtr = UnsafeMutablePointer<CChar>.allocate(capacity: ufvkCStr.count)
ufvkPtr.initialize(from: ufvkCStr, count: ufvkCStr.count) ufvkPtr.initialize(from: ufvkCStr, count: ufvkCStr.count)
@ -476,19 +335,15 @@ class ZcashRustBackend: ZcashRustBackendWelding {
} }
} }
static func initBlockMetadataDb(fsBlockDbRoot: URL) async throws -> Bool { func initBlockMetadataDb() async throws {
let blockDb = fsBlockDbRoot.osPathStr() let result = zcashlc_init_block_metadata_db(fsBlockDbRoot.0, fsBlockDbRoot.1)
let result = zcashlc_init_block_metadata_db(blockDb.0, blockDb.1)
guard result else { guard result else {
throw lastError() ?? .genericError(message: "`initAccountsTable` failed with unknown error") throw lastError() ?? .genericError(message: "`initAccountsTable` failed with unknown error")
} }
return result
} }
static func writeBlocksMetadata(fsBlockDbRoot: URL, blocks: [ZcashCompactBlock]) async throws -> Bool { func writeBlocksMetadata(blocks: [ZcashCompactBlock]) async throws {
var ffiBlockMetaVec: [FFIBlockMeta] = [] var ffiBlockMetaVec: [FFIBlockMeta] = []
for block in blocks { for block in blocks {
@ -507,7 +362,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
hashPtr.deallocate() hashPtr.deallocate()
ffiBlockMetaVec.deallocateElements() ffiBlockMetaVec.deallocateElements()
} }
return false throw RustWeldingError.writeBlocksMetadataAllocationProblem
} }
ffiBlockMetaVec.append( ffiBlockMetaVec.append(
@ -530,50 +385,35 @@ class ZcashRustBackend: ZcashRustBackendWelding {
defer { ffiBlockMetaVec.deallocateElements() } defer { ffiBlockMetaVec.deallocateElements() }
let result = try contiguousFFIBlocks.withContiguousMutableStorageIfAvailable { ptr in try contiguousFFIBlocks.withContiguousMutableStorageIfAvailable { ptr in
var meta = FFIBlocksMeta() var meta = FFIBlocksMeta()
meta.ptr = ptr.baseAddress meta.ptr = ptr.baseAddress
meta.len = len meta.len = len
fsBlocks.initialize(to: meta) fsBlocks.initialize(to: meta)
let fsDb = fsBlockDbRoot.osPathStr() let res = zcashlc_write_block_metadata(fsBlockDbRoot.0, fsBlockDbRoot.1, fsBlocks)
let res = zcashlc_write_block_metadata(fsDb.0, fsDb.1, fsBlocks)
guard res else { guard res else {
throw lastError() ?? RustWeldingError.genericError(message: "failed to write block metadata") throw lastError() ?? RustWeldingError.genericError(message: "failed to write block metadata")
} }
return res
} }
guard let value = result else {
return false
}
return value
} }
// swiftlint:disable function_parameter_count func initBlocksTable(
static func initBlocksTable(
dbData: URL,
height: Int32, height: Int32,
hash: String, hash: String,
time: UInt32, time: UInt32,
saplingTree: String, saplingTree: String
networkType: NetworkType
) async throws { ) async throws {
let dbData = dbData.osStr()
guard !hash.containsCStringNullBytesBeforeStringEnding() else { guard !hash.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.invalidInput(message: "`hash` contains null bytes.") throw RustWeldingError.invalidInput(message: "`hash` contains null bytes.")
} }
guard !saplingTree.containsCStringNullBytesBeforeStringEnding() else { guard !saplingTree.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.invalidInput(message: "`saplingTree` contains null bytes.") throw RustWeldingError.invalidInput(message: "`saplingTree` contains null bytes.")
} }
guard zcashlc_init_blocks_table( guard zcashlc_init_blocks_table(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -587,19 +427,11 @@ class ZcashRustBackend: ZcashRustBackendWelding {
} }
} }
static func latestCachedBlockHeight(fsBlockDbRoot: URL) async -> BlockHeight { func latestCachedBlockHeight() async -> BlockHeight {
let fsBlockDb = fsBlockDbRoot.osPathStr() return BlockHeight(zcashlc_latest_cached_block_height(fsBlockDbRoot.0, fsBlockDbRoot.1))
return BlockHeight(zcashlc_latest_cached_block_height(fsBlockDb.0, fsBlockDb.1))
} }
static func listTransparentReceivers( func listTransparentReceivers(account: Int32) async throws -> [TransparentAddress] {
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> [TransparentAddress] {
let dbData = dbData.osStr()
guard let encodedKeysPtr = zcashlc_list_transparent_receivers( guard let encodedKeysPtr = zcashlc_list_transparent_receivers(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -627,18 +459,14 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return addresses return addresses
} }
static func putUnspentTransparentOutput( func putUnspentTransparentOutput(
dbData: URL,
txid: [UInt8], txid: [UInt8],
index: Int, index: Int,
script: [UInt8], script: [UInt8],
value: Int64, value: Int64,
height: BlockHeight, height: BlockHeight
networkType: NetworkType ) async throws {
) async throws -> Bool {
let dbData = dbData.osStr()
guard zcashlc_put_utxo( guard zcashlc_put_utxo(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -653,48 +481,51 @@ class ZcashRustBackend: ZcashRustBackendWelding {
) else { ) else {
throw lastError() ?? .genericError(message: "No error message available") throw lastError() ?? .genericError(message: "No error message available")
} }
return true
}
static func validateCombinedChain(fsBlockDbRoot: URL, dbData: URL, networkType: NetworkType, limit: UInt32 = 0) async -> Int32 {
let dbCache = fsBlockDbRoot.osPathStr()
let dbData = dbData.osStr()
return zcashlc_validate_combined_chain(dbCache.0, dbCache.1, dbData.0, dbData.1, networkType.networkId, limit)
}
static func rewindToHeight(dbData: URL, height: Int32, networkType: NetworkType) -> Bool {
let dbData = dbData.osStr()
return zcashlc_rewind_to_height(dbData.0, dbData.1, height, networkType.networkId)
} }
static func rewindCacheToHeight( func validateCombinedChain(limit: UInt32 = 0) async throws {
fsBlockDbRoot: URL, let result = zcashlc_validate_combined_chain(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, networkType.networkId, limit)
height: Int32
) -> Bool {
let fsBlockCache = fsBlockDbRoot.osPathStr()
return zcashlc_rewind_fs_block_cache_to_height(fsBlockCache.0, fsBlockCache.1, height) switch result {
case -1:
return
case 0:
throw RustWeldingError.chainValidationFailed(message: getLastError())
default:
throw RustWeldingError.invalidChain(upperBound: result)
}
} }
static func scanBlocks(fsBlockDbRoot: URL, dbData: URL, limit: UInt32 = 0, networkType: NetworkType) async -> Bool { func rewindToHeight(height: Int32) async throws {
let dbCache = fsBlockDbRoot.osPathStr() let result = zcashlc_rewind_to_height(dbData.0, dbData.1, height, networkType.networkId)
let dbData = dbData.osStr()
return zcashlc_scan_blocks(dbCache.0, dbCache.1, dbData.0, dbData.1, limit, networkType.networkId) != 0 guard result else {
throw lastError() ?? .genericError(message: "No error message available")
}
} }
static func shieldFunds( func rewindCacheToHeight(height: Int32) async throws {
dbData: URL, let result = zcashlc_rewind_fs_block_cache_to_height(fsBlockDbRoot.0, fsBlockDbRoot.1, height)
guard result else {
throw lastError() ?? .genericError(message: "No error message available")
}
}
func scanBlocks(limit: UInt32 = 0) async throws {
let result = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, limit, networkType.networkId)
guard result != 0 else {
throw lastError() ?? .genericError(message: "No error message available")
}
}
func shieldFunds(
usk: UnifiedSpendingKey, usk: UnifiedSpendingKey,
memo: MemoBytes?, memo: MemoBytes?,
shieldingThreshold: Zatoshi, shieldingThreshold: Zatoshi
spendParamsPath: String, ) async throws -> Int64 {
outputParamsPath: String, let result = usk.bytes.withUnsafeBufferPointer { uskBuffer in
networkType: NetworkType
) async -> Int64 {
let dbData = dbData.osStr()
return usk.bytes.withUnsafeBufferPointer { uskBuffer in
zcashlc_shield_funds( zcashlc_shield_funds(
dbData.0, dbData.0,
dbData.1, dbData.1,
@ -702,82 +533,36 @@ class ZcashRustBackend: ZcashRustBackendWelding {
UInt(usk.bytes.count), UInt(usk.bytes.count),
memo?.bytes, memo?.bytes,
UInt64(shieldingThreshold.amount), UInt64(shieldingThreshold.amount),
spendParamsPath, spendParamsPath.0,
UInt(spendParamsPath.lengthOfBytes(using: .utf8)), spendParamsPath.1,
outputParamsPath, outputParamsPath.0,
UInt(outputParamsPath.lengthOfBytes(using: .utf8)), outputParamsPath.1,
networkType.networkId, networkType.networkId,
minimumConfirmations, minimumConfirmations,
useZIP317Fees useZIP317Fees
) )
} }
}
static func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey, networkType: NetworkType) throws -> UnifiedFullViewingKey {
let extfvk = try spendingKey.bytes.withUnsafeBufferPointer { uskBufferPtr -> UnsafeMutablePointer<CChar> in
guard let extfvk = zcashlc_spending_key_to_full_viewing_key(
uskBufferPtr.baseAddress,
UInt(spendingKey.bytes.count),
networkType.networkId
) else {
throw lastError() ?? .genericError(message: "No error message available")
}
return extfvk guard result > 0 else {
throw lastError() ?? .genericError(message: "No error message available")
} }
defer { zcashlc_string_free(extfvk) } return result
guard let derived = String(validatingUTF8: extfvk) else {
throw RustWeldingError.unableToDeriveKeys
}
return UnifiedFullViewingKey(validatedEncoding: derived, account: spendingKey.account)
} }
static func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32] { nonisolated func consensusBranchIdFor(height: Int32) throws -> Int32 {
guard !address.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.invalidInput(message: "`address` contains null bytes.")
}
var len = UInt(0)
guard let typecodesPointer = zcashlc_get_typecodes_for_unified_address_receivers(
[CChar](address.utf8CString),
&len
), len > 0
else {
throw RustWeldingError.malformedStringInput
}
var typecodes: [UInt32] = []
for typecodeIndex in 0 ..< Int(len) {
let pointer = typecodesPointer.advanced(by: typecodeIndex)
typecodes.append(pointer.pointee)
}
defer {
zcashlc_free_typecodes(typecodesPointer, len)
}
return typecodes
}
static func consensusBranchIdFor(height: Int32, networkType: NetworkType) throws -> Int32 {
let branchId = zcashlc_branch_id_for_height(height, networkType.networkId) let branchId = zcashlc_branch_id_for_height(height, networkType.networkId)
guard branchId != -1 else { guard branchId != -1 else {
throw RustWeldingError.noConsensusBranchId(height: height) throw RustWeldingError.noConsensusBranchId(height: height)
} }
return branchId return branchId
} }
} }
private extension ZcashRustBackend { private extension ZcashRustBackend {
static func throwDataDbError(_ error: RustWeldingError) -> Error { func throwDataDbError(_ error: RustWeldingError) -> Error {
if case RustWeldingError.genericError(let message) = error, message.contains("is not empty") { if case RustWeldingError.genericError(let message) = error, message.contains("is not empty") {
return RustWeldingError.dataDbNotEmpty return RustWeldingError.dataDbNotEmpty
} }
@ -785,7 +570,7 @@ private extension ZcashRustBackend {
return RustWeldingError.dataDbInitFailed(message: error.localizedDescription) return RustWeldingError.dataDbInitFailed(message: error.localizedDescription)
} }
static func throwBalanceError(account: Int32, _ error: RustWeldingError?, fallbackMessage: String) -> Error { func throwBalanceError(account: Int32, _ error: RustWeldingError?, fallbackMessage: String) -> Error {
guard let balanceError = error else { guard let balanceError = error else {
return RustWeldingError.genericError(message: fallbackMessage) return RustWeldingError.genericError(message: fallbackMessage)
} }
@ -865,6 +650,15 @@ extension RustWeldingError: LocalizedError {
return "`.unableToDeriveKeys` the requested keys could not be derived from the source provided" return "`.unableToDeriveKeys` the requested keys could not be derived from the source provided"
case let .getBalanceError(account, error): case let .getBalanceError(account, error):
return "`.getBalanceError` could not retrieve balance from account: \(account), error:\(error)" return "`.getBalanceError` could not retrieve balance from account: \(account), error:\(error)"
case let .invalidChain(upperBound: upperBound):
return "`.validateCombinedChain` failed to validate chain. Upper bound: \(upperBound)."
case let .chainValidationFailed(message):
return """
`.validateCombinedChain` failed to validate chain because of error unrelated to chain validity. \
Message: \(String(describing: message))
"""
case .writeBlocksMetadataAllocationProblem:
return "`.writeBlocksMetadata` failed to allocate memory on Swift side necessary to write blocks metadata to db."
} }
} }
} }

View File

@ -19,6 +19,13 @@ enum RustWeldingError: Error {
case getBalanceError(Int, Error) case getBalanceError(Int, Error)
case invalidInput(message: String) case invalidInput(message: String)
case invalidRewind(suggestedHeight: Int32) case invalidRewind(suggestedHeight: Int32)
/// Thrown when `upperBound` if the combined chain is invalid. `upperBound` is the height of the highest invalid block (on the assumption that
/// the highest block in the cache database is correct).
case invalidChain(upperBound: Int32)
/// Thrown if there was an error during validation unrelated to chain validity.
case chainValidationFailed(message: String?)
/// Thrown if there was problem with memory allocation on the Swift side while trying to write blocks metadata to DB.
case writeBlocksMetadataAllocationProblem
} }
enum ZcashRustBackendWeldingConstants { enum ZcashRustBackendWeldingConstants {
@ -31,6 +38,7 @@ public enum DbInitResult {
case seedRequired case seedRequired
} }
// sourcery: mockActor
protocol ZcashRustBackendWelding { protocol ZcashRustBackendWelding {
/// Adds the next available account-level spend authority, given the current set of [ZIP 316] /// Adds the next available account-level spend authority, given the current set of [ZIP 316]
/// account identifiers known, to the wallet database. /// account identifiers known, to the wallet database.
@ -46,83 +54,34 @@ protocol ZcashRustBackendWelding {
/// By convention, wallets should only allow a new account to be generated after funds /// By convention, wallets should only allow a new account to be generated after funds
/// have been received by the currently-available account (in order to enable /// have been received by the currently-available account (in order to enable
/// automated account recovery). /// automated account recovery).
/// - parameter dbData: location of the data db
/// - parameter seed: byte array of the zip32 seed /// - parameter seed: byte array of the zip32 seed
/// - parameter networkType: network type of this key
/// - Returns: The `UnifiedSpendingKey` structs for the number of accounts created /// - Returns: The `UnifiedSpendingKey` structs for the number of accounts created
/// func createAccount(seed: [UInt8]) async throws -> UnifiedSpendingKey
static func createAccount(
dbData: URL,
seed: [UInt8],
networkType: NetworkType
) async throws -> UnifiedSpendingKey
/// Creates a transaction to the given address from the given account /// Creates a transaction to the given address from the given account
/// - Parameter dbData: URL for the Data DB
/// - Parameter usk: `UnifiedSpendingKey` for the account that controls the funds to be spent. /// - Parameter usk: `UnifiedSpendingKey` for the account that controls the funds to be spent.
/// - Parameter to: recipient address /// - Parameter to: recipient address
/// - Parameter value: transaction amount in Zatoshi /// - Parameter value: transaction amount in Zatoshi
/// - Parameter memo: the `MemoBytes` for this transaction. pass `nil` when sending to transparent receivers /// - Parameter memo: the `MemoBytes` for this transaction. pass `nil` when sending to transparent receivers
/// - Parameter spendParamsPath: path escaped String for the filesystem locations where the spend parameters are located func createToAddress(
/// - Parameter outputParamsPath: path escaped String for the filesystem locations where the output parameters are located
/// - Parameter networkType: network type of this key
// swiftlint:disable:next function_parameter_count
static func createToAddress(
dbData: URL,
usk: UnifiedSpendingKey, usk: UnifiedSpendingKey,
to address: String, to address: String,
value: Int64, value: Int64,
memo: MemoBytes?, memo: MemoBytes?
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) async -> Int64 // swiftlint:disable function_parameter_count
/// Scans a transaction for any information that can be decrypted by the accounts in the
/// wallet, and saves it to the wallet.
/// - parameter dbData: location of the data db file
/// - parameter tx: the transaction to decrypt
/// - parameter minedHeight: height on which this transaction was mined. this is used to fetch the consensus branch ID.
/// - parameter networkType: network type of this key
/// returns false if fails to decrypt.
static func decryptAndStoreTransaction(
dbData: URL,
txBytes: [UInt8],
minedHeight: Int32,
networkType: NetworkType
) async -> Bool
/// Derives and returns a unified spending key from the given seed for the given account ID.
/// Returns the binary encoding of the spending key. The caller should manage the memory of (and store, if necessary) the returned spending key in a secure fashion.
/// - Parameter seed: a Byte Array with the seed
/// - Parameter accountIndex:account index that the key can spend from
/// - Parameter networkType: network type of this key
/// - Throws `.unableToDerive` when there's an error
static func deriveUnifiedSpendingKey(
from seed: [UInt8],
accountIndex: Int32,
networkType: NetworkType
) throws -> UnifiedSpendingKey
/// get the (unverified) balance from the given account
/// - parameter dbData: location of the data db
/// - parameter account: index of the given account
/// - parameter networkType: network type of this key
static func getBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64 ) async throws -> Int64
/// Returns the most-recently-generated unified payment address for the specified account. /// Scans a transaction for any information that can be decrypted by the accounts in the wallet, and saves it to the wallet.
/// - parameter dbData: location of the data db /// - parameter tx: the transaction to decrypt
/// - parameter minedHeight: height on which this transaction was mined. this is used to fetch the consensus branch ID.
func decryptAndStoreTransaction(txBytes: [UInt8], minedHeight: Int32) async throws
/// Get the (unverified) balance from the given account.
/// - parameter account: index of the given account /// - parameter account: index of the given account
/// - parameter networkType: network type of this key func getBalance(account: Int32) async throws -> Int64
static func getCurrentAddress(
dbData: URL, /// Returns the most-recently-generated unified payment address for the specified account.
account: Int32, /// - parameter account: index of the given account
networkType: NetworkType func getCurrentAddress(account: Int32) async throws -> UnifiedAddress
) async throws -> UnifiedAddress
/// Wallets might need to be rewound because of a reorg, or by user request. /// Wallets might need to be rewound because of a reorg, or by user request.
/// There are times where the wallet could get out of sync for many reasons and /// There are times where the wallet could get out of sync for many reasons and
@ -131,195 +90,64 @@ protocol ZcashRustBackendWelding {
/// of sapling witnesses older than 100 blocks. So in order to reconstruct the witness /// of sapling witnesses older than 100 blocks. So in order to reconstruct the witness
/// tree that allows to spend notes from the given wallet the rewind can't be more than /// tree that allows to spend notes from the given wallet the rewind can't be more than
/// 100 blocks or back to the oldest unspent note that this wallet contains. /// 100 blocks or back to the oldest unspent note that this wallet contains.
/// - parameter dbData: location of the data db file
/// - parameter height: height you would like to rewind to. /// - parameter height: height you would like to rewind to.
/// - parameter networkType: network type of this key]
/// - Returns: the blockheight of the nearest rewind height. /// - Returns: the blockheight of the nearest rewind height.
/// func getNearestRewindHeight(height: Int32) async throws -> Int32
static func getNearestRewindHeight(
dbData: URL,
height: Int32,
networkType: NetworkType
) async -> Int32
/// Returns a newly-generated unified payment address for the specified account, with the next available diversifier. /// Returns a newly-generated unified payment address for the specified account, with the next available diversifier.
/// - parameter dbData: location of the data db
/// - parameter account: index of the given account /// - parameter account: index of the given account
/// - parameter networkType: network type of this key func getNextAvailableAddress(account: Int32) async throws -> UnifiedAddress
static func getNextAvailableAddress(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> UnifiedAddress
/// get received memo from note /// Get received memo from note.
/// - parameter dbData: location of the data db file
/// - parameter idNote: note_id of note where the memo is located /// - parameter idNote: note_id of note where the memo is located
/// - parameter networkType: network type of this key func getReceivedMemo(idNote: Int64) async -> Memo?
static func getReceivedMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo?
/// Returns the Sapling receiver within the given Unified Address, if any. /// Get sent memo from note.
/// - Parameter uAddr: a `UnifiedAddress`
/// - Returns a `SaplingAddress` if any
/// - Throws `receiverNotFound` when the receiver is not found. `invalidUnifiedAddress` if the UA provided is not valid
static func getSaplingReceiver(for uAddr: UnifiedAddress) throws -> SaplingAddress
/// get sent memo from note
/// - parameter dbData: location of the data db file
/// - parameter idNote: note_id of note where the memo is located /// - parameter idNote: note_id of note where the memo is located
/// - parameter networkType: network type of this key
/// - Returns: a `Memo` if any /// - Returns: a `Memo` if any
static func getSentMemo( func getSentMemo(idNote: Int64) async -> Memo?
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo?
/// Get the verified cached transparent balance for the given address /// Get the verified cached transparent balance for the given address
/// - parameter dbData: location of the data db file
/// - parameter account; the account index to query /// - parameter account; the account index to query
/// - parameter networkType: network type of this key func getTransparentBalance(account: Int32) async throws -> Int64
static func getTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64
/// Returns the transparent receiver within the given Unified Address, if any. /// Initialize the accounts table from a set of unified full viewing keys.
/// - parameter uAddr: a `UnifiedAddress` /// - Note: this function should only be used when restoring an existing seed phrase. when creating a new wallet, use `createAccount()` instead.
/// - Returns a `TransparentAddress` if any
/// - Throws `receiverNotFound` when the receiver is not found. `invalidUnifiedAddress` if the UA provided is not valid
static func getTransparentReceiver(for uAddr: UnifiedAddress) throws -> TransparentAddress
/// gets the latest error if available. Clear the existing error
/// - Returns a `RustWeldingError` if exists
static func lastError() -> RustWeldingError?
/// gets the latest error message from librustzcash. Does not clear existing error
static func getLastError() -> String?
/// initialize the accounts table from a set of unified full viewing keys
/// - Note: this function should only be used when restoring an existing seed phrase.
/// when creating a new wallet, use `createAccount()` instead
/// - Parameter dbData: location of the data db
/// - Parameter ufvks: an array of UnifiedFullViewingKeys /// - Parameter ufvks: an array of UnifiedFullViewingKeys
/// - Parameter networkType: network type of this key func initAccountsTable(ufvks: [UnifiedFullViewingKey]) async throws
static func initAccountsTable(
dbData: URL,
ufvks: [UnifiedFullViewingKey],
networkType: NetworkType
) async throws
/// initializes the data db. This will performs any migrations needed on the sqlite file /// Initializes the data db. This will performs any migrations needed on the sqlite file
/// provided. Some migrations might need that callers provide the seed bytes. /// provided. Some migrations might need that callers provide the seed bytes.
/// - Parameter dbData: location of the data db sql file
/// - Parameter seed: ZIP-32 compliant seed bytes for this wallet /// - Parameter seed: ZIP-32 compliant seed bytes for this wallet
/// - Parameter networkType: network type of this key
/// - Returns: `DbInitResult.success` if the dataDb was initialized successfully /// - Returns: `DbInitResult.success` if the dataDb was initialized successfully
/// or `DbInitResult.seedRequired` if the operation requires the seed to be passed /// or `DbInitResult.seedRequired` if the operation requires the seed to be passed
/// in order to be completed successfully. /// in order to be completed successfully.
static func initDataDb( func initDataDb(seed: [UInt8]?) async throws -> DbInitResult
dbData: URL,
seed: [UInt8]?,
networkType: NetworkType
) async throws -> DbInitResult
/// Returns the network and address type for the given Zcash address string, /// Initialize the blocks table from a given checkpoint (heigh, hash, time, saplingTree and networkType).
/// if the string represents a valid Zcash address.
static func getAddressMetadata(_ address: String) -> AddressMetadata?
/// Validates the if the given string is a valid Sapling Address
/// - Parameter address: UTF-8 encoded String to validate
/// - Parameter networkType: network type of this key
/// - Returns: true when the address is valid. Returns false in any other case
/// - Throws: Error when the provided address belongs to another network
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) -> Bool
/// Validates the if the given string is a valid Sapling Extended Full Viewing Key
/// - Parameter key: UTF-8 encoded String to validate
/// - Parameter networkType: network type of this key
/// - Returns: `true` when the Sapling Extended Full Viewing Key is valid. `false` in any other case
/// - Throws: Error when there's another problem not related to validity of the string in question
static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) -> Bool
/// Validates the if the given string is a valid Sapling Extended Spending Key
/// - Returns: `true` when the Sapling Extended Spending Key is valid, false in any other case.
/// - Throws: Error when the key is semantically valid but it belongs to another network
/// - parameter key: String encoded Extended Spending Key
/// - parameter networkType: `NetworkType` signaling testnet or mainnet
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: NetworkType) -> Bool
/// Validates the if the given string is a valid Transparent Address
/// - Parameter address: UTF-8 encoded String to validate
/// - Parameter networkType: network type of this key
/// - Returns: true when the address is valid and transparent. false in any other case
/// - Throws: Error when the provided address belongs to another network
static func isValidTransparentAddress(_ address: String, networkType: NetworkType) -> Bool
/// validates whether a string encoded address is a valid Unified Address.
/// - Parameter address: UTF-8 encoded String to validate
/// - Parameter networkType: network type of this key
/// - Returns: true when the address is valid and transparent. false in any other case
/// - Throws: Error when the provided address belongs to another network
static func isValidUnifiedAddress(_ address: String, networkType: NetworkType) -> Bool
/// verifies that the given string-encoded `UnifiedFullViewingKey` is valid.
/// - Parameter ufvk: UTF-8 encoded String to validate
/// - Parameter networkType: network type of this key
/// - Returns: true when the encoded string is a valid UFVK. false in any other case
/// - Throws: Error when there's another problem not related to validity of the string in question
static func isValidUnifiedFullViewingKey(_ ufvk: String, networkType: NetworkType) -> Bool
/// initialize the blocks table from a given checkpoint (heigh, hash, time, saplingTree and networkType)
/// - parameter dbData: location of the data db
/// - parameter height: represents the block height of the given checkpoint /// - parameter height: represents the block height of the given checkpoint
/// - parameter hash: hash of the merkle tree /// - parameter hash: hash of the merkle tree
/// - parameter time: in milliseconds from reference /// - parameter time: in milliseconds from reference
/// - parameter saplingTree: hash of the sapling tree /// - parameter saplingTree: hash of the sapling tree
/// - parameter networkType: `NetworkType` signaling testnet or mainnet func initBlocksTable(
static func initBlocksTable(
dbData: URL,
height: Int32, height: Int32,
hash: String, hash: String,
time: UInt32, time: UInt32,
saplingTree: String, saplingTree: String
networkType: NetworkType ) async throws
) async throws // swiftlint:disable function_parameter_count
/// Returns a list of the transparent receivers for the diversified unified addresses that have /// Returns a list of the transparent receivers for the diversified unified addresses that have
/// been allocated for the provided account. /// been allocated for the provided account.
/// - parameter dbData: location of the data db
/// - parameter account: index of the given account /// - parameter account: index of the given account
/// - parameter networkType: the network type func listTransparentReceivers(account: Int32) async throws -> [TransparentAddress]
static func listTransparentReceivers(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> [TransparentAddress]
/// get the verified balance from the given account /// Get the verified balance from the given account
/// - parameter dbData: location of the data db
/// - parameter account: index of the given account /// - parameter account: index of the given account
/// - parameter networkType: the network type func getVerifiedBalance(account: Int32) async throws -> Int64
static func getVerifiedBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64
/// Get the verified cached transparent balance for the given account /// Get the verified cached transparent balance for the given account
/// - parameter dbData: location of the data db
/// - parameter account: account index to query the balance for. /// - parameter account: account index to query the balance for.
/// - parameter networkType: the network type func getVerifiedTransparentBalance(account: Int32) async throws -> Int64
static func getVerifiedTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64
/// Checks that the scanned blocks in the data database, when combined with the recent /// Checks that the scanned blocks in the data database, when combined with the recent
/// `CompactBlock`s in the cache database, form a valid chain. /// `CompactBlock`s in the cache database, form a valid chain.
@ -333,39 +161,22 @@ protocol ZcashRustBackendWelding {
/// - parameter dbData: location of the data db file /// - parameter dbData: location of the data db file
/// - parameter networkType: the network type /// - parameter networkType: the network type
/// - parameter limit: a limit to validate a fixed number of blocks instead of the whole cache. /// - parameter limit: a limit to validate a fixed number of blocks instead of the whole cache.
/// - Returns: /// - Throws:
/// - `-1` if the combined chain is valid. /// - `RustWeldingError.chainValidationFailed` if there was an error during validation unrelated to chain validity.
/// - `upper_bound` if the combined chain is invalid. /// - `RustWeldingError.invalidChain(upperBound)` if the combined chain is invalid. `upperBound` is the height of the highest invalid block
/// - `upper_bound` is the height of the highest invalid block (on the assumption that the highest block in the cache database is correct). /// (on the assumption that the highest block in the cache database is correct).
/// - `0` if there was an error during validation unrelated to chain validity. ///
/// - Important: This function does not mutate either of the databases. /// - Important: This function does not mutate either of the databases.
static func validateCombinedChain( func validateCombinedChain(limit: UInt32) async throws
fsBlockDbRoot: URL,
dbData: URL,
networkType: NetworkType,
limit: UInt32
) async -> Int32
/// Resets the state of the database to only contain block and transaction information up to the given height. clears up all derived data as well /// Resets the state of the database to only contain block and transaction information up to the given height. clears up all derived data as well
/// - parameter dbData: `URL` pointing to the filesystem root directory where the fsBlock cache is.
/// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename
/// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions.
/// - parameter height: height to rewind to. /// - parameter height: height to rewind to.
static func rewindToHeight( func rewindToHeight(height: Int32) async throws
dbData: URL,
height: Int32,
networkType: NetworkType
) async -> Bool
/// Resets the state of the FsBlock database to only contain block and transaction information up to the given height. /// Resets the state of the FsBlock database to only contain block and transaction information up to the given height.
/// - Note: this does not delete the files. Only rolls back the database. /// - Note: this does not delete the files. Only rolls back the database.
/// - parameter fsBlockDbRoot: location of the data db file /// - parameter height: height to rewind to. DON'T PASS ARBITRARY HEIGHT. Use `getNearestRewindHeight` when unsure
/// - parameter height: height to rewind to. DON'T PASS ARBITRARY HEIGHT. Use getNearestRewindHeight when unsure func rewindCacheToHeight(height: Int32) async throws
/// - parameter networkType: the network type
static func rewindCacheToHeight(
fsBlockDbRoot: URL,
height: Int32
) async -> Bool
/// Scans new blocks added to the cache for any transactions received by the tracked /// Scans new blocks added to the cache for any transactions received by the tracked
/// accounts. /// accounts.
@ -379,101 +190,48 @@ protocol ZcashRustBackendWelding {
/// Scanned blocks are required to be height-sequential. If a block is missing from the /// Scanned blocks are required to be height-sequential. If a block is missing from the
/// cache, an error will be signalled. /// cache, an error will be signalled.
/// ///
/// - parameter fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is.
/// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename
/// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions.
/// - parameter dbData: location of the data db sqlite file
/// - parameter limit: scan up to limit blocks. pass 0 to set no limit. /// - parameter limit: scan up to limit blocks. pass 0 to set no limit.
/// - parameter networkType: the network type func scanBlocks(limit: UInt32) async throws
/// returns false if fails to scan.
static func scanBlocks(
fsBlockDbRoot: URL,
dbData: URL,
limit: UInt32,
networkType: NetworkType
) async -> Bool
/// Upserts a UTXO into the data db database /// Upserts a UTXO into the data db database
/// - parameter dbData: location of the data db file
/// - parameter txid: the txid bytes for the UTXO /// - parameter txid: the txid bytes for the UTXO
/// - parameter index: the index of the UTXO /// - parameter index: the index of the UTXO
/// - parameter script: the script of the UTXO /// - parameter script: the script of the UTXO
/// - parameter value: the value of the UTXO /// - parameter value: the value of the UTXO
/// - parameter height: the mined height for the UTXO /// - parameter height: the mined height for the UTXO
/// - parameter networkType: the network type func putUnspentTransparentOutput(
/// - Returns: true if the operation succeded or false otherwise
static func putUnspentTransparentOutput(
dbData: URL,
txid: [UInt8], txid: [UInt8],
index: Int, index: Int,
script: [UInt8], script: [UInt8],
value: Int64, value: Int64,
height: BlockHeight, height: BlockHeight
networkType: NetworkType ) async throws
) async throws -> Bool
/// Creates a transaction to shield all found UTXOs in data db for the account the provided `UnifiedSpendingKey` has spend authority for. /// Creates a transaction to shield all found UTXOs in data db for the account the provided `UnifiedSpendingKey` has spend authority for.
/// - Parameter dbData: URL for the Data DB
/// - Parameter usk: `UnifiedSpendingKey` that spend transparent funds and where the funds will be shielded to. /// - Parameter usk: `UnifiedSpendingKey` that spend transparent funds and where the funds will be shielded to.
/// - Parameter memo: the `Memo` for this transaction /// - Parameter memo: the `Memo` for this transaction
/// - Parameter spendParamsPath: path escaped String for the filesystem locations where the spend parameters are located func shieldFunds(
/// - Parameter outputParamsPath: path escaped String for the filesystem locations where the output parameters are located
/// - Parameter networkType: the network type
static func shieldFunds(
dbData: URL,
usk: UnifiedSpendingKey, usk: UnifiedSpendingKey,
memo: MemoBytes?, memo: MemoBytes?,
shieldingThreshold: Zatoshi, shieldingThreshold: Zatoshi
spendParamsPath: String, ) async throws -> Int64
outputParamsPath: String,
networkType: NetworkType
) async -> Int64
/// Obtains the available receiver typecodes for the given String encoded Unified Address
/// - Parameter address: public key represented as a String
/// - Returns the `[UInt32]` that compose the given UA
/// - Throws `RustWeldingError.invalidInput(message: String)` when the UA is either invalid or malformed
static func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32]
/// Gets the consensus branch id for the given height /// Gets the consensus branch id for the given height
/// - Parameter height: the height you what to know the branch id for /// - Parameter height: the height you what to know the branch id for
/// - Parameter networkType: the network type func consensusBranchIdFor(height: Int32) throws -> Int32
static func consensusBranchIdFor(
height: Int32,
networkType: NetworkType
) throws -> Int32
/// Derives a `UnifiedFullViewingKey` from a `UnifiedSpendingKey` /// Initializes Filesystem based block cache
/// - Parameter spendingKey: the `UnifiedSpendingKey` to derive from
/// - Parameter networkType: the network type
/// - Throws: `RustWeldingError.unableToDeriveKeys` if the SDK couldn't derive the UFVK.
/// - Returns: the derived `UnifiedFullViewingKey`
static func deriveUnifiedFullViewingKey(
from spendingKey: UnifiedSpendingKey,
networkType: NetworkType
) throws -> UnifiedFullViewingKey
/// initializes Filesystem based block cache
/// - Parameter fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is.
/// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename
/// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions.
/// - returns `true` when successful, `false` when fails but no throwing information was found
/// - throws `RustWeldingError` when fails to initialize /// - throws `RustWeldingError` when fails to initialize
static func initBlockMetadataDb(fsBlockDbRoot: URL) async throws -> Bool func initBlockMetadataDb() async throws
/// Write compact block metadata to a database known to the Rust layer /// Write compact block metadata to a database known to the Rust layer
/// - Parameter fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is. /// - Parameter blocks: The `ZcashCompactBlock`s that are going to be marked as stored by the metadata Db.
/// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename func writeBlocksMetadata(blocks: [ZcashCompactBlock]) async throws
/// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions.
/// - Parameter blocks: The `ZcashCompactBlock`s that are going to be marked as stored by the
/// metadata Db.
/// - Returns `true` if the operation was successful, `false` otherwise.
static func writeBlocksMetadata(fsBlockDbRoot: URL, blocks: [ZcashCompactBlock]) async throws -> Bool
/// Gets the latest block height stored in the filesystem based cache. /// Gets the latest block height stored in the filesystem based cache.
/// - Parameter fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is. /// - Parameter fsBlockDbRoot: `URL` pointing to the filesystem root directory where the fsBlock cache is.
/// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename /// this directory is expected to contain a `/blocks` sub-directory with the blocks stored in the convened filename
/// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions. /// format `{height}-{hash}-block`. This directory has must be granted both write and read permissions.
/// - Returns `BlockHeight` of the latest cached block or `.empty` if no blocks are stored. /// - Returns `BlockHeight` of the latest cached block or `.empty` if no blocks are stored.
static func latestCachedBlockHeight(fsBlockDbRoot: URL) async -> BlockHeight func latestCachedBlockHeight() async -> BlockHeight
} }

View File

@ -230,24 +230,34 @@ public protocol Synchronizer: AnyObject {
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
/// Get all memos for `transaction`. /// Get all memos for `transaction`.
///
// sourcery: mockedName="getMemosForClearedTransaction"
func getMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo] func getMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo]
/// Get all memos for `receivedTransaction`. /// Get all memos for `receivedTransaction`.
///
// sourcery: mockedName="getMemosForReceivedTransaction"
func getMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] func getMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo]
/// Get all memos for `sentTransaction`. /// Get all memos for `sentTransaction`.
///
// sourcery: mockedName="getMemosForSentTransaction"
func getMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] func getMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo]
/// Attempt to get recipients from a Transaction Overview. /// Attempt to get recipients from a Transaction Overview.
/// - parameter transaction: A transaction overview /// - parameter transaction: A transaction overview
/// - returns the recipients or an empty array if no recipients are found on this transaction because it's not an outgoing /// - returns the recipients or an empty array if no recipients are found on this transaction because it's not an outgoing
/// transaction /// transaction
///
// sourcery: mockedName="getRecipientsForClearedTransaction"
func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient] func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient]
/// Get the recipients for the given a sent transaction /// Get the recipients for the given a sent transaction
/// - parameter transaction: A transaction overview /// - parameter transaction: A transaction overview
/// - returns the recipients or an empty array if no recipients are found on this transaction because it's not an outgoing /// - returns the recipients or an empty array if no recipients are found on this transaction because it's not an outgoing
/// transaction /// transaction
///
// sourcery: mockedName="getRecipientsForSentTransaction"
func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient] func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient]
/// Returns a list of confirmed transactions that preceed the given transaction with a limit count. /// Returns a list of confirmed transactions that preceed the given transaction with a limit count.

View File

@ -37,37 +37,37 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
completion: @escaping (Result<Initializer.InitializationResult, Error>) -> Void completion: @escaping (Result<Initializer.InitializationResult, Error>) -> Void
) { ) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
return try await self.synchronizer.prepare(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday) return try await self.synchronizer.prepare(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday)
} }
} }
public func start(retry: Bool, completion: @escaping (Error?) -> Void) { public func start(retry: Bool, completion: @escaping (Error?) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.start(retry: retry) try await self.synchronizer.start(retry: retry)
} }
} }
public func stop(completion: @escaping () -> Void) { public func stop(completion: @escaping () -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.stop() await self.synchronizer.stop()
} }
} }
public func getSaplingAddress(accountIndex: Int, completion: @escaping (Result<SaplingAddress, Error>) -> Void) { public func getSaplingAddress(accountIndex: Int, completion: @escaping (Result<SaplingAddress, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getSaplingAddress(accountIndex: accountIndex) try await self.synchronizer.getSaplingAddress(accountIndex: accountIndex)
} }
} }
public func getUnifiedAddress(accountIndex: Int, completion: @escaping (Result<UnifiedAddress, Error>) -> Void) { public func getUnifiedAddress(accountIndex: Int, completion: @escaping (Result<UnifiedAddress, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getUnifiedAddress(accountIndex: accountIndex) try await self.synchronizer.getUnifiedAddress(accountIndex: accountIndex)
} }
} }
public func getTransparentAddress(accountIndex: Int, completion: @escaping (Result<TransparentAddress, Error>) -> Void) { public func getTransparentAddress(accountIndex: Int, completion: @escaping (Result<TransparentAddress, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getTransparentAddress(accountIndex: accountIndex) try await self.synchronizer.getTransparentAddress(accountIndex: accountIndex)
} }
} }
@ -79,7 +79,7 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
memo: Memo?, memo: Memo?,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) { ) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo) try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo)
} }
} }
@ -90,37 +90,37 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
shieldingThreshold: Zatoshi, shieldingThreshold: Zatoshi,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) { ) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
} }
} }
public func cancelSpend(transaction: PendingTransactionEntity, completion: @escaping (Bool) -> Void) { public func cancelSpend(transaction: PendingTransactionEntity, completion: @escaping (Bool) -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.cancelSpend(transaction: transaction) await self.synchronizer.cancelSpend(transaction: transaction)
} }
} }
public func pendingTransactions(completion: @escaping ([PendingTransactionEntity]) -> Void) { public func pendingTransactions(completion: @escaping ([PendingTransactionEntity]) -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.pendingTransactions await self.synchronizer.pendingTransactions
} }
} }
public func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) { public func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.clearedTransactions await self.synchronizer.clearedTransactions
} }
} }
public func sentTranscations(completion: @escaping ([ZcashTransaction.Sent]) -> Void) { public func sentTranscations(completion: @escaping ([ZcashTransaction.Sent]) -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.sentTransactions await self.synchronizer.sentTransactions
} }
} }
public func receivedTransactions(completion: @escaping ([ZcashTransaction.Received]) -> Void) { public func receivedTransactions(completion: @escaping ([ZcashTransaction.Received]) -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.receivedTransactions await self.synchronizer.receivedTransactions
} }
} }
@ -128,31 +128,31 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
public func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository { synchronizer.paginatedTransactions(of: kind) } public func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository { synchronizer.paginatedTransactions(of: kind) }
public func getMemos(for transaction: ZcashTransaction.Overview, completion: @escaping (Result<[Memo], Error>) -> Void) { public func getMemos(for transaction: ZcashTransaction.Overview, completion: @escaping (Result<[Memo], Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getMemos(for: transaction) try await self.synchronizer.getMemos(for: transaction)
} }
} }
public func getMemos(for receivedTransaction: ZcashTransaction.Received, completion: @escaping (Result<[Memo], Error>) -> Void) { public func getMemos(for receivedTransaction: ZcashTransaction.Received, completion: @escaping (Result<[Memo], Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getMemos(for: receivedTransaction) try await self.synchronizer.getMemos(for: receivedTransaction)
} }
} }
public func getMemos(for sentTransaction: ZcashTransaction.Sent, completion: @escaping (Result<[Memo], Error>) -> Void) { public func getMemos(for sentTransaction: ZcashTransaction.Sent, completion: @escaping (Result<[Memo], Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getMemos(for: sentTransaction) try await self.synchronizer.getMemos(for: sentTransaction)
} }
} }
public func getRecipients(for transaction: ZcashTransaction.Overview, completion: @escaping ([TransactionRecipient]) -> Void) { public func getRecipients(for transaction: ZcashTransaction.Overview, completion: @escaping ([TransactionRecipient]) -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.getRecipients(for: transaction) await self.synchronizer.getRecipients(for: transaction)
} }
} }
public func getRecipients(for transaction: ZcashTransaction.Sent, completion: @escaping ([TransactionRecipient]) -> Void) { public func getRecipients(for transaction: ZcashTransaction.Sent, completion: @escaping ([TransactionRecipient]) -> Void) {
executeAction(completion) { AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.getRecipients(for: transaction) await self.synchronizer.getRecipients(for: transaction)
} }
} }
@ -162,37 +162,37 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
limit: Int, limit: Int,
completion: @escaping (Result<[ZcashTransaction.Overview], Error>) -> Void completion: @escaping (Result<[ZcashTransaction.Overview], Error>) -> Void
) { ) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit) try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit)
} }
} }
public func latestHeight(completion: @escaping (Result<BlockHeight, Error>) -> Void) { public func latestHeight(completion: @escaping (Result<BlockHeight, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.latestHeight() try await self.synchronizer.latestHeight()
} }
} }
public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight, completion: @escaping (Result<RefreshedUTXOs, Error>) -> Void) { public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight, completion: @escaping (Result<RefreshedUTXOs, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.refreshUTXOs(address: address, from: height) try await self.synchronizer.refreshUTXOs(address: address, from: height)
} }
} }
public func getTransparentBalance(accountIndex: Int, completion: @escaping (Result<WalletBalance, Error>) -> Void) { public func getTransparentBalance(accountIndex: Int, completion: @escaping (Result<WalletBalance, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getTransparentBalance(accountIndex: accountIndex) try await self.synchronizer.getTransparentBalance(accountIndex: accountIndex)
} }
} }
public func getShieldedBalance(accountIndex: Int, completion: @escaping (Result<Zatoshi, Error>) -> Void) { public func getShieldedBalance(accountIndex: Int, completion: @escaping (Result<Zatoshi, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getShieldedBalance(accountIndex: accountIndex) try await self.synchronizer.getShieldedBalance(accountIndex: accountIndex)
} }
} }
public func getShieldedVerifiedBalance(accountIndex: Int, completion: @escaping (Result<Zatoshi, Error>) -> Void) { public func getShieldedVerifiedBalance(accountIndex: Int, completion: @escaping (Result<Zatoshi, Error>) -> Void) {
executeThrowingAction(completion) { AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getShieldedVerifiedBalance(accountIndex: accountIndex) try await self.synchronizer.getShieldedVerifiedBalance(accountIndex: accountIndex)
} }
} }
@ -204,41 +204,3 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
public func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error> { synchronizer.rewind(policy) } public func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error> { synchronizer.rewind(policy) }
public func wipe() -> CompletablePublisher<Error> { synchronizer.wipe() } public func wipe() -> CompletablePublisher<Error> { synchronizer.wipe() }
} }
extension ClosureSDKSynchronizer {
private func executeAction(_ completion: @escaping () -> Void, action: @escaping () async -> Void) {
Task {
await action()
completion()
}
}
private func executeAction<R>(_ completion: @escaping (R) -> Void, action: @escaping () async -> R) {
Task {
let result = await action()
completion(result)
}
}
private func executeThrowingAction(_ completion: @escaping (Error?) -> Void, action: @escaping () async throws -> Void) {
Task {
do {
try await action()
completion(nil)
} catch {
completion(error)
}
}
}
private func executeThrowingAction<R>(_ completion: @escaping (Result<R, Error>) -> Void, action: @escaping () async throws -> R) {
Task {
do {
let result = try await action()
completion(.success(result))
} catch {
completion(.failure(error))
}
}
}
}

View File

@ -36,37 +36,37 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
viewingKeys: [UnifiedFullViewingKey], viewingKeys: [UnifiedFullViewingKey],
walletBirthday: BlockHeight walletBirthday: BlockHeight
) -> SinglePublisher<Initializer.InitializationResult, Error> { ) -> SinglePublisher<Initializer.InitializationResult, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
return try await self.synchronizer.prepare(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday) return try await self.synchronizer.prepare(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday)
} }
} }
public func start(retry: Bool) -> CompletablePublisher<Error> { public func start(retry: Bool) -> CompletablePublisher<Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.start(retry: retry) try await self.synchronizer.start(retry: retry)
} }
} }
public func stop() -> CompletablePublisher<Never> { public func stop() -> CompletablePublisher<Never> {
return executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.stop() await self.synchronizer.stop()
} }
} }
public func getSaplingAddress(accountIndex: Int) -> SinglePublisher<SaplingAddress, Error> { public func getSaplingAddress(accountIndex: Int) -> SinglePublisher<SaplingAddress, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getSaplingAddress(accountIndex: accountIndex) try await self.synchronizer.getSaplingAddress(accountIndex: accountIndex)
} }
} }
public func getUnifiedAddress(accountIndex: Int) -> SinglePublisher<UnifiedAddress, Error> { public func getUnifiedAddress(accountIndex: Int) -> SinglePublisher<UnifiedAddress, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getUnifiedAddress(accountIndex: accountIndex) try await self.synchronizer.getUnifiedAddress(accountIndex: accountIndex)
} }
} }
public func getTransparentAddress(accountIndex: Int) -> SinglePublisher<TransparentAddress, Error> { public func getTransparentAddress(accountIndex: Int) -> SinglePublisher<TransparentAddress, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getTransparentAddress(accountIndex: accountIndex) try await self.synchronizer.getTransparentAddress(accountIndex: accountIndex)
} }
} }
@ -77,7 +77,7 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
toAddress: Recipient, toAddress: Recipient,
memo: Memo? memo: Memo?
) -> SinglePublisher<PendingTransactionEntity, Error> { ) -> SinglePublisher<PendingTransactionEntity, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo) try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo)
} }
} }
@ -87,37 +87,37 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi shieldingThreshold: Zatoshi
) -> SinglePublisher<PendingTransactionEntity, Error> { ) -> SinglePublisher<PendingTransactionEntity, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
} }
} }
public func cancelSpend(transaction: PendingTransactionEntity) -> SinglePublisher<Bool, Never> { public func cancelSpend(transaction: PendingTransactionEntity) -> SinglePublisher<Bool, Never> {
executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.cancelSpend(transaction: transaction) await self.synchronizer.cancelSpend(transaction: transaction)
} }
} }
public var pendingTransactions: SinglePublisher<[PendingTransactionEntity], Never> { public var pendingTransactions: SinglePublisher<[PendingTransactionEntity], Never> {
executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.pendingTransactions await self.synchronizer.pendingTransactions
} }
} }
public var clearedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { public var clearedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> {
executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.clearedTransactions await self.synchronizer.clearedTransactions
} }
} }
public var sentTransactions: SinglePublisher<[ZcashTransaction.Sent], Never> { public var sentTransactions: SinglePublisher<[ZcashTransaction.Sent], Never> {
executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.sentTransactions await self.synchronizer.sentTransactions
} }
} }
public var receivedTransactions: SinglePublisher<[ZcashTransaction.Received], Never> { public var receivedTransactions: SinglePublisher<[ZcashTransaction.Received], Never> {
executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.receivedTransactions await self.synchronizer.receivedTransactions
} }
} }
@ -125,67 +125,67 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
public func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository { synchronizer.paginatedTransactions(of: kind) } public func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository { synchronizer.paginatedTransactions(of: kind) }
public func getMemos(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[Memo], Error> { public func getMemos(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[Memo], Error> {
executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getMemos(for: transaction) try await self.synchronizer.getMemos(for: transaction)
} }
} }
public func getMemos(for receivedTransaction: ZcashTransaction.Received) -> SinglePublisher<[Memo], Error> { public func getMemos(for receivedTransaction: ZcashTransaction.Received) -> SinglePublisher<[Memo], Error> {
executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getMemos(for: receivedTransaction) try await self.synchronizer.getMemos(for: receivedTransaction)
} }
} }
public func getMemos(for sentTransaction: ZcashTransaction.Sent) -> SinglePublisher<[Memo], Error> { public func getMemos(for sentTransaction: ZcashTransaction.Sent) -> SinglePublisher<[Memo], Error> {
executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getMemos(for: sentTransaction) try await self.synchronizer.getMemos(for: sentTransaction)
} }
} }
public func getRecipients(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[TransactionRecipient], Never> { public func getRecipients(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[TransactionRecipient], Never> {
executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.getRecipients(for: transaction) await self.synchronizer.getRecipients(for: transaction)
} }
} }
public func getRecipients(for transaction: ZcashTransaction.Sent) -> SinglePublisher<[TransactionRecipient], Never> { public func getRecipients(for transaction: ZcashTransaction.Sent) -> SinglePublisher<[TransactionRecipient], Never> {
executeAction() { AsyncToCombineGateway.executeAction() {
await self.synchronizer.getRecipients(for: transaction) await self.synchronizer.getRecipients(for: transaction)
} }
} }
public func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error> { public func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) -> SinglePublisher<[ZcashTransaction.Overview], Error> {
executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit) try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit)
} }
} }
public func latestHeight() -> SinglePublisher<BlockHeight, Error> { public func latestHeight() -> SinglePublisher<BlockHeight, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.latestHeight() try await self.synchronizer.latestHeight()
} }
} }
public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) -> SinglePublisher<RefreshedUTXOs, Error> { public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) -> SinglePublisher<RefreshedUTXOs, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.refreshUTXOs(address: address, from: height) try await self.synchronizer.refreshUTXOs(address: address, from: height)
} }
} }
public func getTransparentBalance(accountIndex: Int) -> SinglePublisher<WalletBalance, Error> { public func getTransparentBalance(accountIndex: Int) -> SinglePublisher<WalletBalance, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getTransparentBalance(accountIndex: accountIndex) try await self.synchronizer.getTransparentBalance(accountIndex: accountIndex)
} }
} }
public func getShieldedBalance(accountIndex: Int = 0) -> SinglePublisher<Zatoshi, Error> { public func getShieldedBalance(accountIndex: Int = 0) -> SinglePublisher<Zatoshi, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await synchronizer.getShieldedBalance(accountIndex: accountIndex) try await synchronizer.getShieldedBalance(accountIndex: accountIndex)
} }
} }
public func getShieldedVerifiedBalance(accountIndex: Int = 0) -> SinglePublisher<Zatoshi, Error> { public func getShieldedVerifiedBalance(accountIndex: Int = 0) -> SinglePublisher<Zatoshi, Error> {
return executeThrowingAction() { AsyncToCombineGateway.executeThrowingAction() {
try await synchronizer.getShieldedVerifiedBalance(accountIndex: accountIndex) try await synchronizer.getShieldedVerifiedBalance(accountIndex: accountIndex)
} }
} }
@ -193,53 +193,3 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
public func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error> { synchronizer.rewind(policy) } public func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error> { synchronizer.rewind(policy) }
public func wipe() -> CompletablePublisher<Error> { synchronizer.wipe() } public func wipe() -> CompletablePublisher<Error> { synchronizer.wipe() }
} }
extension CombineSDKSynchronizer {
private func executeAction(action: @escaping () async -> Void) -> CompletablePublisher<Never> {
let subject = PassthroughSubject<Void, Never>()
Task {
await action()
subject.send(completion: .finished)
}
return subject.eraseToAnyPublisher()
}
private func executeAction<R>(action: @escaping () async -> R) -> SinglePublisher<R, Never> {
let subject = PassthroughSubject<R, Never>()
Task {
let result = await action()
subject.send(result)
subject.send(completion: .finished)
}
return subject.eraseToAnyPublisher()
}
private func executeThrowingAction(action: @escaping () async throws -> Void) -> CompletablePublisher<Error> {
let subject = PassthroughSubject<Void, Error>()
Task {
do {
try await action()
subject.send(completion: .finished)
} catch {
subject.send(completion: .failure(error))
}
}
return subject.eraseToAnyPublisher()
}
private func executeThrowingAction<R>(action: @escaping () async throws -> R) -> SinglePublisher<R, Error> {
let subject = PassthroughSubject<R, Error>()
Task {
do {
let result = try await action()
subject.send(result)
subject.send(completion: .finished)
} catch {
subject.send(completion: .failure(error))
}
}
return subject.eraseToAnyPublisher()
}
}

View File

@ -458,21 +458,13 @@ public class SDKSynchronizer: Synchronizer {
} }
public func getShieldedBalance(accountIndex: Int = 0) async throws -> Zatoshi { public func getShieldedBalance(accountIndex: Int = 0) async throws -> Zatoshi {
let balance = try await initializer.rustBackend.getBalance( let balance = try await initializer.rustBackend.getBalance(account: Int32(accountIndex))
dbData: initializer.dataDbURL,
account: Int32(accountIndex),
networkType: network.networkType
)
return Zatoshi(balance) return Zatoshi(balance)
} }
public func getShieldedVerifiedBalance(accountIndex: Int = 0) async throws -> Zatoshi { public func getShieldedVerifiedBalance(accountIndex: Int = 0) async throws -> Zatoshi {
let balance = try await initializer.rustBackend.getVerifiedBalance( let balance = try await initializer.rustBackend.getVerifiedBalance(account: Int32(accountIndex))
dbData: initializer.dataDbURL,
account: Int32(accountIndex),
networkType: network.networkType
)
return Zatoshi(balance) return Zatoshi(balance)
} }
@ -617,7 +609,6 @@ public class SDKSynchronizer: Synchronizer {
newState = await snapshotState(status: newStatus) newState = await snapshotState(status: newStatus)
} else { } else {
newState = await SynchronizerState( newState = await SynchronizerState(
syncSessionID: syncSession.value, syncSessionID: syncSession.value,
shieldedBalance: latestState.shieldedBalance, shieldedBalance: latestState.shieldedBalance,

View File

@ -5,17 +5,14 @@
// Created by Francisco Gindre on 10/8/20. // Created by Francisco Gindre on 10/8/20.
// //
import Combine
import Foundation import Foundation
public protocol KeyValidation { public protocol KeyValidation {
func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool
func isValidTransparentAddress(_ tAddress: String) -> Bool func isValidTransparentAddress(_ tAddress: String) -> Bool
func isValidSaplingAddress(_ zAddress: String) -> Bool func isValidSaplingAddress(_ zAddress: String) -> Bool
func isValidSaplingExtendedSpendingKey(_ extsk: String) -> Bool func isValidSaplingExtendedSpendingKey(_ extsk: String) -> Bool
func isValidUnifiedAddress(_ unifiedAddress: String) -> Bool func isValidUnifiedAddress(_ unifiedAddress: String) -> Bool
} }
@ -25,26 +22,39 @@ public protocol KeyDeriving {
/// - Parameter accountNumber: `Int` with the account number /// - Parameter accountNumber: `Int` with the account number
/// - Throws `.unableToDerive` if there's a problem deriving this key /// - Throws `.unableToDerive` if there's a problem deriving this key
/// - Returns a `UnifiedSpendingKey` /// - Returns a `UnifiedSpendingKey`
func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) throws -> UnifiedSpendingKey func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) async throws -> UnifiedSpendingKey
func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int, completion: @escaping (Result<UnifiedSpendingKey, Error>) -> Void)
func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) -> SinglePublisher<UnifiedSpendingKey, Error>
/// Given a spending key, return the associated viewing key.
/// - Parameter spendingKey: the `UnifiedSpendingKey` from which to derive the `UnifiedFullViewingKey` from.
/// - Returns: the viewing key that corresponds to the spending key.
func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) async throws -> UnifiedFullViewingKey
func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey, completion: @escaping (Result<UnifiedFullViewingKey, Error>) -> Void)
func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) -> SinglePublisher<UnifiedFullViewingKey, Error>
/// Extracts the `SaplingAddress` from the given `UnifiedAddress` /// Extracts the `SaplingAddress` from the given `UnifiedAddress`
/// - Parameter address: the `UnifiedAddress` /// - Parameter address: the `UnifiedAddress`
/// - Throws `KeyDerivationErrors.receiverNotFound` if the receiver is not present /// - Throws `KeyDerivationErrors.receiverNotFound` if the receiver is not present
static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress
/// Extracts the `TransparentAddress` from the given `UnifiedAddress` /// Extracts the `TransparentAddress` from the given `UnifiedAddress`
/// - Parameter address: the `UnifiedAddress` /// - Parameter address: the `UnifiedAddress`
/// - Throws `KeyDerivationErrors.receiverNotFound` if the receiver is not present /// - Throws `KeyDerivationErrors.receiverNotFound` if the receiver is not present
static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress
/// Extracts the `UnifiedAddress.ReceiverTypecodes` from the given `UnifiedAddress` /// Extracts the `UnifiedAddress.ReceiverTypecodes` from the given `UnifiedAddress`
/// - Parameter address: the `UnifiedAddress` /// - Parameter address: the `UnifiedAddress`
/// - Throws /// - Throws
static func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes] func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes]
static func getAddressMetadata(_ addr: String) -> AddressMetadata?
} }
public enum KeyDerivationErrors: Error { public enum KeyDerivationErrors: Error {
case derivationError(underlyingError: Error) case derivationError(underlyingError: Error)
// When something happens that is not related to derivation itself happens. For example if self is nil in closure.
case genericOtherError
case unableToDerive case unableToDerive
case invalidInput case invalidInput
case invalidUnifiedAddress case invalidUnifiedAddress
@ -52,31 +62,43 @@ public enum KeyDerivationErrors: Error {
} }
public class DerivationTool: KeyDeriving { public class DerivationTool: KeyDeriving {
static var rustwelding: ZcashRustBackendWelding.Type = ZcashRustBackend.self let backend: ZcashKeyDerivationBackendWelding
var networkType: NetworkType init(networkType: NetworkType) {
self.backend = ZcashKeyDerivationBackend(networkType: networkType)
public init(networkType: NetworkType) {
self.networkType = networkType
} }
public static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress { public func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress {
try rustwelding.getSaplingReceiver(for: unifiedAddress) try backend.getSaplingReceiver(for: unifiedAddress)
} }
public static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress { public func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress {
try rustwelding.getTransparentReceiver(for: unifiedAddress) try backend.getTransparentReceiver(for: unifiedAddress)
} }
public static func getAddressMetadata(_ addr: String) -> AddressMetadata? { public static func getAddressMetadata(_ addr: String) -> AddressMetadata? {
rustwelding.getAddressMetadata(addr) ZcashKeyDerivationBackend.getAddressMetadata(addr)
} }
/// Given a spending key, return the associated viewing key. /// Given a spending key, return the associated viewing key.
/// - Parameter spendingKey: the `UnifiedSpendingKey` from which to derive the `UnifiedFullViewingKey` from. /// - Parameter spendingKey: the `UnifiedSpendingKey` from which to derive the `UnifiedFullViewingKey` from.
/// - Returns: the viewing key that corresponds to the spending key. /// - Returns: the viewing key that corresponds to the spending key.
public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) throws -> UnifiedFullViewingKey { public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) async throws -> UnifiedFullViewingKey {
try DerivationTool.rustwelding.deriveUnifiedFullViewingKey(from: spendingKey, networkType: self.networkType) try await backend.deriveUnifiedFullViewingKey(from: spendingKey)
}
public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey, completion: @escaping (Result<UnifiedFullViewingKey, Error>) -> Void) {
AsyncToClosureGateway.executeThrowingAction(completion) { [weak self] in
guard let self else { throw KeyDerivationErrors.genericOtherError }
return try await self.deriveUnifiedFullViewingKey(from: spendingKey)
}
}
public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) -> SinglePublisher<UnifiedFullViewingKey, Error> {
AsyncToCombineGateway.executeThrowingAction() { [weak self] in
guard let self else { throw KeyDerivationErrors.genericOtherError }
return try await self.deriveUnifiedFullViewingKey(from: spendingKey)
}
} }
/// Given a seed and a number of accounts, return the associated spending keys. /// Given a seed and a number of accounts, return the associated spending keys.
@ -84,24 +106,34 @@ public class DerivationTool: KeyDeriving {
/// - Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully /// - Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
/// supported so the default value of 1 is recommended. /// supported so the default value of 1 is recommended.
/// - Returns: the spending keys that correspond to the seed, formatted as Strings. /// - Returns: the spending keys that correspond to the seed, formatted as Strings.
public func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) throws -> UnifiedSpendingKey { public func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) async throws -> UnifiedSpendingKey {
guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else { guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else {
throw KeyDerivationErrors.invalidInput throw KeyDerivationErrors.invalidInput
} }
do { do {
return try DerivationTool.rustwelding.deriveUnifiedSpendingKey( return try await backend.deriveUnifiedSpendingKey(from: seed, accountIndex: accountIndex)
from: seed,
accountIndex: accountIndex,
networkType: self.networkType
)
} catch { } catch {
throw KeyDerivationErrors.unableToDerive throw KeyDerivationErrors.unableToDerive
} }
} }
public static func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes] { public func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int, completion: @escaping (Result<UnifiedSpendingKey, Error>) -> Void) {
AsyncToClosureGateway.executeThrowingAction(completion) { [weak self] in
guard let self else { throw KeyDerivationErrors.genericOtherError }
return try await self.deriveUnifiedSpendingKey(seed: seed, accountIndex: accountIndex)
}
}
public func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) -> SinglePublisher<UnifiedSpendingKey, Error> {
AsyncToCombineGateway.executeThrowingAction() { [weak self] in
guard let self else { throw KeyDerivationErrors.genericOtherError }
return try await self.deriveUnifiedSpendingKey(seed: seed, accountIndex: accountIndex)
}
}
public func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes] {
do { do {
return try DerivationTool.rustwelding.receiverTypecodesOnUnifiedAddress(address.stringEncoded) return try backend.receiverTypecodesOnUnifiedAddress(address.stringEncoded)
.map({ UnifiedAddress.ReceiverTypecodes(typecode: $0) }) .map({ UnifiedAddress.ReceiverTypecodes(typecode: $0) })
} catch { } catch {
throw KeyDerivationErrors.invalidUnifiedAddress throw KeyDerivationErrors.invalidUnifiedAddress
@ -121,23 +153,23 @@ public struct AddressMetadata {
extension DerivationTool: KeyValidation { extension DerivationTool: KeyValidation {
public func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool { public func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool {
DerivationTool.rustwelding.isValidUnifiedFullViewingKey(ufvk, networkType: networkType) backend.isValidUnifiedFullViewingKey(ufvk)
} }
public func isValidUnifiedAddress(_ unifiedAddress: String) -> Bool { public func isValidUnifiedAddress(_ unifiedAddress: String) -> Bool {
DerivationTool.rustwelding.isValidUnifiedAddress(unifiedAddress, networkType: networkType) backend.isValidUnifiedAddress(unifiedAddress)
} }
public func isValidTransparentAddress(_ tAddress: String) -> Bool { public func isValidTransparentAddress(_ tAddress: String) -> Bool {
DerivationTool.rustwelding.isValidTransparentAddress(tAddress, networkType: networkType) backend.isValidTransparentAddress(tAddress)
} }
public func isValidSaplingAddress(_ zAddress: String) -> Bool { public func isValidSaplingAddress(_ zAddress: String) -> Bool {
DerivationTool.rustwelding.isValidSaplingAddress(zAddress, networkType: networkType) backend.isValidSaplingAddress(zAddress)
} }
public func isValidSaplingExtendedSpendingKey(_ extsk: String) -> Bool { public func isValidSaplingExtendedSpendingKey(_ extsk: String) -> Bool {
DerivationTool.rustwelding.isValidSaplingExtendedSpendingKey(extsk, networkType: networkType) backend.isValidSaplingExtendedSpendingKey(extsk)
} }
} }
@ -166,8 +198,9 @@ extension UnifiedAddress {
/// already validated by another function. only for internal use. Unless you are /// already validated by another function. only for internal use. Unless you are
/// constructing an address from a primitive function of the FFI, you probably /// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this.. /// shouldn't be using this..
init(validatedEncoding: String) { init(validatedEncoding: String, networkType: NetworkType) {
self.encoding = validatedEncoding self.encoding = validatedEncoding
self.networkType = networkType
} }
} }
@ -206,22 +239,18 @@ public extension UnifiedSpendingKey {
func map<T>(_ transform: (UnifiedSpendingKey) throws -> T) rethrows -> T { func map<T>(_ transform: (UnifiedSpendingKey) throws -> T) rethrows -> T {
try transform(self) try transform(self)
} }
func deriveFullViewingKey() throws -> UnifiedFullViewingKey {
try DerivationTool(networkType: self.network).deriveUnifiedFullViewingKey(from: self)
}
} }
public extension UnifiedAddress { public extension UnifiedAddress {
/// Extracts the sapling receiver from this UA if available /// Extracts the sapling receiver from this UA if available
/// - Returns: an `Optional<SaplingAddress>` /// - Returns: an `Optional<SaplingAddress>`
func saplingReceiver() throws -> SaplingAddress { func saplingReceiver() throws -> SaplingAddress {
try DerivationTool.saplingReceiver(from: self) try DerivationTool(networkType: networkType).saplingReceiver(from: self)
} }
/// Extracts the transparent receiver from this UA if available /// Extracts the transparent receiver from this UA if available
/// - Returns: an `Optional<TransparentAddress>` /// - Returns: an `Optional<TransparentAddress>`
func transparentReceiver() throws -> TransparentAddress { func transparentReceiver() throws -> TransparentAddress {
try DerivationTool.transparentReceiver(from: self) try DerivationTool(networkType: networkType).transparentReceiver(from: self)
} }
} }

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
class WalletTransactionEncoder: TransactionEncoder { class WalletTransactionEncoder: TransactionEncoder {
var rustBackend: ZcashRustBackendWelding.Type var rustBackend: ZcashRustBackendWelding
var repository: TransactionRepository var repository: TransactionRepository
let logger: Logger let logger: Logger
@ -19,7 +19,7 @@ class WalletTransactionEncoder: TransactionEncoder {
private var networkType: NetworkType private var networkType: NetworkType
init( init(
rust: ZcashRustBackendWelding.Type, rustBackend: ZcashRustBackendWelding,
dataDb: URL, dataDb: URL,
fsBlockDbRoot: URL, fsBlockDbRoot: URL,
repository: TransactionRepository, repository: TransactionRepository,
@ -28,7 +28,7 @@ class WalletTransactionEncoder: TransactionEncoder {
networkType: NetworkType, networkType: NetworkType,
logger: Logger logger: Logger
) { ) {
self.rustBackend = rust self.rustBackend = rustBackend
self.dataDbURL = dataDb self.dataDbURL = dataDb
self.fsBlockDbRoot = fsBlockDbRoot self.fsBlockDbRoot = fsBlockDbRoot
self.repository = repository self.repository = repository
@ -40,7 +40,7 @@ class WalletTransactionEncoder: TransactionEncoder {
convenience init(initializer: Initializer) { convenience init(initializer: Initializer) {
self.init( self.init(
rust: initializer.rustBackend, rustBackend: initializer.rustBackend,
dataDb: initializer.dataDbURL, dataDb: initializer.dataDbURL,
fsBlockDbRoot: initializer.fsBlockDbRoot, fsBlockDbRoot: initializer.fsBlockDbRoot,
repository: initializer.transactionRepository, repository: initializer.transactionRepository,
@ -84,22 +84,14 @@ class WalletTransactionEncoder: TransactionEncoder {
guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else { guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else {
throw TransactionEncoderError.missingParams throw TransactionEncoderError.missingParams
} }
let txId = await rustBackend.createToAddress( let txId = try await rustBackend.createToAddress(
dbData: self.dataDbURL,
usk: spendingKey, usk: spendingKey,
to: address, to: address,
value: zatoshi.amount, value: zatoshi.amount,
memo: memoBytes, memo: memoBytes
spendParamsPath: self.spendParamsURL.path,
outputParamsPath: self.outputParamsURL.path,
networkType: networkType
) )
guard txId > 0 else {
throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "create spend failed")
}
return Int(txId) return Int(txId)
} }
@ -134,20 +126,12 @@ class WalletTransactionEncoder: TransactionEncoder {
throw TransactionEncoderError.missingParams throw TransactionEncoderError.missingParams
} }
let txId = await rustBackend.shieldFunds( let txId = try await rustBackend.shieldFunds(
dbData: self.dataDbURL,
usk: spendingKey, usk: spendingKey,
memo: memo, memo: memo,
shieldingThreshold: shieldingThreshold, shieldingThreshold: shieldingThreshold
spendParamsPath: self.spendParamsURL.path,
outputParamsPath: self.outputParamsURL.path,
networkType: networkType
) )
guard txId > 0 else {
throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "create spend failed")
}
return Int(txId) return Int(txId)
} }

View File

@ -0,0 +1,46 @@
//
// AsyncToClosureGateway.swift
//
//
// Created by Michal Fousek on 03.04.2023.
//
import Foundation
enum AsyncToClosureGateway {
static func executeAction(_ completion: @escaping () -> Void, action: @escaping () async -> Void) {
Task {
await action()
completion()
}
}
static func executeAction<R>(_ completion: @escaping (R) -> Void, action: @escaping () async -> R) {
Task {
let result = await action()
completion(result)
}
}
static func executeThrowingAction(_ completion: @escaping (Error?) -> Void, action: @escaping () async throws -> Void) {
Task {
do {
try await action()
completion(nil)
} catch {
completion(error)
}
}
}
static func executeThrowingAction<R>(_ completion: @escaping (Result<R, Error>) -> Void, action: @escaping () async throws -> R) {
Task {
do {
let result = try await action()
completion(.success(result))
} catch {
completion(.failure(error))
}
}
}
}

View File

@ -0,0 +1,59 @@
//
// AsyncToCombineGateway.swift
//
//
// Created by Michal Fousek on 03.04.2023.
//
import Combine
import Foundation
enum AsyncToCombineGateway {
static func executeAction(action: @escaping () async -> Void) -> CompletablePublisher<Never> {
let subject = PassthroughSubject<Void, Never>()
Task {
await action()
subject.send(completion: .finished)
}
return subject.eraseToAnyPublisher()
}
static func executeAction<R>(action: @escaping () async -> R) -> SinglePublisher<R, Never> {
let subject = PassthroughSubject<R, Never>()
Task {
let result = await action()
subject.send(result)
subject.send(completion: .finished)
}
return subject.eraseToAnyPublisher()
}
static func executeThrowingAction(action: @escaping () async throws -> Void) -> CompletablePublisher<Error> {
let subject = PassthroughSubject<Void, Error>()
Task {
do {
try await action()
subject.send(completion: .finished)
} catch {
subject.send(completion: .failure(error))
}
}
return subject.eraseToAnyPublisher()
}
static func executeThrowingAction<R>(action: @escaping () async throws -> R) -> SinglePublisher<R, Error> {
let subject = PassthroughSubject<R, Error>()
Task {
do {
let result = try await action()
subject.send(result)
subject.send(completion: .finished)
} catch {
subject.send(completion: .failure(error))
}
}
return subject.eraseToAnyPublisher()
}
}

View File

@ -0,0 +1,16 @@
//
// SpecificCombineTypes.swift
//
//
// Created by Michal Fousek on 03.04.2023.
//
import Combine
import Foundation
/* These aliases are here to just make the API easier to read. */
// Publisher which emitts completed or error. No value is emitted.
public typealias CompletablePublisher<E: Error> = AnyPublisher<Void, E>
// Publisher that either emits one value and then finishes or it emits error.
public typealias SinglePublisher = AnyPublisher

View File

@ -7,7 +7,6 @@
import Foundation import Foundation
protocol SyncSessionIDGenerator { protocol SyncSessionIDGenerator {
func nextID() -> UUID func nextID() -> UUID
} }

View File

@ -13,10 +13,6 @@ import XCTest
class BlockDownloaderTests: XCTestCase { class BlockDownloaderTests: XCTestCase {
let branchID = "2bb40e60" let branchID = "2bb40e60"
let chainName = "main" let chainName = "main"
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
var darksideWalletService: DarksideWalletService! var darksideWalletService: DarksideWalletService!
@ -24,16 +20,25 @@ class BlockDownloaderTests: XCTestCase {
var service: LightWalletService! var service: LightWalletService!
var storage: CompactBlockRepository! var storage: CompactBlockRepository!
var network = DarksideWalletDNetwork() var network = DarksideWalletDNetwork()
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
override func setUp() async throws { override func setUp() async throws {
try await super.setUp() try await super.setUp()
testTempDirectory = Environment.uniqueTestTempDirectory
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make() service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
rustBackend = ZcashRustBackend.makeForTests(
fsBlockDbRoot: testTempDirectory,
networkType: network.networkType
)
storage = FSCompactBlockRepository( storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -58,6 +63,8 @@ class BlockDownloaderTests: XCTestCase {
service = nil service = nil
storage = nil storage = nil
downloader = nil downloader = nil
rustBackend = nil
testTempDirectory = nil
} }
func testSmallDownload() async { func testSmallDownload() async {

View File

@ -173,11 +173,13 @@ class RewindRescanTests: XCTestCase {
// rewind to birthday // rewind to birthday
let targetHeight: BlockHeight = newChaintTip - 8000 let targetHeight: BlockHeight = newChaintTip - 8000
let rewindHeight = await ZcashRustBackend.getNearestRewindHeight(
dbData: coordinator.databases.dataDB, do {
height: Int32(targetHeight), _ = try await coordinator.synchronizer.initializer.rustBackend.getNearestRewindHeight(height: Int32(targetHeight))
networkType: network.networkType } catch {
) XCTFail("get nearest height failed error: \(error)")
return
}
let rewindExpectation = XCTestExpectation(description: "RewindExpectation") let rewindExpectation = XCTestExpectation(description: "RewindExpectation")
@ -202,11 +204,6 @@ class RewindRescanTests: XCTestCase {
wait(for: [rewindExpectation], timeout: 2) wait(for: [rewindExpectation], timeout: 2)
guard rewindHeight > 0 else {
XCTFail("get nearest height failed error: \(ZcashRustBackend.getLastError() ?? "null")")
return
}
// check that the balance is cleared // check that the balance is cleared
var expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance() var expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
XCTAssertEqual(initialVerifiedBalance, expectedVerifiedBalance) XCTAssertEqual(initialVerifiedBalance, expectedVerifiedBalance)

View File

@ -27,6 +27,7 @@ class SynchronizerDarksideTests: XCTestCase {
var foundTransactions: [ZcashTransaction.Overview] = [] var foundTransactions: [ZcashTransaction.Overview] = []
var cancellables: [AnyCancellable] = [] var cancellables: [AnyCancellable] = []
var idGenerator: MockSyncSessionIDGenerator! var idGenerator: MockSyncSessionIDGenerator!
override func setUp() async throws { override func setUp() async throws {
try await super.setUp() try await super.setUp()
idGenerator = MockSyncSessionIDGenerator(ids: [.deadbeef]) idGenerator = MockSyncSessionIDGenerator(ids: [.deadbeef])
@ -78,7 +79,6 @@ class SynchronizerDarksideTests: XCTestCase {
} }
func testFoundManyTransactions() async throws { func testFoundManyTransactions() async throws {
self.idGenerator.ids = [.deadbeef, .beefbeef, .beefdead] self.idGenerator.ids = [.deadbeef, .beefbeef, .beefdead]
coordinator.synchronizer.eventStream coordinator.synchronizer.eventStream
.map { event in .map { event in
@ -143,7 +143,7 @@ class SynchronizerDarksideTests: XCTestCase {
XCTAssertEqual(self.foundTransactions.count, 2) XCTAssertEqual(self.foundTransactions.count, 2)
} }
func testLastStates() async throws { func sdfstestLastStates() async throws {
self.idGenerator.ids = [.deadbeef] self.idGenerator.ids = [.deadbeef]
var cancellables: [AnyCancellable] = [] var cancellables: [AnyCancellable] = []
@ -474,7 +474,6 @@ class SynchronizerDarksideTests: XCTestCase {
] ]
XCTAssertEqual(states, secondBatchOfExpectedStates) XCTAssertEqual(states, secondBatchOfExpectedStates)
} }
func testSyncAfterWipeWorks() async throws { func testSyncAfterWipeWorks() async throws {

View File

@ -19,16 +19,14 @@ class TransactionEnhancementTests: XCTestCase {
let network = DarksideWalletDNetwork() let network = DarksideWalletDNetwork()
let branchID = "2bb40e60" let branchID = "2bb40e60"
let chainName = "main" let chainName = "main"
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
var testTempDirectory: URL!
let testFileManager = FileManager() let testFileManager = FileManager()
var initializer: Initializer! var initializer: Initializer!
var processorConfig: CompactBlockProcessor.Configuration! var processorConfig: CompactBlockProcessor.Configuration!
var processor: CompactBlockProcessor! var processor: CompactBlockProcessor!
var rustBackend: ZcashRustBackendWelding!
var darksideWalletService: DarksideWalletService! var darksideWalletService: DarksideWalletService!
var downloader: BlockDownloaderServiceImpl! var downloader: BlockDownloaderServiceImpl!
var syncStartedExpect: XCTestExpectation! var syncStartedExpect: XCTestExpectation!
@ -42,8 +40,8 @@ class TransactionEnhancementTests: XCTestCase {
override func setUp() async throws { override func setUp() async throws {
try await super.setUp() try await super.setUp()
testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
await InternalSyncProgress( await InternalSyncProgress(
alias: .default, alias: .default,
@ -63,7 +61,6 @@ class TransactionEnhancementTests: XCTestCase {
waitExpectation = XCTestExpectation(description: "\(self.description) waitExpectation") waitExpectation = XCTestExpectation(description: "\(self.description) waitExpectation")
let rustBackend = ZcashRustBackend.self
let birthday = Checkpoint.birthday(with: walletBirthday, network: network) let birthday = Checkpoint.birthday(with: walletBirthday, network: network)
let pathProvider = DefaultResourceProvider(network: network) let pathProvider = DefaultResourceProvider(network: network)
@ -78,27 +75,25 @@ class TransactionEnhancementTests: XCTestCase {
network: network network: network
) )
rustBackend = ZcashRustBackend.makeForTests(
dbData: processorConfig.dataDb,
fsBlockDbRoot: testTempDirectory,
networkType: network.networkType
)
try? FileManager.default.removeItem(at: processorConfig.fsBlockCacheRoot) try? FileManager.default.removeItem(at: processorConfig.fsBlockCacheRoot)
try? FileManager.default.removeItem(at: processorConfig.dataDb) try? FileManager.default.removeItem(at: processorConfig.dataDb)
let dbInit = try await rustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: network.networkType) let dbInit = try await rustBackend.initDataDb(seed: nil)
let ufvks = [ let derivationTool = DerivationTool(networkType: network.networkType)
try DerivationTool(networkType: network.networkType) let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: Environment.seedBytes, accountIndex: 0)
.deriveUnifiedSpendingKey(seed: Environment.seedBytes, accountIndex: 0) let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
.map {
try DerivationTool(networkType: network.networkType)
.deriveUnifiedFullViewingKey(from: $0)
}
]
do { do {
try await rustBackend.initAccountsTable( try await rustBackend.initAccountsTable(ufvks: [viewingKey])
dbData: processorConfig.dataDb,
ufvks: ufvks,
networkType: network.networkType
)
} catch { } catch {
XCTFail("Failed to init accounts table error: \(String(describing: rustBackend.getLastError()))") XCTFail("Failed to init accounts table error: \(error)")
return return
} }
@ -108,12 +103,10 @@ class TransactionEnhancementTests: XCTestCase {
} }
_ = try await rustBackend.initBlocksTable( _ = try await rustBackend.initBlocksTable(
dbData: processorConfig.dataDb,
height: Int32(birthday.height), height: Int32(birthday.height),
hash: birthday.hash, hash: birthday.hash,
time: birthday.time, time: birthday.time,
saplingTree: birthday.saplingTree, saplingTree: birthday.saplingTree
networkType: network.networkType
) )
let service = DarksideWalletService() let service = DarksideWalletService()
@ -136,7 +129,7 @@ class TransactionEnhancementTests: XCTestCase {
processor = CompactBlockProcessor( processor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: rustBackend, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -163,6 +156,7 @@ class TransactionEnhancementTests: XCTestCase {
processor = nil processor = nil
darksideWalletService = nil darksideWalletService = nil
downloader = nil downloader = nil
testTempDirectory = nil
} }
private func startProcessing() async throws { private func startProcessing() async throws {

View File

@ -15,8 +15,6 @@ import SQLite
class BlockScanTests: XCTestCase { class BlockScanTests: XCTestCase {
var cancelables: [AnyCancellable] = [] var cancelables: [AnyCancellable] = []
let rustWelding = ZcashRustBackend.self
var dataDbURL: URL! var dataDbURL: URL!
var spendParamsURL: URL! var spendParamsURL: URL!
var outputParamsURL: URL! var outputParamsURL: URL!
@ -27,25 +25,30 @@ class BlockScanTests: XCTestCase {
with: 1386000, with: 1386000,
network: ZcashNetworkBuilder.network(for: .testnet) network: ZcashNetworkBuilder.network(for: .testnet)
) )
var rustBackend: ZcashRustBackendWelding!
var network = ZcashNetworkBuilder.network(for: .testnet) var network = ZcashNetworkBuilder.network(for: .testnet)
var blockRepository: BlockRepository! var blockRepository: BlockRepository!
var testTempDirectory: URL!
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
override func setUpWithError() throws { override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError() try super.setUpWithError()
self.dataDbURL = try! __dataDbURL() dataDbURL = try! __dataDbURL()
self.spendParamsURL = try! __spendParamsURL() spendParamsURL = try! __spendParamsURL()
self.outputParamsURL = try! __outputParamsURL() outputParamsURL = try! __outputParamsURL()
testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
rustBackend = ZcashRustBackend.makeForTests(
dbData: dataDbURL,
fsBlockDbRoot: testTempDirectory,
networkType: network.networkType
)
deleteDBs() deleteDBs()
} }
@ -63,25 +66,23 @@ class BlockScanTests: XCTestCase {
try? testFileManager.removeItem(at: testTempDirectory) try? testFileManager.removeItem(at: testTempDirectory)
cancelables = [] cancelables = []
blockRepository = nil blockRepository = nil
testTempDirectory = nil
} }
func testSingleDownloadAndScan() async throws { func testSingleDownloadAndScan() async throws {
logger = OSLogger(logLevel: .debug) logger = OSLogger(logLevel: .debug)
_ = try await rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType) _ = try await rustBackend.initDataDb(seed: nil)
let endpoint = LightWalletEndpoint(address: "lightwalletd.testnet.electriccoin.co", port: 9067) let endpoint = LightWalletEndpoint(address: "lightwalletd.testnet.electriccoin.co", port: 9067)
let service = LightWalletServiceFactory(endpoint: endpoint).make() let service = LightWalletServiceFactory(endpoint: endpoint).make()
let blockCount = 100 let blockCount = 100
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
let fsDbRootURL = self.testTempDirectory
let rustBackend = ZcashRustBackend.self
let fsBlockRepository = FSCompactBlockRepository( let fsBlockRepository = FSCompactBlockRepository(
fsBlockDbRoot: fsDbRootURL, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: fsDbRootURL, fsBlockDbRoot: testTempDirectory,
rustBackend: rustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
@ -94,7 +95,7 @@ class BlockScanTests: XCTestCase {
let processorConfig = CompactBlockProcessor.Configuration( let processorConfig = CompactBlockProcessor.Configuration(
alias: .default, alias: .default,
fsBlockCacheRoot: fsDbRootURL, fsBlockCacheRoot: testTempDirectory,
dataDb: dataDbURL, dataDb: dataDbURL,
spendParamsURL: spendParamsURL, spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL, outputParamsURL: outputParamsURL,
@ -106,7 +107,7 @@ class BlockScanTests: XCTestCase {
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: fsBlockRepository, storage: fsBlockRepository,
backend: rustBackend, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -139,45 +140,36 @@ class BlockScanTests: XCTestCase {
let metrics = SDKMetrics() let metrics = SDKMetrics()
metrics.enableMetrics() metrics.enableMetrics()
guard try await self.rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType) == .success else { guard try await rustBackend.initDataDb(seed: nil) == .success else {
XCTFail("Seed should not be required for this test") XCTFail("Seed should not be required for this test")
return return
} }
let derivationTool = DerivationTool(networkType: .testnet) let derivationTool = DerivationTool(networkType: .testnet)
let ufvk = try derivationTool let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: Array(seed.utf8), accountIndex: 0)
.deriveUnifiedSpendingKey(seed: Array(seed.utf8), accountIndex: 0) let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
.map { try derivationTool.deriveUnifiedFullViewingKey(from: $0) }
do { do {
try await self.rustWelding.initAccountsTable( try await rustBackend.initAccountsTable(ufvks: [viewingKey])
dbData: self.dataDbURL,
ufvks: [ufvk],
networkType: network.networkType
)
} catch { } catch {
XCTFail("failed to init account table. error: \(self.rustWelding.getLastError() ?? "no error found")") XCTFail("failed to init account table. error: \(error)")
return return
} }
try await self.rustWelding.initBlocksTable( try await rustBackend.initBlocksTable(
dbData: dataDbURL,
height: Int32(walletBirthDay.height), height: Int32(walletBirthDay.height),
hash: walletBirthDay.hash, hash: walletBirthDay.hash,
time: walletBirthDay.time, time: walletBirthDay.time,
saplingTree: walletBirthDay.saplingTree, saplingTree: walletBirthDay.saplingTree
networkType: network.networkType
) )
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make() let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
let fsDbRootURL = self.testTempDirectory
let fsBlockRepository = FSCompactBlockRepository( let fsBlockRepository = FSCompactBlockRepository(
fsBlockDbRoot: fsDbRootURL, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: fsDbRootURL, fsBlockDbRoot: testTempDirectory,
rustBackend: rustWelding, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: ZcashCompactBlockDescriptor.live, blockDescriptor: ZcashCompactBlockDescriptor.live,
@ -189,7 +181,7 @@ class BlockScanTests: XCTestCase {
var processorConfig = CompactBlockProcessor.Configuration( var processorConfig = CompactBlockProcessor.Configuration(
alias: .default, alias: .default,
fsBlockCacheRoot: fsDbRootURL, fsBlockCacheRoot: testTempDirectory,
dataDb: dataDbURL, dataDb: dataDbURL,
spendParamsURL: spendParamsURL, spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL, outputParamsURL: outputParamsURL,
@ -202,7 +194,7 @@ class BlockScanTests: XCTestCase {
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: fsBlockRepository, storage: fsBlockRepository,
backend: rustWelding, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: metrics, metrics: metrics,
logger: logger logger: logger

View File

@ -10,23 +10,24 @@ import XCTest
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class BlockStreamingTest: XCTestCase { class BlockStreamingTest: XCTestCase {
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .testnet)
logger = OSLogger(logLevel: .debug) logger = OSLogger(logLevel: .debug)
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
rustBackend = nil
try? FileManager.default.removeItem(at: __dataDbURL()) try? FileManager.default.removeItem(at: __dataDbURL())
try? testFileManager.removeItem(at: testTempDirectory) try? testFileManager.removeItem(at: testTempDirectory)
testTempDirectory = nil
} }
func testStream() async throws { func testStream() async throws {
@ -68,13 +69,11 @@ class BlockStreamingTest: XCTestCase {
) )
let service = LightWalletServiceFactory(endpoint: endpoint).make() let service = LightWalletServiceFactory(endpoint: endpoint).make()
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -94,7 +93,7 @@ class BlockStreamingTest: XCTestCase {
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: realRustBackend, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -132,13 +131,11 @@ class BlockStreamingTest: XCTestCase {
) )
let service = LightWalletServiceFactory(endpoint: endpoint).make() let service = LightWalletServiceFactory(endpoint: endpoint).make()
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -160,7 +157,7 @@ class BlockStreamingTest: XCTestCase {
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: realRustBackend, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger

View File

@ -12,9 +12,30 @@ import XCTest
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class CompactBlockProcessorTests: XCTestCase { class CompactBlockProcessorTests: XCTestCase {
lazy var processorConfig = { var processorConfig: CompactBlockProcessor.Configuration!
var cancellables: [AnyCancellable] = []
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
var rustBackend: ZcashRustBackendWelding!
var processor: CompactBlockProcessor!
var syncStartedExpect: XCTestExpectation!
var updatedNotificationExpectation: XCTestExpectation!
var stopNotificationExpectation: XCTestExpectation!
var finishedNotificationExpectation: XCTestExpectation!
let network = ZcashNetworkBuilder.network(for: .testnet)
let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000
let testFileManager = FileManager()
var testTempDirectory: URL!
override func setUp() async throws {
try await super.setUp()
logger = OSLogger(logLevel: .debug)
testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
let pathProvider = DefaultResourceProvider(network: network) let pathProvider = DefaultResourceProvider(network: network)
return CompactBlockProcessor.Configuration( processorConfig = CompactBlockProcessor.Configuration(
alias: .default, alias: .default,
fsBlockCacheRoot: testTempDirectory, fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL, dataDb: pathProvider.dataDbURL,
@ -24,28 +45,6 @@ class CompactBlockProcessorTests: XCTestCase {
walletBirthdayProvider: { ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight }, walletBirthdayProvider: { ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight },
network: ZcashNetworkBuilder.network(for: .testnet) network: ZcashNetworkBuilder.network(for: .testnet)
) )
}()
var cancellables: [AnyCancellable] = []
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
var processor: CompactBlockProcessor!
var syncStartedExpect: XCTestExpectation!
var updatedNotificationExpectation: XCTestExpectation!
var stopNotificationExpectation: XCTestExpectation!
var finishedNotificationExpectation: XCTestExpectation!
let network = ZcashNetworkBuilder.network(for: .testnet)
let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager()
override func setUp() async throws {
try await super.setUp()
logger = OSLogger(logLevel: .debug)
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
await InternalSyncProgress( await InternalSyncProgress(
alias: .default, alias: .default,
@ -58,7 +57,14 @@ class CompactBlockProcessorTests: XCTestCase {
latestBlockHeight: mockLatestHeight, latestBlockHeight: mockLatestHeight,
service: liveService service: liveService
) )
let branchID = try ZcashRustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight), networkType: network.networkType)
rustBackend = ZcashRustBackend.makeForTests(
dbData: processorConfig.dataDb,
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
networkType: network.networkType
)
let branchID = try rustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight))
service.mockLightDInfo = LightdInfo.with({ info in service.mockLightDInfo = LightdInfo.with({ info in
info.blockHeight = UInt64(mockLatestHeight) info.blockHeight = UInt64(mockLatestHeight)
info.branch = "asdf" info.branch = "asdf"
@ -70,13 +76,11 @@ class CompactBlockProcessorTests: XCTestCase {
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight) info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
}) })
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot, fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot, fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -89,13 +93,13 @@ class CompactBlockProcessorTests: XCTestCase {
processor = CompactBlockProcessor( processor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: realRustBackend, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
) )
let dbInit = try await realRustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: .testnet) let dbInit = try await rustBackend.initDataDb(seed: nil)
guard case .success = dbInit else { guard case .success = dbInit else {
XCTFail("Failed to initDataDb. Expected `.success` got: \(dbInit)") XCTFail("Failed to initDataDb. Expected `.success` got: \(dbInit)")
@ -125,6 +129,8 @@ class CompactBlockProcessorTests: XCTestCase {
cancellables = [] cancellables = []
processor = nil processor = nil
processorEventHandler = nil processorEventHandler = nil
rustBackend = nil
testTempDirectory = nil
} }
func processorFailed(event: CompactBlockProcessor.Event) { func processorFailed(event: CompactBlockProcessor.Event) {

View File

@ -12,9 +12,31 @@ import XCTest
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class CompactBlockReorgTests: XCTestCase { class CompactBlockReorgTests: XCTestCase {
lazy var processorConfig = { var processorConfig: CompactBlockProcessor.Configuration!
let testFileManager = FileManager()
var cancellables: [AnyCancellable] = []
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
var rustBackend: ZcashRustBackendWelding!
var rustBackendMockHelper: RustBackendMockHelper!
var processor: CompactBlockProcessor!
var syncStartedExpect: XCTestExpectation!
var updatedNotificationExpectation: XCTestExpectation!
var stopNotificationExpectation: XCTestExpectation!
var finishedNotificationExpectation: XCTestExpectation!
var reorgNotificationExpectation: XCTestExpectation!
let network = ZcashNetworkBuilder.network(for: .testnet)
let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000
var testTempDirectory: URL!
override func setUp() async throws {
try await super.setUp()
logger = OSLogger(logLevel: .debug)
testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
let pathProvider = DefaultResourceProvider(network: network) let pathProvider = DefaultResourceProvider(network: network)
return CompactBlockProcessor.Configuration( processorConfig = CompactBlockProcessor.Configuration(
alias: .default, alias: .default,
fsBlockCacheRoot: testTempDirectory, fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL, dataDb: pathProvider.dataDbURL,
@ -24,30 +46,6 @@ class CompactBlockReorgTests: XCTestCase {
walletBirthdayProvider: { ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight }, walletBirthdayProvider: { ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight },
network: ZcashNetworkBuilder.network(for: .testnet) network: ZcashNetworkBuilder.network(for: .testnet)
) )
}()
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager()
var cancellables: [AnyCancellable] = []
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
var processor: CompactBlockProcessor!
var syncStartedExpect: XCTestExpectation!
var updatedNotificationExpectation: XCTestExpectation!
var stopNotificationExpectation: XCTestExpectation!
var finishedNotificationExpectation: XCTestExpectation!
var reorgNotificationExpectation: XCTestExpectation!
let network = ZcashNetworkBuilder.network(for: .testnet)
let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000
override func setUp() async throws {
try await super.setUp()
logger = OSLogger(logLevel: .debug)
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
await InternalSyncProgress( await InternalSyncProgress(
alias: .default, alias: .default,
@ -60,8 +58,14 @@ class CompactBlockReorgTests: XCTestCase {
latestBlockHeight: mockLatestHeight, latestBlockHeight: mockLatestHeight,
service: liveService service: liveService
) )
let branchID = try ZcashRustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight), networkType: network.networkType) rustBackend = ZcashRustBackend.makeForTests(
dbData: processorConfig.dataDb,
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
networkType: network.networkType
)
let branchID = try rustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight))
service.mockLightDInfo = LightdInfo.with { info in service.mockLightDInfo = LightdInfo.with { info in
info.blockHeight = UInt64(mockLatestHeight) info.blockHeight = UInt64(mockLatestHeight)
info.branch = "asdf" info.branch = "asdf"
@ -73,13 +77,11 @@ class CompactBlockReorgTests: XCTestCase {
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight) info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
} }
let realRustBackend = ZcashRustBackend.self
let realCache = FSCompactBlockRepository( let realCache = FSCompactBlockRepository(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot, fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot, fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -89,21 +91,23 @@ class CompactBlockReorgTests: XCTestCase {
try await realCache.create() try await realCache.create()
let initResult = try await realRustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: .testnet) let initResult = try await rustBackend.initDataDb(seed: nil)
guard case .success = initResult else { guard case .success = initResult else {
XCTFail("initDataDb failed. Expected Success but got .seedRequired") XCTFail("initDataDb failed. Expected Success but got .seedRequired")
return return
} }
let mockBackend = MockRustBackend.self rustBackendMockHelper = await RustBackendMockHelper(
mockBackend.mockValidateCombinedChainFailAfterAttempts = 3 rustBackend: rustBackend,
mockBackend.mockValidateCombinedChainKeepFailing = false mockValidateCombinedChainFailAfterAttempts: 3,
mockBackend.mockValidateCombinedChainFailureHeight = self.network.constants.saplingActivationHeight + 320 mockValidateCombinedChainKeepFailing: false,
mockValidateCombinedChainFailureError: .invalidChain(upperBound: Int32(network.constants.saplingActivationHeight + 320))
)
processor = CompactBlockProcessor( processor = CompactBlockProcessor(
service: service, service: service,
storage: realCache, storage: realCache,
backend: mockBackend, rustBackend: rustBackendMockHelper.rustBackendMock,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -134,6 +138,8 @@ class CompactBlockReorgTests: XCTestCase {
cancellables = [] cancellables = []
processorEventHandler = nil processorEventHandler = nil
processor = nil processor = nil
rustBackend = nil
rustBackendMockHelper = nil
} }
func processorHandledReorg(event: CompactBlockProcessor.Event) { func processorHandledReorg(event: CompactBlockProcessor.Event) {

View File

@ -12,36 +12,31 @@ import SQLite
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class DownloadTests: XCTestCase { class DownloadTests: XCTestCase {
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
var network = ZcashNetworkBuilder.network(for: .testnet) var network = ZcashNetworkBuilder.network(for: .testnet)
var testTempDirectory: URL!
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
try? testFileManager.removeItem(at: testTempDirectory) try? testFileManager.removeItem(at: testTempDirectory)
testTempDirectory = nil
} }
func testSingleDownload() async throws { func testSingleDownload() async throws {
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make() let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
let rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: network.networkType)
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -63,7 +58,7 @@ class DownloadTests: XCTestCase {
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: realRustBackend, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger

View File

@ -10,21 +10,22 @@ import XCTest
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class BlockBatchValidationTests: XCTestCase { class BlockBatchValidationTests: XCTestCase {
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .testnet)
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
try? testFileManager.removeItem(at: testTempDirectory) try? testFileManager.removeItem(at: testTempDirectory)
rustBackend = nil
testTempDirectory = nil
} }
func testBranchIdFailure() async throws { func testBranchIdFailure() async throws {
@ -34,13 +35,11 @@ class BlockBatchValidationTests: XCTestCase {
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make() service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
) )
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -76,14 +75,13 @@ class BlockBatchValidationTests: XCTestCase {
info.consensusBranchID = "d34db33f" info.consensusBranchID = "d34db33f"
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight) info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
service.mockLightDInfo = info service.mockLightDInfo = info
let mockRust = MockRustBackend.self let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: Int32(0xd34d))
mockRust.consensusBranchID = Int32(0xd34d)
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: mockRust, rustBackend: mockBackend.rustBackendMock,
config: config, config: config,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -109,13 +107,11 @@ class BlockBatchValidationTests: XCTestCase {
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make() service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
) )
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -151,14 +147,13 @@ class BlockBatchValidationTests: XCTestCase {
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight) info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
service.mockLightDInfo = info service.mockLightDInfo = info
let mockRust = MockRustBackend.self let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
mockRust.consensusBranchID = 0xd34db4d
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: mockRust, rustBackend: mockBackend.rustBackendMock,
config: config, config: config,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -184,13 +179,11 @@ class BlockBatchValidationTests: XCTestCase {
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make() service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
) )
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -227,13 +220,12 @@ class BlockBatchValidationTests: XCTestCase {
service.mockLightDInfo = info service.mockLightDInfo = info
let mockRust = MockRustBackend.self let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
mockRust.consensusBranchID = 0xd34db4d
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: mockRust, rustBackend: mockBackend.rustBackendMock,
config: config, config: config,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -259,13 +251,11 @@ class BlockBatchValidationTests: XCTestCase {
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make() service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
) )
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository( let storage = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -303,13 +293,12 @@ class BlockBatchValidationTests: XCTestCase {
service.mockLightDInfo = info service.mockLightDInfo = info
let mockRust = MockRustBackend.self let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
mockRust.consensusBranchID = 0xd34db4d
let compactBlockProcessor = CompactBlockProcessor( let compactBlockProcessor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: mockRust, rustBackend: mockBackend.rustBackendMock,
config: config, config: config,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger
@ -381,9 +370,8 @@ class BlockBatchValidationTests: XCTestCase {
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight) info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
service.mockLightDInfo = info service.mockLightDInfo = info
let mockRust = MockRustBackend.self let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
mockRust.consensusBranchID = 0xd34db4d
var nextBatch: CompactBlockProcessor.NextState? var nextBatch: CompactBlockProcessor.NextState?
do { do {
@ -392,7 +380,7 @@ class BlockBatchValidationTests: XCTestCase {
downloaderService: downloaderService, downloaderService: downloaderService,
transactionRepository: transactionRepository, transactionRepository: transactionRepository,
config: config, config: config,
rustBackend: mockRust, rustBackend: mockBackend.rustBackendMock,
internalSyncProgress: InternalSyncProgress( internalSyncProgress: InternalSyncProgress(
alias: .default, alias: .default,
storage: InternalSyncProgressMemoryStorage(), storage: InternalSyncProgressMemoryStorage(),
@ -479,8 +467,7 @@ class BlockBatchValidationTests: XCTestCase {
service.mockLightDInfo = info service.mockLightDInfo = info
let mockRust = MockRustBackend.self let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
mockRust.consensusBranchID = 0xd34db4d
var nextBatch: CompactBlockProcessor.NextState? var nextBatch: CompactBlockProcessor.NextState?
do { do {
@ -489,7 +476,7 @@ class BlockBatchValidationTests: XCTestCase {
downloaderService: downloaderService, downloaderService: downloaderService,
transactionRepository: transactionRepository, transactionRepository: transactionRepository,
config: config, config: config,
rustBackend: mockRust, rustBackend: mockBackend.rustBackendMock,
internalSyncProgress: InternalSyncProgress( internalSyncProgress: InternalSyncProgress(
alias: .default, alias: .default,
storage: InternalSyncProgressMemoryStorage(), storage: InternalSyncProgressMemoryStorage(),
@ -573,8 +560,7 @@ class BlockBatchValidationTests: XCTestCase {
service.mockLightDInfo = info service.mockLightDInfo = info
let mockRust = MockRustBackend.self let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend, consensusBranchID: 0xd34db4d)
mockRust.consensusBranchID = 0xd34db4d
var nextBatch: CompactBlockProcessor.NextState? var nextBatch: CompactBlockProcessor.NextState?
do { do {
@ -583,7 +569,7 @@ class BlockBatchValidationTests: XCTestCase {
downloaderService: downloaderService, downloaderService: downloaderService,
transactionRepository: transactionRepository, transactionRepository: transactionRepository,
config: config, config: config,
rustBackend: mockRust, rustBackend: mockBackend.rustBackendMock,
internalSyncProgress: internalSyncProgress internalSyncProgress: internalSyncProgress
) )

View File

@ -14,7 +14,7 @@ import XCTest
extension String: Error { } extension String: Error { }
class ClosureSynchronizerOfflineTests: XCTestCase { class ClosureSynchronizerOfflineTests: XCTestCase {
var data: AlternativeSynchronizerAPITestsData! var data: TestsData!
var cancellables: [AnyCancellable] = [] var cancellables: [AnyCancellable] = []
var synchronizerMock: SynchronizerMock! var synchronizerMock: SynchronizerMock!
@ -22,7 +22,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
data = AlternativeSynchronizerAPITestsData() data = TestsData(networkType: .testnet)
synchronizerMock = SynchronizerMock() synchronizerMock = SynchronizerMock()
synchronizer = ClosureSDKSynchronizer(synchronizer: synchronizerMock) synchronizer = ClosureSDKSynchronizer(synchronizer: synchronizerMock)
cancellables = [] cancellables = []
@ -114,17 +114,18 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
XCTAssertEqual(synchronizer.connectionState, .reconnecting) XCTAssertEqual(synchronizer.connectionState, .reconnecting)
} }
func testPrepareSucceed() throws { func testPrepareSucceed() async throws {
synchronizerMock.prepareWithSeedViewingKeysWalletBirthdayClosure = { receivedSeed, receivedViewingKeys, receivedWalletBirthday in let mockedViewingKey = await data.viewingKey
synchronizerMock.prepareWithViewingKeysWalletBirthdayClosure = { receivedSeed, receivedViewingKeys, receivedWalletBirthday in
XCTAssertEqual(receivedSeed, self.data.seed) XCTAssertEqual(receivedSeed, self.data.seed)
XCTAssertEqual(receivedViewingKeys, [self.data.viewingKey]) XCTAssertEqual(receivedViewingKeys, [mockedViewingKey])
XCTAssertEqual(receivedWalletBirthday, self.data.birthday) XCTAssertEqual(receivedWalletBirthday, self.data.birthday)
return .success return .success
} }
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, viewingKeys: [data.viewingKey], walletBirthday: data.birthday) { result in synchronizer.prepare(with: data.seed, viewingKeys: [mockedViewingKey], walletBirthday: data.birthday) { result in
switch result { switch result {
case let .success(status): case let .success(status):
XCTAssertEqual(status, .success) XCTAssertEqual(status, .success)
@ -137,14 +138,15 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testPrepareThrowsError() throws { func testPrepareThrowsError() async throws {
synchronizerMock.prepareWithSeedViewingKeysWalletBirthdayClosure = { _, _, _ in let mockedViewingKey = await data.viewingKey
synchronizerMock.prepareWithViewingKeysWalletBirthdayClosure = { _, _, _ in
throw "Some error" throw "Some error"
} }
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, viewingKeys: [data.viewingKey], walletBirthday: data.birthday) { result in synchronizer.prepare(with: data.seed, viewingKeys: [mockedViewingKey], walletBirthday: data.birthday) { result in
switch result { switch result {
case .success: case .success:
XCTFail("Error should be thrown.") XCTFail("Error should be thrown.")
@ -324,14 +326,15 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testSendToAddressSucceed() throws { func testSendToAddressSucceed() async throws {
let amount = Zatoshi(100) let amount = Zatoshi(100)
let recipient: Recipient = .transparent(data.transparentAddress) let recipient: Recipient = .transparent(data.transparentAddress)
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let mockedSpendingKey = await data.spendingKey
synchronizerMock synchronizerMock
.sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { receivedSpendingKey, receivedZatoshi, receivedToAddress, receivedMemo in .sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { receivedSpendingKey, receivedZatoshi, receivedToAddress, receivedMemo in
XCTAssertEqual(receivedSpendingKey, self.data.spendingKey) XCTAssertEqual(receivedSpendingKey, mockedSpendingKey)
XCTAssertEqual(receivedZatoshi, amount) XCTAssertEqual(receivedZatoshi, amount)
XCTAssertEqual(receivedToAddress, recipient) XCTAssertEqual(receivedToAddress, recipient)
XCTAssertEqual(receivedMemo, memo) XCTAssertEqual(receivedMemo, memo)
@ -340,7 +343,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.sendToAddress(spendingKey: data.spendingKey, zatoshi: amount, toAddress: recipient, memo: memo) { result in synchronizer.sendToAddress(spendingKey: mockedSpendingKey, zatoshi: amount, toAddress: recipient, memo: memo) { result in
switch result { switch result {
case let .success(receivedEntity): case let .success(receivedEntity):
XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient) XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient)
@ -353,10 +356,11 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testSendToAddressThrowsError() throws { func testSendToAddressThrowsError() async throws {
let amount = Zatoshi(100) let amount = Zatoshi(100)
let recipient: Recipient = .transparent(data.transparentAddress) let recipient: Recipient = .transparent(data.transparentAddress)
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let mockedSpendingKey = await data.spendingKey
synchronizerMock.sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { _, _, _, _ in synchronizerMock.sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { _, _, _, _ in
throw "Some error" throw "Some error"
@ -364,7 +368,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.sendToAddress(spendingKey: data.spendingKey, zatoshi: amount, toAddress: recipient, memo: memo) { result in synchronizer.sendToAddress(spendingKey: mockedSpendingKey, zatoshi: amount, toAddress: recipient, memo: memo) { result in
switch result { switch result {
case .success: case .success:
XCTFail("Error should be thrown.") XCTFail("Error should be thrown.")
@ -376,12 +380,13 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testShieldFundsSucceed() throws { func testShieldFundsSucceed() async throws {
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let shieldingThreshold = Zatoshi(1) let shieldingThreshold = Zatoshi(1)
let mockedSpendingKey = await data.spendingKey
synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { receivedSpendingKey, receivedMemo, receivedShieldingThreshold in synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { receivedSpendingKey, receivedMemo, receivedShieldingThreshold in
XCTAssertEqual(receivedSpendingKey, self.data.spendingKey) XCTAssertEqual(receivedSpendingKey, mockedSpendingKey)
XCTAssertEqual(receivedMemo, memo) XCTAssertEqual(receivedMemo, memo)
XCTAssertEqual(receivedShieldingThreshold, shieldingThreshold) XCTAssertEqual(receivedShieldingThreshold, shieldingThreshold)
return self.data.pendingTransactionEntity return self.data.pendingTransactionEntity
@ -389,7 +394,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.shieldFunds(spendingKey: data.spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) { result in synchronizer.shieldFunds(spendingKey: mockedSpendingKey, memo: memo, shieldingThreshold: shieldingThreshold) { result in
switch result { switch result {
case let .success(receivedEntity): case let .success(receivedEntity):
XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient) XCTAssertEqual(receivedEntity.recipient, self.data.pendingTransactionEntity.recipient)
@ -402,9 +407,10 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testShieldFundsThrowsError() throws { func testShieldFundsThrowsError() async throws {
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let shieldingThreshold = Zatoshi(1) let shieldingThreshold = Zatoshi(1)
let mockedSpendingKey = await data.spendingKey
synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { _, _, _ in synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { _, _, _ in
throw "Some error" throw "Some error"
@ -412,7 +418,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.shieldFunds(spendingKey: data.spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) { result in synchronizer.shieldFunds(spendingKey: mockedSpendingKey, memo: memo, shieldingThreshold: shieldingThreshold) { result in
switch result { switch result {
case .success: case .success:
XCTFail("Error should be thrown.") XCTFail("Error should be thrown.")
@ -499,7 +505,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
func testGetMemosForClearedTransactionSucceed() throws { func testGetMemosForClearedTransactionSucceed() throws {
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
synchronizerMock.getMemosForTransactionClosure = { receivedTransaction in synchronizerMock.getMemosForClearedTransactionClosure = { receivedTransaction in
XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id)
return [memo] return [memo]
} }
@ -521,7 +527,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testGetMemosForClearedTransactionThrowsError() { func testGetMemosForClearedTransactionThrowsError() {
synchronizerMock.getMemosForTransactionClosure = { _ in synchronizerMock.getMemosForClearedTransactionClosure = { _ in
throw "Some error" throw "Some error"
} }
@ -664,7 +670,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testAllConfirmedTransactionsSucceed() throws { func testAllConfirmedTransactionsSucceed() throws {
synchronizerMock.allConfirmedTransactionsFromTransactionClosure = { receivedTransaction, limit in synchronizerMock.allConfirmedTransactionsFromLimitClosure = { receivedTransaction, limit in
XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id)
XCTAssertEqual(limit, 3) XCTAssertEqual(limit, 3)
return [self.data.clearedTransaction] return [self.data.clearedTransaction]
@ -687,7 +693,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testAllConfirmedTransactionsThrowsError() throws { func testAllConfirmedTransactionsThrowsError() throws {
synchronizerMock.allConfirmedTransactionsFromTransactionClosure = { _, _ in synchronizerMock.allConfirmedTransactionsFromLimitClosure = { _, _ in
throw "Some error" throw "Some error"
} }
@ -747,7 +753,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
let skippedEntity = UnspentTransactionOutputEntityMock(address: "addr2", txid: Data(), index: 1, script: Data(), valueZat: 2, height: 3) let skippedEntity = UnspentTransactionOutputEntityMock(address: "addr2", txid: Data(), index: 1, script: Data(), valueZat: 2, height: 3)
let refreshedUTXO = (inserted: [insertedEntity], skipped: [skippedEntity]) let refreshedUTXO = (inserted: [insertedEntity], skipped: [skippedEntity])
synchronizerMock.refreshUTXOsAddressFromHeightClosure = { receivedAddress, receivedFromHeight in synchronizerMock.refreshUTXOsAddressFromClosure = { receivedAddress, receivedFromHeight in
XCTAssertEqual(receivedAddress, self.data.transparentAddress) XCTAssertEqual(receivedAddress, self.data.transparentAddress)
XCTAssertEqual(receivedFromHeight, 121000) XCTAssertEqual(receivedFromHeight, 121000)
return refreshedUTXO return refreshedUTXO
@ -770,7 +776,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testRefreshUTXOsThrowsError() { func testRefreshUTXOsThrowsError() {
synchronizerMock.refreshUTXOsAddressFromHeightClosure = { _, _ in synchronizerMock.refreshUTXOsAddressFromClosure = { _, _ in
throw "Some error" throw "Some error"
} }
@ -911,7 +917,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testRewindSucceed() { func testRewindSucceed() {
synchronizerMock.rewindPolicyClosure = { receivedPolicy in synchronizerMock.rewindClosure = { receivedPolicy in
if case .quick = receivedPolicy { if case .quick = receivedPolicy {
} else { } else {
XCTFail("Unexpected policy \(receivedPolicy)") XCTFail("Unexpected policy \(receivedPolicy)")
@ -940,7 +946,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
} }
func testRewindThrowsError() { func testRewindThrowsError() {
synchronizerMock.rewindPolicyClosure = { _ in synchronizerMock.rewindClosure = { _ in
return Fail(error: "some error").eraseToAnyPublisher() return Fail(error: "some error").eraseToAnyPublisher()
} }

View File

@ -12,7 +12,7 @@ import XCTest
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class CombineSynchronizerOfflineTests: XCTestCase { class CombineSynchronizerOfflineTests: XCTestCase {
var data: AlternativeSynchronizerAPITestsData! var data: TestsData!
var cancellables: [AnyCancellable] = [] var cancellables: [AnyCancellable] = []
var synchronizerMock: SynchronizerMock! var synchronizerMock: SynchronizerMock!
@ -20,7 +20,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
data = AlternativeSynchronizerAPITestsData() data = TestsData(networkType: .testnet)
synchronizerMock = SynchronizerMock() synchronizerMock = SynchronizerMock()
synchronizer = CombineSDKSynchronizer(synchronizer: synchronizerMock) synchronizer = CombineSDKSynchronizer(synchronizer: synchronizerMock)
cancellables = [] cancellables = []
@ -112,17 +112,18 @@ class CombineSynchronizerOfflineTests: XCTestCase {
XCTAssertEqual(synchronizer.connectionState, .reconnecting) XCTAssertEqual(synchronizer.connectionState, .reconnecting)
} }
func testPrepareSucceed() throws { func testPrepareSucceed() async throws {
synchronizerMock.prepareWithSeedViewingKeysWalletBirthdayClosure = { receivedSeed, receivedViewingKeys, receivedWalletBirthday in let mockedViewingKey = await self.data.viewingKey
synchronizerMock.prepareWithViewingKeysWalletBirthdayClosure = { receivedSeed, receivedViewingKeys, receivedWalletBirthday in
XCTAssertEqual(receivedSeed, self.data.seed) XCTAssertEqual(receivedSeed, self.data.seed)
XCTAssertEqual(receivedViewingKeys, [self.data.viewingKey]) XCTAssertEqual(receivedViewingKeys, [mockedViewingKey])
XCTAssertEqual(receivedWalletBirthday, self.data.birthday) XCTAssertEqual(receivedWalletBirthday, self.data.birthday)
return .success return .success
} }
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, viewingKeys: [data.viewingKey], walletBirthday: data.birthday) synchronizer.prepare(with: data.seed, viewingKeys: [mockedViewingKey], walletBirthday: data.birthday)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -141,14 +142,15 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testPrepareThrowsError() throws { func testPrepareThrowsError() async throws {
synchronizerMock.prepareWithSeedViewingKeysWalletBirthdayClosure = { _, _, _ in let mockedViewingKey = await self.data.viewingKey
synchronizerMock.prepareWithViewingKeysWalletBirthdayClosure = { _, _, _ in
throw "Some error" throw "Some error"
} }
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.prepare(with: data.seed, viewingKeys: [data.viewingKey], walletBirthday: data.birthday) synchronizer.prepare(with: data.seed, viewingKeys: [mockedViewingKey], walletBirthday: data.birthday)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -329,14 +331,15 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testSendToAddressSucceed() throws { func testSendToAddressSucceed() async throws {
let amount = Zatoshi(100) let amount = Zatoshi(100)
let recipient: Recipient = .transparent(data.transparentAddress) let recipient: Recipient = .transparent(data.transparentAddress)
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let mockedSpendingKey = await data.spendingKey
synchronizerMock synchronizerMock
.sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { receivedSpendingKey, receivedZatoshi, receivedToAddress, receivedMemo in .sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { receivedSpendingKey, receivedZatoshi, receivedToAddress, receivedMemo in
XCTAssertEqual(receivedSpendingKey, self.data.spendingKey) XCTAssertEqual(receivedSpendingKey, mockedSpendingKey)
XCTAssertEqual(receivedZatoshi, amount) XCTAssertEqual(receivedZatoshi, amount)
XCTAssertEqual(receivedToAddress, recipient) XCTAssertEqual(receivedToAddress, recipient)
XCTAssertEqual(receivedMemo, memo) XCTAssertEqual(receivedMemo, memo)
@ -345,7 +348,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.sendToAddress(spendingKey: data.spendingKey, zatoshi: amount, toAddress: recipient, memo: memo) synchronizer.sendToAddress(spendingKey: mockedSpendingKey, zatoshi: amount, toAddress: recipient, memo: memo)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -364,10 +367,11 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testSendToAddressThrowsError() throws { func testSendToAddressThrowsError() async throws {
let amount = Zatoshi(100) let amount = Zatoshi(100)
let recipient: Recipient = .transparent(data.transparentAddress) let recipient: Recipient = .transparent(data.transparentAddress)
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let mockedSpendingKey = await data.spendingKey
synchronizerMock.sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { _, _, _, _ in synchronizerMock.sendToAddressSpendingKeyZatoshiToAddressMemoClosure = { _, _, _, _ in
throw "Some error" throw "Some error"
@ -375,7 +379,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.sendToAddress(spendingKey: data.spendingKey, zatoshi: amount, toAddress: recipient, memo: memo) synchronizer.sendToAddress(spendingKey: mockedSpendingKey, zatoshi: amount, toAddress: recipient, memo: memo)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -394,12 +398,13 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testShieldFundsSucceed() throws { func testShieldFundsSucceed() async throws {
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let shieldingThreshold = Zatoshi(1) let shieldingThreshold = Zatoshi(1)
let mockedSpendingKey = await data.spendingKey
synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { receivedSpendingKey, receivedMemo, receivedShieldingThreshold in synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { receivedSpendingKey, receivedMemo, receivedShieldingThreshold in
XCTAssertEqual(receivedSpendingKey, self.data.spendingKey) XCTAssertEqual(receivedSpendingKey, mockedSpendingKey)
XCTAssertEqual(receivedMemo, memo) XCTAssertEqual(receivedMemo, memo)
XCTAssertEqual(receivedShieldingThreshold, shieldingThreshold) XCTAssertEqual(receivedShieldingThreshold, shieldingThreshold)
return self.data.pendingTransactionEntity return self.data.pendingTransactionEntity
@ -407,7 +412,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.shieldFunds(spendingKey: data.spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) synchronizer.shieldFunds(spendingKey: mockedSpendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -426,9 +431,10 @@ class CombineSynchronizerOfflineTests: XCTestCase {
wait(for: [expectation], timeout: 0.5) wait(for: [expectation], timeout: 0.5)
} }
func testShieldFundsThrowsError() throws { func testShieldFundsThrowsError() async throws {
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
let shieldingThreshold = Zatoshi(1) let shieldingThreshold = Zatoshi(1)
let mockedSpendingKey = await data.spendingKey
synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { _, _, _ in synchronizerMock.shieldFundsSpendingKeyMemoShieldingThresholdClosure = { _, _, _ in
throw "Some error" throw "Some error"
@ -436,7 +442,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
let expectation = XCTestExpectation() let expectation = XCTestExpectation()
synchronizer.shieldFunds(spendingKey: data.spendingKey, memo: memo, shieldingThreshold: shieldingThreshold) synchronizer.shieldFunds(spendingKey: mockedSpendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
.sink( .sink(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -581,7 +587,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
func testGetMemosForClearedTransactionSucceed() throws { func testGetMemosForClearedTransactionSucceed() throws {
let memo: Memo = .text(try MemoText("Some message")) let memo: Memo = .text(try MemoText("Some message"))
synchronizerMock.getMemosForTransactionClosure = { receivedTransaction in synchronizerMock.getMemosForClearedTransactionClosure = { receivedTransaction in
XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id)
return [memo] return [memo]
} }
@ -608,7 +614,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testGetMemosForClearedTransactionThrowsError() { func testGetMemosForClearedTransactionThrowsError() {
synchronizerMock.getMemosForTransactionClosure = { _ in synchronizerMock.getMemosForClearedTransactionClosure = { _ in
throw "Some error" throw "Some error"
} }
@ -802,7 +808,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testAllConfirmedTransactionsSucceed() throws { func testAllConfirmedTransactionsSucceed() throws {
synchronizerMock.allConfirmedTransactionsFromTransactionClosure = { receivedTransaction, limit in synchronizerMock.allConfirmedTransactionsFromLimitClosure = { receivedTransaction, limit in
XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id) XCTAssertEqual(receivedTransaction.id, self.data.clearedTransaction.id)
XCTAssertEqual(limit, 3) XCTAssertEqual(limit, 3)
return [self.data.clearedTransaction] return [self.data.clearedTransaction]
@ -830,7 +836,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testAllConfirmedTransactionsThrowsError() throws { func testAllConfirmedTransactionsThrowsError() throws {
synchronizerMock.allConfirmedTransactionsFromTransactionClosure = { _, _ in synchronizerMock.allConfirmedTransactionsFromLimitClosure = { _, _ in
throw "Some error" throw "Some error"
} }
@ -910,7 +916,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
let skippedEntity = UnspentTransactionOutputEntityMock(address: "addr2", txid: Data(), index: 1, script: Data(), valueZat: 2, height: 3) let skippedEntity = UnspentTransactionOutputEntityMock(address: "addr2", txid: Data(), index: 1, script: Data(), valueZat: 2, height: 3)
let refreshedUTXO = (inserted: [insertedEntity], skipped: [skippedEntity]) let refreshedUTXO = (inserted: [insertedEntity], skipped: [skippedEntity])
synchronizerMock.refreshUTXOsAddressFromHeightClosure = { receivedAddress, receivedFromHeight in synchronizerMock.refreshUTXOsAddressFromClosure = { receivedAddress, receivedFromHeight in
XCTAssertEqual(receivedAddress, self.data.transparentAddress) XCTAssertEqual(receivedAddress, self.data.transparentAddress)
XCTAssertEqual(receivedFromHeight, 121000) XCTAssertEqual(receivedFromHeight, 121000)
return refreshedUTXO return refreshedUTXO
@ -939,7 +945,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testRefreshUTXOsThrowsError() { func testRefreshUTXOsThrowsError() {
synchronizerMock.refreshUTXOsAddressFromHeightClosure = { _, _ in synchronizerMock.refreshUTXOsAddressFromClosure = { _, _ in
throw "Some error" throw "Some error"
} }
@ -1126,7 +1132,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testRewindSucceed() { func testRewindSucceed() {
synchronizerMock.rewindPolicyClosure = { receivedPolicy in synchronizerMock.rewindClosure = { receivedPolicy in
if case .quick = receivedPolicy { if case .quick = receivedPolicy {
} else { } else {
XCTFail("Unexpected policy \(receivedPolicy)") XCTFail("Unexpected policy \(receivedPolicy)")
@ -1155,7 +1161,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
} }
func testRewindThrowsError() { func testRewindThrowsError() {
synchronizerMock.rewindPolicyClosure = { _ in synchronizerMock.rewindClosure = { _ in
return Fail(error: "some error").eraseToAnyPublisher() return Fail(error: "some error").eraseToAnyPublisher()
} }

View File

@ -11,24 +11,22 @@ import XCTest
class CompactBlockProcessorOfflineTests: XCTestCase { class CompactBlockProcessorOfflineTests: XCTestCase {
let testFileManager = FileManager() let testFileManager = FileManager()
let testTempDirectory = URL(fileURLWithPath: NSString( var testTempDirectory: URL!
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
try FileManager.default.removeItem(at: self.testTempDirectory) try FileManager.default.removeItem(at: testTempDirectory)
} }
func testComputeProcessingRangeForSingleLoop() async throws { func testComputeProcessingRangeForSingleLoop() async throws {
let network = ZcashNetworkBuilder.network(for: .testnet) let network = ZcashNetworkBuilder.network(for: .testnet)
let realRustBackend = ZcashRustBackend.self let rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .testnet)
let processorConfig = CompactBlockProcessor.Configuration.standard( let processorConfig = CompactBlockProcessor.Configuration.standard(
for: network, for: network,
@ -44,7 +42,7 @@ class CompactBlockProcessorOfflineTests: XCTestCase {
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: realRustBackend, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -55,7 +53,7 @@ class CompactBlockProcessorOfflineTests: XCTestCase {
let processor = CompactBlockProcessor( let processor = CompactBlockProcessor(
service: service, service: service,
storage: storage, storage: storage,
backend: ZcashRustBackend.self, rustBackend: rustBackend,
config: processorConfig, config: processorConfig,
metrics: SDKMetrics(), metrics: SDKMetrics(),
logger: logger logger: logger

View File

@ -13,22 +13,22 @@ import XCTest
class CompactBlockRepositoryTests: XCTestCase { class CompactBlockRepositoryTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .testnet) let network = ZcashNetworkBuilder.network(for: .testnet)
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) testTempDirectory = Environment.uniqueTestTempDirectory
try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .testnet)
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
try? testFileManager.removeItem(at: testTempDirectory) try? testFileManager.removeItem(at: testTempDirectory)
rustBackend = nil
testTempDirectory = nil
} }
func testEmptyStorage() async throws { func testEmptyStorage() async throws {
@ -36,7 +36,7 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -55,7 +55,7 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -82,7 +82,7 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,
@ -108,7 +108,7 @@ class CompactBlockRepositoryTests: XCTestCase {
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: FSMetadataStore.live( metadataStore: FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: ZcashRustBackend.self, rustBackend: rustBackend,
logger: logger logger: logger
), ),
blockDescriptor: .live, blockDescriptor: .live,

View File

@ -16,7 +16,8 @@ class DerivationToolMainnetTests: XCTestCase {
let testRecipientAddress = UnifiedAddress( let testRecipientAddress = UnifiedAddress(
validatedEncoding: """ validatedEncoding: """
u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj
""" """,
networkType: .mainnet
) )
let expectedSpendingKey = UnifiedSpendingKey( let expectedSpendingKey = UnifiedSpendingKey(
@ -45,82 +46,72 @@ class DerivationToolMainnetTests: XCTestCase {
""") """)
let expectedSaplingAddress = SaplingAddress(validatedEncoding: "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0") let expectedSaplingAddress = SaplingAddress(validatedEncoding: "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0")
let derivationTool = DerivationTool(networkType: NetworkType.mainnet) let derivationTool = TestsData(networkType: .mainnet).derivationTools
let expectedTransparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz") let expectedTransparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz")
func testDeriveViewingKeysFromSeed() throws {
func testDeriveViewingKeysFromSeed() async throws {
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0) let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
XCTAssertEqual(expectedViewingKey, viewingKey) XCTAssertEqual(expectedViewingKey, viewingKey)
} }
func testDeriveViewingKeyFromSpendingKeys() throws { func testDeriveViewingKeyFromSpendingKeys() async throws {
XCTAssertEqual( let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: expectedSpendingKey)
expectedViewingKey, XCTAssertEqual(expectedViewingKey, viewingKey)
try derivationTool.deriveUnifiedFullViewingKey(from: expectedSpendingKey)
)
} }
func testDeriveSpendingKeysFromSeed() throws { func testDeriveSpendingKeysFromSeed() async throws {
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0) let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(expectedSpendingKey, spendingKey) XCTAssertEqual(expectedSpendingKey, spendingKey)
} }
func testDeriveUnifiedSpendingKeyFromSeed() throws { func testDeriveUnifiedSpendingKeyFromSeed() async throws {
let account = 0 let account = 0
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
XCTAssertNoThrow(try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: account)) _ = try await derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: account)
} }
func testGetTransparentAddressFromUA() throws { func testGetTransparentAddressFromUA() throws {
XCTAssertEqual( XCTAssertEqual(
try DerivationTool.transparentReceiver(from: testRecipientAddress), try DerivationTool(networkType: .mainnet).transparentReceiver(from: testRecipientAddress),
expectedTransparentAddress expectedTransparentAddress
) )
} }
func testIsValidViewingKey() { func testIsValidViewingKey() {
XCTAssertTrue( XCTAssertTrue(
DerivationTool.rustwelding.isValidSaplingExtendedFullViewingKey( ZcashKeyDerivationBackend(networkType: .mainnet).isValidSaplingExtendedFullViewingKey(
""" """
zxviews1q0dm7hkzqqqqpqplzv3f50rl4vay8uy5zg9e92f62lqg6gzu63rljety32xy5tcyenzuu3n386ws772nm6tp4sads8n37gff6nxmyz8dn9keehmapk0spc6pzx5ux\ zxviews1q0dm7hkzqqqqpqplzv3f50rl4vay8uy5zg9e92f62lqg6gzu63rljety32xy5tcyenzuu3n386ws772nm6tp4sads8n37gff6nxmyz8dn9keehmapk0spc6pzx5ux\
epgu52xnwzxxnuja5tv465t9asppnj3eqncu3s7g3gzg5x8ss4ypkw08xwwyj7ky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658\ epgu52xnwzxxnuja5tv465t9asppnj3eqncu3s7g3gzg5x8ss4ypkw08xwwyj7ky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658\
vljjddj7s645q399jd7 vljjddj7s645q399jd7
""", """
networkType: .mainnet
) )
) )
XCTAssertFalse( XCTAssertFalse(
DerivationTool.rustwelding.isValidSaplingExtendedFullViewingKey( ZcashKeyDerivationBackend(networkType: .mainnet).isValidSaplingExtendedFullViewingKey(
"zxviews1q0dm7hkzky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658vljjddj7s645q399jd7", "zxviews1q0dm7hkzky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658vljjddj7s645q399jd7"
networkType: .mainnet
) )
) )
} }
func testDeriveQuiteALotOfUnifiedKeysFromSeed() throws { func testDeriveQuiteALotOfUnifiedKeysFromSeed() async throws {
let numberOfAccounts: Int = 10 let numberOfAccounts: Int = 10
let ufvks = try (0 ..< numberOfAccounts) var ufvks: [UnifiedFullViewingKey] = []
.map({ for i in 0..<numberOfAccounts {
try derivationTool.deriveUnifiedSpendingKey( let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: [UInt8](seedData), accountIndex: i)
seed: [UInt8](seedData), let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
accountIndex: $0 ufvks.append(viewingKey)
) }
})
.map {
try derivationTool.deriveUnifiedFullViewingKey(
from: $0
)
}
XCTAssertEqual(ufvks.count, numberOfAccounts) XCTAssertEqual(ufvks.count, numberOfAccounts)
XCTAssertEqual(ufvks[0].account, 0) XCTAssertEqual(ufvks[0].account, 0)
@ -129,7 +120,7 @@ class DerivationToolMainnetTests: XCTestCase {
func testShouldFailOnInvalidChecksumAddresses() { func testShouldFailOnInvalidChecksumAddresses() {
let testAddress = "t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1" let testAddress = "t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1"
XCTAssertFalse(derivationTool.isValidTransparentAddress(testAddress)) XCTAssertFalse(DerivationTool(networkType: .mainnet).isValidTransparentAddress(testAddress))
} }
func testSpendingKeyValidationFailsOnInvalidKey() throws { func testSpendingKeyValidationFailsOnInvalidKey() throws {
@ -139,7 +130,7 @@ class DerivationToolMainnetTests: XCTestCase {
4fsuaz686lgszc7nc9vvZzZzZz 4fsuaz686lgszc7nc9vvZzZzZz
""" """
XCTAssertFalse(derivationTool.isValidSaplingExtendedSpendingKey(wrongSpendingKey)) XCTAssertFalse(DerivationTool(networkType: .mainnet).isValidSaplingExtendedSpendingKey(wrongSpendingKey))
} }
// TODO: [#509] Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509 // TODO: [#509] Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509
// func testSpendingKeyValidationThrowsWhenWrongNetwork() throws { // func testSpendingKeyValidationThrowsWhenWrongNetwork() throws {

View File

@ -7,6 +7,7 @@
// swift-format-ignore-file // swift-format-ignore-file
import XCTest import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class DerivationToolTestnetTests: XCTestCase { class DerivationToolTestnetTests: XCTestCase {
@ -16,7 +17,8 @@ class DerivationToolTestnetTests: XCTestCase {
validatedEncoding: """ validatedEncoding: """
utest1uqmec4a2njqz2z2rwppchsd06qe7a0jh4jmsqr0yy99m9er9646zlxunf3v8qr0hncgv86e8a62vxy0qa32qzetmj8s57yudmyx9zav6f52nurclsqjkqtjtpz6vg679p6wkcz\ utest1uqmec4a2njqz2z2rwppchsd06qe7a0jh4jmsqr0yy99m9er9646zlxunf3v8qr0hncgv86e8a62vxy0qa32qzetmj8s57yudmyx9zav6f52nurclsqjkqtjtpz6vg679p6wkcz\
pl2wu pl2wu
""" """,
networkType: .testnet
) )
let expectedSpendingKey = UnifiedSpendingKey( let expectedSpendingKey = UnifiedSpendingKey(
@ -53,15 +55,14 @@ class DerivationToolTestnetTests: XCTestCase {
validatedEncoding: "ztestsapling1475xtm56czrzmleqzzlu4cxvjjfsy2p6rv78q07232cpsx5ee52k0mn5jyndq09mampkgvrxnwg" validatedEncoding: "ztestsapling1475xtm56czrzmleqzzlu4cxvjjfsy2p6rv78q07232cpsx5ee52k0mn5jyndq09mampkgvrxnwg"
) )
let derivationTool = DerivationTool(networkType: NetworkType.testnet) let derivationTool = TestsData(networkType: .testnet).derivationTools
let expectedTransparentAddress = TransparentAddress(validatedEncoding: "tmXuTnE11JojToagTqxXUn6KvdxDE3iLKbp") let expectedTransparentAddress = TransparentAddress(validatedEncoding: "tmXuTnE11JojToagTqxXUn6KvdxDE3iLKbp")
func testDeriveViewingKeysFromSeed() throws { func testDeriveViewingKeysFromSeed() async throws {
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0) let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
XCTAssertEqual(expectedViewingKey, viewingKey) XCTAssertEqual(expectedViewingKey, viewingKey)
} }
@ -73,62 +74,54 @@ class DerivationToolTestnetTests: XCTestCase {
// ) // )
} }
func testDeriveSpendingKeysFromSeed() throws { func testDeriveSpendingKeysFromSeed() async throws {
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0) let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(expectedSpendingKey, spendingKey) XCTAssertEqual(expectedSpendingKey, spendingKey)
} }
func testDeriveUnifiedSpendingKeyFromSeed() throws { func testDeriveUnifiedSpendingKeyFromSeed() async throws {
let account = 0 let account = 0
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
XCTAssertNoThrow(try derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: account)) _ = try await derivationTool.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: account)
} }
func testGetTransparentAddressFromUA() throws { func testGetTransparentAddressFromUA() throws {
XCTAssertEqual( XCTAssertEqual(
try DerivationTool.transparentReceiver(from: testRecipientAddress), try DerivationTool(networkType: .testnet).transparentReceiver(from: testRecipientAddress),
expectedTransparentAddress expectedTransparentAddress
) )
} }
func testIsValidViewingKey() throws { func testIsValidViewingKey() throws {
XCTAssertTrue( XCTAssertTrue(
DerivationTool.rustwelding.isValidSaplingExtendedFullViewingKey( ZcashKeyDerivationBackend(networkType: .testnet).isValidSaplingExtendedFullViewingKey(
""" """
zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htf\ zxviewtestsapling1qdxykmuaqqqqpqqg3x5c02p4rhw0rtszr8ln4xl7g6wg6qzsqgn445qsu3cq4vd6l5smlqrckkl2x5rnrauzc4gp665q3zyw0qf2sfdsx5wpp832htf\
avqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrz\ avqk72uchuuvq2dpmgk8jfaza5t5l56u66fpx0sr8ewp9s3wj2txavmhhlazn5rj8mshh470fkrmzg4xarhrqlygg8f486307ujhndwhsw2h7ddzf89k3534aeu0ypz2tjgrz\
lcqtat380vhe8awm03f58cqgegsaj lcqtat380vhe8awm03f58cqgegsaj
""", """
networkType: .testnet
) )
) )
XCTAssertFalse( XCTAssertFalse(
DerivationTool.rustwelding.isValidSaplingExtendedFullViewingKey( ZcashKeyDerivationBackend(networkType: .testnet).isValidSaplingExtendedFullViewingKey(
"zxviews1q0dm7hkzky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658vljjddj7s645q399jd7", "zxviews1q0dm7hkzky5skvnd9ldwj2u8fz2ry94s5q8p9lyp3j96yckudmp087d2jr2rnfuvjp7f56v78vpe658vljjddj7s645q399jd7"
networkType: .testnet
) )
) )
} }
func testDeriveQuiteALotOfUnifiedKeysFromSeed() throws { func testDeriveQuiteALotOfUnifiedKeysFromSeed() async throws {
let numberOfAccounts: Int = 10 let numberOfAccounts: Int = 10
let ufvks = try (0 ..< numberOfAccounts) var ufvks: [UnifiedFullViewingKey] = []
.map({ for i in 0..<numberOfAccounts {
try derivationTool.deriveUnifiedSpendingKey( let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: [UInt8](seedData), accountIndex: i)
seed: [UInt8](seedData), let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
accountIndex: $0 ufvks.append(viewingKey)
) }
})
.map {
try derivationTool.deriveUnifiedFullViewingKey(
from: $0
)
}
XCTAssertEqual(ufvks.count, numberOfAccounts) XCTAssertEqual(ufvks.count, numberOfAccounts)
XCTAssertEqual(ufvks[0].account, 0) XCTAssertEqual(ufvks[0].account, 0)
@ -137,7 +130,7 @@ class DerivationToolTestnetTests: XCTestCase {
func testShouldFailOnInvalidChecksumAddresses() throws { func testShouldFailOnInvalidChecksumAddresses() throws {
let testAddress = "t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1" let testAddress = "t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1"
XCTAssertFalse(derivationTool.isValidTransparentAddress(testAddress)) XCTAssertFalse(DerivationTool(networkType: .testnet).isValidTransparentAddress(testAddress))
} }
func testSpendingKeyValidationFailsOnInvalidKey() { func testSpendingKeyValidationFailsOnInvalidKey() {
@ -147,7 +140,7 @@ class DerivationToolTestnetTests: XCTestCase {
4fsuaz686lgszc7nc9vvZzZzZz 4fsuaz686lgszc7nc9vvZzZzZz
""" """
XCTAssertFalse(derivationTool.isValidSaplingExtendedSpendingKey(wrongSpendingKey)) XCTAssertFalse(DerivationTool(networkType: .testnet).isValidSaplingExtendedSpendingKey(wrongSpendingKey))
} }
// TODO: [#509] Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509 // TODO: [#509] Address encoding does not catch this test https://github.com/zcash/ZcashLightClientKit/issues/509
// func testSpendingKeyValidationThrowsWhenWrongNetwork() throws { // func testSpendingKeyValidationThrowsWhenWrongNetwork() throws {

View File

@ -11,26 +11,27 @@ import XCTest
var logger = OSLogger(logLevel: .debug) var logger = OSLogger(logLevel: .debug)
final class FsBlockStorageTests: XCTestCase { final class FsBlockStorageTests: XCTestCase {
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
var fsBlockDb: URL! var fsBlockDb: URL!
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
testTempDirectory = Environment.uniqueTestTempDirectory
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
self.fsBlockDb = self.testTempDirectory.appendingPathComponent("FsBlockDb-\(Int.random(in: 0 ... .max))") self.fsBlockDb = testTempDirectory.appendingPathComponent("FsBlockDb-\(Int.random(in: 0 ... .max))")
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
try self.testFileManager.createDirectory(at: self.fsBlockDb, withIntermediateDirectories: false) try self.testFileManager.createDirectory(at: self.fsBlockDb, withIntermediateDirectories: false)
rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .testnet)
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
try? testFileManager.removeItem(at: testTempDirectory) try? testFileManager.removeItem(at: testTempDirectory)
rustBackend = nil
testTempDirectory = nil
} }
func testLatestHeightEmptyCache() async throws { func testLatestHeightEmptyCache() async throws {
@ -54,7 +55,7 @@ final class FsBlockStorageTests: XCTestCase {
let blockNameFixture = "This-is-a-fixture" let blockNameFixture = "This-is-a-fixture"
let freshCache = FSCompactBlockRepository( let freshCache = FSCompactBlockRepository(
fsBlockDbRoot: self.testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: .mock, metadataStore: .mock,
blockDescriptor: ZcashCompactBlockDescriptor( blockDescriptor: ZcashCompactBlockDescriptor(
height: { _ in nil }, height: { _ in nil },
@ -170,7 +171,7 @@ final class FsBlockStorageTests: XCTestCase {
func testGetLatestHeight() async throws { func testGetLatestHeight() async throws {
let freshCache = FSCompactBlockRepository( let freshCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self, logger: logger), metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: rustBackend, logger: logger),
blockDescriptor: .live, blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted, contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger logger: logger
@ -281,8 +282,8 @@ final class FsBlockStorageTests: XCTestCase {
func testClearTheCache() async throws { func testClearTheCache() async throws {
let fsBlockCache = FSCompactBlockRepository( let fsBlockCache = FSCompactBlockRepository(
fsBlockDbRoot: self.testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self, logger: logger), metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: rustBackend, logger: logger),
blockDescriptor: .live, blockDescriptor: .live,
contentProvider: DirectoryListingProviders.naive, contentProvider: DirectoryListingProviders.naive,
logger: logger logger: logger
@ -319,11 +320,9 @@ final class FsBlockStorageTests: XCTestCase {
} }
func disabled_testStoringTenSandblastedBlocks() async throws { func disabled_testStoringTenSandblastedBlocks() async throws {
let realRustBackend = ZcashRustBackend.self
let realCache = FSCompactBlockRepository( let realCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend, logger: logger), metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: rustBackend, logger: logger),
blockDescriptor: .live, blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted, contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger logger: logger
@ -350,11 +349,9 @@ final class FsBlockStorageTests: XCTestCase {
} }
func testStoringTenSandblastedBlocksFailsAndThrows() async throws { func testStoringTenSandblastedBlocksFailsAndThrows() async throws {
let realRustBackend = ZcashRustBackend.self
let realCache = FSCompactBlockRepository( let realCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend, logger: logger), metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: rustBackend, logger: logger),
blockDescriptor: .live, blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted, contentProvider: DirectoryListingProviders.defaultSorted,
fileWriter: FSBlockFileWriter(writeToURL: { _, _ in throw FixtureError.arbitraryError }), fileWriter: FSBlockFileWriter(writeToURL: { _, _ in throw FixtureError.arbitraryError }),
@ -377,11 +374,9 @@ final class FsBlockStorageTests: XCTestCase {
} }
func testStoringTenSandblastedBlocksAndRewindFiveThenStoreThemBack() async throws { func testStoringTenSandblastedBlocksAndRewindFiveThenStoreThemBack() async throws {
let realRustBackend = ZcashRustBackend.self
let realCache = FSCompactBlockRepository( let realCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: realRustBackend, logger: logger), metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: rustBackend, logger: logger),
blockDescriptor: .live, blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted, contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger logger: logger
@ -420,41 +415,19 @@ final class FsBlockStorageTests: XCTestCase {
XCTAssertEqual(newLatestHeight, 19) XCTAssertEqual(newLatestHeight, 19)
} }
func testMetadataStoreThrowsWhenCallReturnsFalse() async {
guard let sandblastedBlocks = try? SandblastSimulator().sandblast(with: CompactBlockRange(uncheckedBounds: (10, 19))) else {
XCTFail("failed to create sandblasted blocks")
return
}
MockRustBackend.writeBlocksMetadataResult = { false }
do {
try await FSMetadataStore.saveBlocksMeta(
sandblastedBlocks,
fsBlockDbRoot: testTempDirectory,
rustBackend: MockRustBackend.self,
logger: logger
)
} catch CompactBlockRepositoryError.failedToWriteMetadata {
// this is fine
} catch {
XCTFail("Expected `CompactBlockRepositoryError.failedToWriteMetadata` but found: \(error.localizedDescription)")
}
}
func testMetadataStoreThrowsWhenRustThrows() async { func testMetadataStoreThrowsWhenRustThrows() async {
guard let sandblastedBlocks = try? SandblastSimulator().sandblast(with: CompactBlockRange(uncheckedBounds: (10, 19))) else { guard let sandblastedBlocks = try? SandblastSimulator().sandblast(with: CompactBlockRange(uncheckedBounds: (10, 19))) else {
XCTFail("failed to create sandblasted blocks") XCTFail("failed to create sandblasted blocks")
return return
} }
let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend)
MockRustBackend.writeBlocksMetadataResult = { throw RustWeldingError.genericError(message: "oops") } await mockBackend.rustBackendMock.setWriteBlocksMetadataBlocksThrowableError(RustWeldingError.genericError(message: "oops"))
do { do {
try await FSMetadataStore.saveBlocksMeta( try await FSMetadataStore.saveBlocksMeta(
sandblastedBlocks, sandblastedBlocks,
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: MockRustBackend.self, rustBackend: mockBackend.rustBackendMock,
logger: logger logger: logger
) )
} catch CompactBlockRepositoryError.failedToWriteMetadata { } catch CompactBlockRepositoryError.failedToWriteMetadata {
@ -465,13 +438,15 @@ final class FsBlockStorageTests: XCTestCase {
} }
func testMetadataStoreThrowsWhenRewindFails() async { func testMetadataStoreThrowsWhenRewindFails() async {
MockRustBackend.rewindCacheToHeightResult = { false } let mockBackend = await RustBackendMockHelper(rustBackend: rustBackend)
await mockBackend.rustBackendMock.setRewindCacheToHeightHeightThrowableError(RustWeldingError.genericError(message: "oops"))
let expectedHeight = BlockHeight(1000) let expectedHeight = BlockHeight(1000)
do { do {
try await FSMetadataStore.live( try await FSMetadataStore.live(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
rustBackend: MockRustBackend.self, rustBackend: mockBackend.rustBackendMock,
logger: logger logger: logger
) )
.rewindToHeight(expectedHeight) .rewindToHeight(expectedHeight)
@ -496,7 +471,7 @@ final class FsBlockStorageTests: XCTestCase {
// NOTE: performance tests don't work with async code. Thanks Apple! // NOTE: performance tests don't work with async code. Thanks Apple!
let freshCache = FSCompactBlockRepository( let freshCache = FSCompactBlockRepository(
fsBlockDbRoot: testTempDirectory, fsBlockDbRoot: testTempDirectory,
metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: ZcashRustBackend.self, logger: logger), metadataStore: .live(fsBlockDbRoot: testTempDirectory, rustBackend: rustBackend, logger: logger),
blockDescriptor: .live, blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted, contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger logger: logger
@ -551,7 +526,7 @@ extension FSMetadataStore {
static let mock = FSMetadataStore( static let mock = FSMetadataStore(
saveBlocksMeta: { _ in }, saveBlocksMeta: { _ in },
rewindToHeight: { _ in }, rewindToHeight: { _ in },
initFsBlockDbRoot: { _ in true }, initFsBlockDbRoot: { },
latestHeight: { .empty() } latestHeight: { .empty() }
) )
} }

View File

@ -15,8 +15,9 @@ class NotesRepositoryTests: XCTestCase {
override func setUp() async throws { override func setUp() async throws {
try await super.setUp() try await super.setUp()
sentNotesRepository = try! await TestDbBuilder.sentNotesRepository() let rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: Environment.uniqueTestTempDirectory, networkType: .testnet)
receivedNotesRepository = try! await TestDbBuilder.receivedNotesRepository() sentNotesRepository = try! await TestDbBuilder.sentNotesRepository(rustBackend: rustBackend)
receivedNotesRepository = try! await TestDbBuilder.receivedNotesRepository(rustBackend: rustBackend)
} }
override func tearDown() { override func tearDown() {

View File

@ -17,15 +17,15 @@ class NullBytesTests: XCTestCase {
let validZaddr = "zs1gqtfu59z20s9t20mxlxj86zpw6p69l0ev98uxrmlykf2nchj2dw8ny5e0l22kwmld2afc37gkfp" let validZaddr = "zs1gqtfu59z20s9t20mxlxj86zpw6p69l0ev98uxrmlykf2nchj2dw8ny5e0l22kwmld2afc37gkfp"
let zAddrWithNullBytes = "\(validZaddr)\0something else that makes the address invalid" let zAddrWithNullBytes = "\(validZaddr)\0something else that makes the address invalid"
XCTAssertFalse(ZcashRustBackend.isValidSaplingAddress(zAddrWithNullBytes, networkType: networkType)) XCTAssertFalse(DerivationTool(networkType: networkType).isValidSaplingAddress(zAddrWithNullBytes))
} }
func testTaddrNullBytes() throws { func testTaddrNullBytes() throws {
// this is a valid tAddr. if you send ZEC to it, you will be contributing to Human Rights Foundation. see more ways to help at https://paywithz.cash/ // this is a valid tAddr. if you send ZEC to it, you will be contributing to Human Rights Foundation. see more ways to help at https://paywithz.cash/
let validTAddr = "t1J5pTRzJi7j8Xw9VJTrPxPEkaigr69gKVT" let validTAddr = "t1J5pTRzJi7j8Xw9VJTrPxPEkaigr69gKVT"
let tAddrWithNullBytes = "\(validTAddr)\0fasdfasdf" let tAddrWithNullBytes = "\(validTAddr)\0fasdfasdf"
XCTAssertFalse(ZcashRustBackend.isValidTransparentAddress(tAddrWithNullBytes, networkType: networkType)) XCTAssertFalse(DerivationTool(networkType: networkType).isValidTransparentAddress(tAddrWithNullBytes))
} }
func testInitAccountTableNullBytes() async throws { func testInitAccountTableNullBytes() async throws {
@ -50,14 +50,18 @@ class NullBytesTests: XCTestCase {
934dc76f087935a5c07788000b4e3aae24883adfec51b5f4d260 934dc76f087935a5c07788000b4e3aae24883adfec51b5f4d260
""" """
let rustBackend = ZcashRustBackend.makeForTests(
dbData: try! __dataDbURL(),
fsBlockDbRoot: Environment.uniqueTestTempDirectory,
networkType: networkType
)
do { do {
_ = try await ZcashRustBackend.initBlocksTable( _ = try await rustBackend.initBlocksTable(
dbData: __dataDbURL(),
height: height, height: height,
hash: wrongHash, hash: wrongHash,
time: time, time: time,
saplingTree: goodTree, saplingTree: goodTree
networkType: networkType
) )
XCTFail("InitBlocksTable with Null bytes on hash string should have failed") XCTFail("InitBlocksTable with Null bytes on hash string should have failed")
} catch { } catch {
@ -75,13 +79,11 @@ class NullBytesTests: XCTestCase {
} }
do { do {
try await ZcashRustBackend.initBlocksTable( try await rustBackend.initBlocksTable(
dbData: __dataDbURL(),
height: height, height: height,
hash: goodHash, hash: goodHash,
time: time, time: time,
saplingTree: wrongTree, saplingTree: wrongTree
networkType: networkType
) )
XCTFail("InitBlocksTable with Null bytes on saplingTree string should have failed") XCTFail("InitBlocksTable with Null bytes on saplingTree string should have failed")
} catch { } catch {
@ -106,7 +108,7 @@ class NullBytesTests: XCTestCase {
// let goodSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv") // let goodSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv")
// //
// XCTAssertThrowsError( // XCTAssertThrowsError(
// try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(wrongSpendingKeys, networkType: networkType), // try DerivationTool(networkType: networkType)deriveSaplingExtendedFullViewingKey(wrongSpendingKeys, networkType: networkType),
// "Should have thrown an error but didn't! this is dangerous!" // "Should have thrown an error but didn't! this is dangerous!"
// ) { error in // ) { error in
// guard let rustError = error as? RustWeldingError else { // guard let rustError = error as? RustWeldingError else {
@ -122,7 +124,7 @@ class NullBytesTests: XCTestCase {
// } // }
// } // }
// //
// XCTAssertNoThrow(try ZcashRustBackend.deriveSaplingExtendedFullViewingKey(goodSpendingKeys, networkType: networkType)) // XCTAssertNoThrow(try DerivationTool(networkType: networkType)deriveSaplingExtendedFullViewingKey(goodSpendingKeys, networkType: networkType))
} }
func testCheckNullBytes() throws { func testCheckNullBytes() throws {

View File

@ -17,7 +17,7 @@ final class RecipientTests: XCTestCase {
let transparentString = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz" let transparentString = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz"
func testUnifiedRecipient() throws { func testUnifiedRecipient() throws {
let expectedUnifiedAddress = UnifiedAddress(validatedEncoding: uaString) let expectedUnifiedAddress = UnifiedAddress(validatedEncoding: uaString, networkType: .mainnet)
XCTAssertEqual(try Recipient(uaString, network: .mainnet), .unified(expectedUnifiedAddress)) XCTAssertEqual(try Recipient(uaString, network: .mainnet), .unified(expectedUnifiedAddress))
} }

View File

@ -12,7 +12,7 @@ import XCTest
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class SynchronizerOfflineTests: XCTestCase { class SynchronizerOfflineTests: XCTestCase {
let data = AlternativeSynchronizerAPITestsData() let data = TestsData(networkType: .testnet)
var network: ZcashNetwork! var network: ZcashNetwork!
var cancellables: [AnyCancellable] = [] var cancellables: [AnyCancellable] = []
@ -257,12 +257,12 @@ class SynchronizerOfflineTests: XCTestCase {
let synchronizer = SDKSynchronizer(initializer: initializer) let synchronizer = SDKSynchronizer(initializer: initializer)
do { do {
let derivationTool = DerivationTool(networkType: network.networkType) let derivationTool = initializer.makeDerivationTool()
let spendingKey = try derivationTool.deriveUnifiedSpendingKey( let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(
seed: Environment.seedBytes, seed: Environment.seedBytes,
accountIndex: 0 accountIndex: 0
) )
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey) let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
_ = try await synchronizer.prepare(with: Environment.seedBytes, viewingKeys: [viewingKey], walletBirthday: 123000) _ = try await synchronizer.prepare(with: Environment.seedBytes, viewingKeys: [viewingKey], walletBirthday: 123000)
XCTFail("Failure of prepare is expected.") XCTFail("Failure of prepare is expected.")
} catch { } catch {
@ -350,7 +350,6 @@ class SynchronizerOfflineTests: XCTestCase {
} }
func testIsNewSyncSessionWhenRestartingFromError() { func testIsNewSyncSessionWhenRestartingFromError() {
XCTAssertTrue( XCTAssertTrue(
SessionTicker.live.isNewSyncSession( SessionTicker.live.isNewSyncSession(
.error(SynchronizerError.generalError(message: "some error")), .error(SynchronizerError.generalError(message: "some error")),

View File

@ -14,7 +14,8 @@ class TransactionRepositoryTests: XCTestCase {
override func setUp() async throws { override func setUp() async throws {
try await super.setUp() try await super.setUp()
transactionRepository = try! await TestDbBuilder.transactionRepository() let rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: Environment.uniqueTestTempDirectory, networkType: .testnet)
transactionRepository = try! await TestDbBuilder.transactionRepository(rustBackend: rustBackend)
} }
override func tearDown() { override func tearDown() {

View File

@ -28,11 +28,11 @@ final class UnifiedTypecodesTests: XCTestCase {
return return
} }
let address = UnifiedAddress(validatedEncoding: uAddress) let address = UnifiedAddress(validatedEncoding: uAddress, networkType: .testnet)
let typecodes = try ZcashRustBackend.receiverTypecodesOnUnifiedAddress(address.stringEncoded) let typecodes = try DerivationTool(networkType: .testnet).receiverTypecodesFromUnifiedAddress(address)
XCTAssertEqual(typecodes, [2, 0]) XCTAssertEqual(typecodes, [.sapling, .p2pkh])
} }
func testUnifiedAddressHasTransparentSaplingReceivers() throws { func testUnifiedAddressHasTransparentSaplingReceivers() throws {
@ -44,9 +44,9 @@ final class UnifiedTypecodesTests: XCTestCase {
return return
} }
let address = UnifiedAddress(validatedEncoding: uAddress) let address = UnifiedAddress(validatedEncoding: uAddress, networkType: .testnet)
let typecodes = try DerivationTool.receiverTypecodesFromUnifiedAddress(address) let typecodes = try DerivationTool(networkType: .testnet).receiverTypecodesFromUnifiedAddress(address)
XCTAssertEqual( XCTAssertEqual(
Set<UnifiedAddress.ReceiverTypecodes>(typecodes), Set<UnifiedAddress.ReceiverTypecodes>(typecodes),
@ -70,7 +70,8 @@ final class UnifiedTypecodesTests: XCTestCase {
validatedEncoding: """ validatedEncoding: """
u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxg\ u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxg\
pyuj pyuj
""" """,
networkType: .testnet
) )
XCTAssertEqual(try ua.availableReceiverTypecodes(), [.sapling, .p2pkh]) XCTAssertEqual(try ua.availableReceiverTypecodes(), [.sapling, .p2pkh])

View File

@ -12,13 +12,8 @@ import XCTest
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class WalletTests: XCTestCase { class WalletTests: XCTestCase {
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager() let testFileManager = FileManager()
var testTempDirectory: URL!
var dbData: URL! = nil var dbData: URL! = nil
var paramDestination: URL! = nil var paramDestination: URL! = nil
var network = ZcashNetworkBuilder.network(for: .testnet) var network = ZcashNetworkBuilder.network(for: .testnet)
@ -26,8 +21,9 @@ class WalletTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
testTempDirectory = Environment.uniqueTestTempDirectory
dbData = try __dataDbURL() dbData = try __dataDbURL()
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false) try self.testFileManager.createDirectory(at: testTempDirectory, withIntermediateDirectories: false)
paramDestination = try __documentsDirectory().appendingPathComponent("parameters") paramDestination = try __documentsDirectory().appendingPathComponent("parameters")
} }
@ -36,17 +32,17 @@ class WalletTests: XCTestCase {
if testFileManager.fileExists(atPath: dbData.absoluteString) { if testFileManager.fileExists(atPath: dbData.absoluteString) {
try testFileManager.trashItem(at: dbData, resultingItemURL: nil) try testFileManager.trashItem(at: dbData, resultingItemURL: nil)
} }
try? self.testFileManager.removeItem(at: self.testTempDirectory) try? self.testFileManager.removeItem(at: testTempDirectory)
} }
func testWalletInitialization() async throws { func testWalletInitialization() async throws {
let derivationTool = DerivationTool(networkType: network.networkType) let derivationTool = TestsData(networkType: network.networkType).derivationTools
let ufvk = try derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0) let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0)
.map({ try derivationTool.deriveUnifiedFullViewingKey(from: $0) }) let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
let wallet = Initializer( let wallet = Initializer(
cacheDbURL: nil, cacheDbURL: nil,
fsBlockDbRoot: self.testTempDirectory, fsBlockDbRoot: testTempDirectory,
dataDbURL: try __dataDbURL(), dataDbURL: try __dataDbURL(),
pendingDbURL: try TestDbBuilder.pendingTransactionsDbURL(), pendingDbURL: try TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default, endpoint: LightWalletEndpointBuilder.default,
@ -58,7 +54,7 @@ class WalletTests: XCTestCase {
let synchronizer = SDKSynchronizer(initializer: wallet) let synchronizer = SDKSynchronizer(initializer: wallet)
do { do {
guard case .success = try await synchronizer.prepare(with: seedData.bytes, viewingKeys: [ufvk], walletBirthday: 663194) else { guard case .success = try await synchronizer.prepare(with: seedData.bytes, viewingKeys: [viewingKey], walletBirthday: 663194) else {
XCTFail("Failed to initDataDb. Expected `.success` got: `.seedRequired`") XCTFail("Failed to initDataDb. Expected `.success` got: `.seedRequired`")
return return
} }

View File

@ -12,8 +12,8 @@ import XCTest
class ZcashRustBackendTests: XCTestCase { class ZcashRustBackendTests: XCTestCase {
var dbData: URL! var dbData: URL!
var rustBackend: ZcashRustBackendWelding!
var dataDbHandle = TestDbHandle(originalDb: TestDbBuilder.prePopulatedDataDbURL()!) var dataDbHandle = TestDbHandle(originalDb: TestDbBuilder.prePopulatedDataDbURL()!)
let spendingKey = """ let spendingKey = """
secret-extended-key-test1qvpevftsqqqqpqy52ut2vv24a2qh7nsukew7qg9pq6djfwyc3xt5vaxuenshp2hhspp9qmqvdh0gs2ljpwxders5jkwgyhgln0drjqaguaenfhehz4esdl4k\ secret-extended-key-test1qvpevftsqqqqpqy52ut2vv24a2qh7nsukew7qg9pq6djfwyc3xt5vaxuenshp2hhspp9qmqvdh0gs2ljpwxders5jkwgyhgln0drjqaguaenfhehz4esdl4k\
wlm5t9q0l6wmzcrvcf5ed6dqzvct3e2ge7f6qdvzhp02m7sp5a0qjssrwpdh7u6tq89hl3wchuq8ljq8r8rwd6xdwh3nry9at80z7amnj3s6ah4jevnvfr08gxpws523z95g6dmn4wm6l3658\ wlm5t9q0l6wmzcrvcf5ed6dqzvct3e2ge7f6qdvzhp02m7sp5a0qjssrwpdh7u6tq89hl3wchuq8ljq8r8rwd6xdwh3nry9at80z7amnj3s6ah4jevnvfr08gxpws523z95g6dmn4wm6l3658\
@ -24,23 +24,27 @@ class ZcashRustBackendTests: XCTestCase {
let zpend: Int = 500_000 let zpend: Int = 500_000
let networkType = NetworkType.testnet let networkType = NetworkType.testnet
override func setUp() { override func setUp() {
super.setUp() super.setUp()
dbData = try! __dataDbURL() dbData = try! __dataDbURL()
try? dataDbHandle.setUp() try? dataDbHandle.setUp()
rustBackend = ZcashRustBackend.makeForTests(dbData: dbData, fsBlockDbRoot: Environment.uniqueTestTempDirectory, networkType: .testnet)
} }
override func tearDown() { override func tearDown() {
super.tearDown() super.tearDown()
try? FileManager.default.removeItem(at: dbData!) try? FileManager.default.removeItem(at: dbData!)
dataDbHandle.dispose() dataDbHandle.dispose()
rustBackend = nil
} }
func testInitWithShortSeedAndFail() async throws { func testInitWithShortSeedAndFail() async throws {
let seed = "testreferencealice" let seed = "testreferencealice"
let dbInit = try await ZcashRustBackend.initDataDb(dbData: self.dbData!, seed: nil, networkType: self.networkType) let dbInit = try await rustBackend.initDataDb(seed: nil)
guard case .success = dbInit else { guard case .success = dbInit else {
XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))") XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))")
@ -48,76 +52,64 @@ class ZcashRustBackendTests: XCTestCase {
} }
do { do {
_ = try await ZcashRustBackend.createAccount(dbData: dbData!, seed: Array(seed.utf8), networkType: networkType) _ = try await rustBackend.createAccount(seed: Array(seed.utf8))
XCTFail("createAccount should fail here.") XCTFail("createAccount should fail here.")
} catch { } } catch { }
} }
func testIsValidTransparentAddressFalse() { func testIsValidTransparentAddressFalse() {
XCTAssertFalse( XCTAssertFalse(
ZcashRustBackend.isValidTransparentAddress( ZcashKeyDerivationBackend(networkType: networkType).isValidTransparentAddress(
"ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc", "ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc"
networkType: networkType
) )
) )
} }
func testIsValidTransparentAddressTrue() { func testIsValidTransparentAddressTrue() {
XCTAssertTrue( XCTAssertTrue(
ZcashRustBackend.isValidTransparentAddress( ZcashKeyDerivationBackend(networkType: networkType).isValidTransparentAddress(
"tmSwpioc7reeoNrYB9SKpWkurJz3yEj3ee7", "tmSwpioc7reeoNrYB9SKpWkurJz3yEj3ee7"
networkType: networkType
) )
) )
} }
func testIsValidSaplingAddressTrue() { func testIsValidSaplingAddressTrue() {
XCTAssertTrue( XCTAssertTrue(
ZcashRustBackend.isValidSaplingAddress( ZcashKeyDerivationBackend(networkType: networkType).isValidSaplingAddress(
"ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc", "ztestsapling12k9m98wmpjts2m56wc60qzhgsfvlpxcwah268xk5yz4h942sd58jy3jamqyxjwums6hw7kfa4cc"
networkType: networkType
) )
) )
} }
func testIsValidSaplingAddressFalse() { func testIsValidSaplingAddressFalse() {
XCTAssertFalse( XCTAssertFalse(
ZcashRustBackend.isValidSaplingAddress( ZcashKeyDerivationBackend(networkType: networkType).isValidSaplingAddress(
"tmSwpioc7reeoNrYB9SKpWkurJz3yEj3ee7", "tmSwpioc7reeoNrYB9SKpWkurJz3yEj3ee7"
networkType: networkType
) )
) )
} }
func testListTransparentReceivers() async throws { func testListTransparentReceivers() async throws {
let testVector = [TestVector](TestVector.testVectors![0 ... 2]) let testVector = [TestVector](TestVector.testVectors![0 ... 2])
let network = NetworkType.mainnet
let tempDBs = TemporaryDbBuilder.build() let tempDBs = TemporaryDbBuilder.build()
let seed = testVector[0].root_seed! let seed = testVector[0].root_seed!
rustBackend = ZcashRustBackend.makeForTests(dbData: tempDBs.dataDB, fsBlockDbRoot: Environment.uniqueTestTempDirectory, networkType: .mainnet)
try? FileManager.default.removeItem(at: tempDBs.dataDB) try? FileManager.default.removeItem(at: tempDBs.dataDB)
let initResult = try await ZcashRustBackend.initDataDb( let initResult = try await rustBackend.initDataDb(seed: seed)
dbData: tempDBs.dataDB,
seed: seed,
networkType: network
)
XCTAssertEqual(initResult, .success) XCTAssertEqual(initResult, .success)
let usk = try await ZcashRustBackend.createAccount( let usk = try await rustBackend.createAccount(seed: seed)
dbData: tempDBs.dataDB,
seed: seed,
networkType: network
)
XCTAssertEqual(usk.account, 0) XCTAssertEqual(usk.account, 0)
let expectedReceivers = try testVector.map { let expectedReceivers = try testVector.map {
UnifiedAddress(validatedEncoding: $0.unified_addr!) UnifiedAddress(validatedEncoding: $0.unified_addr!, networkType: .mainnet)
} }
.map { try $0.transparentReceiver() } .map { try $0.transparentReceiver() }
let expectedUAs = testVector.map { let expectedUAs = testVector.map {
UnifiedAddress(validatedEncoding: $0.unified_addr!) UnifiedAddress(validatedEncoding: $0.unified_addr!, networkType: .mainnet)
} }
guard expectedReceivers.count >= 2 else { guard expectedReceivers.count >= 2 else {
@ -127,19 +119,11 @@ class ZcashRustBackendTests: XCTestCase {
var uAddresses: [UnifiedAddress] = [] var uAddresses: [UnifiedAddress] = []
for i in 0...2 { for i in 0...2 {
uAddresses.append( uAddresses.append(
try await ZcashRustBackend.getCurrentAddress( try await rustBackend.getCurrentAddress(account: 0)
dbData: tempDBs.dataDB,
account: 0,
networkType: network
)
) )
if i < 2 { if i < 2 {
_ = try await ZcashRustBackend.getNextAvailableAddress( _ = try await rustBackend.getNextAvailableAddress(account: 0)
dbData: tempDBs.dataDB,
account: 0,
networkType: network
)
} }
} }
@ -148,11 +132,7 @@ class ZcashRustBackendTests: XCTestCase {
expectedUAs expectedUAs
) )
let actualReceivers = try await ZcashRustBackend.listTransparentReceivers( let actualReceivers = try await rustBackend.listTransparentReceivers(account: 0)
dbData: tempDBs.dataDB,
account: 0,
networkType: network
)
XCTAssertEqual( XCTAssertEqual(
expectedReceivers.sorted(), expectedReceivers.sorted(),
@ -163,7 +143,7 @@ class ZcashRustBackendTests: XCTestCase {
func testGetMetadataFromAddress() throws { func testGetMetadataFromAddress() throws {
let recipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a" let recipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a"
let metadata = ZcashRustBackend.getAddressMetadata(recipientAddress) let metadata = ZcashKeyDerivationBackend.getAddressMetadata(recipientAddress)
XCTAssertEqual(metadata?.networkType, .mainnet) XCTAssertEqual(metadata?.networkType, .mainnet)
XCTAssertEqual(metadata?.addressType, .sapling) XCTAssertEqual(metadata?.addressType, .sapling)

View File

@ -26,6 +26,8 @@ class SynchronizerTests: XCTestCase {
var coordinator: TestCoordinator! var coordinator: TestCoordinator!
var cancellables: [AnyCancellable] = [] var cancellables: [AnyCancellable] = []
var sdkSynchronizerSyncStatusHandler: SDKSynchronizerSyncStatusHandler! = SDKSynchronizerSyncStatusHandler() var sdkSynchronizerSyncStatusHandler: SDKSynchronizerSyncStatusHandler! = SDKSynchronizerSyncStatusHandler()
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
let seedPhrase = """ let seedPhrase = """
wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame
@ -33,11 +35,19 @@ class SynchronizerTests: XCTestCase {
var birthday: BlockHeight = 1_730_000 var birthday: BlockHeight = 1_730_000
override func setUp() async throws {
try await super.setUp()
testTempDirectory = Environment.uniqueTestTempDirectory
rustBackend = ZcashRustBackend.makeForTests(fsBlockDbRoot: testTempDirectory, networkType: .mainnet)
}
override func tearDown() { override func tearDown() {
super.tearDown() super.tearDown()
coordinator = nil coordinator = nil
cancellables = [] cancellables = []
sdkSynchronizerSyncStatusHandler = nil sdkSynchronizerSyncStatusHandler = nil
rustBackend = nil
testTempDirectory = nil
} }
func testHundredBlocksSync() async throws { func testHundredBlocksSync() async throws {
@ -47,11 +57,11 @@ class SynchronizerTests: XCTestCase {
return return
} }
let seedBytes = [UInt8](seedData) let seedBytes = [UInt8](seedData)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey( let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(
seed: seedBytes, seed: seedBytes,
accountIndex: 0 accountIndex: 0
) )
let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey) let ufvk = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
let network = ZcashNetworkBuilder.network(for: .mainnet) let network = ZcashNetworkBuilder.network(for: .mainnet)
let endpoint = LightWalletEndpoint(address: "lightwalletd.electriccoin.co", port: 9067, secure: true) let endpoint = LightWalletEndpoint(address: "lightwalletd.electriccoin.co", port: 9067, secure: true)

View File

@ -29,6 +29,7 @@ class CompactBlockProcessorEventHandler {
func subscribe(to blockProcessor: CompactBlockProcessor, expectations: [EventIdentifier: XCTestExpectation]) async { func subscribe(to blockProcessor: CompactBlockProcessor, expectations: [EventIdentifier: XCTestExpectation]) async {
let closure: CompactBlockProcessor.EventClosure = { event in let closure: CompactBlockProcessor.EventClosure = { event in
print("Received event: \(event.identifier)")
expectations[event.identifier]?.fulfill() expectations[event.identifier]?.fulfill()
} }

View File

@ -0,0 +1,165 @@
import Combine
@testable import ZcashLightClientKit
{% macro methodName method%}{%if method|annotated:"mockedName" %}{{ method.annotations.mockedName }}{% else %}{% call swiftifyMethodName method.selectorName %}{% endif %}{% endmacro %}
{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}
{% macro methodNameUpper method%}{%if method|annotated:"mockedName" %}{{ method.annotations.mockedName }}{% else %}{% call swiftifyMethodNameUpper method.selectorName %}{% endif %}{% endmacro %}
{% macro swiftifyMethodNameUpper name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | upperFirstLetter }}{% endmacro %}
{% macro methodThrowableErrorDeclaration method type %}
{% if method.isStatic %}static {% endif %}var {% call methodName method %}ThrowableError: Error?
{% call methodMockPropertySetter method type "ThrowableError" "Error?" %}
{% endmacro %}
{% macro methodMockPropertySetter method type postfix propertyType %}
{% if type|annotated:"mockActor" %}{% if not method.isStatic %}
func set{% call methodNameUpper method %}{{ postfix }}(_ param: {{ propertyType }}) async {
{% call methodName method %}{{ postfix }} = param
}
{% endif %}{% endif %}
{% endmacro %}
{% macro methodThrowableErrorUsage method %}
if let error = {% if method.isStatic %}Self.{% endif %}{% call methodName method %}ThrowableError {
throw error
}
{% endmacro %}
{% macro methodReceivedParameters method %}
{%if method.parameters.count == 1 %}
{% if method.isStatic %}Self.{% endif %}{% call methodName method %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% else %}
{% if not method.parameters.count == 0 %}
{% if method.isStatic %}Self.{% endif %}{% call methodName method %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% endif %}
{% endif %}
{% endmacro %}
{% macro methodClosureName method %}{% call methodName method %}Closure{% endmacro %}
{% macro paramTypeName param, method %}{% if method.annotations[param.name] %}{{method.annotations[param.name]}}{% else %}{{ param.typeName }}{% endif %}{% endmacro %}
{% macro unwrappedParamTypeName param, method %}{% if method.annotations[param.name] %}{{method.annotations[param.name]}}{% else %}{{ param.typeName.unwrappedTypeName }}{% endif %}{% endmacro %}
{% macro closureType method type %}({% for param in method.parameters %}{% call paramTypeName param, method %}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.isAsync %}async {% endif %}{% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{{ method.returnTypeName }}{% endif %}{% endmacro %}
{% macro methodClosureDeclaration method type %}
{% if method.isStatic %}static {% endif %}var {% call methodClosureName method %}: ({% call closureType method type %})?
{% if type|annotated:"mockActor" %}{% if not method.isStatic %}
func set{% call methodNameUpper method %}Closure(_ param: ({% call closureType method type %})?) async {
{% call methodName method %}Closure = param
}
{% endif %}{% endif %}
{% endmacro %}
{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}
{% macro mockMethod method type %}
{% if method|!annotated:"skipAutoMock" %}
// MARK: - {{ method.shortName }}
{% if ((type|annotated:"mockActor") and (method.isAsync) or (method.isStatic)) or (not type|annotated:"mockActor") %}
{% if method.throws %}
{% call methodThrowableErrorDeclaration method type %}
{% endif %}
{% if not method.isInitializer %}
{% if method.isStatic %}static {% endif %}var {% call methodName method %}CallsCount = 0
{% if method.isStatic %}static {% endif %}var {% call methodName method %}Called: Bool {
return {% if method.isStatic %}Self.{% endif %}{% call methodName method %}CallsCount > 0
}
{% endif %}
{% if method.parameters.count == 1 %}
{% if method.isStatic %}static {% endif %}var {% call methodName method %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {% if param.isClosure %}({% endif %}{% call unwrappedParamTypeName param, method %}{% if param.isClosure %}){% endif %}?{% endfor %}
{% else %}{% if not method.parameters.count == 0 %}
{% if method.isStatic %}static {% endif %}var {% call methodName method %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {% if param.typeAttributes.escaping %}{% call unwrappedParamTypeName param, method %}{% else %}{% call paramTypeName param, method %}{% endif %}{% if not forloop.last %}, {% endif %}{% endfor %})?
{% endif %}{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
{% if method.isStatic %}static {% endif %}var {% call methodName method %}ReturnValue: {{ method.returnTypeName }}{{ '!' if not method.isOptionalReturnType }}
{% call methodMockPropertySetter method type "ReturnValue" method.returnTypeName %}
{% endif %}
{% call methodClosureDeclaration method type %}
{% endif %}
{% if method.isInitializer %}
required {{ method.name }} {
{% call methodReceivedParameters method %}
{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
}
{% else %}
{% if (not method.isAsync) and (not method.isStatic) and (type|annotated:"mockActor") %}nonisolated {% endif %}{% if method.isStatic %}static {% endif %}func {{ method.name }}{% if method.isAsync %} async{% endif %}{% if method.throws %} throws{% endif %}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} {
{% if ((type|annotated:"mockActor") and ((method.isAsync) or (method.isStatic))) or (not type|annotated:"mockActor") %}
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
{% endif %}
{% if method.isStatic %}Self.{% endif %}{% call methodName method %}CallsCount += 1
{% call methodReceivedParameters method %}
{% if method.returnTypeName.isVoid %}
{% if method.throws %}try {% endif %}{% if method.isAsync %}await {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
{% else %}
if let closure = {% if method.isStatic %}Self.{% endif %}{% call methodClosureName method %} {
return {% if method.throws %}try {% endif %}{% if method.isAsync %}await {% endif %}closure({% call methodClosureCallParameters method %})
} else {
return {% if method.isStatic %}Self.{% endif %}{% call methodName method %}ReturnValue
}
{% endif %}
{% else %}
{% if method.throws %}try {% endif %}{% call methodClosureName method %}!({% call methodClosureCallParameters method %})
{% endif %}
}
{% endif %}
{% endif %}
{% endmacro %}
{% macro mockOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }}
{% endmacro %}
{% macro mockNonOptionalArrayOrDictionaryVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} {
get{% if variable.isAsync %} async{% endif %} { return {% call underlyingMockedVariableName variable %} }
}
var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %}
{% endmacro %}
{% macro mockNonOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} {
get { return {% call underlyingMockedVariableName variable %} }
}
var {% call underlyingMockedVariableName variable %}: {% if variable.typeName.isClosure %}({{ variable.typeName }})!{% else %}{{ variable.typeName }}!{% endif %}
{% endmacro %}
{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %}
{% macro initialMockedVariableValue variable %}initial{{ variable.name|upperFirstLetter }}{% endmacro %}
{% macro mockedVariableName variable %}{{ variable.name }}{% endmacro %}
// MARK: - AutoMockable protocols
{% for type in types.protocols where type.based.AutoMockable or type|annotated:"AutoMockable" %}{% if type.name != "AutoMockable" %}
{% if type|annotated:"moduleName" %}
/// Imported from {{ type.annotations.moduleName }} module
{% endif %}
{% if type|annotated:"targetOS" %}
#if os({{ type.annotations.targetOS }})
{% endif %}
{% if type|annotated:"mockActor" %}actor {% else %}class {% endif %}{{ type.name }}Mock: {% if type|annotated:"baseClass" %}{{ type.annotations.baseClass }}, {% endif %}{% if type|annotated:"moduleName" %}{{ type.annotations.moduleName }}.{% endif %}{{ type.name }} {
{% for method in type.allMethods|!definedInExtension %}
{% if (not method.isAsync) and (not method.isStatic) and (type|annotated:"mockActor") %}
nonisolated let {% call methodName method %}Closure: ({% call closureType method type %})?
{% endif %}
{% endfor %}
init(
{% for method in type.allMethods|!definedInExtension where ((not method.isAsync) and (not method.isStatic) and (type|annotated:"mockActor")) %}
{% call methodName method %}Closure: ({% call closureType method type %})? = nil{% if not forloop.last %},{% endif %}
{% endfor %}
) {
{% for method in type.allMethods|!definedInExtension %}
{% if (not method.isAsync) and (not method.isStatic) and (type|annotated:"mockActor") %}
self.{% call methodName method %}Closure = {% call methodName method %}Closure
{% endif %}
{% endfor %}
}
{% for variable in type.allVariables|!definedInExtension %}
{% if variable|!annotated:"skipAutoMock" %}
{% if variable.isOptional %}{% call mockOptionalVariable variable %}
{% elif variable.isArray or variable.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable variable %}
{% else %}{% call mockNonOptionalVariable variable %}
{% endif %}
{% endif %}
{% endfor %}
{% for method in type.allMethods|!definedInExtension %}
{% call mockMethod method type %}
{% endfor %}
}
{% if type|annotated:"targetOS" %}
#endif
{% endif %}
{% endif %}{% endfor %}

View File

@ -0,0 +1,18 @@
//
// AutoMockable.swift
//
//
// Created by Michal Fousek on 04.04.2023.
//
/// This file defines types for which we need to generate mocks for usage in Player tests.
/// Each type must appear in appropriate section according to which module it comes from.
// sourcery:begin: AutoMockable
@testable import ZcashLightClientKit
extension ZcashRustBackendWelding { }
extension Synchronizer { }
// sourcery:end:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
#!/bin/zsh
sourcery_version=2.0.2
if which sourcery >/dev/null; then
if [[ $(sourcery --version) != $sourcery_version ]]; then
echo "warning: Compatible sourcery version not installed. Install sourcer $sourcery_version. Currently installed version is $(sourcery --version)"
exit 1
fi
sourcery \
--sources ./ \
--sources ../../../Sources/ \
--templates AutoMockable.stencil \
--output GeneratedMocks/
else
echo "warning: sourcery not installed"
fi

View File

@ -49,342 +49,128 @@ extension LightWalletServiceMockResponse {
} }
} }
class MockRustBackend: ZcashRustBackendWelding { class RustBackendMockHelper {
static var networkType = NetworkType.testnet let rustBackendMock: ZcashRustBackendWeldingMock
static var mockDataDb = false var mockValidateCombinedChainFailAfterAttempts: Int?
static var mockAcounts = false
static var mockError: RustWeldingError? init(
static var mockLastError: String? rustBackend: ZcashRustBackendWelding,
static var mockAccounts: [SaplingExtendedSpendingKey]? consensusBranchID: Int32? = nil,
static var mockAddresses: [String]? mockValidateCombinedChainSuccessRate: Float? = nil,
static var mockBalance: Int64? mockValidateCombinedChainFailAfterAttempts: Int? = nil,
static var mockVerifiedBalance: Int64? mockValidateCombinedChainKeepFailing: Bool = false,
static var mockMemo: String? mockValidateCombinedChainFailureError: RustWeldingError = .chainValidationFailed(message: nil)
static var mockSentMemo: String? ) async {
static var mockValidateCombinedChainSuccessRate: Float? self.mockValidateCombinedChainFailAfterAttempts = mockValidateCombinedChainFailAfterAttempts
static var mockValidateCombinedChainFailAfterAttempts: Int? self.rustBackendMock = ZcashRustBackendWeldingMock(
static var mockValidateCombinedChainKeepFailing = false consensusBranchIdForHeightClosure: { height in
static var mockValidateCombinedChainFailureHeight: BlockHeight = 0 if let consensusBranchID {
static var mockScanblocksSuccessRate: Float? return consensusBranchID
static var mockCreateToAddress: Int64? } else {
static var rustBackend = ZcashRustBackend.self return try rustBackend.consensusBranchIdFor(height: height)
static var consensusBranchID: Int32? }
static var writeBlocksMetadataResult: () throws -> Bool = { true } }
static var rewindCacheToHeightResult: () -> Bool = { true } )
static func latestCachedBlockHeight(fsBlockDbRoot: URL) async -> ZcashLightClientKit.BlockHeight { await setupDefaultMock(
.empty() rustBackend: rustBackend,
mockValidateCombinedChainSuccessRate: mockValidateCombinedChainSuccessRate,
mockValidateCombinedChainKeepFailing: mockValidateCombinedChainKeepFailing,
mockValidateCombinedChainFailureError: mockValidateCombinedChainFailureError
)
} }
static func rewindCacheToHeight(fsBlockDbRoot: URL, height: Int32) async -> Bool { private func setupDefaultMock(
rewindCacheToHeightResult() rustBackend: ZcashRustBackendWelding,
} mockValidateCombinedChainSuccessRate: Float? = nil,
mockValidateCombinedChainKeepFailing: Bool = false,
mockValidateCombinedChainFailureError: RustWeldingError = .chainValidationFailed(message: nil)
) async {
await rustBackendMock.setLatestCachedBlockHeightReturnValue(.empty())
await rustBackendMock.setInitBlockMetadataDbClosure() { }
await rustBackendMock.setWriteBlocksMetadataBlocksClosure() { _ in }
await rustBackendMock.setInitAccountsTableUfvksClosure() { _ in }
await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1)
await rustBackendMock.setShieldFundsUskMemoShieldingThresholdReturnValue(-1)
await rustBackendMock.setGetTransparentBalanceAccountReturnValue(0)
await rustBackendMock.setGetVerifiedBalanceAccountReturnValue(0)
await rustBackendMock.setListTransparentReceiversAccountReturnValue([])
await rustBackendMock.setGetCurrentAddressAccountThrowableError(KeyDerivationErrors.unableToDerive)
await rustBackendMock.setGetNextAvailableAddressAccountThrowableError(KeyDerivationErrors.unableToDerive)
await rustBackendMock.setShieldFundsUskMemoShieldingThresholdReturnValue(-1)
await rustBackendMock.setCreateAccountSeedThrowableError(KeyDerivationErrors.unableToDerive)
await rustBackendMock.setGetReceivedMemoIdNoteReturnValue(nil)
await rustBackendMock.setGetSentMemoIdNoteReturnValue(nil)
await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1)
await rustBackendMock.setInitDataDbSeedReturnValue(.seedRequired)
await rustBackendMock.setGetNearestRewindHeightHeightReturnValue(-1)
await rustBackendMock.setInitBlocksTableHeightHashTimeSaplingTreeClosure() { _, _, _, _ in }
await rustBackendMock.setPutUnspentTransparentOutputTxidIndexScriptValueHeightClosure() { _, _, _, _, _ in }
await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1)
await rustBackendMock.setCreateToAddressUskToValueMemoReturnValue(-1)
await rustBackendMock.setDecryptAndStoreTransactionTxBytesMinedHeightThrowableError(RustWeldingError.genericError(message: "mock fail"))
static func initBlockMetadataDb(fsBlockDbRoot: URL) async throws -> Bool { await rustBackendMock.setInitDataDbSeedClosure() { seed in
true return try await rustBackend.initDataDb(seed: seed)
}
static func writeBlocksMetadata(fsBlockDbRoot: URL, blocks: [ZcashLightClientKit.ZcashCompactBlock]) async throws -> Bool {
try writeBlocksMetadataResult()
}
static func initAccountsTable(dbData: URL, ufvks: [ZcashLightClientKit.UnifiedFullViewingKey], networkType: ZcashLightClientKit.NetworkType) async throws { }
static func createToAddress(dbData: URL, usk: ZcashLightClientKit.UnifiedSpendingKey, to address: String, value: Int64, memo: ZcashLightClientKit.MemoBytes?, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) async -> Int64 {
-1
}
static func shieldFunds(
dbData: URL,
usk: ZcashLightClientKit.UnifiedSpendingKey,
memo: ZcashLightClientKit.MemoBytes?,
shieldingThreshold: Zatoshi,
spendParamsPath: String,
outputParamsPath: String,
networkType: ZcashLightClientKit.NetworkType
) async -> Int64 {
-1
}
static func getAddressMetadata(_ address: String) -> ZcashLightClientKit.AddressMetadata? {
nil
}
static func clearUtxos(dbData: URL, address: ZcashLightClientKit.TransparentAddress, sinceHeight: ZcashLightClientKit.BlockHeight, networkType: ZcashLightClientKit.NetworkType) async throws -> Int32 {
0
}
static func getTransparentBalance(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) async throws -> Int64 { 0 }
static func getVerifiedTransparentBalance(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) async throws -> Int64 { 0 }
static func listTransparentReceivers(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) async throws -> [ZcashLightClientKit.TransparentAddress] {
[]
}
static func deriveUnifiedFullViewingKey(from spendingKey: ZcashLightClientKit.UnifiedSpendingKey, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.UnifiedFullViewingKey {
throw KeyDerivationErrors.unableToDerive
}
static func deriveUnifiedSpendingKey(from seed: [UInt8], accountIndex: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.UnifiedSpendingKey {
throw KeyDerivationErrors.unableToDerive
}
static func getCurrentAddress(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) async throws -> ZcashLightClientKit.UnifiedAddress {
throw KeyDerivationErrors.unableToDerive
}
static func getNextAvailableAddress(dbData: URL, account: Int32, networkType: ZcashLightClientKit.NetworkType) async throws -> ZcashLightClientKit.UnifiedAddress {
throw KeyDerivationErrors.unableToDerive
}
static func getSaplingReceiver(for uAddr: ZcashLightClientKit.UnifiedAddress) throws -> ZcashLightClientKit.SaplingAddress {
throw KeyDerivationErrors.unableToDerive
}
static func getTransparentReceiver(for uAddr: ZcashLightClientKit.UnifiedAddress) throws -> ZcashLightClientKit.TransparentAddress {
throw KeyDerivationErrors.unableToDerive
}
static func shieldFunds(dbData: URL, usk: ZcashLightClientKit.UnifiedSpendingKey, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) async -> Int64 {
-1
}
static func receiverTypecodesOnUnifiedAddress(_ address: String) throws -> [UInt32] {
throw KeyDerivationErrors.receiverNotFound
}
static func createAccount(dbData: URL, seed: [UInt8], networkType: ZcashLightClientKit.NetworkType) async throws -> ZcashLightClientKit.UnifiedSpendingKey {
throw KeyDerivationErrors.unableToDerive
}
static func getReceivedMemo(dbData: URL, idNote: Int64, networkType: ZcashLightClientKit.NetworkType) async -> ZcashLightClientKit.Memo? { nil }
static func getSentMemo(dbData: URL, idNote: Int64, networkType: ZcashLightClientKit.NetworkType) async -> ZcashLightClientKit.Memo? { nil }
static func createToAddress(dbData: URL, usk: ZcashLightClientKit.UnifiedSpendingKey, to address: String, value: Int64, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) async -> Int64 {
-1
}
static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: ZcashLightClientKit.NetworkType) async throws -> ZcashLightClientKit.DbInitResult {
.seedRequired
}
static func deriveSaplingAddressFromViewingKey(_ extfvk: ZcashLightClientKit.SaplingExtendedFullViewingKey, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.SaplingAddress {
throw RustWeldingError.unableToDeriveKeys
}
static func isValidSaplingExtendedSpendingKey(_ key: String, networkType: ZcashLightClientKit.NetworkType) -> Bool { false }
static func deriveSaplingExtendedFullViewingKeys(seed: [UInt8], accounts: Int32, networkType: ZcashLightClientKit.NetworkType) throws -> [ZcashLightClientKit.SaplingExtendedFullViewingKey]? {
nil
}
static func isValidUnifiedAddress(_ address: String, networkType: ZcashLightClientKit.NetworkType) -> Bool {
false
}
static func deriveSaplingExtendedFullViewingKey(_ spendingKey: SaplingExtendedSpendingKey, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.SaplingExtendedFullViewingKey? {
nil
}
public func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey] { [] }
static func getNearestRewindHeight(dbData: URL, height: Int32, networkType: NetworkType) async -> Int32 { -1 }
static func network(dbData: URL, address: String, sinceHeight: BlockHeight, networkType: NetworkType) async throws -> Int32 { -1 }
static func initAccountsTable(dbData: URL, ufvks: [UnifiedFullViewingKey], networkType: NetworkType) async throws -> Bool { false }
static func putUnspentTransparentOutput(
dbData: URL,
txid: [UInt8],
index: Int,
script: [UInt8],
value: Int64,
height: BlockHeight,
networkType: NetworkType
) async throws -> Bool {
false
}
static func downloadedUtxoBalance(dbData: URL, address: String, networkType: NetworkType) async throws -> WalletBalance {
throw RustWeldingError.genericError(message: "unimplemented")
}
static func createToAddress(
dbData: URL,
account: Int32,
extsk: String,
to address: String,
value: Int64,
memo: String?,
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) async -> Int64 {
-1
}
static func deriveTransparentAddressFromSeed(seed: [UInt8], account: Int, index: Int, networkType: NetworkType) throws -> TransparentAddress {
throw KeyDerivationErrors.unableToDerive
}
static func deriveUnifiedFullViewingKeyFromSeed(_ seed: [UInt8], numberOfAccounts: Int32, networkType: NetworkType) throws -> [UnifiedFullViewingKey] {
throw KeyDerivationErrors.unableToDerive
}
static func isValidSaplingExtendedFullViewingKey(_ key: String, networkType: NetworkType) -> Bool { false }
static func isValidUnifiedFullViewingKey(_ ufvk: String, networkType: NetworkType) -> Bool { false }
static func deriveSaplingExtendedSpendingKeys(seed: [UInt8], accounts: Int32, networkType: NetworkType) throws -> [SaplingExtendedSpendingKey]? { nil }
static func consensusBranchIdFor(height: Int32, networkType: NetworkType) throws -> Int32 {
guard let consensus = consensusBranchID else {
return try rustBackend.consensusBranchIdFor(height: height, networkType: networkType)
} }
return consensus
}
static func lastError() -> RustWeldingError? { await rustBackendMock.setInitBlocksTableHeightHashTimeSaplingTreeClosure() { height, hash, time, saplingTree in
mockError ?? rustBackend.lastError()
}
static func getLastError() -> String? {
mockLastError ?? rustBackend.getLastError()
}
static func isValidSaplingAddress(_ address: String, networkType: NetworkType) -> Bool {
true
}
static func isValidTransparentAddress(_ address: String, networkType: NetworkType) -> Bool {
true
}
static func initDataDb(dbData: URL, networkType: NetworkType) async throws {
if !mockDataDb {
_ = try await rustBackend.initDataDb(dbData: dbData, seed: nil, networkType: networkType)
}
}
static func initBlocksTable(
dbData: URL,
height: Int32,
hash: String,
time: UInt32,
saplingTree: String,
networkType: NetworkType
) async throws {
if !mockDataDb {
try await rustBackend.initBlocksTable( try await rustBackend.initBlocksTable(
dbData: dbData,
height: height, height: height,
hash: hash, hash: hash,
time: time, time: time,
saplingTree: saplingTree, saplingTree: saplingTree
networkType: networkType
) )
} }
}
await rustBackendMock.setGetBalanceAccountClosure() { account in
static func getBalance(dbData: URL, account: Int32, networkType: NetworkType) async throws -> Int64 { return try await rustBackend.getBalance(account: account)
if let balance = mockBalance {
return balance
}
return try await rustBackend.getBalance(dbData: dbData, account: account, networkType: networkType)
}
static func getVerifiedBalance(dbData: URL, account: Int32, networkType: NetworkType) async throws -> Int64 {
if let balance = mockVerifiedBalance {
return balance
} }
return try await rustBackend.getVerifiedBalance(dbData: dbData, account: account, networkType: networkType) await rustBackendMock.setGetVerifiedBalanceAccountClosure() { account in
} return try await rustBackend.getVerifiedBalance(account: account)
}
static func validateCombinedChain(fsBlockDbRoot: URL, dbData: URL, networkType: NetworkType, limit: UInt32 = 0) async -> Int32 {
if let rate = self.mockValidateCombinedChainSuccessRate { await rustBackendMock.setValidateCombinedChainLimitClosure() { [weak self] limit in
if shouldSucceed(successRate: rate) { guard let self else { throw RustWeldingError.genericError(message: "Self is nil") }
return await validationResult(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: networkType) if let rate = mockValidateCombinedChainSuccessRate {
} else { if Self.shouldSucceed(successRate: rate) {
return Int32(mockValidateCombinedChainFailureHeight) return try await rustBackend.validateCombinedChain(limit: limit)
}
} else if let attempts = self.mockValidateCombinedChainFailAfterAttempts {
self.mockValidateCombinedChainFailAfterAttempts = attempts - 1
if attempts > 0 {
return await validationResult(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: networkType)
} else {
if attempts == 0 {
return Int32(mockValidateCombinedChainFailureHeight)
} else if attempts < 0 && mockValidateCombinedChainKeepFailing {
return Int32(mockValidateCombinedChainFailureHeight)
} else { } else {
return await validationResult(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: networkType) throw mockValidateCombinedChainFailureError
}
} else if let attempts = self.mockValidateCombinedChainFailAfterAttempts {
self.mockValidateCombinedChainFailAfterAttempts = attempts - 1
if attempts > 0 {
return try await rustBackend.validateCombinedChain(limit: limit)
} else {
if attempts == 0 {
throw mockValidateCombinedChainFailureError
} else if attempts < 0 && mockValidateCombinedChainKeepFailing {
throw mockValidateCombinedChainFailureError
} else {
return try await rustBackend.validateCombinedChain(limit: limit)
}
} }
}
}
return await rustBackend.validateCombinedChain(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: networkType)
}
private static func validationResult(fsBlockDbRoot: URL, dbData: URL, networkType: NetworkType) async -> Int32 {
if mockDataDb {
return -1
} else {
return await rustBackend.validateCombinedChain(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: networkType)
}
}
static func rewindToHeight(dbData: URL, height: Int32, networkType: NetworkType) async -> Bool {
mockDataDb ? true : rustBackend.rewindToHeight(dbData: dbData, height: height, networkType: networkType)
}
static func scanBlocks(fsBlockDbRoot: URL, dbData: URL, limit: UInt32, networkType: NetworkType) async -> Bool {
if let rate = mockScanblocksSuccessRate {
if shouldSucceed(successRate: rate) {
return mockDataDb ? true : await rustBackend.scanBlocks(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: networkType)
} else { } else {
return false return try await rustBackend.validateCombinedChain(limit: limit)
} }
} }
return await rustBackend.scanBlocks(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: Self.networkType)
await rustBackendMock.setRewindToHeightHeightClosure() { height in
try await rustBackend.rewindToHeight(height: height)
}
await rustBackendMock.setRewindCacheToHeightHeightClosure() { _ in }
await rustBackendMock.setScanBlocksLimitClosure() { limit in
try await rustBackend.scanBlocks(limit: limit)
}
} }
static func createToAddress( private static func shouldSucceed(successRate: Float) -> Bool {
dbData: URL,
account: Int32,
extsk: String,
consensusBranchId: Int32,
to address: String,
value: Int64,
memo: String?,
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) async -> Int64 {
-1
}
static func shouldSucceed(successRate: Float) -> Bool {
let random = Float.random(in: 0.0...1.0) let random = Float.random(in: 0.0...1.0)
return random <= successRate return random <= successRate
} }
static func deriveExtendedFullViewingKey(_ spendingKey: String, networkType: NetworkType) throws -> String? {
nil
}
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32, networkType: NetworkType) throws -> [String]? {
nil
}
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32, networkType: NetworkType) throws -> [String]? {
nil
}
static func decryptAndStoreTransaction(dbData: URL, txBytes: [UInt8], minedHeight: Int32, networkType: NetworkType) async -> Bool {
false
}
} }
extension SaplingParamsSourceURL { extension SaplingParamsSourceURL {

View File

@ -1,174 +0,0 @@
//
// SynchronizerMock.swift
//
//
// Created by Michal Fousek on 20.03.2023.
//
import Combine
import Foundation
@testable import ZcashLightClientKit
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 }
var underlyingLatestState: SynchronizerState! = nil
var latestState: SynchronizerState { underlyingLatestState }
var underlyingEventStream: AnyPublisher<SynchronizerEvent, Never>! = nil
var eventStream: AnyPublisher<SynchronizerEvent, Never> { underlyingEventStream }
var underlyingConnectionState: ConnectionState! = nil
var connectionState: ConnectionState { underlyingConnectionState }
let metrics = SDKMetrics()
var prepareWithSeedViewingKeysWalletBirthdayClosure: (
([UInt8]?, [UnifiedFullViewingKey], BlockHeight) async throws -> Initializer.InitializationResult
)! = nil
func prepare(
with seed: [UInt8]?,
viewingKeys: [UnifiedFullViewingKey],
walletBirthday: BlockHeight
) async throws -> Initializer.InitializationResult {
return try await prepareWithSeedViewingKeysWalletBirthdayClosure(seed, viewingKeys, walletBirthday)
}
var startRetryClosure: ((Bool) async throws -> Void)! = nil
func start(retry: Bool) async throws {
try await startRetryClosure(retry)
}
var stopClosure: (() async -> Void)! = nil
func stop() async {
await stopClosure()
}
var getSaplingAddressAccountIndexClosure: ((Int) async throws -> SaplingAddress)! = nil
func getSaplingAddress(accountIndex: Int) async throws -> SaplingAddress {
return try await getSaplingAddressAccountIndexClosure(accountIndex)
}
var getUnifiedAddressAccountIndexClosure: ((Int) async throws -> UnifiedAddress)! = nil
func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress {
return try await getUnifiedAddressAccountIndexClosure(accountIndex)
}
var getTransparentAddressAccountIndexClosure: ((Int) async throws -> TransparentAddress)! = nil
func getTransparentAddress(accountIndex: Int) async throws -> TransparentAddress {
return try await getTransparentAddressAccountIndexClosure(accountIndex)
}
var sendToAddressSpendingKeyZatoshiToAddressMemoClosure: (
(UnifiedSpendingKey, Zatoshi, Recipient, Memo?) async throws -> PendingTransactionEntity
)! = nil
func sendToAddress(spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, memo: Memo?) async throws -> PendingTransactionEntity {
return try await sendToAddressSpendingKeyZatoshiToAddressMemoClosure(spendingKey, zatoshi, toAddress, memo)
}
var shieldFundsSpendingKeyMemoShieldingThresholdClosure: ((UnifiedSpendingKey, Memo, Zatoshi) async throws -> PendingTransactionEntity)! = nil
func shieldFunds(spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi) async throws -> PendingTransactionEntity {
return try await shieldFundsSpendingKeyMemoShieldingThresholdClosure(spendingKey, memo, shieldingThreshold)
}
var cancelSpendTransactionClosure: ((PendingTransactionEntity) async -> Bool)! = nil
func cancelSpend(transaction: PendingTransactionEntity) async -> Bool {
return await cancelSpendTransactionClosure(transaction)
}
var underlyingPendingTransactions: [PendingTransactionEntity]! = nil
var pendingTransactions: [PendingTransactionEntity] {
get async { underlyingPendingTransactions }
}
var underlyingClearedTransactions: [ZcashTransaction.Overview]! = nil
var clearedTransactions: [ZcashTransaction.Overview] {
get async { underlyingClearedTransactions }
}
var underlyingSentTransactions: [ZcashTransaction.Sent]! = nil
var sentTransactions: [ZcashTransaction.Sent] {
get async { underlyingSentTransactions }
}
var underlyingReceivedTransactions: [ZcashTransaction.Received]! = nil
var receivedTransactions: [ZcashTransaction.Received] {
get async { underlyingReceivedTransactions }
}
var paginatedTransactionsOfKindClosure: ((TransactionKind) -> PaginatedTransactionRepository)! = nil
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository {
return paginatedTransactionsOfKindClosure(kind)
}
var getMemosForTransactionClosure: ((ZcashTransaction.Overview) async throws -> [Memo])! = nil
func getMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo] {
return try await getMemosForTransactionClosure(transaction)
}
var getMemosForReceivedTransactionClosure: ((ZcashTransaction.Received) async throws -> [Memo])! = nil
func getMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo] {
return try await getMemosForReceivedTransactionClosure(receivedTransaction)
}
var getMemosForSentTransactionClosure: ((ZcashTransaction.Sent) async throws -> [Memo])! = nil
func getMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo] {
return try await getMemosForSentTransactionClosure(sentTransaction)
}
var getRecipientsForClearedTransactionClosure: ((ZcashTransaction.Overview) async -> [TransactionRecipient])! = nil
func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient] {
return await getRecipientsForClearedTransactionClosure(transaction)
}
var getRecipientsForSentTransactionClosure: ((ZcashTransaction.Sent) async -> [TransactionRecipient])! = nil
func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient] {
return await getRecipientsForSentTransactionClosure(transaction)
}
var allConfirmedTransactionsFromTransactionClosure: ((ZcashTransaction.Overview, Int) async throws -> [ZcashTransaction.Overview])! = nil
func allConfirmedTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] {
return try await allConfirmedTransactionsFromTransactionClosure(transaction, limit)
}
var latestHeightClosure: (() async throws -> BlockHeight)! = nil
func latestHeight() async throws -> BlockHeight {
return try await latestHeightClosure()
}
var refreshUTXOsAddressFromHeightClosure: ((TransparentAddress, BlockHeight) async throws -> RefreshedUTXOs)! = nil
func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs {
return try await refreshUTXOsAddressFromHeightClosure(address, height)
}
var getTransparentBalanceAccountIndexClosure: ((Int) async throws -> WalletBalance)! = nil
func getTransparentBalance(accountIndex: Int) async throws -> WalletBalance {
return try await getTransparentBalanceAccountIndexClosure(accountIndex)
}
var getShieldedBalanceAccountIndexClosure: ((Int) async throws -> Zatoshi)! = nil
func getShieldedBalance(accountIndex: Int) async throws -> Zatoshi {
try await getShieldedBalanceAccountIndexClosure(accountIndex)
}
var getShieldedVerifiedBalanceAccountIndexClosure: ((Int) async throws -> Zatoshi)! = nil
func getShieldedVerifiedBalance(accountIndex: Int) async throws -> Zatoshi {
try await getShieldedVerifiedBalanceAccountIndexClosure(accountIndex)
}
var rewindPolicyClosure: ((RewindPolicy) -> AnyPublisher<Void, Error>)! = nil
func rewind(_ policy: RewindPolicy) -> AnyPublisher<Void, Error> {
return rewindPolicyClosure(policy)
}
var wipeClosure: (() -> AnyPublisher<Void, Error>)! = nil
func wipe() -> AnyPublisher<Void, Error> {
return wipeClosure()
}
}

View File

@ -52,56 +52,19 @@ class TestCoordinator {
singleCallTimeoutInMillis: 10000, singleCallTimeoutInMillis: 10000,
streamingCallTimeoutInMillis: 1000000 streamingCallTimeoutInMillis: 1000000
) )
convenience init( init(
alias: ZcashSynchronizerAlias = .default, alias: ZcashSynchronizerAlias = .default,
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
network: ZcashNetwork, network: ZcashNetwork,
callPrepareInConstructor: Bool = true, callPrepareInConstructor: Bool = true,
endpoint: LightWalletEndpoint = TestCoordinator.defaultEndpoint, endpoint: LightWalletEndpoint = TestCoordinator.defaultEndpoint,
syncSessionIDGenerator: SyncSessionIDGenerator = UniqueSyncSessionIDGenerator() syncSessionIDGenerator: SyncSessionIDGenerator = UniqueSyncSessionIDGenerator()
) async throws {
let derivationTool = DerivationTool(networkType: network.networkType)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(
seed: Environment.seedBytes,
accountIndex: 0
)
let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
try await self.init(
alias: alias,
spendingKey: spendingKey,
unifiedFullViewingKey: ufvk,
walletBirthday: walletBirthday,
network: network,
callPrepareInConstructor: callPrepareInConstructor,
endpoint: endpoint,
syncSessionIDGenerator: syncSessionIDGenerator
)
}
required init(
alias: ZcashSynchronizerAlias = .default,
spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight,
network: ZcashNetwork,
callPrepareInConstructor: Bool = true,
endpoint: LightWalletEndpoint = TestCoordinator.defaultEndpoint,
syncSessionIDGenerator: SyncSessionIDGenerator
) async throws { ) async throws {
await InternalSyncProgress(alias: alias, storage: UserDefaults.standard, logger: logger).rewind(to: 0) await InternalSyncProgress(alias: alias, storage: UserDefaults.standard, logger: logger).rewind(to: 0)
self.spendingKey = spendingKey let databases = TemporaryDbBuilder.build()
self.viewingKey = unifiedFullViewingKey self.databases = databases
self.birthday = walletBirthday
self.databases = TemporaryDbBuilder.build()
self.network = network
let liveService = LightWalletServiceFactory(endpoint: endpoint).make()
self.service = DarksideWalletService(endpoint: endpoint, service: liveService)
let initializer = Initializer( let initializer = Initializer(
cacheDbURL: nil, cacheDbURL: nil,
@ -117,9 +80,21 @@ class TestCoordinator {
logLevel: .debug logLevel: .debug
) )
let synchronizer = SDKSynchronizer(initializer: initializer, sessionGenerator: syncSessionIDGenerator, sessionTicker: .live) let derivationTool = initializer.makeDerivationTool()
self.synchronizer = synchronizer self.spendingKey = try await derivationTool.deriveUnifiedSpendingKey(
seed: Environment.seedBytes,
accountIndex: 0
)
self.viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
self.birthday = walletBirthday
self.network = network
let liveService = LightWalletServiceFactory(endpoint: endpoint).make()
self.service = DarksideWalletService(endpoint: endpoint, service: liveService)
self.synchronizer = SDKSynchronizer(initializer: initializer, sessionGenerator: syncSessionIDGenerator, sessionTicker: .live)
subscribeToState(synchronizer: self.synchronizer) subscribeToState(synchronizer: self.synchronizer)
if callPrepareInConstructor { if callPrepareInConstructor {

View File

@ -58,16 +58,12 @@ enum TestDbBuilder {
Bundle.module.url(forResource: "darkside_caches", withExtension: "db") Bundle.module.url(forResource: "darkside_caches", withExtension: "db")
} }
static func prepopulatedDataDbProvider() async throws -> ConnectionProvider? { static func prepopulatedDataDbProvider(rustBackend: ZcashRustBackendWelding) async throws -> ConnectionProvider? {
guard let url = prePopulatedMainnetDataDbURL() else { return nil } guard let url = prePopulatedMainnetDataDbURL() else { return nil }
let provider = SimpleConnectionProvider(path: url.absoluteString, readonly: true) let provider = SimpleConnectionProvider(path: url.absoluteString, readonly: true)
let initResult = try await ZcashRustBackend.initDataDb( let initResult = try await rustBackend.initDataDb(seed: Environment.seedBytes)
dbData: url,
seed: Environment.seedBytes,
networkType: .mainnet
)
switch initResult { switch initResult {
case .success: return provider case .success: return provider
@ -76,19 +72,19 @@ enum TestDbBuilder {
} }
} }
static func transactionRepository() async throws -> TransactionRepository? { static func transactionRepository(rustBackend: ZcashRustBackendWelding) async throws -> TransactionRepository? {
guard let provider = try await prepopulatedDataDbProvider() else { return nil } guard let provider = try await prepopulatedDataDbProvider(rustBackend: rustBackend) else { return nil }
return TransactionSQLDAO(dbProvider: provider) return TransactionSQLDAO(dbProvider: provider)
} }
static func sentNotesRepository() async throws -> SentNotesRepository? { static func sentNotesRepository(rustBackend: ZcashRustBackendWelding) async throws -> SentNotesRepository? {
guard let provider = try await prepopulatedDataDbProvider() else { return nil } guard let provider = try await prepopulatedDataDbProvider(rustBackend: rustBackend) else { return nil }
return SentNotesSQLDAO(dbProvider: provider) return SentNotesSQLDAO(dbProvider: provider)
} }
static func receivedNotesRepository() async throws -> ReceivedNoteRepository? { static func receivedNotesRepository(rustBackend: ZcashRustBackendWelding) async throws -> ReceivedNoteRepository? {
guard let provider = try await prepopulatedDataDbProvider() else { return nil } guard let provider = try await prepopulatedDataDbProvider(rustBackend: rustBackend) else { return nil }
return ReceivedNotesSQLDAO(dbProvider: provider) return ReceivedNotesSQLDAO(dbProvider: provider)
} }

View File

@ -9,10 +9,10 @@
import Combine import Combine
import Foundation import Foundation
import GRPC import GRPC
import ZcashLightClientKit
import XCTest import XCTest
import NIO import NIO
import NIOTransportServices import NIOTransportServices
@testable import ZcashLightClientKit
enum Environment { enum Environment {
static let lightwalletdKey = "LIGHTWALLETD_ADDRESS" static let lightwalletdKey = "LIGHTWALLETD_ADDRESS"
@ -28,6 +28,11 @@ enum Environment {
} }
static let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a" static let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a"
static var uniqueTestTempDirectory: URL {
URL(fileURLWithPath: NSString(string: NSTemporaryDirectory())
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
}
} }
public enum Constants { public enum Constants {
@ -128,3 +133,21 @@ func parametersReady() -> Bool {
return true return true
} }
extension ZcashRustBackend {
static func makeForTests(
dbData: URL = try! __dataDbURL(),
fsBlockDbRoot: URL,
spendParamsPath: URL = SaplingParamsSourceURL.default.spendParamFileURL,
outputParamsPath: URL = SaplingParamsSourceURL.default.outputParamFileURL,
networkType: NetworkType
) -> ZcashRustBackendWelding {
ZcashRustBackend(
dbData: dbData,
fsBlockDbRoot: fsBlockDbRoot,
spendParamsPath: spendParamsPath,
outputParamsPath: outputParamsPath,
networkType: networkType
)
}
}

View File

@ -1,5 +1,5 @@
// //
// AlternativeSynchronizerAPITestsData.swift // TestsData.swift
// //
// //
// Created by Michal Fousek on 20.03.2023. // Created by Michal Fousek on 20.03.2023.
@ -8,13 +8,29 @@
import Foundation import Foundation
@testable import ZcashLightClientKit @testable import ZcashLightClientKit
class AlternativeSynchronizerAPITestsData { class TestsData {
let derivationTools = DerivationTool(networkType: .testnet) let networkType: NetworkType
lazy var initialier = {
Initializer(
cacheDbURL: nil,
fsBlockDbRoot: URL(fileURLWithPath: "/"),
dataDbURL: URL(fileURLWithPath: "/"),
pendingDbURL: URL(fileURLWithPath: "/"),
endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: networkType),
spendParamsURL: URL(fileURLWithPath: "/"),
outputParamsURL: URL(fileURLWithPath: "/"),
saplingParamsSourceURL: .default
)
}()
lazy var derivationTools: DerivationTool = { initialier.makeDerivationTool() }()
let saplingAddress = SaplingAddress(validatedEncoding: "ztestsapling1ctuamfer5xjnnrdr3xdazenljx0mu0gutcf9u9e74tr2d3jwjnt0qllzxaplu54hgc2tyjdc2p6") let saplingAddress = SaplingAddress(validatedEncoding: "ztestsapling1ctuamfer5xjnnrdr3xdazenljx0mu0gutcf9u9e74tr2d3jwjnt0qllzxaplu54hgc2tyjdc2p6")
let unifiedAddress = UnifiedAddress( let unifiedAddress = UnifiedAddress(
validatedEncoding: """ validatedEncoding: """
u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj
""" """,
networkType: .testnet
) )
let transparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz") let transparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz")
lazy var pendingTransactionEntity = { lazy var pendingTransactionEntity = {
@ -73,9 +89,19 @@ class AlternativeSynchronizerAPITestsData {
}() }()
var seed: [UInt8] = Environment.seedBytes var seed: [UInt8] = Environment.seedBytes
lazy var spendingKey: UnifiedSpendingKey = { try! derivationTools.deriveUnifiedSpendingKey(seed: seed, accountIndex: 0) }() var spendingKey: UnifiedSpendingKey {
lazy var viewingKey: UnifiedFullViewingKey = { try! derivationTools.deriveUnifiedFullViewingKey(from: spendingKey) }() get async {
try! await derivationTools.deriveUnifiedSpendingKey(seed: seed, accountIndex: 0)
}
}
var viewingKey: UnifiedFullViewingKey {
get async {
try! await derivationTools.deriveUnifiedFullViewingKey(from: spendingKey)
}
}
var birthday: BlockHeight = 123000 var birthday: BlockHeight = 123000
init() { } init(networkType: NetworkType) {
self.networkType = networkType
}
} }