Hush SDK for Android lite wallets https://hush.is
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

140 lines
5.5 KiB

package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.ext.ZcashSdk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.buffer
import okio.sink
import java.io.File
class SaplingParamTool {
companion object {
/**
* Checks the given directory for the output and spending params and calls [fetchParams] if
* they're missing.
*
* @param destinationDir the directory where the params should be stored.
*/
suspend fun ensureParams(destinationDir: String) {
var hadError = false
arrayOf(
ZcashSdk.SPEND_PARAM_FILE_NAME,
ZcashSdk.OUTPUT_PARAM_FILE_NAME
).forEach { paramFileName ->
if (!File(destinationDir, paramFileName).existsSuspend()) {
twig("WARNING: $paramFileName not found at location: $destinationDir")
hadError = true
}
}
if (hadError) {
try {
Bush.trunk.twigTask("attempting to download missing params") {
fetchParams(destinationDir)
}
} catch (e: Throwable) {
twig("failed to fetch params due to: $e")
throw TransactionEncoderException.MissingParamsException
}
}
}
/**
* Download and store the params into the given directory.
*
* @param destinationDir the directory where the params will be stored. It's assumed that we
* have write access to this directory. Typically, this should be the app's cache directory
* because it is not harmful if these files are cleared by the user since they are downloaded
* on-demand.
*/
suspend fun fetchParams(destinationDir: String) {
val client = createHttpClient()
var failureMessage = ""
arrayOf(
ZcashSdk.SPEND_PARAM_FILE_NAME,
ZcashSdk.OUTPUT_PARAM_FILE_NAME
).forEach { paramFileName ->
val url = "${ZcashSdk.CLOUD_PARAM_DIR_URL.random()}/$paramFileName"
twig("Downloading Sapling params from ${url}...")
val request = Request.Builder().url(url).build()
val response = withContext(Dispatchers.IO) { client.newCall(request).execute() }
if (response.isSuccessful) {
twig("fetch succeeded", -1)
val file = File(destinationDir, paramFileName)
if (file.parentFile?.existsSuspend() == true) {
twig("directory exists!", -1)
} else {
twig("directory did not exist attempting to make it")
file.parentFile?.mkdirsSuspend()
}
withContext(Dispatchers.IO) {
response.body?.let { body ->
body.source().use { source ->
file.sink().buffer().use { sink ->
twig("writing to $file")
sink.writeAll(source)
}
}
}
}
} else {
failureMessage += "Error while fetching $paramFileName : $response\n"
twig(failureMessage)
}
twig("fetch succeeded, done writing $paramFileName")
}
if (failureMessage.isNotEmpty()) throw TransactionEncoderException.FetchParamsException(
failureMessage
)
}
suspend fun clear(destinationDir: String) {
if (validate(destinationDir)) {
arrayOf(
ZcashSdk.SPEND_PARAM_FILE_NAME,
ZcashSdk.OUTPUT_PARAM_FILE_NAME
).forEach { paramFileName ->
val file = File(destinationDir, paramFileName)
if (file.deleteRecursivelySuspend()) {
twig("Files deleted successfully")
} else {
twig("Error: Files not able to be deleted!")
}
}
}
}
suspend fun validate(destinationDir: String): Boolean {
return arrayOf(
ZcashSdk.SPEND_PARAM_FILE_NAME,
ZcashSdk.OUTPUT_PARAM_FILE_NAME
).all { paramFileName ->
File(destinationDir, paramFileName).existsSuspend()
}.also {
println("Param files${if (!it) "did not" else ""} both exist!")
}
}
//
// Helpers
//
/**
* Http client is only used for downloading sapling spend and output params data, which are
* necessary for the wallet to scan blocks.
*
* @return an http client suitable for downloading params data.
*/
private fun createHttpClient(): OkHttpClient {
// TODO: add logging and timeouts
return OkHttpClient()
}
}
}
suspend fun File.existsSuspend() = withContext(Dispatchers.IO) { exists() }
suspend fun File.mkdirsSuspend() = withContext(Dispatchers.IO) { mkdirs() }
suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() }