MobileCoin-Swift/Sources/Fog/FogKeyImageChecker.swift
2021-05-10 19:51:21 -07:00

114 lines
4.4 KiB
Swift

//
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
//
// swiftlint:disable multiline_arguments multiline_function_chains
import Foundation
import LibMobileCoin
struct FogKeyImageChecker {
private let serialQueue: DispatchQueue
private let fogKeyImageService: FogKeyImageService
init(fogKeyImageService: FogKeyImageService, targetQueue: DispatchQueue?) {
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)", target: targetQueue)
self.fogKeyImageService = fogKeyImageService
}
func checkKeyImage(
keyImage: KeyImage,
nextKeyImageQueryBlockIndex: UInt64 = 0,
completion: @escaping (Result<KeyImage.SpentStatus, ConnectionError>) -> Void
) {
checkKeyImages(
keyImageQueries: [(keyImage, nextKeyImageQueryBlockIndex: nextKeyImageQueryBlockIndex)]
) {
completion($0.flatMap { statuses in
guard let keyImageStatus = statuses.first else {
return .failure(.invalidServerResponse(
"CheckKeyImage failed to return results: \(statuses)"))
}
return .success(keyImageStatus)
})
}
}
func checkKeyImages(
keyImageQueries: [KeyImage],
maxKeyImagesPerQuery: Int,
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
) {
checkKeyImages(
keyImageQueries: keyImageQueries.map { ($0, nextKeyImageQueryBlockIndex: 0) },
maxKeyImagesPerQuery: maxKeyImagesPerQuery,
completion: completion)
}
func checkKeyImages(
keyImageQueries: [(KeyImage, nextKeyImageQueryBlockIndex: UInt64)],
maxKeyImagesPerQuery: Int,
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
) {
let queryArrays = keyImageQueries.chunked(maxLength: maxKeyImagesPerQuery).map { Array($0) }
queryArrays.mapAsync({ chunk, callback in
checkKeyImages(keyImageQueries: chunk, completion: callback)
}, serialQueue: serialQueue, completion: { result in
completion(result.map { $0.flatMap { $0 } })
})
}
func checkKeyImages(
keyImageQueries: [KeyImage],
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
) {
checkKeyImages(
keyImageQueries: keyImageQueries.map { ($0, nextKeyImageQueryBlockIndex: 0) },
completion: completion)
}
func checkKeyImages(
keyImageQueries: [(KeyImage, nextKeyImageQueryBlockIndex: UInt64)],
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
) {
var request = FogLedger_CheckKeyImagesRequest()
request.queries = keyImageQueries.map {
var query = FogLedger_KeyImageQuery()
query.keyImage = External_KeyImage($0.0)
query.startBlock = $0.nextKeyImageQueryBlockIndex
return query
}
fogKeyImageService.checkKeyImages(request: request) {
completion($0.flatMap {
Self.parseResponse(keyImageQueries: keyImageQueries, response: $0)
})
}
}
private static func parseResponse(
keyImageQueries: [(KeyImage, nextKeyImageQueryBlockIndex: UInt64)],
response: FogLedger_CheckKeyImagesResponse
) -> Result<[KeyImage.SpentStatus], ConnectionError> {
keyImageQueries.map { query in
guard let keyImageResult = response.results.first(
where: { KeyImage($0.keyImage) == query.0 }) else
{
return .success(.unspent(knownToBeUnspentBlockCount: response.numBlocks))
}
switch keyImageResult.keyImageResultCodeEnum {
case .spent:
let spentAtBlock = BlockMetadata(
index: keyImageResult.spentAt,
timestampStatus: keyImageResult.timestampStatus)
return .success(.spent(block: spentAtBlock))
case .notSpent:
return .success(.unspent(knownToBeUnspentBlockCount: response.numBlocks))
case .keyImageError, .unused, .UNRECOGNIZED:
return .failure(.invalidServerResponse("Fog KeyImage result error: " +
"\(keyImageResult.keyImageResultCodeEnum), response: \(response)"))
}
}.collectResult()
}
}