package cash.z.ecc.android.sdk.demoapp.demos.getblockrange import android.os.Bundle import android.view.LayoutInflater import android.view.View import androidx.core.text.HtmlCompat import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment import cash.z.ecc.android.sdk.demoapp.R import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext import cash.z.ecc.android.sdk.demoapp.type.fromResources import cash.z.ecc.android.sdk.demoapp.util.mainActivity import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime import cash.z.ecc.android.sdk.demoapp.util.withCommas import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.ZcashNetwork import kotlin.math.max /** * Retrieves a range of compact block from the lightwalletd service and displays basic information * about them. This demonstrates the basic ability to connect to the server, request a range of * compact block and parse the response. This could be augmented to display metadata about certain * block ranges for instance, to find the block with the most shielded transactions in a range. */ class GetBlockRangeFragment : BaseDemoFragment() { private fun setBlockRange(blockRange: ClosedRange) { val start = System.currentTimeMillis() val blocks = lightWalletService?.getBlockRange(blockRange) val fetchDelta = System.currentTimeMillis() - start // Note: This is a demo so we won't worry about iterating efficiently over these blocks // Note: Converting the blocks sequence to a list can consume a lot of memory and may // cause OOM. binding.textInfo.text = HtmlCompat.fromHtml( blocks?.toList()?.run { val count = size val emptyCount = count { it.vtxCount == 0 } val maxTxs = maxByOrNull { it.vtxCount } val maxIns = maxByOrNull { block -> block.vtxList.maxOfOrNull { it.spendsCount } ?: -1 } val maxInTx = maxIns?.vtxList?.maxByOrNull { it.spendsCount } val maxOuts = maxByOrNull { block -> block.vtxList.maxOfOrNull { it.outputsCount } ?: -1 } val maxOutTx = maxOuts?.vtxList?.maxByOrNull { it.outputsCount } val txCount = sumOf { it.vtxCount } val outCount = sumOf { block -> block.vtxList.sumOf { it.outputsCount } } val inCount = sumOf { block -> block.vtxList.sumOf { it.spendsCount } } val processTime = System.currentTimeMillis() - start - fetchDelta @Suppress("MaxLineLength", "MagicNumber") """ total blocks: ${count.withCommas()}
fetch time: ${if (fetchDelta > 1000) "%.2f sec".format(fetchDelta / 1000.0) else "%d ms".format(fetchDelta)}
process time: ${if (processTime > 1000) "%.2f sec".format(processTime / 1000.0) else "%d ms".format(processTime)}
block time range: ${first().time.toRelativeTime(requireApplicationContext())}
   to ${last().time.toRelativeTime(requireApplicationContext())}
total empty blocks: ${emptyCount.withCommas()}
total TXs: ${txCount.withCommas()}
total outputs: ${outCount.withCommas()}
total inputs: ${inCount.withCommas()}
avg TXs/block: ${"%.1f".format(txCount / count.toDouble())}
avg TXs (excluding empty blocks): ${"%.1f".format(txCount.toDouble() / (count - emptyCount))}
avg OUTs [per block / per TX]: ${"%.1f / %.1f".format(outCount.toDouble() / (count - emptyCount), outCount.toDouble() / txCount)}
avg INs [per block / per TX]: ${"%.1f / %.1f".format(inCount.toDouble() / (count - emptyCount), inCount.toDouble() / txCount)}
most shielded TXs: ${if (maxTxs == null) "none" else "${maxTxs.vtxCount} in block ${maxTxs.height.withCommas()}"}
most shielded INs: ${if (maxInTx == null) "none" else "${maxInTx.spendsCount} in block ${maxIns.height.withCommas()} at tx index ${maxInTx.index}"}
most shielded OUTs: ${if (maxOutTx == null) "none" else "${maxOutTx.outputsCount} in block ${maxOuts.height.withCommas()} at tx index ${maxOutTx.index}"} """.trimIndent() } ?: "No blocks found in that range.", HtmlCompat.FROM_HTML_MODE_LEGACY ) } @Suppress("UNUSED_PARAMETER") private fun onApply(unused: View) { val network = ZcashNetwork.fromResources(requireApplicationContext()) val start = max( binding.textStartHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value ) val end = max( binding.textEndHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value ) if (start <= end) { @Suppress("TooGenericExceptionCaught") try { with(binding.buttonApply) { isEnabled = false setText(R.string.loading) binding.textInfo.setText(R.string.loading) post { setBlockRange(BlockHeight.new(network, start)..BlockHeight.new(network, end)) isEnabled = true setText(R.string.apply) } } } catch (t: Throwable) { setError(t.toString()) } } else { setError("Invalid range") } mainActivity()?.hideKeyboard() } private fun setError(message: String) { binding.textInfo.text = "Error: $message" } // // Android Lifecycle overrides // override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.buttonApply.setOnClickListener(::onApply) } // // Base Fragment overrides // override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockRangeBinding = FragmentGetBlockRangeBinding.inflate(layoutInflater) override fun onActionButtonClicked() { super.onActionButtonClicked() } }