[#270] Fallback for bad JSON files
This commit is contained in:
parent
a58a235d2c
commit
568cfa6c4b
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": 1290000,
|
||||
"hash": "00000000014836c3cbc011276cbd3702a76a1fea7eb2c0c2c257321220376450",
|
||||
"time": 1624075741,
|
||||
"tree": "01accf4fc3dc4233bbe757f94e0d4cd23b4aa2e6ac472601f4f53ca4dc86a8a05901fae977171a6103a0338990e073ffe50e29fc8bf0400dcd3378ebfe7a146ed1481300014f7b33dd5159ac66f2670b7db8925065e7154e0199ff7ee7559b276ba56ad1200173e9881f21357e54027a4275114f0f6ad4ca17143554182f63c77f3288a23a20011d65465ab942440e200d429ef892452b4b05c5b21e9a6e6d968a719c67b5e85b000000000000000150926c74975e2d8ff095defb75a4a6d9f17007e87a74230a65a3265d8f45032900012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": 1290000,
|
||||
"hash": "00000000014836c3cbc011276cbd3702a76a1fea7eb2c0c2c257321220376450",
|
||||
"time": 1624075741,
|
||||
"tree": "01accf4fc3dc4233bbe757f94e0d4cd23b4aa2e6ac472601f4f53ca4dc86a8a05901fae977171a6103a0338990e073ffe50e29fc8bf0400dcd3378ebfe7a146ed1481300014f7b33dd5159ac66f2670b7db8925065e7154e0199ff7ee7559b276ba56ad1200173e9881f21357e54027a4275114f0f6ad4ca17143554182f63c77f3288a23a20011d65465ab942440e200d429ef892452b4b05c5b21e9a6e6d968a719c67b5e85b000000000000000150926c74975e2d8ff095defb75a4a6d9f17007e87a74230a65a3265d8f45032900012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": 1300000,
|
||||
"hash": "00000000027222bdbcf9c5f807f851f97312ac6e0dbbc2b93f2be21a69c59d44",
|
||||
"time": 1624830312,
|
||||
"tree": "01f5a97e2679a2bb9103caf37b825f92fcd73fff836234844dfcf1815394522b2c01526587b9b9e8aeb0eb572d81fec1f5127b8278ba0f57e451bd6b796596940a2213000131c7ff90fafff6159b8fb6544a2bcbba6c102903158fce8f9a9d3c6654abb23300013555cb7f4f79badeaca9bf2dca5a8704f0929053d50e95c03002f9a4d5286c3a01ad3557e11c1607ec888dc84f5f8899c3c79fb1f50b613946452ec7dd5e53763c0001c4583f4482b949390dba355fc8fa63019c83acd644ddd633cb50211d236f870600000001088da0d78eefd0c222507927e403b972d0890d0c31e08b02268fbe39ac4a6e170001edf82d4e2b4893ea2028ca8c5149e50a4c358b856d73f2de2b9a22034fa78f22012ffde6dccbef68b60cd7b4e7a8fe7989f5954fa4bacad01b247d16b9bfa5084000000125911f4524469c00ccb1ba69e64f0ee7380c8d17bbfc76ecd238421b86eb6e09000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||
}
|
|
@ -2,7 +2,6 @@ package cash.z.ecc.android.sdk
|
|||
|
||||
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
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
|
@ -11,15 +10,8 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AssetTest {
|
||||
@Test
|
||||
@SmallTest
|
||||
fun birthday_height_from_filename() {
|
||||
assertEquals(123, WalletBirthdayTool.birthdayHeight("123.json"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
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.Companion.IS_FALLBACK_ON_FAILURE
|
||||
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 = "saplingtree/goodnet"
|
||||
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val birthday = 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 = "saplingtree/badnet"
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val birthday = WalletBirthdayTool.getFirstValidWalletBirthday(
|
||||
context,
|
||||
directory,
|
||||
listOf("1300000.json", "1290000.json")
|
||||
)
|
||||
assertEquals(1290000, birthday.height)
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import com.google.gson.Gson
|
|||
import com.google.gson.stream.JsonReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
* Tool for loading checkpoints for the wallet, based on the height at which the wallet was born.
|
||||
|
@ -30,11 +29,21 @@ class WalletBirthdayTool(appContext: Context) {
|
|||
|
||||
companion object {
|
||||
|
||||
// Behavior change implemented as a fix for issue #270. Temporarily adding a boolean
|
||||
// that allows the change to be rolled back quickly if needed, although long-term
|
||||
// this flag should be removed.
|
||||
@VisibleForTesting
|
||||
internal val IS_FALLBACK_ON_FAILURE = true
|
||||
|
||||
/**
|
||||
* Load the nearest checkpoint to the given birthday height. If null is given, then this
|
||||
* will load the most recent checkpoint available.
|
||||
*/
|
||||
fun loadNearest(context: Context, network: ZcashNetwork, birthdayHeight: Int? = null): WalletBirthday {
|
||||
fun loadNearest(
|
||||
context: Context,
|
||||
network: ZcashNetwork,
|
||||
birthdayHeight: Int? = null
|
||||
): WalletBirthday {
|
||||
// TODO: potentially pull from shared preferences first
|
||||
return loadBirthdayFromAssets(context, network, birthdayHeight)
|
||||
}
|
||||
|
@ -68,7 +77,8 @@ class WalletBirthdayTool(appContext: Context) {
|
|||
|
||||
internal fun birthdayHeight(fileName: String) = fileName.split('.').first().toInt()
|
||||
|
||||
private fun Array<String>.sortDescending() = apply { sortByDescending { birthdayHeight(it) } }
|
||||
private fun Array<String>.sortDescending() =
|
||||
apply { sortByDescending { birthdayHeight(it) } }
|
||||
|
||||
/**
|
||||
* Load the given birthday file from the assets of the given context. When no height is
|
||||
|
@ -87,35 +97,75 @@ class WalletBirthdayTool(appContext: Context) {
|
|||
): WalletBirthday {
|
||||
twig("loading birthday from assets: $birthdayHeight")
|
||||
val directory = birthdayDirectory(network)
|
||||
val treeFiles = listBirthdayDirectoryContents(context, directory)?.sortDescending()
|
||||
if (treeFiles.isNullOrEmpty()) throw BirthdayException.MissingBirthdayFilesException(
|
||||
directory
|
||||
)
|
||||
twig("found ${treeFiles.size} sapling tree checkpoints: ${Arrays.toString(treeFiles)}")
|
||||
val file: String
|
||||
try {
|
||||
file = if (birthdayHeight == null) treeFiles.first() else {
|
||||
treeFiles.first {
|
||||
birthdayHeight(it) <= birthdayHeight
|
||||
}
|
||||
val treeFiles = getFilteredFileNames(context, directory, birthdayHeight)
|
||||
|
||||
twig("found ${treeFiles.size} sapling tree checkpoints: $treeFiles")
|
||||
|
||||
return getFirstValidWalletBirthday(context, directory, treeFiles)
|
||||
}
|
||||
|
||||
private fun getFilteredFileNames(
|
||||
context: Context,
|
||||
directory: String,
|
||||
birthdayHeight: Int? = null,
|
||||
): List<String> {
|
||||
val unfilteredTreeFiles = listBirthdayDirectoryContents(context, directory)
|
||||
if (unfilteredTreeFiles.isNullOrEmpty()) {
|
||||
throw BirthdayException.MissingBirthdayFilesException(directory)
|
||||
}
|
||||
|
||||
val filteredTreeFiles = unfilteredTreeFiles
|
||||
.sortDescending()
|
||||
.filter { filename ->
|
||||
birthdayHeight?.let { birthdayHeight(filename) <= it } ?: true
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
|
||||
if (filteredTreeFiles.isEmpty()) {
|
||||
throw BirthdayException.BirthdayFileNotFoundException(
|
||||
directory,
|
||||
birthdayHeight
|
||||
)
|
||||
}
|
||||
try {
|
||||
val reader = JsonReader(
|
||||
InputStreamReader(context.assets.open("$directory/$file"))
|
||||
)
|
||||
return Gson().fromJson(reader, WalletBirthday::class.java)
|
||||
} catch (t: Throwable) {
|
||||
throw BirthdayException.MalformattedBirthdayFilesException(
|
||||
directory,
|
||||
treeFiles[0]
|
||||
)
|
||||
|
||||
return filteredTreeFiles
|
||||
}
|
||||
|
||||
/**
|
||||
* @param treeFiles A list of files, sorted in descending order based on `int` value of the first part of the filename.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal fun getFirstValidWalletBirthday(
|
||||
context: Context,
|
||||
directory: String,
|
||||
treeFiles: List<String>
|
||||
): WalletBirthday {
|
||||
var lastException: Exception? = null
|
||||
treeFiles.forEach { treefile ->
|
||||
try {
|
||||
context.assets.open("$directory/$treefile").use { inputStream ->
|
||||
InputStreamReader(inputStream).use { inputStreamReader ->
|
||||
JsonReader(inputStreamReader).use { jsonReader ->
|
||||
return Gson().fromJson(jsonReader, WalletBirthday::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
val exception = BirthdayException.MalformattedBirthdayFilesException(
|
||||
directory,
|
||||
treefile
|
||||
)
|
||||
lastException = exception
|
||||
|
||||
if (IS_FALLBACK_ON_FAILURE) {
|
||||
// TODO: If we ever add crash analytics hooks, this would be something to report
|
||||
twig("Malformed birthday file $t")
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastException!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue