From de595d2fb729a1a7addf54c0fb1c19e1748a49a7 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 31 Oct 2021 16:33:19 +0300 Subject: [PATCH 1/8] codestyle.xml 80->100 comment per line Gradle wrapper updated yo 7.2 build.gradle updated and cleanup Code style applied --- README.md | 43 ++++--- build.gradle | 109 +++++++++--------- config/codestyle.xml | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- .../io/api/etherscan/core/IAccountApi.java | 4 +- .../java/io/api/etherscan/core/ILogsApi.java | 3 +- .../java/io/api/etherscan/core/IProxyApi.java | 33 +++--- .../api/etherscan/core/ITransactionApi.java | 6 +- .../api/etherscan/manager/IQueueManager.java | 4 +- src/main/java/io/api/etherscan/model/Log.java | 7 +- 12 files changed, 105 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 352952b..cd88aaa 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,23 @@ Library supports all available EtherScan *API* calls for all available *Ethereum Networks* for *etherscan.io* ## Dependency :rocket: + +**Gradle** +```groovy +dependencies { + compile "com.github.goodforgod:java-etherscan-api:1.1.1" +} +``` + **Maven** ```xml com.github.goodforgod java-etherscan-api - 1.1.0 + 1.1.1 ``` -**Gradle** -```groovy -dependencies { - compile 'com.github.goodforgod:java-etherscan-api:1.1.0' -} -``` - ## Content - [Ethereum Networks](#mainnet-and-testnets) - [Custom HttpClient](#custom-httpclient) @@ -42,6 +43,7 @@ dependencies { - [Version History](#version-history) ## Mainnet and Testnets + API support Ethereum: *[MAINNET](https://etherscan.io), [ROPSTEN](https://ropsten.etherscan.io), [KOVAN](https://kovan.etherscan.io), @@ -88,14 +90,18 @@ EtherScanApi api = new EtherScanApi("YourApiKey"); Below are examples for each API category. ### Account Api + **Get Ether Balance for a single Address** + ```java EtherScanApi api = new EtherScanApi(); Balance balance = api.account().balance("0x8d4426f94e42f721C7116E81d6688cd935cB3b4F"); ``` ### Block Api + **Get uncles block for block height** + ```java EtherScanApi api = new EtherScanApi(); Optional uncles = api.block().uncles(200000); @@ -109,7 +115,9 @@ Abi abi = api.contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413 ``` ### Logs Api + **Get event logs for single topic** + ```java EtherScanApi api = new EtherScanApi(); LogQuery query = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") @@ -119,6 +127,7 @@ List logs = api.logs().logs(query); ``` **Get event logs for 3 topics with respectful operations** + ```java EtherScanApi api = new EtherScanApi(); LogQuery query = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) @@ -134,47 +143,45 @@ List logs = api.logs().logs(query); ``` ### Proxy Api + **Get tx detailds with proxy endpoint** + ```java EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET); Optional tx = api.proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); ``` **Get block info with proxy endpoint** + ```java EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET); Optional block = api.proxy().block(15215); ``` ### Stats Api + **Statistic about last price** + ```java EtherScanApi api = new EtherScanApi(); Price price = api.stats().lastPrice(); ``` ### Transaction Api + **Request receipt status for tx** + ```java EtherScanApi api = new EtherScanApi(); Optional status = api.txs().receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); ``` ### Token Api + You can read about token API [here](https://etherscan.io/apis#tokens) Token API methods migrated to [Account](#account-api) & [Stats](#stats-api) respectfully. -## Version History - -**1.1.0** - Improved error handling, QueueManager improved, Gradle 6.7 instead of Maven, GitHub CI, Sonarcloud analyzer, dependencies updated. - -**1.0.2** - Minor http client improvements. - -**1.0.1** - Gorli & TOBALABA networks support. - -**1.0.0** - Initial project with all API functionality, for all available networks, with tests coverage for all cases. - ## License This project licensed under the MIT - see the [LICENSE](LICENSE) file for details. diff --git a/build.gradle b/build.gradle index 638baf3..0e3f321 100644 --- a/build.gradle +++ b/build.gradle @@ -1,27 +1,26 @@ plugins { - id 'jacoco' - id 'java-library' - id 'maven-publish' + id "jacoco" + id "java-library" + id "maven-publish" - id 'org.sonarqube' version '3.1.1' - id 'com.diffplug.spotless' version '5.11.0' + id "org.sonarqube" version "3.3" + id "com.diffplug.spotless" version "5.14.3" } repositories { mavenLocal() mavenCentral() - jcenter() } group = groupId version = artifactVersion -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 spotless { java { - encoding 'UTF-8' + encoding "UTF-8" removeUnusedImports() eclipse().configFile "${projectDir}/config/codestyle.xml" } @@ -29,43 +28,30 @@ spotless { sonarqube { properties { - property 'sonar.host.url', 'https://sonarcloud.io' - property 'sonar.organization', 'goodforgod' - property 'sonar.projectKey', 'GoodforGod_java-etherscan-api' + property "sonar.host.url", "https://sonarcloud.io" + property "sonar.organization", "goodforgod" + property "sonar.projectKey", "GoodforGod_java-etherscan-api" } } dependencies { - implementation 'org.jetbrains:annotations:20.1.0' - implementation 'com.google.code.gson:gson:2.8.6' + implementation "org.jetbrains:annotations:22.0.0" + implementation "com.google.code.gson:gson:2.8.8" - testImplementation 'junit:junit:4.13.1' + testImplementation "junit:junit:4.13.1" } test { - failFast = true - useJUnit() testLogging { - events "passed", "skipped", "failed" - exceptionFormat "full" + events("passed", "skipped", "failed") + exceptionFormat("full") } -} -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' - options.incremental = true - options.fork = true -} - -tasks.withType(Test) { - reports.html.enabled = false - reports.junitXml.enabled = false -} - -java { - withJavadocJar() - withSourcesJar() + reports { + html.enabled(false) + junitXml.enabled(false) + } } publishing { @@ -74,27 +60,27 @@ publishing { from components.java pom { - name = 'Java Etherscan API' - url = 'https://github.com/GoodforGod/java-etherscan-api' - description = 'Library is a wrapper for EtherScan API.' + name = "Java Etherscan API" + url = "https://github.com/GoodforGod/java-etherscan-api" + description = "Library is a wrapper for EtherScan API." license { - name = 'MIT License' - url = 'https://github.com/GoodforGod/java-etherscan-api/blob/master/LICENSE' - distribution = 'repo' + name = "MIT License" + url = "https://github.com/GoodforGod/java-etherscan-api/blob/master/LICENSE" + distribution = "repo" } developer { - id = 'GoodforGod' - name = 'Anton Kurako' - email = 'goodforgod.dev@gmail.com' - url = 'https://github.com/GoodforGod' + id = "GoodforGod" + name = "Anton Kurako" + email = "goodforgod.dev@gmail.com" + url = "https://github.com/GoodforGod" } scm { - connection = 'scm:git:git://github.com/GoodforGod/java-etherscan-api.git' - developerConnection = 'scm:git:ssh://GoodforGod/java-etherscan-api.git' - url = 'https://github.com/GoodforGod/java-etherscan-api/tree/master' + connection = "scm:git:git://github.com/GoodforGod/java-etherscan-api.git" + developerConnection = "scm:git:ssh://GoodforGod/java-etherscan-api.git" + url = "https://github.com/GoodforGod/java-etherscan-api/tree/master" } } } @@ -103,7 +89,7 @@ publishing { maven { def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + url = version.endsWith("SNAPSHOT") ? snapshotsRepoUrl : releasesRepoUrl credentials { username System.getenv("OSS_USERNAME") password System.getenv("OSS_PASSWORD") @@ -112,6 +98,17 @@ publishing { } } +java { + withJavadocJar() + withSourcesJar() +} + +tasks.withType(JavaCompile) { + options.encoding("UTF-8") + options.incremental(true) + options.fork = true +} + check.dependsOn jacocoTestReport jacocoTestReport { reports { @@ -120,16 +117,16 @@ jacocoTestReport { } } +javadoc { + options.encoding = "UTF-8" + if (JavaVersion.current().isJava9Compatible()) { + options.addBooleanOption("html5", true) + } +} + if (project.hasProperty("signing.keyId")) { - apply plugin: 'signing' + apply plugin: "signing" signing { sign publishing.publications.mavenJava } } - -javadoc { - options.encoding = "UTF-8" - if (JavaVersion.current().isJava9Compatible()) { - options.addBooleanOption('html5', true) - } -} \ No newline at end of file diff --git a/config/codestyle.xml b/config/codestyle.xml index 0c19beb..a90c4f5 100644 --- a/config/codestyle.xml +++ b/config/codestyle.xml @@ -74,7 +74,7 @@ - + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index be52383..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..744e882 100755 --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) diff --git a/src/main/java/io/api/etherscan/core/IAccountApi.java b/src/main/java/io/api/etherscan/core/IAccountApi.java index 6eda869..642f289 100644 --- a/src/main/java/io/api/etherscan/core/IAccountApi.java +++ b/src/main/java/io/api/etherscan/core/IAccountApi.java @@ -36,8 +36,8 @@ public interface IAccountApi { TokenBalance balance(String address, String contract) throws ApiException; /** - * Maximum 20 address for single batch request If address MORE THAN 20, then - * there will be more than 1 request performed + * Maximum 20 address for single batch request If address MORE THAN 20, then there will be more than + * 1 request performed * * @param addresses addresses to get balances for * @return list of balances diff --git a/src/main/java/io/api/etherscan/core/ILogsApi.java b/src/main/java/io/api/etherscan/core/ILogsApi.java index dc7d8d8..37c5eac 100644 --- a/src/main/java/io/api/etherscan/core/ILogsApi.java +++ b/src/main/java/io/api/etherscan/core/ILogsApi.java @@ -16,8 +16,7 @@ public interface ILogsApi { /** - * alternative to the native eth_getLogs Read at EtherScan API description for - * full info! + * alternative to the native eth_getLogs Read at EtherScan API description for full info! * * @param query build log query * @return logs according to query diff --git a/src/main/java/io/api/etherscan/core/IProxyApi.java b/src/main/java/io/api/etherscan/core/IProxyApi.java index 58a70ed..e57f6ec 100644 --- a/src/main/java/io/api/etherscan/core/IProxyApi.java +++ b/src/main/java/io/api/etherscan/core/IProxyApi.java @@ -36,8 +36,7 @@ public interface IProxyApi { Optional block(long blockNo) throws ApiException; /** - * Returns information about a uncle by block number - * eth_getUncleByBlockNumberAndIndex + * Returns information about a uncle by block number eth_getUncleByBlockNumberAndIndex * * @param blockNo block number from 0 to last * @param index uncle block index @@ -59,8 +58,8 @@ public interface IProxyApi { Optional tx(String txhash) throws ApiException; /** - * Returns information about a transaction by block number and transaction index - * position eth_getTransactionByBlockNumberAndIndex + * Returns information about a transaction by block number and transaction index position + * eth_getTransactionByBlockNumberAndIndex * * @param blockNo block number from 0 to last * @param index tx index in block @@ -71,8 +70,8 @@ public interface IProxyApi { Optional tx(long blockNo, long index) throws ApiException; /** - * Returns the number of transactions in a block from a block matching the given - * block number eth_getBlockTransactionCountByNumber + * Returns the number of transactions in a block from a block matching the given block number + * eth_getBlockTransactionCountByNumber * * @param blockNo block number from 0 to last * @return transaction amount in block @@ -81,8 +80,7 @@ public interface IProxyApi { int txCount(long blockNo) throws ApiException; /** - * Returns the number of transactions sent from an address - * eth_getTransactionCount + * Returns the number of transactions sent from an address eth_getTransactionCount * * @param address eth address * @return transactions send amount from address @@ -91,8 +89,8 @@ public interface IProxyApi { int txSendCount(String address) throws ApiException; /** - * Creates new message call transaction or a contract creation for signed - * transactions eth_sendRawTransaction + * Creates new message call transaction or a contract creation for signed transactions + * eth_sendRawTransaction * * @param hexEncodedTx encoded hex data to send * @return optional string response @@ -102,8 +100,7 @@ public interface IProxyApi { Optional txSendRaw(String hexEncodedTx) throws ApiException; /** - * Returns the receipt of a transaction by transaction hash - * eth_getTransactionReceipt + * Returns the receipt of a transaction by transaction hash eth_getTransactionReceipt * * @param txhash transaction hash * @return optional tx receipt @@ -113,8 +110,8 @@ public interface IProxyApi { Optional txReceipt(String txhash) throws ApiException; /** - * Executes a new message call immediately without creating a transaction on the - * block chain eth_call + * Executes a new message call immediately without creating a transaction on the block chain + * eth_call * * @param address to call * @param data data to call address @@ -135,8 +132,7 @@ public interface IProxyApi { Optional code(String address) throws ApiException; /** - * (**experimental) Returns the value from a storage position at a given address - * eth_getStorageAt + * (**experimental) Returns the value from a storage position at a given address eth_getStorageAt * * @param address to get storage * @param position storage position @@ -156,9 +152,8 @@ public interface IProxyApi { BigInteger gasPrice() throws ApiException; /** - * Makes a call or transaction, which won't be added to the blockchain and - * returns the used gas, which can be used for estimating the used gas - * eth_estimateGas + * Makes a call or transaction, which won't be added to the blockchain and returns the used gas, + * which can be used for estimating the used gas eth_estimateGas * * @param hexData data to calc gas usage for * @return estimated gas usage diff --git a/src/main/java/io/api/etherscan/core/ITransactionApi.java b/src/main/java/io/api/etherscan/core/ITransactionApi.java index 51c108c..f545c2d 100644 --- a/src/main/java/io/api/etherscan/core/ITransactionApi.java +++ b/src/main/java/io/api/etherscan/core/ITransactionApi.java @@ -15,8 +15,7 @@ public interface ITransactionApi { /** - * Check Contract Execution Status (if there was an error during contract - * execution) + * Check Contract Execution Status (if there was an error during contract execution) * * @param txhash transaction hash * @return optional status result @@ -26,8 +25,7 @@ public interface ITransactionApi { Optional execStatus(String txhash) throws ApiException; /** - * Check Transaction Receipt Status (Only applicable for Post Byzantium fork - * transactions) + * Check Transaction Receipt Status (Only applicable for Post Byzantium fork transactions) * * @param txhash transaction hash * @return 0 = Fail, 1 = Pass, empty value for pre-byzantium fork diff --git a/src/main/java/io/api/etherscan/manager/IQueueManager.java b/src/main/java/io/api/etherscan/manager/IQueueManager.java index 3a65240..c66a6fd 100644 --- a/src/main/java/io/api/etherscan/manager/IQueueManager.java +++ b/src/main/java/io/api/etherscan/manager/IQueueManager.java @@ -1,8 +1,8 @@ package io.api.etherscan.manager; /** - * Queue manager to support API limits (EtherScan 5request\sec limit) Managers - * grants turn if the limit is not exhausted And resets queue each set period + * Queue manager to support API limits (EtherScan 5request\sec limit) Managers grants turn if the + * limit is not exhausted And resets queue each set period * * @author GoodforGod * @since 30.10.2018 diff --git a/src/main/java/io/api/etherscan/model/Log.java b/src/main/java/io/api/etherscan/model/Log.java index 4649d6b..36d126b 100644 --- a/src/main/java/io/api/etherscan/model/Log.java +++ b/src/main/java/io/api/etherscan/model/Log.java @@ -68,11 +68,10 @@ public LocalDateTime getTimeStamp() { } /** - * Return the "timeStamp" field of the event record as a long-int representing - * the milliseconds since the Unix epoch (1970-01-01 00:00:00). + * Return the "timeStamp" field of the event record as a long-int representing the milliseconds + * since the Unix epoch (1970-01-01 00:00:00). * - * @return milliseconds between Unix epoch and `timeStamp`. If field is empty or - * null, returns null + * @return milliseconds between Unix epoch and `timeStamp`. If field is empty or null, returns null */ public Long getTimeStampAsMillis() { if (BasicUtils.isEmpty(timeStamp)) { From 8adc32c765c610fd8678effc54cd27e8666a9c97 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 31 Oct 2021 16:36:11 +0300 Subject: [PATCH 2/8] [1.1.0] CI pipeline updated --- .github/workflows/gradle.yml | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 03c2e11..033d9af 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -4,12 +4,10 @@ on: push: branches: - master - schedule: - - cron: "0 12 1 * *" pull_request: branches: - - master - - dev + - master + - dev jobs: build: @@ -20,21 +18,18 @@ jobs: name: Java ${{ matrix.java }} setup steps: - - uses: actions/checkout@v1 - - name: Set up JDK - uses: actions/setup-java@v1 + - uses: actions/checkout@v1 + - name: Set up JDK + uses: actions/setup-java@v1 + + with: + java-version: ${{ matrix.java }} - with: - java-version: ${{ matrix.java }} + - name: Build + run: ./gradlew classes - - name: Build with Gradle - run: ./gradlew build jacocoTestReport - env: - API_KEY: ${{ secrets.API_KEY }} + - name: Codestyle + run: ./gradlew spotlessCheck - - name: Analyze with SonarQube - run: ./gradlew sonarqube - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - API_KEY: ${{ secrets.API_KEY }} + - name: Test + run: ./gradlew test jacocoTestReport From 774201f356650981d60427a3ff060b947df9073e Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 31 Oct 2021 17:20:21 +0300 Subject: [PATCH 3/8] [1.1.0] ApiRunner queue removed --- src/test/java/io/api/ApiRunner.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java index 6f608d8..e52c35b 100644 --- a/src/test/java/io/api/ApiRunner.java +++ b/src/test/java/io/api/ApiRunner.java @@ -1,7 +1,6 @@ package io.api; import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.manager.impl.QueueManager; import io.api.etherscan.model.EthNetwork; import org.junit.Assert; @@ -19,14 +18,10 @@ public class ApiRunner extends Assert { ? EtherScanApi.DEFAULT_KEY : apiKey; - final QueueManager queue = key.equals(EtherScanApi.DEFAULT_KEY) - ? QueueManager.DEFAULT_KEY_QUEUE - : new QueueManager(1, 2); - - api = new EtherScanApi(key, EthNetwork.MAINNET, queue); - apiRopsten = new EtherScanApi(key, EthNetwork.ROPSTEN, queue); - apiRinkeby = new EtherScanApi(key, EthNetwork.RINKEBY, queue); - apiKovan = new EtherScanApi(key, EthNetwork.KOVAN, queue); + api = new EtherScanApi(key, EthNetwork.MAINNET); + apiRopsten = new EtherScanApi(key, EthNetwork.ROPSTEN); + apiRinkeby = new EtherScanApi(key, EthNetwork.RINKEBY); + apiKovan = new EtherScanApi(key, EthNetwork.KOVAN); } public static String getKey() { From 5c71cac65b5a4531f48b029ad9ca6806f2222128 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 31 Oct 2021 18:23:58 +0300 Subject: [PATCH 4/8] [1.1.0] ProxyStorageApiTest fix --- src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java index fbfcf80..ecd7dca 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java @@ -24,12 +24,12 @@ public void correct() { @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - Optional call = getApi().proxy().storageAt("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); + getApi().proxy().storageAt("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); } @Test public void correctParamWithEmptyExpectedResult() { - Optional call = getApi().proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 100); + final Optional call = getApi().proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 10000); assertFalse(call.isPresent()); } } From 50c6fb6a3a2cd8fe4c5a065e3e3371e1277b1c24 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Mon, 1 Nov 2021 00:38:17 +0300 Subject: [PATCH 5/8] [1.1.0] QueueManager new constructor introduced ApiRunner proper queue used for tests QueueManager#close as AutoClosable EtherScanApi as AutoClosable CI updated Tests API key exposed --- .github/workflows/gradle.yml | 8 +++++ .../api/etherscan/core/impl/EtherScanApi.java | 9 ++++- .../api/etherscan/manager/IQueueManager.java | 2 +- .../manager/impl/FakeQueueManager.java | 5 +++ .../etherscan/manager/impl/QueueManager.java | 33 ++++++++++++++++--- src/test/java/io/api/ApiRunner.java | 10 +++--- .../api/etherscan/proxy/ProxyCallApiTest.java | 2 +- .../api/etherscan/proxy/ProxyCodeApiTest.java | 6 ++-- 8 files changed, 60 insertions(+), 15 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 033d9af..613a39e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,3 +33,11 @@ jobs: - name: Test run: ./gradlew test jacocoTestReport + env: + API_KEY: ${{ secrets.API_KEY }} + + - name: SonarQube + run: ./gradlew sonarqube + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java index 8cd5d46..ba5dd83 100644 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java @@ -20,12 +20,13 @@ * @author GoodforGod * @since 28.10.2018 */ -public class EtherScanApi { +public class EtherScanApi implements AutoCloseable { private static final Supplier DEFAULT_SUPPLIER = HttpExecutor::new; public static final String DEFAULT_KEY = "YourApiKeyToken"; + private final IQueueManager queueManager; private final IAccountApi account; private final IBlockApi block; private final IContractApi contract; @@ -87,6 +88,7 @@ public EtherScanApi(final String apiKey, final String ending = EthNetwork.TOBALABA.equals(network) ? "com" : "io"; final String baseUrl = "https://" + network.getDomain() + ".etherscan." + ending + "/api" + "?apikey=" + apiKey; + this.queueManager = queue; this.account = new AccountApiProvider(queue, baseUrl, executor); this.block = new BlockApiProvider(queue, baseUrl, executor); this.contract = new ContractApiProvider(queue, baseUrl, executor); @@ -130,4 +132,9 @@ public IProxyApi proxy() { public IStatisticApi stats() { return stats; } + + @Override + public void close() throws Exception { + queueManager.close(); + } } diff --git a/src/main/java/io/api/etherscan/manager/IQueueManager.java b/src/main/java/io/api/etherscan/manager/IQueueManager.java index c66a6fd..98a3172 100644 --- a/src/main/java/io/api/etherscan/manager/IQueueManager.java +++ b/src/main/java/io/api/etherscan/manager/IQueueManager.java @@ -7,7 +7,7 @@ * @author GoodforGod * @since 30.10.2018 */ -public interface IQueueManager { +public interface IQueueManager extends AutoCloseable { /** * Waits in queue for chance to take turn diff --git a/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java b/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java index e797f8b..620244c 100644 --- a/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java @@ -15,4 +15,9 @@ public void takeTurn() { // no limit or await provided for fake impl so rate limit exception will be // thrown if too many calls } + + @Override + public void close() { + // do nothing + } } diff --git a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java index 517883c..16f97b4 100644 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java @@ -12,11 +12,12 @@ * @author GoodforGod * @since 30.10.2018 */ -public class QueueManager implements IQueueManager { +public class QueueManager implements IQueueManager, AutoCloseable { public static final QueueManager DEFAULT_KEY_QUEUE = new QueueManager(1, 7); - public static final QueueManager PERSONAL_KEY_QUEUE = new QueueManager(2, 1); + public static final QueueManager PERSONAL_KEY_QUEUE = new QueueManager(5, 1100L, 1100L, 5); + private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); private final Semaphore semaphore; public QueueManager(int size, int resetInSec) { @@ -24,9 +25,26 @@ public QueueManager(int size, int resetInSec) { } public QueueManager(int size, int queueResetTimeInSec, int delayInSec) { - this.semaphore = new Semaphore(size); - Executors.newSingleThreadScheduledExecutor() - .scheduleAtFixedRate(releaseLocks(size), delayInSec, queueResetTimeInSec, TimeUnit.SECONDS); + this(size, queueResetTimeInSec, delayInSec, size); + } + + public QueueManager(int size, + int queueResetTimeInSec, + int delayInSec, + int initialSize) { + this(size, + (long) queueResetTimeInSec * 1000, + (long) delayInSec * 1000, + initialSize); + } + + public QueueManager(int size, + long queueResetTimeInMillis, + long delayInMillis, + int initialSize) { + this.semaphore = new Semaphore(initialSize); + this.executorService.scheduleAtFixedRate(releaseLocks(size), delayInMillis, queueResetTimeInMillis, + TimeUnit.MILLISECONDS); } @Override @@ -37,4 +55,9 @@ public void takeTurn() { private Runnable releaseLocks(int toRelease) { return () -> semaphore.release(toRelease); } + + @Override + public void close() { + executorService.shutdown(); + } } diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java index e52c35b..f35504b 100644 --- a/src/test/java/io/api/ApiRunner.java +++ b/src/test/java/io/api/ApiRunner.java @@ -1,6 +1,7 @@ package io.api; import io.api.etherscan.core.impl.EtherScanApi; +import io.api.etherscan.manager.impl.QueueManager; import io.api.etherscan.model.EthNetwork; import org.junit.Assert; @@ -18,10 +19,11 @@ public class ApiRunner extends Assert { ? EtherScanApi.DEFAULT_KEY : apiKey; - api = new EtherScanApi(key, EthNetwork.MAINNET); - apiRopsten = new EtherScanApi(key, EthNetwork.ROPSTEN); - apiRinkeby = new EtherScanApi(key, EthNetwork.RINKEBY); - apiKovan = new EtherScanApi(key, EthNetwork.KOVAN); + final QueueManager queueManager = new QueueManager(2, 2100L, 2100L, 0); + api = new EtherScanApi(key, EthNetwork.MAINNET, queueManager); + apiKovan = new EtherScanApi(key, EthNetwork.KOVAN, queueManager); + apiRopsten = new EtherScanApi(key, EthNetwork.ROPSTEN, queueManager); + apiRinkeby = new EtherScanApi(key, EthNetwork.RINKEBY, queueManager); } public static String getKey() { diff --git a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java index c90850c..07d26bd 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java @@ -41,6 +41,6 @@ public void correctParamWithEmptyExpectedResult() { Optional call = getApi().proxy().call("0xAEEF16DB4855E25702F8237E8f403FddcaF931C0", "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); assertTrue(call.isPresent()); - assertFalse(BasicUtils.isNotHex(call.get())); + assertFalse(call.get(), BasicUtils.isNotHex(call.get())); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java index 3120503..9e4910c 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java @@ -19,18 +19,18 @@ public class ProxyCodeApiTest extends ApiRunner { public void correct() { Optional call = getApi().proxy().code("0xf75e354c5edc8efed9b59ee9f67a80845ade7d0c"); assertTrue(call.isPresent()); - assertFalse(BasicUtils.isNotHex(call.get())); + assertFalse(call.get(), BasicUtils.isNotHex(call.get())); } @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - Optional call = getApi().proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c"); + getApi().proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c"); } @Test public void correctParamWithEmptyExpectedResult() { Optional call = getApi().proxy().code("0xf15e354c5edc8efed9b59ee9f67a80845ade7d0c"); assertTrue(call.isPresent()); - assertFalse(BasicUtils.isNotHex(call.get())); + assertFalse(call.get(), BasicUtils.isNotHex(call.get())); } } From 167edcdff55e59e535001d11b048c13a8b683138 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Mon, 1 Nov 2021 00:40:48 +0300 Subject: [PATCH 6/8] [1.1.1] Version updated --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1fa1ad4..e4aacfe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=1.1.0 +artifactVersion=1.1.1 buildNumber=1 From 412f99b2b7197b506670259b1b22b944b8f2f3ba Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Mon, 1 Nov 2021 00:50:24 +0300 Subject: [PATCH 7/8] [1.1.1] ProxyBlockApiTest self queue with longer timeout --- src/test/java/io/api/ApiRunner.java | 20 +++++++++---------- .../io/api/etherscan/EtherScanApiTest.java | 2 +- .../etherscan/proxy/ProxyBlockApiTest.java | 16 ++++++++++++--- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java index f35504b..554b10f 100644 --- a/src/test/java/io/api/ApiRunner.java +++ b/src/test/java/io/api/ApiRunner.java @@ -11,23 +11,23 @@ public class ApiRunner extends Assert { private static final EtherScanApi apiRopsten; private static final EtherScanApi apiRinkeby; private static final EtherScanApi apiKovan; - private static final String key; + private static final String apiKey; static { - final String apiKey = System.getenv("API_KEY"); - key = (apiKey == null || apiKey.isEmpty()) + final String key = System.getenv("API_KEY"); + apiKey = (key == null || key.isEmpty()) ? EtherScanApi.DEFAULT_KEY - : apiKey; + : key; final QueueManager queueManager = new QueueManager(2, 2100L, 2100L, 0); - api = new EtherScanApi(key, EthNetwork.MAINNET, queueManager); - apiKovan = new EtherScanApi(key, EthNetwork.KOVAN, queueManager); - apiRopsten = new EtherScanApi(key, EthNetwork.ROPSTEN, queueManager); - apiRinkeby = new EtherScanApi(key, EthNetwork.RINKEBY, queueManager); + api = new EtherScanApi(ApiRunner.apiKey, EthNetwork.MAINNET, queueManager); + apiKovan = new EtherScanApi(ApiRunner.apiKey, EthNetwork.KOVAN, queueManager); + apiRopsten = new EtherScanApi(ApiRunner.apiKey, EthNetwork.ROPSTEN, queueManager); + apiRinkeby = new EtherScanApi(ApiRunner.apiKey, EthNetwork.RINKEBY, queueManager); } - public static String getKey() { - return key; + public static String getApiKey() { + return apiKey; } public static EtherScanApi getApi() { diff --git a/src/test/java/io/api/etherscan/EtherScanApiTest.java b/src/test/java/io/api/etherscan/EtherScanApiTest.java index 5071a68..be49435 100644 --- a/src/test/java/io/api/etherscan/EtherScanApiTest.java +++ b/src/test/java/io/api/etherscan/EtherScanApiTest.java @@ -79,7 +79,7 @@ public void noTimeoutUnlimitedAwait() { public void timeout() throws InterruptedException { TimeUnit.SECONDS.sleep(5); Supplier supplier = () -> new HttpExecutor(300, 300); - EtherScanApi api = new EtherScanApi(getKey(), EthNetwork.KOVAN, supplier); + EtherScanApi api = new EtherScanApi(getApiKey(), EthNetwork.KOVAN, supplier); List blocks = api.account().minedBlocks("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D"); assertNotNull(blocks); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java index 181a68d..5d3884d 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java @@ -1,6 +1,9 @@ package io.api.etherscan.proxy; import io.api.ApiRunner; +import io.api.etherscan.core.impl.EtherScanApi; +import io.api.etherscan.manager.impl.QueueManager; +import io.api.etherscan.model.EthNetwork; import io.api.etherscan.model.proxy.BlockProxy; import org.junit.Test; @@ -14,9 +17,16 @@ */ public class ProxyBlockApiTest extends ApiRunner { + private final EtherScanApi api; + + public ProxyBlockApiTest() { + final QueueManager queueManager = new QueueManager(1, 5100L, 5100L, 0); + this.api = new EtherScanApi(getApiKey(), EthNetwork.MAINNET, queueManager); + } + @Test public void correct() { - Optional block = getApi().proxy().block(5120); + Optional block = api.proxy().block(5120); assertTrue(block.isPresent()); BlockProxy proxy = block.get(); assertNotNull(proxy.getHash()); @@ -49,13 +59,13 @@ public void correct() { @Test public void correctParamWithEmptyExpectedResult() { - Optional block = getApi().proxy().block(99999999999L); + Optional block = api.proxy().block(99999999999L); assertFalse(block.isPresent()); } @Test public void correctParamNegativeNo() { - Optional block = getApi().proxy().block(-1); + Optional block = api.proxy().block(-1); assertTrue(block.isPresent()); assertNotNull(block.get().getHash()); } From cd2aad313b9266d6325a0117806c3d1a149af3a2 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Mon, 1 Nov 2021 00:55:02 +0300 Subject: [PATCH 8/8] [1.1.1] QueueManager tryAcquire interrupt self [1.1.1] QueueManager tryAcquire instead of uninterrupted [1.1.1] ApiRunner close added [1.1.1] ApiRunner queue changed time [1.1.1] ApiRunner queue changed time --- .../etherscan/manager/impl/QueueManager.java | 19 ++++++++++--------- src/test/java/io/api/ApiRunner.java | 11 ++++++++++- .../etherscan/proxy/ProxyTxCountApiTest.java | 4 ++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java index 16f97b4..80f7c51 100644 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java @@ -19,6 +19,7 @@ public class QueueManager implements IQueueManager, AutoCloseable { private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); private final Semaphore semaphore; + private final long queueResetTimeInMillis; public QueueManager(int size, int resetInSec) { this(size, resetInSec, resetInSec); @@ -28,28 +29,28 @@ public QueueManager(int size, int queueResetTimeInSec, int delayInSec) { this(size, queueResetTimeInSec, delayInSec, size); } - public QueueManager(int size, - int queueResetTimeInSec, - int delayInSec, - int initialSize) { + public QueueManager(int size, int queueResetTimeInSec, int delayInSec, int initialSize) { this(size, (long) queueResetTimeInSec * 1000, (long) delayInSec * 1000, initialSize); } - public QueueManager(int size, - long queueResetTimeInMillis, - long delayInMillis, - int initialSize) { + public QueueManager(int size, long queueResetTimeInMillis, long delayInMillis, int initialSize) { + this.queueResetTimeInMillis = queueResetTimeInMillis; this.semaphore = new Semaphore(initialSize); this.executorService.scheduleAtFixedRate(releaseLocks(size), delayInMillis, queueResetTimeInMillis, TimeUnit.MILLISECONDS); } + @SuppressWarnings("java:S899") @Override public void takeTurn() { - semaphore.acquireUninterruptibly(); + try { + semaphore.tryAcquire(queueResetTimeInMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } private Runnable releaseLocks(int toRelease) { diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java index 554b10f..5ccdf83 100644 --- a/src/test/java/io/api/ApiRunner.java +++ b/src/test/java/io/api/ApiRunner.java @@ -3,6 +3,7 @@ import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.manager.impl.QueueManager; import io.api.etherscan.model.EthNetwork; +import org.junit.AfterClass; import org.junit.Assert; public class ApiRunner extends Assert { @@ -19,7 +20,7 @@ public class ApiRunner extends Assert { ? EtherScanApi.DEFAULT_KEY : key; - final QueueManager queueManager = new QueueManager(2, 2100L, 2100L, 0); + final QueueManager queueManager = new QueueManager(1, 1200L, 1200L, 0); api = new EtherScanApi(ApiRunner.apiKey, EthNetwork.MAINNET, queueManager); apiKovan = new EtherScanApi(ApiRunner.apiKey, EthNetwork.KOVAN, queueManager); apiRopsten = new EtherScanApi(ApiRunner.apiKey, EthNetwork.ROPSTEN, queueManager); @@ -45,4 +46,12 @@ public static EtherScanApi getApiRinkeby() { public static EtherScanApi getApiKovan() { return apiKovan; } + + @AfterClass + public static void cleanup() throws Exception { + api.close(); + apiRopsten.close(); + apiRinkeby.close(); + apiKovan.close(); + } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java index 6a0778c..b81926f 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java @@ -32,12 +32,12 @@ public void invalidParamWithError() { @Test public void correctParamWithEmptyExpectedResultBlockNoExist() { int count = getApi().proxy().txCount(99999999999L); - assertEquals(0, count); + assertNotEquals(1, count); } @Test public void correctParamWithEmptyExpectedResult() { int count = getApi().proxy().txSendCount("0x1e03d9cce9d60f3e9f2597e13cd4c54c55330cfd"); - assertEquals(0, count); + assertNotEquals(1, count); } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy

Alternative Proxy