[#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
lib
*.a
*.generated.swift
env-vars.sh
.vscode/

View File

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

View File

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

View File

@ -1,4 +1,12 @@
# 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
This is mostly internal change. But it also touches the public API.

View File

@ -104,6 +104,20 @@
ReferencedContainer = "container:../..">
</BuildableReference>
</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>
</BuildAction>
<TestAction

View File

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

View File

@ -39,24 +39,26 @@ class GetUTXOsViewController: UIViewController {
}
@IBAction func shieldFunds(_ sender: Any) {
do {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
Task { @MainActor in
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
let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds(
spendingKey: usk,
memo: try Memo(string: "shielding is fun!"),
shieldingThreshold: Zatoshi(10000)
)
KRProgressHUD.dismiss()
self.messageLabel.text = "funds shielded \(transaction)"
Task { @MainActor in
let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds(
spendingKey: usk,
memo: try Memo(string: "shielding is fun!"),
shieldingThreshold: Zatoshi(10000)
)
KRProgressHUD.dismiss()
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(_:)))
self.view.addGestureRecognizer(tapRecognizer)
setUp()
closureSynchronizer.prepare(
with: DemoAppConfig.defaultSeed,
viewingKeys: [AppDelegate.shared.sharedViewingKey],
walletBirthday: DemoAppConfig.defaultBirthdayHeight
) { result in
loggerProxy.debug("Prepare result: \(result)")
Task { @MainActor in
closureSynchronizer.prepare(
with: DemoAppConfig.defaultSeed,
viewingKeys: [await AppDelegate.shared.sharedViewingKey],
walletBirthday: DemoAppConfig.defaultBirthdayHeight
) { result in
loggerProxy.debug("Prepare result: \(result)")
}
}
}
@ -216,14 +218,8 @@ class SendViewController: UIViewController {
return
}
guard let spendingKey = try? DerivationTool(
networkType: kZcashNetwork.networkType
)
.deriveUnifiedSpendingKey(
seed: DemoAppConfig.defaultSeed,
accountIndex: 0
)
else {
let derivationTool = AppDelegate.shared.sharedWallet.makeDerivationTool()
guard let spendingKey = try? await derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0) else {
loggerProxy.error("NO SPENDING KEY")
return
}

View File

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

View File

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

View File

@ -316,7 +316,7 @@ actor CompactBlockProcessor {
var storage: CompactBlockRepository
var transactionRepository: TransactionRepository
var accountRepository: AccountRepository
var rustBackend: ZcashRustBackendWelding.Type
var rustBackend: ZcashRustBackendWelding
private var retryAttempts: Int = 0
private var backoffTimer: Timer?
private var lastChainValidationFailure: BlockHeight?
@ -344,7 +344,7 @@ actor CompactBlockProcessor {
init(
service: LightWalletService,
storage: CompactBlockRepository,
backend: ZcashRustBackendWelding.Type,
rustBackend: ZcashRustBackendWelding,
config: Configuration,
metrics: SDKMetrics,
logger: Logger
@ -352,11 +352,9 @@ actor CompactBlockProcessor {
self.init(
service: service,
storage: storage,
backend: backend,
rustBackend: rustBackend,
config: config,
repository: TransactionRepositoryBuilder.build(
dataDbURL: config.dataDb
),
repository: TransactionRepositoryBuilder.build(dataDbURL: config.dataDb),
accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: logger),
metrics: metrics,
logger: logger
@ -375,7 +373,7 @@ actor CompactBlockProcessor {
self.init(
service: initializer.lightWalletService,
storage: initializer.storage,
backend: initializer.rustBackend,
rustBackend: initializer.rustBackend,
config: Configuration(
alias: initializer.alias,
fsBlockCacheRoot: initializer.fsBlockDbRoot,
@ -396,7 +394,7 @@ actor CompactBlockProcessor {
internal init(
service: LightWalletService,
storage: CompactBlockRepository,
backend: ZcashRustBackendWelding.Type,
rustBackend: ZcashRustBackendWelding,
config: Configuration,
repository: TransactionRepository,
accountRepository: AccountRepository,
@ -420,73 +418,57 @@ actor CompactBlockProcessor {
self.blockDownloaderService = blockDownloaderService
self.blockDownloader = blockDownloader
let blockValidatorConfig = BlockValidatorConfig(
fsBlockCacheRoot: config.fsBlockCacheRoot,
dataDB: config.dataDb,
networkType: config.network.networkType
)
self.blockValidator = BlockValidatorImpl(
config: blockValidatorConfig,
rustBackend: backend,
rustBackend: rustBackend,
metrics: metrics,
logger: logger
)
let blockScannerConfig = BlockScannerConfig(
fsBlockCacheRoot: config.fsBlockCacheRoot,
dataDB: config.dataDb,
networkType: config.network.networkType,
scanningBatchSize: config.scanningBatchSize
)
self.blockScanner = BlockScannerImpl(
config: blockScannerConfig,
rustBackend: backend,
rustBackend: rustBackend,
transactionRepository: repository,
metrics: metrics,
logger: logger
)
let blockEnhancerConfig = BlockEnhancerConfig(dataDb: config.dataDb, networkType: config.network.networkType)
self.blockEnhancer = BlockEnhancerImpl(
blockDownloaderService: blockDownloaderService,
config: blockEnhancerConfig,
internalSyncProgress: internalSyncProgress,
rustBackend: backend,
rustBackend: rustBackend,
transactionRepository: repository,
metrics: metrics,
logger: logger
)
let utxoFetcherConfig = UTXOFetcherConfig(
dataDb: config.dataDb,
networkType: config.network.networkType,
walletBirthdayProvider: config.walletBirthdayProvider
)
let utxoFetcherConfig = UTXOFetcherConfig(walletBirthdayProvider: config.walletBirthdayProvider)
self.utxoFetcher = UTXOFetcherImpl(
accountRepository: accountRepository,
blockDownloaderService: blockDownloaderService,
config: utxoFetcherConfig,
internalSyncProgress: internalSyncProgress,
rustBackend: backend,
rustBackend: rustBackend,
metrics: metrics,
logger: logger
)
let saplingParametersHandlerConfig = SaplingParametersHandlerConfig(
dataDb: config.dataDb,
networkType: config.network.networkType,
outputParamsURL: config.outputParamsURL,
spendParamsURL: config.spendParamsURL,
saplingParamsSourceURL: config.saplingParamsSourceURL
)
self.saplingParametersHandler = SaplingParametersHandlerImpl(
config: saplingParametersHandlerConfig,
rustBackend: backend,
rustBackend: rustBackend,
logger: logger
)
self.service = service
self.rustBackend = backend
self.rustBackend = rustBackend
self.storage = storage
self.config = config
self.transactionRepository = repository
@ -517,8 +499,8 @@ actor CompactBlockProcessor {
_ info: LightWalletdInfo,
saplingActivation: BlockHeight,
localNetwork: ZcashNetwork,
rustBackend: ZcashRustBackendWelding.Type
) throws {
rustBackend: ZcashRustBackendWelding
) async throws {
// check network types
guard let remoteNetworkType = NetworkType.forChainName(info.chainName) else {
throw CompactBlockProcessorError.generalError(
@ -536,7 +518,7 @@ actor CompactBlockProcessor {
}
// 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 {
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.")
let lastDownloaded = await internalSyncProgress.latestDownloadedBlockHeight
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 error = rustBackend.lastError() ?? RustWeldingError.genericError(
message: "unknown error getting nearest rewind height for height: \(height)"
)
let nearestHeight: Int32
do {
nearestHeight = try await rustBackend.getNearestRewindHeight(height: height)
} catch {
await fail(error)
return await context.completion(.failure(error))
}
// 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))
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)
return await context.completion(.failure(error))
}
@ -715,7 +694,7 @@ actor CompactBlockProcessor {
func validateServer() async {
do {
let info = try await self.service.getInfo()
try Self.validateServerInfo(
try await Self.validateServerInfo(
info,
saplingActivation: self.config.saplingActivation,
localNetwork: self.config.network,
@ -1055,14 +1034,10 @@ actor CompactBlockProcessor {
self.consecutiveChainValidationErrors += 1
let rewindResult = await rustBackend.rewindToHeight(
dbData: config.dataDb,
height: Int32(rewindHeight),
networkType: self.config.network.networkType
)
guard rewindResult else {
await fail(rustBackend.lastError() ?? RustWeldingError.genericError(message: "unknown error rewinding to height \(height)"))
do {
try await rustBackend.rewindToHeight(height: Int32(rewindHeight))
} catch {
await fail(error)
return
}
@ -1205,11 +1180,7 @@ extension CompactBlockProcessor.State: Equatable {
extension CompactBlockProcessor {
func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress {
try await rustBackend.getCurrentAddress(
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType
)
try await rustBackend.getCurrentAddress(account: Int32(accountIndex))
}
func getSaplingAddress(accountIndex: Int) async throws -> SaplingAddress {
@ -1227,18 +1198,10 @@ extension CompactBlockProcessor {
return WalletBalance(
verified: Zatoshi(
try await rustBackend.getVerifiedTransparentBalance(
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType
)
try await rustBackend.getVerifiedTransparentBalance(account: Int32(accountIndex))
),
total: Zatoshi(
try await rustBackend.getTransparentBalance(
dbData: config.dataDb,
account: Int32(accountIndex),
networkType: config.network.networkType
)
try await rustBackend.getTransparentBalance(account: Int32(accountIndex))
)
)
}
@ -1269,19 +1232,15 @@ extension CompactBlockProcessor {
var skipped: [UnspentTransactionOutputEntity] = []
for utxo in utxos {
do {
if try await rustBackend.putUnspentTransparentOutput(
dbData: dataDb,
try await rustBackend.putUnspentTransparentOutput(
txid: utxo.txid.bytes,
index: utxo.index,
script: utxo.script.bytes,
value: Int64(utxo.valueZat),
height: utxo.height,
networkType: self.config.network.networkType
) {
refreshed.append(utxo)
} else {
skipped.append(utxo)
}
height: utxo.height
)
refreshed.append(utxo)
} catch {
logger.info("failed to put utxo - error: \(error)")
skipped.append(utxo)
@ -1389,7 +1348,7 @@ extension CompactBlockProcessor {
downloaderService: BlockDownloaderService,
transactionRepository: TransactionRepository,
config: Configuration,
rustBackend: ZcashRustBackendWelding.Type,
rustBackend: ZcashRustBackendWelding,
internalSyncProgress: InternalSyncProgress
) 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
@ -1397,7 +1356,7 @@ extension CompactBlockProcessor {
let task = Task(priority: .userInitiated) {
let info = try await service.getInfo()
try CompactBlockProcessor.validateServerInfo(
try await CompactBlockProcessor.validateServerInfo(
info,
saplingActivation: config.saplingActivation,
localNetwork: config.network,

View File

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

View File

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

View File

@ -52,7 +52,10 @@ extension FSCompactBlockRepository: CompactBlockRepository {
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
}
}
@ -210,12 +213,12 @@ extension FSBlockFileWriter {
struct FSMetadataStore {
var saveBlocksMeta: ([ZcashCompactBlock]) async throws -> Void
var rewindToHeight: (BlockHeight) async throws -> Void
var initFsBlockDbRoot: (URL) async throws -> Bool
var initFsBlockDbRoot: () async throws -> Void
var latestHeight: () async -> BlockHeight
}
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
try await FSMetadataStore.saveBlocksMeta(
blocks,
@ -224,13 +227,15 @@ extension FSMetadataStore {
logger: logger
)
} 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)
}
} initFsBlockDbRoot: { dbRootURL in
try await rustBackend.initBlockMetadataDb(fsBlockDbRoot: dbRootURL)
} initFsBlockDbRoot: {
try await rustBackend.initBlockMetadataDb()
} latestHeight: {
await rustBackend.latestCachedBlockHeight(fsBlockDbRoot: fsBlockDbRoot)
await rustBackend.latestCachedBlockHeight()
}
}
}
@ -244,15 +249,13 @@ extension FSMetadataStore {
static func saveBlocksMeta(
_ blocks: [ZcashCompactBlock],
fsBlockDbRoot: URL,
rustBackend: ZcashRustBackendWelding.Type,
rustBackend: ZcashRustBackendWelding,
logger: Logger
) async throws {
guard !blocks.isEmpty else { return }
do {
guard try await rustBackend.writeBlocksMetadata(fsBlockDbRoot: fsBlockDbRoot, blocks: blocks) else {
throw CompactBlockRepositoryError.failedToWriteMetadata
}
try await rustBackend.writeBlocksMetadata(blocks: blocks)
} catch {
logger.error("Failed to write metadata with error: \(error)")
throw CompactBlockRepositoryError.failedToWriteMetadata

View File

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

View File

@ -8,8 +8,6 @@
import Foundation
struct BlockScannerConfig {
let fsBlockCacheRoot: URL
let dataDB: URL
let networkType: NetworkType
let scanningBatchSize: Int
}
@ -20,7 +18,7 @@ protocol BlockScanner {
struct BlockScannerImpl {
let config: BlockScannerConfig
let rustBackend: ZcashRustBackendWelding.Type
let rustBackend: ZcashRustBackendWelding
let transactionRepository: TransactionRepository
let metrics: SDKMetrics
let logger: Logger
@ -42,19 +40,16 @@ extension BlockScannerImpl: BlockScanner {
let previousScannedHeight = lastScannedHeight
// 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()
guard await self.rustBackend.scanBlocks(
fsBlockDbRoot: config.fsBlockCacheRoot,
dbData: config.dataDB,
limit: batchSize,
networkType: config.networkType
) else {
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
do {
try await self.rustBackend.scanBlocks(limit: batchSize)
} catch {
logger.debug("block scanning failed with error: \(String(describing: error))")
throw error
}
let scanFinishTime = Date()
lastScannedHeight = try await transactionRepository.lastScannedHeight()

View File

@ -14,20 +14,13 @@ enum BlockValidatorError: Error {
case failedWithUnknownError
}
struct BlockValidatorConfig {
let fsBlockCacheRoot: URL
let dataDB: URL
let networkType: NetworkType
}
protocol BlockValidator {
/// Validate all the downloaded blocks that haven't been yet validated.
func validate() async throws
}
struct BlockValidatorImpl {
let config: BlockValidatorConfig
let rustBackend: ZcashRustBackendWelding.Type
let rustBackend: ZcashRustBackendWelding
let metrics: SDKMetrics
let logger: Logger
}
@ -37,14 +30,24 @@ extension BlockValidatorImpl: BlockValidator {
try Task.checkCancellation()
let startTime = Date()
let result = await rustBackend.validateCombinedChain(
fsBlockDbRoot: config.fsBlockCacheRoot,
dbData: config.dataDB,
networkType: config.networkType,
limit: 0
)
let finishTime = Date()
do {
try await rustBackend.validateCombinedChain(limit: 0)
pushProgressReport(startTime: startTime, finishTime: Date())
logger.debug("validateChainFinished")
} catch {
pushProgressReport(startTime: startTime, 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(
progress: BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0),
start: startTime,
@ -52,24 +55,5 @@ extension BlockValidatorImpl: BlockValidator {
batchSize: 0,
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 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
/// 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.

View File

@ -28,7 +28,7 @@ struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable {
case rawTransactionId = "txid"
case fee
}
var recipient: PendingTransactionRecipient
var accountIndex: Int
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.
let id = UUID()
let rustBackend: ZcashRustBackendWelding.Type
let alias: ZcashSynchronizerAlias
let endpoint: LightWalletEndpoint
let fsBlockDbRoot: URL
@ -131,6 +130,7 @@ public class Initializer {
let blockDownloaderService: BlockDownloaderService
let network: ZcashNetwork
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.
///
@ -180,8 +180,16 @@ public class Initializer {
let (updatedURLs, parsingError) = Self.tryToUpdateURLs(with: alias, urls: urls)
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(
rustBackend: ZcashRustBackend.self,
rustBackend: rustBackend,
network: network,
cacheDbURL: cacheDbURL,
urls: updatedURLs,
@ -198,7 +206,7 @@ public class Initializer {
fsBlockDbRoot: updatedURLs.fsBlockDbRoot,
metadataStore: .live(
fsBlockDbRoot: updatedURLs.fsBlockDbRoot,
rustBackend: ZcashRustBackend.self,
rustBackend: rustBackend,
logger: logger
),
blockDescriptor: .live,
@ -216,7 +224,7 @@ public class Initializer {
///
/// !!! It's expected that URLs put here are already update with the Alias.
init(
rustBackend: ZcashRustBackendWelding.Type,
rustBackend: ZcashRustBackendWelding,
network: ZcashNetwork,
cacheDbURL: URL?,
urls: URLs,
@ -341,7 +349,7 @@ public class Initializer {
}
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
}
} catch {
@ -351,12 +359,10 @@ public class Initializer {
let checkpoint = Checkpoint.birthday(with: walletBirthday, network: network)
do {
try await rustBackend.initBlocksTable(
dbData: dataDbURL,
height: Int32(checkpoint.height),
hash: checkpoint.hash,
time: checkpoint.time,
saplingTree: checkpoint.saplingTree,
networkType: network.networkType
saplingTree: checkpoint.saplingTree
)
} catch RustWeldingError.dataDbNotEmpty {
// this is fine
@ -367,11 +373,7 @@ public class Initializer {
self.walletBirthday = checkpoint.height
do {
try await rustBackend.initAccountsTable(
dbData: dataDbURL,
ufvks: viewingKeys,
networkType: network.networkType
)
try await rustBackend.initAccountsTable(ufvks: viewingKeys)
} catch RustWeldingError.dataDbNotEmpty {
// this is fine
} catch RustWeldingError.malformedStringInput {
@ -395,14 +397,18 @@ public class Initializer {
checks if the provided address is a valid sapling address
*/
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
*/
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
/// found to be invalid
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
}
@ -85,7 +85,7 @@ public struct SaplingExtendedFullViewingKey: Equatable, StringEncoded, Undescrib
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
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
}
self.encoding = encoding
@ -174,6 +174,8 @@ public struct SaplingAddress: Equatable, StringEncoded {
}
public struct UnifiedAddress: Equatable, StringEncoded {
let networkType: NetworkType
public enum Errors: Error {
case couldNotExtractTypecodes
}
@ -212,6 +214,7 @@ public struct UnifiedAddress: Equatable, StringEncoded {
/// - Throws: `KeyEncodingError.invalidEncoding`when the provided encoding is
/// found to be invalid
public init(encoding: String, network: NetworkType) throws {
networkType = network
guard DerivationTool(networkType: network).isValidUnifiedAddress(encoding) else {
throw KeyEncodingError.invalidEncoding
}
@ -224,7 +227,7 @@ public struct UnifiedAddress: Equatable, StringEncoded {
/// couldn't be extracted
public func availableReceiverTypecodes() throws -> [UnifiedAddress.ReceiverTypecodes] {
do {
return try DerivationTool.receiverTypecodesFromUnifiedAddress(self)
return try DerivationTool(networkType: networkType).receiverTypecodesFromUnifiedAddress(self)
} catch {
throw Errors.couldNotExtractTypecodes
}
@ -272,7 +275,7 @@ public enum Recipient: Equatable, StringEncoded {
metadata.networkType)
case .p2sh: return (.transparent(TransparentAddress(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 libzcashlc
class ZcashRustBackend: ZcashRustBackendWelding {
static let minimumConfirmations: UInt32 = 10
static let useZIP317Fees = false
static func createAccount(dbData: URL, seed: [UInt8], networkType: NetworkType) async throws -> UnifiedSpendingKey {
let dbData = dbData.osStr()
actor ZcashRustBackend: ZcashRustBackendWelding {
let minimumConfirmations: UInt32 = 10
let useZIP317Fees = false
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(
dbData.0,
dbData.1,
@ -30,20 +55,13 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return ffiBinaryKeyPtr.pointee.unsafeToUnifiedSpendingKey(network: networkType)
}
// swiftlint:disable function_parameter_count
static func createToAddress(
dbData: URL,
func createToAddress(
usk: UnifiedSpendingKey,
to address: String,
value: Int64,
memo: MemoBytes?,
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) async -> Int64 {
let dbData = dbData.osStr()
return usk.bytes.withUnsafeBufferPointer { uskPtr in
memo: MemoBytes?
) async throws -> Int64 {
let result = usk.bytes.withUnsafeBufferPointer { uskPtr in
zcashlc_create_to_address(
dbData.0,
dbData.1,
@ -52,55 +70,39 @@ class ZcashRustBackend: ZcashRustBackendWelding {
[CChar](address.utf8CString),
value,
memo?.bytes,
spendParamsPath,
UInt(spendParamsPath.lengthOfBytes(using: .utf8)),
outputParamsPath,
UInt(outputParamsPath.lengthOfBytes(using: .utf8)),
spendParamsPath.0,
spendParamsPath.1,
outputParamsPath.0,
outputParamsPath.1,
networkType.networkId,
minimumConfirmations,
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 {
let dbData = dbData.osStr()
return zcashlc_decrypt_and_store_transaction(
func decryptAndStoreTransaction(txBytes: [UInt8], minedHeight: Int32) async throws {
let result = zcashlc_decrypt_and_store_transaction(
dbData.0,
dbData.1,
txBytes,
UInt(txBytes.count),
UInt32(minedHeight),
networkType.networkId
) != 0
}
)
static func deriveUnifiedSpendingKey(
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 {
guard result != 0 else {
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 {
let dbData = dbData.osStr()
func getBalance(account: Int32) async throws -> Int64 {
let balance = zcashlc_get_balance(dbData.0, dbData.1, account, networkType.networkId)
guard balance >= 0 else {
@ -110,13 +112,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance
}
static func getCurrentAddress(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> UnifiedAddress {
let dbData = dbData.osStr()
func getCurrentAddress(account: Int32) async throws -> UnifiedAddress {
guard let addressCStr = zcashlc_get_current_address(
dbData.0,
dbData.1,
@ -132,31 +128,25 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.unableToDeriveKeys
}
return UnifiedAddress(validatedEncoding: address)
return UnifiedAddress(validatedEncoding: address, networkType: networkType)
}
static func getNearestRewindHeight(
dbData: URL,
height: Int32,
networkType: NetworkType
) async -> Int32 {
let dbData = dbData.osStr()
return zcashlc_get_nearest_rewind_height(
func getNearestRewindHeight(height: Int32) async throws -> Int32 {
let result = zcashlc_get_nearest_rewind_height(
dbData.0,
dbData.1,
height,
networkType.networkId
)
guard result > 0 else {
throw lastError() ?? .genericError(message: "No error message available")
}
return result
}
static func getNextAvailableAddress(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> UnifiedAddress {
let dbData = dbData.osStr()
func getNextAvailableAddress(account: Int32) async throws -> UnifiedAddress {
guard let addressCStr = zcashlc_get_next_available_address(
dbData.0,
dbData.1,
@ -172,16 +162,10 @@ class ZcashRustBackend: ZcashRustBackendWelding {
throw RustWeldingError.unableToDeriveKeys
}
return UnifiedAddress(validatedEncoding: address)
return UnifiedAddress(validatedEncoding: address, networkType: networkType)
}
static func getReceivedMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo? {
let dbData = dbData.osStr()
func getReceivedMemo(idNote: Int64) async -> Memo? {
var contiguousMemoBytes = ContiguousArray<UInt8>(MemoBytes.empty().bytes)
var success = false
@ -194,29 +178,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() }
}
static 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)
}
static func getSentMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo? {
let dbData = dbData.osStr()
func getSentMemo(idNote: Int64) async -> Memo? {
var contiguousMemoBytes = ContiguousArray<UInt8>(MemoBytes.empty().bytes)
var success = false
@ -229,16 +191,11 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() }
}
static func getTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64 {
func getTransparentBalance(account: Int32) async throws -> Int64 {
guard account >= 0 else {
throw RustWeldingError.invalidInput(message: "Account index must be non-negative")
}
let dbData = dbData.osStr()
let balance = zcashlc_get_total_transparent_balance_for_account(
dbData.0,
dbData.1,
@ -253,8 +210,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance
}
static func getVerifiedBalance(dbData: URL, account: Int32, networkType: NetworkType) async throws -> Int64 {
let dbData = dbData.osStr()
func getVerifiedBalance(account: Int32) async throws -> Int64 {
let balance = zcashlc_get_verified_balance(
dbData.0,
dbData.1,
@ -270,17 +226,11 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance
}
static func getVerifiedTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64 {
func getVerifiedTransparentBalance(account: Int32) async throws -> Int64 {
guard account >= 0 else {
throw RustWeldingError.invalidInput(message: "`account` must be non-negative")
}
let dbData = dbData.osStr()
let balance = zcashlc_get_verified_transparent_balance_for_account(
dbData.0,
dbData.1,
@ -299,8 +249,8 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return balance
}
static func lastError() -> RustWeldingError? {
private nonisolated func lastError() -> RustWeldingError? {
defer { zcashlc_clear_last_error() }
guard let message = getLastError() else {
@ -316,43 +266,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return RustWeldingError.genericError(message: message)
}
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)
}
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? {
private nonisolated func getLastError() -> String? {
let errorLen = zcashlc_last_error_length()
if errorLen > 0 {
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 {
let dbData = dbData.osStr()
func initDataDb(seed: [UInt8]?) async throws -> DbInitResult {
switch zcashlc_init_data_database(dbData.0, dbData.1, seed, UInt(seed?.count ?? 0), networkType.networkId) {
case 0: // ok
return DbInitResult.success
@ -375,74 +288,20 @@ class ZcashRustBackend: ZcashRustBackendWelding {
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)
}
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()
func initAccountsTable(ufvks: [UnifiedFullViewingKey]) async throws {
var ffiUfvks: [FFIEncodedKey] = []
for ufvk in ufvks {
guard !ufvk.encoding.containsCStringNullBytesBeforeStringEnding() else {
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.")
}
let ufvkCStr = [CChar](String(ufvk.encoding).utf8CString)
let ufvkPtr = UnsafeMutablePointer<CChar>.allocate(capacity: ufvkCStr.count)
ufvkPtr.initialize(from: ufvkCStr, count: ufvkCStr.count)
@ -476,19 +335,15 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
}
static func initBlockMetadataDb(fsBlockDbRoot: URL) async throws -> Bool {
let blockDb = fsBlockDbRoot.osPathStr()
let result = zcashlc_init_block_metadata_db(blockDb.0, blockDb.1)
func initBlockMetadataDb() async throws {
let result = zcashlc_init_block_metadata_db(fsBlockDbRoot.0, fsBlockDbRoot.1)
guard result else {
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] = []
for block in blocks {
@ -507,7 +362,7 @@ class ZcashRustBackend: ZcashRustBackendWelding {
hashPtr.deallocate()
ffiBlockMetaVec.deallocateElements()
}
return false
throw RustWeldingError.writeBlocksMetadataAllocationProblem
}
ffiBlockMetaVec.append(
@ -530,50 +385,35 @@ class ZcashRustBackend: ZcashRustBackendWelding {
defer { ffiBlockMetaVec.deallocateElements() }
let result = try contiguousFFIBlocks.withContiguousMutableStorageIfAvailable { ptr in
try contiguousFFIBlocks.withContiguousMutableStorageIfAvailable { ptr in
var meta = FFIBlocksMeta()
meta.ptr = ptr.baseAddress
meta.len = len
fsBlocks.initialize(to: meta)
let fsDb = fsBlockDbRoot.osPathStr()
let res = zcashlc_write_block_metadata(fsDb.0, fsDb.1, fsBlocks)
let res = zcashlc_write_block_metadata(fsBlockDbRoot.0, fsBlockDbRoot.1, fsBlocks)
guard res else {
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
static func initBlocksTable(
dbData: URL,
func initBlocksTable(
height: Int32,
hash: String,
time: UInt32,
saplingTree: String,
networkType: NetworkType
saplingTree: String
) async throws {
let dbData = dbData.osStr()
guard !hash.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.invalidInput(message: "`hash` contains null bytes.")
}
guard !saplingTree.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.invalidInput(message: "`saplingTree` contains null bytes.")
}
guard zcashlc_init_blocks_table(
dbData.0,
dbData.1,
@ -587,19 +427,11 @@ class ZcashRustBackend: ZcashRustBackendWelding {
}
}
static func latestCachedBlockHeight(fsBlockDbRoot: URL) async -> BlockHeight {
let fsBlockDb = fsBlockDbRoot.osPathStr()
return BlockHeight(zcashlc_latest_cached_block_height(fsBlockDb.0, fsBlockDb.1))
func latestCachedBlockHeight() async -> BlockHeight {
return BlockHeight(zcashlc_latest_cached_block_height(fsBlockDbRoot.0, fsBlockDbRoot.1))
}
static func listTransparentReceivers(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> [TransparentAddress] {
let dbData = dbData.osStr()
func listTransparentReceivers(account: Int32) async throws -> [TransparentAddress] {
guard let encodedKeysPtr = zcashlc_list_transparent_receivers(
dbData.0,
dbData.1,
@ -627,18 +459,14 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return addresses
}
static func putUnspentTransparentOutput(
dbData: URL,
func putUnspentTransparentOutput(
txid: [UInt8],
index: Int,
script: [UInt8],
value: Int64,
height: BlockHeight,
networkType: NetworkType
) async throws -> Bool {
let dbData = dbData.osStr()
height: BlockHeight
) async throws {
guard zcashlc_put_utxo(
dbData.0,
dbData.1,
@ -653,48 +481,51 @@ class ZcashRustBackend: ZcashRustBackendWelding {
) else {
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(
fsBlockDbRoot: URL,
height: Int32
) -> Bool {
let fsBlockCache = fsBlockDbRoot.osPathStr()
func validateCombinedChain(limit: UInt32 = 0) async throws {
let result = zcashlc_validate_combined_chain(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, networkType.networkId, limit)
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 {
let dbCache = fsBlockDbRoot.osPathStr()
let dbData = dbData.osStr()
return zcashlc_scan_blocks(dbCache.0, dbCache.1, dbData.0, dbData.1, limit, networkType.networkId) != 0
func rewindToHeight(height: Int32) async throws {
let result = zcashlc_rewind_to_height(dbData.0, dbData.1, height, networkType.networkId)
guard result else {
throw lastError() ?? .genericError(message: "No error message available")
}
}
static func shieldFunds(
dbData: URL,
func rewindCacheToHeight(height: Int32) async throws {
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,
memo: MemoBytes?,
shieldingThreshold: Zatoshi,
spendParamsPath: String,
outputParamsPath: String,
networkType: NetworkType
) async -> Int64 {
let dbData = dbData.osStr()
return usk.bytes.withUnsafeBufferPointer { uskBuffer in
shieldingThreshold: Zatoshi
) async throws -> Int64 {
let result = usk.bytes.withUnsafeBufferPointer { uskBuffer in
zcashlc_shield_funds(
dbData.0,
dbData.1,
@ -702,82 +533,36 @@ class ZcashRustBackend: ZcashRustBackendWelding {
UInt(usk.bytes.count),
memo?.bytes,
UInt64(shieldingThreshold.amount),
spendParamsPath,
UInt(spendParamsPath.lengthOfBytes(using: .utf8)),
outputParamsPath,
UInt(outputParamsPath.lengthOfBytes(using: .utf8)),
spendParamsPath.0,
spendParamsPath.1,
outputParamsPath.0,
outputParamsPath.1,
networkType.networkId,
minimumConfirmations,
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) }
guard let derived = String(validatingUTF8: extfvk) else {
throw RustWeldingError.unableToDeriveKeys
}
return UnifiedFullViewingKey(validatedEncoding: derived, account: spendingKey.account)
return result
}
static 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
}
static func consensusBranchIdFor(height: Int32, networkType: NetworkType) throws -> Int32 {
nonisolated func consensusBranchIdFor(height: Int32) throws -> Int32 {
let branchId = zcashlc_branch_id_for_height(height, networkType.networkId)
guard branchId != -1 else {
throw RustWeldingError.noConsensusBranchId(height: height)
}
return branchId
}
}
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") {
return RustWeldingError.dataDbNotEmpty
}
@ -785,7 +570,7 @@ private extension ZcashRustBackend {
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 {
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"
case let .getBalanceError(account, 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 invalidInput(message: String)
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 {
@ -31,6 +38,7 @@ public enum DbInitResult {
case seedRequired
}
// sourcery: mockActor
protocol ZcashRustBackendWelding {
/// Adds the next available account-level spend authority, given the current set of [ZIP 316]
/// 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
/// have been received by the currently-available account (in order to enable
/// automated account recovery).
/// - parameter dbData: location of the data db
/// - 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
///
static func createAccount(
dbData: URL,
seed: [UInt8],
networkType: NetworkType
) async throws -> UnifiedSpendingKey
func createAccount(seed: [UInt8]) async throws -> UnifiedSpendingKey
/// 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 to: recipient address
/// - Parameter value: transaction amount in Zatoshi
/// - 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
/// - 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,
func createToAddress(
usk: UnifiedSpendingKey,
to address: String,
value: Int64,
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
memo: MemoBytes?
) async throws -> Int64
/// Returns the most-recently-generated unified payment address for the specified account.
/// - parameter dbData: location of the data db
/// Scans a transaction for any information that can be decrypted by the accounts in the wallet, and saves it to the wallet.
/// - 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 networkType: network type of this key
static func getCurrentAddress(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> UnifiedAddress
func getBalance(account: Int32) async throws -> Int64
/// Returns the most-recently-generated unified payment address for the specified account.
/// - parameter account: index of the given account
func getCurrentAddress(account: Int32) async throws -> UnifiedAddress
/// 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
@ -131,195 +90,64 @@ protocol ZcashRustBackendWelding {
/// 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
/// 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 networkType: network type of this key]
/// - Returns: the blockheight of the nearest rewind height.
///
static func getNearestRewindHeight(
dbData: URL,
height: Int32,
networkType: NetworkType
) async -> Int32
func getNearestRewindHeight(height: Int32) async throws -> Int32
/// 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 networkType: network type of this key
static func getNextAvailableAddress(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> UnifiedAddress
func getNextAvailableAddress(account: Int32) async throws -> UnifiedAddress
/// get received memo from note
/// - parameter dbData: location of the data db file
/// Get received memo from note.
/// - parameter idNote: note_id of note where the memo is located
/// - parameter networkType: network type of this key
static func getReceivedMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo?
func getReceivedMemo(idNote: Int64) async -> Memo?
/// 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
static func getSaplingReceiver(for uAddr: UnifiedAddress) throws -> SaplingAddress
/// get sent memo from note
/// - parameter dbData: location of the data db file
/// Get sent memo from note.
/// - parameter idNote: note_id of note where the memo is located
/// - parameter networkType: network type of this key
/// - Returns: a `Memo` if any
static func getSentMemo(
dbData: URL,
idNote: Int64,
networkType: NetworkType
) async -> Memo?
func getSentMemo(idNote: Int64) async -> Memo?
/// 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 networkType: network type of this key
static func getTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64
func getTransparentBalance(account: Int32) async throws -> Int64
/// 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
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
/// 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 ufvks: an array of UnifiedFullViewingKeys
/// - Parameter networkType: network type of this key
static func initAccountsTable(
dbData: URL,
ufvks: [UnifiedFullViewingKey],
networkType: NetworkType
) async throws
func initAccountsTable(ufvks: [UnifiedFullViewingKey]) 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.
/// - Parameter dbData: location of the data db sql file
/// - 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
/// or `DbInitResult.seedRequired` if the operation requires the seed to be passed
/// in order to be completed successfully.
static func initDataDb(
dbData: URL,
seed: [UInt8]?,
networkType: NetworkType
) async throws -> DbInitResult
func initDataDb(seed: [UInt8]?) async throws -> DbInitResult
/// Returns the network and address type for the given Zcash address string,
/// 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
/// Initialize the blocks table from a given checkpoint (heigh, hash, time, saplingTree and networkType).
/// - parameter height: represents the block height of the given checkpoint
/// - parameter hash: hash of the merkle tree
/// - parameter time: in milliseconds from reference
/// - parameter saplingTree: hash of the sapling tree
/// - parameter networkType: `NetworkType` signaling testnet or mainnet
static func initBlocksTable(
dbData: URL,
func initBlocksTable(
height: Int32,
hash: String,
time: UInt32,
saplingTree: String,
networkType: NetworkType
) async throws // swiftlint:disable function_parameter_count
saplingTree: String
) async throws
/// Returns a list of the transparent receivers for the diversified unified addresses that have
/// been allocated for the provided account.
/// - parameter dbData: location of the data db
/// - parameter account: index of the given account
/// - parameter networkType: the network type
static func listTransparentReceivers(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> [TransparentAddress]
func listTransparentReceivers(account: Int32) async throws -> [TransparentAddress]
/// get the verified balance from the given account
/// - parameter dbData: location of the data db
/// Get the verified balance from the given account
/// - parameter account: index of the given account
/// - parameter networkType: the network type
static func getVerifiedBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64
func getVerifiedBalance(account: Int32) async throws -> Int64
/// 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 networkType: the network type
static func getVerifiedTransparentBalance(
dbData: URL,
account: Int32,
networkType: NetworkType
) async throws -> Int64
func getVerifiedTransparentBalance(account: Int32) async throws -> Int64
/// Checks that the scanned blocks in the data database, when combined with the recent
/// `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 networkType: the network type
/// - parameter limit: a limit to validate a fixed number of blocks instead of the whole cache.
/// - Returns:
/// - `-1` if the combined chain is valid.
/// - `upper_bound` if the combined chain is invalid.
/// - `upper_bound` is the height of the highest invalid block (on the assumption that the highest block in the cache database is correct).
/// - `0` if there was an error during validation unrelated to chain validity.
/// - Throws:
/// - `RustWeldingError.chainValidationFailed` if there was an error during validation unrelated to chain validity.
/// - `RustWeldingError.invalidChain(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).
///
/// - Important: This function does not mutate either of the databases.
static func validateCombinedChain(
fsBlockDbRoot: URL,
dbData: URL,
networkType: NetworkType,
limit: UInt32
) async -> Int32
func validateCombinedChain(limit: UInt32) async throws
/// 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.
static func rewindToHeight(
dbData: URL,
height: Int32,
networkType: NetworkType
) async -> Bool
func rewindToHeight(height: Int32) async throws
/// 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.
/// - parameter fsBlockDbRoot: location of the data db file
/// - parameter height: height to rewind to. DON'T PASS ARBITRARY HEIGHT. Use getNearestRewindHeight when unsure
/// - parameter networkType: the network type
static func rewindCacheToHeight(
fsBlockDbRoot: URL,
height: Int32
) async -> Bool
/// - parameter height: height to rewind to. DON'T PASS ARBITRARY HEIGHT. Use `getNearestRewindHeight` when unsure
func rewindCacheToHeight(height: Int32) async throws
/// Scans new blocks added to the cache for any transactions received by the tracked
/// accounts.
@ -379,101 +190,48 @@ protocol ZcashRustBackendWelding {
/// Scanned blocks are required to be height-sequential. If a block is missing from the
/// 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 networkType: the network type
/// returns false if fails to scan.
static func scanBlocks(
fsBlockDbRoot: URL,
dbData: URL,
limit: UInt32,
networkType: NetworkType
) async -> Bool
func scanBlocks(limit: UInt32) async throws
/// 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 index: the index of the UTXO
/// - parameter script: the script of the UTXO
/// - parameter value: the value of the UTXO
/// - parameter height: the mined height for the UTXO
/// - parameter networkType: the network type
/// - Returns: true if the operation succeded or false otherwise
static func putUnspentTransparentOutput(
dbData: URL,
func putUnspentTransparentOutput(
txid: [UInt8],
index: Int,
script: [UInt8],
value: Int64,
height: BlockHeight,
networkType: NetworkType
) async throws -> Bool
height: BlockHeight
) async throws
/// 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 memo: the `Memo` for this transaction
/// - Parameter spendParamsPath: path escaped String for the filesystem locations where the spend parameters are located
/// - 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,
func shieldFunds(
usk: UnifiedSpendingKey,
memo: MemoBytes?,
shieldingThreshold: Zatoshi,
spendParamsPath: String,
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]
shieldingThreshold: Zatoshi
) async throws -> Int64
/// Gets the consensus branch id for the given height
/// - Parameter height: the height you what to know the branch id for
/// - Parameter networkType: the network type
static func consensusBranchIdFor(
height: Int32,
networkType: NetworkType
) throws -> Int32
func consensusBranchIdFor(height: Int32) throws -> Int32
/// Derives a `UnifiedFullViewingKey` from a `UnifiedSpendingKey`
/// - 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
/// Initializes Filesystem based block cache
/// - 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
/// - 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 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
/// - Parameter blocks: The `ZcashCompactBlock`s that are going to be marked as stored by the metadata Db.
func writeBlocksMetadata(blocks: [ZcashCompactBlock]) async throws
/// 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.
/// 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 `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
/// Get all memos for `transaction`.
///
// sourcery: mockedName="getMemosForClearedTransaction"
func getMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo]
/// Get all memos for `receivedTransaction`.
///
// sourcery: mockedName="getMemosForReceivedTransaction"
func getMemos(for receivedTransaction: ZcashTransaction.Received) async throws -> [Memo]
/// Get all memos for `sentTransaction`.
///
// sourcery: mockedName="getMemosForSentTransaction"
func getMemos(for sentTransaction: ZcashTransaction.Sent) async throws -> [Memo]
/// Attempt to get recipients from 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
/// transaction
///
// sourcery: mockedName="getRecipientsForClearedTransaction"
func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient]
/// Get the recipients for the given a sent transaction
/// - 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
/// transaction
///
// sourcery: mockedName="getRecipientsForSentTransaction"
func getRecipients(for transaction: ZcashTransaction.Sent) async -> [TransactionRecipient]
/// 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,
completion: @escaping (Result<Initializer.InitializationResult, Error>) -> Void
) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
return try await self.synchronizer.prepare(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday)
}
}
public func start(retry: Bool, completion: @escaping (Error?) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.start(retry: retry)
}
}
public func stop(completion: @escaping () -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.stop()
}
}
public func getSaplingAddress(accountIndex: Int, completion: @escaping (Result<SaplingAddress, Error>) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getSaplingAddress(accountIndex: accountIndex)
}
}
public func getUnifiedAddress(accountIndex: Int, completion: @escaping (Result<UnifiedAddress, Error>) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getUnifiedAddress(accountIndex: accountIndex)
}
}
public func getTransparentAddress(accountIndex: Int, completion: @escaping (Result<TransparentAddress, Error>) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getTransparentAddress(accountIndex: accountIndex)
}
}
@ -79,7 +79,7 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
memo: Memo?,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo)
}
}
@ -90,37 +90,37 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
shieldingThreshold: Zatoshi,
completion: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
}
}
public func cancelSpend(transaction: PendingTransactionEntity, completion: @escaping (Bool) -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.cancelSpend(transaction: transaction)
}
}
public func pendingTransactions(completion: @escaping ([PendingTransactionEntity]) -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.pendingTransactions
}
}
public func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.clearedTransactions
}
}
public func sentTranscations(completion: @escaping ([ZcashTransaction.Sent]) -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.sentTransactions
}
}
public func receivedTransactions(completion: @escaping ([ZcashTransaction.Received]) -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.receivedTransactions
}
}
@ -128,31 +128,31 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
public func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository { synchronizer.paginatedTransactions(of: kind) }
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)
}
}
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)
}
}
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)
}
}
public func getRecipients(for transaction: ZcashTransaction.Overview, completion: @escaping ([TransactionRecipient]) -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.getRecipients(for: transaction)
}
}
public func getRecipients(for transaction: ZcashTransaction.Sent, completion: @escaping ([TransactionRecipient]) -> Void) {
executeAction(completion) {
AsyncToClosureGateway.executeAction(completion) {
await self.synchronizer.getRecipients(for: transaction)
}
}
@ -162,37 +162,37 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
limit: Int,
completion: @escaping (Result<[ZcashTransaction.Overview], Error>) -> Void
) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.allConfirmedTransactions(from: transaction, limit: limit)
}
}
public func latestHeight(completion: @escaping (Result<BlockHeight, Error>) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.latestHeight()
}
}
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)
}
}
public func getTransparentBalance(accountIndex: Int, completion: @escaping (Result<WalletBalance, Error>) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getTransparentBalance(accountIndex: accountIndex)
}
}
public func getShieldedBalance(accountIndex: Int, completion: @escaping (Result<Zatoshi, Error>) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getShieldedBalance(accountIndex: accountIndex)
}
}
public func getShieldedVerifiedBalance(accountIndex: Int, completion: @escaping (Result<Zatoshi, Error>) -> Void) {
executeThrowingAction(completion) {
AsyncToClosureGateway.executeThrowingAction(completion) {
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 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],
walletBirthday: BlockHeight
) -> SinglePublisher<Initializer.InitializationResult, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
return try await self.synchronizer.prepare(with: seed, viewingKeys: viewingKeys, walletBirthday: walletBirthday)
}
}
public func start(retry: Bool) -> CompletablePublisher<Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.start(retry: retry)
}
}
public func stop() -> CompletablePublisher<Never> {
return executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.stop()
}
}
public func getSaplingAddress(accountIndex: Int) -> SinglePublisher<SaplingAddress, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getSaplingAddress(accountIndex: accountIndex)
}
}
public func getUnifiedAddress(accountIndex: Int) -> SinglePublisher<UnifiedAddress, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getUnifiedAddress(accountIndex: accountIndex)
}
}
public func getTransparentAddress(accountIndex: Int) -> SinglePublisher<TransparentAddress, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getTransparentAddress(accountIndex: accountIndex)
}
}
@ -77,7 +77,7 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
toAddress: Recipient,
memo: Memo?
) -> SinglePublisher<PendingTransactionEntity, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.sendToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo)
}
}
@ -87,37 +87,37 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
memo: Memo,
shieldingThreshold: Zatoshi
) -> SinglePublisher<PendingTransactionEntity, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.shieldFunds(spendingKey: spendingKey, memo: memo, shieldingThreshold: shieldingThreshold)
}
}
public func cancelSpend(transaction: PendingTransactionEntity) -> SinglePublisher<Bool, Never> {
executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.cancelSpend(transaction: transaction)
}
}
public var pendingTransactions: SinglePublisher<[PendingTransactionEntity], Never> {
executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.pendingTransactions
}
}
public var clearedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> {
executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.clearedTransactions
}
}
public var sentTransactions: SinglePublisher<[ZcashTransaction.Sent], Never> {
executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.sentTransactions
}
}
public var receivedTransactions: SinglePublisher<[ZcashTransaction.Received], Never> {
executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.receivedTransactions
}
}
@ -125,67 +125,67 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
public func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository { synchronizer.paginatedTransactions(of: kind) }
public func getMemos(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[Memo], Error> {
executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getMemos(for: transaction)
}
}
public func getMemos(for receivedTransaction: ZcashTransaction.Received) -> SinglePublisher<[Memo], Error> {
executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getMemos(for: receivedTransaction)
}
}
public func getMemos(for sentTransaction: ZcashTransaction.Sent) -> SinglePublisher<[Memo], Error> {
executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getMemos(for: sentTransaction)
}
}
public func getRecipients(for transaction: ZcashTransaction.Overview) -> SinglePublisher<[TransactionRecipient], Never> {
executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.getRecipients(for: transaction)
}
}
public func getRecipients(for transaction: ZcashTransaction.Sent) -> SinglePublisher<[TransactionRecipient], Never> {
executeAction() {
AsyncToCombineGateway.executeAction() {
await self.synchronizer.getRecipients(for: transaction)
}
}
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)
}
}
public func latestHeight() -> SinglePublisher<BlockHeight, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.latestHeight()
}
}
public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) -> SinglePublisher<RefreshedUTXOs, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.refreshUTXOs(address: address, from: height)
}
}
public func getTransparentBalance(accountIndex: Int) -> SinglePublisher<WalletBalance, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getTransparentBalance(accountIndex: accountIndex)
}
}
public func getShieldedBalance(accountIndex: Int = 0) -> SinglePublisher<Zatoshi, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
try await synchronizer.getShieldedBalance(accountIndex: accountIndex)
}
}
public func getShieldedVerifiedBalance(accountIndex: Int = 0) -> SinglePublisher<Zatoshi, Error> {
return executeThrowingAction() {
AsyncToCombineGateway.executeThrowingAction() {
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 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 {
let balance = try await initializer.rustBackend.getBalance(
dbData: initializer.dataDbURL,
account: Int32(accountIndex),
networkType: network.networkType
)
let balance = try await initializer.rustBackend.getBalance(account: Int32(accountIndex))
return Zatoshi(balance)
}
public func getShieldedVerifiedBalance(accountIndex: Int = 0) async throws -> Zatoshi {
let balance = try await initializer.rustBackend.getVerifiedBalance(
dbData: initializer.dataDbURL,
account: Int32(accountIndex),
networkType: network.networkType
)
let balance = try await initializer.rustBackend.getVerifiedBalance(account: Int32(accountIndex))
return Zatoshi(balance)
}
@ -617,7 +609,6 @@ public class SDKSynchronizer: Synchronizer {
newState = await snapshotState(status: newStatus)
} else {
newState = await SynchronizerState(
syncSessionID: syncSession.value,
shieldedBalance: latestState.shieldedBalance,

View File

@ -5,17 +5,14 @@
// Created by Francisco Gindre on 10/8/20.
//
import Combine
import Foundation
public protocol KeyValidation {
func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool
func isValidTransparentAddress(_ tAddress: String) -> Bool
func isValidSaplingAddress(_ zAddress: String) -> Bool
func isValidSaplingExtendedSpendingKey(_ extsk: String) -> Bool
func isValidUnifiedAddress(_ unifiedAddress: String) -> Bool
}
@ -25,26 +22,39 @@ public protocol KeyDeriving {
/// - Parameter accountNumber: `Int` with the account number
/// - Throws `.unableToDerive` if there's a problem deriving this key
/// - 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`
/// - Parameter address: the `UnifiedAddress`
/// - 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`
/// - Parameter address: the `UnifiedAddress`
/// - 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`
/// - Parameter address: the `UnifiedAddress`
/// - 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 {
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 invalidInput
case invalidUnifiedAddress
@ -52,31 +62,43 @@ public enum KeyDerivationErrors: Error {
}
public class DerivationTool: KeyDeriving {
static var rustwelding: ZcashRustBackendWelding.Type = ZcashRustBackend.self
let backend: ZcashKeyDerivationBackendWelding
var networkType: NetworkType
public init(networkType: NetworkType) {
self.networkType = networkType
init(networkType: NetworkType) {
self.backend = ZcashKeyDerivationBackend(networkType: networkType)
}
public static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress {
try rustwelding.getSaplingReceiver(for: unifiedAddress)
public func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress {
try backend.getSaplingReceiver(for: unifiedAddress)
}
public static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress {
try rustwelding.getTransparentReceiver(for: unifiedAddress)
public func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress {
try backend.getTransparentReceiver(for: unifiedAddress)
}
public static func getAddressMetadata(_ addr: String) -> AddressMetadata? {
rustwelding.getAddressMetadata(addr)
ZcashKeyDerivationBackend.getAddressMetadata(addr)
}
/// 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.
public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) throws -> UnifiedFullViewingKey {
try DerivationTool.rustwelding.deriveUnifiedFullViewingKey(from: spendingKey, networkType: self.networkType)
public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) async throws -> UnifiedFullViewingKey {
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.
@ -84,24 +106,34 @@ public class DerivationTool: KeyDeriving {
/// - Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
/// supported so the default value of 1 is recommended.
/// - 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 {
throw KeyDerivationErrors.invalidInput
}
do {
return try DerivationTool.rustwelding.deriveUnifiedSpendingKey(
from: seed,
accountIndex: accountIndex,
networkType: self.networkType
)
return try await backend.deriveUnifiedSpendingKey(from: seed, accountIndex: accountIndex)
} catch {
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 {
return try DerivationTool.rustwelding.receiverTypecodesOnUnifiedAddress(address.stringEncoded)
return try backend.receiverTypecodesOnUnifiedAddress(address.stringEncoded)
.map({ UnifiedAddress.ReceiverTypecodes(typecode: $0) })
} catch {
throw KeyDerivationErrors.invalidUnifiedAddress
@ -121,23 +153,23 @@ public struct AddressMetadata {
extension DerivationTool: KeyValidation {
public func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool {
DerivationTool.rustwelding.isValidUnifiedFullViewingKey(ufvk, networkType: networkType)
backend.isValidUnifiedFullViewingKey(ufvk)
}
public func isValidUnifiedAddress(_ unifiedAddress: String) -> Bool {
DerivationTool.rustwelding.isValidUnifiedAddress(unifiedAddress, networkType: networkType)
backend.isValidUnifiedAddress(unifiedAddress)
}
public func isValidTransparentAddress(_ tAddress: String) -> Bool {
DerivationTool.rustwelding.isValidTransparentAddress(tAddress, networkType: networkType)
backend.isValidTransparentAddress(tAddress)
}
public func isValidSaplingAddress(_ zAddress: String) -> Bool {
DerivationTool.rustwelding.isValidSaplingAddress(zAddress, networkType: networkType)
backend.isValidSaplingAddress(zAddress)
}
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
/// constructing an address from a primitive function of the FFI, you probably
/// shouldn't be using this..
init(validatedEncoding: String) {
init(validatedEncoding: String, networkType: NetworkType) {
self.encoding = validatedEncoding
self.networkType = networkType
}
}
@ -206,22 +239,18 @@ public extension UnifiedSpendingKey {
func map<T>(_ transform: (UnifiedSpendingKey) throws -> T) rethrows -> T {
try transform(self)
}
func deriveFullViewingKey() throws -> UnifiedFullViewingKey {
try DerivationTool(networkType: self.network).deriveUnifiedFullViewingKey(from: self)
}
}
public extension UnifiedAddress {
/// Extracts the sapling receiver from this UA if available
/// - Returns: an `Optional<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
/// - Returns: an `Optional<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
class WalletTransactionEncoder: TransactionEncoder {
var rustBackend: ZcashRustBackendWelding.Type
var rustBackend: ZcashRustBackendWelding
var repository: TransactionRepository
let logger: Logger
@ -19,7 +19,7 @@ class WalletTransactionEncoder: TransactionEncoder {
private var networkType: NetworkType
init(
rust: ZcashRustBackendWelding.Type,
rustBackend: ZcashRustBackendWelding,
dataDb: URL,
fsBlockDbRoot: URL,
repository: TransactionRepository,
@ -28,7 +28,7 @@ class WalletTransactionEncoder: TransactionEncoder {
networkType: NetworkType,
logger: Logger
) {
self.rustBackend = rust
self.rustBackend = rustBackend
self.dataDbURL = dataDb
self.fsBlockDbRoot = fsBlockDbRoot
self.repository = repository
@ -40,7 +40,7 @@ class WalletTransactionEncoder: TransactionEncoder {
convenience init(initializer: Initializer) {
self.init(
rust: initializer.rustBackend,
rustBackend: initializer.rustBackend,
dataDb: initializer.dataDbURL,
fsBlockDbRoot: initializer.fsBlockDbRoot,
repository: initializer.transactionRepository,
@ -84,22 +84,14 @@ class WalletTransactionEncoder: TransactionEncoder {
guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else {
throw TransactionEncoderError.missingParams
}
let txId = await rustBackend.createToAddress(
dbData: self.dataDbURL,
let txId = try await rustBackend.createToAddress(
usk: spendingKey,
to: address,
value: zatoshi.amount,
memo: memoBytes,
spendParamsPath: self.spendParamsURL.path,
outputParamsPath: self.outputParamsURL.path,
networkType: networkType
memo: memoBytes
)
guard txId > 0 else {
throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "create spend failed")
}
return Int(txId)
}
@ -134,20 +126,12 @@ class WalletTransactionEncoder: TransactionEncoder {
throw TransactionEncoderError.missingParams
}
let txId = await rustBackend.shieldFunds(
dbData: self.dataDbURL,
let txId = try await rustBackend.shieldFunds(
usk: spendingKey,
memo: memo,
shieldingThreshold: shieldingThreshold,
spendParamsPath: self.spendParamsURL.path,
outputParamsPath: self.outputParamsURL.path,
networkType: networkType
shieldingThreshold: shieldingThreshold
)
guard txId > 0 else {
throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "create spend failed")
}
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
protocol SyncSessionIDGenerator {
func nextID() -> UUID
}

View File

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

View File

@ -173,11 +173,13 @@ class RewindRescanTests: XCTestCase {
// rewind to birthday
let targetHeight: BlockHeight = newChaintTip - 8000
let rewindHeight = await ZcashRustBackend.getNearestRewindHeight(
dbData: coordinator.databases.dataDB,
height: Int32(targetHeight),
networkType: network.networkType
)
do {
_ = try await coordinator.synchronizer.initializer.rustBackend.getNearestRewindHeight(height: Int32(targetHeight))
} catch {
XCTFail("get nearest height failed error: \(error)")
return
}
let rewindExpectation = XCTestExpectation(description: "RewindExpectation")
@ -202,11 +204,6 @@ class RewindRescanTests: XCTestCase {
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
var expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
XCTAssertEqual(initialVerifiedBalance, expectedVerifiedBalance)

View File

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

View File

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

View File

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

View File

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

View File

@ -12,9 +12,30 @@ import XCTest
@testable import ZcashLightClientKit
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)
return CompactBlockProcessor.Configuration(
processorConfig = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL,
@ -24,28 +45,6 @@ class CompactBlockProcessorTests: XCTestCase {
walletBirthdayProvider: { ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight },
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(
alias: .default,
@ -58,7 +57,14 @@ class CompactBlockProcessorTests: XCTestCase {
latestBlockHeight: mockLatestHeight,
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
info.blockHeight = UInt64(mockLatestHeight)
info.branch = "asdf"
@ -70,13 +76,11 @@ class CompactBlockProcessorTests: XCTestCase {
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
})
let realRustBackend = ZcashRustBackend.self
let storage = FSCompactBlockRepository(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
rustBackend: realRustBackend,
rustBackend: rustBackend,
logger: logger
),
blockDescriptor: .live,
@ -89,13 +93,13 @@ class CompactBlockProcessorTests: XCTestCase {
processor = CompactBlockProcessor(
service: service,
storage: storage,
backend: realRustBackend,
rustBackend: rustBackend,
config: processorConfig,
metrics: SDKMetrics(),
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 {
XCTFail("Failed to initDataDb. Expected `.success` got: \(dbInit)")
@ -125,6 +129,8 @@ class CompactBlockProcessorTests: XCTestCase {
cancellables = []
processor = nil
processorEventHandler = nil
rustBackend = nil
testTempDirectory = nil
}
func processorFailed(event: CompactBlockProcessor.Event) {

View File

@ -12,9 +12,31 @@ import XCTest
@testable import ZcashLightClientKit
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)
return CompactBlockProcessor.Configuration(
processorConfig = CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL,
@ -24,30 +46,6 @@ class CompactBlockReorgTests: XCTestCase {
walletBirthdayProvider: { ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight },
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(
alias: .default,
@ -60,8 +58,14 @@ class CompactBlockReorgTests: XCTestCase {
latestBlockHeight: mockLatestHeight,
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
info.blockHeight = UInt64(mockLatestHeight)
info.branch = "asdf"
@ -73,13 +77,11 @@ class CompactBlockReorgTests: XCTestCase {
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
}
let realRustBackend = ZcashRustBackend.self
let realCache = FSCompactBlockRepository(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
rustBackend: realRustBackend,
rustBackend: rustBackend,
logger: logger
),
blockDescriptor: .live,
@ -89,21 +91,23 @@ class CompactBlockReorgTests: XCTestCase {
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 {
XCTFail("initDataDb failed. Expected Success but got .seedRequired")
return
}
let mockBackend = MockRustBackend.self
mockBackend.mockValidateCombinedChainFailAfterAttempts = 3
mockBackend.mockValidateCombinedChainKeepFailing = false
mockBackend.mockValidateCombinedChainFailureHeight = self.network.constants.saplingActivationHeight + 320
rustBackendMockHelper = await RustBackendMockHelper(
rustBackend: rustBackend,
mockValidateCombinedChainFailAfterAttempts: 3,
mockValidateCombinedChainKeepFailing: false,
mockValidateCombinedChainFailureError: .invalidChain(upperBound: Int32(network.constants.saplingActivationHeight + 320))
)
processor = CompactBlockProcessor(
service: service,
storage: realCache,
backend: mockBackend,
rustBackend: rustBackendMockHelper.rustBackendMock,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
@ -134,6 +138,8 @@ class CompactBlockReorgTests: XCTestCase {
cancellables = []
processorEventHandler = nil
processor = nil
rustBackend = nil
rustBackendMockHelper = nil
}
func processorHandledReorg(event: CompactBlockProcessor.Event) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,15 +17,15 @@ class NullBytesTests: XCTestCase {
let validZaddr = "zs1gqtfu59z20s9t20mxlxj86zpw6p69l0ev98uxrmlykf2nchj2dw8ny5e0l22kwmld2afc37gkfp"
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 {
// 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 tAddrWithNullBytes = "\(validTAddr)\0fasdfasdf"
XCTAssertFalse(ZcashRustBackend.isValidTransparentAddress(tAddrWithNullBytes, networkType: networkType))
XCTAssertFalse(DerivationTool(networkType: networkType).isValidTransparentAddress(tAddrWithNullBytes))
}
func testInitAccountTableNullBytes() async throws {
@ -50,14 +50,18 @@ class NullBytesTests: XCTestCase {
934dc76f087935a5c07788000b4e3aae24883adfec51b5f4d260
"""
let rustBackend = ZcashRustBackend.makeForTests(
dbData: try! __dataDbURL(),
fsBlockDbRoot: Environment.uniqueTestTempDirectory,
networkType: networkType
)
do {
_ = try await ZcashRustBackend.initBlocksTable(
dbData: __dataDbURL(),
_ = try await rustBackend.initBlocksTable(
height: height,
hash: wrongHash,
time: time,
saplingTree: goodTree,
networkType: networkType
saplingTree: goodTree
)
XCTFail("InitBlocksTable with Null bytes on hash string should have failed")
} catch {
@ -75,13 +79,11 @@ class NullBytesTests: XCTestCase {
}
do {
try await ZcashRustBackend.initBlocksTable(
dbData: __dataDbURL(),
try await rustBackend.initBlocksTable(
height: height,
hash: goodHash,
time: time,
saplingTree: wrongTree,
networkType: networkType
saplingTree: wrongTree
)
XCTFail("InitBlocksTable with Null bytes on saplingTree string should have failed")
} catch {
@ -106,7 +108,7 @@ class NullBytesTests: XCTestCase {
// let goodSpendingKeys = SaplingExtendedSpendingKey(validatedEncoding: "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv")
//
// 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!"
// ) { error in
// 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 {

View File

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

View File

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

View File

@ -14,7 +14,8 @@ class TransactionRepositoryTests: XCTestCase {
override func setUp() async throws {
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() {

View File

@ -28,11 +28,11 @@ final class UnifiedTypecodesTests: XCTestCase {
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 {
@ -44,9 +44,9 @@ final class UnifiedTypecodesTests: XCTestCase {
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(
Set<UnifiedAddress.ReceiverTypecodes>(typecodes),
@ -70,7 +70,8 @@ final class UnifiedTypecodesTests: XCTestCase {
validatedEncoding: """
u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxg\
pyuj
"""
""",
networkType: .testnet
)
XCTAssertEqual(try ua.availableReceiverTypecodes(), [.sapling, .p2pkh])

View File

@ -12,13 +12,8 @@ import XCTest
@testable import ZcashLightClientKit
class WalletTests: XCTestCase {
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager()
var testTempDirectory: URL!
var dbData: URL! = nil
var paramDestination: URL! = nil
var network = ZcashNetworkBuilder.network(for: .testnet)
@ -26,8 +21,9 @@ class WalletTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
testTempDirectory = Environment.uniqueTestTempDirectory
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")
}
@ -36,17 +32,17 @@ class WalletTests: XCTestCase {
if testFileManager.fileExists(atPath: dbData.absoluteString) {
try testFileManager.trashItem(at: dbData, resultingItemURL: nil)
}
try? self.testFileManager.removeItem(at: self.testTempDirectory)
try? self.testFileManager.removeItem(at: testTempDirectory)
}
func testWalletInitialization() async throws {
let derivationTool = DerivationTool(networkType: network.networkType)
let ufvk = try derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0)
.map({ try derivationTool.deriveUnifiedFullViewingKey(from: $0) })
let derivationTool = TestsData(networkType: network.networkType).derivationTools
let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0)
let viewingKey = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
let wallet = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: self.testTempDirectory,
fsBlockDbRoot: testTempDirectory,
dataDbURL: try __dataDbURL(),
pendingDbURL: try TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default,
@ -58,7 +54,7 @@ class WalletTests: XCTestCase {
let synchronizer = SDKSynchronizer(initializer: wallet)
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`")
return
}

View File

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

View File

@ -26,6 +26,8 @@ class SynchronizerTests: XCTestCase {
var coordinator: TestCoordinator!
var cancellables: [AnyCancellable] = []
var sdkSynchronizerSyncStatusHandler: SDKSynchronizerSyncStatusHandler! = SDKSynchronizerSyncStatusHandler()
var rustBackend: ZcashRustBackendWelding!
var testTempDirectory: URL!
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
@ -33,11 +35,19 @@ class SynchronizerTests: XCTestCase {
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() {
super.tearDown()
coordinator = nil
cancellables = []
sdkSynchronizerSyncStatusHandler = nil
rustBackend = nil
testTempDirectory = nil
}
func testHundredBlocksSync() async throws {
@ -47,11 +57,11 @@ class SynchronizerTests: XCTestCase {
return
}
let seedBytes = [UInt8](seedData)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(
let spendingKey = try await derivationTool.deriveUnifiedSpendingKey(
seed: seedBytes,
accountIndex: 0
)
let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
let ufvk = try await derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
let network = ZcashNetworkBuilder.network(for: .mainnet)
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 {
let closure: CompactBlockProcessor.EventClosure = { event in
print("Received event: \(event.identifier)")
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 {
static var networkType = NetworkType.testnet
static var mockDataDb = false
static var mockAcounts = false
static var mockError: RustWeldingError?
static var mockLastError: String?
static var mockAccounts: [SaplingExtendedSpendingKey]?
static var mockAddresses: [String]?
static var mockBalance: Int64?
static var mockVerifiedBalance: Int64?
static var mockMemo: String?
static var mockSentMemo: String?
static var mockValidateCombinedChainSuccessRate: Float?
static var mockValidateCombinedChainFailAfterAttempts: Int?
static var mockValidateCombinedChainKeepFailing = false
static var mockValidateCombinedChainFailureHeight: BlockHeight = 0
static var mockScanblocksSuccessRate: Float?
static var mockCreateToAddress: Int64?
static var rustBackend = ZcashRustBackend.self
static var consensusBranchID: Int32?
static var writeBlocksMetadataResult: () throws -> Bool = { true }
static var rewindCacheToHeightResult: () -> Bool = { true }
static func latestCachedBlockHeight(fsBlockDbRoot: URL) async -> ZcashLightClientKit.BlockHeight {
.empty()
class RustBackendMockHelper {
let rustBackendMock: ZcashRustBackendWeldingMock
var mockValidateCombinedChainFailAfterAttempts: Int?
init(
rustBackend: ZcashRustBackendWelding,
consensusBranchID: Int32? = nil,
mockValidateCombinedChainSuccessRate: Float? = nil,
mockValidateCombinedChainFailAfterAttempts: Int? = nil,
mockValidateCombinedChainKeepFailing: Bool = false,
mockValidateCombinedChainFailureError: RustWeldingError = .chainValidationFailed(message: nil)
) async {
self.mockValidateCombinedChainFailAfterAttempts = mockValidateCombinedChainFailAfterAttempts
self.rustBackendMock = ZcashRustBackendWeldingMock(
consensusBranchIdForHeightClosure: { height in
if let consensusBranchID {
return consensusBranchID
} else {
return try rustBackend.consensusBranchIdFor(height: height)
}
}
)
await setupDefaultMock(
rustBackend: rustBackend,
mockValidateCombinedChainSuccessRate: mockValidateCombinedChainSuccessRate,
mockValidateCombinedChainKeepFailing: mockValidateCombinedChainKeepFailing,
mockValidateCombinedChainFailureError: mockValidateCombinedChainFailureError
)
}
static func rewindCacheToHeight(fsBlockDbRoot: URL, height: Int32) async -> Bool {
rewindCacheToHeightResult()
}
private func setupDefaultMock(
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 {
true
}
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)
await rustBackendMock.setInitDataDbSeedClosure() { seed in
return try await rustBackend.initDataDb(seed: seed)
}
return consensus
}
static func lastError() -> RustWeldingError? {
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 {
await rustBackendMock.setInitBlocksTableHeightHashTimeSaplingTreeClosure() { height, hash, time, saplingTree in
try await rustBackend.initBlocksTable(
dbData: dbData,
height: height,
hash: hash,
time: time,
saplingTree: saplingTree,
networkType: networkType
saplingTree: saplingTree
)
}
}
static func getBalance(dbData: URL, account: Int32, networkType: NetworkType) async throws -> Int64 {
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
await rustBackendMock.setGetBalanceAccountClosure() { account in
return try await rustBackend.getBalance(account: account)
}
return try await rustBackend.getVerifiedBalance(dbData: dbData, account: account, networkType: networkType)
}
static func validateCombinedChain(fsBlockDbRoot: URL, dbData: URL, networkType: NetworkType, limit: UInt32 = 0) async -> Int32 {
if let rate = self.mockValidateCombinedChainSuccessRate {
if shouldSucceed(successRate: rate) {
return await validationResult(fsBlockDbRoot: fsBlockDbRoot, dbData: dbData, networkType: networkType)
} else {
return Int32(mockValidateCombinedChainFailureHeight)
}
} 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)
await rustBackendMock.setGetVerifiedBalanceAccountClosure() { account in
return try await rustBackend.getVerifiedBalance(account: account)
}
await rustBackendMock.setValidateCombinedChainLimitClosure() { [weak self] limit in
guard let self else { throw RustWeldingError.genericError(message: "Self is nil") }
if let rate = mockValidateCombinedChainSuccessRate {
if Self.shouldSucceed(successRate: rate) {
return try await rustBackend.validateCombinedChain(limit: limit)
} 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 {
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(
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 {
private static func shouldSucceed(successRate: Float) -> Bool {
let random = Float.random(in: 0.0...1.0)
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 {

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,
streamingCallTimeoutInMillis: 1000000
)
convenience init(
init(
alias: ZcashSynchronizerAlias = .default,
walletBirthday: BlockHeight,
network: ZcashNetwork,
callPrepareInConstructor: Bool = true,
endpoint: LightWalletEndpoint = TestCoordinator.defaultEndpoint,
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 {
await InternalSyncProgress(alias: alias, storage: UserDefaults.standard, logger: logger).rewind(to: 0)
self.spendingKey = spendingKey
self.viewingKey = unifiedFullViewingKey
self.birthday = walletBirthday
self.databases = TemporaryDbBuilder.build()
self.network = network
let liveService = LightWalletServiceFactory(endpoint: endpoint).make()
self.service = DarksideWalletService(endpoint: endpoint, service: liveService)
let databases = TemporaryDbBuilder.build()
self.databases = databases
let initializer = Initializer(
cacheDbURL: nil,
@ -117,9 +80,21 @@ class TestCoordinator {
logLevel: .debug
)
let synchronizer = SDKSynchronizer(initializer: initializer, sessionGenerator: syncSessionIDGenerator, sessionTicker: .live)
self.synchronizer = synchronizer
let derivationTool = initializer.makeDerivationTool()
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)
if callPrepareInConstructor {

View File

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

View File

@ -9,10 +9,10 @@
import Combine
import Foundation
import GRPC
import ZcashLightClientKit
import XCTest
import NIO
import NIOTransportServices
@testable import ZcashLightClientKit
enum Environment {
static let lightwalletdKey = "LIGHTWALLETD_ADDRESS"
@ -28,6 +28,11 @@ enum Environment {
}
static let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a"
static var uniqueTestTempDirectory: URL {
URL(fileURLWithPath: NSString(string: NSTemporaryDirectory())
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
}
}
public enum Constants {
@ -128,3 +133,21 @@ func parametersReady() -> Bool {
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.
@ -8,13 +8,29 @@
import Foundation
@testable import ZcashLightClientKit
class AlternativeSynchronizerAPITestsData {
let derivationTools = DerivationTool(networkType: .testnet)
class TestsData {
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 unifiedAddress = UnifiedAddress(
validatedEncoding: """
u1l9f0l4348negsncgr9pxd9d3qaxagmqv3lnexcplmufpq7muffvfaue6ksevfvd7wrz7xrvn95rc5zjtn7ugkmgh5rnxswmcj30y0pw52pn0zjvy38rn2esfgve64rj5pcmazxgpyuj
"""
""",
networkType: .testnet
)
let transparentAddress = TransparentAddress(validatedEncoding: "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz")
lazy var pendingTransactionEntity = {
@ -73,9 +89,19 @@ class AlternativeSynchronizerAPITestsData {
}()
var seed: [UInt8] = Environment.seedBytes
lazy var spendingKey: UnifiedSpendingKey = { try! derivationTools.deriveUnifiedSpendingKey(seed: seed, accountIndex: 0) }()
lazy var viewingKey: UnifiedFullViewingKey = { try! derivationTools.deriveUnifiedFullViewingKey(from: spendingKey) }()
var spendingKey: UnifiedSpendingKey {
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
init() { }
init(networkType: NetworkType) {
self.networkType = networkType
}
}