From 8acd3f79d16555850621c5d3b8c0c433345102e4 Mon Sep 17 00:00:00 2001 From: James Brown Date: Tue, 29 Nov 2022 13:57:53 +1100 Subject: [PATCH] Patches for rate usage fix tests --- app/src/main/cpp/keys.c | 13 +++ .../app/entity/CoinGeckoTicker.java | 6 +- .../app/repository/EthereumNetworkBase.java | 26 +++++ .../EthereumNetworkRepositoryType.java | 2 +- .../app/repository/KeyProvider.java | 2 + .../app/repository/KeyProviderJNIImpl.java | 2 + .../app/service/TickerService.java | 5 +- .../alphawallet/app/util/ens/EnsResolver.java | 94 +++++++++++++++---- .../app/viewmodel/DappBrowserViewModel.java | 3 +- .../java/com/alphawallet/app/ENSTest.java | 9 ++ .../app/di/EthereumNetworkBaseTest.java | 2 +- .../app/di/mock/KeyProviderMockImpl.java | 6 ++ .../KeyProviderMockNonProductionImpl.java | 6 ++ 13 files changed, 154 insertions(+), 22 deletions(-) diff --git a/app/src/main/cpp/keys.c b/app/src/main/cpp/keys.c index ef5fa7615a..f73b69fb1e 100644 --- a/app/src/main/cpp/keys.c +++ b/app/src/main/cpp/keys.c @@ -92,6 +92,19 @@ Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getSecondaryInfuraKey( JN #endif } +JNIEXPORT jstring JNICALL +Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getTertiaryInfuraKey( JNIEnv* env, jobject thiz ) +{ +#if (HAS_KEYS == 1) + return getDecryptedKey(env, tertiaryInfuraKey); +#elif (HAS_INFURA == 1) + return (*env)->NewStringUTF(env, IFKEY); +#else + const jstring key = "da3717f25f824cc1baa32d812386d93f"; + return (*env)->NewStringUTF(env, key); +#endif +} + JNIEXPORT jstring JNICALL Java_com_alphawallet_app_repository_KeyProviderJNIImpl_getKlaytnKey( JNIEnv* env, jobject thiz ) { diff --git a/app/src/main/java/com/alphawallet/app/entity/CoinGeckoTicker.java b/app/src/main/java/com/alphawallet/app/entity/CoinGeckoTicker.java index a2087a1722..cb3087f3d2 100644 --- a/app/src/main/java/com/alphawallet/app/entity/CoinGeckoTicker.java +++ b/app/src/main/java/com/alphawallet/app/entity/CoinGeckoTicker.java @@ -45,11 +45,15 @@ public static List buildTickerList(String jsonData, String curr fiatPrice = obj.getDouble(currencyIsoSymbol.toLowerCase()); fiatChangeStr = obj.getString(currencyIsoSymbol.toLowerCase() + "_24h_change"); } - else + else if (obj.has("usd")) { fiatPrice = obj.getDouble("usd") * currentConversionRate; fiatChangeStr = obj.getString("usd_24h_change"); } + else + { + continue; //handle empty/corrupt returns + } res.add(new CoinGeckoTicker(address, fiatPrice, getFiatChange(fiatChangeStr))); } diff --git a/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java b/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java index 07492871fc..918a2d8d85 100644 --- a/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java +++ b/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkBase.java @@ -185,6 +185,32 @@ public abstract class EthereumNetworkBase implements EthereumNetworkRepositoryTy // Add deprecated testnet IDs here )); + private static final String INFURA_ENDPOINT = ".infura.io/v3/"; + + @Override + public String getDappBrowserRPC(long chainId) + { + NetworkInfo info = getNetworkByChain(chainId); + + if (info == null) + { + return ""; + } + else if (chainId == MAINNET_ID) + { + int index = info.rpcServerUrl.indexOf(INFURA_ENDPOINT); + return info.rpcServerUrl.substring(0, index + INFURA_ENDPOINT.length()) + keyProvider.getTertiaryInfuraKey(); + } + else if (info.backupNodeUrl != null) + { + return info.backupNodeUrl; + } + else + { + return info.rpcServerUrl; + } + } + // for reset built-in network private static final LongSparseArray builtinNetworkMap = new LongSparseArray() { diff --git a/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkRepositoryType.java b/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkRepositoryType.java index fb318ada50..085e66c6bf 100644 --- a/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkRepositoryType.java +++ b/app/src/main/java/com/alphawallet/app/repository/EthereumNetworkRepositoryType.java @@ -5,7 +5,6 @@ import com.alphawallet.app.entity.NetworkInfo; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.tokens.Token; -import com.alphawallet.app.repository.entity.RealmToken; import org.web3j.protocol.Web3j; @@ -54,6 +53,7 @@ public interface EthereumNetworkRepositoryType { void setHasSetNetworkFilters(); boolean isMainNetSelected(); void setActiveMainnet(boolean isMainNet); + String getDappBrowserRPC(long chainId); void saveCustomRPCNetwork(String networkName, String rpcUrl, long chainId, String symbol, String blockExplorerUrl, String explorerApiUrl, boolean isTestnet, Long oldChainId); void removeCustomRPCNetwork(long chainId); diff --git a/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java b/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java index dc6a441977..ad02288fb8 100644 --- a/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java +++ b/app/src/main/java/com/alphawallet/app/repository/KeyProvider.java @@ -20,6 +20,8 @@ public interface KeyProvider String getSecondaryInfuraKey(); + String getTertiaryInfuraKey(); + String getRampKey(); String getOpenSeaKey(); diff --git a/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java b/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java index e07661d698..4bd99d0593 100644 --- a/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java +++ b/app/src/main/java/com/alphawallet/app/repository/KeyProviderJNIImpl.java @@ -11,6 +11,8 @@ public KeyProviderJNIImpl() public native String getSecondaryInfuraKey(); + public native String getTertiaryInfuraKey(); + public native String getBSCExplorerKey(); public native String getAnalyticsKey(); diff --git a/app/src/main/java/com/alphawallet/app/service/TickerService.java b/app/src/main/java/com/alphawallet/app/service/TickerService.java index ccc154086f..0351db96e6 100644 --- a/app/src/main/java/com/alphawallet/app/service/TickerService.java +++ b/app/src/main/java/com/alphawallet/app/service/TickerService.java @@ -101,6 +101,7 @@ public class TickerService private static String currentCurrencySymbol; private static final Map canUpdate = new ConcurrentHashMap<>(); private static final Map dexGuruQuery = new ConcurrentHashMap<>(); + private static long lastTickerUpdate; @Nullable private Disposable tickerUpdateTimer; @@ -119,11 +120,12 @@ public TickerService(OkHttpClient httpClient, PreferenceRepositoryType sharedPre resetTickerUpdate(); initCurrency(); + lastTickerUpdate = 0; } public void updateTickers() { - if (mainTickerUpdate != null && !mainTickerUpdate.isDisposed()) + if (mainTickerUpdate != null && !mainTickerUpdate.isDisposed() && System.currentTimeMillis() > (lastTickerUpdate + DateUtils.MINUTE_IN_MILLIS)) { return; //do not update if update is currently in progress } @@ -151,6 +153,7 @@ private void tickersUpdated(int tickerCount) { Timber.d("Tickers Updated: %s", tickerCount); mainTickerUpdate = null; + lastTickerUpdate = System.currentTimeMillis(); } public Single updateCurrencyConversion() diff --git a/app/src/main/java/com/alphawallet/app/util/ens/EnsResolver.java b/app/src/main/java/com/alphawallet/app/util/ens/EnsResolver.java index 5bdf7b9ac0..d25590ed9a 100644 --- a/app/src/main/java/com/alphawallet/app/util/ens/EnsResolver.java +++ b/app/src/main/java/com/alphawallet/app/util/ens/EnsResolver.java @@ -59,6 +59,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -76,6 +78,7 @@ public class EnsResolver implements Resolvable // Permit number offchain calls for a single contract call. public static final int LOOKUP_LIMIT = 4; + private static final long ENS_CACHE_TIME_VALIDITY = 10 * (1000*60); //10 minutes public static final String REVERSE_NAME_SUFFIX = ".addr.reverse"; @@ -112,11 +115,61 @@ public EnsResolver(Web3j web3j) { this(web3j, Keys.ADDRESS_LENGTH_IN_HEX); } - protected ContractAddress obtainOffchainResolverAddr(String ensName) throws Exception + protected ContractAddress obtainOffChainResolverAddress(String ensName) throws Exception { return new ContractAddress(chainId, getResolverAddress(ensName)); } + private static class CachedENSRead + { + public final String cachedResult; + public final long cachedResultTime; + + public CachedENSRead(String result) + { + cachedResult = result; + cachedResultTime = System.currentTimeMillis(); + } + + public boolean isValid() + { + return System.currentTimeMillis() < (cachedResultTime + ENS_CACHE_TIME_VALIDITY); //10 minutes cache validity + } + } + + //Need to cache results for Resolve + private static final Map cachedNameReads = new ConcurrentHashMap<>(); + + private String cacheKey(String ensName, String addrFunction) + { + return ((ensName != null) ? ensName : "") + "%" + ((addrFunction != null) ? addrFunction : ""); + } + + private String resolveWithCaching(String ensName, byte[] nameHash, String resolverAddr) throws Exception + { + String dnsEncoded = NameHash.dnsEncode(ensName); + String addrFunction = encodeResolverAddr(nameHash); + + CachedENSRead lookupData = cachedNameReads.get(cacheKey(ensName, addrFunction)); + String lookupDataHex = lookupData != null ? lookupData.cachedResult : null; + + if (lookupData == null || !lookupData.isValid()) + { + EthCall result = + resolve( + Numeric.hexStringToByteArray(dnsEncoded), + Numeric.hexStringToByteArray(addrFunction), + resolverAddr); + lookupDataHex = result.isReverted() ? Utils.removeDoubleQuotes(result.getError().getData()) : result.getValue();// .toString(); + if (!TextUtils.isEmpty(lookupDataHex) && !lookupDataHex.equals("0x")) + { + cachedNameReads.put(cacheKey(ensName, addrFunction), new CachedENSRead(lookupDataHex)); + } + } + + return lookupDataHex; + } + /** * Returns the address of the resolver for the specified node. * @@ -133,7 +186,7 @@ public String resolve(String ensName) throws Exception try { if (isValidEnsName(ensName, addressLength)) { - ContractAddress resolverAddress = obtainOffchainResolverAddr(ensName); + ContractAddress resolverAddress = obtainOffChainResolverAddress(ensName); boolean supportWildcard = supportsInterface(EnsUtils.ENSIP_10_INTERFACE_ID, resolverAddress.address); @@ -141,16 +194,7 @@ public String resolve(String ensName) throws Exception String resolvedName; if (supportWildcard) { - String dnsEncoded = NameHash.dnsEncode(ensName); - String addrFunction = encodeResolverAddr(nameHash); - - EthCall result = - resolve( - Numeric.hexStringToByteArray(dnsEncoded), - Numeric.hexStringToByteArray(addrFunction), - resolverAddress.address); - - String lookupDataHex = result.isReverted() ? Utils.removeDoubleQuotes(result.getError().getData()) : result.getValue();// .toString(); + String lookupDataHex = resolveWithCaching(ensName, nameHash, resolverAddress.address); resolvedName = resolveOffchain(lookupDataHex, resolverAddress, LOOKUP_LIMIT); } else { try { @@ -359,7 +403,7 @@ public String reverseResolve(String address) throws Exception if (WalletUtils.isValidAddress(address, addressLength)) { String reverseName = Numeric.cleanHexPrefix(address) + REVERSE_NAME_SUFFIX; - ContractAddress resolverAddress = obtainOffchainResolverAddr(reverseName); + ContractAddress resolverAddress = obtainOffChainResolverAddress(reverseName); byte[] nameHash = NameHash.nameHashAsBytes(reverseName); String name; @@ -438,11 +482,27 @@ public boolean supportsInterface(byte[] interfaceID, String address) throws Exce public String resolverAddr(byte[] node, String address) throws Exception { - final org.web3j.abi.datatypes.Function function = new org.web3j.abi.datatypes.Function(FUNC_addr, - Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(node)), - Arrays.>asList(new TypeReference
() {})); + //use caching + String nodeData = Numeric.toHexString(node); + CachedENSRead resolverData = cachedNameReads.get(cacheKey(nodeData, address)); + String resolverAddr = resolverData != null ? resolverData.cachedResult : null; - return getContractData(address, function, ""); + if (resolverData == null || !resolverData.isValid()) + { + final org.web3j.abi.datatypes.Function function = new org.web3j.abi.datatypes.Function(FUNC_addr, + Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(node)), + Arrays.>asList(new TypeReference
() + { + })); + + resolverAddr = getContractData(address, function, ""); + if (!TextUtils.isEmpty(resolverAddr) && resolverAddr.length() > 2) + { + cachedNameReads.put(cacheKey(nodeData, address), new CachedENSRead(resolverAddr)); + } + } + + return resolverAddr; } public String encodeResolverAddr(byte[] node) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java index 49408c2ffb..0608384765 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java @@ -375,9 +375,10 @@ public Single calculateGasEstimate(Wallet wallet, Web3Transaction tr } } + // Use the backup node if avail public String getNetworkNodeRPC(long chainId) { - return ethereumNetworkRepository.getNetworkByChain(chainId).rpcServerUrl; + return ethereumNetworkRepository.getDappBrowserRPC(chainId); } public NetworkInfo getNetworkInfo(long chainId) diff --git a/app/src/test/java/com/alphawallet/app/ENSTest.java b/app/src/test/java/com/alphawallet/app/ENSTest.java index bfd6e8417d..686844683b 100644 --- a/app/src/test/java/com/alphawallet/app/ENSTest.java +++ b/app/src/test/java/com/alphawallet/app/ENSTest.java @@ -69,6 +69,15 @@ public void testResolve() throws Exception { assertEquals( ensResolver.resolve("offchainexample.eth"), ("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045").toLowerCase()); + + //Now test the cache + assertEquals( + ensResolver.resolve("1.offchainexample.eth"), ("0x41563129cdbbd0c5d3e1c86cf9563926b243834d").toLowerCase()); + assertEquals( + ensResolver.resolve("1.offchainexample.eth"), ("0x41563129cdbbd0c5d3e1c86cf9563926b243834d").toLowerCase()); + + assertEquals( + ensResolver.resolve("web3j.eth"), ("0x7bfd522dea355ddee2be3c01dfa4419451759310").toLowerCase()); } //Temporarily remove - DAS seems to be acting up diff --git a/app/src/test/java/com/alphawallet/app/di/EthereumNetworkBaseTest.java b/app/src/test/java/com/alphawallet/app/di/EthereumNetworkBaseTest.java index 935261ff4e..c82b98a1b1 100644 --- a/app/src/test/java/com/alphawallet/app/di/EthereumNetworkBaseTest.java +++ b/app/src/test/java/com/alphawallet/app/di/EthereumNetworkBaseTest.java @@ -28,7 +28,7 @@ public void should_getNodeURLByNetworkId_when_use_production_key() public void should_construct_infura_url_when_getNodeURLByNetworkId_given_production_key() { assertThat(EthereumNetworkBase.getNodeURLByNetworkId(1L), equalTo("https://mainnet.infura.io/v3/fake-key-for-testing")); - assertThat(EthereumNetworkBase.getNodeURLByNetworkId(3L), equalTo("https://rpc.ankr.com/eth_ropsten")); + assertThat(EthereumNetworkBase.getNodeURLByNetworkId(5L), equalTo("https://goerli.infura.io/v3/fake-key-for-testing")); } @Test diff --git a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java index c4b68ca255..4566ab054f 100644 --- a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java +++ b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockImpl.java @@ -60,6 +60,12 @@ public String getSecondaryInfuraKey() return FAKE_KEY_FOR_TESTING; } + @Override + public String getTertiaryInfuraKey() + { + return FAKE_KEY_FOR_TESTING; + } + @Override public String getRampKey() { diff --git a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java index b3be351fad..ae030b12c2 100644 --- a/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java +++ b/app/src/test/java/com/alphawallet/app/di/mock/KeyProviderMockNonProductionImpl.java @@ -59,6 +59,12 @@ public String getSecondaryInfuraKey() return null; } + @Override + public String getTertiaryInfuraKey() + { + return null; + } + @Override public String getRampKey() {