ZcashLightClientKit/Sources/ZcashLightClientKit/Utils/SaplingParameterDownloader....

160 lines
6.4 KiB
Swift

//
// SaplingParameterDownloader.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/7/20.
//
import Foundation
import CryptoKit
/// Small utility that converts Digest to the String
extension Digest {
var bytes: [UInt8] { Array(makeIterator()) }
var data: Data { Data(bytes) }
var hexString: String {
bytes.map { String(format: "%02x", $0) }.joined()
}
}
/// Helper class to handle the download of Sapling parameters
public enum SaplingParameterDownloader {
/// Download a Spend parameter from default host and stores it at given URL
/// - Parameters:
/// - at: The destination URL for the download
/// - Throws:
/// - `saplingParamsDownload` if file downloading fails.
/// - `saplingParamsCantMoveDownloadedFile` if file is downloaded but moving to final destination fails.
/// - `saplingParamsInvalidSpendParams` if the downloaded file is invalid.
@discardableResult
public static func downloadSpendParameter(_ at: URL, sourceURL: URL, logger: Logger) async throws -> URL {
let resultURL = try await downloadFileWithRequestWithContinuation(sourceURL, logger: logger, at: at)
try isSpendParamsSHA1Valid(url: resultURL)
return resultURL
}
/// Download an Output parameter from default host and stores it at given URL
/// - Parameters:
/// - at: The destination URL for the download
/// - Throws:
/// - `saplingParamsDownload` if file downloading fails.
/// - `saplingParamsCantMoveDownloadedFile` if file is downloaded but moving to final destination fails.
/// - `saplingParamsInvalidOutputParams` if the downloaded file is invalid.
@discardableResult
public static func downloadOutputParameter(_ at: URL, sourceURL: URL, logger: Logger) async throws -> URL {
let resultURL = try await downloadFileWithRequestWithContinuation(sourceURL, logger: logger, at: at)
try isOutputParamsSHA1Valid(url: resultURL)
return resultURL
}
/// Downloads the parameters if not present and provides the resulting URLs for both parameters
/// - Parameters:
/// - spendURL: URL to check whether the parameter is already downloaded
/// - outputURL: URL to check whether the parameter is already downloaded
/// - Throws:
/// - `saplingParamsDownload` if file downloading fails.
/// - `saplingParamsCantMoveDownloadedFile` if file is downloaded but moving to final destination fails.
/// - `saplingParamsInvalidSpendParams` if the downloaded file is invalid.
/// - `saplingParamsInvalidOutputParams` if the downloaded file is invalid.
@discardableResult
public static func downloadParamsIfnotPresent(
spendURL: URL,
spendSourceURL: URL,
outputURL: URL,
outputSourceURL: URL,
logger: Logger
) async throws -> (spend: URL, output: URL) {
async let spendResultURL = ensureSpendParameter(at: spendURL, sourceURL: spendSourceURL, logger: logger)
async let outputResultURL = ensureOutputParameter(at: outputURL, sourceURL: outputSourceURL, logger: logger)
let results = try await [spendResultURL, outputResultURL]
return (spend: results[0], output: results[1])
}
static func ensureSpendParameter(at url: URL, sourceURL: URL, logger: Logger) async throws -> URL {
if isFilePresent(url: url) {
try isSpendParamsSHA1Valid(url: url)
return url
} else {
return try await downloadSpendParameter(url, sourceURL: sourceURL, logger: logger)
}
}
static func ensureOutputParameter(at url: URL, sourceURL: URL, logger: Logger) async throws -> URL {
if isFilePresent(url: url) {
try isOutputParamsSHA1Valid(url: url)
return url
} else {
return try await downloadOutputParameter(url, sourceURL: sourceURL, logger: logger)
}
}
static func isFilePresent(url: URL) -> Bool {
(try? FileManager.default.attributesOfItem(atPath: url.path)) != nil
}
static func isSpendParamsSHA1Valid(url: URL) throws {
if Insecure.SHA1.hash(data: try Data(contentsOf: url)).hexString != Constants.spendParamFileSHA1 {
try? FileManager.default.removeItem(at: url)
throw ZcashError.saplingParamsInvalidSpendParams
}
}
static func isOutputParamsSHA1Valid(url: URL) throws {
if Insecure.SHA1.hash(data: try Data(contentsOf: url)).hexString != Constants.outputParamFileSHA1 {
try? FileManager.default.removeItem(at: url)
throw ZcashError.saplingParamsInvalidOutputParams
}
}
}
private extension SaplingParameterDownloader {
enum Constants {
public static let spendParamFileSHA1 = "a15ab54c2888880e53c823a3063820c728444126"
public static let outputParamFileSHA1 = "0ebc5a1ef3653948e1c46cf7a16071eac4b7e352"
}
static func downloadFileWithRequestWithContinuation(
_ sourceURL: URL,
logger: Logger,
at destination: URL
) async throws -> URL {
return try await withCheckedThrowingContinuation { continuation in
downloadFileWithRequest(sourceURL, at: destination, logger: logger) { result in
switch result {
case .success(let outputResultURL):
continuation.resume(returning: outputResultURL)
case .failure(let outputResultError):
continuation.resume(throwing: outputResultError)
}
}
}
}
static func downloadFileWithRequest(
_ sourceURL: URL,
at destination: URL,
logger: Logger,
result: @escaping (Result<URL, Error>) -> Void
) {
logger.debug("Downloading sapling file from \(sourceURL)")
let request = URLRequest(url: sourceURL)
let task = URLSession.shared.downloadTask(with: request) { url, _, error in
if let error {
result(.failure(ZcashError.saplingParamsDownload(error, sourceURL)))
return
} else if let localUrl = url {
do {
try FileManager.default.moveItem(at: localUrl, to: destination)
result(.success(destination))
} catch {
result(.failure(ZcashError.saplingParamsCantMoveDownloadedFile(error, sourceURL, destination)))
}
}
}
task.resume()
}
}