Initial commit
This commit is contained in:
10
sdk-lib/src/androidTest/AndroidManifest.xml
Normal file
10
sdk-lib/src/androidTest/AndroidManifest.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cash.z.ecc.android.sdk">
|
||||
|
||||
<!-- For code coverage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application android:name="androidx.multidex.MultiDexApplication" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": 1290000,
|
||||
"hash": "00000000014836c3cbc011276cbd3702a76a1fea7eb2c0c2c257321220376450",
|
||||
"time": 1624075741,
|
||||
"saplingTree": "01accf4fc3dc4233bbe757f94e0d4cd23b4aa2e6ac472601f4f53ca4dc86a8a05901fae977171a6103a0338990e073ffe50e29fc8bf0400dcd3378ebfe7a146ed1481300014f7b33dd5159ac66f2670b7db8925065e7154e0199ff7ee7559b276ba56ad1200173e9881f21357e54027a4275114f0f6ad4ca17143554182f63c77f3288a23a20011d65465ab942440e200d429ef892452b4b05c5b21e9a6e6d968a719c67b5e85b000000000000000150926c74975e2d8ff095defb75a4a6d9f17007e87a74230a65a3265d8f45032900012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": 1290000,
|
||||
"hash": "00000000014836c3cbc011276cbd3702a76a1fea7eb2c0c2c257321220376450",
|
||||
"time": 1624075741,
|
||||
"saplingTree": "01accf4fc3dc4233bbe757f94e0d4cd23b4aa2e6ac472601f4f53ca4dc86a8a05901fae977171a6103a0338990e073ffe50e29fc8bf0400dcd3378ebfe7a146ed1481300014f7b33dd5159ac66f2670b7db8925065e7154e0199ff7ee7559b276ba56ad1200173e9881f21357e54027a4275114f0f6ad4ca17143554182f63c77f3288a23a20011d65465ab942440e200d429ef892452b4b05c5b21e9a6e6d968a719c67b5e85b000000000000000150926c74975e2d8ff095defb75a4a6d9f17007e87a74230a65a3265d8f45032900012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": 1300000,
|
||||
"hash": "00000000027222bdbcf9c5f807f851f97312ac6e0dbbc2b93f2be21a69c59d44",
|
||||
"time": 1624830312,
|
||||
"saplingTree": "01f5a97e2679a2bb9103caf37b825f92fcd73fff836234844dfcf1815394522b2c01526587b9b9e8aeb0eb572d81fec1f5127b8278ba0f57e451bd6b796596940a2213000131c7ff90fafff6159b8fb6544a2bcbba6c102903158fce8f9a9d3c6654abb23300013555cb7f4f79badeaca9bf2dca5a8704f0929053d50e95c03002f9a4d5286c3a01ad3557e11c1607ec888dc84f5f8899c3c79fb1f50b613946452ec7dd5e53763c0001c4583f4482b949390dba355fc8fa63019c83acd644ddd633cb50211d236f870600000001088da0d78eefd0c222507927e403b972d0890d0c31e08b02268fbe39ac4a6e170001edf82d4e2b4893ea2028ca8c5149e50a4c358b856d73f2de2b9a22034fa78f22012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
103
sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt
Normal file
103
sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt
Normal file
@@ -0,0 +1,103 @@
|
||||
package cash.z.ecc.android.sdk
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class AssetTest {
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun validate_mainnet_assets() {
|
||||
val network = ZcashNetwork.Mainnet
|
||||
val assets = listAssets(network)
|
||||
|
||||
assertFilesExist(assets)
|
||||
assertFilenames(assets)
|
||||
assertFileContents(network, assets)
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun validate_testnet_assets() {
|
||||
val network = ZcashNetwork.Testnet
|
||||
val assets = listAssets(network)
|
||||
|
||||
assertFilesExist(assets)
|
||||
assertFilenames(assets)
|
||||
assertFileContents(network, assets)
|
||||
}
|
||||
|
||||
private fun assertFilesExist(files: Array<String>?) {
|
||||
assertFalse(files.isNullOrEmpty())
|
||||
}
|
||||
|
||||
private fun assertFilenames(files: Array<String>?) {
|
||||
files?.forEach {
|
||||
val split = it.split('.')
|
||||
assertEquals(2, split.size)
|
||||
|
||||
val intString = split.first()
|
||||
val extensionString = split.last()
|
||||
|
||||
// Will throw exception if cannot be parsed
|
||||
intString.toInt()
|
||||
|
||||
assertEquals("json", extensionString)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertFileContents(network: ZcashNetwork, files: Array<String>?) {
|
||||
files?.map { filename ->
|
||||
val filePath = "${WalletBirthdayTool.birthdayDirectory(network)}/$filename"
|
||||
ApplicationProvider.getApplicationContext<Context>().assets.open(filePath)
|
||||
.use { inputSteam ->
|
||||
inputSteam.bufferedReader().use { bufferedReader ->
|
||||
val slurped = bufferedReader.readText()
|
||||
|
||||
JsonFile(JSONObject(slurped), filename)
|
||||
}
|
||||
}
|
||||
}?.forEach {
|
||||
val jsonObject = it.jsonObject
|
||||
assertTrue(jsonObject.has("network"))
|
||||
assertTrue(jsonObject.has("height"))
|
||||
assertTrue(jsonObject.has("hash"))
|
||||
assertTrue(jsonObject.has("time"))
|
||||
assertTrue(jsonObject.has("saplingTree"))
|
||||
|
||||
val expectedNetworkName = when (network) {
|
||||
ZcashNetwork.Mainnet -> "main"
|
||||
ZcashNetwork.Testnet -> "test"
|
||||
}
|
||||
assertEquals("File: ${it.filename}", expectedNetworkName, jsonObject.getString("network"))
|
||||
|
||||
assertEquals(
|
||||
"File: ${it.filename}",
|
||||
WalletBirthdayTool.birthdayHeight(it.filename),
|
||||
jsonObject.getInt("height")
|
||||
)
|
||||
|
||||
// In the future, additional validation of the JSON can be added
|
||||
}
|
||||
}
|
||||
|
||||
private data class JsonFile(val jsonObject: JSONObject, val filename: String)
|
||||
|
||||
companion object {
|
||||
fun listAssets(network: ZcashNetwork) = runBlocking {
|
||||
WalletBirthdayTool.listBirthdayDirectoryContents(
|
||||
ApplicationProvider.getApplicationContext<Context>(),
|
||||
WalletBirthdayTool.birthdayDirectory(network)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cash.z.ecc.android.sdk
|
||||
|
||||
class InitializerTest {
|
||||
|
||||
// lateinit var initializer: Initializer
|
||||
//
|
||||
// @After
|
||||
// fun cleanUp() {
|
||||
// // don't leave databases sitting around after this test is run
|
||||
// if (::initializer.isInitialized) initializer.erase()
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testInit() {
|
||||
// val height = 980000
|
||||
//
|
||||
// initializer = Initializer(context) { config ->
|
||||
// config.importedWalletBirthday(height)
|
||||
// config.setViewingKeys(
|
||||
// "zxviews1qvn6j50dqqqqpqxqkvqgx2sp63jccr4k5t8zefadpzsu0yy73vczfznwc794xz6lvy3yp5ucv43lww48zz95ey5vhrsq83dqh0ky9junq0cww2wjp9c3cd45n5l5x8l2g9atnx27e9jgyy8zasjy26gugjtefphan9al3tx208m8ekev5kkx3ug6pd0qk4gq4j4wfuxajn388pfpq54wklwktqkyjz9e6gam0n09xjc35ncd3yah5aa9ezj55lk4u7v7hn0v86vz7ygq4qj2v",
|
||||
// "zxviews1qv886f6hqqqqpqy2ajg9sm22vs4gm4hhajthctfkfws34u45pjtut3qmz0eatpqzvllgsvlk3x0y35ktx5fnzqqzueyph20k3328kx46y3u5xs4750cwuwjuuccfp7la6rh8yt2vjz6tylsrwzy3khtjjzw7etkae6gw3vq608k7quka4nxkeqdxxsr9xxdagv2rhhwugs6w0cquu2ykgzgaln2vyv6ah3ram2h6lrpxuznyczt2xl3lyxcwlk4wfz5rh7wzfd7642c2ae5d7"
|
||||
// )
|
||||
// config.alias = "VkInitTest1"
|
||||
// }
|
||||
// assertEquals(height, initializer.birthday.height)
|
||||
// initializer.erase()
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testErase() {
|
||||
// val alias = "VkInitTest2"
|
||||
// initializer = Initializer(context) { config ->
|
||||
// config.importedWalletBirthday(1_419_900)
|
||||
// config.setViewingKeys(
|
||||
// "zxviews1qvn6j50dqqqqpqxqkvqgx2sp63jccr4k5t8zefadpzsu0yy73vczfznwc794xz6lvy3yp5ucv43lww48zz95ey5vhrsq83dqh0ky9junq0cww2wjp9c3cd45n5l5x8l2g9atnx27e9jgyy8zasjy26gugjtefphan9al3tx208m8ekev5kkx3ug6pd0qk4gq4j4wfuxajn388pfpq54wklwktqkyjz9e6gam0n09xjc35ncd3yah5aa9ezj55lk4u7v7hn0v86vz7ygq4qj2v",
|
||||
// "zxviews1qv886f6hqqqqpqy2ajg9sm22vs4gm4hhajthctfkfws34u45pjtut3qmz0eatpqzvllgsvlk3x0y35ktx5fnzqqzueyph20k3328kx46y3u5xs4750cwuwjuuccfp7la6rh8yt2vjz6tylsrwzy3khtjjzw7etkae6gw3vq608k7quka4nxkeqdxxsr9xxdagv2rhhwugs6w0cquu2ykgzgaln2vyv6ah3ram2h6lrpxuznyczt2xl3lyxcwlk4wfz5rh7wzfd7642c2ae5d7"
|
||||
// )
|
||||
// config.alias = alias
|
||||
// }
|
||||
//
|
||||
// assertTrue("Failed to erase initializer", Initializer.erase(context, alias))
|
||||
// assertFalse("Expected false when erasing nothing.", Initializer.erase(context))
|
||||
// }
|
||||
//
|
||||
// @Test(expected = InitializerException.MissingDefaultBirthdayException::class)
|
||||
// fun testMissingBirthday() {
|
||||
// val config = Initializer.Config { config ->
|
||||
// config.setViewingKeys("vk1")
|
||||
// }
|
||||
// config.validate()
|
||||
// }
|
||||
//
|
||||
// @Test(expected = InitializerException.InvalidBirthdayHeightException::class)
|
||||
// fun testOutOfBoundsBirthday() {
|
||||
// val config = Initializer.Config { config ->
|
||||
// config.setViewingKeys("vk1")
|
||||
// config.setBirthdayHeight(ZcashSdk.SAPLING_ACTIVATION_HEIGHT - 1)
|
||||
// }
|
||||
// config.validate()
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testImportedWalletUsesSaplingActivation() {
|
||||
// initializer = Initializer(context) { config ->
|
||||
// config.setViewingKeys("vk1")
|
||||
// config.importWallet(ByteArray(32))
|
||||
// }
|
||||
// assertEquals("Incorrect height used for import.", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, initializer.birthday.height)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testDefaultToOldestHeight_true() {
|
||||
// initializer = Initializer(context) { config ->
|
||||
// config.setViewingKeys("vk1")
|
||||
// config.setBirthdayHeight(null, true)
|
||||
// }
|
||||
// assertEquals("Height should equal sapling activation height when defaultToOldestHeight is true", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, initializer.birthday.height)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testDefaultToOldestHeight_false() {
|
||||
// val initialHeight = 750_000
|
||||
// initializer = Initializer(context) { config ->
|
||||
// config.setViewingKeys("vk1")
|
||||
// config.setBirthdayHeight(initialHeight, false)
|
||||
// }
|
||||
// val h = initializer.birthday.height
|
||||
// assertNotEquals("Height should not equal sapling activation height when defaultToOldestHeight is false", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, h)
|
||||
// assertTrue("expected $h to be higher", h >= initialHeight)
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
// init {
|
||||
// Twig.plant(TroubleshootingTwig())
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cash.z.ecc.android.sdk
|
||||
|
||||
import cash.z.ecc.android.sdk.integration.SanityTest
|
||||
import cash.z.ecc.android.sdk.integration.SmokeTest
|
||||
import cash.z.ecc.android.sdk.integration.service.ChangeServiceTest
|
||||
import cash.z.ecc.android.sdk.internal.transaction.PersistentTransactionManagerTest
|
||||
import cash.z.ecc.android.sdk.jni.BranchIdTest
|
||||
import cash.z.ecc.android.sdk.jni.TransparentTest
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Suite
|
||||
|
||||
/**
|
||||
* Suite of tests to run before submitting a pull request.
|
||||
*
|
||||
* For now, these are just the tests that are known to be recently updated and that pass. In the
|
||||
* near future this suite will contain only fast running tests that can be used to quickly validate
|
||||
* that a PR hasn't broken anything major.
|
||||
*/
|
||||
@RunWith(Suite::class)
|
||||
@Suite.SuiteClasses(
|
||||
// Fast tests that only run locally and don't require darksidewalletd or lightwalletd
|
||||
BranchIdTest::class,
|
||||
TransparentTest::class,
|
||||
PersistentTransactionManagerTest::class,
|
||||
|
||||
// potentially exclude because these are long-running (and hit external srvcs)
|
||||
SanityTest::class,
|
||||
|
||||
// potentially exclude because these hit external services
|
||||
ChangeServiceTest::class,
|
||||
SmokeTest::class
|
||||
)
|
||||
class PullRequestSuite
|
||||
@@ -0,0 +1,29 @@
|
||||
package cash.z.ecc.android.sdk.annotation
|
||||
|
||||
enum class TestPurpose {
|
||||
|
||||
/**
|
||||
* These tests are explicitly designed to preserve behavior that we do not want to lose after
|
||||
* major upgrades or refactors. It is acceptable for these test to run long and require
|
||||
* additional infrastructure.
|
||||
*/
|
||||
REGRESSION,
|
||||
|
||||
/**
|
||||
* These tests are designed to be run against new pull requests and generally before any changes
|
||||
* are committed. It is not ideal for these tests to run long.
|
||||
*/
|
||||
COMMIT,
|
||||
|
||||
/**
|
||||
* These tests require a running instance of [darksidewalletd](https://github.com/zcash/lightwalletd/blob/master/docs/darksidewalletd.md).
|
||||
*/
|
||||
DARKSIDE,
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that this test is explicitly intended to be maintained and run regularly in order to
|
||||
* achieve the given purpose. Eventually, we will run all such tests nightly.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class MaintainedTest(vararg val purpose: TestPurpose)
|
||||
@@ -0,0 +1,53 @@
|
||||
package cash.z.ecc.android.sdk.ext
|
||||
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import cash.z.ecc.android.sdk.util.SimpleMnemonics
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONObject
|
||||
import ru.gildor.coroutines.okhttp.await
|
||||
|
||||
fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) {
|
||||
runBlocking { setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network) }
|
||||
}
|
||||
|
||||
object BlockExplorer {
|
||||
suspend fun fetchLatestHeight(): Int {
|
||||
val client = OkHttpClient()
|
||||
val request = Request.Builder()
|
||||
.url("https://api.blockchair.com/zcash/blocks?limit=1")
|
||||
.build()
|
||||
val result = client.newCall(request).await()
|
||||
val body = result.body?.string()
|
||||
return JSONObject(body).getJSONArray("data").getJSONObject(0).getInt("id")
|
||||
}
|
||||
}
|
||||
|
||||
object Transactions {
|
||||
val outbound = arrayOf(
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt"
|
||||
)
|
||||
|
||||
val inbound = arrayOf(
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package cash.z.ecc.android.sdk.integration
|
||||
|
||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||
import cash.z.ecc.android.sdk.ext.BlockExplorer
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import cash.z.ecc.android.sdk.util.TestWallet
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
/**
|
||||
* This test is intended to run to make sure that basic things are functional and pinpoint what is
|
||||
* not working. It was originally developed after a major refactor to find what broke.
|
||||
*/
|
||||
@MaintainedTest(TestPurpose.COMMIT)
|
||||
@RunWith(Parameterized::class)
|
||||
class SanityTest(
|
||||
private val wallet: TestWallet,
|
||||
private val extfvk: String,
|
||||
private val extpub: String,
|
||||
private val birthday: Int
|
||||
|
||||
) {
|
||||
|
||||
val networkName = wallet.networkName
|
||||
val name = "$networkName wallet"
|
||||
|
||||
@Test
|
||||
fun testNotPlaintext() {
|
||||
val message =
|
||||
"is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false"
|
||||
assertFalse("$name $message", wallet.service.connectionInfo.usePlaintext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePaths() {
|
||||
assertEquals(
|
||||
"$name has invalid DataDB file",
|
||||
"/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_${networkName}_Data.db",
|
||||
wallet.initializer.rustBackend.pathDataDb
|
||||
)
|
||||
assertEquals(
|
||||
"$name has invalid CacheDB file",
|
||||
"/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_${networkName}_Cache.db",
|
||||
wallet.initializer.rustBackend.pathCacheDb
|
||||
)
|
||||
assertEquals(
|
||||
"$name has invalid CacheDB params dir",
|
||||
"/data/user/0/cash.z.ecc.android.sdk.test/cache/params",
|
||||
wallet.initializer.rustBackend.pathParamsDir
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBirthday() {
|
||||
assertEquals(
|
||||
"$name has invalid birthday height",
|
||||
birthday,
|
||||
wallet.initializer.birthday.height
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testViewingKeys() {
|
||||
assertEquals(
|
||||
"$name has invalid extfvk",
|
||||
extfvk,
|
||||
wallet.initializer.viewingKeys[0].extfvk
|
||||
)
|
||||
assertEquals(
|
||||
"$name has invalid extpub",
|
||||
extpub,
|
||||
wallet.initializer.viewingKeys[0].extpub
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testServerConnection() {
|
||||
assertEquals(
|
||||
"$name has an invalid server connection",
|
||||
"$networkName.lightwalletd.com:9067?usePlaintext=false",
|
||||
wallet.connectionInfo
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLatestHeight() = runBlocking {
|
||||
if (wallet.networkName == "mainnet") {
|
||||
val expectedHeight = BlockExplorer.fetchLatestHeight()
|
||||
// fetch height directly because the synchronizer hasn't started, yet
|
||||
val downloaderHeight = wallet.service.getLatestBlockHeight()
|
||||
val info = wallet.connectionInfo
|
||||
assertTrue(
|
||||
"$info\n ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight",
|
||||
expectedHeight - 10 < downloaderHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleBlockDownload() = runBlocking {
|
||||
// fetch block directly because the synchronizer hasn't started, yet
|
||||
val height = 1_000_000
|
||||
val block = wallet.service.getBlockRange(height..height)[0]
|
||||
assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height.toInt() == height)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun wallets() = listOf(
|
||||
// Testnet wallet
|
||||
arrayOf(
|
||||
TestWallet(TestWallet.Backups.SAMPLE_WALLET),
|
||||
"zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063",
|
||||
"0234965f30c8611253d035f44e68d4e2ce82150e8665c95f41ccbaf916b16c69d8",
|
||||
1320000
|
||||
),
|
||||
// Mainnet wallet
|
||||
arrayOf(
|
||||
TestWallet(TestWallet.Backups.SAMPLE_WALLET, ZcashNetwork.Mainnet),
|
||||
"zxviews1q0hxkupsqqqqpqzsffgrk2smjuccedua7zswf5e3rgtv3ga9nhvhjug670egshd6me53r5n083s2m9mf4va4z7t39ltd3wr7hawnjcw09eu85q0ammsg0tsgx24p4ma0uvr4p8ltx5laum2slh2whc23ctwlnxme9w4dw92kalwk5u4wyem8dynknvvqvs68ktvm8qh7nx9zg22xfc77acv8hk3qqll9k3x4v2fa26puu2939ea7hy4hh60ywma69xtqhcy4037ne8g2sg8sq",
|
||||
"031c6355641237643317e2d338f5e8734c57e8aa8ce960ee22283cf2d76bef73be",
|
||||
1000000
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cash.z.ecc.android.sdk.integration
|
||||
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.util.TestWallet
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* This test is intended to run to make sure that basic things are functional and pinpoint what is
|
||||
* not working. It was originally developed after a major refactor to find what broke.
|
||||
*/
|
||||
@MaintainedTest(TestPurpose.COMMIT)
|
||||
@MediumTest
|
||||
class SmokeTest {
|
||||
|
||||
@Test
|
||||
fun testNotPlaintext() {
|
||||
val service =
|
||||
wallet.synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService
|
||||
Assert.assertFalse(
|
||||
"Wallet is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false",
|
||||
service.connectionInfo.usePlaintext
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePaths() {
|
||||
Assert.assertEquals("Invalid DataDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Data.db", wallet.initializer.rustBackend.pathDataDb)
|
||||
Assert.assertEquals("Invalid CacheDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Cache.db", wallet.initializer.rustBackend.pathCacheDb)
|
||||
Assert.assertEquals("Invalid CacheDB params dir", "/data/user/0/cash.z.ecc.android.sdk.test/cache/params", wallet.initializer.rustBackend.pathParamsDir)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBirthday() {
|
||||
Assert.assertEquals("Invalid birthday height", 1_320_000, wallet.initializer.birthday.height)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testViewingKeys() {
|
||||
Assert.assertEquals("Invalid extfvk", "zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063", wallet.initializer.viewingKeys[0].extfvk)
|
||||
Assert.assertEquals("Invalid extpub", "0234965f30c8611253d035f44e68d4e2ce82150e8665c95f41ccbaf916b16c69d8", wallet.initializer.viewingKeys[0].extpub)
|
||||
}
|
||||
|
||||
// This test takes an extremely long time
|
||||
// Does its runtime grow over time based on growth of the blockchain?
|
||||
@Test
|
||||
@LargeTest
|
||||
@Ignore("This test is extremely slow and times out before the timeout given")
|
||||
fun testSync() = runBlocking<Unit> {
|
||||
wallet.sync(300_000L)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package cash.z.wallet.sdk.integration
|
||||
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED
|
||||
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.ext.onFirst
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class TestnetIntegrationTest : ScopedTest() {
|
||||
|
||||
var stopWatch = CountDownLatch(1)
|
||||
val saplingActivation = synchronizer.network.saplingActivationHeight
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun testLatestBlockTest() {
|
||||
val service = LightWalletGrpcService(
|
||||
context,
|
||||
host
|
||||
)
|
||||
val height = service.getLatestBlockHeight()
|
||||
assertTrue(height > saplingActivation)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadBirthday() {
|
||||
val (height, hash, time, tree) = runBlocking {
|
||||
WalletBirthdayTool.loadNearest(
|
||||
context,
|
||||
synchronizer.network,
|
||||
saplingActivation + 1
|
||||
)
|
||||
}
|
||||
assertEquals(saplingActivation, height)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun getAddress() = runBlocking {
|
||||
assertEquals(address, synchronizer.getAddress())
|
||||
}
|
||||
|
||||
// This is an extremely slow test; it is disabled so that we can get CI set up
|
||||
@Test
|
||||
@LargeTest
|
||||
@Ignore("This test is extremely slow")
|
||||
fun testBalance() = runBlocking {
|
||||
var availableBalance: Zatoshi? = null
|
||||
synchronizer.saplingBalances.onFirst {
|
||||
availableBalance = it?.available
|
||||
}
|
||||
|
||||
synchronizer.status.filter { it == SYNCED }.onFirst {
|
||||
delay(100)
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
"No funds available when we expected a balance greater than zero!",
|
||||
availableBalance!!.value > 0
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun testSpend() = runBlocking {
|
||||
var success = false
|
||||
synchronizer.saplingBalances.filterNotNull().onEach {
|
||||
success = sendFunds()
|
||||
}.first()
|
||||
log("asserting $success")
|
||||
assertTrue(success)
|
||||
}
|
||||
|
||||
private suspend fun sendFunds(): Boolean {
|
||||
val spendingKey = DerivationTool.deriveSpendingKeys(seed, synchronizer.network)[0]
|
||||
log("sending to address")
|
||||
synchronizer.sendToAddress(
|
||||
spendingKey,
|
||||
ZcashSdk.MINERS_FEE,
|
||||
toAddress,
|
||||
"first mainnet tx from the SDK"
|
||||
).filter { it?.isSubmitSuccess() == true }.onFirst {
|
||||
log("DONE SENDING!!!")
|
||||
}
|
||||
log("returning true from sendFunds")
|
||||
return true
|
||||
}
|
||||
|
||||
fun log(message: String) {
|
||||
twig("\n---\n[TESTLOG]: $message\n---\n")
|
||||
}
|
||||
|
||||
companion object {
|
||||
init { Twig.plant(TroubleshootingTwig()) }
|
||||
|
||||
const val host = "lightwalletd.testnet.z.cash"
|
||||
private const val birthdayHeight = 963150
|
||||
private const val targetHeight = 663250
|
||||
private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||
val seed = "cash.z.ecc.android.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray()
|
||||
val address = "zs1m30y59wxut4zk9w24d6ujrdnfnl42hpy0ugvhgyhr8s0guszutqhdj05c7j472dndjstulph74m"
|
||||
val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val initializer = runBlocking {
|
||||
Initializer.new(context) { config ->
|
||||
config.setNetwork(ZcashNetwork.Testnet, host)
|
||||
runBlocking { config.importWallet(seed, birthdayHeight, ZcashNetwork.Testnet) }
|
||||
}
|
||||
}
|
||||
private lateinit var synchronizer: Synchronizer
|
||||
|
||||
@JvmStatic
|
||||
@BeforeClass
|
||||
fun startUp() {
|
||||
synchronizer = Synchronizer.newBlocking(initializer)
|
||||
synchronizer.start(classScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package cash.z.ecc.android.sdk.integration.service
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||
import cash.z.ecc.android.sdk.exception.LightWalletException.ChangeServerException.ChainInfoNotMatching
|
||||
import cash.z.ecc.android.sdk.exception.LightWalletException.ChangeServerException.StatusException
|
||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
|
||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Spy
|
||||
|
||||
@MaintainedTest(TestPurpose.REGRESSION)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class ChangeServiceTest : ScopedTest() {
|
||||
|
||||
val network = ZcashNetwork.Mainnet
|
||||
|
||||
@Mock
|
||||
lateinit var mockBlockStore: CompactBlockStore
|
||||
var mockCloseable: AutoCloseable? = null
|
||||
|
||||
@Spy
|
||||
val service = LightWalletGrpcService(context, network)
|
||||
|
||||
lateinit var downloader: CompactBlockDownloader
|
||||
lateinit var otherService: LightWalletService
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
initMocks()
|
||||
downloader = CompactBlockDownloader(service, mockBlockStore)
|
||||
otherService = LightWalletGrpcService(context, "lightwalletd.electriccoin.co")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockCloseable?.close()
|
||||
}
|
||||
|
||||
private fun initMocks() {
|
||||
mockCloseable = MockitoAnnotations.openMocks(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSanityCheck() {
|
||||
val result = service.getLatestBlockHeight()
|
||||
assertTrue(result > network.saplingActivationHeight)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCleanSwitch() = runBlocking {
|
||||
downloader.changeService(otherService)
|
||||
val result = downloader.downloadBlockRange(900_000..901_000)
|
||||
assertEquals(1_001, result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeatedly connect to servers and download a range of blocks. Switch part way through and
|
||||
* verify that the servers change over, even while actively downloading.
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun testSwitchWhileActive() = runBlocking {
|
||||
val start = 900_000
|
||||
val count = 5
|
||||
val differentiators = mutableListOf<String>()
|
||||
var initialValue = downloader.getServerInfo().buildUser
|
||||
val job = testScope.launch {
|
||||
repeat(count) {
|
||||
differentiators.add(downloader.getServerInfo().buildUser)
|
||||
twig("downloading from ${differentiators.last()}")
|
||||
downloader.downloadBlockRange(start..(start + 100 * it))
|
||||
delay(10L)
|
||||
}
|
||||
}
|
||||
delay(30)
|
||||
testScope.launch {
|
||||
downloader.changeService(otherService)
|
||||
}
|
||||
job.join()
|
||||
assertTrue(differentiators.count { it == initialValue } < differentiators.size)
|
||||
assertEquals(count, differentiators.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwitchToInvalidServer() = runBlocking {
|
||||
var caughtException: Throwable? = null
|
||||
downloader.changeService(LightWalletGrpcService(context, "invalid.lightwalletd")) {
|
||||
caughtException = it
|
||||
}
|
||||
assertNotNull("Using an invalid host should generate an exception.", caughtException)
|
||||
assertTrue(
|
||||
"Exception was of the wrong type.",
|
||||
caughtException is StatusException
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwitchToTestnetFails() = runBlocking {
|
||||
var caughtException: Throwable? = null
|
||||
downloader.changeService(LightWalletGrpcService(context, ZcashNetwork.Testnet)) {
|
||||
caughtException = it
|
||||
}
|
||||
assertNotNull("Using an invalid host should generate an exception.", caughtException)
|
||||
assertTrue(
|
||||
"Exception was of the wrong type. Expected ${ChainInfoNotMatching::class.simpleName} but was ${caughtException!!::class.simpleName}",
|
||||
caughtException is ChainInfoNotMatching
|
||||
)
|
||||
(caughtException as ChainInfoNotMatching).propertyNames.let { props ->
|
||||
arrayOf("saplingActivationHeight", "chainName").forEach {
|
||||
assertTrue(
|
||||
"$it should be a non-matching property but properties were [$props]",
|
||||
props.contains(it, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SaplingParamToolTest {
|
||||
|
||||
val context: Context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
val cacheDir = "${context.cacheDir.absolutePath}/params"
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// clear the param files
|
||||
runBlocking { SaplingParamTool.clear(cacheDir) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun testFilesExists() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(cacheDir)
|
||||
|
||||
// When
|
||||
val result = SaplingParamTool.validate(cacheDir)
|
||||
|
||||
// Then
|
||||
Assert.assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlySpendFileExits() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(cacheDir)
|
||||
File("$cacheDir/${ZcashSdk.OUTPUT_PARAM_FILE_NAME}").delete()
|
||||
|
||||
// When
|
||||
val result = SaplingParamTool.validate(cacheDir)
|
||||
|
||||
// Then
|
||||
Assert.assertFalse("Validation should fail when the spend params are missing", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnlyOutputOFileExits() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(cacheDir)
|
||||
File("$cacheDir/${ZcashSdk.SPEND_PARAM_FILE_NAME}").delete()
|
||||
|
||||
// When
|
||||
val result = SaplingParamTool.validate(cacheDir)
|
||||
|
||||
// Then
|
||||
Assert.assertFalse("Validation should fail when the spend params are missing", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsufficientDeviceStorage() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(cacheDir)
|
||||
|
||||
Assert.assertFalse("insufficient storage", false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSufficientDeviceStorageForOnlyOneFile() = runBlocking {
|
||||
SaplingParamTool.fetchParams(cacheDir)
|
||||
|
||||
Assert.assertFalse("insufficient storage", false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
||||
import cash.z.ecc.fixture.WalletBirthdayFixture
|
||||
import cash.z.ecc.fixture.toJson
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class WalletBirthdayTest {
|
||||
@Test
|
||||
@SmallTest
|
||||
fun deserialize() {
|
||||
val fixtureBirthday = WalletBirthdayFixture.new()
|
||||
|
||||
val deserialized = WalletBirthday.from(fixtureBirthday.toJson())
|
||||
|
||||
assertEquals(fixtureBirthday, deserialized)
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun epoch_seconds_as_long_that_would_overflow_int() {
|
||||
val jsonString = WalletBirthdayFixture.new(time = Long.MAX_VALUE).toJson()
|
||||
|
||||
WalletBirthday.from(jsonString).also {
|
||||
assertEquals(Long.MAX_VALUE, it.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package cash.z.ecc.android.sdk.internal.transaction
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||
import cash.z.ecc.android.sdk.db.entity.isCancelled
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
import com.nhaarman.mockitokotlin2.any
|
||||
import com.nhaarman.mockitokotlin2.stub
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@MaintainedTest(TestPurpose.REGRESSION)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
class PersistentTransactionManagerTest : ScopedTest() {
|
||||
|
||||
@Mock lateinit var mockEncoder: TransactionEncoder
|
||||
|
||||
@Mock lateinit var mockService: LightWalletService
|
||||
|
||||
val pendingDbName = "PersistentTxMgrTest_Pending.db"
|
||||
val dataDbName = "PersistentTxMgrTest_Data.db"
|
||||
private lateinit var manager: OutboundTransactionManager
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
initMocks()
|
||||
deleteDb()
|
||||
manager = PersistentTransactionManager(context, mockEncoder, mockService, pendingDbName)
|
||||
}
|
||||
|
||||
private fun deleteDb() {
|
||||
context.getDatabasePath(pendingDbName).delete()
|
||||
}
|
||||
|
||||
private fun initMocks() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
mockEncoder.stub {
|
||||
onBlocking {
|
||||
createTransaction(any(), any(), any(), any(), any())
|
||||
}.thenAnswer { invocation ->
|
||||
runBlocking {
|
||||
delay(200)
|
||||
EncodedTransaction(byteArrayOf(1, 2, 3), byteArrayOf(8, 9), 5_000_000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCancellation_RaceCondition() = runBlocking {
|
||||
val tx = manager.initSpend(Zatoshi(1234), "taddr", "memo-good", 0)
|
||||
val txFlow = manager.monitorById(tx.id)
|
||||
|
||||
// encode TX
|
||||
testScope.launch {
|
||||
twig("ENCODE: start"); manager.encode("fookey", tx); twig("ENCODE: end")
|
||||
}
|
||||
|
||||
// then cancel it before it is done encoding
|
||||
testScope.launch {
|
||||
delay(100)
|
||||
twig("CANCEL: start"); manager.cancel(tx.id); twig("CANCEL: end")
|
||||
}
|
||||
|
||||
txFlow.drop(2).onEach {
|
||||
twig("found tx: $it")
|
||||
assertTrue("Expected the encoded tx to be cancelled but it wasn't", it.isCancelled())
|
||||
twig("found it to be successfully cancelled")
|
||||
testScope.cancel()
|
||||
}.launchIn(testScope).join()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCancel() = runBlocking {
|
||||
var tx = manager.initSpend(Zatoshi(1234), "a", "b", 0)
|
||||
assertFalse(tx.isCancelled())
|
||||
manager.cancel(tx.id)
|
||||
tx = manager.findById(tx.id)!!
|
||||
assertTrue("Transaction was not cancelled", tx.isCancelled())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAbort() = runBlocking {
|
||||
var tx: PendingTransaction? = manager.initSpend(Zatoshi(1234), "a", "b", 0)
|
||||
assertNotNull(tx)
|
||||
manager.abort(tx!!)
|
||||
tx = manager.findById(tx.id)
|
||||
assertNull("Transaction was not removed from the DB", tx)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
fun init() {
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package cash.z.ecc.android.sdk.jni
|
||||
|
||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
/**
|
||||
* This test is intended to run to make sure that branch ID logic works across all target devices.
|
||||
*/
|
||||
@MaintainedTest(TestPurpose.REGRESSION)
|
||||
@RunWith(Parameterized::class)
|
||||
class BranchIdTest(
|
||||
private val networkName: String,
|
||||
private val height: Int,
|
||||
private val branchId: Long,
|
||||
private val branchHex: String,
|
||||
private val rustBackend: RustBackendWelding
|
||||
) {
|
||||
|
||||
@Test
|
||||
fun testBranchId_Hex() {
|
||||
val branchId = rustBackend.getBranchIdForHeight(height)
|
||||
val clientBranch = "%x".format(branchId)
|
||||
assertEquals("Invalid branch Id Hex value for $networkName at height $height on ${rustBackend.network.networkName}", branchHex, clientBranch)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBranchId_Numeric() {
|
||||
val actual = rustBackend.getBranchIdForHeight(height)
|
||||
assertEquals("Invalid branch ID for $networkName at height $height on ${rustBackend.network.networkName}", branchId, actual)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun wallets(): List<Array<Any>> {
|
||||
// init values don't matter for this test because we're just checking branchIds, which
|
||||
// is an abnormal use of the SDK because this really should run at the rust level
|
||||
// However, due to quirks on certain devices, we created this test at the Android level,
|
||||
// as a sanity check
|
||||
val testnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Testnet) }
|
||||
val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet) }
|
||||
return listOf(
|
||||
// Mainnet Cases
|
||||
arrayOf("Sapling", 419_200, 1991772603L, "76b809bb", mainnetBackend),
|
||||
arrayOf("Blossom", 653_600, 733220448L, "2bb40e60", mainnetBackend),
|
||||
arrayOf("Heartwood", 903_000, 4122551051L, "f5b9230b", mainnetBackend),
|
||||
arrayOf("Canopy", 1_046_400, 3925833126L, "e9ff75a6", mainnetBackend),
|
||||
|
||||
// Testnet Cases
|
||||
arrayOf("Sapling", 280_000, 1991772603L, "76b809bb", testnetBackend),
|
||||
arrayOf("Blossom", 584_000, 733220448L, "2bb40e60", testnetBackend),
|
||||
arrayOf("Heartwood", 903_800, 4122551051L, "f5b9230b", testnetBackend),
|
||||
arrayOf("Canopy", 1_028_500, 3925833126L, "e9ff75a6", testnetBackend)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package cash.z.ecc.android.sdk.jni
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
@MaintainedTest(TestPurpose.REGRESSION)
|
||||
@RunWith(Parameterized::class)
|
||||
@SmallTest
|
||||
class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
|
||||
|
||||
@Test
|
||||
fun deriveTransparentSecretKeyTest() = runBlocking {
|
||||
assertEquals(expected.tskCompressed, DerivationTool.deriveTransparentSecretKey(SEED, network = network))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deriveTransparentAddressTest() = runBlocking {
|
||||
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddress(SEED, network = network))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deriveTransparentAddressFromSecretKeyTest() = runBlocking {
|
||||
val pk = DerivationTool.deriveTransparentSecretKey(SEED, network = network)
|
||||
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPrivateKey(pk, network = network))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deriveUnifiedViewingKeysFromSeedTest() = runBlocking {
|
||||
val uvks = DerivationTool.deriveUnifiedViewingKeys(SEED, network = network)
|
||||
assertEquals(1, uvks.size)
|
||||
val uvk = uvks.first()
|
||||
assertEquals(expected.zAddr, DerivationTool.deriveShieldedAddress(uvk.extfvk, network = network))
|
||||
assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(uvk.extpub, network = network))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PHRASE = "deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"
|
||||
val MNEMONIC = MnemonicCode(PHRASE)
|
||||
val SEED = MNEMONIC.toSeed()
|
||||
|
||||
object ExpectedMainnet : Expected {
|
||||
override val tAddr = "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4"
|
||||
override val zAddr = "zs1yc4sgtfwwzz6xfsy2xsradzr6m4aypgxhfw2vcn3hatrh5ryqsr08sgpemlg39vdh9kfupx20py"
|
||||
override val tskCompressed = "L4BvDC33yLjMRxipZvdiUmdYeRfZmR8viziwsVwe72zJdGbiJPv2"
|
||||
override val tpk = "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af"
|
||||
}
|
||||
|
||||
object ExpectedTestnet : Expected {
|
||||
override val tAddr = "tm9v3KTsjXK8XWSqiwFjic6Vda6eHY9Mjjq"
|
||||
override val zAddr = "ztestsapling1wn3tw9w5rs55x5yl586gtk72e8hcfdq8zsnjzcu8p7ghm8lrx54axc74mvm335q7lmy3g0sqje6"
|
||||
override val tskCompressed = "KzVugoXxR7AtTMdR5sdJtHxCNvMzQ4H196k7ATv4nnjoummsRC9G"
|
||||
override val tpk = "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af"
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun startup() {
|
||||
Twig.plant(TroubleshootingTwig(formatter = { "@TWIG $it" }))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data() = listOf(
|
||||
arrayOf(ExpectedTestnet, ZcashNetwork.Testnet),
|
||||
arrayOf(ExpectedMainnet, ZcashNetwork.Mainnet)
|
||||
)
|
||||
}
|
||||
|
||||
interface Expected {
|
||||
val tAddr: String
|
||||
val zAddr: String
|
||||
val tskCompressed: String
|
||||
val tpk: String
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cash.z.ecc.android.sdk.sample
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import cash.z.ecc.android.sdk.util.TestWallet
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Samples related to shielding funds.
|
||||
*/
|
||||
class ShieldFundsSample {
|
||||
|
||||
val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" // \"//\"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"//"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"
|
||||
|
||||
/**
|
||||
* This test will construct a t2z transaction. It is safe to run this repeatedly, because
|
||||
* nothing is submitted to the network (because the keys don't match the address so the encoding
|
||||
* fails). Originally, it's intent is just to exercise the code and troubleshoot any issues but
|
||||
* then it became clear that this would be a cool Sample Test and PoC for writing a SimpleWallet
|
||||
* class.
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun constructT2Z() = runBlocking {
|
||||
Twig.sprout("ShieldFundsSample")
|
||||
|
||||
val wallet = TestWallet(TestWallet.Backups.DEV_WALLET, ZcashNetwork.Mainnet)
|
||||
|
||||
Assert.assertEquals("foo", "${wallet.shieldedAddress} ${wallet.transparentAddress}")
|
||||
// wallet.shieldFunds()
|
||||
|
||||
Twig.clip("ShieldFundsSample")
|
||||
Assert.assertEquals(Zatoshi(5), wallet.synchronizer.saplingBalances.value?.available)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package cash.z.ecc.android.sdk.sample
|
||||
|
||||
import androidx.test.filters.LargeTest
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork.Testnet
|
||||
import cash.z.ecc.android.sdk.util.TestWallet
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Sample tests are used to demonstrate functionality. This one attempts to setup a scenario where
|
||||
* one wallet shields funds and the other restores from the blockchain. Ultimately, they should have
|
||||
* the same data.
|
||||
*/
|
||||
class TransparentRestoreSample {
|
||||
|
||||
val TX_VALUE = Zatoshi(ZcashSdk.MINERS_FEE.value / 2)
|
||||
|
||||
// val walletA = SimpleWallet(SEED_PHRASE, "WalletA")
|
||||
|
||||
// the wallet that only restores what everyone else did
|
||||
// val walletB = SimpleWallet(SEED_PHRASE, "WalletB")
|
||||
// // the wallet that sends Z2T transactions
|
||||
//
|
||||
// // sandbox wallet
|
||||
// val walletSandbox = SimpleWallet(SEED_PHRASE, "WalletC")
|
||||
// val walletZ2T = SimpleWallet(SEED_PHRASE, "WalletZ2T")
|
||||
// val externalTransparentAddress =
|
||||
// DerivationTool.deriveTransparentAddress(Mnemonics.MnemonicCode(RANDOM_PHRASE).toSeed(), Testnet)
|
||||
|
||||
// @Test
|
||||
fun sendZ2Texternal() = runBlocking {
|
||||
twig("Syncing WalletExt")
|
||||
val extWallet = TestWallet(TestWallet.Backups.ALICE, alias = "WalletE")
|
||||
extWallet.sync()
|
||||
// extWallet.send(542, walletSandbox.transparentAddress, "External funds memo is lost, though")
|
||||
delay(1000)
|
||||
twig("Done sending funds to external address (Z->T COMPLETE!)")
|
||||
}
|
||||
|
||||
// @Test
|
||||
fun sendZ2T() = runBlocking {
|
||||
// walletSandbox.sync()
|
||||
// walletZ2T.send(543, externalTransparentAddress, "External funds memo is lost, though")
|
||||
delay(1000)
|
||||
twig("Done sending funds to external address (Z->T COMPLETE!)")
|
||||
}
|
||||
|
||||
// @Test
|
||||
fun autoShield() = runBlocking<Unit> {
|
||||
val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC")
|
||||
wallet.sync()
|
||||
twig("Done syncing wallet!")
|
||||
val tbalance = wallet.transparentBalance()
|
||||
val address = wallet.transparentAddress
|
||||
|
||||
twig("t-avail: ${tbalance.available} t-total: ${tbalance.total}")
|
||||
Assert.assertTrue("Not enough funds to run sample. Expected some Zatoshi but found ${tbalance.available}. Try adding funds to $address", tbalance.available.value > 0)
|
||||
|
||||
twig("Shielding available transparent funds!")
|
||||
// wallet.shieldFunds()
|
||||
}
|
||||
|
||||
// @Test
|
||||
fun cli() = runBlocking<Unit> {
|
||||
// val wallet = SimpleWallet(SEED_PHRASE, "WalletCli")
|
||||
// wallet.sync()
|
||||
// wallet.rewindToHeight(1343500).join(45_000)
|
||||
val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC")
|
||||
// wallet.sync().rewindToHeight(1339178).join(10000)
|
||||
wallet.sync().rewindToHeight(1339178).send(
|
||||
"ztestsapling17zazsl8rryl8kjaqxnr2r29rw9d9a2mud37ugapm0s8gmyv0ue43h9lqwmhdsp3nu9dazeqfs6l",
|
||||
"is send broken?"
|
||||
).join(5)
|
||||
}
|
||||
|
||||
// This test is extremely slow and doesn't assert anything, so the benefit of this test is unclear
|
||||
// It is disabled to allow moving forward with configuring CI.
|
||||
@Test
|
||||
@LargeTest
|
||||
@Ignore("This test is extremely slow")
|
||||
fun kris() = runBlocking<Unit> {
|
||||
val wallet0 = TestWallet(TestWallet.Backups.SAMPLE_WALLET.seedPhrase, "tmpabc", Testnet, startHeight = 1330190)
|
||||
// val wallet1 = SimpleWallet(WALLET0_PHRASE, "Wallet1")
|
||||
|
||||
wallet0.sync() // .shieldFunds()
|
||||
// .send(amount = 1543L, memo = "")
|
||||
.join()
|
||||
// wallet1.sync().join(5_000L)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sanity check that the wallet has enough funds for the test
|
||||
*/
|
||||
// @Test
|
||||
fun hasFunds() = runBlocking<Unit> {
|
||||
val walletSandbox = TestWallet(TestWallet.Backups.SAMPLE_WALLET.seedPhrase, "WalletC", Testnet, startHeight = 1330190)
|
||||
// val job = walletA.walletScope.launch {
|
||||
// twig("Syncing WalletA")
|
||||
// walletA.sync()
|
||||
// }
|
||||
twig("Syncing WalletSandbox")
|
||||
walletSandbox.sync()
|
||||
// job.join()
|
||||
delay(500)
|
||||
|
||||
twig("Done syncing both wallets!")
|
||||
// val value = walletA.available
|
||||
// val address = walletA.shieldedAddress
|
||||
// Assert.assertTrue("Not enough funds to run sample. Expected at least $TX_VALUE Zatoshi but found $value. Try adding funds to $address", value >= TX_VALUE)
|
||||
|
||||
// send z->t
|
||||
// walletA.send(TX_VALUE, walletA.transparentAddress, "${TransparentRestoreSample::class.java.simpleName} z->t")
|
||||
|
||||
walletSandbox.rewindToHeight(1339178)
|
||||
twig("Done REWINDING!")
|
||||
twig("T-ADDR (for the win!): ${walletSandbox.transparentAddress}")
|
||||
delay(500)
|
||||
// walletB.sync()
|
||||
// rewind database B to height then rescan
|
||||
}
|
||||
|
||||
// // when startHeight is null, it will use the latest checkpoint
|
||||
// class SimpleWallet(
|
||||
// seedPhrase: String,
|
||||
// alias: String = ZcashSdk.DEFAULT_ALIAS,
|
||||
// startHeight: Int? = null
|
||||
// ) {
|
||||
// val walletScope = CoroutineScope(
|
||||
// SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
|
||||
// )
|
||||
// private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
// private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||
// private val shieldedSpendingKey = DerivationTool.deriveSpendingKeys(seed, Testnet)[0]
|
||||
// private val transparentSecretKey = DerivationTool.deriveTransparentSecretKey(seed, Testnet)
|
||||
// private val host = "lightwalletd.testnet.electriccoin.co"
|
||||
// private val initializer = Initializer(context) { config ->
|
||||
// config.importWallet(seed, startHeight)
|
||||
// config.setNetwork(Testnet, host)
|
||||
// config.alias = alias
|
||||
// }
|
||||
//
|
||||
// val synchronizer = Synchronizer(initializer)
|
||||
// val available get() = synchronizer.latestBalance.availableZatoshi
|
||||
// val shieldedAddress = DerivationTool.deriveShieldedAddress(seed, Testnet)
|
||||
// val transparentAddress = DerivationTool.deriveTransparentAddress(seed, Testnet)
|
||||
// val birthdayHeight get() = synchronizer.latestBirthdayHeight
|
||||
//
|
||||
// suspend fun transparentBalance(): WalletBalance {
|
||||
// synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
|
||||
// return synchronizer.getTransparentBalance(transparentAddress)
|
||||
// }
|
||||
//
|
||||
// suspend fun sync(): SimpleWallet {
|
||||
// if (!synchronizer.isStarted) {
|
||||
// twig("Starting sync")
|
||||
// synchronizer.start(walletScope)
|
||||
// } else {
|
||||
// twig("Awaiting next SYNCED status")
|
||||
// }
|
||||
//
|
||||
// // block until synced
|
||||
// synchronizer.status.first { it == SYNCED }
|
||||
// twig("Synced!")
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// suspend fun send(address: String = transparentAddress, memo: String = "", amount: Long = 500L): SimpleWallet {
|
||||
// synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo)
|
||||
// .takeWhile { it.isPending() }
|
||||
// .collect {
|
||||
// twig("Updated transaction: $it")
|
||||
// }
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// suspend fun rewindToHeight(height: Int): SimpleWallet {
|
||||
// synchronizer.rewindToHeight(height, false)
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// suspend fun shieldFunds(): SimpleWallet {
|
||||
// twig("checking $transparentAddress for transactions!")
|
||||
// synchronizer.refreshUtxos(transparentAddress, 935000).let { count ->
|
||||
// twig("FOUND $count new UTXOs")
|
||||
// }
|
||||
//
|
||||
// synchronizer.getTransparentBalance(transparentAddress).let { walletBalance ->
|
||||
// twig("FOUND utxo balance of total: ${walletBalance.totalZatoshi} available: ${walletBalance.availableZatoshi}")
|
||||
//
|
||||
// if (walletBalance.availableZatoshi > 0L) {
|
||||
// synchronizer.shieldFunds(shieldedSpendingKey, transparentSecretKey)
|
||||
// .onCompletion { twig("done shielding funds") }
|
||||
// .catch { twig("Failed with $it") }
|
||||
// .collect()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// suspend fun join(timeout: Long? = null): SimpleWallet {
|
||||
// // block until stopped
|
||||
// twig("Staying alive until synchronizer is stopped!")
|
||||
// if (timeout != null) {
|
||||
// twig("Scheduling a stop in ${timeout}ms")
|
||||
// walletScope.launch {
|
||||
// delay(timeout)
|
||||
// synchronizer.stop()
|
||||
// }
|
||||
// }
|
||||
// synchronizer.status.first { it == Synchronizer.Status.STOPPED }
|
||||
// twig("Stopped!")
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// init {
|
||||
// Twig.plant(TroubleshootingTwig())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package cash.z.ecc.android.sdk.test
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.newFixedThreadPoolContext
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.AfterClass
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
open class ScopedTest(val defaultTimeout: Long = 2000L) {
|
||||
protected lateinit var testScope: CoroutineScope
|
||||
|
||||
// if an androidTest doesn't need a context, then maybe it should be a unit test instead?!
|
||||
val context: Context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
@Before
|
||||
fun start() {
|
||||
twig("===================== TEST STARTED ==================================")
|
||||
testScope = CoroutineScope(
|
||||
Job(classScope.coroutineContext[Job]!!) + newFixedThreadPoolContext(
|
||||
5,
|
||||
this.javaClass.simpleName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun end() = runBlocking<Unit> {
|
||||
twig("======================= TEST CANCELLING =============================")
|
||||
testScope.cancel()
|
||||
testScope.coroutineContext[Job]?.join()
|
||||
twig("======================= TEST ENDED ==================================")
|
||||
}
|
||||
|
||||
fun timeout(duration: Long, block: suspend () -> Unit) = timeoutWith(testScope, duration, block)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
lateinit var classScope: CoroutineScope
|
||||
|
||||
init {
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
twig("================================================================ INIT")
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun createScope() {
|
||||
twig("======================= CLASS STARTED ===============================")
|
||||
classScope = CoroutineScope(
|
||||
SupervisorJob() + newFixedThreadPoolContext(2, this.javaClass.simpleName)
|
||||
)
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic
|
||||
fun destroyScope() = runBlocking<Unit> {
|
||||
twig("======================= CLASS CANCELLING ============================")
|
||||
classScope.cancel()
|
||||
classScope.coroutineContext[Job]?.join()
|
||||
twig("======================= CLASS ENDED =================================")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun timeoutWith(scope: CoroutineScope, duration: Long, block: suspend () -> Unit) {
|
||||
scope.launch {
|
||||
delay(duration)
|
||||
val message = "ERROR: Test timed out after ${duration}ms"
|
||||
twig(message)
|
||||
throw TimeoutException(message)
|
||||
}.let { selfDestruction ->
|
||||
scope.launch {
|
||||
block()
|
||||
selfDestruction.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cash.z.ecc.android.sdk.tool
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool.IS_FALLBACK_ON_FAILURE
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class WalletBirthdayToolTest {
|
||||
@Test
|
||||
@SmallTest
|
||||
fun birthday_height_from_filename() {
|
||||
assertEquals(123, WalletBirthdayTool.birthdayHeight("123.json"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun load_latest_birthday() {
|
||||
// Using a separate directory, so that we don't have to keep updating this test each time
|
||||
// mainnet or testnet changes
|
||||
val directory = "co.electriccoin.zcash/checkpoint/goodnet"
|
||||
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val birthday = runBlocking {
|
||||
WalletBirthdayTool.getFirstValidWalletBirthday(
|
||||
context,
|
||||
directory,
|
||||
listOf("1300000.json", "1290000.json")
|
||||
)
|
||||
}
|
||||
assertEquals(1300000, birthday.height)
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun load_latest_birthday_fallback_on_bad_json() {
|
||||
if (!IS_FALLBACK_ON_FAILURE) {
|
||||
return
|
||||
}
|
||||
|
||||
val directory = "co.electriccoin.zcash/checkpoint/badnet"
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val birthday = runBlocking {
|
||||
WalletBirthdayTool.getFirstValidWalletBirthday(
|
||||
context,
|
||||
directory,
|
||||
listOf("1300000.json", "1290000.json")
|
||||
)
|
||||
}
|
||||
assertEquals(1290000, birthday.height)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cash.z.ecc.android.sdk.util
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class AddressGeneratorUtil {
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
private val mnemonics = SimpleMnemonics()
|
||||
|
||||
@Test
|
||||
fun printMnemonic() {
|
||||
mnemonics.apply {
|
||||
val mnemonicPhrase = String(nextMnemonic())
|
||||
println("example mnemonic: $mnemonicPhrase")
|
||||
assertEquals(24, mnemonicPhrase.split(" ").size)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generateAddresses() = runBlocking {
|
||||
readLines()
|
||||
.map { seedPhrase ->
|
||||
mnemonics.toSeed(seedPhrase.toCharArray())
|
||||
}.map { seed ->
|
||||
DerivationTool.deriveShieldedAddress(seed, ZcashNetwork.Mainnet)
|
||||
}.collect { address ->
|
||||
println("xrxrx2\t$address")
|
||||
assertTrue(address.startsWith("zs1"))
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readLines() = flow<String> {
|
||||
val seedFile = javaClass.getResourceAsStream("/utils/seeds.txt")!!
|
||||
seedFile.source().buffer().use { source ->
|
||||
var line: String? = source.readUtf8Line()
|
||||
while (line != null) {
|
||||
emit(line)
|
||||
line = source.readUtf8Line()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package cash.z.ecc.android.sdk.util
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* A tool for checking transactions since the given birthday and printing balances. This was useful for the Zcon1 app to
|
||||
* ensure that we loaded all the pokerchips correctly.
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
class BalancePrinterUtil {
|
||||
|
||||
private val network = ZcashNetwork.Mainnet
|
||||
private val downloadBatchSize = 9_000
|
||||
private val birthdayHeight = 523240
|
||||
|
||||
private val mnemonics = SimpleMnemonics()
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val alias = "BalanceUtil"
|
||||
// private val caceDbPath = Initializer.cacheDbPath(context, alias)
|
||||
//
|
||||
// private val downloader = CompactBlockDownloader(
|
||||
// LightWalletGrpcService(context, host, port),
|
||||
// CompactBlockDbStore(context, caceDbPath)
|
||||
// )
|
||||
|
||||
// private val processor = CompactBlockProcessor(downloader)
|
||||
|
||||
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
||||
|
||||
private lateinit var birthday: WalletBirthday
|
||||
private var synchronizer: Synchronizer? = null
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
cacheBlocks()
|
||||
birthday = runBlocking { WalletBirthdayTool.loadNearest(context, network, birthdayHeight) }
|
||||
}
|
||||
|
||||
private fun cacheBlocks() = runBlocking {
|
||||
// twig("downloading compact blocks...")
|
||||
// val latestBlockHeight = downloader.getLatestBlockHeight()
|
||||
// val lastDownloaded = downloader.getLastDownloadedHeight()
|
||||
// val blockRange = (Math.max(birthday, lastDownloaded))..latestBlockHeight
|
||||
// downloadNewBlocks(blockRange)
|
||||
// val error = validateNewBlocks(blockRange)
|
||||
// twig("validation completed with result $error")
|
||||
// assertEquals(-1, error)
|
||||
}
|
||||
|
||||
private suspend fun deleteDb(dbName: String) {
|
||||
context.getDatabasePath(dbName).absoluteFile.deleteSuspend()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun printBalances() = runBlocking {
|
||||
readLines()
|
||||
.map { seedPhrase ->
|
||||
twig("checking balance for: $seedPhrase")
|
||||
mnemonics.toSeed(seedPhrase.toCharArray())
|
||||
}.collect { seed ->
|
||||
// TODO: clear the dataDb but leave the cacheDb
|
||||
val initializer = Initializer.new(context) { config ->
|
||||
runBlocking { config.importWallet(seed, birthdayHeight, network) }
|
||||
config.setNetwork(network)
|
||||
config.alias = alias
|
||||
}
|
||||
/*
|
||||
what I need to do right now
|
||||
- for each seed
|
||||
- I can reuse the cache of blocks... so just like get the cache once
|
||||
- I need to scan into a new database
|
||||
- I don't really need a new rustbackend
|
||||
- I definitely don't need a new grpc connection
|
||||
- can I just use a processor and point it to a different DB?
|
||||
+ so yeah, I think I need to use the processor directly right here and just swap out its pieces
|
||||
- perhaps create a new initializer and use that to configure the processor?
|
||||
- or maybe just set the data destination for the processor
|
||||
- I might need to consider how state is impacting this design
|
||||
- can we be more stateless and thereby improve the flexibility of this code?!!!
|
||||
*/
|
||||
synchronizer?.stop()
|
||||
synchronizer = Synchronizer.new(initializer).apply {
|
||||
start()
|
||||
}
|
||||
|
||||
// deleteDb(dataDbPath)
|
||||
// initWallet(seed)
|
||||
// twig("scanning blocks for seed <$seed>")
|
||||
// // rustBackend.scanBlocks()
|
||||
// twig("done scanning blocks for seed $seed")
|
||||
// // val total = rustBackend.getBalance(0)
|
||||
// twig("found total: $total")
|
||||
// // val available = rustBackend.getVerifiedBalance(0)
|
||||
// twig("found available: $available")
|
||||
// twig("xrxrx2\t$seed\t$total\t$available")
|
||||
// println("xrxrx2\t$seed\t$total\t$available")
|
||||
}
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun printBalances() = runBlocking {
|
||||
// readLines().collect { seed ->
|
||||
// deleteDb(dataDbName)
|
||||
// initWallet(seed)
|
||||
// twig("scanning blocks for seed <$seed>")
|
||||
// rustBackend.scanBlocks()
|
||||
// twig("done scanning blocks for seed $seed")
|
||||
// val total = rustBackend.getBalance(0)
|
||||
// twig("found total: $total")
|
||||
// val available = rustBackend.getVerifiedBalance(0)
|
||||
// twig("found available: $available")
|
||||
// twig("xrxrx2\t$seed\t$total\t$available")
|
||||
// println("xrxrx2\t$seed\t$total\t$available")
|
||||
// }
|
||||
|
||||
// Thread.sleep(5000)
|
||||
// assertEquals("foo", "bar")
|
||||
// }
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readLines() = flow<String> {
|
||||
val seedFile = javaClass.getResourceAsStream("/utils/seeds.txt")!!
|
||||
seedFile.source().buffer().use { source ->
|
||||
var line: String? = source.readUtf8Line()
|
||||
while (line != null) {
|
||||
emit(line)
|
||||
line = source.readUtf8Line()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private fun initWallet(seed: String): Wallet {
|
||||
// val spendingKeyProvider = Delegates.notNull<String>()
|
||||
// return Wallet(
|
||||
// context,
|
||||
// rustBackend,
|
||||
// SampleSeedProvider(seed),
|
||||
// spendingKeyProvider,
|
||||
// Wallet.loadBirthdayFromAssets(context, birthday)
|
||||
// ).apply {
|
||||
// runCatching {
|
||||
// initialize()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun downloadNewBlocks(range: IntRange) = runBlocking {
|
||||
Twig.sprout("downloading")
|
||||
twig("downloading blocks in range $range")
|
||||
|
||||
var downloadedBlockHeight = range.start
|
||||
val count = range.last - range.first + 1
|
||||
val batches =
|
||||
(count / downloadBatchSize + (if (count.rem(downloadBatchSize) == 0) 0 else 1))
|
||||
twig("found $count missing blocks, downloading in $batches batches of $downloadBatchSize...")
|
||||
for (i in 1..batches) {
|
||||
val end = Math.min(range.first + (i * downloadBatchSize), range.last + 1)
|
||||
val batchRange = downloadedBlockHeight until end
|
||||
twig("downloaded $batchRange (batch $i of $batches)") {
|
||||
// downloader.downloadBlockRange(batchRange)
|
||||
}
|
||||
downloadedBlockHeight = end
|
||||
}
|
||||
Twig.clip("downloading")
|
||||
}
|
||||
|
||||
// private fun validateNewBlocks(range: IntRange?): Int {
|
||||
// // val dummyWallet = initWallet("dummySeed")
|
||||
// Twig.sprout("validating")
|
||||
// twig("validating blocks in range $range")
|
||||
// // val result = rustBackend.validateCombinedChain()
|
||||
// Twig.clip("validating")
|
||||
// return result
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package cash.z.ecc.android.sdk.util
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* A tool for validating an existing database and testing reorgs.
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
class DataDbScannerUtil {
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
private val host = "lightd-main.zecwallet.co"
|
||||
private val port = 443
|
||||
private val alias = "ScannerUtil"
|
||||
|
||||
// private val mnemonics = SimpleMnemonics()
|
||||
// private val caceDbPath = Initializer.cacheDbPath(context, alias)
|
||||
|
||||
// private val downloader = CompactBlockDownloader(
|
||||
// LightWalletGrpcService(context, host, port),
|
||||
// CompactBlockDbStore(context, caceDbPath)
|
||||
// )
|
||||
|
||||
// private val processor = CompactBlockProcessor(downloader)
|
||||
|
||||
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
||||
|
||||
private val birthdayHeight = 600_000
|
||||
private lateinit var synchronizer: Synchronizer
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
// cacheBlocks()
|
||||
}
|
||||
|
||||
private fun cacheBlocks() = runBlocking {
|
||||
// twig("downloading compact blocks...")
|
||||
// val latestBlockHeight = downloader.getLatestBlockHeight()
|
||||
// val lastDownloaded = downloader.getLastDownloadedHeight()
|
||||
// val blockRange = (Math.max(birthday, lastDownloaded))..latestBlockHeight
|
||||
// downloadNewBlocks(blockRange)
|
||||
// val error = validateNewBlocks(blockRange)
|
||||
// twig("validation completed with result $error")
|
||||
// assertEquals(-1, error)
|
||||
}
|
||||
|
||||
private fun deleteDb(dbName: String) {
|
||||
context.getDatabasePath(dbName).absoluteFile.delete()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun scanExistingDb() {
|
||||
synchronizer = run {
|
||||
val initializer = runBlocking {
|
||||
Initializer.new(context) {
|
||||
it.setBirthdayHeight(
|
||||
birthdayHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val synchronizer = runBlocking {
|
||||
Synchronizer.new(
|
||||
initializer
|
||||
)
|
||||
}
|
||||
|
||||
synchronizer
|
||||
}
|
||||
|
||||
println("sync!")
|
||||
synchronizer.start()
|
||||
val scope = (synchronizer as SdkSynchronizer).coroutineScope
|
||||
|
||||
scope.launch {
|
||||
synchronizer.status.collect { status ->
|
||||
// when (status) {
|
||||
println("received status of $status")
|
||||
// }
|
||||
}
|
||||
}
|
||||
println("going to sleep!")
|
||||
Thread.sleep(125000)
|
||||
println("I'm back and I'm out!")
|
||||
synchronizer.stop()
|
||||
}
|
||||
//
|
||||
// @Test
|
||||
// fun printBalances() = runBlocking {
|
||||
// readLines()
|
||||
// .map { seedPhrase ->
|
||||
// twig("checking balance for: $seedPhrase")
|
||||
// mnemonics.toSeed(seedPhrase.toCharArray())
|
||||
// }.collect { seed ->
|
||||
// initializer.import(seed, birthday, clearDataDb = true, clearCacheDb = false)
|
||||
// /*
|
||||
// what I need to do right now
|
||||
// - for each seed
|
||||
// - I can reuse the cache of blocks... so just like get the cache once
|
||||
// - I need to scan into a new database
|
||||
// - I don't really need a new rustbackend
|
||||
// - I definitely don't need a new grpc connection
|
||||
// - can I just use a processor and point it to a different DB?
|
||||
// + so yeah, I think I need to use the processor directly right here and just swap out its pieces
|
||||
// - perhaps create a new initializer and use that to configure the processor?
|
||||
// - or maybe just set the data destination for the processor
|
||||
// - I might need to consider how state is impacting this design
|
||||
// - can we be more stateless and thereby improve the flexibility of this code?!!!
|
||||
// */
|
||||
// synchronizer?.stop()
|
||||
// synchronizer = Synchronizer(context, initializer)
|
||||
//
|
||||
// // deleteDb(dataDbPath)
|
||||
// // initWallet(seed)
|
||||
// // twig("scanning blocks for seed <$seed>")
|
||||
// //// rustBackend.scanBlocks()
|
||||
// // twig("done scanning blocks for seed $seed")
|
||||
// //// val total = rustBackend.getBalance(0)
|
||||
// // twig("found total: $total")
|
||||
// //// val available = rustBackend.getVerifiedBalance(0)
|
||||
// // twig("found available: $available")
|
||||
// // twig("xrxrx2\t$seed\t$total\t$available")
|
||||
// // println("xrxrx2\t$seed\t$total\t$available")
|
||||
// }
|
||||
//
|
||||
// Thread.sleep(5000)
|
||||
// assertEquals("foo", "bar")
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cash.z.ecc.android.sdk.util
|
||||
|
||||
import cash.z.android.plugin.MnemonicPlugin
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode
|
||||
import cash.z.ecc.android.bip39.Mnemonics.WordCount
|
||||
import cash.z.ecc.android.bip39.toEntropy
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import java.util.Locale
|
||||
|
||||
class SimpleMnemonics : MnemonicPlugin {
|
||||
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
||||
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
||||
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
||||
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars
|
||||
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
||||
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words
|
||||
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
||||
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package cash.z.ecc.android.sdk.util
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.db.entity.isPending
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.newFixedThreadPoolContext
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* A simple wallet that connects to testnet for integration testing. The intention is that it is
|
||||
* easy to drive and nice to use.
|
||||
*/
|
||||
class TestWallet(
|
||||
val seedPhrase: String,
|
||||
val alias: String = "TestWallet",
|
||||
val network: ZcashNetwork = ZcashNetwork.Testnet,
|
||||
val host: String = network.defaultHost,
|
||||
startHeight: Int? = null,
|
||||
val port: Int = network.defaultPort
|
||||
) {
|
||||
constructor(
|
||||
backup: Backups,
|
||||
network: ZcashNetwork = ZcashNetwork.Testnet,
|
||||
alias: String = "TestWallet"
|
||||
) : this(
|
||||
backup.seedPhrase,
|
||||
network = network,
|
||||
startHeight = if (network == ZcashNetwork.Mainnet) backup.mainnetBirthday else backup.testnetBirthday,
|
||||
alias = alias
|
||||
)
|
||||
|
||||
val walletScope = CoroutineScope(
|
||||
SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
|
||||
)
|
||||
|
||||
// Although runBlocking isn't great, this usage is OK because this is only used within the
|
||||
// automated tests
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||
private val shieldedSpendingKey =
|
||||
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
|
||||
private val transparentSecretKey =
|
||||
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
|
||||
val initializer = runBlocking {
|
||||
Initializer.new(context) { config ->
|
||||
runBlocking { config.importWallet(seed, startHeight, network, host, alias = alias) }
|
||||
}
|
||||
}
|
||||
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(initializer) as SdkSynchronizer
|
||||
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
|
||||
|
||||
val available get() = synchronizer.saplingBalances.value?.available
|
||||
val shieldedAddress =
|
||||
runBlocking { DerivationTool.deriveShieldedAddress(seed, network = network) }
|
||||
val transparentAddress =
|
||||
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
|
||||
val birthdayHeight get() = synchronizer.latestBirthdayHeight
|
||||
val networkName get() = synchronizer.network.networkName
|
||||
val connectionInfo get() = service.connectionInfo.toString()
|
||||
|
||||
suspend fun transparentBalance(): WalletBalance {
|
||||
synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
|
||||
return synchronizer.getTransparentBalance(transparentAddress)
|
||||
}
|
||||
|
||||
suspend fun sync(timeout: Long = -1): TestWallet {
|
||||
val killSwitch = walletScope.launch {
|
||||
if (timeout > 0) {
|
||||
delay(timeout)
|
||||
throw TimeoutException("Failed to sync wallet within ${timeout}ms")
|
||||
}
|
||||
}
|
||||
if (!synchronizer.isStarted) {
|
||||
twig("Starting sync")
|
||||
synchronizer.start(walletScope)
|
||||
} else {
|
||||
twig("Awaiting next SYNCED status")
|
||||
}
|
||||
|
||||
// block until synced
|
||||
synchronizer.status.first { it == Synchronizer.Status.SYNCED }
|
||||
killSwitch.cancel()
|
||||
twig("Synced!")
|
||||
return this
|
||||
}
|
||||
|
||||
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
|
||||
Twig.sprout("$alias sending")
|
||||
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
||||
.takeWhile { it.isPending() }
|
||||
.collect {
|
||||
twig("Updated transaction: $it")
|
||||
}
|
||||
Twig.clip("$alias sending")
|
||||
return this
|
||||
}
|
||||
|
||||
suspend fun rewindToHeight(height: Int): TestWallet {
|
||||
synchronizer.rewindToNearestHeight(height, false)
|
||||
return this
|
||||
}
|
||||
|
||||
suspend fun shieldFunds(): TestWallet {
|
||||
twig("checking $transparentAddress for transactions!")
|
||||
synchronizer.refreshUtxos(transparentAddress, 935000).let { count ->
|
||||
twig("FOUND $count new UTXOs")
|
||||
}
|
||||
|
||||
synchronizer.getTransparentBalance(transparentAddress).let { walletBalance ->
|
||||
twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}")
|
||||
|
||||
if (walletBalance.available.value > 0L) {
|
||||
synchronizer.shieldFunds(shieldedSpendingKey, transparentSecretKey)
|
||||
.onCompletion { twig("done shielding funds") }
|
||||
.catch { twig("Failed with $it") }
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
suspend fun join(timeout: Long? = null): TestWallet {
|
||||
// block until stopped
|
||||
twig("Staying alive until synchronizer is stopped!")
|
||||
if (timeout != null) {
|
||||
twig("Scheduling a stop in ${timeout}ms")
|
||||
walletScope.launch {
|
||||
delay(timeout)
|
||||
synchronizer.stop()
|
||||
}
|
||||
}
|
||||
synchronizer.status.first { it == Synchronizer.Status.STOPPED }
|
||||
twig("Stopped!")
|
||||
return this
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
Twig.enabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Backups(val seedPhrase: String, val testnetBirthday: Int, val mainnetBirthday: Int) {
|
||||
// TODO: get the proper birthday values for these wallets
|
||||
DEFAULT("column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana", 1_355_928, 1_000_000),
|
||||
SAMPLE_WALLET("input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose", 1_330_190, 1_000_000),
|
||||
DEV_WALLET("still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread", 1_000_000, 991645),
|
||||
ALICE("quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten", 1_330_190, 1_000_000),
|
||||
BOB("canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena", 1_330_190, 1_000_000),
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cash.z.ecc.android.sdk.util
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
class TransactionCounterUtil {
|
||||
|
||||
private val network = ZcashNetwork.Mainnet
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val service = LightWalletGrpcService(context, network)
|
||||
|
||||
init {
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun testBlockSize() {
|
||||
val sizes = mutableMapOf<Int, Int>()
|
||||
service.getBlockRange(900_000..910_000).forEach { b ->
|
||||
twig("h: ${b.header.size()}")
|
||||
val s = b.serializedSize
|
||||
sizes[s] = (sizes[s] ?: 0) + 1
|
||||
}
|
||||
twig("sizes: ${sizes.toSortedMap()}")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun testCountTransactions() {
|
||||
val txCounts = mutableMapOf<Int, Int>()
|
||||
val outputCounts = mutableMapOf<Int, Int>()
|
||||
var totalOutputs = 0
|
||||
var totalTxs = 0
|
||||
service.getBlockRange(900_000..950_000).forEach { b ->
|
||||
b.header.size()
|
||||
b.vtxList.map { it.outputsCount }.forEach { oCount ->
|
||||
outputCounts[oCount] = (outputCounts[oCount] ?: 0) + oCount.coerceAtLeast(1)
|
||||
totalOutputs += oCount
|
||||
}
|
||||
b.vtxCount.let { count ->
|
||||
txCounts[count] = (txCounts[count] ?: 0) + count.coerceAtLeast(1)
|
||||
totalTxs += count
|
||||
}
|
||||
}
|
||||
twig("txs: $txCounts")
|
||||
twig("outputs: $outputCounts")
|
||||
twig("total: $totalTxs $totalOutputs")
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,36 @@
|
||||
package cash.z.ecc.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.KEY_EPOCH_SECONDS
|
||||
import cash.z.ecc.android.sdk.internal.KEY_HASH
|
||||
import cash.z.ecc.android.sdk.internal.KEY_HEIGHT
|
||||
import cash.z.ecc.android.sdk.internal.KEY_TREE
|
||||
import cash.z.ecc.android.sdk.internal.KEY_VERSION
|
||||
import cash.z.ecc.android.sdk.internal.VERSION_1
|
||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
||||
import org.json.JSONObject
|
||||
|
||||
object WalletBirthdayFixture {
|
||||
|
||||
// These came from the mainnet 1500000.json file
|
||||
const val HEIGHT = 1500000
|
||||
const val HASH = "00000000019e5b25a95c7607e7789eb326fddd69736970ebbe1c7d00247ef902"
|
||||
const val EPOCH_SECONDS = 1639913234L
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
const val TREE = "01ce183032b16ed87fcc5052a42d908376526126346567773f55bc58a63e4480160013000001bae5112769a07772345dd402039f2949c457478fe9327363ff631ea9d78fb80d0177c0b6c21aa9664dc255336ed450914088108c38a9171c85875b4e53d31b3e140171add6f9129e124651ca894aa842a3c71b1738f3ee2b7ba829106524ef51e62101f9cebe2141ee9d0a3f3a3e28bce07fa6b6e1c7b42c01cc4fe611269e9d52da540001d0adff06de48569129bd2a211e3253716362da97270d3504d9c1b694689ebe3c0122aaaea90a7fa2773b8166937310f79a4278b25d759128adf3138d052da3725b0137fb2cbc176075a45db2a3c32d3f78e669ff2258fd974e99ec9fb314d7fd90180165aaee3332ea432d13a9398c4863b38b8a7a491877a5c46b0802dcd88f7e324301a9a262f8b92efc2e0e3e4bd1207486a79d62e87b4ab9cc41814d62a23c4e28040001e3c4ee998682df5c5e230d6968e947f83d0c03682f0cfc85f1e6ec8e8552c95a000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
|
||||
fun new(
|
||||
height: Int = HEIGHT,
|
||||
hash: String = HASH,
|
||||
time: Long = EPOCH_SECONDS,
|
||||
tree: String = TREE
|
||||
) = WalletBirthday(height = height, hash = hash, time = time, tree = tree)
|
||||
}
|
||||
|
||||
fun WalletBirthday.toJson() = JSONObject().apply {
|
||||
put(WalletBirthday.KEY_VERSION, WalletBirthday.VERSION_1)
|
||||
put(WalletBirthday.KEY_HEIGHT, height)
|
||||
put(WalletBirthday.KEY_HASH, hash)
|
||||
put(WalletBirthday.KEY_EPOCH_SECONDS, time)
|
||||
put(WalletBirthday.KEY_TREE, tree)
|
||||
}.toString()
|
||||
4
sdk-lib/src/androidTest/res/values/bools.xml
Normal file
4
sdk-lib/src/androidTest/res/values/bools.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="lightwalletd_allow_very_insecure_connections">false</bool>
|
||||
</resources>
|
||||
18
sdk-lib/src/androidTest/resources/utils/seeds.txt
Normal file
18
sdk-lib/src/androidTest/resources/utils/seeds.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner
|
||||
urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top
|
||||
wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame
|
||||
labor elite banana cement royal tiger smile robust talk street bread bitter admit spy leg alcohol opinion mimic crane bid damp trigger wagon share
|
||||
icon future member loan initial music bless cigar artist cross scorpion disease click else palm recall obscure horse wire energy frost route stone raven
|
||||
way fruit group range army seven stem ridge panel duty deal like mango engage adult market drama large year love clay desert culture evoke
|
||||
stairs bridge romance offer bronze organ soldier point unveil soup figure economy purity rapid eight error make goat poet when letter gold coil gate
|
||||
execute thing home flat rare pitch plug poverty never design cute essay mosquito unhappy pen phone aerobic basket empower system extend concert leopard leopard
|
||||
thought balcony raw renew sister define isolate bridge rigid critic extra enhance accuse skin either lock owner boat grid legal coral judge oyster olympic
|
||||
pull curious short apology slot giraffe island caution cricket attract episode acoustic age fly crucial earth broccoli eternal eyebrow marriage lazy thank actor police
|
||||
army boat guess direct network version mean rice brown sauce bronze health stable way proud gift primary reason company raw sorry virtual other ahead
|
||||
humble educate desert govern quality cup illness spatial whale zoo novel hollow velvet erosion gadget glove great occur milk staff gravity word skate soul
|
||||
horror scene device ahead before blossom surface staff shrug horse wood drill style garage north account twice easily slam require nose sentence catalog mango
|
||||
bronze this era window wonder strike label grid keep paddle kiwi age input flock just eagle coil like toward burst mobile obtain giant idle
|
||||
aisle dwarf bulb catch anxiety follow attack that habit exclude laptop spoon enough walnut picture reward pact license behind question save cover exotic drip
|
||||
two length electric immune antique rotate junior spoon torch liberty eyebrow shoe army away horn anger oak chase grow ride enrich soft push orient
|
||||
bike crunch vintage smoke okay screen side pattern thrive top timber payment flight garment lift heavy enable sting humble obscure reveal art kangaroo owner
|
||||
treat stumble only reward else turtle across shop vocal dynamic goddess toss review polar enable plate process cabin injury rifle sword group agree slush
|
||||
@@ -0,0 +1,15 @@
|
||||
package cash.z.ecc.android.sdk.annotation
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class OpenClass
|
||||
|
||||
/**
|
||||
* Used in conjunction with the kotlin-allopen plugin to make any class with this annotation open for extension.
|
||||
* Typically, we apply this to classes that we want to mock in androidTests because unit tests don't have this problem,
|
||||
* it's only an issue with JUnit4 Instrumentation tests.
|
||||
*
|
||||
* Note: the counterpart to this annotation in the release buildType does not apply the OpenClass annotation
|
||||
*/
|
||||
@OpenClass
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class OpenForTesting
|
||||
5
sdk-lib/src/main/AndroidManifest.xml
Normal file
5
sdk-lib/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cash.z.ecc.android.sdk">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1000000",
|
||||
"hash": "000000000062eff9ae053020017bfef24e521a2704c5ec9ead2a4608ac70fc7a",
|
||||
"time": 1602206541,
|
||||
"saplingTree": "01a4d1f92e2c051e039ca80b14a86d35c755d88ff9856a3c562da4ed14f77f5d0e0012000001f1ff712c8269b7eb11df56b23f8263d59bc4bb2bbc449973e1c85f399c433a0401e0e8b56e5d56de16c173d83c2d96d4e2f94ce0cbd323a53434c647deff020c08000129acf59ead19b76e487e47cf1d100e953acedc62afa6b384f91a620321b1585300018179961f79f609e6759032f3466067548244c3fe0bf31d275d1b6595bb2d486401b622d3f80d8231c44483faa2a27e96e06a2e08d099b26d15828e8f0bde0bd42001a8d1f585aeceb5c3f22ffb43014fe89db9f7efc080361d4fa4e8d596ab1224400103ee02ae59c6688dcaadf1c4ff95e7b1a902837e4989a4c4994dce7dac6ecb20014ff8c0fe6bce02ac4ad684996bfa931d61c724015d797642819361d611ebd61201c7ae83949d9502b0eff10618124d335f046e4aae52c19ccad5567feceb342a5200000001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1010000",
|
||||
"hash": "0000000000faa7ec382bf9c0c1abae839f663834f8f307a2141b1403bc85fcbb",
|
||||
"time": 1602960322,
|
||||
"saplingTree": "01f89aad9f6ed7532fc8866580ad425ba2a63ade58b064be8448804ccf9a60fd68001201dd7166b177a37db095801301de34572e69cf151db80201ee08d677c8a1a7986f000001d30d2ef05fd6f29519da13f09623f440c0f8dcc8154c56dc7f881c77c3504f2201b3ae640c5770b95f68aa24c57c32c01713e450c63b1bc5535ebc81ef2a26b519018611a216a563b1cf44cb1b27821c076869676b7b341a5b4f2cef10e8896a364101091d319cbcb2ed9b4caef347a32268ca70012899b85cf438ecf4bd6d6700c04e01337e6de0bdb259c3bd864fec2617a2cd9e127a7e6ba4212a1a7b9ae8c65e2d1c01121f952ffbab89fac050c927d06f855f8baa39ceb1584a5144c32b45d6bbe35a0110188995758e67a4c8a9e71aaecc9a7bc94e085d189252b385eab74587424e5c0000011ed4073019f93951e17d7e19f48d922bb6f3a9aa1a4827e619e0ae791c9539240128e88325aeb1eaae03e9085f4128fb62f367aaf4a6340dadffe427c1d66aa445000001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1020000",
|
||||
"hash": "0000000000c3c4887bd6f5d3cbf9030914e6d1bdd51370ade6657e831d9e3377",
|
||||
"time": 1603714448,
|
||||
"saplingTree": "014f6722b03c84e99cb0f198c604fec8c1c395fa726f403e239a66b124d4ee2f7200120001d6920ed4fa9340ece9cc4918f8b5aba3b0ebc5be16831e7d56e6acc93a7e73110001c6cc38510b51ef7ef6dfd1d48df4ef97de8a52f19a9964ecb53301de1057760d018c66fe955d1c4a7dbeae31896dda4cc0bc7824abdea432101fed279a628f5a4f000001935ddd089f29e35bd356a176191995f91239127eb93bff018a3447f80615a05100000182e624e6852072075532264ac2cc76bcc92327ce55e1dd464817d4c6b9637a6e018d5dcc90c3e7134891d77aee71063b8d542b337e4d43f55f0db9cef746f5a732000001bde7578541c07920c1bc94312596adfcee32519cb80a95bcd06a1272c127ff020001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1030000",
|
||||
"hash": "000000000216b8552281f6b73332ec873e0eb062f9b83ede4f3102af78446b7c",
|
||||
"time": 1604467637,
|
||||
"saplingTree": "016f037481382b438c1a41d74153ee8a6131db5a9fcfa2526718e7b4fa1577e658001201622ea365daf3d6cbd5b712bb33b77764708d69f852b903f053c47569cabd930a0001e2175829e38f95f1b3415bdaa796a732d83da1b137e1b0aecfa9802b8c8e9a540001c783c98897bc46693d1d2d2891489663f0d9ff12b34f28b3db5a841236d9d76501d671aaba2921e416e7b7b81a89cb8e524cb6c49d89c575e04e0da0461799216f0001ba538b78350bfeae6538bfac75fe8709eb59bb72f6d74d64c92df41ac1e464560001ef2204037f952a1365afd291acf2361dcebda719b5e659de073ebe2f7f3eae1a01264c173c66e9b7c36ac9f7a6c928600107fa40f11b8d81862464f849885c50620189c3e3ed72b0d445f043e2d2d5ec23c693ef67b9a15488911ad35480a6041c6301f7f497a2f9ded8bb6d14d9f1bb83f43396270e1fc7e86c2c789d9b74b9d2d3070001bde7578541c07920c1bc94312596adfcee32519cb80a95bcd06a1272c127ff020001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1040000",
|
||||
"hash": "0000000000852680280d876e4870ca18242527eb247994047dfd4b2601fb1fd1",
|
||||
"time": 1605220381,
|
||||
"saplingTree": "01d4ad84ae2c7072a2632e767ef7d38d6adb125d76b2780d0dc32eb607bf839b3a0012000001677d02da42c17e72416ef72a07d165a142d3ab2d1363b07219c44b220bc58843000001035218b5415347549dd0315585ee95b8b1f811d81aca93a551478e3b04d0a527015df8455e3415c857ca94d357d48e093391715644afcfec96656b0b33bbc8592e0001d722471e81e674f55400ad26e5730a88e4a63138cc741c0aa40961ec9ce8076e000001c4d6088cf30ed54a892b82679f9531247c8dcefd1501d47f45bc35095af2e7070001347a0b5c418e301923ccc684d984b80b8ac1f52888b6d6021f7fe3676eafda1201bde7578541c07920c1bc94312596adfcee32519cb80a95bcd06a1272c127ff020001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1050000",
|
||||
"hash": "000000000174c1aa6e70950d51e574311da9fe75b0ba66c38785c8934a00ad3d",
|
||||
"time": 1605974807,
|
||||
"saplingTree": "019c0533c31d2d90c2ac6d8c9193bc9d57cb77ac24993a93202dd97ee8586dac72017ba6e3eae1efa5fec05a764fac9bd552caeba096e73840c4d6d71a46f3ca0f6b12016388275ca6bed5564c68f57c17aaa50e209a900b8f257b60c7e548d39946dc160000014d6580e971d6502e2ef9278cd5527f07c96ce26548d8e13e290708e517e6a40c0000000150d24a2ea34b9eeac3f79051e6ee9dbb8c482083a57ef1b1ccf5a1f164a00547000191cbb6ea3483a6a60f6598710cb210392b7469be8f650c7ea4491965e51fa51b01f0aa6de369775a9747bf7d5f034ee6a7604437f497ca45df942a45c53a99e93e0001e2aeb2d8da652922c8714924c41335140a40bd1c966dc611a9d88058839a881901347a0b5c418e301923ccc684d984b80b8ac1f52888b6d6021f7fe3676eafda1201bde7578541c07920c1bc94312596adfcee32519cb80a95bcd06a1272c127ff020001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1060000",
|
||||
"hash": "0000000002a74e26908b2caab798d341430d83a012aefb113d9707acd6eb5162",
|
||||
"time": 1606729750,
|
||||
"saplingTree": "014dccd8971611a03b3698c14e48f76d2526109374c0336a4aa0c0559567fafe0a01f0875252b1ec66c1835e362c19ec570b02b47c5a284477adc599f4868535370812018c94b59acb73c782a3b3b2d9c46babb6f6ef03e413b5816794518b839bb8d360011061c3e44c652e1ba70b85886fada65fc781b202335715489e21ebd64de8d92e01af2d971d01ebf43d4f1b99f500fe0a97e2068bf1ee03ac6e309316e7be88f221000143b8807fdc7a87ce8623370eeef95140aaf315c9e9df7bbf64a4faf34f4ba213000000011fe71b62cc883a426b1b623e3df4c91cae8de566fdc9a746253d6350bc48451c0001e15174bb37ccc2ad5cba5f9f879d459bec83e975afe6304d23980c0ab87fa34a0000000001891b1e6bfec42e97c79ec505c7ae1b584cf47d4ed8f6cdfcad815b02a5496f6701b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1070000",
|
||||
"hash": "0000000000e69217b151ca8349e3c9f0c5406877554924d7a4f90fb6434bed9d",
|
||||
"time": 1607482606,
|
||||
"saplingTree": "01ea25320fb5dffce1469a9018d2dbcd02bc16350761c1b24ef1f7c282ff3e70070179ccaff1ff2218010dfdb5e7cd8ab67c27c0f578fc94133fa2db58cba2a1b83112013d996c5be4fa4e5d75fb2f782f02dbaec8582a86a4a404397c2ed5bbb75b1660000153c736227e8f11677599ac536a2d19a2000035609833788b19062cbe075ea20200011a38e1599c81fa7039f159cee2ef7144f23c729ac1d235d1d81ea111ba1c8e520000000001aeda840e11db19f50b7b64c54de8a389e84a5e64e8c3ea8f4861503f587d9b2301802a64566b4a1fa471e350bd86ef0409f6b4712a8fdede1c537347f41506884100000103bcde16c3ed62026afcdeb7c33c7aae0bbaaa357e8d67a10457244bdacabf4f0001891b1e6bfec42e97c79ec505c7ae1b584cf47d4ed8f6cdfcad815b02a5496f6701b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1080000",
|
||||
"hash": "0000000001a6faf5681b8565d50145fd84547b534c5f869e77cb802518d14341",
|
||||
"time": 1608237278,
|
||||
"saplingTree": "01f3955ce270f5718bf68883ed37b3b9d2de8fd77be7bd95334fbedc6083f16026001200000001bd5dd7584bc157cebc9d63c7ee761ab453892482246aae3ef9db17de80b84a4b000195fa995a764f9afbd6c14984dbc72175f49f2259bcf0abc4a82ac92446532c44000168fb4180546c77370ff4175d40a29c357e5787f820e383028243ba623fce4e61017cd28108a3c64a8923444af9b7409eb5dda47d8536cf5aafc80abf62e9551b3501fc0832fb90a473de0da1ae7f62b03d547655aa82d1f279c5ab5a997d6472085901647f2444d093ad8668eac738fe0ff6b59b8191bcbc13dc53f581e64de755122a000101e8d7f1b32b8bc1ec539b93f6c2912c839a55c36c509711340a5cf6d1803a360103bcde16c3ed62026afcdeb7c33c7aae0bbaaa357e8d67a10457244bdacabf4f0001891b1e6bfec42e97c79ec505c7ae1b584cf47d4ed8f6cdfcad815b02a5496f6701b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1090000",
|
||||
"hash": "0000000000a48d9a9ec16a0ee42afba830507824086335e81290a55dfbbe0a0b",
|
||||
"time": 1608990684,
|
||||
"saplingTree": "01bba893b54492e05be51cc957d99591f60e3c30b7bbc134f4a79e1219721d023901ed7c0412fd24ccb7919323fbfe9af815adcc0c4e29e581d5e10cf06ebc5af823120001f98603677f74513affc6586569aa76c60766b4249a25118ddcefef18698a2c6300000001dd255f2e4e75b7eef0d5b4c077025792a979fbe44e4b7fde9a1273a3c4d6450401aaf59d640c3cd74fd1cb67396250e4b536de1d300a3c17783ecfcb5ddeec9d3a0001d94a0cf0b8ea2f688b1e107cf6a013747f5f0af6543552291adda1c28668da6b0001ab131afe01856dc81300246b7df2fdc556fcf764cb3bdeea33e83507b706a0420000000185555efbf4f751c9c4a7068f9c1c3303bc33bc8bbd3847a6fa5b6d5b3d547f6101891b1e6bfec42e97c79ec505c7ae1b584cf47d4ed8f6cdfcad815b02a5496f6701b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1100000",
|
||||
"hash": "00000000022262ee215e5b6269cf159b4c9fb89b8a38f70873dde8dbc4c64669",
|
||||
"time": 1609745782,
|
||||
"saplingTree": "012f5514d9c08767fcc3c8c2cb6d7e00cb68ff2eb69ccc869821f3c901a495fb5c0121aab76f6b245f76d9a89c82a5ba88c6b427bf0154f1689520e8a24fefba106812000001c2965bb8fd73128357ae46fd0451ff0d34a4b391a4a7d54c8d00dc00277b5b2c0001bb34e1eb64d2bad151a251de23fe7ab0d92c0d8125fb3c3aa8ce0a772ac1f83701c45242a1ce86ebb47c2abe6ef76f814b26f56b5883e5f0c520266563633e5d5600015b9ea4977075a83dad3e5e4547f93369f81807b960fa5d0377fea025dc47fd25000000000001103bcd926a1c140a15876ce5dd6bb4e0a32172f9fde5de340bd11fdd87d696670185555efbf4f751c9c4a7068f9c1c3303bc33bc8bbd3847a6fa5b6d5b3d547f6101891b1e6bfec42e97c79ec505c7ae1b584cf47d4ed8f6cdfcad815b02a5496f6701b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1110000",
|
||||
"hash": "00000000019dd5701deda91aee19f653d7d89983fec6253d5728b738ce1cc575",
|
||||
"time": 1610499871,
|
||||
"saplingTree": "017f230e3a91ab56970595bc9037a6bb38b69390070076c46f9a3d364f96171701001300011ebe10c1b67824d2b71e48010a03f817d80963999e15e3b0364eeccac01e2e70011681651b3c5fe38d3c35445560b9a4599f9370e618c1e72f2b8a69efd47fc9720001ec4a3829e823478478cc46af80d6d4c6b947b422dea6b525b18986dde5698a370001b52370947e83349c731edf7c5ef43dc7f9b14ca47074dee8fd16b14c5663fe4a0001d5b32170d5ffab2c95ac01821260f970881c8a3fb5770aeac7739490135e152f0000000000000000000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1120000",
|
||||
"hash": "0000000000dcdf40c671af85eec40424e0475fecb06355fb5e37efda51f5dee7",
|
||||
"time": 1611253575,
|
||||
"saplingTree": "01da06acb487d1ceaf8db4dc5ad8183bc8b4e9a4b020188336624bcd83adb7884f0013000000000187183a83d396d7d50514a081195ee24703202e0ec9e3196eb3bdef5e34354548000001d446400bb7cf59eac909c183eb8685cf6cc4517496708ad2bffd07acc46ce524000001e240979184856d63a9bbf849eaf06963acfc861af5a44a30e82193792d58fb4100019498b9b148ee924288e2225a474f2c9a7739500cb3c4821a52679f6a0a26d41000000000000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1130000",
|
||||
"hash": "000000000009bcbe15ce9e005c9ebda0a1c5213b9573ad29c3a86a1a9f3f6586",
|
||||
"time": 1612007546,
|
||||
"saplingTree": "01cdb094138ff68752216908f08ac0978b612b5b928b1613ec79275071f1c968710013011f9339db47ecf1f7510683dc263074e79825ffb487fccdf81a9f4c748024024600012695a02e2d0d49c3cff6192b46d4c4cacbb86bbd4cd88b6bf65564070bea0f20000195556413de9fadd73e9d5ef1cfb905a021dfea4ffa2f1cb6204438c60d665b5b01c1125574436b79f9fc6364ec470f453570fe7ad0e313878b809a525224166d580199820bae76e624f5c06b7f744e6a6ed57dc03314e91aefd883648c7fe0ff1370014576446ae9961962903fbcd41c9d7ff2d8c41b77244ff39e2f1233286f95301001df16b424017aef9e9138169b7e183c2613ca6ebf9680fd27c9c280c194d0772f000001fd3610fa060d8eca861a55d6bf0042b7e11d67bdac285b4677c995f2b466276101e5f0a7191091bc4ea3b06cc6cef4644b426122d74b91cbece497f7bdaa19526401226d7b2448f34416550c5aa0730c58f1eb72ee60cb9e9d61bd66c81d31b5ef51000000000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1140000",
|
||||
"hash": "00000000006d9c69d8c4d7818dd2b9b106078317c6e881eab86ba41e4a546337",
|
||||
"time": 1612761940,
|
||||
"saplingTree": "012afd078f200a60fe586c8ebb81208bc6b656f2c24935ed9ae483606368c6101c001301e0a84c415504116132c953c1885150a6335bc3293aa17c89626e02406f944f39000000017c0a218b969475ad665bfdea14177bd73b8510d451bd1f533f29d5f86f92a14201faee45bdbbec94f64fd8b641b3e2c1c473880b14a86134d766b9ffae127f0506014815011726d11513734103e47971902e4e8c1245ab2b96107184f2978a13cb2501eecd48ee70785ed9d31c4edb6da31287fe1b42082466c787262133017fe3ab210183352b54c9e84bed4c1fb4c31dc1bf690e88aec9f477f6e00d51a4bc918cba32018b702b3a1bb47c1455d5ca00cdb6d2eb72256f421937dee7d5453e7108c8df3a00017c41a5948f315986b60909e07a15c2639fb1b13a968aaf322d654aa1e823f60b00000116316e325ad5299b583121e42838f9cb432a88a7042cfeca8d36f8f5e86e234f0000000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1150000",
|
||||
"hash": "00000000012e01251034b1cf6ced448544f350f023280e3118cb23863affbb58",
|
||||
"time": 1613515993,
|
||||
"saplingTree": "01a4948b191c1169242c0dfd9c287ce1ad06a681615ec99620fa5936bb05119d3000130001e9eb3a657f63031a386c64296e66b98374a0d74280132543535eb82180a5706801e0807fba775afd5acc8714c1c33ac709faee472841c3874ad477e57a01a01a4c0000000001dd716f606210678f0ce78fa535ba46a5cd1d0f22f7b237491e0136e71e51916400012278350ab0e251df819aa820ab100a1264bf5aa73c94bf5968730fa61668996001430989441afaafa2f64683ff0424b12b5051763ff094a88a4cc96cd00f09b8650113625bb6dc4092d25f906a05fd3e2f1a7c5df03381d0e0760aebc2ad0ecf0f0b01582a57901af4736388425e1bd26fff5b23fe6c74d10b1fd7145c46e57eeed609000116316e325ad5299b583121e42838f9cb432a88a7042cfeca8d36f8f5e86e234f0000000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1160000",
|
||||
"hash": "0000000001a7bb6c535053418e8e9e1620c3e5611eebcb15218a0584754e8980",
|
||||
"time": 1614269669,
|
||||
"saplingTree": "01ab89bc17a2fae59a5170033dea2302d8825a71a0e3fdb1a01d50c44fae59cf2a00130113607db0515d576fdaa165b61e9bbebf997bbf039ceb75aa04ccb3dffe492e5f000129140c523912d51834243412016078da091020f5c02bd498cba160342d20b92101f842c5a07a703b080c4d73cfa95460265b10bbb6c7dd5ba4cc50c7e24c4f380e0000000171df72fb201bb6c7f4fb3c9e06ac94ce0eb0f8c0259719d071587c3e5a74e00b00017010f179ca3f1dbb0865b00a418114dbdd67e031e430ee1bbea1b30081f14236016068bc4c06705014dae9c2a5f86f6734b08e39fb4ebd21366531dd93c0d0561d013ecba16781a5b5f4837c648de2a32f96c3d635a3b9e3630dc1bd1b1f4276be170001bf57fd5f3e010a317aae249ee1c4583acc2688c9c22f84710f0173cab1f151560116316e325ad5299b583121e42838f9cb432a88a7042cfeca8d36f8f5e86e234f0000000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1170000",
|
||||
"hash": "0000000001225d341f424c445c71b13872ff89300210594f3d9d405ead56f539",
|
||||
"time": 1615024297,
|
||||
"saplingTree": "019968e9b4725455dea88d986b33458d199353a26f1349abbe7f39c3d72f82365701abcab1e8db711ee75949326fa6a0ad302776ff40036cfb4c1c770ea430a1af51130001352edbeb2884d8ed1f39c18125493783d76f495a88fdb32c7e5d7e61de1cfd7001b6966af9deaf4512b7922f65ad099fd11f1c3d34d80c93bf4e6d9aec18686d5201727534576ffd6b0cd7146d0e3516b3c9836e9ba5965e35b35033a3add146254901851e9189feb9e5f62c2df5049c930d0438083c86958cd179543156ada7da910f01392e342052ef08587ebf642bda8e3948b437b915a5d1856d59ced86d9e640a5e01199e767f23a7d41044303fe914e98c9b059b9ce0b85b37b7fa26e43d4fb5b72d000000000000000001089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1180000",
|
||||
"hash": "0000000000521c0d55ce1765092caff595acb5d546147b192fa9272051f55f42",
|
||||
"time": 1615778593,
|
||||
"saplingTree": "0102a70ba9e31d6c24be0fc283577072d815d418da5e1b14cc81923f7bed308460016d9d5c674c15bdaec54c70d528e94be910994213683e3aac2fc94092fd8b4a5b1301dce7f293aee6c2769fe616dd47d46e79b51568c8bc08c67198b20d28eaf12c6f000001ff83e392690a332103433465b2034df2ab6cf35c164149a3951bf0d2bcf2a96601d019ed97502d718643c32851eb1acf6307861a3fe0ce3174697b0c2df4568568011d8eb058dd0a73a4314e04f12a6de50dd18879ee891db5a20c890e1c707f6f01000001cda4d5ff76022119fed8ffb94b8d3a26a0db37a1a886a0f31a57d1c0ce03f106014fb59c2d5dd4cc176d226c57b48d4dfc4d200088d5b69cce675f84e885ee5c1b017d5b561bf80be788ebf12e901e4c544cad95e24d0b21e6145e1718ec9dbfe91101678fbb171e5715c63bfc888d345e755c69c6dbfc2818f05b6e6ca32211ffd44400000001089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1190000",
|
||||
"hash": "00000000019caeb64ab8caa12e1dcdd9a8f391b063ec252887c91526b7ac5e0c",
|
||||
"time": 1616531745,
|
||||
"saplingTree": "017ae2fbedf5cad68ee80df0ae9caef9e0168914780bfd14eae016e2fb89068071001301c78a8f9bfddd9a1f0076048c48d1d58298ac6f4368472a39b0b240d540117c4301b58c9284084704437af784307ab0a334dc9c7aef588bf7d26491274d79c4471301b0af0fff110293b555f17d5ced0a598693ff4cde3e680d988c6ccf498846753e01bb9d0f21f621448c03ee49de4ef3bf0faa4844544f9668197ef5921164d2401601a15d695922c5441e80aa770861f91a97dd460561515d32c9d06bd3c6f98ce26f000000014a772e6ce520bcedf07793ded6148fd3415ecbdd89c3efe183b6048f1fb4791c0001e281c5ec71bc1a301ad0d285f6f1aa2552907645b02f9d43160f5354c2be7c63012b4d8d83df48c8b5c35752268eb71a428aa05103809a887fb36519dedbc8de27017b610946256293c523b36cf95ec60f2c346d866d98d1276bbaba22e46815516d000001089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1200000",
|
||||
"hash": "0000000000347d5011108fdcf667c93e622e8635c94e586556898e41db18d192",
|
||||
"time": 1617285467,
|
||||
"saplingTree": "01dcc15a1314130c3ae61401ea76c0c1b32b0c44d8486fb6cb8764af9f6dc03d540132d6897fe6cbc340c786a129e69aaac499ba9ae33cebc0ae6163d02dd15db4061300000000011b153a8182d97d204f5ddf0cdbe66543d67ef3b25dac5602987d4dcb57f35c260001a3d62e7611681256eb34d5945fbbc085ccf1d04d6a507295c3c9724f6e899069017fd5efaf2ba1e3c53b0039674b6667e401c83834d53b0970c7ff3a6dad684a540001c77d47e4819c69c3a473b8e4c660bc73c2a4fc0a067e5ad21c6fba9aa031c96e00013729a1708bbe29fcd683e3e3337e8b8ded4fa6abb24cb7bef506168be7fe8a5b0001df26abb60966dedb257d90ef08aa7232474229e08e62ba390336a07c4775a7260001089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1210000",
|
||||
"hash": "0000000000a821109fda9b1d0acb83d4d34cb9e614e88a9ad4670f6efdeadb9a",
|
||||
"time": 1618039638,
|
||||
"saplingTree": "01a1600bc5514eeb3ea29fce0a0185037c3df92e1bbd5c8cf70a1b1da64ca28b6700130001b8f68e87b49aab3680df1b451ebfd640737d9d353561d048ef4f2598c6ddf8050118874bd135fc959ce3ef0261a9b67186694da8a26b87af2b23035ebf231ad312012d96d3a7e8be0546973ac3ea0523f424cf4b68272c167667945d9be7c187f23a016bec2e122ea858292faae0a76b1a8207742e0c629278b9309605530f1ce8161e0000000178e04f6a50b625ba98a4683e7c28aa6bb05e0c5246ecc28dcb5e89682be5bb2f01c19b696f17fb6fa0ac7904a2f827587fb216e785746b82f6e26f1b7768fcd50f000167d59235e5bcbb4e7466ac0182f48d63de7b48bfa195d42ee483acd072fab90d0180541ee92782f67894645a8a976ccd0934e37c13fbbc7cbb501b7ba78d1a902301df26abb60966dedb257d90ef08aa7232474229e08e62ba390336a07c4775a7260001089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1220000",
|
||||
"hash": "0000000000751d7ffb3aac6f6a66ed01aa4e991a96162c1749dc556e50fe6df0",
|
||||
"time": 1618794113,
|
||||
"saplingTree": "01c3bbf34ce189a5285144c79cc37c4e76385b803947819ea8bc43b1e8eb2c020801861a52e2fb19b150b0b7c7d7f879bd1ac4fa3a11ac9763db4f837f4db048896413013ddab8738a0cc8e00e96c7487d21a9771a7986b7e1e5d5fb0348d46caa18360c01d6b0010e9664a01a47a8acc9bf073255f6fb62f617cb491b249671da85001862000000012fe6dc35a5d51af73b73854855f9861775296a9c56d6aa9455be2762a101d7390168ee29d7b083e5af0d1895b2832a4fc63a9c7b6cea37b75d17d28e6f5842ee0c0159781dcd759c87f8bc8622bc19f9a8732c06b52897bfb6e0ddcbadb111d6a95601036e008ad224efaa9833fa9e790192dad7aab0ba71bf872430f48ba6aa0d1d1b00000169bce4bc91631cf6158d978b5ce6ad03f3e4cc3317c28964b575ca736f8b4d68000001ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1230000",
|
||||
"hash": "0000000001bcfbeb53fd5e9647168265bc5fc8e8622049b9cdd07098f0f51601",
|
||||
"time": 1619548843,
|
||||
"saplingTree": "0164566f774ea1f6d7cf21a9fbf9cb3f2e7d1e400097417822a7c233422619c3710013000001bad8de55d9adf4d4ff08f4ad172a5ee80aa0049974c5227d2b07474078f73c2a01e8d5303b72f034efacb9b9aa0697aa5568bf6116f2fdc1e528c03b76ce569c340000000000015334d52667d65b8ff3dcfb3d6ed72c46ad19ea9112813aaf344a3e614eb9012600016cad8c39b711691f2bda74017894c657555a8bf7ef913931ac4f7ba0b48a30120121c25bceccda091622bfac1b7973ffaa638abe1f334b3b56f48dc93dc549c9070001ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1240000",
|
||||
"hash": "0000000002473bf56688195f05b6f5acea0f99506fca40ae72f2ab8c1fd7390d",
|
||||
"time": 1620303759,
|
||||
"saplingTree": "017b7c743693588568510844e4bc181d12ab6433dced0542d149cbec2b96ba526500130001cb8eccc27eb266385f9e4b880ff337b2ebdf10238dac74414fde8937dfa2264b0001bb0cb6201a0d003f1e4df17cfd4815c382ced6bf038468873495ff5c9c25412501ba626167f23eb767c229a0498b37e8322326779a3a6454ebcefd639056b3e64400013ff350f9e880271ea2c713c509491605ea24589c369262c18c417fdf1027305e0000000001849b1538147707b1c880e7eee02f29ada52e8a5a6c3a9027087493f41bd8304a00018e7922ca798cd3e26d3369ca2425ec19baa7d79407a979ec1090ae48fdcd094a01ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1250000",
|
||||
"hash": "0000000000f3d2c352c395d66866032bcb67094228dd4a27e561b1c399ea612e",
|
||||
"time": 1621056898,
|
||||
"saplingTree": "01c9a0dd6f6dfaaafe6ae4b432c2d1c41d2a73e564c8cb6d2c5ab637c7001a2456001300000000017da32b486a8ea9f13afb93b99d2b1de69aa969e7c2fd7b9ee958bece70c08d6b000001b3a4486b176dfcedc0b3d9287c0333ff464ecbd02bac7c89bcda7932e6a0a36100010d451c18b56877b8a11cb401ab7024c82b9669ede862a53e461087f57220035001a1c5260bc4dfe010510b8135209c6f64229965f71717f1e693abdcf88a58f36700012f0bf70e372e536fc3b76ecd7e2b69eebf2fbcf71b828c64b0a8b99390fbf754018e7922ca798cd3e26d3369ca2425ec19baa7d79407a979ec1090ae48fdcd094a01ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1260000",
|
||||
"hash": "0000000000b67fcb1e315f03576a2e31760c53a8b64946c9e8a39e4181e4d9d6",
|
||||
"time": 1621811756,
|
||||
"saplingTree": "01bc3f08c90eb18661e7c8d044b1329b4b06a283aac02fe2ba34fad05783166f0e01699edd553b5bea328345571a01e700eefabd7ffec02efa80bdfdd72fd6b50451130001f8095f6876e0a565958fa5aa1c86daeb70cb1c0a911f77af030e8502ba140069012c2172fc55f196ea1cbfee113f2c9c70a7d0957bc109d8e89861ab63ce44fa000159c63c1e52060afabb6a646f0f60b06ea68582dc1a6ea7c942c0fd985f39bf19000000000000015e81c3019619cab416e6902c8f40b594d22c8ad56e97b555432a87b5b59ec44800000000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1270000",
|
||||
"hash": "0000000001cbf71da9e2d9bcf76453072daffc7d14178556efc01b7a17efa160",
|
||||
"time": 1622565837,
|
||||
"saplingTree": "01b0990c08b4e5d81550b486d427c04d8ef4c72ee06cca3eb79507d77f1ba1de5101c611b66c4bdac44247f60b2b82fc321dae605de4aa7fe2a0d3a60d8c22931a3e13018cd8cc518349b4c4b0ea0119b4e9d41f4898ff00159c97c34fdeb8d4294e8a5e016f3af588d6ecef931e5bffb9a61f1c1843bbf15d766e7cdf380b7418f424d14b01c505e0fc3a93c0a32a10c1f97fd0f3b527a1adc7e6978b8b8039e1384ee4e16701e1096a00453965b59c338cad0c5218bcb6dbc0548d38ecc125dc31567f5812550103cf5609127b65dcc11cf3ab99e7a50819a1ef82face5139d14d766eda4e8e6e016fb5e140322e0fe6ce36860068df80ab1eb7c113a72c6a9d54090bfff1b09140000000000000018ffb9849febf4f36b4ab9053782cd279e8f6c867b3cb174e39103814fdec2a5d0000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1280000",
|
||||
"hash": "0000000002b968fdae8ef414b44f477457faa1d9276e4bf7d00790a899d3a344",
|
||||
"time": 1623321153,
|
||||
"saplingTree": "0130af1f7c9775877e195a344a66dc49c8e9acc107a803c6580f021b52030f69190013013da36deb5334a683635dfa01bc013656291b40c167fa98569fb2cdec9210632c000000000001c4962847d99e39ae73d2d9961e096de75921cf48d2e61c09db2d8c7f04c3a54d01fe919352b3cead4ee760e7657fa230f5fb6221901488d4b72eb17d52e89d011f000001670b7cf05053459789226650a54d5c82784a9494aa0e84b2aba27d9bef663452014be51a412e65e1c004e5fc93e46ee73a676e520aa67c6549d1ff63ca41353a03018ffb9849febf4f36b4ab9053782cd279e8f6c867b3cb174e39103814fdec2a5d0000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1290000",
|
||||
"hash": "00000000014836c3cbc011276cbd3702a76a1fea7eb2c0c2c257321220376450",
|
||||
"time": 1624075741,
|
||||
"saplingTree": "01accf4fc3dc4233bbe757f94e0d4cd23b4aa2e6ac472601f4f53ca4dc86a8a05901fae977171a6103a0338990e073ffe50e29fc8bf0400dcd3378ebfe7a146ed1481300014f7b33dd5159ac66f2670b7db8925065e7154e0199ff7ee7559b276ba56ad1200173e9881f21357e54027a4275114f0f6ad4ca17143554182f63c77f3288a23a20011d65465ab942440e200d429ef892452b4b05c5b21e9a6e6d968a719c67b5e85b000000000000000150926c74975e2d8ff095defb75a4a6d9f17007e87a74230a65a3265d8f45032900012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1300000",
|
||||
"hash": "00000000027222bdbcf9c5f807f851f97312ac6e0dbbc2b93f2be21a69c59d44",
|
||||
"time": 1624830312,
|
||||
"saplingTree": "01f5a97e2679a2bb9103caf37b825f92fcd73fff836234844dfcf1815394522b2c01526587b9b9e8aeb0eb572d81fec1f5127b8278ba0f57e451bd6b796596940a2213000131c7ff90fafff6159b8fb6544a2bcbba6c102903158fce8f9a9d3c6654abb23300013555cb7f4f79badeaca9bf2dca5a8704f0929053d50e95c03002f9a4d5286c3a01ad3557e11c1607ec888dc84f5f8899c3c79fb1f50b613946452ec7dd5e53763c0001c4583f4482b949390dba355fc8fa63019c83acd644ddd633cb50211d236f870600000001088da0d78eefd0c222507927e403b972d0890d0c31e08b02268fbe39ac4a6e170001edf82d4e2b4893ea2028ca8c5149e50a4c358b856d73f2de2b9a22034fa78f22012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1310000",
|
||||
"hash": "00000000011ced7ee934fe237a80b74f04901db85fa82a4eec008fbb11ecf2e5",
|
||||
"time": 1625583320,
|
||||
"saplingTree": "014be5784c617834157df657bafbc419ca3930f25deddaa9f29fc17483b235524800130001cc95eae2b1c3759963275342e56f1b71cd0bb6a4e2c0405f0108eb96f668851201f559a0e5f1b41af70fd6daccb0a48c3c178dfd8d25c09ff5152abded864a264f000000010bf2a42d0faed94259d4747ef48adaedf5772ebc93211f89be1612c1a9644603019f067dfc649ee5291bdf3caf7a307fb939eceb716c5050b63f620f0e41e1724400000000000001b35fe4a943a47404f68db220c77b0573e13c3378a65c6f2396f93be7609d8f2a000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1320000",
|
||||
"hash": "00000000001dd2deed141caa32a167fc9c11dc2d95f76f7cdda9f50326411309",
|
||||
"time": 1626337252,
|
||||
"saplingTree": "018095da27911b257bdeaa799f1c36be3767f8ef268e1f232263e88df0aa7b4f6300130001e4e456c3aa306bf54a77176e2462715076b3586fe36716bfc3ec113a239fdb3c0000014e4fa821d93b8944df3d5500fd71b3fca0f4c2a6add80137f7b3379fdc56a4260000000000011cc990619b9371b0240cf3038e8b3b0aaa6c50e78db250a77f0b4c63f49d811a019b4b90179cf9bf8c7556caacf6acb77ac97620777d2dff5be661e576cac55f22000001b35fe4a943a47404f68db220c77b0573e13c3378a65c6f2396f93be7609d8f2a000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1330000",
|
||||
"hash": "0000000001126cdf85b463d086c6bf54d67094f6e17db3b08e38c2b4b16de926",
|
||||
"time": 1627091654,
|
||||
"saplingTree": "016cc0651c73617064bf6de5962c99ec81fdce76d58ac2aab8b881e3b35991486d01593438b57eb9cfa76d5ea965b77a0304ff46163846c7c1ffb2695419a8dbfd4c1301e9766721a57a35e08763a27244291cb7db2ca67a711fd0569fd78aa675a21e1b019a9908d737043f786d7cdddbb22cb4d026c6c3221d34bd82bf4dc55acdd847640000000001de097ad39006362760b514f69a3aa88c66ada02085de26de23b522167df616320000000001a090ee174239a34a5d684425d09006d238c6075a61c5842d0fc26043f09ccd7001a2b7ee187c7b8ce18ebda8600bed7695b12f7d35ac971ed6ee67184a7ceebd490001b35fe4a943a47404f68db220c77b0573e13c3378a65c6f2396f93be7609d8f2a000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1340000",
|
||||
"hash": "00000000031bc547da975ebd77d9113b178053f88fb6a1d8511b4f8962c21c4b",
|
||||
"time": 1627846248,
|
||||
"saplingTree": "01ef55e131bf5e7d6737a6e353fe0ff246ba8938a264335457452db2c4023241590113f4e2a1f043d0a303c769d9aac5eeb8b6854d1a64d71b6b86cda2e0eeee07621301206a8d77952d4143cc5ba4d7943261e7145f0f138a81fe37c10e50a487487966012fb54cf3a70cccf01479fefc42e539c92a8215aead4179278cf1e8a302cb4868014574313eb9fd9ee592346fdf27752f698c1f629b044437853972e266e95b56020001be3f0fa5b20bbfa445293d588073dc27a856c92e9903831c6de4455f03d57a0401bb534b0af17c990f836204115aa17d4c2504fa0a675353ec7ae8a7d67510cc46012e2edeb7e5acb0d440dd5b500bec4a6efd6f53ba02c10e3883e23e53d7f91369000183c334e455aeeeb82cceddbe832919324d7011418749fc9dea759cfa6c2cc21501f4a3504117d35efa15f57d5fdd19515b7fb1dd14c3b98b8a91685f0f788db330000000018846ec9170ad4e40a093cfb53162e5211d55377d8d22f826cde7783d30c1dd5f01b35fe4a943a47404f68db220c77b0573e13c3378a65c6f2396f93be7609d8f2a000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1350000",
|
||||
"hash": "000000000099e0aeebc419b0a28dc4b1a1dd4d4aec21520866cfcfe5cde71ca5",
|
||||
"time": 1628599615,
|
||||
"saplingTree": "01cf732718d25faeb79bb9c3864bbe0d61f40c414376eed022ad87f4a4b41f633200130001dc1d47b7d1130a55a899e620a4804cdab77179c5b72ced3a3e49e45a05b5c66d01b41601df42540130bd7068d083681ebdb2fa63cce4d056bf1e0f7cf9aab71c2900011bc98da07cd67743562cb8dbfc942beaad4a6006c645824d83463f9867c3950b0116451681d54938a0bda548f5e88997f579935eadbebeeb157a488552f671d44a0157bae7dc312ff7f4993badaae9a23dee5ea45f1029c2cef37e0a4fd4bf5c6a050161ad7756b9e26b4cbf783e65d5f1a601c212b43fde7a080e41e0e5029fb87c120001e38bb9933f32dc63ea64b17178c2b721beed5f21b8153e6ab09d1b5115ca172f01727947aca912649b1b6adfcb3894776d5fc82352de31dddaf47348f3fea3dc4901be844a7587ad14fca8977a88dfb91eb9d1fd80a470f48b71b53da8864ef9c06500018846ec9170ad4e40a093cfb53162e5211d55377d8d22f826cde7783d30c1dd5f01b35fe4a943a47404f68db220c77b0573e13c3378a65c6f2396f93be7609d8f2a000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1360000",
|
||||
"hash": "0000000000f5016305ac33ee0cf6abcebe4fa03bf8fd90a7ab68ca15e5610ee2",
|
||||
"time": 1629353998,
|
||||
"saplingTree": "01b5f8c31991a15242151a45cf5001f80fffbc43b6126cdf35af4aefa8de4db13900130001e14bd96a65729e93b93125d8e1ade77874508a819fc23ab46e03785b818bf80c0001f5b285e882827bb5be3eeb6d2f33c61abb1048b4d94d02a9b2d51a27b93a241901d431f7948eb3a2d4854baa349a275e3ff595af37b613ec693a227b5c4513231d000001eab8998921cb513c8aa655549a179b90c0208e3e5129d04cba004b679102853501168d63716073330d131b20c336026d5024030fa98333c072dac619240a41db22010f08f16fb1f4aeb3243b6d0c3393a5cd30ec89ef1722f8714ba0f69bd278883900012c61289308f10f2310056bb980453d26dde5ad1ab92b5af281f13a63e7beb44301f954adcf1d5d5347296ab24408fe7714cb3d08f814a90c516e7dcce1e73fc932018846ec9170ad4e40a093cfb53162e5211d55377d8d22f826cde7783d30c1dd5f01b35fe4a943a47404f68db220c77b0573e13c3378a65c6f2396f93be7609d8f2a000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1370000",
|
||||
"hash": "000000000266f9dfa1e64e21f9462ad6a039ab9fa7d088ff4dcef05f19ff6a0b",
|
||||
"time": 1630108279,
|
||||
"saplingTree": "010349f2d44dd5345afca2e43fcebea8830ba1dd5008c033d652e5cd1e183e5316001301a7c4738bc91c9f34e368d05651a6d7f5daf5055ffa69859bbb04911a54d66d19015880f32973a54a1ebabc2cacfe0b12fb6f1efe260ab560cf393b5fb7b1568a0a000001cfaeb4efed5a78e31ed76e78caa297010591797f34d614306805e12adbb1b402000000019408f50fbc9bd2582e9fe881b6b2142a65aa32d09109640a312cc5f0fd9f6560000001142382a32d4e6140bed51bc21866afb918df31853d4afd24df0e8fe88d98180b00000001cdd92a1e884cf1914dae8345423203832ec7bbf9d95ae50b82e4327b39d6d9120125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1380000",
|
||||
"hash": "0000000002b1828c6d6780396ffd9913650d59a2a83244603ba6966139b8b048",
|
||||
"time": 1630862790,
|
||||
"saplingTree": "018bf84fec6b6f338ad0f51d89b8d3574e09933518004eda5f2fc11b8451c1552400130001376c749ee4fe13b45f6b5fcdcf688d6fe56bbb9307706e7eb7bba1b230ae5e4a01a4f407e77adf32c718348db873e0ff291c613fd2c41e2f11e4e9781ce0ec151f0125bfdfa026e4e5416be5708fcd9f7be18e4b4926a1ad9926f1ebf1f7a9c0a32c0001e3284f49e37f78a3642a99705e4e0948021df437bc9fc7e3df2d2340ec6cf14301bcd2d3043f1798abfbe7aea6da4570e6db84d829c28a617e3732727b0182d30801c721b79d73a4ce988678d0c3d3fe401eb18a7cf6680a8ce805601e4192c1e34d00000108b51ab721697bf70c78ae720293a441544dbed43bb9d81e70ce63b3daa9e71e00012bcf3a07cce1cd7126e5d70dbd8d1a1eb88bc0bf9733b0ef311472245137b265000001cdd92a1e884cf1914dae8345423203832ec7bbf9d95ae50b82e4327b39d6d9120125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1390000",
|
||||
"hash": "00000000018460cc1bb94ca574c6680737634446b3e522f990f31f4ae1cd7b17",
|
||||
"time": 1631617484,
|
||||
"saplingTree": "01a34dc63dd0f69bce785ffb85c4814ce2bdc75d5ff262f1de698089e109fb51730013000137183b73e281f7d00160b011135ae7e1c7eb462f5d3092c0395db3ce95793437000000000001f4b1e1c93f7b38bfdf80a40cfeff23229ef7a10863fc40dba079f2d4a379676b01c76bbcefffaad07e3da5bd4e88b095bc9ddaaef2ce80bb4240ae2d75f379df140000000001a99df0908eece22cb06f261ff7bb1354e0c8fd9e8dcac21f9ee77083d9a7c7190001cdd92a1e884cf1914dae8345423203832ec7bbf9d95ae50b82e4327b39d6d9120125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1400000",
|
||||
"hash": "0000000001155ecec0ad3924d47ad476c0a5ed7527b8776f53cbda1a780b9f76",
|
||||
"time": 1632371451,
|
||||
"saplingTree": "01fb0b81a53dcc442a4c5573dbfe4371e0d5bc2db326462bfe87436e2277fa410600130001365805c2f50a6be7398751227dbfafe17be7c8a8e302ce280012ebfc5718954c0000010d53a1f9214a068f183be51c9b9457acb4921b254b330810b058816c342453600000000001e37c5bfb3df3ecc5af5442e054f28f9685f9af81e538421c26e1cddc12af76140178dc447a89d83b02f6f24f668b0369418f706bb530670bb5f0f74923004d6a3a01f50ce69def3c60191475fe1cc716cc3c80da1c5a4410b16f2ac5665b55bfbf630001a99df0908eece22cb06f261ff7bb1354e0c8fd9e8dcac21f9ee77083d9a7c7190001cdd92a1e884cf1914dae8345423203832ec7bbf9d95ae50b82e4327b39d6d9120125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1410000",
|
||||
"hash": "0000000000783ec65dcff820068a1578009cbf27f95549422146a4c347b54d2f",
|
||||
"time": 1633126606,
|
||||
"saplingTree": "01e2138a08dcd17c989b78ca3b93338902dcd48225d058ae9285f6dae91205ab30014f8a5ab5a88a4a644ec6919314a2f45bb29122a4c20ec3717864aec62319b315130107decb29d5e3afa8901fcc0349eced81687ca8b196156243c9acb80a4522013a0175f445369a9f69037b90183f0291a543e47341cdb0b5aa9583f99ebd2caf774a00000001ca1619f57693b8e846157d29ae9901fb708525f0a23ec8b23679efef6f961f0b000189a3336c6c5385409fab92df142f9fb788262afab200a0aeb7bdd09620c0c24c0001a3d16ba2806bc3b63af53662a7ee6b72fdf9803476cf040cc91a172f4af0ee090001a934e6d68646c32020a9ff0efe3b2298f6c21861b0506a84dc6e0a94de62e96b000001e4e58f392013439b8fbc5535d3f71e0ef1cb2523a9247270bb8174ba9f94664601cdd92a1e884cf1914dae8345423203832ec7bbf9d95ae50b82e4327b39d6d9120125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1420000",
|
||||
"hash": "00000000004a50f9ddcc751aad5b92ff770f3201e0f385b71662616123b6711a",
|
||||
"time": 1633881192,
|
||||
"saplingTree": "018ba655ae600f6ff0720957a5eac54b188116b9b85143ed41663ec8df6db3b8350141715de7f4db41039cfd5f6269a287444c1b61121a3de5df9b09b286e5a4ac5213012f372c56e223d8238110632002fb2dea22cf120f7831625a9dd534024ea4251f000001133894aec7ad649e3f5cbd7a2ed7fecd8187f65cbf226369d031ab976094ee5501101a21e19f693b771ba1c7320cd57059f7d0b648864b96664ffc2228db066e180001ab07813144a7771a9fbee4de0818b6daefdc4b6abdeb9384f81139280f40c01201edd61af02dc2d1e135b30e6a24209f73d267c282fb617cbcc5e8a311c99abf4a00010ca0d41d60de2537c08aa4ec0d5dae6fcc0d9bc840fb73a1d79722a63a6231200000000000000001ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1430000",
|
||||
"hash": "00000000018865a445b8677f1227b1ab1194a790997e5b5c7419364b8b2c5ed9",
|
||||
"time": 1634635150,
|
||||
"saplingTree": "01c5acc382555e72951ceb578a4b3ee586fbdc5f38b813e926ebe3d02180fe036f00130001897617c720372627310c50150d0a32a4c2940d946f9c6097f89c10d5dcdcd31001b9d91186dbc4b7de2fc90923da31d28691b1ff09bc6c22fa285b2b2514613e1b0001d3fec2031df5b05cfe5ae60da0251c497b82c00109ec5995487dfba3dff5400300000123d1c05283a0a805397b6a27a62d1ad51921f7761d2a6bd0462780a411c6c95301a5bbeff16030f5314d7cd7f83c258068b35ba5dc52152423bced5c117a98011c01bc0d4b825129e3910eb822375b4467f1486ce9fafd82c90f744287f0d481545501bd6d46ab0ad0be85a948886aacad2279e0c3b073108a6c4cc093dd60b323454100000001034afd8677bf7cb50ebd76fdc07fb5a94224fd468b233a20fde901263e422815000001ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1440000",
|
||||
"hash": "000000000247c6016ad378f400845c262ae2b8bc334acc7ffd9fc6972bb67490",
|
||||
"time": 1635388721,
|
||||
"saplingTree": "0129cd246395f824714089d5c8e5d124bf63c77e497e7026832f2f611b9b84544d00130182d7d4ef6880ea43f8aaac00a43873dcb5fa31993f33cb9f56b067da8ef7f05900015e860dce80e82fc7907c318c398c2be0060060300e3a033352cd521b75d3b36e017e234696d965d382f2886a9f7dcf963b34ca7a4278af0ea04ed6fa944717705a000180b909079e5f2c198cdad9b9480aff8c6983660a2a9b37bb52961478cfe94d4a019f12a7920cb895a1d7f14d4b6d09e9811b1793f84e49b8e82d277e1df364903e0001b94a87395911403f543a636cd39cd36ae20981eda36a4f127ec25740caa5dc420182fb4897757ac93cd15afa9589f7d4099f42a4d8950c9205d019b8bc5bef597300000143de8f5283a022fa4df580f63e7069c638d61da854c2ba4a17f66d03b96d621b000001b10dab6ee286c68e34c1612f9839f6380aa9b8fb74ed935b1796a1eaff8262400001ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1450000",
|
||||
"hash": "0000000001338877726d97aadcb2d89e23aa29cb111602ee638e61a692d7c7e4",
|
||||
"time": 1636143269,
|
||||
"saplingTree": "0112c2614c9919a0562364dcde1027855f4aa9245fe0d49b2ee12fa6c96297de3d0013000001f328b54f7a63fd41bbeea1e633ac69019e9b4c6f9f8ada44eb28bcee4d65594401f579b2ab8c9c660e9e20e369c4f851a60b7bf85364ae681d181c382673cc3f1101fdacb99e8ed20cb11b74dfdcd009c54f5cf89e5bd649f3cef2316bf6eab7b11501a5100ce99536c8adcc7e043a27a71f6f3f75a045ebe39ca3c21334081a31154301b68c5c90371be375a8dc64e774a045d2156521d4113b9a9b8fbbb8042041ba0901b651bb9e52bd02c36d7ccf6f69106de4919f93fdaaccc389905c70461af35b0f000000000001c7db8aebaaefc738de4629a87cd963ed2e59fe50fdde855e851922aa9fda8e6a016867ec5a6ca436cad87f26aac584515939678fff9908ba3538b50338baf87d5301b10dab6ee286c68e34c1612f9839f6380aa9b8fb74ed935b1796a1eaff8262400001ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1460000",
|
||||
"hash": "000000000000714c69212852c4764d8fe9fd3cea7e2c9cc093bfb0fc2f28d6c3",
|
||||
"time": 1636897133,
|
||||
"saplingTree": "01a0fe43327293e68a9b9210f70046d9c1a38f16534d1409e22fdec1d771ff3e3d01d1fdaa37ce023cbfd83d5cd1be1895320f31675f293e65a6c56fd1c52df7ec321300000000019bf096e11c4bd2bf119f9a6b1d2e5197aafdb7b9b5d3f8feb429ef025b82756b016a9933f873d9d6ce371d04edbd6c6e3f8b868f07c448e2c98b231f23e153414001e86054fafed015ea0fbd113a5d9ae494c883821cb5aa3b0b827b2dda4fc60f3f01db4dc511bc38339083ac772a72dce39b8530328c054fb1a1de4d373f557c1b3801753e5cadd0dbab898f63da5f6dc38760e92a81a43b72b713ca82c4f02f355b390143a2058b9ccb1a223b72fa877a391dba52addbaa47b004ba164b72c18aa6d9370001f9066a8f64ab332788c28813df1fb83b7592270879480df548d98d59d40db947000141c1664f7d31ad73c6bc5b15696a837b067781b69d57120ac4175fc6483dd50400000185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1470000",
|
||||
"hash": "00000000015ef19e861665276f9374818b5bb02bc739532fd5c93ddfb9dd975e",
|
||||
"time": 1637652036,
|
||||
"saplingTree": "01f4874904f357cd95c58c5692beb1130dfab0f22b86fcbeb51a2557b007654d6e0013014f9503719bf0580e70f0aa247e1ad5572014ebd9e91219378b70fd97b23f755500000000012263c8901016717feb884fd56938839313cda60a002b5f4add086c148cb6256b0000014b9b8c5472009f068cd5d5e2a5a9bd1282af047406ac88e0edd348da9124d933000000011180d4d7e24b11637623ea88f71f3fc0db1b9d81067c37b13efc2a6f075e9936013ff8f7472d8c178de6fccaa3fb068c3c93fa2f684ec20c272b118692b664cd36010fbac3e8d4e0e6195a2ee0969bb0e6c0e372fe434fd305a2cbc27679bbd59309000185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1480000",
|
||||
"hash": "0000000001bd720c40fc18329ff4af94ed736cc33820bf443319f044061f7e4d",
|
||||
"time": 1638406121,
|
||||
"saplingTree": "016f96aa85ec89180866b6f3a9a70fa0220df1313aded06bb5fc6abb99fd7a7859011dd198f8966817d6a31031b8343122e66d44cbafb47173b975de07d404d52a2f13012f6cab3151d5ae90b712ba73ed608e8812b15b8a1292f4b02d6488bb5aba811f01384601cb96c1c372c5aa8de4cfcc62dce1340c61d5b906bec6052fbcc559661701fca71bc6572e0b2e6a68a2f152c010135cf0bd87ca956166105a21890de8a44d0001af57b83235dc045cd92f83eff4f9c4ed834976538d296ed1bcdc762144ec404c01ade0642f122069408a49463adc2c8f49cdf2a564949dfd71d7817e8ea9602f2a0144562bc812730731709e3b7533dc73475677b27c5c49750a524e4251c7b11520018e6fd5bfbf578d0c31c825cbfe653f6e1634e99b24f34a0e87bd344ebae69b300001947848e0759715c36146b5131d2c14b3733b295520fcdbc8e7ca73d29f40b31f000001521d49ad5f0e9e5f57209f8eb423ee72d96d0c930758faf71afca75da01f836700000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1490000",
|
||||
"hash": "0000000001cbf07a62da910cd01865af2e38a0a40995c806336e70c39143ab78",
|
||||
"time": 1639159413,
|
||||
"saplingTree": "0150c91ee95194e91f6412e454ce1eee23d9dc372415ec6b844332db5b0dc3206e0186fe65e85b0b4d591532d4d5850654a8d5e5bcce1b5cc9f3d38cf50b5bc65a5e130001adcf96d90ce6f1f64af82e22faa9f9db6fe4469e49010b3dc60fa1aa42a9d64c0162c9c79002547445bbc57afa4d36855d1a5c295751971fb274bf388465b7745d000001ba8cd6521f522c4dd5a60ba65638ca0c192f0447c350df26d3475a4c04aeff140001ba36032a7f1aec7ca10044fe1ac9cad8184eebd58a247fb3a60e9c7703c8c03c01cf39a5f813a10df7a04c96efa02b303e0a5e8827346a90c31d9154a24e449b230000000001e3c4ee998682df5c5e230d6968e947f83d0c03682f0cfc85f1e6ec8e8552c95a000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1500000",
|
||||
"hash": "00000000019e5b25a95c7607e7789eb326fddd69736970ebbe1c7d00247ef902",
|
||||
"time": 1639913234,
|
||||
"saplingTree": "01ce183032b16ed87fcc5052a42d908376526126346567773f55bc58a63e4480160013000001bae5112769a07772345dd402039f2949c457478fe9327363ff631ea9d78fb80d0177c0b6c21aa9664dc255336ed450914088108c38a9171c85875b4e53d31b3e140171add6f9129e124651ca894aa842a3c71b1738f3ee2b7ba829106524ef51e62101f9cebe2141ee9d0a3f3a3e28bce07fa6b6e1c7b42c01cc4fe611269e9d52da540001d0adff06de48569129bd2a211e3253716362da97270d3504d9c1b694689ebe3c0122aaaea90a7fa2773b8166937310f79a4278b25d759128adf3138d052da3725b0137fb2cbc176075a45db2a3c32d3f78e669ff2258fd974e99ec9fb314d7fd90180165aaee3332ea432d13a9398c4863b38b8a7a491877a5c46b0802dcd88f7e324301a9a262f8b92efc2e0e3e4bd1207486a79d62e87b4ab9cc41814d62a23c4e28040001e3c4ee998682df5c5e230d6968e947f83d0c03682f0cfc85f1e6ec8e8552c95a000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1510000",
|
||||
"hash": "000000000124a1633bdbf45ef31713b19762765ef76a96cbe85803b2f1c18742",
|
||||
"time": 1640666476,
|
||||
"saplingTree": "0157fd74a49b531a5225c9cc93474f7daa19a110b78f251d03cd1a9b8c31dad3420013000119427b542617dcf7487d301582e3e902bbd0362af294277e465a684c77cb8d3c01b256fba8f0beae8e9e0c46bdf5c29718f37bcbab8a104ffb87a7126120b0490f012248ccadecfea6dd6c834c9742ef4eccb79c2fa509dc35ddbfd742d7fbf0d6120103b92a724fd8c33e1a30e973286410f7af6658035044ad67251929ab44d0114c0001e9eeb9adbdf2b729d0de7da1ad859eb46bea7f1bc536429ccaafc037db4a353a01b3cf5c3cb57f679ee0c4d6e63015eae3081686ba9e5cefd6c279e1acaf8c253b0001e27f4a1e1f48613dd775994ca4996203e3bac5989f156bea14ab9f0cfd0cbf63013953f6dc20d99704a8fb7f8a0862170105a67f209a67a9cdc80f1fa9859dc816011077ab70d2d41361876610cfbc1b0b988d37602e2b48e62e41867aa53c0ace32013efe1c57bdb27672a608931e36526a2b19e7ae10c504444a775594e22720f90d01e3c4ee998682df5c5e230d6968e947f83d0c03682f0cfc85f1e6ec8e8552c95a000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1520000",
|
||||
"hash": "000000000086782f677f3cb59ec5ebb5b9a11c48bde039e47defe9abe9c8c199",
|
||||
"time": 1641420827,
|
||||
"saplingTree": "0156fb043013ffdfd7203e695734cd4d0970b97a575f79bbbaa988701e8260683a01dff6ab4952d656ac0707a84e27dd5f648f01bcf9e13ad5813bc473c1be2c422e13000001e4ed02252ac47555166f5f9872c886d2655041456d20acc09b9703c92054734b0000010c39864f03601122cdaeb822bbe50156db7b3cb087a16fdb56c4cce48c2fb7390001fef0f8045ce18bf87e6fd37ca0461cc62e2ab607041cc637a5e5216a99241f5801ad95c39e8711ec387bb79110d950ad65434dda0dff3361c242dee63c70f1325a0001d6ceec9feebbd7ff1ae4abfae57f591e8a3775548e4d1f85853c1bf24f477c5f01b0c5456b64396b353874da063d10a220e67d1fa787317418a85600041de22d0b00000149fa3998a85436863ce7c0672dbc69b4b4fdbe65b9964eeb2de59877880f49250155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1530000",
|
||||
"hash": "000000000006aa29fe447ae75e1b5ec09c697f89f008a43fd2861b5ad9a50b49",
|
||||
"time": 1642174402,
|
||||
"saplingTree": "013490df0f89d64359ecf0ea4bcec11b0a1e8799aba1f7684cfa96bf79d83b76050172bc1daed3e35d20c2d82ce797a29a757724dc36ceab7a4b9e1d2f2c5334c056130001de8046415b67ad2c504772d964c035bdb12784dfb6256e5c4d86a574daacbd6f0182bfb6661e4e7eac70664ceac73f2b5a7652f5dd9d21e03abe4dc5004d50dd140177c0c389b644ef602ee3fa35720862b0c3b888b65c0d43482a5a40dab7544c3f017650089949a0e1907f78208c91b72af827f2224bdeb16b7b3dbfe55468f7251b000000000001b2369cc5d222541526c196e060970c126952e8f385b83631819af72b75cfc229015f77e7db7a1794f020ebbb477ccb158e97726ab451e4bdaf5ceb3810495ecd3801aedb39ff503e0cb5b4ae3cc953a9ee44b2c6941b641b2a140dd9c1b8800b414b000149fa3998a85436863ce7c0672dbc69b4b4fdbe65b9964eeb2de59877880f49250155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1540000",
|
||||
"hash": "0000000000d151b19ae48a43fb0adb196db7b3bc245aa7ddc29b1768a1ad8aaf",
|
||||
"time": 1642928383,
|
||||
"saplingTree": "016d7e9c79bd1c3c151eb8b7852eae27973217ce2038fd7949d737de966661d33b01262f0a6cd6c22f707c18e0b765112a8f49f79091243b7ad8eef766c9ba854a4f1300011a1e5a0829cf53ec493ef5d833cb3e762db5c91a73f16612377752c2d00cb90b000001052899c6521d6297c360e5dfdd4550c5ba3afcc852498ba24add12f8892d5913000167b96f385461d0993e3fdd23c5ca1b2775cd8a13c85797b03d3011025d6ad9200001041acc9c0d60954172be14620778c88a241aaffd43d4e034a97ff5f01048fb6d000157e956d3c6df7a69a25ccf60bb9755442d7d8cbb7d50fc9c214381332de89224017e5b3ff3bec6a6babb2a4fae1e99f1b6bee1b8f2bf0e3cd19198da7f71f066400001434d1e18a9158cee7aeffac0aadddbf6de6bb3bec5a666cbf6c392369482e75f0149fa3998a85436863ce7c0672dbc69b4b4fdbe65b9964eeb2de59877880f49250155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1550000",
|
||||
"hash": "0000000000c366844f675c743d3955e6d1c2d136f98ed7837373cae7aa19ff2f",
|
||||
"time": 1643682901,
|
||||
"saplingTree": "019732acfadcb04ec5efbaa693d175d36880f421eed374d45969eaf58bba3f5d04013b598846ffe5b200173fc8c18298107ce11411bf443b6cba92f8408c69c89001130001f29363407f3096433838a8fde4240601836c4e0dc5e1c5e08ed2a0ad0d73eb590000000001d77b83e658acac01a7109690b2918bf26076f8dd04adde378ecd6f3dd38fdc5501053c6ec909c7784c3c5767509224af0a37b56227bb27ade5d0c1d3a4510744640114266f95f4c3e76910bdfc65413596a1a3da0235d1e80170bdb54579281b5d680135f7745122c122b81898bdf4d4f278bd3b6241701e67c1183ded95b1264ab20f018e033494f7c90bcf5d34a02a329dfb539401f0e49268047c300cf73181650532010d7162664a0a2e5db282f7f417fd5b6ffcd91ed61eed01c0d03caac24a36c36901279d80494fd5d8d4f14617accb9a54a030188ba7531055eed443e16bfefc2f1401434d1e18a9158cee7aeffac0aadddbf6de6bb3bec5a666cbf6c392369482e75f0149fa3998a85436863ce7c0672dbc69b4b4fdbe65b9964eeb2de59877880f49250155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1560000",
|
||||
"hash": "00000000019ba02d1f469a262cb6bae792f72a544ed397cf76c5d22e7e3b18b9",
|
||||
"time": 1644437522,
|
||||
"saplingTree": "015e37c56f5e3de9661a604699d35e3a814585bdc27b015563b54bb3c3281e1e2a00140001fcf45e9fcc74467b6f24c50af970b6226930ce8a37b3ba42e0d222220e6a966001371a7a3440759dc4ae1a3f827ce54dff0d7b29634bb0c1dd2c64aef33263326e0000000001054a85591052990693586ca8e160520114dfe0a28599b98de91e05b86696192001b50616541a8cb8e51c186c8bd637ffd62a63992f900bc9abe21245a31c29ac0a01b0fb1f373cf3053dffe0d64c60174d96ab72ddf5afab7b498214656566c574010000014314d247639c433becdad340f2bac789c824c547edea246f2a48d88562aa240b000000000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1570000",
|
||||
"hash": "00000000016f38d81a61bde37115ed6e1ca2a73dad8b8c664474495a5467bcc4",
|
||||
"time": 1645190504,
|
||||
"saplingTree": "01a2a8f088345cf840a619ad9f15d5075af6fd65e842f5b6864c1cbf2f0b7d506001b05ea4714239ab5e84ec5f841b82171a1d310062818104be28bb63400733695d14019ee543ffad8222a40110a8427706786f450bb02f1bf9b50c23723dc34e2e586b0001756c5eb79df9742b6cf0d0d0b471166a44758032a1dc45631a6325982651c94f0001e373b76f7d44096e239b838523c2c8587ca2a7b9e2b7f0116dac0eaf1735e13b0001d4e7697b01139086dd329794b0b040e4e18d1755fae84dd1707c552f0785174f012bd132595b0d4969615db7a1ba763650bc7c5e26737127bddcfb7b98bedd06110000010a3e2bc3c2e7f7f0703476e9516e987d9e96a5cd932c3a49c0ab98d57d43753b000001d2da173dea92452ef7c607109b24e72dad298e0b40e5fc0442f8a662aa8262720000000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1580000",
|
||||
"hash": "000000000032f4a418ded4a885477ce692d9b545eef417bad12a0f723952871c",
|
||||
"time": 1645944081,
|
||||
"saplingTree": "01c8f491d6a60a739266ce41ae23dfcd9644a807ed73eaa48a27db17b6254ade5e01cf2ae986399bff1468e9709e8fdbd9a6bb1390bb54cb3c9b624ef8773dc47b2a140195ab2e6808087e36b34ba68a4faeb5fa72408a4f09aa1c17eea245b46d786b5001ed4256f1a96660e302aeb2e4ee849b1c1efa19aa8ea54dab92a49dbd2a89b454019d675b2bfde773e48c54caa8bd8d3436fd8755ff8a715e4e34a029735218fb000000011b3e6ad808dd9021e0790be90d9f355357bfe06ea859cacd68ae257d82d0ef060180e84edba246cd46160a56ff391686eee8dba6235c08f4549e7c16ce0e61b8460001dc5452a24e83cfeda62c7e03ba5f0d6f296d800b3e617fb11756cf0db8fa274c01cbce9b0c9a6028c50e05f49abbd2c9450be7a2fed9d8b1c76e769d347fcbdc2a01e218eacafa4b4e0b8abd2d99f88806d155e3d962801198267f3a435f678dea2b0001d467488acdecf0ba030161d89865fb8f5c45ab8e0486fee11025b971fcceaa0001d2da173dea92452ef7c607109b24e72dad298e0b40e5fc0442f8a662aa8262720000000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1590000",
|
||||
"hash": "000000000000b3bb94cd2aeee986e840884bebf36bbd562763bf9736a7a31343",
|
||||
"time": 1646697150,
|
||||
"saplingTree": "01bbec75cd09ac2d3959d59aa0679100b6f5da6621c222774692a46b5eac90c80b001400000169acd9a7b9e7bc9ebecfb65b0d51fdcd3d5f9d6d17c85b2b7f6da11927c67c430001ae6271da221b4ac25df73acd50c9562baee7317415b849a26a39a02ee8335b1e000174393f207d197a9f895845bffdc21310be222c7a6965a116e9db40b51f19c52f0000000001c829d3185e05d791ab693a1656dc8aec88932418b68062fe3ea70c1ea9a6017300000178c754572ab0d6e7894f1970e24881fe45d3e90ccac9b942c2bfdeea83a3a54a00000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1600000",
|
||||
"hash": "0000000000aae69fb228f90e77f34c24b7920667eaca726c3a3939536f03dcfc",
|
||||
"time": 1647451327,
|
||||
"saplingTree": "0123211a4b6f1ba3c2bd284ecc46ae64b18cabd08aa0950b0f905f4b019fcb3137001401f556e9479363d471ec3d7498aef6e6c69ebcf368eb01216de96cc91dac629e4f011909d70bf3e51c48fb1da8581923baac21a65cf4ea4f0ff1646ca1e262023d720001f8d239ded8f7ae0a8f3587796c89132cee710299792b2faac78555992b7cc25001b9968a886ca4978cc0b8c59070cf9c3966ace805c9dc49f5f246b6eb6551f043011fb7b3b41358b71ec97fc51c54d02771c7f0efb386e917d9fa1dbecde445e407014a116a9e92c977c0cc35382156008f9a56156ab274511110a6987a395508236d010355e314af0f1592e563d53fe93f91bd535f8464ecb8fb6a27fb8df724f48e400001417e6cffbed73c83caf8a12afe47dab732ae6bc2b16743357f82fcf0a445cd53000134396f58aabc14d95f0c4c9b33b0968fc628e0b5e7f777e84d993aaf76181a100100d11f1378ead2db235dbba917dc5f1f70a7ddd7307d54b3c7b14cee1d659941000178c754572ab0d6e7894f1970e24881fe45d3e90ccac9b942c2bfdeea83a3a54a00000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1610000",
|
||||
"hash": "000000000108f30a17b0064c1d100a6da9a18878dd7d2a47cd5e2b0b3b726709",
|
||||
"time": 1648204645,
|
||||
"saplingTree": "01ce1c8e08056889dd332f667a640d2cc5f9f18a74713a67980b0dff8fc65de842019610f7047e877215d5744acbd3edcce3559cea41a8685b4174d20c184d260650140000014c341b0c8eccc745b581b092ffe20bcf7ee8e2210b91d73e8be7c6d369a98b0600000185e35bc1dab2c71ce5066f4b5ea520d773a4e6de07ce492c260ef2a6df4492020000000001b5169be16a2acb2f2a1de9518a053db53d4942c86831529fa8e380ea971e034a018e15625f11df865e5dafab59e4080ca533c8c93b244ef4258e55a9911f63f24100012977501c7c16ffa838d8d73e0947a3958fe8afca0227e7f537107c99c1ac2c380178c754572ab0d6e7894f1970e24881fe45d3e90ccac9b942c2bfdeea83a3a54a00000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1620000",
|
||||
"hash": "000000000086c9b3b8bbe6902cfbb773d0373123b4e976aea2dea3f4b6ea37de",
|
||||
"time": 1648958731,
|
||||
"saplingTree": "01e5389a37b4916a93e116ebd9378d0d7b207f820fc655f4516b2f842feb87944601bb09db5bb969690d9499a602d8d0b0bd7d033bf649158fb4cfbfdad9f820875414012358ab5487478f19981b2ad341e58eba14deb6839d2a81dd08a8b88fe81f4f3d0152c1e370000dc347cdc0cb27cbdf36e6457d11471444773bdd8452182c71f338013ecd572a10aae02049de09c6a37888642c1ad3adf6f6c084108f52da1e150267013f23966db40c2723ebaccd54dc93ac5f7319ed00033093e5a110f3b6a262613d0105d390635a0b41a5cf9950249a961070d44358d3b253b9a9fb20bdd0d6028e490000016808c414dc7ec7c6fe0b1e6b619f82016fd29ffc402f0c27b9beacecb6302a390000011b6c0b1e2aa202c715dbfb9c1d9fd4be8d60257c7a2287cb1c15746fe0c71259013f94660cba68fc10ef584032b0758f704e3503b3664c4c3dbabef80a2fc187030126f6f651d189daa6d25cc97d4470634d04cb473dbd415009b56ff6ee15d6034e012977501c7c16ffa838d8d73e0947a3958fe8afca0227e7f537107c99c1ac2c380178c754572ab0d6e7894f1970e24881fe45d3e90ccac9b942c2bfdeea83a3a54a00000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1630000",
|
||||
"hash": "0000000000dcac59b6d190dc533e07a3acfec0b3d13455cef8449cb8a4734596",
|
||||
"time": 1649713022,
|
||||
"saplingTree": "015c0c4fbdb13a5a64d4aec5396037cd40adebd6718dff3f6b35d2b2ec2bb17d590014015ea9a099629e5cbb3191b67bd1abbc8173fbf34976d16e2403b002107b49b7400000000000013d825282657345921104ec4e902e49a87a0d9076a3000a30261409424ea6726701d35d38beb21dd399604193b32009ecd26eb63aee19c5742a84ef71afc23ba62b017f214e7831c490de6041093f4d89ea4272f7e611faeb248311cfa8f352159805016981634ecd4aa96e6cf8d3b15b984565d802757fd71102dff784bf4316d2cb2000018c42eb366fb66fbae8336659120d8ba0ce0945c10700df54d3b716eeddaee043000000017eda713cd733ccc555123788692a1876f9ca292b0aa2ddf3f45ed2b47f027340000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1640000",
|
||||
"hash": "00000000005559d591672bc15b3ab025b723f6b96cfdd8f8cd8b46066a8db258",
|
||||
"time": 1650467008,
|
||||
"saplingTree": "01f2f114940074b873d88ea3f379946739f0663fb9c14e71ba70c4c496a6f95c5a00140160670ddbf8ea8c14d9802ab2f3e2b0068c9f901b1a84f2557a5de55b14e86372000001395978b72c1aa91916cfc984516a6dff41b9ca9092d7918f514f986f9d85fa140001f8055848fe2c632264516581b0cff0280d12c1817f09a29791958db18df7ff45014c71feef28764f3dda746ccc8273e04ecfaa0029d0b8e7da59b4a18f14d61b3500018097012ba79918459d68b6c019a6999208674d75872251793c936a6574278f100000017b9e6d79243147aad264112c7ac2ffcecc2b4fe33d7e17107289e17eeb72cb2901c153d913355ddf0d1b803b5f994aef2cdb5e1c530221e35df2c7ff71ecbe2a5f0000017eda713cd733ccc555123788692a1876f9ca292b0aa2ddf3f45ed2b47f027340000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"network": "main",
|
||||
"height": "1650000",
|
||||
"hash": "0000000000bc60722f1be8b7aeadc5389f87757cc1710799f4260254b6bac90d",
|
||||
"time": 1651219887,
|
||||
"saplingTree": "01320d3b28fc96802f8aab6902fec838c9f88753d04d55e6072912810de4131e020014000137727e6090e80d5f9907512af97d5ddda8b7677f8e85b063118839539d85f92e01884504cceb6d34edce65c5fcc31f83b472eeee96dc24128b5b8c765c1603066a018552423d261ef738d539900ac6f71a102b20d0c753b4af3d726a282ec5ec343801e9425a1c0aa2bbe6030ed600267b3cee5bd8fa1e05fdb5b99a0ebced35b39a3e01eadf3917fa55e51bb6302e927cbededa1ed9f8dc5820e45116c6e163f77e563f016f81427815aa858b5e3d37f849e2203f73f121bc40419ce049eee9396e2c39730001532f5e43976e684a43d7d64bb737d7079fc7e70132add49fbecb7786bb5b9b48000001ed99ed215882cbd3efa0b637d6397188a011011b8c4ee5c3a15ddb0fa10abd330001232675992d1b5406f0c4a35c977f06319f3e4644ac1d165b0d0b9399f16f5d2600017eda713cd733ccc555123788692a1876f9ca292b0aa2ddf3f45ed2b47f027340000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user