Compare commits

...

5 Commits

Author SHA1 Message Date
Lukas Korba 8fa86ff37f
Merge pull request #1408 from LukasKorba/1406--Release-2-1-3
[#1406] Release 2.1.3
2024-03-28 16:13:43 +01:00
Lukas Korba f41ac27eb8 [#1406] Release 2.1.3
- changelog updated
2024-03-28 16:10:50 +01:00
Lukas Korba 4a16957795
Merge pull request #1407 from Electric-Coin-Company/orchard-subtree-roots
Fetch and store Orchard subtree roots
2024-03-28 16:09:08 +01:00
Lukas Korba bdfe0b6823 orchard-subtree-roots
- test testUpdateSubtreeRootsAction_RootsAvailablePutRootsSuccess fixed
2024-03-28 16:03:00 +01:00
Jack Grigg 006861595d Fetch and store Orchard subtree roots 2024-03-28 10:52:17 -04:00
12 changed files with 198 additions and 13 deletions

View File

@ -6,6 +6,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
# Unreleased
# 2.1.3 - 2024-03-28
## Fixed
- Orchard subtree roots are now fetched alongside Sapling subtree roots.
# 2.1.2 - 2024-03-27
## Fixed

View File

@ -176,8 +176,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "8838b4f0ee4193349fed09f0248220d4ada271fc",
"version" : "0.7.3"
"revision" : "e2d8763f3a963fb0026b6160af2d211b527453cd",
"version" : "0.7.4"
}
}
],

View File

@ -122,8 +122,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "c7e5158edf5e62af15492d30237163b78af35ce9",
"version" : "0.7.1"
"revision" : "e2d8763f3a963fb0026b6160af2d211b527453cd",
"version" : "0.7.4"
}
}
],

View File

@ -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.7.3")
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.7.4")
],
targets: [
.target(

View File

@ -31,26 +31,55 @@ extension UpdateSubtreeRootsAction: Action {
logger.debug("Attempt to get subtree roots, this may fail because lightwalletd may not support Spend before Sync.")
let stream = service.getSubtreeRoots(request)
var roots: [SubtreeRoot] = []
var saplingRoots: [SubtreeRoot] = []
do {
for try await subtreeRoot in stream {
roots.append(subtreeRoot)
saplingRoots.append(subtreeRoot)
}
} catch ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut) {
throw ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut)
}
logger.debug("Sapling tree has \(roots.count) subtrees")
logger.debug("Sapling tree has \(saplingRoots.count) subtrees")
do {
try await rustBackend.putSaplingSubtreeRoots(startIndex: UInt64(request.startIndex), roots: roots)
try await rustBackend.putSaplingSubtreeRoots(startIndex: UInt64(request.startIndex), roots: saplingRoots)
await context.update(state: .updateChainTip)
} catch {
logger.debug("putSaplingSubtreeRoots failed with error \(error.localizedDescription)")
throw ZcashError.compactBlockProcessorPutSaplingSubtreeRoots(error)
}
if !saplingRoots.isEmpty {
logger.debug("Found Sapling subtree roots, SbS supported, fetching Orchard subtree roots")
var orchardRequest = GetSubtreeRootsArg()
orchardRequest.shieldedProtocol = .orchard
let stream = service.getSubtreeRoots(orchardRequest)
var orchardRoots: [SubtreeRoot] = []
do {
for try await subtreeRoot in stream {
orchardRoots.append(subtreeRoot)
}
} catch ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut) {
throw ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut)
}
logger.debug("Orchard tree has \(orchardRoots.count) subtrees")
do {
try await rustBackend.putOrchardSubtreeRoots(startIndex: UInt64(orchardRequest.startIndex), roots: orchardRoots)
await context.update(state: .updateChainTip)
} catch {
logger.debug("putOrchardSubtreeRoots failed with error \(error.localizedDescription)")
throw ZcashError.compactBlockProcessorPutOrchardSubtreeRoots(error)
}
}
return context
}

View File

