Repository: horizontalsystems/bitcoin-kit-android
Issue: Restoring account with many transactions crashes some devices

The following devices throw StackOverflow exception
* mi a1
* redmi note 4
* oneplus

Changed files:
bitcoinkit/src/test/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApiTest.kt
bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/ApiManager.kt
bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApi.kt

diff --git a/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/ApiManager.kt b/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/ApiManager.kt
index 6280c67a..b411d468 100644
--- a/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/ApiManager.kt
+++ b/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/ApiManager.kt
@@ -15,7 +15,7 @@ class ApiManager(private val host: String) {
         return getJsonValue(file).asObject()
     }
 
-    @Throws
+    @Throws(Exception::class)
     fun getJsonArray(file: String): JsonArray {
         return getJsonValue(file).asArray()
     }
@@ -44,6 +44,6 @@ data class BlockResponse(val hash: String, val height: Int) {
     }
 
     override fun hashCode(): Int {
-        return 31 * hash.hashCode() + height.hashCode()
+        return 31 * hash.hashCode() + height
     }
 }
diff --git a/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApi.kt b/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApi.kt
index b6248844..da7713c2 100644
--- a/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApi.kt
+++ b/bitcoinkit/src/main/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApi.kt
@@ -6,8 +6,6 @@ import io.horizontalsystems.bitcoinkit.models.BlockHash
 import io.horizontalsystems.bitcoinkit.models.PublicKey
 import io.horizontalsystems.bitcoinkit.network.*
 import io.horizontalsystems.hdwalletkit.HDWallet
-import io.reactivex.BackpressureStrategy
-import io.reactivex.Flowable
 import io.reactivex.Single
 
 class InitialSyncerApi(private val wallet: HDWallet, private val addressSelector: IAddressSelector, network: Network) {
@@ -17,27 +15,31 @@ class InitialSyncerApi(private val wallet: HDWallet, private val addressSelector
 
     private val host = when (network) {
         is MainNet -> "https://btc.horizontalsystems.xyz"
-        is TestNet -> "http://btc-testnet.horizontalsystems.xyz"
+        is TestNet -> "http://btc-testnet.horizontalsystems.xyz/apih"
         is MainNetBitcoinCash -> "https://bch.horizontalsystems.xyz"
-        is TestNetBitcoinCash -> "http://bch-testnet.horizontalsystems.xyz"
-        else -> "http://btc-testnet.horizontalsystems.xyz"
+        is TestNetBitcoinCash -> "http://bch-testnet.horizontalsystems.xyz/apih"
+        else -> "http://btc-testnet.horizontalsystems.xyz/apih"
     }
 
     private val apiManager = ApiManager(host)
 
     @Throws
-    fun fetchFromApi(account: Int, external: Boolean): Single<Pair<List<PublicKey>, List<BlockHash>>> {
-        return requestApiRecursive(account, external)
-                .map { (publicKeys, blockResponses) ->
-                    publicKeys to blockResponses.mapNotNull { blockResponse ->
+    fun fetchFromApi(account: Int, external: Boolean): Single<Pair<List<PublicKey>, List<BlockHash>>> =
+            Single.create<Pair<List<PublicKey>, List<BlockHash>>> { emitter ->
+                val (publicKeys, blockResponses) = requestApiRecursive(account, external)
+                try {
+                    val accountData = publicKeys to blockResponses.mapNotNull { blockResponse ->
                         try {
                             BlockHash(blockResponse.hash.hexStringToByteArray().reversedArray(), blockResponse.height)
                         } catch (e: NumberFormatException) {
                             null
                         }
                     }
+                    emitter.onSuccess(accountData)
+                } catch (e: Exception) {
+                    emitter.onError(e)
                 }
-    }
+            }
 
     private fun requestApiRecursive(
             account: Int,
@@ -46,45 +48,34 @@ class InitialSyncerApi(private val wallet: HDWallet, private val addressSelector
             emptyResponsesInRow: Int = 0,
             allKeys: MutableList<PublicKey> = mutableListOf(),
             allBlockResponses: MutableList<BlockResponse> = mutableListOf()
-    ): Single<Pair<List<PublicKey>, List<BlockResponse>>> {
+    ): Pair<MutableList<PublicKey>, MutableList<BlockResponse>> {
 
         if (emptyResponsesInRow < gapLimit) {
             val publicKey = wallet.publicKey(account, index, external)
             val addresses = addressSelector.getAddressVariants(publicKey)
+            val blockResponses = addresses.map { getBlockHashes(it) }.flatten().distinct()
 
-            return Flowable.merge(addresses.map { getBlockHashes(it) })
-                    .toList()
-                    .map { it.flatten().distinct() }
-                    .flatMap { blockResponses ->
-                        allKeys.add(publicKey)
-                        allBlockResponses.addAll(blockResponses.filter { it.height <= maxHeight })
+            allKeys.add(publicKey)
+            allBlockResponses.addAll(blockResponses.filter { it.height <= maxHeight })
 
-                        requestApiRecursive(account, external, index + 1, if (blockResponses.isEmpty()) emptyResponsesInRow + 1 else 0, allKeys, allBlockResponses)
-                    }
+            return requestApiRecursive(account, external, index + 1, if (blockResponses.isEmpty()) emptyResponsesInRow + 1 else 0, allKeys, allBlockResponses)
         }
 
-        return Single.just(allKeys to allBlockResponses)
+        return allKeys to allBlockResponses
     }
 
-    private fun getBlockHashes(address: String): Flowable<List<BlockResponse>> {
-        return Flowable.create<List<BlockResponse>>({ emitter ->
-            val list = mutableListOf<BlockResponse>()
-            val data = apiManager.getJsonArray("tx/address/$address")
+    private fun getBlockHashes(address: String): List<BlockResponse> {
+        val list = mutableListOf<BlockResponse>()
+        val data = apiManager.getJsonArray("tx/address/$address")
 
-            for (item in data) {
-                val tx = item.asObject()
-                val txBlockHash = tx["block"]
-                val txBlockHeight = tx["height"]
-                if (txBlockHash.isNull || txBlockHeight.isNull) {
-                    continue
-                }
+        for (item in data) {
+            val tx = item.asObject()
+            val txBlockHash = tx["block"] ?: continue
+            val txBlockHeight = tx["height"] ?: continue
 
-                list.add(BlockResponse(txBlockHash.asString(), txBlockHeight.asInt()))
-            }
-
-            emitter.onNext(list)
-            emitter.onComplete()
-        }, BackpressureStrategy.LATEST)
+            list.add(BlockResponse(txBlockHash.asString(), txBlockHeight.asInt()))
+        }
+        return list
     }
 
 }
diff --git a/bitcoinkit/src/test/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApiTest.kt b/bitcoinkit/src/test/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApiTest.kt
index 1b8bf5d5..660112ce 100644
--- a/bitcoinkit/src/test/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApiTest.kt
+++ b/bitcoinkit/src/test/kotlin/io/horizontalsystems/bitcoinkit/managers/InitialSyncerApiTest.kt
@@ -60,6 +60,17 @@ class InitialSyncerApiTest {
         verify(apiManager).getJsonArray("tx/address/1A282zR9uMz84P9vQNwCBCqfwGtKEu3K3v")
     }
 
+    @Test
+    fun fetchFromApi_onError() {
+        val error = Exception()
+
+        whenever(apiManager.getJsonArray(any())).thenThrow(error)
+
+        initialSyncerApi.fetchFromApi(account, true)
+                .test()
+                .assertError(error)
+    }
+
     @Test
     fun fetchFromApi_handleResponse() {
         val obj1 = JsonObject().add("block", "001").add("height", 1)