Compare commits
19 Commits
70a7c90b93
...
5817f6926d
Author | SHA1 | Date |
---|---|---|
Pacu | 5817f6926d | |
Lukas Korba | d8f389b8da | |
Lukas Korba | eb2ebeaf97 | |
Lukas Korba | 279de9a2a4 | |
Lukas Korba | e3cb722db8 | |
Lukas Korba | 8909f23722 | |
Lukas Korba | c5e07943d3 | |
Lukas Korba | b183b10d13 | |
Lukas Korba | c3e6201bcd | |
Lukas Korba | 4a1edd7e44 | |
Lukas Korba | 41cf1357bd | |
Jack Grigg | fa4def9f29 | |
Jack Grigg | 43534d26d9 | |
str4d | 730aee0c5f | |
Lukas Korba | e1c600b8ff | |
Jack Grigg | a5a0ef0ac1 | |
Jack Grigg | 86defc8b4a | |
Jack Grigg | dd9942b6ab | |
Francisco Gindre | 09d220593f |
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -5,6 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
# Unreleased
|
||||
|
||||
# 2.1.2 - 2024-03-27
|
||||
|
||||
## Fixed
|
||||
- Bug in note selection when sending to a transparent recipient.
|
||||
|
||||
# 2.1.1 - 2024-03-27
|
||||
|
||||
## Fixed
|
||||
- Bug in an SQL query that prevented shielding of transparent funds.
|
||||
|
||||
# 2.1.0 - 2024-03-26
|
||||
|
||||
### [#1379] Fulfill Payment from a valid ZIP-321 request
|
||||
New API implemented that allows clients to use a ZIP-321 Payment URI to create transaction.
|
||||
```
|
||||
|
@ -18,6 +31,29 @@ Possible errors:
|
|||
- `ZcashError.rustProposeTransferFromURI`
|
||||
- Other errors that `sentToAddress` can throw
|
||||
|
||||
## Removed
|
||||
|
||||
- `SDKSynchronizer.latestUTXOs`
|
||||
|
||||
## Checkpoints
|
||||
|
||||
Mainnet
|
||||
|
||||
````
|
||||
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/2430000.json
|
||||
...
|
||||
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/2447500.json
|
||||
````
|
||||
|
||||
|
||||
Testnet
|
||||
|
||||
````
|
||||
Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2750000.json
|
||||
...
|
||||
Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2770000.json
|
||||
````
|
||||
|
||||
# 2.0.11 - 2024-03-08
|
||||
|
||||
## Changed
|
||||
|
|
|
@ -176,8 +176,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
|
||||
"state" : {
|
||||
"revision" : "7c801be1f445402a433b32835a50d832e8a50437",
|
||||
"version" : "0.6.0"
|
||||
"revision" : "8838b4f0ee4193349fed09f0248220d4ada271fc",
|
||||
"version" : "0.7.3"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -122,8 +122,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
|
||||
"state" : {
|
||||
"revision" : "7c801be1f445402a433b32835a50d832e8a50437",
|
||||
"version" : "0.6.0"
|
||||
"revision" : "c7e5158edf5e62af15492d30237163b78af35ce9",
|
||||
"version" : "0.7.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -16,7 +16,7 @@ let package = Package(
|
|||
dependencies: [
|
||||
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.19.1"),
|
||||
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"),
|
||||
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.6.0")
|
||||
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.7.3")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
|
|
|
@ -28,7 +28,6 @@ actor CompactBlockProcessor {
|
|||
private let configProvider: ConfigProvider
|
||||
private var afterSyncHooksManager = AfterSyncHooksManager()
|
||||
|
||||
private let accountRepository: AccountRepository
|
||||
var blockDownloaderService: BlockDownloaderService
|
||||
private var latestBlocksDataProvider: LatestBlocksDataProvider
|
||||
private let logger: Logger
|
||||
|
@ -142,20 +141,6 @@ actor CompactBlockProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Initializes a CompactBlockProcessor instance
|
||||
/// - Parameters:
|
||||
/// - service: concrete implementation of `LightWalletService` protocol
|
||||
/// - storage: concrete implementation of `CompactBlockRepository` protocol
|
||||
/// - backend: a class that complies to `ZcashRustBackendWelding`
|
||||
/// - config: `Configuration` struct for this processor
|
||||
init(container: DIContainer, config: Configuration) {
|
||||
self.init(
|
||||
container: container,
|
||||
config: config,
|
||||
accountRepository: AccountRepositoryBuilder.build(dataDbURL: config.dataDb, readOnly: true, logger: container.resolve(Logger.self))
|
||||
)
|
||||
}
|
||||
|
||||
/// Initializes a CompactBlockProcessor instance from an Initialized object
|
||||
/// - Parameters:
|
||||
/// - initializer: an instance that complies to CompactBlockDownloading protocol
|
||||
|
@ -171,20 +156,23 @@ actor CompactBlockProcessor {
|
|||
saplingParamsSourceURL: initializer.saplingParamsSourceURL,
|
||||
walletBirthdayProvider: walletBirthdayProvider,
|
||||
network: initializer.network
|
||||
),
|
||||
accountRepository: initializer.accountRepository
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Initializes a CompactBlockProcessor instance
|
||||
/// - Parameters:
|
||||
/// - service: concrete implementation of `LightWalletService` protocol
|
||||
/// - storage: concrete implementation of `CompactBlockRepository` protocol
|
||||
/// - backend: a class that complies to `ZcashRustBackendWelding`
|
||||
/// - config: `Configuration` struct for this processor
|
||||
init(
|
||||
container: DIContainer,
|
||||
config: Configuration,
|
||||
accountRepository: AccountRepository
|
||||
config: Configuration
|
||||
) {
|
||||
Dependencies.setupCompactBlockProcessor(
|
||||
in: container,
|
||||
config: config,
|
||||
accountRepository: accountRepository
|
||||
config: config
|
||||
)
|
||||
|
||||
let configProvider = ConfigProvider(config: config)
|
||||
|
@ -200,7 +188,6 @@ actor CompactBlockProcessor {
|
|||
self.storage = container.resolve(CompactBlockRepository.self)
|
||||
self.config = config
|
||||
self.transactionRepository = container.resolve(TransactionRepository.self)
|
||||
self.accountRepository = accountRepository
|
||||
self.fileManager = container.resolve(ZcashFileManager.self)
|
||||
self.configProvider = configProvider
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ protocol UTXOFetcher {
|
|||
}
|
||||
|
||||
struct UTXOFetcherImpl {
|
||||
let accountRepository: AccountRepository
|
||||
let blockDownloaderService: BlockDownloaderService
|
||||
let config: UTXOFetcherConfig
|
||||
let rustBackend: ZcashRustBackendWelding
|
||||
|
@ -37,8 +36,7 @@ extension UTXOFetcherImpl: UTXOFetcher {
|
|||
) async throws -> (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]) {
|
||||
try Task.checkCancellation()
|
||||
|
||||
let accounts = try accountRepository.getAll()
|
||||
.map { $0.account }
|
||||
let accounts = try await rustBackend.listAccounts()
|
||||
|
||||
var tAddresses: [TransparentAddress] = []
|
||||
for account in accounts {
|
||||
|
|
|
@ -23,6 +23,7 @@ protocol BlockScanner {
|
|||
struct BlockScannerImpl {
|
||||
let config: BlockScannerConfig
|
||||
let rustBackend: ZcashRustBackendWelding
|
||||
let service: LightWalletService
|
||||
let transactionRepository: TransactionRepository
|
||||
let metrics: SDKMetrics
|
||||
let logger: Logger
|
||||
|
@ -56,7 +57,9 @@ extension BlockScannerImpl: BlockScanner {
|
|||
let scanSummary: ScanSummary
|
||||
let scanStartTime = Date()
|
||||
do {
|
||||
scanSummary = try await self.rustBackend.scanBlocks(fromHeight: Int32(startHeight), limit: batchSize)
|
||||
let fromState = try await service.getTreeState(BlockID(height: startHeight - 1))
|
||||
|
||||
scanSummary = try await self.rustBackend.scanBlocks(fromHeight: Int32(startHeight), fromState: fromState, limit: batchSize)
|
||||
} catch {
|
||||
logger.debug("block scanning failed with error: \(String(describing: error))")
|
||||
throw error
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct CheckpointSourceFactory {
|
||||
enum CheckpointSourceFactory {
|
||||
static func fromBundle(for network: NetworkType) -> CheckpointSource {
|
||||
BundleCheckpointSource(network: network)
|
||||
}
|
||||
|
|
|
@ -8,25 +8,12 @@
|
|||
import Foundation
|
||||
|
||||
struct UTXO: Decodable, Encodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "id_utxo"
|
||||
case address
|
||||
case prevoutTxId = "prevout_txid"
|
||||
case prevoutIndex = "prevout_idx"
|
||||
case script
|
||||
case valueZat = "value_zat"
|
||||
case height
|
||||
case spentInTx = "spent_in_tx"
|
||||
}
|
||||
|
||||
let id: Int?
|
||||
let address: String
|
||||
var prevoutTxId: Data
|
||||
var prevoutIndex: Int
|
||||
let script: Data
|
||||
let valueZat: Int
|
||||
let height: Int
|
||||
let spentInTx: Int?
|
||||
}
|
||||
|
||||
extension UTXO: UnspentTransactionOutputEntity {
|
||||
|
@ -48,145 +35,3 @@ extension UTXO: UnspentTransactionOutputEntity {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UnspentTransactionOutputEntity {
|
||||
/**
|
||||
As UTXO, with id and spentIntTx set to __nil__
|
||||
*/
|
||||
func asUTXO() -> UTXO {
|
||||
UTXO(
|
||||
id: nil,
|
||||
address: address,
|
||||
prevoutTxId: txid,
|
||||
prevoutIndex: index,
|
||||
script: script,
|
||||
valueZat: valueZat,
|
||||
height: height,
|
||||
spentInTx: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
import SQLite
|
||||
class UnspentTransactionOutputSQLDAO: UnspentTransactionOutputRepository {
|
||||
enum TableColumns {
|
||||
static let id = Expression<Int>("id_utxo")
|
||||
static let address = Expression<String>("address")
|
||||
static let txid = Expression<Blob>("prevout_txid")
|
||||
static let index = Expression<Int>("prevout_idx")
|
||||
static let script = Expression<Blob>("script")
|
||||
static let valueZat = Expression<Int>("value_zat")
|
||||
static let height = Expression<Int>("height")
|
||||
static let spentInTx = Expression<Int?>("spent_in_tx")
|
||||
}
|
||||
|
||||
let table = Table("utxos")
|
||||
|
||||
let dbProvider: ConnectionProvider
|
||||
|
||||
init(dbProvider: ConnectionProvider) {
|
||||
self.dbProvider = dbProvider
|
||||
}
|
||||
|
||||
/// - Throws: `unspentTransactionOutputDAOCreateTable` if creation table fails.
|
||||
func initialise() async throws {
|
||||
try await createTableIfNeeded()
|
||||
}
|
||||
|
||||
private func createTableIfNeeded() async throws {
|
||||
let stringStatement =
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS utxos (
|
||||
id_utxo INTEGER PRIMARY KEY,
|
||||
address TEXT NOT NULL,
|
||||
prevout_txid BLOB NOT NULL,
|
||||
prevout_idx INTEGER NOT NULL,
|
||||
script BLOB NOT NULL,
|
||||
value_zat INTEGER NOT NULL,
|
||||
height INTEGER NOT NULL,
|
||||
spent_in_tx INTEGER,
|
||||
FOREIGN KEY (spent_in_tx) REFERENCES transactions(id_tx),
|
||||
CONSTRAINT tx_outpoint UNIQUE (prevout_txid, prevout_idx)
|
||||
)
|
||||
"""
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
try dbProvider.connection().run(stringStatement)
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOCreateTable(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws: `unspentTransactionOutputDAOStore` if sqlite query fails.
|
||||
func store(utxos: [UnspentTransactionOutputEntity]) async throws {
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
let db = try dbProvider.connection()
|
||||
try db.transaction {
|
||||
for utxo in utxos.map({ $0 as? UTXO ?? $0.asUTXO() }) {
|
||||
try db.run(table.insert(utxo))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOStore(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws: `unspentTransactionOutputDAOClearAll` if sqlite query fails.
|
||||
func clearAll(address: String?) async throws {
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
if let tAddr = address {
|
||||
try dbProvider.connection().run(table.filter(TableColumns.address == tAddr).delete())
|
||||
} else {
|
||||
try dbProvider.connection().run(table.delete())
|
||||
}
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOClearAll(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws:
|
||||
/// - `unspentTransactionOutputDAOClearAll` if the data fetched from the DB can't be decoded to `UTXO` object.
|
||||
/// - `unspentTransactionOutputDAOGetAll` if sqlite query fails.
|
||||
func getAll(address: String?) async throws -> [UnspentTransactionOutputEntity] {
|
||||
do {
|
||||
if let tAddress = address {
|
||||
let allTxs: [UTXO] = try dbProvider.connection()
|
||||
.prepare(table.filter(TableColumns.address == tAddress))
|
||||
.map { row in
|
||||
do {
|
||||
return try row.decode()
|
||||
} catch {
|
||||
throw ZcashError.unspentTransactionOutputDAOGetAllCantDecode(error)
|
||||
}
|
||||
}
|
||||
return allTxs
|
||||
} else {
|
||||
let allTxs: [UTXO] = try dbProvider.connection()
|
||||
.prepare(table)
|
||||
.map { row in
|
||||
try row.decode()
|
||||
}
|
||||
return allTxs
|
||||
}
|
||||
} catch {
|
||||
if let error = error as? ZcashError {
|
||||
throw error
|
||||
} else {
|
||||
throw ZcashError.unspentTransactionOutputDAOGetAll(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum UTXORepositoryBuilder {
|
||||
static func build(initializer: Initializer) -> UnspentTransactionOutputRepository {
|
||||
return UnspentTransactionOutputSQLDAO(dbProvider: SimpleConnectionProvider(path: initializer.dataDbURL.path))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,6 @@ protocol AccountEntity {
|
|||
}
|
||||
|
||||
struct DbAccount: AccountEntity, Encodable, Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case account
|
||||
case ufvk
|
||||
}
|
||||
|
||||
let account: Int
|
||||
let ufvk: String
|
||||
}
|
||||
|
@ -38,171 +33,3 @@ extension DbAccount: Hashable {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
protocol AccountRepository {
|
||||
func getAll() throws -> [AccountEntity]
|
||||
func findBy(account: Int) throws -> AccountEntity?
|
||||
func update(_ account: AccountEntity) throws
|
||||
}
|
||||
|
||||
class AccountSQDAO: AccountRepository {
|
||||
enum TableColums {
|
||||
static let account = Expression<Int>("account")
|
||||
static let extfvk = Expression<String>("ufvk")
|
||||
}
|
||||
|
||||
let table = Table("accounts")
|
||||
|
||||
let dbProvider: ConnectionProvider
|
||||
let logger: Logger
|
||||
|
||||
init(dbProvider: ConnectionProvider, logger: Logger) {
|
||||
self.dbProvider = dbProvider
|
||||
self.logger = logger
|
||||
}
|
||||
|
||||
/// - Throws:
|
||||
/// - `accountDAOGetAllCantDecode` if account data fetched from the db can't be decoded to the `Account` object.
|
||||
/// - `accountDAOGetAll` if sqlite query fetching account data failed.
|
||||
func getAll() throws -> [AccountEntity] {
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
return try dbProvider.connection()
|
||||
.prepare(table)
|
||||
.map { row -> DbAccount in
|
||||
do {
|
||||
return try row.decode()
|
||||
} catch {
|
||||
throw ZcashError.accountDAOGetAllCantDecode(error)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if let error = error as? ZcashError {
|
||||
throw error
|
||||
} else {
|
||||
throw ZcashError.accountDAOGetAll(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws:
|
||||
/// - `accountDAOFindByCantDecode` if account data fetched from the db can't be decoded to the `Account` object.
|
||||
/// - `accountDAOFindBy` if sqlite query fetching account data failed.
|
||||
func findBy(account: Int) throws -> AccountEntity? {
|
||||
let query = table.filter(TableColums.account == account).limit(1)
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
return try dbProvider.connection()
|
||||
.prepare(query)
|
||||
.map {
|
||||
do {
|
||||
return try $0.decode() as DbAccount
|
||||
} catch {
|
||||
throw ZcashError.accountDAOFindByCantDecode(error)
|
||||
}
|
||||
}
|
||||
.first
|
||||
} catch {
|
||||
if let error = error as? ZcashError {
|
||||
throw error
|
||||
} else {
|
||||
throw ZcashError.accountDAOFindBy(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// - Throws:
|
||||
/// - `accountDAOUpdate` if sqlite query updating account failed.
|
||||
/// - `accountDAOUpdatedZeroRows` if sqlite query updating account pass but it affects 0 rows.
|
||||
func update(_ account: AccountEntity) throws {
|
||||
guard let acc = account as? DbAccount else {
|
||||
throw ZcashError.accountDAOUpdateInvalidAccount
|
||||
}
|
||||
|
||||
let updatedRows: Int
|
||||
do {
|
||||
globalDBLock.lock()
|
||||
defer { globalDBLock.unlock() }
|
||||
|
||||
updatedRows = try dbProvider.connection().run(table.filter(TableColums.account == acc.account).update(acc))
|
||||
} catch {
|
||||
throw ZcashError.accountDAOUpdate(error)
|
||||
}
|
||||
|
||||
if updatedRows == 0 {
|
||||
logger.error("attempted to update pending transactions but no rows were updated")
|
||||
throw ZcashError.accountDAOUpdatedZeroRows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CachingAccountDao: AccountRepository {
|
||||
let dao: AccountRepository
|
||||
lazy var cache: [Int: AccountEntity] = {
|
||||
var accountCache: [Int: AccountEntity] = [:]
|
||||
guard let all = try? dao.getAll() else {
|
||||
return accountCache
|
||||
}
|
||||
|
||||
for acc in all {
|
||||
accountCache[acc.account] = acc
|
||||
}
|
||||
|
||||
return accountCache
|
||||
}()
|
||||
|
||||
init(dao: AccountRepository) {
|
||||
self.dao = dao
|
||||
}
|
||||
|
||||
func getAll() throws -> [AccountEntity] {
|
||||
guard cache.isEmpty else {
|
||||
return cache.values.sorted(by: { $0.account < $1.account })
|
||||
}
|
||||
|
||||
let all = try dao.getAll()
|
||||
|
||||
for acc in all {
|
||||
cache[acc.account] = acc
|
||||
}
|
||||
|
||||
return all
|
||||
}
|
||||
|
||||
func findBy(account: Int) throws -> AccountEntity? {
|
||||
if let acc = cache[account] {
|
||||
return acc
|
||||
}
|
||||
|
||||
let acc = try dao.findBy(account: account)
|
||||
cache[account] = acc
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
func update(_ account: AccountEntity) throws {
|
||||
try dao.update(account)
|
||||
}
|
||||
}
|
||||
|
||||
enum AccountRepositoryBuilder {
|
||||
static func build(dataDbURL: URL, readOnly: Bool = false, caching: Bool = false, logger: Logger) -> AccountRepository {
|
||||
if caching {
|
||||
return CachingAccountDao(
|
||||
dao: AccountSQDAO(
|
||||
dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly),
|
||||
logger: logger
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return AccountSQDAO(
|
||||
dbProvider: SimpleConnectionProvider(path: dataDbURL.path, readonly: readOnly),
|
||||
logger: logger
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ public enum ZcashTransaction {
|
|||
public enum Pool {
|
||||
case transaparent
|
||||
case sapling
|
||||
case orchard
|
||||
case other(Int)
|
||||
init(rawValue: Int) {
|
||||
switch rawValue {
|
||||
|
@ -72,6 +73,8 @@ public enum ZcashTransaction {
|
|||
self = .transaparent
|
||||
case 2:
|
||||
self = .sapling
|
||||
case 3:
|
||||
self = .orchard
|
||||
default:
|
||||
self = .other(rawValue)
|
||||
}
|
||||
|
@ -101,8 +104,8 @@ extension ZcashTransaction.Output {
|
|||
static let rawID = Expression<Blob>("txid")
|
||||
static let pool = Expression<Int>("output_pool")
|
||||
static let index = Expression<Int>("output_index")
|
||||
static let toAccount = Expression<Int?>("to_account")
|
||||
static let fromAccount = Expression<Int?>("from_account")
|
||||
static let toAccount = Expression<Int?>("to_account_id")
|
||||
static let fromAccount = Expression<Int?>("from_account_id")
|
||||
static let toAddress = Expression<String?>("to_address")
|
||||
static let value = Expression<Int64>("value")
|
||||
static let isChange = Expression<Bool>("is_change")
|
||||
|
|
|
@ -325,6 +325,14 @@ public enum ZcashError: Equatable, Error {
|
|||
/// - `rustError` contains error generated by the rust layer.
|
||||
/// ZRUST0057
|
||||
case rustProposeTransferFromURI(_ rustError: String)
|
||||
/// Error from rust layer when calling ZcashRustBackend.
|
||||
/// - `rustError` contains error generated by the rust layer.
|
||||
/// ZRUST0058
|
||||
case rustListAccounts(_ rustError: String)
|
||||
/// Error from rust layer when calling ZcashRustBackend.rustIsSeedRelevantToAnyDerivedAccount
|
||||
/// - `rustError` contains error generated by the rust layer.
|
||||
/// ZRUST0059
|
||||
case rustIsSeedRelevantToAnyDerivedAccount(_ rustError: String)
|
||||
/// SQLite query failed when fetching all accounts from the database.
|
||||
/// - `sqliteError` is error produced by SQLite library.
|
||||
/// ZADAO0001
|
||||
|
@ -681,6 +689,8 @@ public enum ZcashError: Equatable, Error {
|
|||
case .rustScanProgressOutOfRange: return "Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%."
|
||||
case .rustGetWalletSummary: return "Error from rust layer when calling ZcashRustBackend.getWalletSummary"
|
||||
case .rustProposeTransferFromURI: return "Error from rust layer when calling ZcashRustBackend."
|
||||
case .rustListAccounts: return "Error from rust layer when calling ZcashRustBackend."
|
||||
case .rustIsSeedRelevantToAnyDerivedAccount: return "Error from rust layer when calling ZcashRustBackend.rustIsSeedRelevantToAnyDerivedAccount"
|
||||
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
|
||||
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
|
||||
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
|
||||
|
@ -856,6 +866,8 @@ public enum ZcashError: Equatable, Error {
|
|||
case .rustScanProgressOutOfRange: return .rustScanProgressOutOfRange
|
||||
case .rustGetWalletSummary: return .rustGetWalletSummary
|
||||
case .rustProposeTransferFromURI: return .rustProposeTransferFromURI
|
||||
case .rustListAccounts: return .rustListAccounts
|
||||
case .rustIsSeedRelevantToAnyDerivedAccount: return .rustIsSeedRelevantToAnyDerivedAccount
|
||||
case .accountDAOGetAll: return .accountDAOGetAll
|
||||
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
|
||||
case .accountDAOFindBy: return .accountDAOFindBy
|
||||
|
|
|
@ -177,6 +177,10 @@ public enum ZcashErrorCode: String {
|
|||
case rustGetWalletSummary = "ZRUST0056"
|
||||
/// Error from rust layer when calling ZcashRustBackend.
|
||||
case rustProposeTransferFromURI = "ZRUST0057"
|
||||
/// Error from rust layer when calling ZcashRustBackend.
|
||||
case rustListAccounts = "ZRUST0058"
|
||||
/// Error from rust layer when calling ZcashRustBackend.rustIsSeedRelevantToAnyDerivedAccount
|
||||
case rustIsSeedRelevantToAnyDerivedAccount = "ZRUST0059"
|
||||
/// SQLite query failed when fetching all accounts from the database.
|
||||
case accountDAOGetAll = "ZADAO0001"
|
||||
/// Fetched accounts from SQLite but can't decode them.
|
||||
|
|
|
@ -352,6 +352,14 @@ enum ZcashErrorDefinition {
|
|||
/// - `rustError` contains error generated by the rust layer.
|
||||
// sourcery: code="ZRUST0057"
|
||||
case rustProposeTransferFromURI(_ rustError: String)
|
||||
/// Error from rust layer when calling ZcashRustBackend.
|
||||
/// - `rustError` contains error generated by the rust layer.
|
||||
// sourcery: code="ZRUST0058"
|
||||
case rustListAccounts(_ rustError: String)
|
||||
/// Error from rust layer when calling ZcashRustBackend.rustIsSeedRelevantToAnyDerivedAccount
|
||||
/// - `rustError` contains error generated by the rust layer.
|
||||
// sourcery: code="ZRUST0059"
|
||||
case rustIsSeedRelevantToAnyDerivedAccount(_ rustError: String)
|
||||
|
||||
// MARK: - Account DAO
|
||||
|
||||
|
|
|
@ -120,7 +120,6 @@ public class Initializer {
|
|||
let saplingParamsSourceURL: SaplingParamsSourceURL
|
||||
var lightWalletService: LightWalletService
|
||||
let transactionRepository: TransactionRepository
|
||||
let accountRepository: AccountRepository
|
||||
let storage: CompactBlockRepository
|
||||
var blockDownloaderService: BlockDownloaderService
|
||||
let network: ZcashNetwork
|
||||
|
@ -272,12 +271,6 @@ public class Initializer {
|
|||
self.alias = alias
|
||||
self.lightWalletService = container.resolve(LightWalletService.self)
|
||||
self.transactionRepository = container.resolve(TransactionRepository.self)
|
||||
self.accountRepository = AccountRepositoryBuilder.build(
|
||||
dataDbURL: urls.dataDbURL,
|
||||
readOnly: true,
|
||||
caching: true,
|
||||
logger: container.resolve(Logger.self)
|
||||
)
|
||||
self.storage = container.resolve(CompactBlockRepository.self)
|
||||
self.blockDownloaderService = container.resolve(BlockDownloaderService.self)
|
||||
self.network = network
|
||||
|
@ -419,7 +412,7 @@ public class Initializer {
|
|||
self.walletBirthday = checkpoint.height
|
||||
|
||||
// If there are no accounts it must be created, the default amount of accounts is 1
|
||||
if let seed, try accountRepository.getAll().isEmpty {
|
||||
if let seed, try await rustBackend.listAccounts().isEmpty {
|
||||
var chainTip: UInt32?
|
||||
|
||||
if walletMode == .restoreWallet {
|
||||
|
|
|
@ -21,9 +21,10 @@ public struct PoolBalance: Equatable {
|
|||
|
||||
public struct AccountBalance: Equatable {
|
||||
public let saplingBalance: PoolBalance
|
||||
public let orchardBalance: PoolBalance
|
||||
public let unshielded: Zatoshi
|
||||
|
||||
static let zero = AccountBalance(saplingBalance: .zero, unshielded: .zero)
|
||||
static let zero = AccountBalance(saplingBalance: .zero, orchardBalance: .zero, unshielded: .zero)
|
||||
}
|
||||
|
||||
struct ScanProgress: Equatable {
|
||||
|
@ -53,4 +54,5 @@ struct WalletSummary: Equatable {
|
|||
let fullyScannedHeight: BlockHeight
|
||||
let scanProgress: ScanProgress?
|
||||
let nextSaplingSubtreeIndex: UInt32
|
||||
let nextOrchardSubtreeIndex: UInt32
|
||||
}
|
||||
|
|
|
@ -223,14 +223,12 @@ extension LightWalletGRPCService: LightWalletService {
|
|||
do {
|
||||
guard let reply = try await iterator.next() else { return nil }
|
||||
return UTXO(
|
||||
id: nil,
|
||||
address: reply.address,
|
||||
prevoutTxId: reply.txid,
|
||||
prevoutIndex: Int(reply.index),
|
||||
script: reply.script,
|
||||
valueZat: Int(reply.valueZat),
|
||||
height: Int(reply.height),
|
||||
spentInTx: nil
|
||||
height: Int(reply.height)
|
||||
)
|
||||
} catch {
|
||||
let serviceError = error.mapToServiceError()
|
||||
|
@ -278,6 +276,10 @@ extension LightWalletGRPCService: LightWalletService {
|
|||
}
|
||||
}
|
||||
|
||||
func getTreeState(_ id: BlockID) async throws -> TreeState {
|
||||
try await compactTxStreamer.getTreeState(id)
|
||||
}
|
||||
|
||||
func closeConnection() {
|
||||
_ = channel.close()
|
||||
}
|
||||
|
|
|
@ -196,4 +196,6 @@ protocol LightWalletService: AnyObject {
|
|||
/// - Parameters:
|
||||
/// - request: Request to send to GetSubtreeRoots.
|
||||
func getSubtreeRoots(_ request: GetSubtreeRootsArg) -> AsyncThrowingStream<SubtreeRoot, Error>
|
||||
|
||||
func getTreeState(_ id: BlockID) async throws -> TreeState
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// UnspentTransactionOutputRepository.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 12/11/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol UnspentTransactionOutputRepository {
|
||||
func initialise() async throws
|
||||
func getAll(address: String?) async throws -> [UnspentTransactionOutputEntity]
|
||||
func store(utxos: [UnspentTransactionOutputEntity]) async throws
|
||||
func clearAll(address: String?) async throws
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2430000",
|
||||
"hash": "0000000000125ca44e9fcf4cc3dcca62ea97fe57675de5f4eefff14019da9c59",
|
||||
"time": 1710019165,
|
||||
"saplingTree": "0180dc86263144bf0578faef14fe255918b3daa7f6575e3219a45f84c58aadb565001a00013f49b7b4e8dab0710793531f5b17721583682b01b3ddf268b1a1683fbff52506015523cd9344fe1ef83e00b455846e900a1b5deb413cb6a55103358e5b5294222d00000159eca2abc6624a885838426a0b57bf19334e730f02d2496365c5aac0f81a6c0901ce005d7ec39cdff90a74856266d2a1a299651274faa215522ee152facc7296670000010f496f195cbf0b25829ab6822834a2aff38ef2ddabc4554b3c743c0b4e9277320001330ac2cf864d6286bc980e689309f15471c479d5ce5349e47329459c834f8b0e0178f9d1c5155d10cb72343cdba0308148656de82cacf5665a8d8bd13b7c9a932500000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "0156a89f3f7f90d5f432cda7fff64aebdf9985682f367ea3487a39a214519cd42a0148de740daab68ea109932f8fa094c0c366fed18b1303a4dd21477a0f319d762e1f0143c1a60d9f30927b79e9cb6c811d5d51bbd35618d126e0af5bef10510c5b541c0131343cbd09085b36a3c659324542f02edbbe0c15c1f112267e075f1d6c57c4200001716145dbe0fc2ba71a8d9ad9441fbff828b681a93f6396be7b07698428c7af1800000100306d1f79c74ca0425b6c4885f58e72a4d696489e05d4ed223cded613f3040c000141b0bc00892aa321cef46ca03e784f6c521834e77944776244b76c3ab566fc2f01c27e5a58efba5a37af4d6b5e9838249f972672372a6b9a85346be924e64e5d1c000133ebf1946b8daef9af45b0105f8ddc87f406da6552ce60cb837e9b6323df8e020001dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2432500",
|
||||
"hash": "00000000006bab4115f085ee76d0db4f993a10be75d234718b13fdff118f592d",
|
||||
"time": 1710207121,
|
||||
"saplingTree": "01f801cbf5eb3c278d735c90c3d016d86d781521c85d23f094da5b95bd7f5e7601014efb10c23ae510a46478c2a8effacd37bb695d8e5eb23d7a2b45b406c585e1621a0001c031081d455edcccc55e591c8c2047f59ee6b17fac5d60922a5dab1615e65f41018ea8aac2c80557b07c35844b0a5f994511975dde8ae2a93a78799f2d5bb01f0a01d777f1bb4e1c42d6888a983d8443c203d9d91c193c391e7b18ce15161a34ba5c0001954cc72390c04c20e0f4b6d9af7f370fc7341985f60f95d6bc894ee5bdea8572019f85951f4496c4a96844f9de83606be8a9aa09c0d5f26aa1393a98505398fc4200000001592739b2ff134389decac775f21d3aa2a0d67168e3a45add204fb70e25115d6201330ac2cf864d6286bc980e689309f15471c479d5ce5349e47329459c834f8b0e0178f9d1c5155d10cb72343cdba0308148656de82cacf5665a8d8bd13b7c9a932500000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "0131bcbe72d425937ecf12a1b1422712c41b7db05ce31d628055c60fad4a3cba19001f0000000001b5d9ddd00c0a0e0152884719008b94484a34e23596e12fe227941268eb1a5f2001614a42b07e005bb98d37927ad44a63b8683ed4dccb9ff5953ff1c8d356598a2a01c9e9f49555ca08e75c03b3c8be117042bfbd04642437c01ce740428dead53c2d012661350c44f41f1adf0ee682fbda495de5d5b228cb063acd360d9ffe18188b390000019b11e54b92195de900cea1584a17b7238cabdb2605c04202ff900405eab301220133ebf1946b8daef9af45b0105f8ddc87f406da6552ce60cb837e9b6323df8e020001dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2435000",
|
||||
"hash": "00000000009bd2c651d8ae1c17d68044331d1fc31706e319653a6987f7ebf6ec",
|
||||
"time": 1710395535,
|
||||
"saplingTree": "0173b53d084813832ff9f2bb94af02621366d65cf5cb6199b7efc4bb37a4f9b72d014502f1327e3618b1358faf277fb1ceae01350ac12decaeba47150536cc04a3641a01e5e8fa0ca6544b3e2c6963efa86ad4013c8bff3e534a8f2c18fac7e8fcc643640103bde549304b260340bac0123c6defc3c8038e23e54161803ff10e7bfbf2e523013a7b35839d4c73f402370a538ef270a3a560ae93171f7395c65e6fa39381254901a549da8bea62adb1667f4ff4c67d0563d32e31b66f261430bd743952c126626e01026de477715a88c4129cddd86ef5c2c08b644a604a3aa6bcf5a3a1b4d15d2f4f019152f8070c760fbeadfbd65c5123aacd3f6d5cf8e4d2a127b4689d2085b714460188fd1ca704f246922548a7fe0a31ea0d9fc59a8a1d65775173dd226dc89f336a000001c2fdb92d6aa7abfa3412e7e064391ac65080907fe6e6277b48c290f58825da3a01592739b2ff134389decac775f21d3aa2a0d67168e3a45add204fb70e25115d6201330ac2cf864d6286bc980e689309f15471c479d5ce5349e47329459c834f8b0e0178f9d1c5155d10cb72343cdba0308148656de82cacf5665a8d8bd13b7c9a932500000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "012106c64514bac7ec475a8a3d956d1b252914cfd5b8e9a7e9a31e45c7b3706a38001f013875f1718df2e5825687dbd3c34e42506c4229032859a54404089bee053bfe37016fa782e38939d93fbbd1ec50d5e01e2381fbdc7ea453cd1aee635d62c40e8b200001e405453d111dc46d380c5ad29b8e28949f56a0f6c9e78265816441f392fac9230001bf291a84889c2f239a02cc81fc4851dad66d2c05f078fa3dfabf8bde4c0e840900015820a68951f0c8c823490b8fff96bf4964a2e0a87a823edcbadce0086480cd3800019b71db011e11c95751c9a7c91bf62b9a92f665e3067924281647a93b614c7327019b11e54b92195de900cea1584a17b7238cabdb2605c04202ff900405eab301220133ebf1946b8daef9af45b0105f8ddc87f406da6552ce60cb837e9b6323df8e020001dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2437500",
|
||||
"hash": "0000000001660722f6b67144e7f46c6839bcf186fd271e9d8a2c9ee52c507141",
|
||||
"time": 1710584940,
|
||||
"saplingTree": "01d933677fe8d163bdd3d862ac9630ec9767a9a8df71720d565bd6cba48f2d8d5d011e1c64cc7ce901d49d02585d9321611cfc7491a8d3922e6414b57fb72abd20421a01414c13577c3343a61ba87c0cd147a813aa802505e9ebf69260a7d2d445498c540000010cd4f39dfe5d870f200d8170677fa72cd61589eaf27c9a3f643de0e5f17cee0d016c53ad3a61d97ec6459d1ccdfd305bb0954e6b5242b52579f4c58b3a448f380301384a3a811c1f78c671187e2124e06a0f3f3aa35f725917d92b9b3b557621e72e01ebc8afd1ee40675e5f7390e7943d196d7d4157185c888ef6c01f2d8b178a777000000000000001e3644c42c5d7ecd832a2c662dcd397824930ebe37b37bc61ec5af84f724c2571000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "010f988985056887ac24576fb86acddff6c668c69f72160aaeebc5bbfd8fc3f60c01dbdd7a0fea320e6902a517af8e719befd858e8aab433275f72ff8099dfaa09141f0000017d84bd6e8417ae9c490b51635ee59175611d6223b530ed8b4cc301bb836e4b35014b6da84ed7616c286ae41932575be5a18b22c4a650376426a4a3c450b13d542801786e7a1e8da6e69955ce02a79a121354eb11530ae5fb40b4340e17ce0ea5093d0000000000000001b317414af14283ac9ab63130ea6700eda06ec5f0702aa83a4b168a0e4eba773d01dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2440000",
|
||||
"hash": "00000000018aa1792888e7f52aaa4a491f9fbc8b2b3cbf2f7c61bce55925fe1a",
|
||||
"time": 1710773380,
|
||||
"saplingTree": "01fb1ad6730d051bdce7784d776ffff105824535c01bf4542d76b924198a69704a011f5c0e0f6e2c9bc44c22261c15dec4ef3810faf7491769a8e055a5000278650f1a015df95e48fc0ad9928390fd6c3ae09902c837fbaee45b4c603b88f67fdb5d962a0001d3918aa18fa1e5d1d7fd19f19ad8439d724c4d5c19a980c7761c688a3289810201494d3878deb5d888ceb5686851f72473527e8f8e4ce04fcbeae9df0c02d90301000000000001686eb402fba0131374aefd1da61e310a7b378c7b3085c0216234b0c9073fa32c00000001e3644c42c5d7ecd832a2c662dcd397824930ebe37b37bc61ec5af84f724c2571000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "0147a6f9d105380685acaf06121f4a4e73255cf306c7489f47e4637d4bce0e350201d6de40cf0031826655d3f106cd06b0ecf624d37040e38ec3c06fbd55547fda251f00012b8bc0c564cd98f599d12a041ef1d914cbe56d9d9208485d5ebe9ccb580bc60201072a949f5f31122af6b80e1e62396506d6a5cbabf08345481e221e55ad05f23b0000013a8bd26f29c49b7c8f0aa02ef6f95284b145e1b281eaf357e0042f83a0430624000163efdc4ca36aec225b76e1a60a69faf152d119f78afc277979696b91ab1ec62d012d2d92046864657193f8957b2a17382634c4197cd6de95d316e721260c99193300000001b317414af14283ac9ab63130ea6700eda06ec5f0702aa83a4b168a0e4eba773d01dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2442500",
|
||||
"hash": "0000000000e01a9fdf001f3e7ce37f97297dfda65565cee2a5647e1dabf6e58d",
|
||||
"time": 1710961233,
|
||||
"saplingTree": "013c37d2664456cbe5c5e48c64dfea53f515ecfa2348b8f8d059f18fb2b622d5200126cc3c024b182b258d5ac3f1bb6c8047ff3d21a5f90d757e29badd8b9e2e120c1a000187b7910243390d4a57950e3726c20cc6196c9f3de6cc35ddeb05cf3a91b3b22300010d5f518de07315aa39a8f19c8ec857fc701f1b1d036acb57080868e4d23b94190001096a541cee2d3c64952753d284a757ce67d4ca13ac9b66d89d046c4b62d0e91a01d9ce6bbf0494ab1efa765d32e6f0431e6aded3fa205070aa19531aa7d1bffa06017190666f6837d3449d345f4077c8e6aa5b53a0e0c52c002f1047215e98d269060145acf941b65c7966c03a210db51db76756886d6f3c6671b9ed2df37db53ab74701686eb402fba0131374aefd1da61e310a7b378c7b3085c0216234b0c9073fa32c00000001e3644c42c5d7ecd832a2c662dcd397824930ebe37b37bc61ec5af84f724c2571000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01a27c9df6e1009152c5db2fffc3856f7c8cadb89a09f0833c96c1dfa2fc99cf08015ec5a59c4dff47ede70f4cbfc27d7227ee6c0938d8443c8306f64a3df13d56311f01f62d858afe1f4d37fa975b07789c65b1ef16e67cb2d4301990d190fd05da4e3d000198f59f323fdb55dc69ce636f9055de6cf4d0e6110343f747455c2cb9e417563b0001091ca87e38804d889c942f751df05c73c66c898cdf612cd32fc1d0924acf8113000107d607af6e0d90e878cab9c443ccc3c2ffb2d1787ff4a98cf393cb41bf4314330001636f6be10fb282255101364ff07e3d36455ca24a681654340b185edfb3d5f92f019b57450e5ceaa159777d6ba338f6932d560e5e1d0fb7e0b9f6851e4aeff6d220000001b317414af14283ac9ab63130ea6700eda06ec5f0702aa83a4b168a0e4eba773d01dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2445000",
|
||||
"hash": "000000000032ba3efc3e8c1c10a6dad5ddc4db01889b5cf8c3b83287c00bc2ae",
|
||||
"time": 1711150054,
|
||||
"saplingTree": "01efe79fb76d25d5b8aa76362acfa7adc18c52768681b30bffa8957d7dd1952d17001a00000000000001809e9ee7a27e8dcca3be914d59ae9c6aa569381ab12ec681cd94755169b83a2f01c1e8caa49f92f0b9998e63055056856ed7c243f3c4b01d079a7c400e73dbee6e016a4e2d8a2667d5f784a6ad1837e0c294884b4277daa35487510c95236aee69070001661a4760490eaebe48d77a020c789562c029750e16c01cfd195b960a1d7f9248000001e3644c42c5d7ecd832a2c662dcd397824930ebe37b37bc61ec5af84f724c2571000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "01e6226f66172d9b52de8f3efd1722cf3d9de4878df0ca0230ddf95f87e1e5ae31001f0001c949193b4d40d7b25181b2885dbb0ee41bf0bc53b37ac2f1cbddd6e00ceca4100001c2220a8a49fdb47edadd405a43039ca69c9158fde74ccbb5c6838464cc563b260001f46913c82ab9854fbb2a2207cca3e397a8f23903c0be58e3d26536e1b08b110b01c12cf5c3940490167a7753ede643275e47e137902c9287b1be84ca962da9f310010b4d4cb8f25e5ac4228aaa6b84ca38ce7e1563c26a594bcc7944794bce41fa2e000001818f8b592633baf0d32d20b6bdf8fc1f0383a20c95f949fdf871466c288789140001b317414af14283ac9ab63130ea6700eda06ec5f0702aa83a4b168a0e4eba773d01dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "2447500",
|
||||
"hash": "00000000012db8b5bdd6c44c6fbeeee7b24bbb7f14a214cbf678510a59cadf3f",
|
||||
"time": 1711338099,
|
||||
"saplingTree": "0121c86d5e6ffa93ef5a3734a836b560782a6e700129e8b9feb9052042fb87be2a012f9ac3bdebbbe0387ec38dba15faeabac24e6d136a5f2d8f8a9a811c596adc3c1a0001d876c965043694af1e4e8413d8aeea1e53517b3959796eb93b3dcfdba2b60168018dd52ff0b5454c74f69c84cf4ef73f2370811ff361cdf3f170f3700f0c3d110e0180955ea26df3668f83bac3a8f2961db643070703421e9132385c8ad43a9ea2460000010f6dbc8b6394ebb5950a4fd6b5796771ce2f6ed93aee9c44b0066cb994a137560001895d803e6d1a96ffed6e66c8d5b1f9611b330bcf3d8c77fc828619f239236610010ebeeae59b247f37eeb6aa57dc2e52f4b965c69864809b1f6cafcd0534d0221601661a4760490eaebe48d77a020c789562c029750e16c01cfd195b960a1d7f9248000001e3644c42c5d7ecd832a2c662dcd397824930ebe37b37bc61ec5af84f724c2571000132c525343fc4ebe79ab6515e9d9fceb916d920394bad5926a1afe7f46badef420001d1b36bbba8e6e1be8f09baf2b829bafc4ccd89ad25fb730d2b8a995b60fc3a6701d8ccea507421a590ed38116b834189cdc22421b4764179fa4e364803dfa66d56018cf6f5034a55fed59d1d832620916a43c756d2379e50ef9440fc4c3e7a29aa2300011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
|
||||
"orchardTree": "019a450901c143c43ea51cdbcf8b6de8b5a8d05f6d55845cb5f15d50663f819217015be7f0cffa838a09fa378bf699d218fc7674dc7bd99a525261adca4bfe9e002e1f00010a7ae37530c3b644773eebafaf8a9a15e46d7bbeed3e902b084529978976e53900017baad349203e05ed63da22471b555136cfd8bed26590ac9f7826442c568bc923000000012288799495392e86b155ddd18069b8eafb2402a33af2a1b5b16093a0c48bfa2d0001c81341a707a972d0f388802e56c99c58257eae60b6dbd44b537e70d097579a3301818f8b592633baf0d32d20b6bdf8fc1f0383a20c95f949fdf871466c288789140001b317414af14283ac9ab63130ea6700eda06ec5f0702aa83a4b168a0e4eba773d01dc8f099e807208a39136acdae660ffd7fa2c9fca740c623f73c152cf2958563d0001d386508c9fabdc60836bfe3c7251fcbdd4617180a804d40fa29dc25fb9c0aa3401cf3bf92f69798e68555548afcce1648add1fb2548d64fa9a1ec22a3e26e7890101e637281deb58dff0c44ba13149b784a95da1b493005efd057e6f4ac20ef5d81d000001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2750000",
|
||||
"hash": "0032ee34dff008317a8b7078198e3e62a30d41b041bc821c07f2481d93fbcb59",
|
||||
"time": 1710015877,
|
||||
"saplingTree": "01dd6271359272245882abb3e2bb9d5500d92153adcff74977dcd8d00b38bb280c01cd5d69cb79696e6183abb1884a64aeecbdc6636daed5760b0c3dbb8239c8616e100000000000016dd9892b9c2e5daf14b579e607b515374d5b29f44bfd1eda0cc44902e7ce4d11016526e08f5ea3f5e89c8434a0b0249642b86354dc00c32f0c104e802a0e0eaf6000000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01e8865e5c0fec206805c50225066d28461cec4791744c8a4269fff48caf8c563f015f41071dacad598328cdb0603154ed656b9ec65d00295cf93afc6a392c666f241f0171152098d19f906049dfd98c777a5ccd5f9d3f8b80d4ced4dac79d125a0a3e13013488aaf56dde9d60d789c8bfc346baf09cd209070ded9d15f2d455cc86c44b040000000001fcb318566b2a3c4812c1ac1ffeb99226c2c653094bedf9608cf409ffc54e18200172316ba203fdc9df151f6101a287f203befcd8bdecac5cf43a2f421d6a467f080000011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2760000",
|
||||
"hash": "001e7783656e6d9801bbd49483da9798b321626ebdf606993a5c68b40f2e636f",
|
||||
"time": 1710643466,
|
||||
"saplingTree": "0137552d8794f50ca3e6d8c04b79641c61531137a36e0c16797c7b4042dcfb2922001001efd0586745505fa5c1358a4976f0511cb08d32f808ce45e0874ce84bc03767010001b73c4685a80e900cc7f3f31b0808ae5e31f744a934bf530870f762daaf55a6440114fdc3f78cb10a1daa80e89f40d4834e7964e7fbe0f12cd3d346a23739d3eb2600016dd9892b9c2e5daf14b579e607b515374d5b29f44bfd1eda0cc44902e7ce4d11016526e08f5ea3f5e89c8434a0b0249642b86354dc00c32f0c104e802a0e0eaf6000000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01def56c50751a5c5fbdc117450890b01c1bf37fa05daa9e1d318a09213080b70201980479154da09ad5cdd7b184a66b89cb544c9b46d83cfa4e9da11a5e0d935d0a1f000000000001061089b21464cd2d65ac65d509f398014d89f00a996ec0807a1da1f2ac88cc0d01fcb318566b2a3c4812c1ac1ffeb99226c2c653094bedf9608cf409ffc54e18200172316ba203fdc9df151f6101a287f203befcd8bdecac5cf43a2f421d6a467f080000011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2770000",
|
||||
"hash": "00065215d6079640ddf94ac7414130f35efabc9d24c9fa8feefa86030f5e5dcf",
|
||||
"time": 1711329173,
|
||||
"saplingTree": "01df4ab40e947423f0d6e527bdf8f4bfb33bd9487d28632b1480dc454a51174f600010014af622a06b0138e40355cfb8905dcb0d3a24d8e0793d886763b0c53275b75b1b0001668c0700dbe3d5efa57cc583bd2ff8a0e547b31044f216f11f02e0982d74fe2e0000010c96e4cc8a6a80fba0d41e4eb3070d80769104dc33fb61133b1304c15bf9e23e000107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01071ec35db52f66ce2ae4a131ba489adae60f7d028b928111abdcaa5784c597320150da3f6787f2d6ae4f48892554efb2e64d26b4e56f3d6ffe819770bee8ee851e1f0000000001c4ac2643a1b48b270cebdc0d99da3f8f4e52bcd6a81b2a681919661e91c6610901061089b21464cd2d65ac65d509f398014d89f00a996ec0807a1da1f2ac88cc0d01fcb318566b2a3c4812c1ac1ffeb99226c2c653094bedf9608cf409ffc54e18200172316ba203fdc9df151f6101a287f203befcd8bdecac5cf43a2f421d6a467f080000011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
|
||||
}
|
|
@ -49,6 +49,31 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
|||
}
|
||||
}
|
||||
|
||||
func listAccounts() async throws -> [Int32] {
|
||||
globalDBLock.lock()
|
||||
let accountsPtr = zcashlc_list_accounts(
|
||||
dbData.0,
|
||||
dbData.1,
|
||||
networkType.networkId
|
||||
)
|
||||
globalDBLock.unlock()
|
||||
|
||||
guard let accountsPtr else {
|
||||
throw ZcashError.rustListAccounts(lastErrorMessage(fallback: "`listAccounts` failed with unknown error"))
|
||||
}
|
||||
|
||||
defer { zcashlc_free_accounts(accountsPtr) }
|
||||
|
||||
var accounts: [Int32] = []
|
||||
|
||||
for i in (0 ..< Int(accountsPtr.pointee.len)) {
|
||||
let account = accountsPtr.pointee.ptr.advanced(by: i).pointee
|
||||
accounts.append(Int32(account.account_index))
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
func createAccount(seed: [UInt8], treeState: TreeState, recoverUntil: UInt32?) async throws -> UnifiedSpendingKey {
|
||||
var rUntil: Int64 = -1
|
||||
|
||||
|
@ -80,6 +105,26 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
|||
return ffiBinaryKeyPtr.pointee.unsafeToUnifiedSpendingKey(network: networkType)
|
||||
}
|
||||
|
||||
func isSeedRelevantToAnyDerivedAccount(seed: [UInt8]) async throws -> Bool {
|
||||
globalDBLock.lock()
|
||||
let result = zcashlc_is_seed_relevant_to_any_derived_account(
|
||||
dbData.0,
|
||||
dbData.1,
|
||||
seed,
|
||||
UInt(seed.count),
|
||||
networkType.networkId
|
||||
)
|
||||
globalDBLock.unlock()
|
||||
|
||||
// -1 is the error sentinel.
|
||||
guard result >= 0 else {
|
||||
throw ZcashError.rustIsSeedRelevantToAnyDerivedAccount(lastErrorMessage(fallback: "`isSeedRelevantToAnyDerivedAccount` failed with unknown error"))
|
||||
}
|
||||
|
||||
// 0 is false, 1 is true.
|
||||
return result != 0
|
||||
}
|
||||
|
||||
func proposeTransfer(
|
||||
account: Int32,
|
||||
to address: String,
|
||||
|
@ -298,6 +343,8 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
|||
return DbInitResult.success
|
||||
case 1:
|
||||
return DbInitResult.seedRequired
|
||||
case 2:
|
||||
return DbInitResult.seedNotRelevant
|
||||
default:
|
||||
throw ZcashError.rustInitDataDb(lastErrorMessage(fallback: "`initDataDb` failed with unknown error"))
|
||||
}
|
||||
|
@ -589,7 +636,8 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
|||
chainTipHeight: BlockHeight(summaryPtr.pointee.chain_tip_height),
|
||||
fullyScannedHeight: BlockHeight(summaryPtr.pointee.fully_scanned_height),
|
||||
scanProgress: summaryPtr.pointee.scan_progress?.pointee.toScanProgress(),
|
||||
nextSaplingSubtreeIndex: UInt32(summaryPtr.pointee.next_sapling_subtree_index)
|
||||
nextSaplingSubtreeIndex: UInt32(summaryPtr.pointee.next_sapling_subtree_index),
|
||||
nextOrchardSubtreeIndex: UInt32(summaryPtr.pointee.next_orchard_subtree_index)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -623,9 +671,20 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
|
|||
return scanRanges
|
||||
}
|
||||
|
||||
func scanBlocks(fromHeight: Int32, limit: UInt32 = 0) async throws -> ScanSummary {
|
||||
func scanBlocks(fromHeight: Int32, fromState: TreeState, limit: UInt32 = 0) async throws -> ScanSummary {
|
||||
let fromStateBytes = try fromState.serializedData(partial: false).bytes
|
||||
|
||||
globalDBLock.lock()
|
||||
let summaryPtr = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, fromHeight, limit, networkType.networkId)
|
||||
let summaryPtr = zcashlc_scan_blocks(
|
||||
fsBlockDbRoot.0,
|
||||
fsBlockDbRoot.1,
|
||||
dbData.0,
|
||||
dbData.1,
|
||||
fromHeight,
|
||||
fromStateBytes,
|
||||
UInt(fromStateBytes.count),
|
||||
limit,
|
||||
networkType.networkId)
|
||||
globalDBLock.unlock()
|
||||
|
||||
guard let summaryPtr else {
|
||||
|
@ -841,6 +900,7 @@ extension FfiAccountBalance {
|
|||
func toAccountBalance() -> AccountBalance {
|
||||
.init(
|
||||
saplingBalance: self.sapling_balance.toPoolBalance(),
|
||||
orchardBalance: self.orchard_balance.toPoolBalance(),
|
||||
unshielded: Zatoshi(self.unshielded)
|
||||
)
|
||||
}
|
||||
|
@ -856,6 +916,7 @@ extension FfiScanProgress {
|
|||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable large_tuple line_length
|
||||
struct FfiTxId {
|
||||
var tuple: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
|
||||
var array: [UInt8] {
|
||||
|
|
|
@ -11,15 +11,21 @@ import Foundation
|
|||
enum ZcashRustBackendWeldingConstants {
|
||||
static let validChain: Int32 = -1
|
||||
}
|
||||
/// Enumeration of potential return states for database initialization. If `seedRequired`
|
||||
/// is returned, the caller must re-attempt initialization providing the seed
|
||||
|
||||
/// Enumeration of potential return states for database initialization.
|
||||
///
|
||||
/// If `seedRequired` is returned, the caller must re-attempt initialization providing the seed.
|
||||
public enum DbInitResult {
|
||||
case success
|
||||
case seedRequired
|
||||
case seedNotRelevant
|
||||
}
|
||||
|
||||
// sourcery: mockActor
|
||||
protocol ZcashRustBackendWelding {
|
||||
/// Returns a list of the accounts in the wallet.
|
||||
func listAccounts() async throws -> [Int32]
|
||||
|
||||
/// Adds the next available account-level spend authority, given the current set of [ZIP 316]
|
||||
/// account identifiers known, to the wallet database.
|
||||
///
|
||||
|
@ -41,6 +47,11 @@ protocol ZcashRustBackendWelding {
|
|||
/// - Throws: `rustCreateAccount`.
|
||||
func createAccount(seed: [UInt8], treeState: TreeState, recoverUntil: UInt32?) async throws -> UnifiedSpendingKey
|
||||
|
||||
/// Checks whether the given seed is relevant to any of the derived accounts in the wallet.
|
||||
///
|
||||
/// - parameter seed: byte array of the seed
|
||||
func isSeedRelevantToAnyDerivedAccount(seed: [UInt8]) async throws -> Bool
|
||||
|
||||
/// 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.
|
||||
|
@ -173,9 +184,10 @@ protocol ZcashRustBackendWelding {
|
|||
/// cache, an error will be signalled.
|
||||
///
|
||||
/// - parameter fromHeight: scan starting from the given height.
|
||||
/// - parameter fromState: The TreeState Protobuf object for the height prior to `fromHeight`
|
||||
/// - parameter limit: scan up to limit blocks.
|
||||
/// - Throws: `rustScanBlocks` if rust layer returns error.
|
||||
func scanBlocks(fromHeight: Int32, limit: UInt32) async throws -> ScanSummary
|
||||
func scanBlocks(fromHeight: Int32, fromState: TreeState, limit: UInt32) async throws -> ScanSummary
|
||||
|
||||
/// Upserts a UTXO into the data db database
|
||||
/// - parameter txid: the txid bytes for the UTXO
|
||||
|
|
|
@ -351,6 +351,11 @@ public protocol Synchronizer: AnyObject {
|
|||
/// - Throws: ZcashError when failures occur and related to `synchronizer.start(retry: Bool)`, it's the only throwing operation
|
||||
/// during the whole endpoint change.
|
||||
func switchTo(endpoint: LightWalletEndpoint) async throws
|
||||
|
||||
/// Checks whether the given seed is relevant to any of the derived accounts in the wallet.
|
||||
///
|
||||
/// - parameter seed: byte array of the seed
|
||||
func isSeedRelevantToAnyDerivedAccount(seed: [UInt8]) async throws -> Bool
|
||||
}
|
||||
|
||||
public enum SyncStatus: Equatable {
|
||||
|
|
|
@ -100,8 +100,7 @@ enum Dependencies {
|
|||
|
||||
static func setupCompactBlockProcessor(
|
||||
in container: DIContainer,
|
||||
config: CompactBlockProcessor.Configuration,
|
||||
accountRepository: AccountRepository
|
||||
config: CompactBlockProcessor.Configuration
|
||||
) {
|
||||
container.register(type: BlockDownloader.self, isSingleton: true) { di in
|
||||
let service = di.resolve(LightWalletService.self)
|
||||
|
@ -120,6 +119,7 @@ enum Dependencies {
|
|||
}
|
||||
|
||||
container.register(type: BlockScanner.self, isSingleton: true) { di in
|
||||
let service = di.resolve(LightWalletService.self)
|
||||
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
|
||||
let transactionRepository = di.resolve(TransactionRepository.self)
|
||||
let metrics = di.resolve(SDKMetrics.self)
|
||||
|
@ -133,6 +133,7 @@ enum Dependencies {
|
|||
return BlockScannerImpl(
|
||||
config: blockScannerConfig,
|
||||
rustBackend: rustBackend,
|
||||
service: service,
|
||||
transactionRepository: transactionRepository,
|
||||
metrics: metrics,
|
||||
logger: logger
|
||||
|
@ -163,7 +164,6 @@ enum Dependencies {
|
|||
let logger = di.resolve(Logger.self)
|
||||
|
||||
return UTXOFetcherImpl(
|
||||
accountRepository: accountRepository,
|
||||
blockDownloaderService: blockDownloaderService,
|
||||
config: utxoFetcherConfig,
|
||||
rustBackend: rustBackend,
|
||||
|
|
|
@ -40,7 +40,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
public let network: ZcashNetwork
|
||||
private let transactionEncoder: TransactionEncoder
|
||||
private let transactionRepository: TransactionRepository
|
||||
private let utxoRepository: UnspentTransactionOutputRepository
|
||||
|
||||
private let syncSessionIDGenerator: SyncSessionIDGenerator
|
||||
private let syncSession: SyncSession
|
||||
|
@ -55,7 +54,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
initializer: initializer,
|
||||
transactionEncoder: WalletTransactionEncoder(initializer: initializer),
|
||||
transactionRepository: initializer.transactionRepository,
|
||||
utxoRepository: UTXORepositoryBuilder.build(initializer: initializer),
|
||||
blockProcessor: CompactBlockProcessor(
|
||||
initializer: initializer,
|
||||
walletBirthdayProvider: { initializer.walletBirthday }
|
||||
|
@ -69,7 +67,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
initializer: Initializer,
|
||||
transactionEncoder: TransactionEncoder,
|
||||
transactionRepository: TransactionRepository,
|
||||
utxoRepository: UnspentTransactionOutputRepository,
|
||||
blockProcessor: CompactBlockProcessor,
|
||||
syncSessionTicker: SessionTicker
|
||||
) {
|
||||
|
@ -78,7 +75,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
self.initializer = initializer
|
||||
self.transactionEncoder = transactionEncoder
|
||||
self.transactionRepository = transactionRepository
|
||||
self.utxoRepository = utxoRepository
|
||||
self.blockProcessor = blockProcessor
|
||||
self.network = initializer.network
|
||||
self.metrics = initializer.container.resolve(SDKMetrics.self)
|
||||
|
@ -137,8 +133,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
throw error
|
||||
}
|
||||
|
||||
try await utxoRepository.initialise()
|
||||
|
||||
if case .seedRequired = try await self.initializer.initialize(with: seed, walletBirthday: walletBirthday, for: walletMode) {
|
||||
return .seedRequired
|
||||
}
|
||||
|
@ -309,8 +303,8 @@ public class SDKSynchronizer: Synchronizer {
|
|||
uri,
|
||||
accountIndex: accountIndex
|
||||
)
|
||||
} catch ZcashError.rustCreateToAddress(let e) {
|
||||
throw ZcashError.rustProposeTransferFromURI(e)
|
||||
} catch ZcashError.rustCreateToAddress(let error) {
|
||||
throw ZcashError.rustProposeTransferFromURI(error)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
|
@ -502,25 +496,6 @@ public class SDKSynchronizer: Synchronizer {
|
|||
try await blockProcessor.latestHeight()
|
||||
}
|
||||
|
||||
public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] {
|
||||
try throwIfUnprepared()
|
||||
|
||||
guard initializer.isValidTransparentAddress(address) else {
|
||||
throw ZcashError.synchronizerLatestUTXOsInvalidTAddress
|
||||
}
|
||||
|
||||
let stream = initializer.lightWalletService.fetchUTXOs(for: address, height: network.constants.saplingActivationHeight)
|
||||
|
||||
// swiftlint:disable:next array_constructor
|
||||
var utxos: [UnspentTransactionOutputEntity] = []
|
||||
for try await transactionEntity in stream {
|
||||
utxos.append(transactionEntity)
|
||||
}
|
||||
try await self.utxoRepository.clearAll(address: address)
|
||||
try await self.utxoRepository.store(utxos: utxos)
|
||||
return utxos
|
||||
}
|
||||
|
||||
public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs {
|
||||
try throwIfUnprepared()
|
||||
return try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
|
||||
|
@ -628,7 +603,11 @@ public class SDKSynchronizer: Synchronizer {
|
|||
|
||||
return subject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
public func isSeedRelevantToAnyDerivedAccount(seed: [UInt8]) async throws -> Bool {
|
||||
try await initializer.rustBackend.isSeedRelevantToAnyDerivedAccount(seed: seed)
|
||||
}
|
||||
|
||||
// MARK: Server switch
|
||||
|
||||
public func switchTo(endpoint: LightWalletEndpoint) async throws {
|
||||
|
@ -692,8 +671,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
// CompactBlockProcessor dependency update
|
||||
Dependencies.setupCompactBlockProcessor(
|
||||
in: initializer.container,
|
||||
config: await blockProcessor.config,
|
||||
accountRepository: initializer.accountRepository
|
||||
config: await blockProcessor.config
|
||||
)
|
||||
|
||||
// INITIALIZER
|
||||
|
|
|
@ -28,6 +28,10 @@ class AdvancedReOrgTests: ZcashTestCase {
|
|||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
// don't use an exact birthday, users never do.
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
|
|
|
@ -26,6 +26,10 @@ class BalanceTests: ZcashTestCase {
|
|||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
walletBirthday: birthday,
|
||||
|
|
|
@ -10,7 +10,7 @@ import XCTest
|
|||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class BlockDownloaderTests: XCTestCase {
|
||||
class BlockDownloaderTests: ZcashTestCase {
|
||||
let branchID = "2bb40e60"
|
||||
let chainName = "main"
|
||||
|
||||
|
@ -21,10 +21,14 @@ class BlockDownloaderTests: XCTestCase {
|
|||
var storage: CompactBlockRepository!
|
||||
var network = DarksideWalletDNetwork()
|
||||
var rustBackend: ZcashRustBackendWelding!
|
||||
var testTempDirectory: URL!
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
testTempDirectory = Environment.uniqueTestTempDirectory
|
||||
|
||||
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default).make()
|
||||
|
|
|
@ -27,6 +27,10 @@ class DarksideSanityCheckTests: ZcashTestCase {
|
|||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock (type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
walletBirthday: birthday,
|
||||
|
|
|
@ -315,6 +315,7 @@ class PaymentURIFulfillmentTests: ZcashTestCase {
|
|||
*/
|
||||
|
||||
let memo = "VGhpcyBpcyBhIHNpbXBsZSBtZW1vLg" // "This is a simple memo."
|
||||
// swiftlint:disable:next line_length
|
||||
let paymentURI = "zcash:zecIsGreat17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a?amount=0.0002&memo=\(memo)&message=Thank%20you%20for%20your%20purchase&label=Your%20Purchase"
|
||||
|
||||
do {
|
||||
|
|
|
@ -25,6 +25,10 @@ class PendingTransactionUpdatesTest: ZcashTestCase {
|
|||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
walletBirthday: birthday,
|
||||
|
|
|
@ -44,6 +44,10 @@ class ReOrgTests: ZcashTestCase {
|
|||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
walletBirthday: self.birthday,
|
||||
|
|
|
@ -25,6 +25,10 @@ class ShieldFundsTests: ZcashTestCase {
|
|||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
walletBirthday: birthday,
|
||||
|
|
|
@ -30,6 +30,11 @@ class SynchronizerDarksideTests: ZcashTestCase {
|
|||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
let idGenerator = MockSyncSessionIDGenerator(ids: [.deadbeef])
|
||||
mockContainer.mock(type: SyncSessionIDGenerator.self, isSingleton: false) { _ in idGenerator }
|
||||
self.idGenerator = idGenerator
|
||||
|
|
|
@ -26,7 +26,11 @@ final class SynchronizerTests: ZcashTestCase {
|
|||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
// don't use an exact birthday, users never do.
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
|
|
|
@ -28,6 +28,10 @@ class Z2TReceiveTests: ZcashTestCase {
|
|||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
mockContainer.mock(type: CheckpointSource.self, isSingleton: true) { _ in
|
||||
return DarksideMainnetCheckpointSource()
|
||||
}
|
||||
|
||||
self.coordinator = try await TestCoordinator(
|
||||
container: mockContainer,
|
||||
walletBirthday: birthday,
|
||||
|
|
|
@ -638,7 +638,7 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testGetTransparentBalanceSucceed() {
|
||||
let expectedBalance = AccountBalance(saplingBalance: .zero, unshielded: Zatoshi(200))
|
||||
let expectedBalance = AccountBalance(saplingBalance: .zero, orchardBalance: .zero, unshielded: Zatoshi(200))
|
||||
|
||||
synchronizerMock.getAccountBalanceAccountIndexClosure = { receivedAccountIndex in
|
||||
XCTAssertEqual(receivedAccountIndex, 3)
|
||||
|
@ -685,7 +685,13 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
|
|||
PoolBalance(
|
||||
spendableValue: Zatoshi(333),
|
||||
changePendingConfirmation: .zero,
|
||||
valuePendingSpendability: .zero), unshielded: .zero
|
||||
valuePendingSpendability: .zero),
|
||||
orchardBalance:
|
||||
PoolBalance(
|
||||
spendableValue: Zatoshi(333),
|
||||
changePendingConfirmation: .zero,
|
||||
valuePendingSpendability: .zero),
|
||||
unshielded: .zero
|
||||
)
|
||||
|
||||
synchronizerMock.getAccountBalanceAccountIndexClosure = { receivedAccountIndex in
|
||||
|
@ -733,7 +739,13 @@ class ClosureSynchronizerOfflineTests: XCTestCase {
|
|||
PoolBalance(
|
||||
spendableValue: .zero,
|
||||
changePendingConfirmation: Zatoshi(333),
|
||||
valuePendingSpendability: .zero), unshielded: .zero
|
||||
valuePendingSpendability: .zero),
|
||||
orchardBalance:
|
||||
PoolBalance(
|
||||
spendableValue: .zero,
|
||||
changePendingConfirmation: Zatoshi(333),
|
||||
valuePendingSpendability: .zero),
|
||||
unshielded: .zero
|
||||
)
|
||||
|
||||
synchronizerMock.getAccountBalanceAccountIndexClosure = { receivedAccountIndex in
|
||||
|
|
|
@ -748,7 +748,7 @@ class CombineSynchronizerOfflineTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testGetTransparentBalanceSucceed() {
|
||||
let expectedBalance = AccountBalance(saplingBalance: .zero, unshielded: Zatoshi(100))
|
||||
let expectedBalance = AccountBalance(saplingBalance: .zero, orchardBalance: .zero, unshielded: Zatoshi(100))
|
||||
|
||||
synchronizerMock.getAccountBalanceAccountIndexClosure = { receivedAccountIndex in
|
||||
XCTAssertEqual(receivedAccountIndex, 3)
|
||||
|
@ -809,6 +809,11 @@ class CombineSynchronizerOfflineTests: XCTestCase {
|
|||
spendableValue: .zero,
|
||||
changePendingConfirmation: Zatoshi(333),
|
||||
valuePendingSpendability: .zero),
|
||||
orchardBalance:
|
||||
PoolBalance(
|
||||
spendableValue: .zero,
|
||||
changePendingConfirmation: Zatoshi(333),
|
||||
valuePendingSpendability: .zero),
|
||||
unshielded: .zero
|
||||
)
|
||||
|
||||
|
@ -871,6 +876,11 @@ class CombineSynchronizerOfflineTests: XCTestCase {
|
|||
spendableValue: Zatoshi(333),
|
||||
changePendingConfirmation: .zero,
|
||||
valuePendingSpendability: .zero),
|
||||
orchardBalance:
|
||||
PoolBalance(
|
||||
spendableValue: Zatoshi(333),
|
||||
changePendingConfirmation: .zero,
|
||||
valuePendingSpendability: .zero),
|
||||
unshielded: .zero
|
||||
)
|
||||
|
||||
|
|
|
@ -29,10 +29,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testCallPrepareWithAlreadyUsedAliasThrowsError() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
let firstTestCoordinator = try await TestCoordinator(
|
||||
alias: .custom("alias"),
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -40,7 +43,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
let secondTestCoordinator = try await TestCoordinator(
|
||||
alias: .custom("alias"),
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -58,10 +61,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testWhenSynchronizerIsDeallocatedAliasIsntUsedAnymore() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
var testCoordinator: TestCoordinator! = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -75,7 +81,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
testCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -88,10 +94,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testCallWipeWithAlreadyUsedAliasThrowsError() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
let firstTestCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -99,7 +108,7 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
let secondTestCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -146,10 +155,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testPrepareCanBeCalledAfterWipeWithSameInstanceOfSDKSynchronizer() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
let testCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -180,10 +192,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testSendToAddressCalledWithoutPrepareThrowsError() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
let testCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -205,10 +220,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testShieldFundsCalledWithoutPrepareThrowsError() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
let testCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -229,10 +247,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testRefreshUTXOCalledWithoutPrepareThrowsError() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
let testCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
@ -249,10 +270,13 @@ class SynchronizerOfflineTests: ZcashTestCase {
|
|||
}
|
||||
|
||||
func testRewindCalledWithoutPrepareThrowsError() async throws {
|
||||
// Pick a testnet height for which both Sapling and Orchard are active.
|
||||
let walletBirthday = 1900000
|
||||
|
||||
let testCoordinator = try await TestCoordinator(
|
||||
alias: .default,
|
||||
container: mockContainer,
|
||||
walletBirthday: 10,
|
||||
walletBirthday: walletBirthday,
|
||||
network: network,
|
||||
callPrepareInConstructor: false
|
||||
)
|
||||
|
|
|
@ -32,7 +32,7 @@ final class UnifiedTypecodesTests: XCTestCase {
|
|||
|
||||
let typecodes = try DerivationTool(networkType: .testnet).receiverTypecodesFromUnifiedAddress(address)
|
||||
|
||||
XCTAssertEqual(typecodes, [.sapling, .p2pkh])
|
||||
XCTAssertEqual(typecodes, [.orchard, .sapling, .p2pkh])
|
||||
}
|
||||
|
||||
func testUnifiedAddressHasTransparentSaplingReceivers() throws {
|
||||
|
@ -51,6 +51,7 @@ final class UnifiedTypecodesTests: XCTestCase {
|
|||
XCTAssertEqual(
|
||||
Set<UnifiedAddress.ReceiverTypecodes>(typecodes),
|
||||
Set([
|
||||
.orchard,
|
||||
.sapling,
|
||||
.p2pkh
|
||||
])
|
||||
|
|
|
@ -126,6 +126,11 @@ class ZcashRustBackendTests: XCTestCase {
|
|||
XCTFail("not enough transparent receivers")
|
||||
return
|
||||
}
|
||||
|
||||
// The first address in the wallet is created when the account is created, using
|
||||
// the default receivers specified inside `zcash_client_sqlite`. The remaining
|
||||
// addresses are generated here, using the receivers specified in the Swift SDK's
|
||||
// FFI backend.
|
||||
var uAddresses: [UnifiedAddress] = []
|
||||
for i in 0...2 {
|
||||
uAddresses.append(
|
||||
|
|
|
@ -190,6 +190,10 @@ class DarksideWalletService: LightWalletService {
|
|||
func getSubtreeRoots(_ request: ZcashLightClientKit.GetSubtreeRootsArg) -> AsyncThrowingStream<ZcashLightClientKit.SubtreeRoot, Error> {
|
||||
service.getSubtreeRoots(request)
|
||||
}
|
||||
|
||||
func getTreeState(_ id: BlockID) async throws -> TreeState {
|
||||
try await service.getTreeState(id)
|
||||
}
|
||||
}
|
||||
|
||||
enum DarksideWalletDConstants: NetworkConstants {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// TestingCheckpoints.swift
|
||||
//
|
||||
//
|
||||
// Created by Francisco Gindre on 2023-10-31.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
struct DarksideMainnetCheckpointSource: CheckpointSource {
|
||||
private let treeState = Checkpoint(
|
||||
height: 663150,
|
||||
hash: "0000000002fd3be4c24c437bd22620901617125ec2a3a6c902ec9a6c06f734fc",
|
||||
time: 1576821833,
|
||||
saplingTree: "01ec6278a1bed9e1b080fd60ef50eb17411645e3746ff129283712bc4757ecc833001001b4e1d4a26ac4a2810b57a14f4ffb69395f55dde5674ecd2462af96f9126e054701a36afb68534f640938bdffd80dfcb3f4d5e232488abbf67d049b33a761e7ed6901a16e35205fb7fe626a9b13fc43e1d2b98a9c241f99f93d5e93a735454073025401f5b9bcbf3d0e3c83f95ee79299e8aeadf30af07717bda15ffb7a3d00243b58570001fa6d4c2390e205f81d86b85ace0b48f3ce0afb78eeef3e14c70bcfd7c5f0191c0000011bc9521263584de20822f9483e7edb5af54150c4823c775b2efc6a1eded9625501a6030f8d4b588681eddb66cad63f09c5c7519db49500fc56ebd481ce5e903c22000163f4eec5a2fe00a5f45e71e1542ff01e937d2210c99f03addcce5314a5278b2d0163ab01f46a3bb6ea46f5a19d5bdd59eb3f81e19cfa6d10ab0fd5566c7a16992601fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13",
|
||||
orchardTree: nil
|
||||
)
|
||||
|
||||
var network: NetworkType {
|
||||
DarksideWalletDNetwork().networkType
|
||||
}
|
||||
|
||||
var saplingActivation: Checkpoint {
|
||||
treeState
|
||||
}
|
||||
|
||||
func latestKnownCheckpoint() -> Checkpoint {
|
||||
treeState
|
||||
}
|
||||
|
||||
func birthday(for height: BlockHeight) -> Checkpoint {
|
||||
treeState
|
||||
}
|
||||
}
|
|
@ -82,4 +82,8 @@ class MockLightWalletService: LightWalletService {
|
|||
func getSubtreeRoots(_ request: ZcashLightClientKit.GetSubtreeRootsArg) -> AsyncThrowingStream<ZcashLightClientKit.SubtreeRoot, Error> {
|
||||
service.getSubtreeRoots(request)
|
||||
}
|
||||
|
||||
func getTreeState(_ id: BlockID) async throws -> TreeState {
|
||||
try await service.getTreeState(id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -963,6 +963,30 @@ class LightWalletServiceMock: LightWalletService {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - getTreeState
|
||||
|
||||
var getTreeStateThrowableError: Error?
|
||||
var getTreeStateCallsCount = 0
|
||||
var getTreeStateCalled: Bool {
|
||||
return getTreeStateCallsCount > 0
|
||||
}
|
||||
var getTreeStateReceivedId: BlockID?
|
||||
var getTreeStateReturnValue: TreeState!
|
||||
var getTreeStateClosure: ((BlockID) async throws -> TreeState)?
|
||||
|
||||
func getTreeState(_ id: BlockID) async throws -> TreeState {
|
||||
if let error = getTreeStateThrowableError {
|
||||
throw error
|
||||
}
|
||||
getTreeStateCallsCount += 1
|
||||
getTreeStateReceivedId = id
|
||||
if let closure = getTreeStateClosure {
|
||||
return try await closure(id)
|
||||
} else {
|
||||
return getTreeStateReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
class LightWalletdInfoMock: LightWalletdInfo {
|
||||
|
||||
|
@ -1776,6 +1800,30 @@ class SynchronizerMock: Synchronizer {
|
|||
try await switchToEndpointClosure!(endpoint)
|
||||
}
|
||||
|
||||
// MARK: - isSeedRelevantToAnyDerivedAccount
|
||||
|
||||
var isSeedRelevantToAnyDerivedAccountSeedThrowableError: Error?
|
||||
var isSeedRelevantToAnyDerivedAccountSeedCallsCount = 0
|
||||
var isSeedRelevantToAnyDerivedAccountSeedCalled: Bool {
|
||||
return isSeedRelevantToAnyDerivedAccountSeedCallsCount > 0
|
||||
}
|
||||
var isSeedRelevantToAnyDerivedAccountSeedReceivedSeed: [UInt8]?
|
||||
var isSeedRelevantToAnyDerivedAccountSeedReturnValue: Bool!
|
||||
var isSeedRelevantToAnyDerivedAccountSeedClosure: (([UInt8]) async throws -> Bool)?
|
||||
|
||||
func isSeedRelevantToAnyDerivedAccount(seed: [UInt8]) async throws -> Bool {
|
||||
if let error = isSeedRelevantToAnyDerivedAccountSeedThrowableError {
|
||||
throw error
|
||||
}
|
||||
isSeedRelevantToAnyDerivedAccountSeedCallsCount += 1
|
||||
isSeedRelevantToAnyDerivedAccountSeedReceivedSeed = seed
|
||||
if let closure = isSeedRelevantToAnyDerivedAccountSeedClosure {
|
||||
return try await closure(seed)
|
||||
} else {
|
||||
return isSeedRelevantToAnyDerivedAccountSeedReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
class TransactionRepositoryMock: TransactionRepository {
|
||||
|
||||
|
@ -2213,6 +2261,37 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
|
|||
self.consensusBranchIdForHeightClosure = consensusBranchIdForHeightClosure
|
||||
}
|
||||
|
||||
// MARK: - listAccounts
|
||||
|
||||
var listAccountsThrowableError: Error?
|
||||
func setListAccountsThrowableError(_ param: Error?) async {
|
||||
listAccountsThrowableError = param
|
||||
}
|
||||
var listAccountsCallsCount = 0
|
||||
var listAccountsCalled: Bool {
|
||||
return listAccountsCallsCount > 0
|
||||
}
|
||||
var listAccountsReturnValue: [Int32]!
|
||||
func setListAccountsReturnValue(_ param: [Int32]) async {
|
||||
listAccountsReturnValue = param
|
||||
}
|
||||
var listAccountsClosure: (() async throws -> [Int32])?
|
||||
func setListAccountsClosure(_ param: (() async throws -> [Int32])?) async {
|
||||
listAccountsClosure = param
|
||||
}
|
||||
|
||||
func listAccounts() async throws -> [Int32] {
|
||||
if let error = listAccountsThrowableError {
|
||||
throw error
|
||||
}
|
||||
listAccountsCallsCount += 1
|
||||
if let closure = listAccountsClosure {
|
||||
return try await closure()
|
||||
} else {
|
||||
return listAccountsReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - createAccount
|
||||
|
||||
var createAccountSeedTreeStateRecoverUntilThrowableError: Error?
|
||||
|
@ -2246,6 +2325,39 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - isSeedRelevantToAnyDerivedAccount
|
||||
|
||||
var isSeedRelevantToAnyDerivedAccountSeedThrowableError: Error?
|
||||
func setIsSeedRelevantToAnyDerivedAccountSeedThrowableError(_ param: Error?) async {
|
||||
isSeedRelevantToAnyDerivedAccountSeedThrowableError = param
|
||||
}
|
||||
var isSeedRelevantToAnyDerivedAccountSeedCallsCount = 0
|
||||
var isSeedRelevantToAnyDerivedAccountSeedCalled: Bool {
|
||||
return isSeedRelevantToAnyDerivedAccountSeedCallsCount > 0
|
||||
}
|
||||
var isSeedRelevantToAnyDerivedAccountSeedReceivedSeed: [UInt8]?
|
||||
var isSeedRelevantToAnyDerivedAccountSeedReturnValue: Bool!
|
||||
func setIsSeedRelevantToAnyDerivedAccountSeedReturnValue(_ param: Bool) async {
|
||||
isSeedRelevantToAnyDerivedAccountSeedReturnValue = param
|
||||
}
|
||||
var isSeedRelevantToAnyDerivedAccountSeedClosure: (([UInt8]) async throws -> Bool)?
|
||||
func setIsSeedRelevantToAnyDerivedAccountSeedClosure(_ param: (([UInt8]) async throws -> Bool)?) async {
|
||||
isSeedRelevantToAnyDerivedAccountSeedClosure = param
|
||||
}
|
||||
|
||||
func isSeedRelevantToAnyDerivedAccount(seed: [UInt8]) async throws -> Bool {
|
||||
if let error = isSeedRelevantToAnyDerivedAccountSeedThrowableError {
|
||||
throw error
|
||||
}
|
||||
isSeedRelevantToAnyDerivedAccountSeedCallsCount += 1
|
||||
isSeedRelevantToAnyDerivedAccountSeedReceivedSeed = seed
|
||||
if let closure = isSeedRelevantToAnyDerivedAccountSeedClosure {
|
||||
return try await closure(seed)
|
||||
} else {
|
||||
return isSeedRelevantToAnyDerivedAccountSeedReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - decryptAndStoreTransaction
|
||||
|
||||
var decryptAndStoreTransactionTxBytesMinedHeightThrowableError: Error?
|
||||
|
@ -2761,34 +2873,34 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
|
|||
|
||||
// MARK: - scanBlocks
|
||||
|
||||
var scanBlocksFromHeightLimitThrowableError: Error?
|
||||
func setScanBlocksFromHeightLimitThrowableError(_ param: Error?) async {
|
||||
scanBlocksFromHeightLimitThrowableError = param
|
||||
var scanBlocksFromHeightFromStateLimitThrowableError: Error?
|
||||
func setScanBlocksFromHeightFromStateLimitThrowableError(_ param: Error?) async {
|
||||
scanBlocksFromHeightFromStateLimitThrowableError = param
|
||||
}
|
||||
var scanBlocksFromHeightLimitCallsCount = 0
|
||||
var scanBlocksFromHeightLimitCalled: Bool {
|
||||
return scanBlocksFromHeightLimitCallsCount > 0
|
||||
var scanBlocksFromHeightFromStateLimitCallsCount = 0
|
||||
var scanBlocksFromHeightFromStateLimitCalled: Bool {
|
||||
return scanBlocksFromHeightFromStateLimitCallsCount > 0
|
||||
}
|
||||
var scanBlocksFromHeightLimitReceivedArguments: (fromHeight: Int32, limit: UInt32)?
|
||||
var scanBlocksFromHeightLimitReturnValue: ScanSummary!
|
||||
func setScanBlocksFromHeightLimitReturnValue(_ param: ScanSummary) async {
|
||||
scanBlocksFromHeightLimitReturnValue = param
|
||||
var scanBlocksFromHeightFromStateLimitReceivedArguments: (fromHeight: Int32, fromState: TreeState, limit: UInt32)?
|
||||
var scanBlocksFromHeightFromStateLimitReturnValue: ScanSummary!
|
||||
func setScanBlocksFromHeightFromStateLimitReturnValue(_ param: ScanSummary) async {
|
||||
scanBlocksFromHeightFromStateLimitReturnValue = param
|
||||
}
|
||||
var scanBlocksFromHeightLimitClosure: ((Int32, UInt32) async throws -> ScanSummary)?
|
||||
func setScanBlocksFromHeightLimitClosure(_ param: ((Int32, UInt32) async throws -> ScanSummary)?) async {
|
||||
scanBlocksFromHeightLimitClosure = param
|
||||
var scanBlocksFromHeightFromStateLimitClosure: ((Int32, TreeState, UInt32) async throws -> ScanSummary)?
|
||||
func setScanBlocksFromHeightFromStateLimitClosure(_ param: ((Int32, TreeState, UInt32) async throws -> ScanSummary)?) async {
|
||||
scanBlocksFromHeightFromStateLimitClosure = param
|
||||
}
|
||||
|
||||
func scanBlocks(fromHeight: Int32, limit: UInt32) async throws -> ScanSummary {
|
||||
if let error = scanBlocksFromHeightLimitThrowableError {
|
||||
func scanBlocks(fromHeight: Int32, fromState: TreeState, limit: UInt32) async throws -> ScanSummary {
|
||||
if let error = scanBlocksFromHeightFromStateLimitThrowableError {
|
||||
throw error
|
||||
}
|
||||
scanBlocksFromHeightLimitCallsCount += 1
|
||||
scanBlocksFromHeightLimitReceivedArguments = (fromHeight: fromHeight, limit: limit)
|
||||
if let closure = scanBlocksFromHeightLimitClosure {
|
||||
return try await closure(fromHeight, limit)
|
||||
scanBlocksFromHeightFromStateLimitCallsCount += 1
|
||||
scanBlocksFromHeightFromStateLimitReceivedArguments = (fromHeight: fromHeight, fromState: fromState, limit: limit)
|
||||
if let closure = scanBlocksFromHeightFromStateLimitClosure {
|
||||
return try await closure(fromHeight, fromState, limit)
|
||||
} else {
|
||||
return scanBlocksFromHeightLimitReturnValue
|
||||
return scanBlocksFromHeightFromStateLimitReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,8 +102,8 @@ class RustBackendMockHelper {
|
|||
try await rustBackend.suggestScanRanges()
|
||||
}
|
||||
|
||||
await rustBackendMock.setScanBlocksFromHeightLimitClosure() { fromHeight, limit in
|
||||
try await rustBackend.scanBlocks(fromHeight: fromHeight, limit: limit)
|
||||
await rustBackendMock.setScanBlocksFromHeightFromStateLimitClosure { fromHeight, fromState, limit in
|
||||
try await rustBackend.scanBlocks(fromHeight: fromHeight, fromState: fromState, limit: limit)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ extension SynchronizerState {
|
|||
static var mock: SynchronizerState {
|
||||
SynchronizerState(
|
||||
syncSessionID: .nullID,
|
||||
accountBalance: AccountBalance(saplingBalance: .zero, unshielded: Zatoshi(200)),
|
||||
accountBalance: AccountBalance(saplingBalance: .zero, orchardBalance: .zero, unshielded: Zatoshi(200)),
|
||||
internalSyncStatus: .syncing(0),
|
||||
latestBlockHeight: 222222
|
||||
)
|
||||
|
|
|
@ -63,6 +63,8 @@ enum TestDbBuilder {
|
|||
case .success: return provider
|
||||
case .seedRequired:
|
||||
throw ZcashError.compactBlockProcessorDataDbInitFailed("Seed value required to initialize the wallet database")
|
||||
case .seedNotRelevant:
|
||||
throw ZcashError.compactBlockProcessorDataDbInitFailed("Relevant seed value required")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -169,9 +169,9 @@ public let testVector: [[Any?]] =
|
|||
[
|
||||
["From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/unified_address.py"],
|
||||
["p2pkh_bytes, p2sh_bytes, sapling_raw_addr, orchard_raw_addr, unknown_typecode, unknown_bytes, unified_addr, root_seed, account, diversifier_index"],
|
||||
["7bb83570b8fae146e03c5331a020b1e0892f631d", nil, "d8ef8293d26de832e7193f296ba1922d90f122c6135bc231eebd91efdb03b1a8606771cd4fd6480574d43e", nil, nil, nil, "u1l8xunezsvhq8fgzfl7404m450nwnd76zshscn6nfys7vyz2ywyh4cc5daaq0c7q2su5lqfh23sp7fkf3kt27ve5948mzpfdvckzaect2jtte308mkwlycj2u0eac077wu70vqcetkxf", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 0],
|
||||
["a7244a362f49f29644a955cf0039b88a61657861", nil, "435b0bbc95b5b7d52531a3944f2b85603ee22aaf850963bc156eb561edf2cbe7cf0e770e393ae5d7049026", nil, nil, nil, "u1fl5mprj0t9p4jg92hjjy8q5myvwc60c9wv0xachauqpn3c3k4xwzlaueafq27dcg7tzzzaz5jl8tyj93wgs983y0jq0qfhzu6n4r8rakpv5f4gg2lrw4z6pyqqcrcqx04d38yunc6je", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 3],
|
||||
["e256dcb03e05dde7c91212b47a7461311c415059", nil, "69a25a38699708e5f6e76e54e6a7a2ab84dcf288df0d1f2563670168d6c44ace0ef11155c60d5c225e9dec", nil, nil, nil, "u1qxqf8ctkxlsdh7xdcgkdtyw4mku7dxma8tsz45xd6ttgs322gdk7kazg3sdn52z7na3tzcrzf7lt3xrdtfp9d4pccderalchvvxk8hghduxrky5guzqlw65fmgp6x7aj4k8v5jkgwuw", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 4],
|
||||
["7bb83570b8fae146e03c5331a020b1e0892f631d", nil, "d8ef8293d26de832e7193f296ba1922d90f122c6135bc231eebd91efdb03b1a8606771cd4fd6480574d43e", nil, nil, nil, "u1m2p65qdnhexpfcmejjf6hjqd485xwfsrlf3cvc6wx5xhwrcq8myrdlqyhdpklz5d87ct0epfty0cr9d6q9fqtycx0j3vdhc2tzmkeejhtdqj3zrjqk3dd5ufpqkmueg89e6a6alvpaaxcx4fxsxqk4yj7g8dayn94d3afrsx66m3vq4rk03hc0ufmtkcm8wca7xja42f9kjau9708z7", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 0],
|
||||
["a7244a362f49f29644a955cf0039b88a61657861", nil, "435b0bbc95b5b7d52531a3944f2b85603ee22aaf850963bc156eb561edf2cbe7cf0e770e393ae5d7049026", nil, nil, nil, "u196qfugjlzacex3pnhj5hgkj7ppjxeg3egcwkn364rnt59nx7p2f4pp35h4v677hla3negz4w0quy9ul6an2azqv0lt7f5n4dz5pspk0uvhkqfft4ntarat6mf0zfc46s5rsvmrd63lvnp2y8kyyes2r0k439de6je9ugy85qkq9ldc7hkkvjav69gzyfl5sen8kfxk3gyhjpzn39xj0", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 3],
|
||||
["e256dcb03e05dde7c91212b47a7461311c415059", nil, "69a25a38699708e5f6e76e54e6a7a2ab84dcf288df0d1f2563670168d6c44ace0ef11155c60d5c225e9dec", nil, nil, nil, "u1vs0kanmdm4vtp2jy5lfsfspty0l7umph3ny6v9xwqpamvw2adshzsh24ukvc2hamwd8wky7sfalzv4szttsrss26m5yvxnh2r0h9u5vtksr0puhw89h7gm202h5qhn53cm2pt9uj7cwnymmgp26jxe9c0lt5z3egvl03hmdrg6r923t5xudetg2f6kzzn5h6xhaye8wmufnguaj8pws", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0, 4],
|
||||
["cad268758c5e71493066446b98e71df9d1d6a5ca", nil, "9f6e0bf90a18fc0b9b83ae9f23ad4358648638482b5def8975635b66fd8a708335f9235a3186ec0f033f84", "cecbe5e689a453a3fe10ccf7617e6c1fb382819d7fc9200a1f42092ac84a30378f8c1fb90dff71a6d5042d", nil, nil, "u1pg2aaph7jp8rpf6yhsza25722sg5fcn3vaca6ze27hqjw7jvvhhuxkpcg0ge9xh6drsgdkda8qjq5chpehkcpxf87rnjryjqwymdheptpvnljqqrjqzjwkc2ma6hcq666kgwfytxwac8eyex6ndgr6ezte66706e3vaqrd25dzvzkc69kw0jgywtd0cmq52q5lkw6uh7hyvzjse8ksx", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 3],
|
||||
["8d653347a0fd3cd0842a790a5eaf89d8e3854659", nil, "e1adf156a07d56bcac91bdb2f7bb3ea7c44569dcfee54273c09e8065807b6823faa94a77219554d0f6e017", "24f8a60cbd97e012618d56054ad39241411a28fdd50ee35efa91152f60d5fa21172e5d458ddbcb6b709896", nil, nil, "u19mzuf4l37ny393m59v4mxx4t3uyxkh7qpqjdfvlfk9f504cv9w4fpl7cql0kqvssz8jay8mgl8lnrtvg6yzh9pranjj963acc3h2z2qt7007du0lsmdf862dyy40c3wmt0kq35k5z836tfljgzsqtdsccchayfjpygqzkx24l77ga3ngfgskqddyepz8we7ny4ggmt7q48cgvgu57mz", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 7],
|
||||
["e511f439b5f96cf824cd5e0e6b2eb8ee1bc83cb7", nil, "60ba572f8e379312d86897025decdd64b4b95e2c4afa9d13726b8cc393edb4988c51b976028f890f108bd2", "1f24294ed1b405c7b3b1c3f13db5b9b27b5d0f2aca9d589a69e5be00eb978621e6776e87ea326d47a34c1a", nil, nil, "u1mtxw5nras5glkxz093282sv3n2h8qs7cpxcmmaxj96vtzjzl6rmdaxs4e9es7mxwmd0h3k5wz3ce4ll5g4jz2pn9su4pufq74pxhp4t235n6j7aed3hh8ss7pf3sekf7apsf6vtg84ue5zcq2k9q3xv5yth3q50fu4czdm8sn8q4de3m5k76g2vwwyjsf50hqfxgmwxqxu0rsy22ktw", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 1, 8],
|
||||
|
|
Loading…
Reference in New Issue