@ -333,6 +333,15 @@ public enum ZcashError: Equatable, Error {
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0059
case rustIsSeedRelevantToAnyDerivedAccount(_ rustError: String)
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putOrchardSubtreeRoots
/// sourcery: code="ZRUST0060"
/// ZRUST0060
case rustPutOrchardSubtreeRootsAllocationProblem
/// Error from rust layer when calling ZcashRustBackend.putOrchardSubtreeRoots
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0061"
/// ZRUST0061
case rustPutOrchardSubtreeRoots(_ rustError: String)
/// SQLite query failed when fetching all accounts from the database.
/// - `sqliteError` is error produced by SQLite library.
/// ZADAO0001
@ -578,6 +587,9 @@ public enum ZcashError: Equatable, Error {
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
/// ZCBPEO0021
case compactBlockProcessorSupportedSyncAlgorithm
/// Put Orchard subtree roots to the DB failed.
/// ZCBPEO0022
case compactBlockProcessorPutOrchardSubtreeRoots(_ error: Error)
/// The synchronizer is unprepared.
/// ZSYNCO0001
case synchronizerNotPrepared
@ -691,6 +703,8 @@ public enum ZcashError: Equatable, Error {
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 .rustPutOrchardSubtreeRootsAllocationProblem: return "Unable to allocate memory required to write blocks when calling ZcashRustBackend.putOrchardSubtreeRoots"
case .rustPutOrchardSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putOrchardSubtreeRoots"
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."
@ -769,6 +783,7 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorPutSaplingSubtreeRoots: return "Put sapling subtree roots to the DB failed."
case .compactBlockProcessorLastScannedHeight: return "Getting the `lastScannedHeight` failed but it's supposed to always provide some value."
case .compactBlockProcessorSupportedSyncAlgorithm: return "Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value."
case .compactBlockProcessorPutOrchardSubtreeRoots: return "Put Orchard 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."
@ -868,6 +883,8 @@ public enum ZcashError: Equatable, Error {
case .rustProposeTransferFromURI: return .rustProposeTransferFromURI
case .rustListAccounts: return .rustListAccounts
case .rustIsSeedRelevantToAnyDerivedAccount: return .rustIsSeedRelevantToAnyDerivedAccount
case .rustPutOrchardSubtreeRootsAllocationProblem: return .rustPutOrchardSubtreeRootsAllocationProblem
case .rustPutOrchardSubtreeRoots: return .rustPutOrchardSubtreeRoots
case .accountDAOGetAll: return .accountDAOGetAll
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
case .accountDAOFindBy: return .accountDAOFindBy
@ -946,6 +963,7 @@ public enum ZcashError: Equatable, Error {
case .compactBlockProcessorPutSaplingSubtreeRoots: return .compactBlockProcessorPutSaplingSubtreeRoots
case .compactBlockProcessorLastScannedHeight: return .compactBlockProcessorLastScannedHeight
case .compactBlockProcessorSupportedSyncAlgorithm: return .compactBlockProcessorSupportedSyncAlgorithm
case .compactBlockProcessorPutOrchardSubtreeRoots: return .compactBlockProcessorPutOrchardSubtreeRoots
case .synchronizerNotPrepared: return .synchronizerNotPrepared
case .synchronizerSendMemoToTransparentAddress: return .synchronizerSendMemoToTransparentAddress
case .synchronizerShieldFundsInsuficientTransparentFunds: return .synchronizerShieldFundsInsuficientTransparentFunds

View File

@ -181,6 +181,10 @@ public enum ZcashErrorCode: String {
case rustListAccounts = "ZRUST0058"
/// Error from rust layer when calling ZcashRustBackend.rustIsSeedRelevantToAnyDerivedAccount
case rustIsSeedRelevantToAnyDerivedAccount = "ZRUST0059"
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putOrchardSubtreeRoots
case rustPutOrchardSubtreeRootsAllocationProblem = "ZRUST0060"
/// Error from rust layer when calling ZcashRustBackend.putOrchardSubtreeRoots
case rustPutOrchardSubtreeRoots = "ZRUST0061"
/// SQLite query failed when fetching all accounts from the database.
case accountDAOGetAll = "ZADAO0001"
/// Fetched accounts from SQLite but can't decode them.
@ -337,6 +341,8 @@ public enum ZcashErrorCode: String {
case compactBlockProcessorLastScannedHeight = "ZCBPEO0020"
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
case compactBlockProcessorSupportedSyncAlgorithm = "ZCBPEO0021"
/// Put Orchard subtree roots to the DB failed.
case compactBlockProcessorPutOrchardSubtreeRoots = "ZCBPEO0022"
/// The synchronizer is unprepared.
case synchronizerNotPrepared = "ZSYNCO0001"
/// Memos can't be sent to transparent addresses.

View File

@ -360,6 +360,13 @@ enum ZcashErrorDefinition {
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0059"
case rustIsSeedRelevantToAnyDerivedAccount(_ rustError: String)
/// Unable to allocate memory required to write blocks when calling ZcashRustBackend.putOrchardSubtreeRoots
/// sourcery: code="ZRUST0060"
case rustPutOrchardSubtreeRootsAllocationProblem
/// Error from rust layer when calling ZcashRustBackend.putOrchardSubtreeRoots
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0061"
case rustPutOrchardSubtreeRoots(_ rustError: String)
// MARK: - Account DAO
@ -654,6 +661,9 @@ enum ZcashErrorDefinition {
/// Getting the `supportedSyncAlgorithm` failed but it's supposed to always provide some value.
// sourcery: code="ZCBPEO0021"
case compactBlockProcessorSupportedSyncAlgorithm
/// Put Orchard subtree roots to the DB failed.
// sourcery: code="ZCBPEO0022"
case compactBlockProcessorPutOrchardSubtreeRoots(_ error: Error)
// MARK: - SDKSynchronizer

View File

@ -571,6 +571,64 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
}
}
func putOrchardSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws {
var ffiSubtreeRootsVec: [FfiSubtreeRoot] = []
for root in roots {
let hashPtr = UnsafeMutablePointer<UInt8>.allocate(capacity: root.rootHash.count)
let contiguousHashBytes = ContiguousArray(root.rootHash.bytes)
let result: Void? = contiguousHashBytes.withContiguousStorageIfAvailable { hashBytesPtr in
// swiftlint:disable:next force_unwrapping
hashPtr.initialize(from: hashBytesPtr.baseAddress!, count: hashBytesPtr.count)
}
guard result != nil else {
defer {
hashPtr.deallocate()
ffiSubtreeRootsVec.deallocateElements()
}
throw ZcashError.rustPutOrchardSubtreeRootsAllocationProblem
}
ffiSubtreeRootsVec.append(
FfiSubtreeRoot(
root_hash_ptr: hashPtr,
root_hash_ptr_len: UInt(contiguousHashBytes.count),
completing_block_height: UInt32(root.completingBlockHeight)
)
)
}
var contiguousFfiRoots = ContiguousArray(ffiSubtreeRootsVec)
let len = UInt(contiguousFfiRoots.count)
let rootsPtr = UnsafeMutablePointer<FfiSubtreeRoots>.allocate(capacity: 1)
defer {
ffiSubtreeRootsVec.deallocateElements()
rootsPtr.deallocate()
}
try contiguousFfiRoots.withContiguousMutableStorageIfAvailable { ptr in
var roots = FfiSubtreeRoots()
roots.ptr = ptr.baseAddress
roots.len = len
rootsPtr.initialize(to: roots)
globalDBLock.lock()
let res = zcashlc_put_orchard_subtree_roots(dbData.0, dbData.1, startIndex, rootsPtr, networkType.networkId)
globalDBLock.unlock()
guard res else {
throw ZcashError.rustPutOrchardSubtreeRoots(lastErrorMessage(fallback: "`putOrchardSubtreeRoots` failed with unknown error"))
}
}
}
func updateChainTip(height: Int32) async throws {
globalDBLock.lock()
let result = zcashlc_update_chain_tip(dbData.0, dbData.1, height, networkType.networkId)

View File

@ -133,6 +133,8 @@ protocol ZcashRustBackendWelding {
func putSaplingSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws
func putOrchardSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws
/// Updates the wallet's view of the blockchain.
///
/// This method is used to provide the wallet with information about the state of the blockchain,

View File

@ -70,6 +70,7 @@ final class UpdateSubtreeRootsActionTests: ZcashTestCase {
}
}
await tupple.rustBackendMock.setPutSaplingSubtreeRootsStartIndexRootsClosure({ _, _ in })
await tupple.rustBackendMock.setPutOrchardSubtreeRootsStartIndexRootsClosure({ _, _ in })
do {
let context = ActionContextMock.default()
@ -77,13 +78,13 @@ final class UpdateSubtreeRootsActionTests: ZcashTestCase {
let nextContext = try await updateSubtreeRootsActionAction.run(with: context) { _ in }
let acResult = nextContext.checkStateIs(.updateChainTip)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
XCTAssertTrue(acResult == .called(2), "Check of state failed with '\(acResult)'")
} catch {
XCTFail("testUpdateSubtreeRootsAction_RootsAvailablePutRootsSuccess is not expected to fail. \(error)")
}
}
func testUpdateSubtreeRootsAction_RootsAvailablePutRootsFailure() async throws {
func testUpdateSubtreeRootsAction_RootsAvailablePutSaplingRootsFailure() async throws {
let loggerMock = LoggerMock()
loggerMock.infoFileFunctionLineClosure = { _, _, _, _ in }
@ -98,6 +99,7 @@ final class UpdateSubtreeRootsActionTests: ZcashTestCase {
}
}
await tupple.rustBackendMock.setPutSaplingSubtreeRootsStartIndexRootsThrowableError("putSaplingFailed")
await tupple.rustBackendMock.setPutOrchardSubtreeRootsStartIndexRootsClosure({ _, _ in })
do {
let context = ActionContextMock.default()
@ -111,7 +113,37 @@ final class UpdateSubtreeRootsActionTests: ZcashTestCase {
XCTFail("testUpdateSubtreeRootsAction_RootsAvailablePutRootsFailure is not expected to fail. \(error)")
}
}
func testUpdateSubtreeRootsAction_RootsAvailablePutOrchardRootsFailure() async throws {
let loggerMock = LoggerMock()
loggerMock.infoFileFunctionLineClosure = { _, _, _, _ in }
loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in }
let tupple = setupAction(loggerMock)
let updateSubtreeRootsActionAction = tupple.action
tupple.serviceMock.getSubtreeRootsClosure = { _ in
AsyncThrowingStream { continuation in
continuation.yield(SubtreeRoot())
continuation.finish()
}
}
await tupple.rustBackendMock.setPutSaplingSubtreeRootsStartIndexRootsClosure({ _, _ in })
await tupple.rustBackendMock.setPutOrchardSubtreeRootsStartIndexRootsThrowableError("putOrchardFailed")
do {
let context = ActionContextMock.default()
_ = try await updateSubtreeRootsActionAction.run(with: context) { _ in }
XCTFail("updateSubtreeRootsActionAction.run(with:) is excpected to fail but didn't.")
} catch ZcashError.compactBlockProcessorPutOrchardSubtreeRoots {
// this is expected result of this test
} catch {
XCTFail("testUpdateSubtreeRootsAction_RootsAvailablePutRootsFailure is not expected to fail. \(error)")
}
}
// swiftlint:disable large_tuple
private func setupAction(
_ loggerMock: LoggerMock = LoggerMock()

View File

@ -2722,6 +2722,31 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
try await putSaplingSubtreeRootsStartIndexRootsClosure!(startIndex, roots)
}
// MARK: - putOrchardSubtreeRoots
var putOrchardSubtreeRootsStartIndexRootsThrowableError: Error?
func setPutOrchardSubtreeRootsStartIndexRootsThrowableError(_ param: Error?) async {
putOrchardSubtreeRootsStartIndexRootsThrowableError = param
}
var putOrchardSubtreeRootsStartIndexRootsCallsCount = 0
var putOrchardSubtreeRootsStartIndexRootsCalled: Bool {
return putOrchardSubtreeRootsStartIndexRootsCallsCount > 0
}
var putOrchardSubtreeRootsStartIndexRootsReceivedArguments: (startIndex: UInt64, roots: [SubtreeRoot])?
var putOrchardSubtreeRootsStartIndexRootsClosure: ((UInt64, [SubtreeRoot]) async throws -> Void)?
func setPutOrchardSubtreeRootsStartIndexRootsClosure(_ param: ((UInt64, [SubtreeRoot]) async throws -> Void)?) async {
putOrchardSubtreeRootsStartIndexRootsClosure = param
}
func putOrchardSubtreeRoots(startIndex: UInt64, roots: [SubtreeRoot]) async throws {
if let error = putOrchardSubtreeRootsStartIndexRootsThrowableError {
throw error
}
putOrchardSubtreeRootsStartIndexRootsCallsCount += 1
putOrchardSubtreeRootsStartIndexRootsReceivedArguments = (startIndex: startIndex, roots: roots)
try await putOrchardSubtreeRootsStartIndexRootsClosure!(startIndex, roots)
}
// MARK: - updateChainTip
var updateChainTipHeightThrowableError: Error?