draft

[#1165] Step 1 - Download note commitment tree data from lightwalletd

- code cleanup after draft

[#1165] Step 1 - Download note commitment tree data from lightwalletd

- UpdateSubtreeRootsAction added, ensuring the roots are downloaded and stored in the DB

[#1165] Step 1 - Download note commitment tree data from lightwalletd

- added ZcashError for putSaplingSubtreeRoots failure
- cleaned up action

[#1165] Step 1 - Download note commitment tree data from lightwalletd

- demo app config temporarily updated to Nighthawk server

[#1165] Step 1 - Download note commitment tree data from lightwalletd

- file header updated

[#1165] Step 1 - Download note commitment tree data from lightwalletd (#1174)

- demo app config cleaned up

[#1165] Step 1 - Download note commitment tree data from lightwalletd (#1174)

- offline tests fixed
This commit is contained in:
Lukas Korba 2023-07-31 08:51:04 +02:00
parent d446c6d336
commit 7694b04d42
16 changed files with 263 additions and 47 deletions

View File

@ -18,7 +18,7 @@ enum DemoAppConfig {
let seed: [UInt8]
}
static let host = ZcashSDK.isMainnet ? "lightwalletd.electriccoin.co" : "lightwalletd.testnet.electriccoin.co"
static let host = ZcashSDK.isMainnet ? "mainnet.lightwalletd.com" : "testnet.lightwalletd.com"
static let port: Int = 9067
static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000

View File

@ -34,6 +34,7 @@ enum CBPState: CaseIterable {
case idle
case migrateLegacyCacheDB
case validateServer
case updateSubtreeRoots
case computeSyncControlData
case download
case scan

View File

@ -0,0 +1,68 @@
//
// UpdateSubtreeRootsAction.swift
//
//
// Created by Lukas Korba on 01.08.2023.
//
import Foundation
final class UpdateSubtreeRootsAction {
let rustBackend: ZcashRustBackendWelding
let service: LightWalletService
let logger: Logger
init(container: DIContainer) {
service = container.resolve(LightWalletService.self)
rustBackend = container.resolve(ZcashRustBackendWelding.self)
logger = container.resolve(Logger.self)
}
}
extension UpdateSubtreeRootsAction: Action {
var removeBlocksCacheWhenFailed: Bool { false }
func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
var request = GetSubtreeRootsArg()
request.shieldedProtocol = .sapling
request.maxEntries = 65536
logger.info("Attempt to get subtree roots, this may fail because lightwalletd may not support DAG sync.")
let stream = service.getSubtreeRoots(request)
var roots: [SubtreeRoot] = []
var err: Error?
do {
for try await subtreeRoot in stream {
roots.append(subtreeRoot)
}
} catch {
logger.debug("getSubtreeRoots failed with error \(error.localizedDescription)")
err = error
}
// In case of error, the lightwalletd doesn't support DAG sync -> switching to linear sync.
// Likewise, no subtree roots results in switching to linear sync.
if err != nil || roots.isEmpty {
logger.info("DAG sync is not possible, switching to linear sync.")
await context.update(state: .computeSyncControlData)
} else {
logger.info("Sapling tree has \(roots.count) subtrees")
do {
try await rustBackend.putSaplingSubtreeRoots(startIndex: UInt64(request.startIndex), roots: roots)
// TODO: [#1167] Switching back to linear sync for now before step 3 & 4 are implemented
// https://github.com/zcash/ZcashLightClientKit/issues/1167
await context.update(state: .computeSyncControlData)
} catch {
logger.debug("putSaplingSubtreeRoots failed with error \(error.localizedDescription)")
throw ZcashError.compactBlockProcessorPutSaplingSubtreeRoots(error)
}
}
return context
}
func stop() async { }
}

View File

@ -52,7 +52,7 @@ extension ValidateServerAction: Action {
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
}
await context.update(state: .computeSyncControlData)
await context.update(state: .updateSubtreeRoots)
return context
}

View File

@ -214,6 +214,8 @@ actor CompactBlockProcessor {
action = MigrateLegacyCacheDBAction(container: container, configProvider: configProvider)
case .validateServer:
action = ValidateServerAction(container: container, configProvider: configProvider)
case .updateSubtreeRoots:
action = UpdateSubtreeRootsAction(container: container)
case .computeSyncControlData:
action = ComputeSyncControlDataAction(container: container, configProvider: configProvider)
case .download:
@ -585,6 +587,8 @@ extension CompactBlockProcessor {
break
case .validateServer:
break
case .updateSubtreeRoots:
break
case .computeSyncControlData:
break
case .download:

View File

@ -58,6 +58,9 @@ public enum ZcashError: Equatable, Error {
/// LightWalletService.blockStream failed.
/// ZSRVC0000
case serviceBlockStreamFailed(_ error: LightWalletServiceError)
/// LightWalletService.getSubtreeRoots failed.
/// ZSRVC0009
case serviceSubtreeRootsStreamFailed(_ error: LightWalletServiceError)
/// SimpleConnectionProvider init of Connection failed.
/// ZSCPC0001
case simpleConnectionProvider(_ error: Error)
@ -277,18 +280,22 @@ public enum ZcashError: Equatable, Error {
/// ZRUST0045
case rustGetTransparentReceiverInvalidReceiver
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots
/// sourcery: code="ZRUST0046"
/// ZRUST0046
case rustPutSaplingSubtreeRootsAllocationProblem
/// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0047"
/// ZRUST0047
case rustPutSaplingSubtreeRoots(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.updateChainTip
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0048"
/// ZRUST0048
case rustUpdateChainTip(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.suggestScanRanges
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0049"
/// ZRUST0049
case rustSuggestScanRanges(_ rustError: String)
/// Invalid transaction ID length when calling ZcashRustBackend.getMemo
@ -530,6 +537,9 @@ public enum ZcashError: Equatable, Error {
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
/// ZCBPEO0018
case compactBlockProcessorDownloadBlockActionRewind
/// Put sapling subtree roots to the DB failed.
/// ZCBPEO0019
case compactBlockProcessorPutSaplingSubtreeRoots(_ error: Error)
/// The synchronizer is unprepared.
/// ZSYNCO0001
case synchronizerNotPrepared
@ -566,6 +576,7 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchTransactionFailed: return "LightWalletService.fetchTransaction failed."
case .serviceFetchUTXOsFailed: return "LightWalletService.fetchUTXOs failed."
case .serviceBlockStreamFailed: return "LightWalletService.blockStream failed."
case .serviceSubtreeRootsStreamFailed: return "LightWalletService.getSubtreeRoots failed."
case .simpleConnectionProvider: return "SimpleConnectionProvider init of Connection failed."
case .saplingParamsInvalidSpendParams: return "Downloaded file with sapling spending parameters isn't valid."
case .saplingParamsInvalidOutputParams: return "Downloaded file with sapling output parameters isn't valid."
@ -623,7 +634,7 @@ public enum ZcashError: Equatable, Error {
case .rustGetSaplingReceiverInvalidReceiver: return "Sapling receiver generated by rust layer is invalid when calling ZcashRustBackend.getSaplingReceiver"
case .rustGetTransparentReceiverInvalidAddress: return "Error from rust layer when calling ZcashRustBackend.getTransparentReceiver"
case .rustGetTransparentReceiverInvalidReceiver: return "Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver"
case .rustPutSaplingSubtreeRootsAllocationProblem: return "Unable to allocate memory required to store subtree roots when calling ZcashRustBackend.putSaplingSubtreeRoots"
case .rustPutSaplingSubtreeRootsAllocationProblem: return "Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots"
case .rustPutSaplingSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots"
case .rustUpdateChainTip: return "Error from rust layer when calling ZcashRustBackend.updateChainTip"
case .rustSuggestScanRanges: return "Error from rust layer when calling ZcashRustBackend.suggestScanRanges"
@ -703,6 +714,7 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorChainName: return "Chain name does not match. Expected either 'test' or 'main'. This is probably an API or programming error."
case .compactBlockProcessorConsensusBranchID: return "Consensus BranchIDs don't match this is probably an API or programming error."
case .compactBlockProcessorDownloadBlockActionRewind: return "Rewind of DownloadBlockAction failed as no action is possible to unwrapp."
case .compactBlockProcessorPutSaplingSubtreeRoots: return "Put sapling subtree roots to the DB failed."
case .synchronizerNotPrepared: return "The synchronizer is unprepared."
case .synchronizerSendMemoToTransparentAddress: return "Memos can't be sent to transparent addresses."
case .synchronizerShieldFundsInsuficientTransparentFunds: return "There is not enough transparent funds to cover fee for the shielding."
@ -729,6 +741,7 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchTransactionFailed: return .serviceFetchTransactionFailed
case .serviceFetchUTXOsFailed: return .serviceFetchUTXOsFailed
case .serviceBlockStreamFailed: return .serviceBlockStreamFailed
case .serviceSubtreeRootsStreamFailed: return .serviceSubtreeRootsStreamFailed
case .simpleConnectionProvider: return .simpleConnectionProvider
case .saplingParamsInvalidSpendParams: return .saplingParamsInvalidSpendParams
case .saplingParamsInvalidOutputParams: return .saplingParamsInvalidOutputParams
@ -866,6 +879,7 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorChainName: return .compactBlockProcessorChainName
case .compactBlockProcessorConsensusBranchID: return .compactBlockProcessorConsensusBranchID
case .compactBlockProcessorDownloadBlockActionRewind: return .compactBlockProcessorDownloadBlockActionRewind
case .compactBlockProcessorPutSaplingSubtreeRoots: return .compactBlockProcessorPutSaplingSubtreeRoots
case .synchronizerNotPrepared: return .synchronizerNotPrepared
case .synchronizerSendMemoToTransparentAddress: return .synchronizerSendMemoToTransparentAddress
case .synchronizerShieldFundsInsuficientTransparentFunds: return .synchronizerShieldFundsInsuficientTransparentFunds

View File

@ -39,6 +39,8 @@ public enum ZcashErrorCode: String {
case serviceFetchUTXOsFailed = "ZSRVC0008"
/// LightWalletService.blockStream failed.
case serviceBlockStreamFailed = "ZSRVC0000"
/// LightWalletService.getSubtreeRoots failed.
case serviceSubtreeRootsStreamFailed = "ZSRVC0009"
/// SimpleConnectionProvider init of Connection failed.
case simpleConnectionProvider = "ZSCPC0001"
/// Downloaded file with sapling spending parameters isn't valid.
@ -313,6 +315,8 @@ public enum ZcashErrorCode: String {
case compactBlockProcessorConsensusBranchID = "ZCBPEO0017"
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
case compactBlockProcessorDownloadBlockActionRewind = "ZCBPEO0018"
/// Put sapling subtree roots to the DB failed.
case compactBlockProcessorPutSaplingSubtreeRoots = "ZCBPEO0019"
/// The synchronizer is unprepared.
case synchronizerNotPrepared = "ZSYNCO0001"
/// Memos can't be sent to transparent addresses.

View File

@ -77,6 +77,9 @@ enum ZcashErrorDefinition {
/// LightWalletService.blockStream failed.
// sourcery: code="ZSRVC0000"
case serviceBlockStreamFailed(_ error: LightWalletServiceError)
/// LightWalletService.getSubtreeRoots failed.
// sourcery: code="ZSRVC0009"
case serviceSubtreeRootsStreamFailed(_ error: LightWalletServiceError)
// MARK: SQLite connection
@ -307,6 +310,21 @@ enum ZcashErrorDefinition {
/// Transparent receiver generated by rust layer is invalid when calling ZcashRustBackend.getTransparentReceiver
// sourcery: code="ZRUST0045"
case rustGetTransparentReceiverInvalidReceiver
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putSaplingSubtreeRoots
/// sourcery: code="ZRUST0046"
case rustPutSaplingSubtreeRootsAllocationProblem
/// Error from rust layer when calling ZcashRustBackend.putSaplingSubtreeRoots
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0047"
case rustPutSaplingSubtreeRoots(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.updateChainTip
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0048"
case rustUpdateChainTip(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.suggestScanRanges
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0049"
case rustSuggestScanRanges(_ rustError: String)
// MARK: - Account DAO
@ -592,6 +610,9 @@ enum ZcashErrorDefinition {
/// Rewind of DownloadBlockAction failed as no action is possible to unwrapp.
// sourcery: code="ZCBPEO0018"
case compactBlockProcessorDownloadBlockActionRewind
/// Put sapling subtree roots to the DB failed.
// sourcery: code="ZCBPEO0019"
case compactBlockProcessorPutSaplingSubtreeRoots(_ error: Error)
// MARK: - SDKSynchronizer

View File

@ -263,6 +263,21 @@ extension LightWalletGRPCService: LightWalletService {
}
}
func getSubtreeRoots(_ request: GetSubtreeRootsArg) -> AsyncThrowingStream<SubtreeRoot, Error> {
let stream = compactTxStreamer.getSubtreeRoots(request)
var iterator = stream.makeAsyncIterator()
return AsyncThrowingStream() {
do {
guard let subtreeRoot = try await iterator.next() else { return nil }
return subtreeRoot
} catch {
let serviceError = error.mapToServiceError()
throw ZcashError.serviceSubtreeRootsStreamFailed(serviceError)
}
}
}
func closeConnection() {
_ = channel.close()
}

View File

@ -193,4 +193,11 @@ protocol LightWalletService: AnyObject {
) -> AsyncThrowingStream<ZcashCompactBlock, Error>
func closeConnection()
/// Returns a stream of information about roots of subtrees of the Sapling and Orchard
/// note commitment trees.
///
/// - Parameters:
/// - request: Request to send to GetSubtreeRoots.
func getSubtreeRoots(_ request: GetSubtreeRootsArg) -> AsyncThrowingStream<SubtreeRoot, Error>
}

View File

@ -166,6 +166,10 @@ protocol ZcashRustBackendWelding {
/// - Throws: `rustRewindCacheToHeight` if rust layer returns error.
func rewindCacheToHeight(height: Int32) async throws
func putSaplingSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws
func updateChainTip(height: Int32) async throws
/// Returns a list of suggested scan ranges based upon the current wallet state.
///
/// This method should only be used in cases where the `CompactBlock` data that will be

View File

@ -31,8 +31,8 @@ final class ValidateServerActionTests: ZcashTestCase {
let nextContext = try await validateServerAction.run(with: .init(state: .validateServer)) { _ in }
let nextState = await nextContext.state
XCTAssertTrue(
nextState == .computeSyncControlData,
"nextContext after .validateServer is expected to be .computeSyncControlData but received \(nextState)"
nextState == .updateSubtreeRoots,
"nextContext after .validateServer is expected to be .updateSubtreeRoots but received \(nextState)"
)
} catch {
XCTFail("testValidateServerAction_NextAction is not expected to fail. \(error)")

View File

@ -15,23 +15,23 @@ enum DarksideDataset: String {
case beforeReOrg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
/**
see
https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-index-reorg
*/
see
https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-index-reorg
*/
case txIndexChangeBefore = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/before-reorg.txt"
case txIndexChangeAfter = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/after-reorg.txt"
/**
See https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-height-reorg
*/
See https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-height-reorg
*/
case txHeightReOrgBefore = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-height-reorg/before-reorg.txt"
case txHeightReOrgAfter = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-height-reorg/after-reorg.txt"
/*
see: https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-remove-reorg
*/
see: https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-remove-reorg
*/
case txReOrgRemovesInboundTxBefore = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-remove-reorg/before-reorg.txt"
case txReOrgRemovesInboundTxAfter = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-remove-reorg/after-reorg.txt"
@ -111,8 +111,8 @@ class DarksideWalletService: LightWalletService {
Empty(),
handler: { txs.append($0) }
)
.status
.wait()
.status
.wait()
switch response.code {
case .ok:
@ -178,6 +178,10 @@ class DarksideWalletService: LightWalletService {
func fetchTransaction(txId: Data) async throws -> ZcashTransaction.Fetched {
try await service.fetchTransaction(txId: txId)
}
func getSubtreeRoots(_ request: ZcashLightClientKit.GetSubtreeRootsArg) -> AsyncThrowingStream<ZcashLightClientKit.SubtreeRoot, Error> {
service.getSubtreeRoots(request)
}
}
enum DarksideWalletDConstants: NetworkConstants {

View File

@ -78,4 +78,8 @@ class MockLightWalletService: LightWalletService {
func fetchTransaction(txId: Data) async throws -> ZcashTransaction.Fetched {
return ZcashTransaction.Fetched(rawID: Data(), minedHeight: -1, raw: Data())
}
func getSubtreeRoots(_ request: ZcashLightClientKit.GetSubtreeRootsArg) -> AsyncThrowingStream<ZcashLightClientKit.SubtreeRoot, Error> {
service.getSubtreeRoots(request)
}
}

View File

@ -820,6 +820,26 @@ class LightWalletServiceMock: LightWalletService {
closeConnectionClosure!()
}
// MARK: - getSubtreeRoots
var getSubtreeRootsCallsCount = 0
var getSubtreeRootsCalled: Bool {
return getSubtreeRootsCallsCount > 0
}
var getSubtreeRootsReceivedRequest: GetSubtreeRootsArg?
var getSubtreeRootsReturnValue: AsyncThrowingStream<SubtreeRoot, Error>!
var getSubtreeRootsClosure: ((GetSubtreeRootsArg) -> AsyncThrowingStream<SubtreeRoot, Error>)?
func getSubtreeRoots(_ request: GetSubtreeRootsArg) -> AsyncThrowingStream<SubtreeRoot, Error> {
getSubtreeRootsCallsCount += 1
getSubtreeRootsReceivedRequest = request
if let closure = getSubtreeRootsClosure {
return closure(request)
} else {
return getSubtreeRootsReturnValue
}
}
}
class LightWalletdInfoMock: LightWalletdInfo {
@ -2574,6 +2594,56 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
try await rewindCacheToHeightHeightClosure!(height)
}
// MARK: - putSaplingSubtreeRoots
var putSaplingSubtreeRootsStartIndexRootsThrowableError: Error?
func setPutSaplingSubtreeRootsStartIndexRootsThrowableError(_ param: Error?) async {
putSaplingSubtreeRootsStartIndexRootsThrowableError = param
}
var putSaplingSubtreeRootsStartIndexRootsCallsCount = 0
var putSaplingSubtreeRootsStartIndexRootsCalled: Bool {
return putSaplingSubtreeRootsStartIndexRootsCallsCount > 0
}
var putSaplingSubtreeRootsStartIndexRootsReceivedArguments: (startIndex: UInt64, roots: [SubtreeRoot])?
var putSaplingSubtreeRootsStartIndexRootsClosure: ((UInt64, [SubtreeRoot]) async throws -> Void)?
func setPutSaplingSubtreeRootsStartIndexRootsClosure(_ param: ((UInt64, [SubtreeRoot]) async throws -> Void)?) async {
putSaplingSubtreeRootsStartIndexRootsClosure = param
}
func putSaplingSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws {
if let error = putSaplingSubtreeRootsStartIndexRootsThrowableError {
throw error
}
putSaplingSubtreeRootsStartIndexRootsCallsCount += 1
putSaplingSubtreeRootsStartIndexRootsReceivedArguments = (startIndex: startIndex, roots: roots)
try await putSaplingSubtreeRootsStartIndexRootsClosure!(startIndex, roots)
}
// MARK: - updateChainTip
var updateChainTipHeightThrowableError: Error?
func setUpdateChainTipHeightThrowableError(_ param: Error?) async {
updateChainTipHeightThrowableError = param
}
var updateChainTipHeightCallsCount = 0
var updateChainTipHeightCalled: Bool {
return updateChainTipHeightCallsCount > 0
}
var updateChainTipHeightReceivedHeight: Int32?
var updateChainTipHeightClosure: ((Int32) async throws -> Void)?
func setUpdateChainTipHeightClosure(_ param: ((Int32) async throws -> Void)?) async {
updateChainTipHeightClosure = param
}
func updateChainTip(height: Int32) async throws {
if let error = updateChainTipHeightThrowableError {
throw error
}
updateChainTipHeightCallsCount += 1
updateChainTipHeightReceivedHeight = height
try await updateChainTipHeightClosure!(height)
}
// MARK: - suggestScanRanges
var suggestScanRangesThrowableError: Error?