Skip to content

Commit

Permalink
Merge pull request #36 from ProtonProtocol/develop
Browse files Browse the repository at this point in the history
Improve Initialization Speed
  • Loading branch information
Joey Harward authored Jun 9, 2021
2 parents cc8fb15 + 3aaf34a commit e22d590
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 117 deletions.
6 changes: 3 additions & 3 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ const val kotlinVersion = "1.5.0"
const val orchidVersion = "0.21.1"

object ProtonSdk {
const val versionCode = 42
const val versionName = "1.0.4"
const val versionCode = 43
const val versionName = "1.0.5"
}

object BuildPlugins {
object Versions {
const val gradle = "4.2.1" // TODO: 4.2.1
const val gradle = "4.2.1"
const val dokka = "0.10.1" // TODO: 1.4.0
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class ChainProviderModule {
return chainProviderRepository.getChainProvider(chainId)
}

suspend fun updateChainProvider(chainProvider: ChainProvider) {
chainProviderRepository.updateChainProvider(chainProvider)
}

suspend fun updateChainUrl(chainId: String, chainUrl: String) {
chainProviderRepository.updateChainUrl(chainId, chainUrl)
}
Expand Down
20 changes: 18 additions & 2 deletions protonsdk/src/main/java/com/metallicus/protonsdk/Proton.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,34 @@ class Proton private constructor(context: Context) {
}
}

private suspend fun getChainProviderWithUrlStatsAsync() = suspendCoroutine<ChainProvider> { continuation ->
workersModule.onInitChainUrlStats { success, data ->
if (success) {
protonCoroutineScope.launch {
continuation.resume(chainProviderModule.getActiveChainProvider())
}
} else {
continuation.resumeWithException(ProtonException(data))
}
}
}

/**
* Get the [ChainProvider] info
*
* This will return the [ChainProvider] info given the Proton Chain URl provided during initialization.
*
* @return LiveData<Resource<[ChainProvider]>>
*/
fun getChainProvider(): LiveData<Resource<ChainProvider>> = liveData {
fun getChainProvider(includeUrlStats: Boolean=false): LiveData<Resource<ChainProvider>> = liveData {
emit(Resource.loading())

try {
val chainProvider = getChainProviderAsync()
val chainProvider =
if (includeUrlStats)
getChainProviderWithUrlStatsAsync()
else
getChainProviderAsync()
emit(Resource.success(chainProvider))
} catch (e: ProtonException) {
val error: Resource<ChainProvider> = Resource.error(e)
Expand Down
39 changes: 34 additions & 5 deletions protonsdk/src/main/java/com/metallicus/protonsdk/WorkersModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ import javax.inject.Inject
* Helper class used to facilitate WorkManager operations
*/
class WorkersModule {
companion object {
const val INIT = "WORKER_INIT"
}

@Inject
lateinit var context: Context

Expand All @@ -55,11 +59,6 @@ class WorkersModule {
workManager = WorkManager.getInstance(context)
}

companion object {
const val INIT = "WORKER_INIT"
const val UPDATE_RATES = "WORKER_UPDATE_RATES"
}

fun init(protonChainUrl: String) {
workManager.pruneWork()

Expand All @@ -82,16 +81,21 @@ class WorkersModule {
val initActiveAccount = OneTimeWorkRequest.Builder(InitActiveAccountWorker::class.java)
.setConstraints(constraints).build()

val initChainUrlsStats = OneTimeWorkRequest.Builder(InitChainUrlStatsWorker::class.java)
.setConstraints(constraints).build()

if (prefs.getActiveAccountName().isNotEmpty()) {
workManager
.beginUniqueWork(INIT, ExistingWorkPolicy.REPLACE, initChainProvider)
.then(initTokenContracts)
.then(initActiveAccount)
.then(initChainUrlsStats)
.enqueue()
} else {
workManager
.beginUniqueWork(INIT, ExistingWorkPolicy.REPLACE, initChainProvider)
.then(initTokenContracts)
.then(initChainUrlsStats)
.enqueue()
}
}
Expand Down Expand Up @@ -170,4 +174,29 @@ class WorkersModule {
workInfoLiveData.observeForever(workInfoObserver)
}
}

fun onInitChainUrlStats(callback: (Boolean, Data?) -> Unit) {
if (prefs.hasChainUrlStats) {
callback(true, null)
} else {
val workInfoLiveData = workManager.getWorkInfosForUniqueWorkLiveData(INIT)
val workInfoObserver = object : Observer<List<WorkInfo>> {
override fun onChanged(workInfos: List<WorkInfo>) {
val chainUrlStatsWorkInfos =
workInfos.filter { it.tags.contains(InitChainUrlStatsWorker::class.java.name) }
if (chainUrlStatsWorkInfos.isEmpty() ||
chainUrlStatsWorkInfos.any { it.state == WorkInfo.State.FAILED || it.state == WorkInfo.State.CANCELLED }) {
val data = chainUrlStatsWorkInfos.find { it.state == WorkInfo.State.FAILED }?.outputData
callback(false, data)
workInfoLiveData.removeObserver(this)
} else if (chainUrlStatsWorkInfos.all { it.state == WorkInfo.State.SUCCEEDED }) {
prefs.hasChainUrlStats = true
callback(true, null)
workInfoLiveData.removeObserver(this)
}
}
}
workInfoLiveData.observeForever(workInfoObserver)
}
}
}
10 changes: 10 additions & 0 deletions protonsdk/src/main/java/com/metallicus/protonsdk/common/Prefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Prefs(context: Context) {

const val HAS_CHAIN_PROVIDER = "has_chain_provider"
const val HAS_TOKEN_CONTRACTS = "has_token_contracts"
const val HAS_CHAIN_URL_STATS = "has_chain_url_stats"

const val ACTIVE_CHAIN_ID = "active_chain_id"
const val ACTIVE_ACCOUNT_NAME = "active_account_name"
Expand All @@ -57,6 +58,14 @@ class Prefs(context: Context) {
}
}

var hasChainUrlStats: Boolean
get() = prefs.getBoolean(HAS_CHAIN_URL_STATS, false)
set(value) {
value.let {
prefs.edit { putBoolean(HAS_CHAIN_URL_STATS, it) }
}
}

var activeChainId: String
get() = prefs.getString(ACTIVE_CHAIN_ID, "").orEmpty()
set(value) {
Expand Down Expand Up @@ -94,6 +103,7 @@ class Prefs(context: Context) {
remove(HAS_CHAIN_PROVIDER)
remove(HAS_TOKEN_CONTRACTS)
remove(HAS_ACTIVE_ACCOUNT)
remove(HAS_CHAIN_URL_STATS)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
*/
package com.metallicus.protonsdk.db

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.*
import com.metallicus.protonsdk.model.ChainProvider

/**
Expand All @@ -35,6 +32,9 @@ interface ChainProviderDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(chainProvider: ChainProvider)

@Update
suspend fun update(chainProvider: ChainProvider)

@Query("SELECT * FROM chainProvider WHERE chainId = :id")
suspend fun findById(id: String): ChainProvider

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ class ProtonModule {
logging.level = HttpLoggingInterceptor.Level.BODY

val httpClient = OkHttpClient.Builder()
.callTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.callTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.addInterceptor(logging)

val gson = GsonBuilder()
Expand Down
33 changes: 19 additions & 14 deletions protonsdk/src/main/java/com/metallicus/protonsdk/di/WorkerModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,26 @@ import dagger.multibindings.IntoMap
@Suppress("unused")
@Module
abstract class WorkerModule {
@Binds
@IntoMap
@WorkerKey(InitChainProviderWorker::class)
abstract fun bindInitChainProvidersWorker(factory: InitChainProviderWorker.Factory): ChildWorkerFactory
@Binds
@IntoMap
@WorkerKey(InitChainProviderWorker::class)
abstract fun bindInitChainProvidersWorker(factory: InitChainProviderWorker.Factory): ChildWorkerFactory

@Binds
@IntoMap
@WorkerKey(InitTokenContractsWorker::class)
abstract fun bindInitTokenContractsWorker(factory: InitTokenContractsWorker.Factory): ChildWorkerFactory
@Binds
@IntoMap
@WorkerKey(InitTokenContractsWorker::class)
abstract fun bindInitTokenContractsWorker(factory: InitTokenContractsWorker.Factory): ChildWorkerFactory

@Binds
@IntoMap
@WorkerKey(InitActiveAccountWorker::class)
abstract fun bindInitActiveAccountWorker(factory: InitActiveAccountWorker.Factory): ChildWorkerFactory
@Binds
@IntoMap
@WorkerKey(InitActiveAccountWorker::class)
abstract fun bindInitActiveAccountWorker(factory: InitActiveAccountWorker.Factory): ChildWorkerFactory

@Binds
abstract fun bindWorkerFactory(factory: ProtonWorkerFactory): WorkerFactory
@Binds
@IntoMap
@WorkerKey(InitChainUrlStatsWorker::class)
abstract fun bindInitChainUrlStatsWorker(factory: InitChainUrlStatsWorker.Factory): ChildWorkerFactory

@Binds
abstract fun bindWorkerFactory(factory: ProtonWorkerFactory): WorkerFactory
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@
*/
package com.metallicus.protonsdk.model

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class ChainUrlInfo(
@PrimaryKey
val url: String,

val responseTimeMillis: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@
*/
package com.metallicus.protonsdk.model

import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName

@Entity
data class KYCProvider(
@PrimaryKey
@SerializedName("kyc_provider") val kycProvider: String,

@SerializedName("desc") val description: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class ChainProviderRepository @Inject constructor(
chainProviderDao.insert(chainProvider)
}

suspend fun updateChainProvider(chainProvider: ChainProvider) {
chainProviderDao.update(chainProvider)
}

suspend fun updateChainUrl(chainId: String, chainUrl: String) {
chainProviderDao.updateChainUrl(chainId, chainUrl)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,80 +64,8 @@ class InitChainProviderWorker
val chainProvider = Gson().fromJson(response.body(), ChainProvider::class.java)
chainProvider.protonChainUrl = protonChainUrl

val acceptableChainBlockDiff = ChainUrlInfo.ACCEPTABLE_CHAIN_BLOCK_DIFF
val acceptableHyperionHistoryBlockDiff = ChainUrlInfo.ACCEPTABLE_HYPERION_HISTORY_BLOCK_DIFF

val chainUrlStats = mutableListOf<ChainUrlInfo>()
chainProvider.chainUrls.forEach { chainUrl ->
try {
val chainUrlResponse = chainProviderRepository.getChainInfo(chainUrl)
if (chainUrlResponse.isSuccessful) {
chainUrlResponse.body()?.let { chainInfo ->
val blockDiff = chainInfo.headBlockNum.toLong() - chainInfo.lastIrreversibleBlockNum.toLong()
val responseTimeMillis = chainUrlResponse.raw().receivedResponseAtMillis - chainUrlResponse.raw().sentRequestAtMillis
val inSync = blockDiff < acceptableChainBlockDiff

val chainUrlInfo = ChainUrlInfo(chainUrl, responseTimeMillis, blockDiff, inSync)
chainUrlStats.add(chainUrlInfo)
}
}
} catch (e: Exception) {
Timber.d(e)
}
}
chainProvider.chainUrlStats = chainUrlStats

val hyperionHistoryUrlStats = mutableListOf<ChainUrlInfo>()
chainProvider.hyperionHistoryUrls.forEach { hyperionHistoryUrl ->
try {
val healthResponse = chainProviderRepository.getHealth(hyperionHistoryUrl)
if (healthResponse.isSuccessful) {
var blockDiff: Long? = null

healthResponse.body()?.let { body ->
var headBlockNum = 0L
var lastIndexedBlock = 0L
val health = body.get("health").asJsonArray
health.forEach { healthElement ->
val serviceObj = healthElement.asJsonObject
if (serviceObj.get("service").asString == "NodeosRPC") {
val serviceDataObj = serviceObj.get("service_data").asJsonObject
headBlockNum = serviceDataObj.get("head_block_num").asLong
}
if (serviceObj.get("service").asString == "Elasticsearch") {
val serviceDataObj = serviceObj.get("service_data").asJsonObject
lastIndexedBlock = serviceDataObj.get("last_indexed_block").asLong
}
}

if (headBlockNum != 0L && lastIndexedBlock != 0L) {
blockDiff = headBlockNum - lastIndexedBlock
}
}

blockDiff?.let { blkDiff ->
val responseTimeMillis = healthResponse.raw().receivedResponseAtMillis - healthResponse.raw().sentRequestAtMillis
val inSync = blkDiff < acceptableHyperionHistoryBlockDiff

val chainUrlInfo = ChainUrlInfo(hyperionHistoryUrl, responseTimeMillis, blkDiff, inSync)
hyperionHistoryUrlStats.add(chainUrlInfo)
}
}
} catch (e: Exception) {
Timber.d(e)
}
}
chainProvider.hyperionHistoryUrlStats = hyperionHistoryUrlStats

val fastestChainUrl = chainUrlStats.filter { it.inSync }.minByOrNull { it.responseTimeMillis }
fastestChainUrl?.apply {
chainProvider.chainUrl = url
}

val fastestHyperionUrl = hyperionHistoryUrlStats.filter { it.inSync }.minByOrNull { it.responseTimeMillis }
fastestHyperionUrl?.apply {
chainProvider.hyperionHistoryUrl = url
}
chainProvider.chainUrlStats = mutableListOf()
chainProvider.hyperionHistoryUrlStats = mutableListOf()

val kycProviders = mutableListOf<KYCProvider>()

Expand All @@ -154,8 +82,7 @@ class InitChainProviderWorker
rows?.forEach {
val kycProvidersJsonObject = it.asJsonObject

val kycProvider =
gson.fromJson(kycProvidersJsonObject, KYCProvider::class.java)
val kycProvider = gson.fromJson(kycProvidersJsonObject, KYCProvider::class.java)

if (kycProvider.blackListed == 0) {
kycProviders.add(kycProvider)
Expand Down
Loading

0 comments on commit e22d590

Please sign in to comment.