fix: retry TronGrid 429s instead of just tolerating the flake

Public TronGrid rate-limits aggressively, and this test now runs on
every PR (plus its own 8h schedule) - a bare 429 was failing runs for
a transient, infrastructure reason unrelated to code correctness. Add
exponential backoff retry instead of treating it as an accepted flake.

Also required-status-check balances_test.yml's run-tests job on main
now that both this and the wallet-utils phantom-asset fix are confirmed
clean - it was deliberately left optional until proven, per "don't
promise gates you haven't verified."
This commit is contained in:
2026-07-09 17:29:55 -07:00
parent 54bea1234a
commit 01effc26c9
@@ -2,10 +2,12 @@ package io.novafoundation.nova.balances
import io.novafoundation.nova.feature_wallet_impl.data.network.tron.RealTronGridApi import io.novafoundation.nova.feature_wallet_impl.data.network.tron.RealTronGridApi
import io.novafoundation.nova.feature_wallet_impl.data.network.tron.RetrofitTronGridApi import io.novafoundation.nova.feature_wallet_impl.data.network.tron.RetrofitTronGridApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import retrofit2.HttpException
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory import retrofit2.converter.scalars.ScalarsConverterFactory
@@ -40,9 +42,25 @@ class TronBalancesIntegrationTest {
RealTronGridApi(retrofit.create(RetrofitTronGridApi::class.java)) RealTronGridApi(retrofit.create(RetrofitTronGridApi::class.java))
} }
// TronGrid's public (no API key) endpoint rate-limits aggressively, and this test now runs on every PR
// (see balances_test.yml) in addition to its own 2 calls back-to-back - a bare 429 previously failed the
// whole run for a transient, infrastructure-level reason unrelated to whether the wallet's code is correct.
// Retry with backoff instead of just tolerating the flake.
private suspend fun <T> retryOn429(maxAttempts: Int = 4, block: suspend () -> T): T {
repeat(maxAttempts - 1) { attempt ->
try {
return block()
} catch (e: HttpException) {
if (e.code() != 429) throw e
delay(2_000L * (attempt + 1))
}
}
return block()
}
@Test @Test
fun testNativeTrxBalanceLoading() = runBlocking { fun testNativeTrxBalanceLoading() = runBlocking {
val freeBalance = tronGridApi.fetchNativeBalance(baseUrl, testAddress) val freeBalance = retryOn429 { tronGridApi.fetchNativeBalance(baseUrl, testAddress) }
assertTrue("TRX balance: $freeBalance is less than $maxAmount", maxAmount > freeBalance) assertTrue("TRX balance: $freeBalance is less than $maxAmount", maxAmount > freeBalance)
assertTrue("TRX balance: $freeBalance is greater than 0", BigInteger.ZERO < freeBalance) assertTrue("TRX balance: $freeBalance is greater than 0", BigInteger.ZERO < freeBalance)
@@ -50,7 +68,7 @@ class TronBalancesIntegrationTest {
@Test @Test
fun testTrc20UsdtBalanceLoading() = runBlocking { fun testTrc20UsdtBalanceLoading() = runBlocking {
val freeBalance = tronGridApi.fetchTrc20Balance(baseUrl, testAddress, usdtContractAddress) val freeBalance = retryOn429 { tronGridApi.fetchTrc20Balance(baseUrl, testAddress, usdtContractAddress) }
assertTrue("USDT-TRC20 balance: $freeBalance is less than $maxAmount", maxAmount > freeBalance) assertTrue("USDT-TRC20 balance: $freeBalance is less than $maxAmount", maxAmount > freeBalance)
assertTrue("USDT-TRC20 balance: $freeBalance is greater than 0", BigInteger.ZERO < freeBalance) assertTrue("USDT-TRC20 balance: $freeBalance is greater than 0", BigInteger.ZERO < freeBalance)