From 05bc02028eda65ab50f0e819eaa120a60d1c29d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 10:49:02 +0000 Subject: [PATCH 01/16] Bump junit from 4.12 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a9bcfa8..6c51f28 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ 1.6.8 2.7 - 4.12 + 4.13.1 2.8.5 From 670ce918b639d27e0bcbe6f0c3603784d4bb0a7d Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Thu, 9 Jul 2020 22:01:21 +0200 Subject: [PATCH 02/16] Method to get an event time stamp as milliseconds since the Unix epoch to avoid time zone calculations (cherry picked from commit d27d01a51d0d2bab653ee0f11b37bce7282ea55d) --- .gitignore | 32 ++++++++++++++++++- src/main/java/io/api/etherscan/model/Log.java | 15 ++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1062418..719a4de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,32 @@ -.idea/ +# Compiled class files +*.class + +# Log file +*.log +**/.log + +# IntelliJ *.iml +/.idea + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# other +/bin/ +/.classpath +/.project +/target/ +/out/ +/.DS_Store +/.settings/ + diff --git a/src/main/java/io/api/etherscan/model/Log.java b/src/main/java/io/api/etherscan/model/Log.java index cf485fd..f1535b3 100644 --- a/src/main/java/io/api/etherscan/model/Log.java +++ b/src/main/java/io/api/etherscan/model/Log.java @@ -60,12 +60,25 @@ public LocalDateTime getTimeStamp() { if(_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) { long formatted = (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') ? BasicUtils.parseHex(timeStamp).longValue() - : Long.valueOf(timeStamp); + : Long.parseLong(timeStamp); _timeStamp = LocalDateTime.ofEpochSecond(formatted, 0, ZoneOffset.UTC); } return _timeStamp; } + /** + * + * @return + */ + public Long getTimeStampAsMillis() { + if (BasicUtils.isEmpty(timeStamp)) { + return null; + } + return (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') + ? BasicUtils.parseHex(timeStamp).longValue() + : Long.parseLong(timeStamp) * 1000; + } + public String getData() { return data; } From 4a570d203b864795ea62a5be55c35e0516e4ade3 Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Thu, 9 Jul 2020 22:11:13 +0200 Subject: [PATCH 03/16] Method to get an event time stamp as milliseconds since the Unix epoch to avoid time zone calculations (cherry picked from commit 8de0601e788773696f583bbe4467c938ac494e73) --- src/main/java/io/api/etherscan/model/Log.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/api/etherscan/model/Log.java b/src/main/java/io/api/etherscan/model/Log.java index f1535b3..2fa58ee 100644 --- a/src/main/java/io/api/etherscan/model/Log.java +++ b/src/main/java/io/api/etherscan/model/Log.java @@ -67,16 +67,18 @@ public LocalDateTime getTimeStamp() { } /** - * - * @return + * 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 */ public Long getTimeStampAsMillis() { if (BasicUtils.isEmpty(timeStamp)) { return null; } - return (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') + long tsSecs = (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') ? BasicUtils.parseHex(timeStamp).longValue() - : Long.parseLong(timeStamp) * 1000; + : Long.parseLong(timeStamp); + return tsSecs * 1000; } public String getData() { From c1e995fa0b95160bf6b57e573f994fbf3514098b Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Tue, 14 Jul 2020 15:07:56 +0200 Subject: [PATCH 04/16] Method to get all events without a topic filter (cherry picked from commit 235be0d2021420d920a44b60979c7143e5e9d60c) --- .../api/etherscan/model/query/impl/LogQueryBuilder.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java b/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java index d4e10be..1397f15 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java @@ -2,6 +2,7 @@ import io.api.etherscan.core.ILogsApi; import io.api.etherscan.error.LogQueryException; +import io.api.etherscan.model.query.IQueryBuilder; import io.api.etherscan.util.BasicUtils; /** @@ -12,7 +13,7 @@ * @author GoodforGod * @since 31.10.2018 */ -public class LogQueryBuilder { +public class LogQueryBuilder implements IQueryBuilder { private static final long MIN_BLOCK = 0; private static final long MAX_BLOCK = 99999999999L; @@ -75,4 +76,9 @@ public LogTopicQuadro topic(String topic0, String topic1, String topic2, String return new LogTopicQuadro(address, startBlock, endBlock, topic0, topic1, topic2, topic3); } + + @Override + public LogQuery build() throws LogQueryException { + return new LogQuery("&address=" + this.address + "&fromBlock=" + this.startBlock + "&toBlock=" + this.endBlock); + } } From 1e039952fdce04a0c23c755e777f604fc7551cd6 Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Tue, 14 Jul 2020 22:05:33 +0200 Subject: [PATCH 05/16] Higher-level API to read well-known ETH events as polymorphic classes (cherry picked from commit ec0cf80dc4d79f47f638ea5d56e793ee27b6f358) --- .../io/api/etherscan/core/IEventsApi.java | 28 ++++++++++ .../core/impl/EventsApiProvider.java | 55 +++++++++++++++++++ .../etherscan/error/EventModelException.java | 11 ++++ .../io/api/etherscan/model/event/IEvent.java | 32 +++++++++++ .../model/event/impl/ApprovalEvent.java | 10 ++++ .../model/event/impl/DepositEvent.java | 10 ++++ .../api/etherscan/model/event/impl/Event.java | 42 ++++++++++++++ .../etherscan/model/event/impl/MintEvent.java | 11 ++++ .../etherscan/model/event/impl/SyncEvent.java | 10 ++++ .../model/event/impl/TransferErc20Event.java | 31 +++++++++++ .../model/event/impl/WithdrawEvent.java | 10 ++++ 11 files changed, 250 insertions(+) create mode 100644 src/main/java/io/api/etherscan/core/IEventsApi.java create mode 100644 src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java create mode 100644 src/main/java/io/api/etherscan/error/EventModelException.java create mode 100644 src/main/java/io/api/etherscan/model/event/IEvent.java create mode 100644 src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java create mode 100644 src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java create mode 100644 src/main/java/io/api/etherscan/model/event/impl/Event.java create mode 100644 src/main/java/io/api/etherscan/model/event/impl/MintEvent.java create mode 100644 src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java create mode 100644 src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java create mode 100644 src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java diff --git a/src/main/java/io/api/etherscan/core/IEventsApi.java b/src/main/java/io/api/etherscan/core/IEventsApi.java new file mode 100644 index 0000000..c5f3665 --- /dev/null +++ b/src/main/java/io/api/etherscan/core/IEventsApi.java @@ -0,0 +1,28 @@ +package io.api.etherscan.core; + +import io.api.etherscan.error.ApiException; +import io.api.etherscan.model.Log; +import io.api.etherscan.model.event.IEvent; +import io.api.etherscan.model.query.impl.LogQuery; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * EtherScan - API Descriptions + * https://etherscan.io/apis#logs + */ +public interface IEventsApi { + + /** + * This is a high-level alternative to the ILogsApi and an alternative to the native eth_getLogs + * Read at EtherScan API description for full info! + * @param query build log query + * @return logs according to query + * @throws ApiException parent exception class + * + * @see io.api.etherscan.model.query.impl.LogQueryBuilder + */ + @NotNull + List events(LogQuery query) throws ApiException; +} diff --git a/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java b/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java new file mode 100644 index 0000000..b99a934 --- /dev/null +++ b/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java @@ -0,0 +1,55 @@ +package io.api.etherscan.core.impl; + +import io.api.etherscan.core.IEventsApi; +import io.api.etherscan.core.ILogsApi; +import io.api.etherscan.error.ApiException; +import io.api.etherscan.executor.IHttpExecutor; +import io.api.etherscan.manager.IQueueManager; +import io.api.etherscan.model.Log; +import io.api.etherscan.model.event.IEvent; +import io.api.etherscan.model.event.impl.Event; +import io.api.etherscan.model.query.impl.LogQuery; +import io.api.etherscan.model.utility.LogResponseTO; +import io.api.etherscan.util.BasicUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Logs API Implementation + * + * @see IEventsApi + * + */ +public class EventsApiProvider extends BasicProvider implements IEventsApi { + + private static final String ACT_LOGS_PARAM = ACT_PREFIX + "getLogs"; + + EventsApiProvider(final IQueueManager queue, + final String baseUrl, + final IHttpExecutor executor) { + super(queue, "logs", baseUrl, executor); + } + + @NotNull + @Override + public List events(final LogQuery query) throws ApiException { + final String urlParams = ACT_LOGS_PARAM + query.getParams(); + final LogResponseTO response = getRequest(urlParams, LogResponseTO.class); + BasicUtils.validateTxResponse(response); + + if (BasicUtils.isEmpty(response.getResult())) { + return Collections.emptyList(); + }; + return response + .getResult() + .stream() + .map((log) -> { + String eventTypeHash = log.getTopics().get(0); + return IEvent.createEvent(eventTypeHash, log); + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/api/etherscan/error/EventModelException.java b/src/main/java/io/api/etherscan/error/EventModelException.java new file mode 100644 index 0000000..5c3e17e --- /dev/null +++ b/src/main/java/io/api/etherscan/error/EventModelException.java @@ -0,0 +1,11 @@ +package io.api.etherscan.error; + +public class EventModelException extends ApiException { + public EventModelException(String message) { + super(message); + } + + public EventModelException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/api/etherscan/model/event/IEvent.java b/src/main/java/io/api/etherscan/model/event/IEvent.java new file mode 100644 index 0000000..0fb0e65 --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/IEvent.java @@ -0,0 +1,32 @@ +package io.api.etherscan.model.event; + +import io.api.etherscan.error.ApiException; +import io.api.etherscan.error.EventModelException; +import io.api.etherscan.model.Log; + +import java.util.HashMap; +import java.util.Map; + +public interface IEvent { + static final Map> subTypes = new HashMap<>(); + + void setLog(Log log); + + static void registerEventType(String typeHash, Class clazz) { + subTypes.put(typeHash, clazz); + } + + static IEvent createEvent(String typeHash, Log log) { + if (null == typeHash) { + throw new EventModelException("Event type hash cannot be null"); + } + Class clazz = subTypes.get(typeHash); + try { + IEvent evt = (IEvent) clazz.newInstance(); + evt.setLog(log); + return evt; + } catch (InstantiationException | IllegalAccessException ex) { + throw new ApiException("Client-side error instantiating Event object", ex); + } + } +} diff --git a/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java b/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java new file mode 100644 index 0000000..915b99c --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java @@ -0,0 +1,10 @@ +package io.api.etherscan.model.event.impl; + +import io.api.etherscan.model.event.IEvent; + +public class ApprovalEvent extends Event { + static final String eventTypeHash = "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"; + static { + IEvent.registerEventType(ApprovalEvent.eventTypeHash, ApprovalEvent.class); + } +} diff --git a/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java b/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java new file mode 100644 index 0000000..31343a5 --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java @@ -0,0 +1,10 @@ +package io.api.etherscan.model.event.impl; + +import io.api.etherscan.model.event.IEvent; + +public class DepositEvent extends Event { + static final String eventTypeHash = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"; + static { + IEvent.registerEventType(DepositEvent.eventTypeHash, DepositEvent.class); + } +} diff --git a/src/main/java/io/api/etherscan/model/event/impl/Event.java b/src/main/java/io/api/etherscan/model/event/impl/Event.java new file mode 100644 index 0000000..dbcffcd --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/impl/Event.java @@ -0,0 +1,42 @@ +package io.api.etherscan.model.event.impl; + +import io.api.etherscan.error.ApiException; +import io.api.etherscan.error.EventModelException; +import io.api.etherscan.model.Log; +import io.api.etherscan.model.event.IEvent; + +import java.util.HashMap; +import java.util.Map; + +/** + * Base class for a higher-level API on top of {@link Log}. Each Event class has an identifying hash + */ +public class Event implements IEvent { + + static String eventTypeHash; + + private Log log; + + String address; + + public static String getEventTypeHash() { + return eventTypeHash; + } + + public Log getLog() { + return log; + } + + public String getAddress() { + return address; + } + + public void setLog(Log log) { + this.log = log; + } + + public void setAddress(String address) { + this.address = address; + } + +} diff --git a/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java b/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java new file mode 100644 index 0000000..597dd7e --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java @@ -0,0 +1,11 @@ +package io.api.etherscan.model.event.impl; + +import io.api.etherscan.model.event.IEvent; + +public class MintEvent extends Event { + static final String eventTypeHash = "0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f"; + static { + IEvent.registerEventType(MintEvent.eventTypeHash, MintEvent.class); + } + +} diff --git a/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java b/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java new file mode 100644 index 0000000..ff566f4 --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java @@ -0,0 +1,10 @@ +package io.api.etherscan.model.event.impl; + +import io.api.etherscan.model.event.IEvent; + +public class SyncEvent extends Event { + static final String eventTypeHash = "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"; + static { + IEvent.registerEventType(SyncEvent.eventTypeHash, SyncEvent.class); + } +} diff --git a/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java b/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java new file mode 100644 index 0000000..a1a8be0 --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java @@ -0,0 +1,31 @@ +package io.api.etherscan.model.event.impl; + +import io.api.etherscan.model.event.IEvent; + +public class TransferErc20Event extends Event { + static final String eventTypeHash = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; + static { + IEvent.registerEventType(TransferErc20Event.eventTypeHash, TransferErc20Event.class); + } + + String fromAddress; + + String toAddress; + + public String getFromAddress() { + return fromAddress; + } + + public void setFromAddress(String fromAddress) { + this.fromAddress = fromAddress; + } + + public String getToAddress() { + return toAddress; + } + + public void setToAddress(String toAddress) { + this.toAddress = toAddress; + } + +} diff --git a/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java b/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java new file mode 100644 index 0000000..3fa442b --- /dev/null +++ b/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java @@ -0,0 +1,10 @@ +package io.api.etherscan.model.event.impl; + +import io.api.etherscan.model.event.IEvent; + +public class WithdrawEvent extends Event { + static final String eventTypeHash = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"; + static { + IEvent.registerEventType(WithdrawEvent.eventTypeHash, WithdrawEvent.class); + } +} From d7ab7ecceb2a82e75ea37993b606209d9a2e2a86 Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Mon, 5 Oct 2020 17:01:10 +0200 Subject: [PATCH 06/16] Added better handling of communication errors (cherry picked from commit 28932837fc84a959e071cc2a264fc70894ea031a) --- .../java/io/api/etherscan/error/ConnectionException.java | 4 ++++ .../java/io/api/etherscan/executor/impl/HttpExecutor.java | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/api/etherscan/error/ConnectionException.java b/src/main/java/io/api/etherscan/error/ConnectionException.java index c22955c..410c0ac 100644 --- a/src/main/java/io/api/etherscan/error/ConnectionException.java +++ b/src/main/java/io/api/etherscan/error/ConnectionException.java @@ -8,6 +8,10 @@ */ public class ConnectionException extends ApiException { + public ConnectionException(String message) { + super(message); + } + public ConnectionException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java b/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java index 3a33515..c059d27 100644 --- a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java +++ b/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java @@ -18,8 +18,7 @@ import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; -import static java.net.HttpURLConnection.HTTP_MOVED_PERM; -import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; +import static java.net.HttpURLConnection.*; /** * Http client implementation @@ -88,6 +87,10 @@ public String get(final String urlAsString) { final int status = connection.getResponseCode(); if (status == HTTP_MOVED_TEMP || status == HTTP_MOVED_PERM) { return get(connection.getHeaderField("Location")); + } else if ((status >= HTTP_BAD_REQUEST) && (status < HTTP_INTERNAL_ERROR)) { + throw new ConnectionException("Protocol error: "+connection.getResponseMessage()); + } else if (status >= HTTP_INTERNAL_ERROR) { + throw new ConnectionException("Server error: "+connection.getResponseMessage()); } final String data = readData(connection); From 01b31ff82aa4e9a764a56435d631a627013d900c Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Tue, 6 Oct 2020 09:27:35 +0200 Subject: [PATCH 07/16] Removed events stuff again, moved to different library (cherry picked from commit 5c79d665b63806935f6a89e70aefb6fdf28c3f84) --- src/main/java/io/api/etherscan/core/impl/BasicProvider.java | 2 +- src/main/java/io/api/etherscan/error/ParseException.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index 5b95f68..67c790b 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -42,7 +42,7 @@ T convert(final String json, final Class tClass) { try { return gson.fromJson(json, tClass); } catch (Exception e) { - throw new ParseException(e.getMessage(), e.getCause()); + throw new ParseException(e.getMessage(), e.getCause(), json); } } diff --git a/src/main/java/io/api/etherscan/error/ParseException.java b/src/main/java/io/api/etherscan/error/ParseException.java index 81974df..f279fda 100644 --- a/src/main/java/io/api/etherscan/error/ParseException.java +++ b/src/main/java/io/api/etherscan/error/ParseException.java @@ -7,8 +7,10 @@ * @since 29.10.2018 */ public class ParseException extends ApiException { + String json; - public ParseException(String message, Throwable cause) { + public ParseException(String message, Throwable cause, String json) { super(message, cause); + this.json = json; } } From 5729da7d7c1fbffd8e68adf13d7d3ab166793402 Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Tue, 6 Oct 2020 09:45:28 +0200 Subject: [PATCH 08/16] Removed events stuff again, moved to different library (cherry picked from commit 50d5799439967f0bb7a21c98d211cde5c604b1d0) --- src/test/java/io/api/util/BasicUtilsTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/api/util/BasicUtilsTests.java b/src/test/java/io/api/util/BasicUtilsTests.java index 1b1753e..2c5ad71 100644 --- a/src/test/java/io/api/util/BasicUtilsTests.java +++ b/src/test/java/io/api/util/BasicUtilsTests.java @@ -98,6 +98,6 @@ public void isResponseNullThrows() { @Test(expected = ParseException.class) public void isThrowParseException() { - throw new ParseException("Test", null); + throw new ParseException("Test", null, null); } } From a4e4b0993d9abbe367b5a6b60a098a88d5a9a278 Mon Sep 17 00:00:00 2001 From: iSnow <139699+iSnow@users.noreply.github.com> Date: Tue, 6 Oct 2020 11:23:03 +0200 Subject: [PATCH 09/16] Added support for rate limiting by Etherscan: throw RateLimitException (cherry picked from commit 120ba0fc8712f42e0492b451a8db8243fddece52) --- .../api/etherscan/core/impl/BasicProvider.java | 17 +++++++++++++++++ .../api/etherscan/error/RateLimitException.java | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/main/java/io/api/etherscan/error/RateLimitException.java diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index 67c790b..d242a76 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -1,12 +1,16 @@ package io.api.etherscan.core.impl; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import io.api.etherscan.error.EtherScanException; import io.api.etherscan.error.ParseException; +import io.api.etherscan.error.RateLimitException; import io.api.etherscan.executor.IHttpExecutor; import io.api.etherscan.manager.IQueueManager; import io.api.etherscan.util.BasicUtils; +import java.util.Map; + /** * Base provider for API Implementations * @@ -42,6 +46,19 @@ T convert(final String json, final Class tClass) { try { return gson.fromJson(json, tClass); } catch (Exception e) { + if (e instanceof JsonSyntaxException) { + Map map = gson.fromJson(json, Map.class); + Object statusCode = map.get("status"); + if ((statusCode instanceof String) && (statusCode.equals("0"))) { + Object message = map.get("message"); + if ((message instanceof String) && (message.equals("NOTOK"))) { + Object result = map.get("result"); + if ((result instanceof String) && (result.equals("Max rate limit reached"))) { + throw new RateLimitException ("Max rate limit reached"); + } + } + } + } throw new ParseException(e.getMessage(), e.getCause(), json); } } diff --git a/src/main/java/io/api/etherscan/error/RateLimitException.java b/src/main/java/io/api/etherscan/error/RateLimitException.java new file mode 100644 index 0000000..2562342 --- /dev/null +++ b/src/main/java/io/api/etherscan/error/RateLimitException.java @@ -0,0 +1,15 @@ +package io.api.etherscan.error; + +/** + * ! NO DESCRIPTION ! + * + * @author iSnow + * @since 2020-10-06 + */ +public class RateLimitException extends ApiException { + + public RateLimitException(String message) { + super(message); + } + +} From 33b519f07e50f9bbf0935a1fc65146083cbaa8a6 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 18 Oct 2020 15:46:10 +0300 Subject: [PATCH 10/16] [1.1.0-SNAPSHOT] POM removed Gradle 6.7 wuth gradle wrapper added Gradle configs and properties added codestyle.xml config added github CI added --- .editorconfig | 21 ++ .gitattributes | 41 +++ .github/workflows/gradle.yml | 38 +++ .gitignore | 37 +-- .travis.yml | 14 - README.md | 8 +- build.gradle | 133 +++++++++ config/codestyle.xml | 348 +++++++++++++++++++++++ gradle.properties | 12 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++++++++ gradlew.bat | 89 ++++++ pom.xml | 190 ------------- settings.gradle | 1 + settings.xml | 26 -- 16 files changed, 882 insertions(+), 266 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/gradle.yml delete mode 100644 .travis.yml create mode 100644 build.gradle create mode 100644 config/codestyle.xml create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat delete mode 100644 pom.xml create mode 100644 settings.gradle delete mode 100644 settings.xml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5b9451e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# all-encompassing default settings unless otherwise specified +[*] +end_of_line = lf +charset = utf-8 + +# Yaml +[{*.yml, *.yaml}] +indent_size = 2 +indent_style = space + +# Property files +[*.properties] +indent_size = 2 +indent_style = space + + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ccc6fb5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,41 @@ +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto + +# +# The above will handle all files NOT found below +# +# These files are text and should be normalized (Convert crlf => lf) +*.bash text eol=lf +*.css text diff=css +*.df text +*.htm text diff=html +*.html text diff=html eol=lf +*.java text diff=java eol=lf +*.js text +*.json text eol=lf +*.jsp text eol=lf +*.jspf text eol=lf +*.jspx text eol=lf +*.properties text eol=lf +*.sh text eol=lf +*.tld text +*.txt text eol=lf +*.tag text +*.tagx text +*.xml text +*.yml text eol=lf + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.dll binary +*.ear binary +*.gif binary +*.ico binary +*.jar binary +*.jpg binary +*.jpeg binary +*.png binary +*.so binary +*.war binary \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..655e4ad --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,38 @@ +name: Java CI + +on: + push: + branches: + - master + - dev + schedule: + - cron: "0 12 1 * *" + pull_request: + branches: + - master + - dev + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '1.8', '11' ] + name: Java ${{ matrix.java }} setup + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK + uses: actions/setup-java@v1 + + with: + java-version: ${{ matrix.java }} + + - name: Build with Gradle + run: ./gradlew build jacocoTestReport + + - name: Analyze with SonarQube + run: ./gradlew sonarqube + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.gitignore b/.gitignore index 719a4de..c48c7a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,7 @@ -# Compiled class files -*.class - -# Log file -*.log -**/.log - -# IntelliJ -*.iml -/.idea - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -# other -/bin/ -/.classpath -/.project -/target/ -/out/ -/.DS_Store /.settings/ - +.idea +.idea/httpRequests +*.iml +.gradle +build +target/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ea1440d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: java - -dist: trusty -sudo: false - -script: - - "mvn cobertura:cobertura" - -after_success: - - bash <(curl -s https://codecov.io/bash) - -jdk: - - oraclejdk8 - - openjdk8 diff --git a/README.md b/README.md index f62f766..733a2ed 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # java-etherscan-api -![travis](https://travis-ci.org/GoodforGod/java-etherscan-api.svg?branch=master) -[![Maintainability](https://api.codeclimate.com/v1/badges/808997be2e69ff1ae8fe/maintainability)](https://codeclimate.com/github/GoodforGod/java-etherscan-api/maintainability) -[![codecov](https://codecov.io/gh/GoodforGod/java-etherscan-api/branch/master/graph/badge.svg)](https://codecov.io/gh/GoodforGod/java-etherscan-api) +[![Jitpack](https://jitpack.io/v/iSnow/java-etherscan-api.svg)](https://jitpack.io/#GoodforGod/java-etherscan-api) [Etherscan](https://etherscan.io/apis) Java API implementation. @@ -14,14 +12,14 @@ Library supports all available EtherScan *API* calls for all available *Ethereum com.github.goodforgod java-etherscan-api - 1.0.2 + 1.1.0 ``` **Gradle** ```groovy dependencies { - compile 'com.github.goodforgod:java-etherscan-api:1.0.2' + compile 'com.github.goodforgod:java-etherscan-api:1.1.0' } ``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a68159c --- /dev/null +++ b/build.gradle @@ -0,0 +1,133 @@ +plugins { + id 'jacoco' + id 'java-library' + id 'maven-publish' + + id 'org.sonarqube' version '3.0' + id 'com.diffplug.gradle.spotless' version '4.4.0' +} + +repositories { + mavenLocal() + mavenCentral() + jcenter() +} + +group = groupId +version = artifactVersion + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +spotless { + java { + encoding 'UTF-8' + removeUnusedImports() + eclipse().configFile "${projectDir}/config/codestyle.xml" + } +} + +sonarqube { + properties { + 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' + + testImplementation 'junit:junit:4.13.1' +} + +test { + useJUnit() + testLogging { + 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() +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + + pom { + 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' + } + + developer { + 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' + } + } + } + } + repositories { + 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 + credentials { + username System.getenv("OSS_USERNAME") + password System.getenv("OSS_PASSWORD") + } + } + } +} + +check.dependsOn jacocoTestReport +jacocoTestReport { + reports { + xml.enabled true + html.destination file("${buildDir}/jacocoHtml") + } +} + +if (project.hasProperty("signing.keyId")) { + 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 new file mode 100644 index 0000000..0c19beb --- /dev/null +++ b/config/codestyle.xml @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..0c89028 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +groupId=com.github.goodforgod +artifactId=java-etherscan-api +artifactVersion=1.1.0-SNAPSHOT +buildNumber=1 + + +##### GRADLE ##### +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.jvmargs=-Dfile.encoding=UTF-8 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..be52383 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 6c51f28..0000000 --- a/pom.xml +++ /dev/null @@ -1,190 +0,0 @@ - - - 4.0.0 - - com.github.goodforgod - java-etherscan-api - 1.0.2 - jar - - ${project.groupId}:${project.artifactId} - Library is a wrapper for EtherScan API. - http://maven.apache.org - - - - MIT License - https://github.com/GoodforGod/java-etherscan-api/blob/master/LICENSE - repo - - - - - - Anton Kurako - goodforgod.dev@gmail.com - https://github.com/GoodforGod - - - - - scm:git:git://github.com/GoodforGod/java-etherscan-api.git - scm:git:ssh://GoodforGod/java-etherscan-api.git - https://github.com/GoodforGod/java-etherscan-api/tree/master - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - UTF-8 - 1.8 - 1.8 - - 3.0.2 - 1.6 - 3.0.1 - 3.0.1 - 1.6.8 - 2.7 - - 4.13.1 - 2.8.5 - - - - - junit - junit - ${junit-version} - test - - - - com.google.code.gson - gson - ${gson-version} - - - - org.jetbrains - annotations - 13.0 - - - - - - release - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${maven-nexus-staging-maven-plugin-version} - true - - ossrh - https://oss.sonatype.org/ - true - - - - - - - - sign - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin-version} - - - sign-artifacts - verify - - sign - - - - - - - - - - build-extras - - true - - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin-version} - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin-version} - - - attach-javadocs - - jar - - - - - -html5 - - - - - - - - - - - org.codehaus.mojo - cobertura-maven-plugin - ${cobertura-plugin-version} - - - html - xml - - - - - - org.apache.maven.plugins - maven-jar-plugin - ${maven-build-plugin-version} - - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..8e004bc --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = artifactId diff --git a/settings.xml b/settings.xml deleted file mode 100644 index ab979c3..0000000 --- a/settings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - ossrh - ${env.OSSRH_JIRA_USERNAME} - ${env.OSSRH_JIRA_PASSWORD} - - - - - - ossrh - - true - - - gpg - ${env.GPG_KEY_NAME} - ${env.GPG_PASSPHRASE} - - - - \ No newline at end of file From 664cb72a2d876205bcd7f6058913608e0dcc9141 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 18 Oct 2020 15:48:32 +0300 Subject: [PATCH 11/16] [1.1.0-SNAPSHOT] Error handling and error message improved RateLimitException handling added and improved HttpExecutor impl error handling improved IQueueManager contract refactored QueueManager semaphore impl added IQueueManager construct parameter added Default queue manager 1req\7 sec added as other limits throw rate limit --- .../io/api/etherscan/core/IAccountApi.java | 77 ++++++++++----- .../java/io/api/etherscan/core/IBlockApi.java | 7 +- .../io/api/etherscan/core/IContractApi.java | 7 +- .../io/api/etherscan/core/IEventsApi.java | 9 +- .../java/io/api/etherscan/core/ILogsApi.java | 11 ++- .../java/io/api/etherscan/core/IProxyApi.java | 98 +++++++++++-------- .../io/api/etherscan/core/IStatisticApi.java | 15 ++- .../api/etherscan/core/ITransactionApi.java | 17 ++-- .../core/impl/AccountApiProvider.java | 13 ++- .../etherscan/core/impl/BasicProvider.java | 48 ++++++--- .../etherscan/core/impl/BlockApiProvider.java | 2 +- .../core/impl/ContractApiProvider.java | 4 +- .../api/etherscan/core/impl/EtherScanApi.java | 52 ++++++---- .../core/impl/EventsApiProvider.java | 7 +- .../etherscan/core/impl/ProxyApiProvider.java | 15 +-- .../core/impl/StatisticApiProvider.java | 6 +- .../io/api/etherscan/error/ApiException.java | 2 - .../api/etherscan/error/ApiKeyException.java | 2 - .../etherscan/error/ApiTimeoutException.java | 2 - .../etherscan/error/ConnectionException.java | 2 - .../etherscan/error/EtherScanException.java | 13 ++- .../etherscan/error/EventModelException.java | 3 +- .../error/InvalidAddressException.java | 2 - .../error/InvalidDataHexException.java | 2 - .../error/InvalidTxHashException.java | 2 - .../etherscan/error/LogQueryException.java | 2 - .../api/etherscan/error/ParseException.java | 9 +- .../etherscan/error/RateLimitException.java | 3 - .../api/etherscan/executor/IHttpExecutor.java | 2 +- .../etherscan/executor/impl/HttpExecutor.java | 26 ++--- .../api/etherscan/manager/IQueueManager.java | 8 +- .../manager/impl/FakeQueueManager.java | 4 +- .../etherscan/manager/impl/QueueManager.java | 57 +++-------- src/main/java/io/api/etherscan/model/Abi.java | 10 +- .../java/io/api/etherscan/model/Balance.java | 13 ++- .../java/io/api/etherscan/model/BaseTx.java | 27 +++-- .../java/io/api/etherscan/model/Block.java | 12 ++- .../io/api/etherscan/model/EthNetwork.java | 1 + src/main/java/io/api/etherscan/model/Log.java | 39 +++++--- .../java/io/api/etherscan/model/Price.java | 16 +-- .../java/io/api/etherscan/model/Status.java | 9 +- .../io/api/etherscan/model/TokenBalance.java | 9 +- src/main/java/io/api/etherscan/model/Tx.java | 22 +++-- .../io/api/etherscan/model/TxInternal.java | 16 +-- .../java/io/api/etherscan/model/TxToken.java | 4 +- .../java/io/api/etherscan/model/Uncle.java | 16 +-- .../io/api/etherscan/model/UncleBlock.java | 13 ++- src/main/java/io/api/etherscan/model/Wei.java | 10 +- .../io/api/etherscan/model/event/IEvent.java | 1 + .../model/event/impl/ApprovalEvent.java | 1 + .../model/event/impl/DepositEvent.java | 3 +- .../api/etherscan/model/event/impl/Event.java | 8 +- .../etherscan/model/event/impl/MintEvent.java | 3 +- .../etherscan/model/event/impl/SyncEvent.java | 1 + .../model/event/impl/TransferErc20Event.java | 1 + .../model/event/impl/WithdrawEvent.java | 3 +- .../api/etherscan/model/proxy/BlockProxy.java | 26 ++--- .../etherscan/model/proxy/ReceiptProxy.java | 21 ++-- .../io/api/etherscan/model/proxy/TxProxy.java | 26 ++--- .../etherscan/model/query/IQueryBuilder.java | 1 + .../io/api/etherscan/model/query/LogOp.java | 1 + .../etherscan/model/query/impl/LogQuery.java | 3 +- .../model/query/impl/LogQueryBuilder.java | 20 ++-- .../etherscan/model/utility/BlockParam.java | 1 + .../io/api/etherscan/util/BasicUtils.java | 5 +- 65 files changed, 492 insertions(+), 379 deletions(-) diff --git a/src/main/java/io/api/etherscan/core/IAccountApi.java b/src/main/java/io/api/etherscan/core/IAccountApi.java index 7d44d15..6eda869 100644 --- a/src/main/java/io/api/etherscan/core/IAccountApi.java +++ b/src/main/java/io/api/etherscan/core/IAccountApi.java @@ -7,8 +7,7 @@ import java.util.List; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#accounts + * EtherScan - API Descriptions https://etherscan.io/apis#accounts * * @author GoodforGod * @since 28.10.2018 @@ -17,79 +16,107 @@ public interface IAccountApi { /** * Address ETH balance + * * @param address get balance for * @return balance * @throws ApiException parent exception class */ - @NotNull Balance balance(String address) throws ApiException; + @NotNull + Balance balance(String address) throws ApiException; /** * ERC20 token balance for address - * @param address get balance for + * + * @param address get balance for * @param contract token contract * @return token balance for address * @throws ApiException parent exception class */ - @NotNull TokenBalance balance(String address, String contract) throws ApiException; + @NotNull + 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 * @throws ApiException parent exception class */ - @NotNull List balances(List addresses) throws ApiException; + @NotNull + List balances(List addresses) throws ApiException; /** * All txs for given address - * @param address get txs for + * + * @param address get txs for * @param startBlock tx from this blockNumber - * @param endBlock tx to this blockNumber + * @param endBlock tx to this blockNumber * @return txs for address * @throws ApiException parent exception class */ - @NotNull List txs(String address, long startBlock, long endBlock) throws ApiException; - @NotNull List txs(String address, long startBlock) throws ApiException; - @NotNull List txs(String address) throws ApiException; + @NotNull + List txs(String address, long startBlock, long endBlock) throws ApiException; + + @NotNull + List txs(String address, long startBlock) throws ApiException; + + @NotNull + List txs(String address) throws ApiException; /** * All internal txs for given address - * @param address get txs for + * + * @param address get txs for * @param startBlock tx from this blockNumber - * @param endBlock tx to this blockNumber + * @param endBlock tx to this blockNumber * @return txs for address * @throws ApiException parent exception class */ - @NotNull List txsInternal(String address, long startBlock, long endBlock) throws ApiException; - @NotNull List txsInternal(String address, long startBlock) throws ApiException; - @NotNull List txsInternal(String address) throws ApiException; + @NotNull + List txsInternal(String address, long startBlock, long endBlock) throws ApiException; + + @NotNull + List txsInternal(String address, long startBlock) throws ApiException; + + @NotNull + List txsInternal(String address) throws ApiException; /** * All internal tx for given transaction hash + * * @param txhash transaction hash * @return internal txs list * @throws ApiException parent exception class */ - @NotNull List txsInternalByHash(String txhash) throws ApiException; + @NotNull + List txsInternalByHash(String txhash) throws ApiException; /** * All token txs for given address - * @param address get txs for + * + * @param address get txs for * @param startBlock tx from this blockNumber - * @param endBlock tx to this blockNumber + * @param endBlock tx to this blockNumber * @return txs for address * @throws ApiException parent exception class */ - @NotNull List txsToken(String address, long startBlock, long endBlock) throws ApiException; - @NotNull List txsToken(String address, long startBlock) throws ApiException; - @NotNull List txsToken(String address) throws ApiException; + @NotNull + List txsToken(String address, long startBlock, long endBlock) throws ApiException; + + @NotNull + List txsToken(String address, long startBlock) throws ApiException; + + @NotNull + List txsToken(String address) throws ApiException; /** * All blocks mined by address + * * @param address address to search for * @return blocks mined * @throws ApiException parent exception class */ - @NotNull List minedBlocks(String address) throws ApiException; + @NotNull + List minedBlocks(String address) throws ApiException; } diff --git a/src/main/java/io/api/etherscan/core/IBlockApi.java b/src/main/java/io/api/etherscan/core/IBlockApi.java index e33b0d7..7381ac0 100644 --- a/src/main/java/io/api/etherscan/core/IBlockApi.java +++ b/src/main/java/io/api/etherscan/core/IBlockApi.java @@ -7,8 +7,7 @@ import java.util.Optional; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#blocks + * EtherScan - API Descriptions https://etherscan.io/apis#blocks * * @author GoodforGod * @since 30.10.2018 @@ -17,9 +16,11 @@ public interface IBlockApi { /** * Return uncle blocks + * * @param blockNumber block number form 0 to last * @return optional uncle blocks * @throws ApiException parent exception class */ - @NotNull Optional uncles(long blockNumber) throws ApiException; + @NotNull + Optional uncles(long blockNumber) throws ApiException; } diff --git a/src/main/java/io/api/etherscan/core/IContractApi.java b/src/main/java/io/api/etherscan/core/IContractApi.java index 5e3c771..3e9388d 100644 --- a/src/main/java/io/api/etherscan/core/IContractApi.java +++ b/src/main/java/io/api/etherscan/core/IContractApi.java @@ -5,8 +5,7 @@ import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#contracts + * EtherScan - API Descriptions https://etherscan.io/apis#contracts * * @author GoodforGod * @since 28.10.2018 @@ -15,9 +14,11 @@ public interface IContractApi { /** * Get Verified Contract Sources + * * @param address to verify * @return ABI verified * @throws ApiException parent exception class */ - @NotNull Abi contractAbi(String address) throws ApiException; + @NotNull + Abi contractAbi(String address) throws ApiException; } diff --git a/src/main/java/io/api/etherscan/core/IEventsApi.java b/src/main/java/io/api/etherscan/core/IEventsApi.java index c5f3665..12c19db 100644 --- a/src/main/java/io/api/etherscan/core/IEventsApi.java +++ b/src/main/java/io/api/etherscan/core/IEventsApi.java @@ -1,7 +1,6 @@ package io.api.etherscan.core; import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.Log; import io.api.etherscan.model.event.IEvent; import io.api.etherscan.model.query.impl.LogQuery; import org.jetbrains.annotations.NotNull; @@ -9,14 +8,14 @@ import java.util.List; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#logs + * EtherScan - API Descriptions https://etherscan.io/apis#logs */ public interface IEventsApi { /** - * This is a high-level alternative to the ILogsApi and an alternative to the native eth_getLogs - * Read at EtherScan API description for full info! + * This is a high-level alternative to the ILogsApi and an alternative to the + * native eth_getLogs Read at EtherScan API description for full info! + * * @param query build log query * @return logs according to query * @throws ApiException parent exception class diff --git a/src/main/java/io/api/etherscan/core/ILogsApi.java b/src/main/java/io/api/etherscan/core/ILogsApi.java index d1901dd..dc7d8d8 100644 --- a/src/main/java/io/api/etherscan/core/ILogsApi.java +++ b/src/main/java/io/api/etherscan/core/ILogsApi.java @@ -8,8 +8,7 @@ import java.util.List; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#logs + * EtherScan - API Descriptions https://etherscan.io/apis#logs * * @author GoodforGod * @since 30.10.2018 @@ -17,13 +16,15 @@ 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 * @throws ApiException parent exception class * * @see io.api.etherscan.model.query.impl.LogQueryBuilder */ - @NotNull List logs(LogQuery query) throws ApiException; + @NotNull + List logs(LogQuery query) throws ApiException; } diff --git a/src/main/java/io/api/etherscan/core/IProxyApi.java b/src/main/java/io/api/etherscan/core/IProxyApi.java index 171c752..58a70ed 100644 --- a/src/main/java/io/api/etherscan/core/IProxyApi.java +++ b/src/main/java/io/api/etherscan/core/IProxyApi.java @@ -10,8 +10,7 @@ import java.util.Optional; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#proxy + * EtherScan - API Descriptions https://etherscan.io/apis#proxy * * @author GoodforGod * @since 30.10.2018 @@ -19,54 +18,62 @@ public interface IProxyApi { /** - * Returns the number of most recent block - * eth_blockNumber + * Returns the number of most recent block eth_blockNumber + * * @return last block number * @throws ApiException parent exception class */ long blockNoLast(); /** - * Returns information about a block by block number - * eth_getBlockByNumber + * Returns information about a block by block number eth_getBlockByNumber + * * @param blockNo block number from 0 to last * @return optional block result * @throws ApiException parent exception class */ - @NotNull Optional block(long blockNo) throws ApiException; + @NotNull + Optional block(long blockNo) throws ApiException; /** * Returns information about a uncle by block number * eth_getUncleByBlockNumberAndIndex + * * @param blockNo block number from 0 to last - * @param index uncle block index + * @param index uncle block index * @return optional block result * @throws ApiException parent exception class */ - @NotNull Optional blockUncle(long blockNo, long index) throws ApiException; + @NotNull + Optional blockUncle(long blockNo, long index) throws ApiException; /** * Returns the information about a transaction requested by transaction hash * eth_getTransactionByHash + * * @param txhash transaction hash * @return optional tx result * @throws ApiException parent exception class */ - @NotNull Optional tx(String txhash) throws ApiException; + @NotNull + 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 + * @param index tx index in block * @return optional tx result * @throws ApiException parent exception class */ - @NotNull Optional tx(long blockNo, long index) throws ApiException; + @NotNull + 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 * @throws ApiException parent exception class @@ -76,6 +83,7 @@ public interface IProxyApi { /** * Returns the number of transactions sent from an address * eth_getTransactionCount + * * @param address eth address * @return transactions send amount from address * @throws ApiException parent exception class @@ -83,70 +91,82 @@ 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 * @throws ApiException parent exception class */ - @NotNull Optional txSendRaw(String hexEncodedTx) throws ApiException; + @NotNull + Optional txSendRaw(String hexEncodedTx) throws ApiException; /** * Returns the receipt of a transaction by transaction hash * eth_getTransactionReceipt + * * @param txhash transaction hash * @return optional tx receipt * @throws ApiException parent exception class */ - @NotNull Optional txReceipt(String txhash) throws ApiException; + @NotNull + 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 + * @param data data to call address * @return optional the return value of executed contract. * @throws ApiException parent exception class */ - @NotNull Optional call(String address, String data) throws ApiException; + @NotNull + Optional call(String address, String data) throws ApiException; /** - * Returns code at a given address - * eth_getCode + * Returns code at a given address eth_getCode + * * @param address get code from * @return optional the code from the given address * @throws ApiException parent exception class */ - @NotNull Optional code(String address) throws ApiException; + @NotNull + Optional code(String address) throws ApiException; /** - * (**experimental) - * Returns the value from a storage position at a given address + * (**experimental) Returns the value from a storage position at a given address * eth_getStorageAt - * @param address to get storage + * + * @param address to get storage * @param position storage position * @return optional the value at this storage position * @throws ApiException parent exception class */ - @NotNull Optional storageAt(String address, long position) throws ApiException; + @NotNull + Optional storageAt(String address, long position) throws ApiException; /** - * Returns the current price per gas in wei - * eth_gasPrice + * Returns the current price per gas in wei eth_gasPrice + * * @return estimated gas price * @throws ApiException parent exception class */ - @NotNull BigInteger gasPrice() throws ApiException; - + @NotNull + 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 + * 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 * @throws ApiException parent exception class */ - @NotNull BigInteger gasEstimated(String hexData) throws ApiException; - @NotNull BigInteger gasEstimated() throws ApiException; + @NotNull + BigInteger gasEstimated(String hexData) throws ApiException; + + @NotNull + BigInteger gasEstimated() throws ApiException; } diff --git a/src/main/java/io/api/etherscan/core/IStatisticApi.java b/src/main/java/io/api/etherscan/core/IStatisticApi.java index c0b838e..1b7ef59 100644 --- a/src/main/java/io/api/etherscan/core/IStatisticApi.java +++ b/src/main/java/io/api/etherscan/core/IStatisticApi.java @@ -8,8 +8,7 @@ import java.math.BigInteger; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#stats + * EtherScan - API Descriptions https://etherscan.io/apis#stats * * @author GoodforGod * @since 30.10.2018 @@ -18,23 +17,29 @@ public interface IStatisticApi { /** * ERC20 token total Supply + * * @param contract contract address * @return token supply for specified contract * @throws ApiException parent exception class */ - @NotNull BigInteger supply(String contract) throws ApiException; + @NotNull + BigInteger supply(String contract) throws ApiException; /** * Eth total supply + * * @return total ETH supply for moment * @throws ApiException parent exception class */ - @NotNull Supply supply() throws ApiException; + @NotNull + Supply supply() throws ApiException; /** * Eth last USD and BTC price + * * @return last usd/btc price for ETH * @throws ApiException parent exception class */ - @NotNull Price lastPrice() throws ApiException; + @NotNull + Price lastPrice() throws ApiException; } diff --git a/src/main/java/io/api/etherscan/core/ITransactionApi.java b/src/main/java/io/api/etherscan/core/ITransactionApi.java index 3c248f3..51c108c 100644 --- a/src/main/java/io/api/etherscan/core/ITransactionApi.java +++ b/src/main/java/io/api/etherscan/core/ITransactionApi.java @@ -7,8 +7,7 @@ import java.util.Optional; /** - * EtherScan - API Descriptions - * https://etherscan.io/apis#transactions + * EtherScan - API Descriptions https://etherscan.io/apis#transactions * * @author GoodforGod * @since 30.10.2018 @@ -16,18 +15,24 @@ 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 * @throws ApiException parent exception class */ - @NotNull Optional execStatus(String txhash) throws ApiException; + @NotNull + 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 * @throws ApiException parent exception class */ - @NotNull Optional receiptStatus(String txhash) throws ApiException; + @NotNull + Optional receiptStatus(String txhash) throws ApiException; } diff --git a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java b/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java index 195d0f0..b512019 100644 --- a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java @@ -62,7 +62,7 @@ public Balance balance(final String address) throws ApiException { final String urlParams = ACT_BALANCE_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + address; final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + throw new EtherScanException(response); return new Balance(address, new BigInteger(response.getResult())); } @@ -76,7 +76,7 @@ public TokenBalance balance(final String address, final String contract) throws final String urlParams = ACT_TOKEN_BALANCE_PARAM + ADDRESS_PARAM + address + CONTRACT_PARAM + contract; final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + throw new EtherScanException(response); return new TokenBalance(address, new BigInteger(response.getResult()), contract); } @@ -97,7 +97,7 @@ public List balances(final List addresses) throws ApiException final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + toAddressParam(batch); final BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + throw new EtherScanException(response); if (!BasicUtils.isEmpty(response.getResult())) balances.addAll(response.getResult().stream() @@ -138,8 +138,7 @@ public List txs(final String address, final long startBlock, final long endB } /** - * Generic search for txs using offset api param - * To avoid 10k limit per response + * Generic search for txs using offset api param To avoid 10k limit per response * * @param urlParams Url params for #getRequest() * @param tClass responseListTO class @@ -147,8 +146,8 @@ public List txs(final String address, final long startBlock, final long endB * @param responseListTO type * @return List of T values */ - private List getRequestUsingOffset(final String urlParams, Class tClass) - throws ApiException { + private List getRequestUsingOffset(final String urlParams, + Class tClass) throws ApiException { final List result = new ArrayList<>(); int page = 1; while (true) { diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index d242a76..f2be0d2 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -1,15 +1,20 @@ package io.api.etherscan.core.impl; import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; +import io.api.etherscan.error.ApiException; import io.api.etherscan.error.EtherScanException; import io.api.etherscan.error.ParseException; import io.api.etherscan.error.RateLimitException; import io.api.etherscan.executor.IHttpExecutor; import io.api.etherscan.manager.IQueueManager; +import io.api.etherscan.manager.impl.QueueManager; +import io.api.etherscan.model.utility.StringResponseTO; import io.api.etherscan.util.BasicUtils; +import java.time.LocalTime; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Base provider for API Implementations @@ -20,7 +25,9 @@ */ abstract class BasicProvider { - static final int MAX_END_BLOCK = 999999999; + private static final Logger logger = Logger.getLogger(QueueManager.class.getName()); + + static final int MAX_END_BLOCK = Integer.MAX_VALUE; static final int MIN_START_BLOCK = 0; static final String ACT_PREFIX = "&action="; @@ -44,27 +51,34 @@ abstract class BasicProvider { T convert(final String json, final Class tClass) { try { - return gson.fromJson(json, tClass); - } catch (Exception e) { - if (e instanceof JsonSyntaxException) { - Map map = gson.fromJson(json, Map.class); - Object statusCode = map.get("status"); - if ((statusCode instanceof String) && (statusCode.equals("0"))) { - Object message = map.get("message"); - if ((message instanceof String) && (message.equals("NOTOK"))) { - Object result = map.get("result"); - if ((result instanceof String) && (result.equals("Max rate limit reached"))) { - throw new RateLimitException ("Max rate limit reached"); - } - } + final T t = gson.fromJson(json, tClass); + if (t instanceof StringResponseTO) { + if (((StringResponseTO) t).getResult().startsWith("Max rate limit reached")) { + throw new RateLimitException(((StringResponseTO) t).getResult()); } } - throw new ParseException(e.getMessage(), e.getCause(), json); + + return t; + } catch (Exception e) { + try { + final Map map = gson.fromJson(json, Map.class); + final Object result = map.get("result"); + if (result instanceof String && ((String) result).startsWith("Max rate limit reached")) + throw new RateLimitException(((String) result)); + + throw new ParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); + } catch (ApiException ex) { + throw ex; + } catch (Exception ex) { + throw new ParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); + } } } String getRequest(final String urlParameters) { + logger.log(Level.SEVERE, "ASKED - " + LocalTime.now()); queue.takeTurn(); + logger.log(Level.SEVERE, "GRANTED - " + LocalTime.now()); final String url = baseUrl + module + urlParameters; final String result = executor.get(url); if (BasicUtils.isEmpty(result)) @@ -74,7 +88,9 @@ String getRequest(final String urlParameters) { } String postRequest(final String urlParameters, final String dataToPost) { + logger.log(Level.SEVERE, "ASKED - " + LocalTime.now()); queue.takeTurn(); + logger.log(Level.SEVERE, "GRANTED - " + LocalTime.now()); final String url = baseUrl + module + urlParameters; return executor.post(url, dataToPost); } diff --git a/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java b/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java index d076e18..9f386a7 100644 --- a/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java @@ -36,7 +36,7 @@ public class BlockApiProvider extends BasicProvider implements IBlockApi { public Optional uncles(long blockNumber) throws ApiException { final String urlParam = ACT_BLOCK_PARAM + BLOCKNO_PARAM + blockNumber; final String response = getRequest(urlParam); - if(BasicUtils.isEmpty(response) || response.contains("NOTOK")) + if (BasicUtils.isEmpty(response) || response.contains("NOTOK")) return Optional.empty(); final UncleBlockResponseTO responseTO = convert(response, UncleBlockResponseTO.class); diff --git a/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java b/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java index 83a6e0a..125087f 100644 --- a/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java @@ -37,8 +37,8 @@ public Abi contractAbi(final String address) throws ApiException { final String urlParam = ACT_ABI_PARAM + ADDRESS_PARAM + address; final StringResponseTO response = getRequest(urlParam, StringResponseTO.class); - if (response.getStatus() != 1 && !"NOTOK".equals(response.getMessage())) - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + if (response.getStatus() != 1 && "NOTOK".equals(response.getMessage())) + throw new EtherScanException(response); return (response.getResult().startsWith("Contract sou")) ? Abi.nonVerified() 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 1f4d0b4..789794f 100644 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java @@ -15,8 +15,7 @@ import java.util.function.Supplier; /** - * EtherScan full API Description - * https://etherscan.io/apis + * EtherScan full API Description https://etherscan.io/apis * * @author GoodforGod * @since 28.10.2018 @@ -25,6 +24,8 @@ public class EtherScanApi { private static final Supplier DEFAULT_SUPPLIER = HttpExecutor::new; + public static final String DEFAULT_KEY = "YourApiKeyToken"; + private final IAccountApi account; private final IBlockApi block; private final IContractApi contract; @@ -34,11 +35,11 @@ public class EtherScanApi { private final ITransactionApi txs; public EtherScanApi() { - this("YourApiKeyToken", EthNetwork.MAINNET); + this(DEFAULT_KEY, EthNetwork.MAINNET); } public EtherScanApi(final EthNetwork network) { - this("YourApiKeyToken", network); + this(DEFAULT_KEY, network); } public EtherScanApi(final String apiKey) { @@ -47,7 +48,13 @@ public EtherScanApi(final String apiKey) { public EtherScanApi(final EthNetwork network, final Supplier executorSupplier) { - this("YourApiKeyToken", network, executorSupplier); + this(DEFAULT_KEY, network, executorSupplier); + } + + public EtherScanApi(final String apiKey, + final EthNetwork network, + final IQueueManager queue) { + this(apiKey, network, DEFAULT_SUPPLIER, queue); } public EtherScanApi(final String apiKey, @@ -58,29 +65,36 @@ public EtherScanApi(final String apiKey, public EtherScanApi(final String apiKey, final EthNetwork network, final Supplier executorSupplier) { + this(apiKey, network, executorSupplier, + DEFAULT_KEY.equals(apiKey) + ? new QueueManager(1, 6) + : new FakeQueueManager() + ); + } + + public EtherScanApi(final String apiKey, + final EthNetwork network, + final Supplier executorSupplier, + final IQueueManager queue) { if (BasicUtils.isBlank(apiKey)) throw new ApiKeyException("API key can not be null or empty"); - if(network == null) + if (network == null) throw new ApiException("Ethereum Network is set to NULL value"); - // EtherScan 5request\sec limit support by queue manager - final IQueueManager masterQueue = (apiKey.equals("YourApiKeyToken")) - ? new FakeQueueManager() - : new QueueManager(5, 1); - + // EtherScan 1request\5sec limit support by queue manager final IHttpExecutor executor = executorSupplier.get(); - final String ending = (EthNetwork.TOBALABA.equals(network)) ? "com" : "io"; + final String ending = EthNetwork.TOBALABA.equals(network) ? "com" : "io"; final String baseUrl = "https://" + network.getDomain() + ".etherscan." + ending + "/api" + "?apikey=" + apiKey; - this.account = new AccountApiProvider(masterQueue, baseUrl, executor); - this.block = new BlockApiProvider(masterQueue, baseUrl, executor); - this.contract = new ContractApiProvider(masterQueue, baseUrl, executor); - this.logs = new LogsApiProvider(masterQueue, baseUrl, executor); - this.proxy = new ProxyApiProvider(masterQueue, baseUrl, executor); - this.stats = new StatisticApiProvider(masterQueue, baseUrl, executor); - this.txs = new TransactionApiProvider(masterQueue, baseUrl, executor); + this.account = new AccountApiProvider(queue, baseUrl, executor); + this.block = new BlockApiProvider(queue, baseUrl, executor); + this.contract = new ContractApiProvider(queue, baseUrl, executor); + this.logs = new LogsApiProvider(queue, baseUrl, executor); + this.proxy = new ProxyApiProvider(queue, baseUrl, executor); + this.stats = new StatisticApiProvider(queue, baseUrl, executor); + this.txs = new TransactionApiProvider(queue, baseUrl, executor); } @NotNull diff --git a/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java b/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java index b99a934..d6bfa25 100644 --- a/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java @@ -1,13 +1,10 @@ package io.api.etherscan.core.impl; import io.api.etherscan.core.IEventsApi; -import io.api.etherscan.core.ILogsApi; import io.api.etherscan.error.ApiException; import io.api.etherscan.executor.IHttpExecutor; import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.Log; import io.api.etherscan.model.event.IEvent; -import io.api.etherscan.model.event.impl.Event; import io.api.etherscan.model.query.impl.LogQuery; import io.api.etherscan.model.utility.LogResponseTO; import io.api.etherscan.util.BasicUtils; @@ -41,8 +38,8 @@ public List events(final LogQuery query) throws ApiException { BasicUtils.validateTxResponse(response); if (BasicUtils.isEmpty(response.getResult())) { - return Collections.emptyList(); - }; + return Collections.emptyList(); + } ; return response .getResult() .stream() diff --git a/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java b/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java index f2376d6..cb0c6a5 100644 --- a/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java @@ -62,7 +62,7 @@ public class ProxyApiProvider extends BasicProvider implements IProxyApi { ProxyApiProvider(final IQueueManager queue, final String baseUrl, final IHttpExecutor executor) { - super(queue, "proxy", baseUrl,executor); + super(queue, "proxy", baseUrl, executor); } @Override @@ -111,7 +111,8 @@ public Optional tx(final long blockNo, final long index) throws ApiExce final long compBlockNo = BasicUtils.compensateMinBlock(blockNo); final long compIndex = (index < 1) ? 1 : index; - final String urlParams = ACT_TX_BY_BLOCKNOINDEX_PARAM + TAG_PARAM + compBlockNo + INDEX_PARAM + "0x" + Long.toHexString(compIndex); + final String urlParams = ACT_TX_BY_BLOCKNOINDEX_PARAM + TAG_PARAM + compBlockNo + INDEX_PARAM + "0x" + + Long.toHexString(compIndex); final TxProxyTO response = getRequest(urlParams, TxProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -136,12 +137,12 @@ public int txSendCount(final String address) throws ApiException { @Override @NotNull public Optional txSendRaw(final String hexEncodedTx) throws ApiException { - if(BasicUtils.isNotHex(hexEncodedTx)) + if (BasicUtils.isNotHex(hexEncodedTx)) throw new InvalidDataHexException("Data is not encoded in hex format - " + hexEncodedTx); final String urlParams = ACT_SEND_RAW_TX_PARAM + HEX_PARAM + hexEncodedTx; final StringProxyTO response = postRequest(urlParams, "", StringProxyTO.class); - if(response.getError() != null) + if (response.getError() != null) throw new EtherScanException("Error occurred with code " + response.getError().getCode() + " with message " + response.getError().getMessage() + ", error id " + response.getId() + ", jsonRPC " + response.getJsonrpc()); @@ -163,12 +164,12 @@ public Optional txReceipt(final String txhash) throws ApiException @Override public Optional call(final String address, final String data) throws ApiException { BasicUtils.validateAddress(address); - if(BasicUtils.isNotHex(data)) + if (BasicUtils.isNotHex(data)) throw new InvalidDataHexException("Data is not hex encoded."); final String urlParams = ACT_CALL_PARAM + TO_PARAM + address + DATA_PARAM + data + TAG_LAST_PARAM; final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); - return Optional.ofNullable (response.getResult()); + return Optional.ofNullable(response.getResult()); } @NotNull @@ -212,7 +213,7 @@ public BigInteger gasEstimated() throws ApiException { @NotNull @Override public BigInteger gasEstimated(final String hexData) throws ApiException { - if(!BasicUtils.isEmpty(hexData) && BasicUtils.isNotHex(hexData)) + if (!BasicUtils.isEmpty(hexData) && BasicUtils.isNotHex(hexData)) throw new InvalidDataHexException("Data is not in hex format."); final String urlParams = ACT_ESTIMATEGAS_PARAM + DATA_PARAM + hexData + GAS_PARAM + "2000000000000000"; diff --git a/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java b/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java index 0125850..d178a81 100644 --- a/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java @@ -41,7 +41,7 @@ public class StatisticApiProvider extends BasicProvider implements IStatisticApi public Supply supply() throws ApiException { final StringResponseTO response = getRequest(ACT_SUPPLY_PARAM, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + throw new EtherScanException(response); return new Supply(new BigInteger(response.getResult())); } @@ -54,7 +54,7 @@ public BigInteger supply(final String contract) throws ApiException { final String urlParams = ACT_TOKEN_SUPPLY_PARAM + CONTRACT_ADDRESS_PARAM + contract; final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + throw new EtherScanException(response); return new BigInteger(response.getResult()); } @@ -64,7 +64,7 @@ public BigInteger supply(final String contract) throws ApiException { public Price lastPrice() throws ApiException { final PriceResponseTO response = getRequest(ACT_LASTPRICE_PARAM, PriceResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + throw new EtherScanException(response); return response.getResult(); } diff --git a/src/main/java/io/api/etherscan/error/ApiException.java b/src/main/java/io/api/etherscan/error/ApiException.java index 087758e..33e4228 100644 --- a/src/main/java/io/api/etherscan/error/ApiException.java +++ b/src/main/java/io/api/etherscan/error/ApiException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/error/ApiKeyException.java b/src/main/java/io/api/etherscan/error/ApiKeyException.java index 0e2f81a..4e22934 100644 --- a/src/main/java/io/api/etherscan/error/ApiKeyException.java +++ b/src/main/java/io/api/etherscan/error/ApiKeyException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 05.11.2018 */ diff --git a/src/main/java/io/api/etherscan/error/ApiTimeoutException.java b/src/main/java/io/api/etherscan/error/ApiTimeoutException.java index 803731b..39b6e93 100644 --- a/src/main/java/io/api/etherscan/error/ApiTimeoutException.java +++ b/src/main/java/io/api/etherscan/error/ApiTimeoutException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 12.11.2018 */ diff --git a/src/main/java/io/api/etherscan/error/ConnectionException.java b/src/main/java/io/api/etherscan/error/ConnectionException.java index 410c0ac..96a881c 100644 --- a/src/main/java/io/api/etherscan/error/ConnectionException.java +++ b/src/main/java/io/api/etherscan/error/ConnectionException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/error/EtherScanException.java b/src/main/java/io/api/etherscan/error/EtherScanException.java index 3865572..cb7dd7f 100644 --- a/src/main/java/io/api/etherscan/error/EtherScanException.java +++ b/src/main/java/io/api/etherscan/error/EtherScanException.java @@ -1,13 +1,22 @@ package io.api.etherscan.error; +import io.api.etherscan.model.utility.BaseResponseTO; +import io.api.etherscan.model.utility.StringResponseTO; + /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ public class EtherScanException extends ApiException { + public EtherScanException(BaseResponseTO response) { + this(response.getMessage() + ", with status: " + response.getStatus()); + } + + public EtherScanException(StringResponseTO response) { + this(response.getResult() + ", with status: " + response.getStatus() + ", with message: " + response.getMessage()); + } + public EtherScanException(String message) { super(message); } diff --git a/src/main/java/io/api/etherscan/error/EventModelException.java b/src/main/java/io/api/etherscan/error/EventModelException.java index 5c3e17e..feb60be 100644 --- a/src/main/java/io/api/etherscan/error/EventModelException.java +++ b/src/main/java/io/api/etherscan/error/EventModelException.java @@ -1,6 +1,7 @@ package io.api.etherscan.error; -public class EventModelException extends ApiException { +public class EventModelException extends ApiException { + public EventModelException(String message) { super(message); } diff --git a/src/main/java/io/api/etherscan/error/InvalidAddressException.java b/src/main/java/io/api/etherscan/error/InvalidAddressException.java index b6a448c..9a0c143 100644 --- a/src/main/java/io/api/etherscan/error/InvalidAddressException.java +++ b/src/main/java/io/api/etherscan/error/InvalidAddressException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/error/InvalidDataHexException.java b/src/main/java/io/api/etherscan/error/InvalidDataHexException.java index 5b5952d..dd12cb9 100644 --- a/src/main/java/io/api/etherscan/error/InvalidDataHexException.java +++ b/src/main/java/io/api/etherscan/error/InvalidDataHexException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 02.11.2018 */ diff --git a/src/main/java/io/api/etherscan/error/InvalidTxHashException.java b/src/main/java/io/api/etherscan/error/InvalidTxHashException.java index fb18578..aba32c1 100644 --- a/src/main/java/io/api/etherscan/error/InvalidTxHashException.java +++ b/src/main/java/io/api/etherscan/error/InvalidTxHashException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 02.11.2018 */ diff --git a/src/main/java/io/api/etherscan/error/LogQueryException.java b/src/main/java/io/api/etherscan/error/LogQueryException.java index c72cd3e..504219f 100644 --- a/src/main/java/io/api/etherscan/error/LogQueryException.java +++ b/src/main/java/io/api/etherscan/error/LogQueryException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/error/ParseException.java b/src/main/java/io/api/etherscan/error/ParseException.java index f279fda..5dc6199 100644 --- a/src/main/java/io/api/etherscan/error/ParseException.java +++ b/src/main/java/io/api/etherscan/error/ParseException.java @@ -1,16 +1,19 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ public class ParseException extends ApiException { - String json; + + private final String json; public ParseException(String message, Throwable cause, String json) { super(message, cause); this.json = json; } + + public String getJson() { + return json; + } } diff --git a/src/main/java/io/api/etherscan/error/RateLimitException.java b/src/main/java/io/api/etherscan/error/RateLimitException.java index 2562342..c29f54d 100644 --- a/src/main/java/io/api/etherscan/error/RateLimitException.java +++ b/src/main/java/io/api/etherscan/error/RateLimitException.java @@ -1,8 +1,6 @@ package io.api.etherscan.error; /** - * ! NO DESCRIPTION ! - * * @author iSnow * @since 2020-10-06 */ @@ -11,5 +9,4 @@ public class RateLimitException extends ApiException { public RateLimitException(String message) { super(message); } - } diff --git a/src/main/java/io/api/etherscan/executor/IHttpExecutor.java b/src/main/java/io/api/etherscan/executor/IHttpExecutor.java index 0a47714..0c80282 100644 --- a/src/main/java/io/api/etherscan/executor/IHttpExecutor.java +++ b/src/main/java/io/api/etherscan/executor/IHttpExecutor.java @@ -19,7 +19,7 @@ public interface IHttpExecutor { /** * Performs a Http POST request * - * @param url as string + * @param url as string * @param data to post * @return result as string */ diff --git a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java b/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java index c059d27..5ba39f2 100644 --- a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java +++ b/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java @@ -65,8 +65,8 @@ public HttpExecutor(final int connectTimeout, final int readTimeout) { public HttpExecutor(final int connectTimeout, final int readTimeout, final Map headers) { - this.connectTimeout = (connectTimeout < 0) ? 0 : connectTimeout; - this.readTimeout = (readTimeout < 0) ? 0 : readTimeout; + this.connectTimeout = Math.max(connectTimeout, 0); + this.readTimeout = Math.max(readTimeout, 0); this.headers = headers; } @@ -88,9 +88,9 @@ public String get(final String urlAsString) { if (status == HTTP_MOVED_TEMP || status == HTTP_MOVED_PERM) { return get(connection.getHeaderField("Location")); } else if ((status >= HTTP_BAD_REQUEST) && (status < HTTP_INTERNAL_ERROR)) { - throw new ConnectionException("Protocol error: "+connection.getResponseMessage()); - } else if (status >= HTTP_INTERNAL_ERROR) { - throw new ConnectionException("Server error: "+connection.getResponseMessage()); + throw new ConnectionException("Protocol error: " + connection.getResponseMessage()); + } else if (status >= HTTP_INTERNAL_ERROR) { + throw new ConnectionException("Server error: " + connection.getResponseMessage()); } final String data = readData(connection); @@ -99,7 +99,7 @@ public String get(final String urlAsString) { } catch (SocketTimeoutException e) { throw new ApiTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); } catch (Exception e) { - throw new ConnectionException(e.getLocalizedMessage(), e); + throw new ConnectionException(e.getMessage(), e); } } @@ -121,6 +121,10 @@ public String post(final String urlAsString, final String dataToPost) { final int status = connection.getResponseCode(); if (status == HTTP_MOVED_TEMP || status == HTTP_MOVED_PERM) { return post(connection.getHeaderField("Location"), dataToPost); + } else if ((status >= HTTP_BAD_REQUEST) && (status < HTTP_INTERNAL_ERROR)) { + throw new ConnectionException("Protocol error: " + connection.getResponseMessage()); + } else if (status >= HTTP_INTERNAL_ERROR) { + throw new ConnectionException("Server error: " + connection.getResponseMessage()); } final String data = readData(connection); @@ -129,7 +133,7 @@ public String post(final String urlAsString, final String dataToPost) { } catch (SocketTimeoutException e) { throw new ApiTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); } catch (Exception e) { - throw new ConnectionException(e.getLocalizedMessage(), e); + throw new ConnectionException(e.getMessage(), e); } } @@ -139,8 +143,6 @@ private String readData(final HttpURLConnection connection) throws IOException { String inputLine; while ((inputLine = in.readLine()) != null) content.append(inputLine); - - in.close(); } return content.toString(); @@ -149,11 +151,11 @@ private String readData(final HttpURLConnection connection) throws IOException { private InputStreamReader getStreamReader(final HttpURLConnection connection) throws IOException { switch (String.valueOf(connection.getContentEncoding())) { case "gzip": - return new InputStreamReader(new GZIPInputStream(connection.getInputStream()), "utf-8"); + return new InputStreamReader(new GZIPInputStream(connection.getInputStream()), StandardCharsets.UTF_8); case "deflate": - return new InputStreamReader(new InflaterInputStream(connection.getInputStream()), "utf-8"); + return new InputStreamReader(new InflaterInputStream(connection.getInputStream()), StandardCharsets.UTF_8); default: - return new InputStreamReader(connection.getInputStream(), "utf-8"); + return new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8); } } } diff --git a/src/main/java/io/api/etherscan/manager/IQueueManager.java b/src/main/java/io/api/etherscan/manager/IQueueManager.java index 3fddf19..3a65240 100644 --- a/src/main/java/io/api/etherscan/manager/IQueueManager.java +++ b/src/main/java/io/api/etherscan/manager/IQueueManager.java @@ -1,9 +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 @@ -12,7 +11,6 @@ public interface IQueueManager { /** * Waits in queue for chance to take turn - * @return can or can not rake turn */ - boolean takeTurn(); + void takeTurn(); } 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 45241af..d8bc048 100644 --- a/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java @@ -11,7 +11,5 @@ public class FakeQueueManager implements IQueueManager { @Override - public boolean takeTurn() { - return true; - } + public void takeTurn() {} } 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 f1d9f1a..cd957ee 100644 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java @@ -2,15 +2,11 @@ import io.api.etherscan.manager.IQueueManager; -import java.util.List; import java.util.concurrent.*; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.IntStream; /** - * Queue implementation - * With size and reset time as params + * Queue implementation With size and reset time as params + * * @see IQueueManager * * @author GoodforGod @@ -18,55 +14,24 @@ */ public class QueueManager implements IQueueManager { - private static final Logger logger = Logger.getLogger(QueueManager.class.getName()); - - private final int queueSize; - private final BlockingQueue queue; - private final List queueValues; - - private final ScheduledExecutorService queueExecutor; + private final Semaphore semaphore; public QueueManager(int queueSize, int queueResetTimeInSec) { - this(queueSize, queueResetTimeInSec, 0); + this(queueSize, queueResetTimeInSec, queueResetTimeInSec); } public QueueManager(int queueSize, int queueResetTimeInSec, int delayInSec) { - this.queueSize = queueSize; - this.queueValues = generateList(queueSize); - this.queue = new ArrayBlockingQueue<>(queueSize); - - this.queueExecutor = Executors.newSingleThreadScheduledExecutor(); - this.queueExecutor.scheduleAtFixedRate(createTask(), delayInSec, queueResetTimeInSec, TimeUnit.SECONDS); + this.semaphore = new Semaphore(queueSize); + Executors.newSingleThreadScheduledExecutor() + .scheduleAtFixedRate(releaseLocks(queueSize), delayInSec, queueResetTimeInSec, TimeUnit.SECONDS); } @Override - public boolean takeTurn() { - try { - queue.take(); - return true; - } catch (InterruptedException e) { - logger.warning(e.getLocalizedMessage()); - return false; - } - } - - private Runnable createTask() { - return () -> { - try { - if(queue.size() == queueSize) - return; - - queue.clear(); - queue.addAll(queueValues); - } catch (Exception e) { - logger.warning(e.getLocalizedMessage()); - } - }; + public void takeTurn() { + semaphore.acquireUninterruptibly(); } - private List generateList(int size) { - return IntStream.range(0, size) - .boxed() - .collect(Collectors.toList()); + private Runnable releaseLocks(int toRelease) { + return () -> semaphore.release(toRelease); } } diff --git a/src/main/java/io/api/etherscan/model/Abi.java b/src/main/java/io/api/etherscan/model/Abi.java index b5203bc..a48a11d 100644 --- a/src/main/java/io/api/etherscan/model/Abi.java +++ b/src/main/java/io/api/etherscan/model/Abi.java @@ -9,6 +9,7 @@ * @since 31.10.2018 */ public class Abi { + private String contractAbi; private boolean isVerified; @@ -39,12 +40,15 @@ public boolean isVerified() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Abi abi = (Abi) o; - if (isVerified != abi.isVerified) return false; + if (isVerified != abi.isVerified) + return false; return contractAbi != null ? contractAbi.equals(abi.contractAbi) : abi.contractAbi == null; } diff --git a/src/main/java/io/api/etherscan/model/Balance.java b/src/main/java/io/api/etherscan/model/Balance.java index 30cc676..5529a90 100644 --- a/src/main/java/io/api/etherscan/model/Balance.java +++ b/src/main/java/io/api/etherscan/model/Balance.java @@ -26,7 +26,7 @@ public static Balance of(BalanceTO balance) { return new Balance(balance.getAccount(), new BigInteger(balance.getBalance())); } - // + // public String getAddress() { return address; } @@ -50,16 +50,19 @@ public BigInteger getGwei() { public BigInteger getEther() { return balance.asEther(); } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Balance balance1 = (Balance) o; - if (!balance.equals(balance1.balance)) return false; + if (!balance.equals(balance1.balance)) + return false; return address != null ? address.equals(balance1.address) : balance1.address == null; } diff --git a/src/main/java/io/api/etherscan/model/BaseTx.java b/src/main/java/io/api/etherscan/model/BaseTx.java index af2286f..5aea827 100644 --- a/src/main/java/io/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/api/etherscan/model/BaseTx.java @@ -26,13 +26,13 @@ abstract class BaseTx { private BigInteger gas; private BigInteger gasUsed; - // + // public long getBlockNumber() { return blockNumber; } public LocalDateTime getTimeStamp() { - if(_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) + if (_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) _timeStamp = LocalDateTime.ofEpochSecond(Long.valueOf(timeStamp), 0, ZoneOffset.UTC); return _timeStamp; } @@ -68,20 +68,27 @@ public BigInteger getGas() { public BigInteger getGasUsed() { return gasUsed; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BaseTx)) return false; + if (this == o) + return true; + if (!(o instanceof BaseTx)) + return false; BaseTx baseTx = (BaseTx) o; - if (blockNumber != baseTx.blockNumber) return false; - if (timeStamp != null ? !timeStamp.equals(baseTx.timeStamp) : baseTx.timeStamp != null) return false; - if (hash != null ? !hash.equals(baseTx.hash) : baseTx.hash != null) return false; - if (from != null ? !from.equals(baseTx.from) : baseTx.from != null) return false; - if (to != null ? !to.equals(baseTx.to) : baseTx.to != null) return false; + if (blockNumber != baseTx.blockNumber) + return false; + if (timeStamp != null ? !timeStamp.equals(baseTx.timeStamp) : baseTx.timeStamp != null) + return false; + if (hash != null ? !hash.equals(baseTx.hash) : baseTx.hash != null) + return false; + if (from != null ? !from.equals(baseTx.from) : baseTx.from != null) + return false; + if (to != null ? !to.equals(baseTx.to) : baseTx.to != null) + return false; return value != null ? value.equals(baseTx.value) : baseTx.value == null; } diff --git a/src/main/java/io/api/etherscan/model/Block.java b/src/main/java/io/api/etherscan/model/Block.java index 0406e4e..2e9b96b 100644 --- a/src/main/java/io/api/etherscan/model/Block.java +++ b/src/main/java/io/api/etherscan/model/Block.java @@ -19,13 +19,13 @@ public class Block { private String timeStamp; private LocalDateTime _timeStamp; - // + // public long getBlockNumber() { return blockNumber; } public LocalDateTime getTimeStamp() { - if(_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) + if (_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) _timeStamp = LocalDateTime.ofEpochSecond(Long.valueOf(timeStamp), 0, ZoneOffset.UTC); return _timeStamp; } @@ -33,12 +33,14 @@ public LocalDateTime getTimeStamp() { public BigInteger getBlockReward() { return blockReward; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Block block = (Block) o; diff --git a/src/main/java/io/api/etherscan/model/EthNetwork.java b/src/main/java/io/api/etherscan/model/EthNetwork.java index d10aead..f7b91de 100644 --- a/src/main/java/io/api/etherscan/model/EthNetwork.java +++ b/src/main/java/io/api/etherscan/model/EthNetwork.java @@ -7,6 +7,7 @@ * @since 28.10.2018 */ public enum EthNetwork { + MAINNET("api"), ROPSTEN("api-ropsten"), KOVAN("api-kovan"), diff --git a/src/main/java/io/api/etherscan/model/Log.java b/src/main/java/io/api/etherscan/model/Log.java index 2fa58ee..2a7cc09 100644 --- a/src/main/java/io/api/etherscan/model/Log.java +++ b/src/main/java/io/api/etherscan/model/Log.java @@ -32,9 +32,9 @@ public class Log { private String logIndex; private Long _logIndex; - // + // public Long getBlockNumber() { - if(_blockNumber == null && !BasicUtils.isEmpty(blockNumber)){ + if (_blockNumber == null && !BasicUtils.isEmpty(blockNumber)) { _blockNumber = BasicUtils.parseHex(blockNumber).longValue(); } return _blockNumber; @@ -49,7 +49,7 @@ public String getTransactionHash() { } public Long getTransactionIndex() { - if(_transactionIndex == null && !BasicUtils.isEmpty(transactionIndex)){ + if (_transactionIndex == null && !BasicUtils.isEmpty(transactionIndex)) { _transactionIndex = BasicUtils.parseHex(transactionIndex).longValue(); } @@ -57,7 +57,7 @@ public Long getTransactionIndex() { } public LocalDateTime getTimeStamp() { - if(_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) { + if (_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) { long formatted = (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') ? BasicUtils.parseHex(timeStamp).longValue() : Long.parseLong(timeStamp); @@ -67,9 +67,11 @@ 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 milliseconds between Unix epoch and `timeStamp`. If field is empty or null, returns null + * 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 */ public Long getTimeStampAsMillis() { if (BasicUtils.isEmpty(timeStamp)) { @@ -86,7 +88,7 @@ public String getData() { } public BigInteger getGasPrice() { - if(!BasicUtils.isEmpty(gasPrice)){ + if (!BasicUtils.isEmpty(gasPrice)) { _gasPrice = BasicUtils.parseHex(gasPrice); } @@ -94,7 +96,7 @@ public BigInteger getGasPrice() { } public BigInteger getGasUsed() { - if(!BasicUtils.isEmpty(gasUsed)){ + if (!BasicUtils.isEmpty(gasUsed)) { _gasUsed = BasicUtils.parseHex(gasUsed); } @@ -106,25 +108,30 @@ public List getTopics() { } public Long getLogIndex() { - if(_logIndex == null && !BasicUtils.isEmpty(logIndex)){ + if (_logIndex == null && !BasicUtils.isEmpty(logIndex)) { _logIndex = BasicUtils.parseHex(logIndex).longValue(); } return _logIndex; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Log log = (Log) o; - if (blockNumber != null ? !blockNumber.equals(log.blockNumber) : log.blockNumber != null) return false; - if (address != null ? !address.equals(log.address) : log.address != null) return false; + if (blockNumber != null ? !blockNumber.equals(log.blockNumber) : log.blockNumber != null) + return false; + if (address != null ? !address.equals(log.address) : log.address != null) + return false; if (transactionHash != null ? !transactionHash.equals(log.transactionHash) : log.transactionHash != null) return false; - if (timeStamp != null ? !timeStamp.equals(log.timeStamp) : log.timeStamp != null) return false; + if (timeStamp != null ? !timeStamp.equals(log.timeStamp) : log.timeStamp != null) + return false; return logIndex != null ? logIndex.equals(log.logIndex) : log.logIndex == null; } diff --git a/src/main/java/io/api/etherscan/model/Price.java b/src/main/java/io/api/etherscan/model/Price.java index f9839e2..9a37592 100644 --- a/src/main/java/io/api/etherscan/model/Price.java +++ b/src/main/java/io/api/etherscan/model/Price.java @@ -27,26 +27,30 @@ public double inBtc() { } public LocalDateTime usdTimestamp() { - if(_ethusd_timestamp == null) + if (_ethusd_timestamp == null) _ethusd_timestamp = LocalDateTime.ofEpochSecond(Long.valueOf(ethusd_timestamp), 0, ZoneOffset.UTC); return _ethusd_timestamp; } public LocalDateTime btcTimestamp() { - if(_ethbtc_timestamp == null) + if (_ethbtc_timestamp == null) _ethbtc_timestamp = LocalDateTime.ofEpochSecond(Long.valueOf(ethbtc_timestamp), 0, ZoneOffset.UTC); return _ethbtc_timestamp; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Price price = (Price) o; - if (Double.compare(price.ethusd, ethusd) != 0) return false; - if (Double.compare(price.ethbtc, ethbtc) != 0) return false; + if (Double.compare(price.ethusd, ethusd) != 0) + return false; + if (Double.compare(price.ethbtc, ethbtc) != 0) + return false; if (ethusd_timestamp != null ? !ethusd_timestamp.equals(price.ethusd_timestamp) : price.ethusd_timestamp != null) return false; return (ethbtc_timestamp != null ? !ethbtc_timestamp.equals(price.ethbtc_timestamp) : price.ethbtc_timestamp != null); diff --git a/src/main/java/io/api/etherscan/model/Status.java b/src/main/java/io/api/etherscan/model/Status.java index 71a4ebb..4a1fe18 100644 --- a/src/main/java/io/api/etherscan/model/Status.java +++ b/src/main/java/io/api/etherscan/model/Status.java @@ -24,12 +24,15 @@ public String getErrDescription() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Status status = (Status) o; - if (isError != status.isError) return false; + if (isError != status.isError) + return false; return errDescription != null ? errDescription.equals(status.errDescription) : status.errDescription == null; } diff --git a/src/main/java/io/api/etherscan/model/TokenBalance.java b/src/main/java/io/api/etherscan/model/TokenBalance.java index b40ae0c..e198079 100644 --- a/src/main/java/io/api/etherscan/model/TokenBalance.java +++ b/src/main/java/io/api/etherscan/model/TokenBalance.java @@ -23,9 +23,12 @@ public String getContract() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; TokenBalance that = (TokenBalance) o; diff --git a/src/main/java/io/api/etherscan/model/Tx.java b/src/main/java/io/api/etherscan/model/Tx.java index 68f00f1..6fce75b 100644 --- a/src/main/java/io/api/etherscan/model/Tx.java +++ b/src/main/java/io/api/etherscan/model/Tx.java @@ -21,7 +21,7 @@ public class Tx extends BaseTx { private String isError; private String txreceipt_status; - // + // public long getNonce() { return nonce; } @@ -53,19 +53,25 @@ public BigInteger getCumulativeGasUsed() { public long getConfirmations() { return confirmations; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; Tx tx = (Tx) o; - if (nonce != tx.nonce) return false; - if (transactionIndex != tx.transactionIndex) return false; - if (blockHash != null ? !blockHash.equals(tx.blockHash) : tx.blockHash != null) return false; + if (nonce != tx.nonce) + return false; + if (transactionIndex != tx.transactionIndex) + return false; + if (blockHash != null ? !blockHash.equals(tx.blockHash) : tx.blockHash != null) + return false; return isError != null ? isError.equals(tx.isError) : tx.isError == null; } diff --git a/src/main/java/io/api/etherscan/model/TxInternal.java b/src/main/java/io/api/etherscan/model/TxInternal.java index 1d9d8a8..e7b75d9 100644 --- a/src/main/java/io/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/api/etherscan/model/TxInternal.java @@ -13,7 +13,7 @@ public class TxInternal extends BaseTx { private int isError; private String errCode; - // + // public String getType() { return type; } @@ -29,17 +29,21 @@ public boolean haveError() { public String getErrCode() { return errCode; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof TxInternal)) return false; - if (!super.equals(o)) return false; + if (this == o) + return true; + if (!(o instanceof TxInternal)) + return false; + if (!super.equals(o)) + return false; TxInternal that = (TxInternal) o; - if (traceId != that.traceId) return false; + if (traceId != that.traceId) + return false; return errCode != null ? errCode.equals(that.errCode) : that.errCode == null; } diff --git a/src/main/java/io/api/etherscan/model/TxToken.java b/src/main/java/io/api/etherscan/model/TxToken.java index 985066b..8f5e36f 100644 --- a/src/main/java/io/api/etherscan/model/TxToken.java +++ b/src/main/java/io/api/etherscan/model/TxToken.java @@ -18,7 +18,7 @@ public class TxToken extends BaseTx { private long cumulativeGasUsed; private long confirmations; - // + // public long getNonce() { return nonce; } @@ -54,7 +54,7 @@ public long getCumulativeGasUsed() { public long getConfirmations() { return confirmations; } - // + // @Override public String toString() { diff --git a/src/main/java/io/api/etherscan/model/Uncle.java b/src/main/java/io/api/etherscan/model/Uncle.java index bf5145d..2ee206b 100644 --- a/src/main/java/io/api/etherscan/model/Uncle.java +++ b/src/main/java/io/api/etherscan/model/Uncle.java @@ -14,7 +14,7 @@ public class Uncle { private BigInteger blockreward; private int unclePosition; - // + // public String getMiner() { return miner; } @@ -26,17 +26,21 @@ public BigInteger getBlockreward() { public int getUnclePosition() { return unclePosition; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Uncle uncle = (Uncle) o; - if (unclePosition != uncle.unclePosition) return false; - if (miner != null ? !miner.equals(uncle.miner) : uncle.miner != null) return false; + if (unclePosition != uncle.unclePosition) + return false; + if (miner != null ? !miner.equals(uncle.miner) : uncle.miner != null) + return false; return blockreward != null ? blockreward.equals(uncle.blockreward) : uncle.blockreward == null; } diff --git a/src/main/java/io/api/etherscan/model/UncleBlock.java b/src/main/java/io/api/etherscan/model/UncleBlock.java index 6b7eb4f..88c975d 100644 --- a/src/main/java/io/api/etherscan/model/UncleBlock.java +++ b/src/main/java/io/api/etherscan/model/UncleBlock.java @@ -16,7 +16,7 @@ public class UncleBlock extends Block { private List uncles; private String uncleInclusionReward; - // + // public boolean isEmpty() { return getBlockNumber() == 0 && getBlockReward() == null && getTimeStamp() == null @@ -34,13 +34,16 @@ public List getUncles() { public String getUncleInclusionReward() { return uncleInclusionReward; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; UncleBlock that = (UncleBlock) o; diff --git a/src/main/java/io/api/etherscan/model/Wei.java b/src/main/java/io/api/etherscan/model/Wei.java index cf5f465..35f90d0 100644 --- a/src/main/java/io/api/etherscan/model/Wei.java +++ b/src/main/java/io/api/etherscan/model/Wei.java @@ -16,7 +16,7 @@ public Wei(BigInteger value) { this.result = value; } - // + // public BigInteger getValue() { return result; } @@ -36,12 +36,14 @@ public BigInteger asGwei() { public BigInteger asEther() { return result.divide(BigInteger.valueOf(1000000000000000L)); } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Wei wei = (Wei) o; diff --git a/src/main/java/io/api/etherscan/model/event/IEvent.java b/src/main/java/io/api/etherscan/model/event/IEvent.java index 0fb0e65..47e2e2c 100644 --- a/src/main/java/io/api/etherscan/model/event/IEvent.java +++ b/src/main/java/io/api/etherscan/model/event/IEvent.java @@ -8,6 +8,7 @@ import java.util.Map; public interface IEvent { + static final Map> subTypes = new HashMap<>(); void setLog(Log log); diff --git a/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java b/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java index 915b99c..ba52faf 100644 --- a/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java +++ b/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java @@ -3,6 +3,7 @@ import io.api.etherscan.model.event.IEvent; public class ApprovalEvent extends Event { + static final String eventTypeHash = "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"; static { IEvent.registerEventType(ApprovalEvent.eventTypeHash, ApprovalEvent.class); diff --git a/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java b/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java index 31343a5..fe3ad06 100644 --- a/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java +++ b/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java @@ -2,7 +2,8 @@ import io.api.etherscan.model.event.IEvent; -public class DepositEvent extends Event { +public class DepositEvent extends Event { + static final String eventTypeHash = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"; static { IEvent.registerEventType(DepositEvent.eventTypeHash, DepositEvent.class); diff --git a/src/main/java/io/api/etherscan/model/event/impl/Event.java b/src/main/java/io/api/etherscan/model/event/impl/Event.java index dbcffcd..6f30e25 100644 --- a/src/main/java/io/api/etherscan/model/event/impl/Event.java +++ b/src/main/java/io/api/etherscan/model/event/impl/Event.java @@ -1,15 +1,11 @@ package io.api.etherscan.model.event.impl; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EventModelException; import io.api.etherscan.model.Log; import io.api.etherscan.model.event.IEvent; -import java.util.HashMap; -import java.util.Map; - /** - * Base class for a higher-level API on top of {@link Log}. Each Event class has an identifying hash + * Base class for a higher-level API on top of {@link Log}. Each Event class has + * an identifying hash */ public class Event implements IEvent { diff --git a/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java b/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java index 597dd7e..cd3b6d5 100644 --- a/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java +++ b/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java @@ -2,7 +2,8 @@ import io.api.etherscan.model.event.IEvent; -public class MintEvent extends Event { +public class MintEvent extends Event { + static final String eventTypeHash = "0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f"; static { IEvent.registerEventType(MintEvent.eventTypeHash, MintEvent.class); diff --git a/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java b/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java index ff566f4..76b4931 100644 --- a/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java +++ b/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java @@ -3,6 +3,7 @@ import io.api.etherscan.model.event.IEvent; public class SyncEvent extends Event { + static final String eventTypeHash = "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"; static { IEvent.registerEventType(SyncEvent.eventTypeHash, SyncEvent.class); diff --git a/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java b/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java index a1a8be0..ce16958 100644 --- a/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java +++ b/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java @@ -3,6 +3,7 @@ import io.api.etherscan.model.event.IEvent; public class TransferErc20Event extends Event { + static final String eventTypeHash = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; static { IEvent.registerEventType(TransferErc20Event.eventTypeHash, TransferErc20Event.class); diff --git a/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java b/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java index 3fa442b..23036bf 100644 --- a/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java +++ b/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java @@ -2,7 +2,8 @@ import io.api.etherscan.model.event.IEvent; -public class WithdrawEvent extends Event { +public class WithdrawEvent extends Event { + static final String eventTypeHash = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"; static { IEvent.registerEventType(WithdrawEvent.eventTypeHash, WithdrawEvent.class); diff --git a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java b/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java index 414a797..3d7ddd3 100644 --- a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java @@ -44,9 +44,9 @@ public class BlockProxy { private String transactionsRoot; private List transactions; - // + // public Long getNumber() { - if(_number == null && !BasicUtils.isEmpty(number)) + if (_number == null && !BasicUtils.isEmpty(number)) _number = BasicUtils.parseHex(number).longValue(); return _number; } @@ -64,7 +64,7 @@ public String getStateRoot() { } public Long getSize() { - if(_size == null && !BasicUtils.isEmpty(size)) + if (_size == null && !BasicUtils.isEmpty(size)) _size = BasicUtils.parseHex(size).longValue(); return _size; } @@ -78,7 +78,7 @@ public String getTotalDifficulty() { } public LocalDateTime getTimeStamp() { - if(_timestamp == null && !BasicUtils.isEmpty(timestamp)) + if (_timestamp == null && !BasicUtils.isEmpty(timestamp)) _timestamp = LocalDateTime.ofEpochSecond(BasicUtils.parseHex(timestamp).longValue(), 0, ZoneOffset.UTC); return _timestamp; } @@ -104,13 +104,13 @@ public String getMixHash() { } public BigInteger getGasUsed() { - if(_gasUsed == null && !BasicUtils.isEmpty(gasUsed)) + if (_gasUsed == null && !BasicUtils.isEmpty(gasUsed)) _gasUsed = BasicUtils.parseHex(gasUsed); return _gasUsed; } public BigInteger getGasLimit() { - if(_gasLimit == null && !BasicUtils.isEmpty(gasLimit)) + if (_gasLimit == null && !BasicUtils.isEmpty(gasLimit)) _gasLimit = BasicUtils.parseHex(gasLimit); return _gasLimit; } @@ -134,17 +134,21 @@ public String getTransactionsRoot() { public List getTransactions() { return transactions; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; BlockProxy that = (BlockProxy) o; - if (number != null ? !number.equals(that.number) : that.number != null) return false; - if (hash != null ? !hash.equals(that.hash) : that.hash != null) return false; + if (number != null ? !number.equals(that.number) : that.number != null) + return false; + if (hash != null ? !hash.equals(that.hash) : that.hash != null) + return false; return parentHash != null ? parentHash.equals(that.parentHash) : that.parentHash == null; } diff --git a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java b/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java index b6e2bcc..d69a627 100644 --- a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java @@ -32,7 +32,7 @@ public class ReceiptProxy { private List logs; private String logsBloom; - // + // public String getRoot() { return root; } @@ -46,7 +46,7 @@ public String getTo() { } public Long getBlockNumber() { - if(_blockNumber == null && !BasicUtils.isEmpty(blockNumber)) + if (_blockNumber == null && !BasicUtils.isEmpty(blockNumber)) _blockNumber = BasicUtils.parseHex(blockNumber).longValue(); return _blockNumber; } @@ -60,19 +60,19 @@ public String getTransactionHash() { } public Long getTransactionIndex() { - if(_transactionIndex == null && !BasicUtils.isEmpty(transactionIndex)) + if (_transactionIndex == null && !BasicUtils.isEmpty(transactionIndex)) _transactionIndex = BasicUtils.parseHex(transactionIndex).longValue(); return _transactionIndex; } public BigInteger getGasUsed() { - if(_gasUsed == null && !BasicUtils.isEmpty(gasUsed)) + if (_gasUsed == null && !BasicUtils.isEmpty(gasUsed)) _gasUsed = BasicUtils.parseHex(gasUsed); return _gasUsed; } public BigInteger getCumulativeGasUsed() { - if(_cumulativeGasUsed == null && !BasicUtils.isEmpty(cumulativeGasUsed)) + if (_cumulativeGasUsed == null && !BasicUtils.isEmpty(cumulativeGasUsed)) _cumulativeGasUsed = BasicUtils.parseHex(cumulativeGasUsed); return _cumulativeGasUsed; } @@ -88,16 +88,19 @@ public List getLogs() { public String getLogsBloom() { return logsBloom; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; ReceiptProxy that = (ReceiptProxy) o; - if (blockNumber != null ? !blockNumber.equals(that.blockNumber) : that.blockNumber != null) return false; + if (blockNumber != null ? !blockNumber.equals(that.blockNumber) : that.blockNumber != null) + return false; if (transactionHash != null ? !transactionHash.equals(that.transactionHash) : that.transactionHash != null) return false; return transactionIndex != null ? transactionIndex.equals(that.transactionIndex) : that.transactionIndex == null; diff --git a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java b/src/main/java/io/api/etherscan/model/proxy/TxProxy.java index c45e679..25b50c8 100644 --- a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/TxProxy.java @@ -32,7 +32,7 @@ public class TxProxy { private String blockNumber; private Long _blockNumber; - // + // public String getTo() { return to; } @@ -42,7 +42,7 @@ public String getHash() { } public Long getTransactionIndex() { - if(_transactionIndex == null && !BasicUtils.isEmpty(transactionIndex)) + if (_transactionIndex == null && !BasicUtils.isEmpty(transactionIndex)) _transactionIndex = BasicUtils.parseHex(transactionIndex).longValue(); return _transactionIndex; } @@ -52,7 +52,7 @@ public String getFrom() { } public BigInteger getGas() { - if(_gas == null && !BasicUtils.isEmpty(gas)) + if (_gas == null && !BasicUtils.isEmpty(gas)) _gas = BasicUtils.parseHex(gas); return _gas; } @@ -74,7 +74,7 @@ public String getR() { } public Long getNonce() { - if(_nonce == null && !BasicUtils.isEmpty(nonce)) + if (_nonce == null && !BasicUtils.isEmpty(nonce)) _nonce = BasicUtils.parseHex(nonce).longValue(); return _nonce; } @@ -84,7 +84,7 @@ public String getValue() { } public BigInteger getGasPrice() { - if(_gasPrice == null && !BasicUtils.isEmpty(gasPrice)) + if (_gasPrice == null && !BasicUtils.isEmpty(gasPrice)) _gasPrice = BasicUtils.parseHex(gasPrice); return _gasPrice; } @@ -94,21 +94,25 @@ public String getBlockHash() { } public Long getBlockNumber() { - if(_blockNumber == null && !BasicUtils.isEmpty(blockNumber)) + if (_blockNumber == null && !BasicUtils.isEmpty(blockNumber)) _blockNumber = BasicUtils.parseHex(blockNumber).longValue(); return _blockNumber; } - // + // @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; TxProxy txProxy = (TxProxy) o; - if (hash != null ? !hash.equals(txProxy.hash) : txProxy.hash != null) return false; - if (blockHash != null ? !blockHash.equals(txProxy.blockHash) : txProxy.blockHash != null) return false; + if (hash != null ? !hash.equals(txProxy.hash) : txProxy.hash != null) + return false; + if (blockHash != null ? !blockHash.equals(txProxy.blockHash) : txProxy.blockHash != null) + return false; return blockNumber != null ? blockNumber.equals(txProxy.blockNumber) : txProxy.blockNumber == null; } diff --git a/src/main/java/io/api/etherscan/model/query/IQueryBuilder.java b/src/main/java/io/api/etherscan/model/query/IQueryBuilder.java index 17bc2f9..6a76c62 100644 --- a/src/main/java/io/api/etherscan/model/query/IQueryBuilder.java +++ b/src/main/java/io/api/etherscan/model/query/IQueryBuilder.java @@ -10,5 +10,6 @@ * @since 31.10.2018 */ public interface IQueryBuilder { + LogQuery build() throws LogQueryException; } diff --git a/src/main/java/io/api/etherscan/model/query/LogOp.java b/src/main/java/io/api/etherscan/model/query/LogOp.java index 5c6138c..0c0ebee 100644 --- a/src/main/java/io/api/etherscan/model/query/LogOp.java +++ b/src/main/java/io/api/etherscan/model/query/LogOp.java @@ -7,6 +7,7 @@ * @since 31.10.2018 */ public enum LogOp { + AND("and"), OR("or"); diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java b/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java index ae429b0..3ba6c4f 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java @@ -5,8 +5,7 @@ /** * Final builded container for The Event Log API * - * EtherScan - API Descriptions - * https://etherscan.io/apis#logs + * EtherScan - API Descriptions https://etherscan.io/apis#logs * * @see LogQueryBuilder * @see ILogsApi diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java b/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java index 1397f15..bd8a9fc 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java @@ -41,37 +41,37 @@ public static LogQueryBuilder with(String address, long startBlock, long endBloc } public LogTopicSingle topic(String topic0) { - if(BasicUtils.isNotHex(topic0)) + if (BasicUtils.isNotHex(topic0)) throw new LogQueryException("topic0 can not be empty or non hex."); return new LogTopicSingle(address, startBlock, endBlock, topic0); } public LogTopicTuple topic(String topic0, String topic1) { - if(BasicUtils.isNotHex(topic0)) + if (BasicUtils.isNotHex(topic0)) throw new LogQueryException("topic0 can not be empty or non hex."); - if(BasicUtils.isNotHex(topic1)) + if (BasicUtils.isNotHex(topic1)) throw new LogQueryException("topic1 can not be empty or non hex."); return new LogTopicTuple(address, startBlock, endBlock, topic0, topic1); } public LogTopicTriple topic(String topic0, String topic1, String topic2) { - if(BasicUtils.isNotHex(topic0)) + if (BasicUtils.isNotHex(topic0)) throw new LogQueryException("topic0 can not be empty or non hex."); - if(BasicUtils.isNotHex(topic1)) + if (BasicUtils.isNotHex(topic1)) throw new LogQueryException("topic1 can not be empty or non hex."); - if(BasicUtils.isNotHex(topic2)) + if (BasicUtils.isNotHex(topic2)) throw new LogQueryException("topic2 can not be empty or non hex."); return new LogTopicTriple(address, startBlock, endBlock, topic0, topic1, topic2); } public LogTopicQuadro topic(String topic0, String topic1, String topic2, String topic3) { - if(BasicUtils.isNotHex(topic0)) + if (BasicUtils.isNotHex(topic0)) throw new LogQueryException("topic0 can not be empty or non hex."); - if(BasicUtils.isNotHex(topic1)) + if (BasicUtils.isNotHex(topic1)) throw new LogQueryException("topic1 can not be empty or non hex."); - if(BasicUtils.isNotHex(topic2)) + if (BasicUtils.isNotHex(topic2)) throw new LogQueryException("topic2 can not be empty or non hex."); - if(BasicUtils.isNotHex(topic3)) + if (BasicUtils.isNotHex(topic3)) throw new LogQueryException("topic3 can not be empty or non hex."); return new LogTopicQuadro(address, startBlock, endBlock, topic0, topic1, topic2, topic3); diff --git a/src/main/java/io/api/etherscan/model/utility/BlockParam.java b/src/main/java/io/api/etherscan/model/utility/BlockParam.java index 22774f2..cbc5a3e 100644 --- a/src/main/java/io/api/etherscan/model/utility/BlockParam.java +++ b/src/main/java/io/api/etherscan/model/utility/BlockParam.java @@ -7,6 +7,7 @@ * @since 31.10.2018 */ public class BlockParam { + private long startBlock; private long endBlock; diff --git a/src/main/java/io/api/etherscan/util/BasicUtils.java b/src/main/java/io/api/etherscan/util/BasicUtils.java index cec41e5..0d2a654 100644 --- a/src/main/java/io/api/etherscan/util/BasicUtils.java +++ b/src/main/java/io/api/etherscan/util/BasicUtils.java @@ -108,9 +108,10 @@ public static void validateTxResponse(T response) { if (response.getStatus() != 1) { if (response.getMessage() == null) { - throw new EtherScanException("Unexpected Etherscan exception, no information from server about error, code " + response.getStatus()); + throw new EtherScanException( + "Unexpected Etherscan exception, no information from server about error, code " + response.getStatus()); } else if (!response.getMessage().startsWith("No tra") && !response.getMessage().startsWith("No rec")) { - throw new EtherScanException(response.getMessage() + ", with status " + response.getStatus()); + throw new EtherScanException(response); } } } From 268e5e957c098a166b1131053d925e2e04afb313 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 18 Oct 2020 15:52:18 +0300 Subject: [PATCH 12/16] [1.1.0-SNAPSHOT] ApiRunner added All tests refactored to use ApiRunner ApiRunner correct key retrival and queue set added All tests fixed and improved --- .../api/etherscan/core/impl/EtherScanApi.java | 2 +- .../etherscan/manager/impl/QueueManager.java | 4 ++ src/test/java/io/api/ApiRunner.java | 46 ++++++++++++++++++ .../io/api/etherscan/EtherScanApiTest.java | 20 +++----- .../account/AccountBalanceListTest.java | 33 +++++-------- .../etherscan/account/AccountBalanceTest.java | 40 ++++------------ .../account/AccountMinedBlocksTest.java | 31 +++++------- .../account/AccountTokenBalanceTest.java | 48 +++++-------------- .../account/AccountTxInternalByHashTest.java | 44 +++++------------ .../account/AccountTxInternalTest.java | 17 +++---- .../etherscan/account/AccountTxTokenTest.java | 21 ++++---- .../api/etherscan/account/AccountTxsTest.java | 17 +++---- .../io/api/etherscan/block/BlockApiTest.java | 17 +++---- .../etherscan/contract/ContractApiTest.java | 13 ++--- .../etherscan/logs/LogQueryBuilderTest.java | 4 +- .../io/api/etherscan/logs/LogsApiTest.java | 25 +++++----- .../etherscan/proxy/ProxyBlockApiTest.java | 13 ++--- .../proxy/ProxyBlockLastNoApiTest.java | 9 ++-- .../proxy/ProxyBlockUncleApiTest.java | 13 ++--- .../api/etherscan/proxy/ProxyCallApiTest.java | 15 +++--- .../api/etherscan/proxy/ProxyCodeApiTest.java | 13 ++--- .../api/etherscan/proxy/ProxyGasApiTest.java | 17 +++---- .../etherscan/proxy/ProxyStorageApiTest.java | 13 ++--- .../api/etherscan/proxy/ProxyTxApiTest.java | 17 +++---- .../etherscan/proxy/ProxyTxCountApiTest.java | 17 +++---- .../proxy/ProxyTxReceiptApiTest.java | 16 +++---- .../proxy/ProxyTxSendRawApiTest.java | 18 ++++--- .../statistic/StatisticPriceApiTest.java | 9 ++-- .../statistic/StatisticSupplyApiTest.java | 9 ++-- .../StatisticTokenSupplyApiTest.java | 15 +++--- .../transaction/TransactionExecApiTest.java | 13 ++--- .../TransactionReceiptApiTest.java | 15 +++--- .../java/io/api/manager/QueueManagerTest.java | 24 +++++----- .../java/io/api/util/BasicUtilsTests.java | 4 +- 34 files changed, 266 insertions(+), 366 deletions(-) create mode 100644 src/test/java/io/api/ApiRunner.java 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 789794f..0dd0f9e 100644 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java @@ -67,7 +67,7 @@ public EtherScanApi(final String apiKey, final Supplier executorSupplier) { this(apiKey, network, executorSupplier, DEFAULT_KEY.equals(apiKey) - ? new QueueManager(1, 6) + ? QueueManager.DEFAULT_KEY_QUEUE : new FakeQueueManager() ); } 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 cd957ee..a9a32dd 100644 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java @@ -1,5 +1,6 @@ package io.api.etherscan.manager.impl; +import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.manager.IQueueManager; import java.util.concurrent.*; @@ -14,6 +15,9 @@ */ public class QueueManager implements IQueueManager { + public static final QueueManager DEFAULT_KEY_QUEUE = new QueueManager(1, 6); + public static final QueueManager PERSONAL_KEY_QUEUE = new QueueManager(5, 1); + private final Semaphore semaphore; public QueueManager(int queueSize, int queueResetTimeInSec) { diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java new file mode 100644 index 0000000..75b3e4e --- /dev/null +++ b/src/test/java/io/api/ApiRunner.java @@ -0,0 +1,46 @@ +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; + +public class ApiRunner extends Assert { + + private static final EtherScanApi api; + private static final EtherScanApi apiRopsten; + private static final EtherScanApi apiRinkeby; + private static final EtherScanApi apiKovan; + + static { + final String apiKey = System.getenv("API_KEY"); + final String keyOrDefault = (apiKey == null || apiKey.isEmpty()) + ? EtherScanApi.DEFAULT_KEY + : apiKey; + + final QueueManager queue = keyOrDefault.equals(EtherScanApi.DEFAULT_KEY) + ? QueueManager.DEFAULT_KEY_QUEUE + : QueueManager.PERSONAL_KEY_QUEUE; + + api = new EtherScanApi(keyOrDefault, EthNetwork.MAINNET, queue); + apiRopsten = new EtherScanApi(keyOrDefault, EthNetwork.ROPSTEN, queue); + apiRinkeby = new EtherScanApi(keyOrDefault, EthNetwork.RINKEBY, queue); + apiKovan = new EtherScanApi(keyOrDefault, EthNetwork.KOVAN, queue); + } + + public static EtherScanApi getApi() { + return api; + } + + public static EtherScanApi getApiRopsten() { + return apiRopsten; + } + + public static EtherScanApi getApiRinkeby() { + return apiRinkeby; + } + + public static EtherScanApi getApiKovan() { + return apiKovan; + } +} diff --git a/src/test/java/io/api/etherscan/EtherScanApiTest.java b/src/test/java/io/api/etherscan/EtherScanApiTest.java index 2d0d978..c478b59 100644 --- a/src/test/java/io/api/etherscan/EtherScanApiTest.java +++ b/src/test/java/io/api/etherscan/EtherScanApiTest.java @@ -1,5 +1,6 @@ package io.api.etherscan; +import io.api.ApiRunner; import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.ApiException; import io.api.etherscan.error.ApiKeyException; @@ -9,22 +10,19 @@ import io.api.etherscan.model.Balance; import io.api.etherscan.model.Block; import io.api.etherscan.model.EthNetwork; -import org.junit.Assert; import org.junit.Test; import java.util.List; import java.util.function.Supplier; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 05.11.2018 */ -public class EtherScanApiTest extends Assert { +public class EtherScanApiTest extends ApiRunner { - private EthNetwork network = EthNetwork.KOVAN; - private String validKey = "YourKey"; + private final EthNetwork network = EthNetwork.KOVAN; + private final String validKey = "YourKey"; @Test public void validKey() { @@ -59,24 +57,20 @@ public void noTimeoutOnRead() { @Test public void noTimeoutOnReadGroli() { Supplier supplier = () -> new HttpExecutor(300); - EtherScanApi api = new EtherScanApi(EthNetwork.GORLI, supplier); - Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); + Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } @Test public void noTimeoutOnReadTobalala() { Supplier supplier = () -> new HttpExecutor(30000); - EtherScanApi api = new EtherScanApi(EthNetwork.TOBALABA, supplier); - Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); + Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } @Test public void noTimeoutUnlimitedAwait() { - Supplier supplier = () -> new HttpExecutor(-30, -300); - EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET, supplier); - Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); + Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } diff --git a/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java b/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java index f5a1ebd..fdeb1e9 100644 --- a/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java +++ b/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java @@ -1,12 +1,12 @@ package io.api.etherscan.account; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Balance; import io.api.support.AddressUtil; -import org.junit.Assert; import org.junit.Test; +import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -16,9 +16,7 @@ * @author GoodforGod * @since 03.11.2018 */ -public class AccountBalanceListTest extends Assert { - - private EtherScanApi api = new EtherScanApi(); +public class AccountBalanceListTest extends ApiRunner { @Test public void correct() { @@ -26,13 +24,13 @@ public void correct() { addresses.add("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); addresses.add("0xC9F32CE1127e44C51cbD182D6364F3D707Fd0d47"); - List balances = api.account().balances(addresses); + List balances = getApi().account().balances(addresses); assertNotNull(balances); assertFalse(balances.isEmpty()); assertEquals(2, balances.size()); assertNotEquals(balances.get(0), balances.get(1)); assertNotEquals(balances.get(0).hashCode(), balances.get(1).hashCode()); - for(Balance balance : balances) { + for (Balance balance : balances) { assertNotNull(balance.getAddress()); assertNotNull(balance.getGwei()); assertNotNull(balance.getKwei()); @@ -40,7 +38,7 @@ public void correct() { assertNotNull(balance.getEther()); assertNotNull(balance.getGwei()); assertNotNull(balance.getAddress()); - assertNotEquals(0, balance.getWei()); + assertNotEquals(BigInteger.ZERO, balance.getWei()); assertNotNull(balance.toString()); } } @@ -49,20 +47,15 @@ public void correct() { public void correctMoreThat20Addresses() { List addresses = AddressUtil.genRealAddresses(); - List balances = api.account().balances(addresses); + List balances = getApi().account().balances(addresses); assertNotNull(balances); assertFalse(balances.isEmpty()); assertEquals(25, balances.size()); - for(Balance balance : balances) { + for (Balance balance : balances) { assertNotNull(balance.getAddress()); - assertNotEquals(0, balance.getWei()); - assertNotEquals(0, balance.getKwei()); - assertNotEquals(0, balance.getMwei()); - assertNotEquals(0, balance.getEther()); - assertNotEquals(0, balance.getGwei()); } - assertFalse(balances.get(0).equals(balances.get(1))); + assertNotEquals(balances.get(0), balances.get(1)); } @Test(expected = InvalidAddressException.class) @@ -71,13 +64,13 @@ public void invalidParamWithError() { addresses.add("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); addresses.add("C9F32CE1127e44C51cbD182D6364F3D707Fd0d47"); - api.account().balances(addresses); + getApi().account().balances(addresses); } @Test public void emptyParamList() { List addresses = new ArrayList<>(); - List balances = api.account().balances(addresses); + List balances = getApi().account().balances(addresses); assertNotNull(balances); assertTrue(balances.isEmpty()); } @@ -88,11 +81,11 @@ public void correctParamWithEmptyExpectedResult() { addresses.add("0x1327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); addresses.add("0xC1F32CE1127e44C51cbD182D6364F3D707Fd0d47"); - List balances = api.account().balances(addresses); + List balances = getApi().account().balances(addresses); assertNotNull(balances); assertFalse(balances.isEmpty()); assertEquals(2, balances.size()); - for(Balance balance : balances) { + for (Balance balance : balances) { assertNotNull(balance.getAddress()); assertEquals(0, balance.getWei().intValue()); } diff --git a/src/test/java/io/api/etherscan/account/AccountBalanceTest.java b/src/test/java/io/api/etherscan/account/AccountBalanceTest.java index 21eca22..76aca68 100644 --- a/src/test/java/io/api/etherscan/account/AccountBalanceTest.java +++ b/src/test/java/io/api/etherscan/account/AccountBalanceTest.java @@ -1,10 +1,9 @@ package io.api.etherscan.account; +import io.api.ApiRunner; import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Balance; -import io.api.etherscan.model.EthNetwork; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -20,12 +19,12 @@ * @since 03.11.2018 */ @RunWith(Parameterized.class) -public class AccountBalanceTest extends Assert { +public class AccountBalanceTest extends ApiRunner { - private EtherScanApi api; - private String addressCorrect; - private String addressInvalid; - private String addressNoResponse; + private final EtherScanApi api; + private final String addressCorrect; + private final String addressInvalid; + private final String addressNoResponse; public AccountBalanceTest(EtherScanApi api, String addressCorrect, String addressInvalid, String addressNoResponse) { this.api = api; @@ -36,31 +35,13 @@ public AccountBalanceTest(EtherScanApi api, String addressCorrect, String addres @Parameters public static Collection data() { - return Arrays.asList(new Object[][]{ + return Arrays.asList(new Object[][] { { - new EtherScanApi(), + getApi(), "0x8d4426f94e42f721C7116E81d6688cd935cB3b4F", "8d4426f94e42f721C7116E81d6688cd935cB3b4F", "0x1d4426f94e42f721C7116E81d6688cd935cB3b4F" - }, - { - new EtherScanApi(EthNetwork.ROPSTEN), - "0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a", - "xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a", - "0x1dbd2b932c763ba5b1b7ae3b362eac3e8d40121a" - }, - { - new EtherScanApi(EthNetwork.RINKEBY), - "0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a", - "xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a", - "0x1dbd2b932c763ba5b1b7ae3b362eac3e8d40121a" - }, - { - new EtherScanApi(EthNetwork.KOVAN), - "0xB9F36EE9df7E2A24B61b1738F4127BFDe8bA1A87", - "xB9F36EE9df7E2A24B61b1738F4127BFDe8bA1A87", - "0xB1F36EE9df7E2A24B61b1738F4127BFDe8bA1A87" - }, + } }); } @@ -74,13 +55,12 @@ public void correct() { assertNotNull(balance.getGwei()); assertNotNull(balance.getEther()); assertNotNull(balance.getAddress()); - assertNotEquals(0, balance.getWei()); assertNotNull(balance.toString()); } @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - Balance balance = api.account().balance(addressInvalid); + Balance balance = getApi().account().balance(addressInvalid); } @Test diff --git a/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java b/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java index 5f44b68..3a46858 100644 --- a/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java +++ b/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java @@ -1,10 +1,9 @@ package io.api.etherscan.account; +import io.api.ApiRunner; import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Block; -import io.api.etherscan.model.EthNetwork; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -21,13 +20,13 @@ * @since 03.11.2018 */ @RunWith(Parameterized.class) -public class AccountMinedBlocksTest extends Assert { +public class AccountMinedBlocksTest extends ApiRunner { - private EtherScanApi api; - private int blocksMined; - private String addressCorrect; - private String addressInvalid; - private String addressNoResponse; + private final EtherScanApi api; + private final int blocksMined; + private final String addressCorrect; + private final String addressInvalid; + private final String addressNoResponse; public AccountMinedBlocksTest(EtherScanApi api, int blocksMined, @@ -43,22 +42,14 @@ public AccountMinedBlocksTest(EtherScanApi api, @Parameters public static Collection data() { - return Arrays.asList(new Object[][]{ + return Arrays.asList(new Object[][] { { - new EtherScanApi(), + getApi(), 223, "0xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23", "xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23", "0xE1C6175183029A0f039bf2DFffa5C6e8F3cA9B23", - }, - { - new EtherScanApi(EthNetwork.ROPSTEN), - 1, - "0x0923DafEB5A5d11a83E188d5dbCdEd14f9b161a7", - "00923DafEB5A5d11a83E188d5dbCdEd14f9b161a7", - "0x1923DafEB5A5d11a83E188d5dbCdEd14f9b161a7", } - // Other netWorks not presented due to 30k+ mined blocks, tests runs forever }); } @@ -71,7 +62,7 @@ public void correct() { assertBlocks(blocks); assertNotNull(blocks.get(0).toString()); - if(blocks.size() > 1) { + if (blocks.size() > 1) { assertNotEquals(blocks.get(0), blocks.get(1)); assertNotEquals(blocks.get(0).hashCode(), blocks.get(1).hashCode()); } @@ -79,7 +70,7 @@ public void correct() { @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - List txs = api.account().minedBlocks(addressInvalid); + List txs = getApi().account().minedBlocks(addressInvalid); } @Test diff --git a/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java b/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java index 2c747a0..2794e95 100644 --- a/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java @@ -1,11 +1,10 @@ package io.api.etherscan.account; +import io.api.ApiRunner; import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Balance; -import io.api.etherscan.model.EthNetwork; import io.api.etherscan.model.TokenBalance; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -21,14 +20,14 @@ * @since 03.11.2018 */ @RunWith(Parameterized.class) -public class AccountTokenBalanceTest extends Assert { +public class AccountTokenBalanceTest extends ApiRunner { - private EtherScanApi api; - private String contractValid; - private String addressValid; - private String contractInvalid; - private String addressInvalid; - private String addressEmpty; + private final EtherScanApi api; + private final String contractValid; + private final String addressValid; + private final String contractInvalid; + private final String addressInvalid; + private final String addressEmpty; public AccountTokenBalanceTest(EtherScanApi api, String contractValid, @@ -46,39 +45,15 @@ public AccountTokenBalanceTest(EtherScanApi api, @Parameters public static Collection data() { - return Arrays.asList(new Object[][]{ + return Arrays.asList(new Object[][] { { - new EtherScanApi(), + getApi(), "0x5EaC95ad5b287cF44E058dCf694419333b796123", "0x5d807e7F124EC2103a59c5249187f772c0b8D6b2", "0xEaC95ad5b287cF44E058dCf694419333b796123", "0x5807e7F124EC2103a59c5249187f772c0b8D6b2", "0x1d807e7F124EC2103a59c5249187f772c0b8D6b2", - }, - { - new EtherScanApi(EthNetwork.ROPSTEN), - "0x60a5aa08619bd5f71c6d20bfaefb5ac2c2806745", - "0x0923dafeb5a5d11a83e188d5dbcded14f9b161a7", - "0x0a5aa08619bd5f71c6d20bfaefb5ac2c2806745", - "0x923dafeb5a5d11a83e188d5dbcded14f9b161a7", - "0x1923dafeb5a5d11a83e188d5dbcded14f9b161a7", - }, - { - new EtherScanApi(EthNetwork.RINKEBY), - "0xb8b6f3fb67403c90652dc5f085ba4a62ab1ef5ce", - "0x7ffc57839b00206d1ad20c69a1981b489f772031", - "0x8b6f3fb67403c90652dc5f085ba4a62ab1ef5ce", - "0x7fc57839b00206d1ad20c69a1981b489f772031", - "0x1ffc57839b00206d1ad20c69a1981b489f772031", - }, - { - new EtherScanApi(EthNetwork.KOVAN), - "0xde0eaa632f071069214f1c1ad7eb609ff8152dfe", - "0x00e6d2b931f55a3f1701c7389d592a7778897879", - "0xd0eaa632f071069214f1c1ad7eb609ff8152dfe", - "0x0e6d2b931f55a3f1701c7389d592a7778897879", - "0x10e6d2b931f55a3f1701c7389d592a7778897879", - }, + } }); } @@ -89,7 +64,6 @@ public void correct() { assertNotNull(balance.getWei()); assertNotNull(balance.getAddress()); assertNotNull(balance.getContract()); - assertNotEquals(0, balance.getWei()); assertNotNull(balance.toString()); TokenBalance balance2 = new TokenBalance("125161", balance.getWei(), balance.getContract()); diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java index 4727317..d1ed2bc 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java @@ -1,11 +1,10 @@ package io.api.etherscan.account; +import io.api.ApiRunner; import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.InvalidTxHashException; -import io.api.etherscan.model.EthNetwork; import io.api.etherscan.model.TxInternal; import io.api.etherscan.util.BasicUtils; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -22,13 +21,13 @@ * @since 03.11.2018 */ @RunWith(Parameterized.class) -public class AccountTxInternalByHashTest extends Assert { +public class AccountTxInternalByHashTest extends ApiRunner { - private EtherScanApi api; - private int txAmount; - private String validTx; - private String invalidTx; - private String emptyTx; + private final EtherScanApi api; + private final int txAmount; + private final String validTx; + private final String invalidTx; + private final String emptyTx; public AccountTxInternalByHashTest(EtherScanApi api, int txAmount, String validTx, String invalidTx, String emptyTx) { this.api = api; @@ -40,35 +39,14 @@ public AccountTxInternalByHashTest(EtherScanApi api, int txAmount, String validT @Parameters public static Collection data() { - return Arrays.asList(new Object[][]{ + return Arrays.asList(new Object[][] { { - new EtherScanApi(), + getApi(), 1, "0x1b513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b", "0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b", "0x2b513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b", - }, - { - new EtherScanApi(EthNetwork.ROPSTEN), - 1, - "0x8bc5504517d40ad7b4f32d008d82436cc118e856593f61d23c70eec73c10322a", - "0x8c5504517d40ad7b4f32d008d82436cc118e856593f61d23c70eec73c10322a", - "0x7bc5504517d40ad7b4f32d008d82436cc118e856593f61d23c70eec73c10322a", - }, - { - new EtherScanApi(EthNetwork.RINKEBY), - 10, - "0x4be697e735f594038e2e70e051948896bdfd6193fc399e38d6113e96f96e8567", - "0x4e697e735f594038e2e70e051948896bdfd6193fc399e38d6113e96f96e8567", - "0x3be697e735f594038e2e70e051948896bdfd6193fc399e38d6113e96f96e8567", - }, - { - new EtherScanApi(EthNetwork.KOVAN), - 1, - "0x3b85c47f2a8c5efd639a85f5233097af834c9ab6ab7965cf7cfc3ab3c8bae285", - "0x385c47f2a8c5efd639a85f5233097af834c9ab6ab7965cf7cfc3ab3c8bae285", - "0x2b85c47f2a8c5efd639a85f5233097af834c9ab6ab7965cf7cfc3ab3c8bae285", - }, + } }); } @@ -89,7 +67,7 @@ public void correct() { assertTrue(BasicUtils.isEmpty(txs.get(0).getErrCode())); assertNotNull(txs.get(0).toString()); - if(txs.size() > 9) { + if (txs.size() > 9) { assertNotEquals(txs.get(0), txs.get(9)); assertNotEquals(txs.get(0).hashCode(), txs.get(9).hashCode()); } diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java b/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java index a80be28..f993c39 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.account; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.TxInternal; -import org.junit.Assert; import org.junit.Test; import java.util.List; @@ -14,13 +13,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class AccountTxInternalTest extends Assert { - - private EtherScanApi api = new EtherScanApi(); +public class AccountTxInternalTest extends ApiRunner { @Test public void correct() { - List txs = api.account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3"); + List txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3"); assertNotNull(txs); assertEquals(66, txs.size()); assertTxs(txs); @@ -29,7 +26,7 @@ public void correct() { @Test public void correctStartBlock() { - List txs = api.account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3", 2558775); + List txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3", 2558775); assertNotNull(txs); assertEquals(24, txs.size()); assertTxs(txs); @@ -37,7 +34,7 @@ public void correctStartBlock() { @Test public void correctStartBlockEndBlock() { - List txs = api.account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3", 2558775, 2685504); + List txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3", 2558775, 2685504); assertNotNull(txs); assertEquals(21, txs.size()); assertTxs(txs); @@ -45,12 +42,12 @@ public void correctStartBlockEndBlock() { @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - List txs = api.account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51"); + List txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51"); } @Test public void correctParamWithEmptyExpectedResult() { - List txs = api.account().txsInternal("0x2C1ba59D6F58433FB2EaEe7d20b26Ed83bDA51A3"); + List txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB2EaEe7d20b26Ed83bDA51A3"); assertNotNull(txs); assertTrue(txs.isEmpty()); } diff --git a/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java b/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java index 7bdf2d6..b82d4d1 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.account; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.TxToken; -import org.junit.Assert; import org.junit.Test; import java.util.List; @@ -14,13 +13,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class AccountTxTokenTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class AccountTxTokenTest extends ApiRunner { @Test public void correct() { - List txs = api.account().txsToken("0xE376F69ED2218076682e2b3B7b9099eC50aD68c4"); + List txs = getApi().account().txsToken("0xE376F69ED2218076682e2b3B7b9099eC50aD68c4"); assertNotNull(txs); assertEquals(3, txs.size()); assertTxs(txs); @@ -39,7 +36,7 @@ public void correct() { @Test public void correctStartBlock() { - List txs = api.account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167); + List txs = getApi().account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167); assertNotNull(txs); assertEquals(11, txs.size()); assertTxs(txs); @@ -47,7 +44,7 @@ public void correctStartBlock() { @Test public void correctStartBlockEndBlock() { - List txs = api.account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167, 5813576); + List txs = getApi().account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167, 5813576); assertNotNull(txs); assertEquals(5, txs.size()); assertTxs(txs); @@ -55,12 +52,12 @@ public void correctStartBlockEndBlock() { @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - api.account().txsToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + getApi().account().txsToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); } @Test public void correctParamWithEmptyExpectedResult() { - List txs = api.account().txsToken("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + List txs = getApi().account().txsToken("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); assertNotNull(txs); assertTrue(txs.isEmpty()); } @@ -74,9 +71,9 @@ private void assertTxs(List txs) { assertNotNull(tx.getTo()); assertNotNull(tx.getTimeStamp()); assertNotNull(tx.getTokenDecimal()); - assertNotEquals(-1,(tx.getConfirmations())); + assertNotEquals(-1, (tx.getConfirmations())); assertNotNull(tx.getGasUsed()); - assertNotEquals(-1 ,tx.getCumulativeGasUsed()); + assertNotEquals(-1, tx.getCumulativeGasUsed()); assertNotEquals(-1, tx.getTransactionIndex()); } } diff --git a/src/test/java/io/api/etherscan/account/AccountTxsTest.java b/src/test/java/io/api/etherscan/account/AccountTxsTest.java index 5c0ad48..66a95e4 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxsTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxsTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.account; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Tx; -import org.junit.Assert; import org.junit.Test; import java.util.List; @@ -14,13 +13,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class AccountTxsTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class AccountTxsTest extends ApiRunner { @Test public void correct() { - List txs = api.account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); + List txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); assertNotNull(txs); assertEquals(5, txs.size()); assertTxs(txs); @@ -43,7 +40,7 @@ public void correct() { @Test public void correctStartBlock() { - List txs = api.account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9", 3892842); + List txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9", 3892842); assertNotNull(txs); assertEquals(4, txs.size()); assertTxs(txs); @@ -51,7 +48,7 @@ public void correctStartBlock() { @Test public void correctStartBlockEndBlock() { - List txs = api.account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9", 3892842, 3945741); + List txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9", 3892842, 3945741); assertNotNull(txs); assertEquals(3, txs.size()); assertTxs(txs); @@ -60,12 +57,12 @@ public void correctStartBlockEndBlock() { @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - List txs = api.account().txs("9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); + List txs = getApi().account().txs("9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); } @Test public void correctParamWithEmptyExpectedResult() { - List txs = api.account().txs("0x9321cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); + List txs = getApi().account().txs("0x9321cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); assertNotNull(txs); assertTrue(txs.isEmpty()); } diff --git a/src/test/java/io/api/etherscan/block/BlockApiTest.java b/src/test/java/io/api/etherscan/block/BlockApiTest.java index c459b1a..34b9de5 100644 --- a/src/test/java/io/api/etherscan/block/BlockApiTest.java +++ b/src/test/java/io/api/etherscan/block/BlockApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.block; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.model.UncleBlock; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -13,13 +12,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class BlockApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class BlockApiTest extends ApiRunner { @Test public void correct() { - Optional uncle = api.block().uncles(2165403); + Optional uncle = getApi().block().uncles(2165403); assertTrue(uncle.isPresent()); assertFalse(uncle.get().isEmpty()); assertNotNull(uncle.get().getBlockMiner()); @@ -36,13 +33,13 @@ public void correct() { assertNotEquals(uncle.get(), empty); assertTrue(empty.isEmpty()); - if(uncle.get().getUncles().size() > 0) { + if (uncle.get().getUncles().size() > 0) { assertNotEquals(-1, uncle.get().getUncles().get(0).getUnclePosition()); assertEquals(uncle.get().getUncles().get(0), uncle.get().getUncles().get(0)); assertEquals(uncle.get().getUncles().get(0).hashCode(), uncle.get().getUncles().get(0).hashCode()); } - if(uncle.get().getUncles().size() > 1) { + if (uncle.get().getUncles().size() > 1) { assertNotEquals(uncle.get().getUncles().get(1), uncle.get().getUncles().get(0)); assertNotEquals(uncle.get().getUncles().get(1).hashCode(), uncle.get().getUncles().get(0).hashCode()); } @@ -50,14 +47,14 @@ public void correct() { @Test public void correctNoUncles() { - Optional uncles = api.block().uncles(34); + Optional uncles = getApi().block().uncles(34); assertTrue(uncles.isPresent()); assertTrue(uncles.get().getUncles().isEmpty()); } @Test public void correctParamWithEmptyExpectedResult() { - Optional uncles = api.block().uncles(99999999934L); + Optional uncles = getApi().block().uncles(99999999934L); assertFalse(uncles.isPresent()); } } diff --git a/src/test/java/io/api/etherscan/contract/ContractApiTest.java b/src/test/java/io/api/etherscan/contract/ContractApiTest.java index 7ecf26f..9d43c5f 100644 --- a/src/test/java/io/api/etherscan/contract/ContractApiTest.java +++ b/src/test/java/io/api/etherscan/contract/ContractApiTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.contract; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Abi; -import org.junit.Assert; import org.junit.Test; /** @@ -12,13 +11,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ContractApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ContractApiTest extends ApiRunner { @Test public void correct() { - Abi abi = api.contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); + Abi abi = getApi().contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); assertNotNull(abi); assertTrue(abi.isVerified()); assertTrue(abi.haveAbi()); @@ -32,12 +29,12 @@ public void correct() { @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - api.contract().contractAbi("0xBBbc244D798123fDe783fCc1C72d3Bb8C189413"); + getApi().contract().contractAbi("0xBBbc244D798123fDe783fCc1C72d3Bb8C189413"); } @Test public void correctParamWithEmptyExpectedResult() { - Abi abi = api.contract().contractAbi("0xBB1bc244D798123fDe783fCc1C72d3Bb8C189413"); + Abi abi = getApi().contract().contractAbi("0xBB1bc244D798123fDe783fCc1C72d3Bb8C189413"); assertNotNull(abi); assertFalse(abi.isVerified()); } diff --git a/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java b/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java index 058f592..85b35e8 100644 --- a/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java +++ b/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java @@ -1,12 +1,12 @@ package io.api.etherscan.logs; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.error.LogQueryException; import io.api.etherscan.model.query.LogOp; import io.api.etherscan.model.query.impl.LogQuery; import io.api.etherscan.model.query.impl.LogQueryBuilder; import io.api.etherscan.model.query.impl.LogTopicQuadro; -import org.junit.Assert; import org.junit.Test; /** @@ -15,7 +15,7 @@ * @author GoodforGod * @since 03.11.2018 */ -public class LogQueryBuilderTest extends Assert { +public class LogQueryBuilderTest extends ApiRunner { @Test public void singleCorrect() { diff --git a/src/test/java/io/api/etherscan/logs/LogsApiTest.java b/src/test/java/io/api/etherscan/logs/LogsApiTest.java index 4a3f9e1..7143a83 100644 --- a/src/test/java/io/api/etherscan/logs/LogsApiTest.java +++ b/src/test/java/io/api/etherscan/logs/LogsApiTest.java @@ -1,11 +1,10 @@ package io.api.etherscan.logs; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.model.Log; import io.api.etherscan.model.query.LogOp; import io.api.etherscan.model.query.impl.LogQuery; import io.api.etherscan.model.query.impl.LogQueryBuilder; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -22,12 +21,10 @@ * @since 03.11.2018 */ @RunWith(Parameterized.class) -public class LogsApiTest extends Assert { +public class LogsApiTest extends ApiRunner { - private final EtherScanApi api = new EtherScanApi(); - - private LogQuery query; - private int logsSize; + private final LogQuery query; + private final int logsSize; public LogsApiTest(LogQuery query, int logsSize) { this.query = query; @@ -56,21 +53,21 @@ public static Collection data() { .setOpTopic0_1(LogOp.OR) .build(); - return Arrays.asList(new Object[][]{ - {single, 423}, - {singleInvalidAddr, 0}, - {tupleAnd, 1}, - {tupleOr, 425} + return Arrays.asList(new Object[][] { + { single, 423 }, + { singleInvalidAddr, 0 }, + { tupleAnd, 1 }, + { tupleOr, 425 } }); } @Test public void validateQuery() { - List logs = api.logs().logs(query); + List logs = getApi().logs().logs(query); assertEquals(logsSize, logs.size()); if (logsSize > 0) { - if(logsSize > 1) { + if (logsSize > 1) { assertNotEquals(logs.get(0), logs.get(1)); assertNotEquals(logs.get(0).hashCode(), logs.get(1).hashCode()); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java index 43b4851..181a68d 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.model.proxy.BlockProxy; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -13,13 +12,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyBlockApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyBlockApiTest extends ApiRunner { @Test public void correct() { - Optional block = api.proxy().block(5120); + Optional block = getApi().proxy().block(5120); assertTrue(block.isPresent()); BlockProxy proxy = block.get(); assertNotNull(proxy.getHash()); @@ -52,13 +49,13 @@ public void correct() { @Test public void correctParamWithEmptyExpectedResult() { - Optional block = api.proxy().block(99999999999L); + Optional block = getApi().proxy().block(99999999999L); assertFalse(block.isPresent()); } @Test public void correctParamNegativeNo() { - Optional block = api.proxy().block(-1); + Optional block = getApi().proxy().block(-1); assertTrue(block.isPresent()); assertNotNull(block.get().getHash()); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java index b981a42..5485391 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java @@ -1,7 +1,6 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; -import org.junit.Assert; +import io.api.ApiRunner; import org.junit.Test; /** @@ -10,13 +9,11 @@ * @author GoodforGod * @since 13.11.2018 */ -public class ProxyBlockLastNoApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyBlockLastNoApiTest extends ApiRunner { @Test public void correct() { - long noLast = api.proxy().blockNoLast(); + long noLast = getApi().proxy().blockNoLast(); assertNotEquals(0, noLast); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java index 3ccac29..474c5bb 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.model.proxy.BlockProxy; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -13,13 +12,11 @@ * @author GoodforGod * @since 13.11.2018 */ -public class ProxyBlockUncleApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyBlockUncleApiTest extends ApiRunner { @Test public void correct() { - Optional block = api.proxy().blockUncle(603183, 0); + Optional block = getApi().proxy().blockUncle(603183, 0); assertTrue(block.isPresent()); assertNotNull(block.get().getHash()); assertNotNull(block.get().toString()); @@ -27,13 +24,13 @@ public void correct() { @Test public void correctParamWithEmptyExpectedResult() { - Optional block = api.proxy().blockUncle(5120, 1); + Optional block = getApi().proxy().blockUncle(5120, 1); assertFalse(block.isPresent()); } @Test public void correctParamNegativeNo() { - Optional block = api.proxy().blockUncle(-603183, 0); + Optional block = getApi().proxy().blockUncle(-603183, 0); assertFalse(block.isPresent()); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java index 159ed9c..c90850c 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java @@ -1,10 +1,9 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.error.InvalidDataHexException; import io.api.etherscan.util.BasicUtils; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -15,13 +14,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyCallApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyCallApiTest extends ApiRunner { @Test public void correct() { - Optional call = api.proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", + Optional call = getApi().proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); assertTrue(call.isPresent()); assertFalse(BasicUtils.isNotHex(call.get())); @@ -29,19 +26,19 @@ public void correct() { @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - Optional call = api.proxy().call("0xEEF46DB4855E25702F8237E8f403FddcaF931C0", + Optional call = getApi().proxy().call("0xEEF46DB4855E25702F8237E8f403FddcaF931C0", "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); } @Test(expected = InvalidDataHexException.class) public void invalidParamNotHex() { - Optional call = api.proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", + Optional call = getApi().proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", "7-0a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); } @Test public void correctParamWithEmptyExpectedResult() { - Optional call = api.proxy().call("0xAEEF16DB4855E25702F8237E8f403FddcaF931C0", + Optional call = getApi().proxy().call("0xAEEF16DB4855E25702F8237E8f403FddcaF931C0", "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); assertTrue(call.isPresent()); assertFalse(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 3c47171..3120503 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.util.BasicUtils; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -14,25 +13,23 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyCodeApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyCodeApiTest extends ApiRunner { @Test public void correct() { - Optional call = api.proxy().code("0xf75e354c5edc8efed9b59ee9f67a80845ade7d0c"); + Optional call = getApi().proxy().code("0xf75e354c5edc8efed9b59ee9f67a80845ade7d0c"); assertTrue(call.isPresent()); assertFalse(BasicUtils.isNotHex(call.get())); } @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - Optional call = api.proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c"); + Optional call = getApi().proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c"); } @Test public void correctParamWithEmptyExpectedResult() { - Optional call = api.proxy().code("0xf15e354c5edc8efed9b59ee9f67a80845ade7d0c"); + Optional call = getApi().proxy().code("0xf15e354c5edc8efed9b59ee9f67a80845ade7d0c"); assertTrue(call.isPresent()); assertFalse(BasicUtils.isNotHex(call.get())); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java index 2665175..63e476c 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidDataHexException; -import org.junit.Assert; import org.junit.Test; import java.math.BigInteger; @@ -13,20 +12,18 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyGasApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyGasApiTest extends ApiRunner { @Test public void correctPrice() { - BigInteger price = api.proxy().gasPrice(); + BigInteger price = getApi().proxy().gasPrice(); assertNotNull(price); assertNotEquals(0, price.intValue()); } @Test public void correctEstimated() { - BigInteger price = api.proxy().gasEstimated(); + BigInteger price = getApi().proxy().gasEstimated(); assertNotNull(price); assertNotEquals(0, price.intValue()); } @@ -34,8 +31,8 @@ public void correctEstimated() { @Test public void correctEstimatedWithData() { String dataCustom = "606060405260728060106000396000f360606040526000606060405260728060106000396000f360606040526000"; - BigInteger price = api.proxy().gasEstimated(); - BigInteger priceCustom = api.proxy().gasEstimated(dataCustom); + BigInteger price = getApi().proxy().gasEstimated(); + BigInteger priceCustom = getApi().proxy().gasEstimated(dataCustom); assertNotNull(price); assertNotNull(priceCustom); assertNotEquals(price, priceCustom); @@ -44,6 +41,6 @@ public void correctEstimatedWithData() { @Test(expected = InvalidDataHexException.class) public void invalidParamWithError() { String dataCustom = "280&60106000396000f360606040526000"; - BigInteger priceCustom = api.proxy().gasEstimated(dataCustom); + BigInteger priceCustom = getApi().proxy().gasEstimated(dataCustom); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java index a96ddb3..fbfcf80 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.util.BasicUtils; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -14,25 +13,23 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyStorageApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyStorageApiTest extends ApiRunner { @Test public void correct() { - Optional call = api.proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); + Optional call = getApi().proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); assertTrue(call.isPresent()); assertFalse(BasicUtils.isNotHex(call.get())); } @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - Optional call = api.proxy().storageAt("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); + Optional call = getApi().proxy().storageAt("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); } @Test public void correctParamWithEmptyExpectedResult() { - Optional call = api.proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 100); + Optional call = getApi().proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 100); assertFalse(call.isPresent()); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java index 1a36372..2779120 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.proxy.TxProxy; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -14,13 +13,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyTxApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyTxApiTest extends ApiRunner { @Test public void correctByHash() { - Optional tx = api.proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + Optional tx = getApi().proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertTrue(tx.isPresent()); assertNotNull(tx.get().getBlockHash()); assertNotNull(tx.get().getFrom()); @@ -37,7 +34,7 @@ public void correctByHash() { @Test public void correctByBlockNo() { - Optional tx = api.proxy().tx(637368, 0); + Optional tx = getApi().proxy().tx(637368, 0); assertTrue(tx.isPresent()); assertNotNull(tx.get().getBlockHash()); assertNotNull(tx.get().getFrom()); @@ -57,18 +54,18 @@ public void correctByBlockNo() { @Test(expected = InvalidTxHashException.class) public void invalidParamWithError() { - Optional tx = api.proxy().tx("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + Optional tx = getApi().proxy().tx("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); } @Test public void correctParamWithEmptyExpectedResultBlockNoExist() { - Optional tx = api.proxy().tx(99999999L, 0); + Optional tx = getApi().proxy().tx(99999999L, 0); assertFalse(tx.isPresent()); } @Test public void correctParamWithEmptyExpectedResult() { - Optional tx = api.proxy().tx("0x2e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + Optional tx = getApi().proxy().tx("0x2e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertFalse(tx.isPresent()); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java index 7ab1100..6a0778c 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; -import org.junit.Assert; import org.junit.Test; /** @@ -11,36 +10,34 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyTxCountApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyTxCountApiTest extends ApiRunner { @Test public void correctSended() { - int count = api.proxy().txSendCount("0x2910543af39aba0cd09dbb2d50200b3e800a63d2"); + int count = getApi().proxy().txSendCount("0x2910543af39aba0cd09dbb2d50200b3e800a63d2"); assertNotEquals(0, count); } @Test public void correctByBlockNo() { - int count = api.proxy().txCount(6137420); + int count = getApi().proxy().txCount(6137420); assertNotEquals(0, count); } @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - int count = api.proxy().txSendCount("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd"); + int count = getApi().proxy().txSendCount("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd"); } @Test public void correctParamWithEmptyExpectedResultBlockNoExist() { - int count = api.proxy().txCount(99999999999L); + int count = getApi().proxy().txCount(99999999999L); assertEquals(0, count); } @Test public void correctParamWithEmptyExpectedResult() { - int count = api.proxy().txSendCount("0x1e03d9cce9d60f3e9f2597e13cd4c54c55330cfd"); + int count = getApi().proxy().txSendCount("0x1e03d9cce9d60f3e9f2597e13cd4c54c55330cfd"); assertEquals(0, count); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java index a530bdb..c4a3383 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.proxy.ReceiptProxy; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -14,13 +13,12 @@ * @author GoodforGod * @since 03.11.2018 */ -public class ProxyTxReceiptApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class ProxyTxReceiptApiTest extends ApiRunner { @Test public void correct() { - Optional infoProxy = api.proxy().txReceipt("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + Optional infoProxy = getApi().proxy() + .txReceipt("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertTrue(infoProxy.isPresent()); assertNotNull(infoProxy.get().getBlockHash()); assertNotNull(infoProxy.get().getRoot()); @@ -44,12 +42,14 @@ public void correct() { @Test(expected = InvalidTxHashException.class) public void invalidParamWithError() { - Optional infoProxy = api.proxy().txReceipt("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + Optional infoProxy = getApi().proxy() + .txReceipt("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); } @Test public void correctParamWithEmptyExpectedResult() { - Optional infoProxy = api.proxy().txReceipt("0x2e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + Optional infoProxy = getApi().proxy() + .txReceipt("0x2e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertFalse(infoProxy.isPresent()); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java index a5eb5eb..40e79a6 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.proxy; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.EtherScanException; import io.api.etherscan.error.InvalidDataHexException; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -14,28 +13,27 @@ * @author GoodforGod * @since 03.11.2018 */ -//TODO contact etherscan and ask about method behavior -public class ProxyTxSendRawApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +// TODO contact etherscan and ask about method behavior +public class ProxyTxSendRawApiTest extends ApiRunner { public void correct() { - Optional sendRaw = api.proxy().txSendRaw("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + Optional sendRaw = getApi().proxy() + .txSendRaw("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertTrue(sendRaw.isPresent()); } @Test(expected = InvalidDataHexException.class) public void invalidParamWithError() { - Optional sendRaw = api.proxy().txSendRaw("5151=0561"); + Optional sendRaw = getApi().proxy().txSendRaw("5151=0561"); } @Test(expected = EtherScanException.class) public void invalidParamEtherScanDataException() { - Optional sendRaw = api.proxy().txSendRaw("0x1"); + Optional sendRaw = getApi().proxy().txSendRaw("0x1"); } public void correctParamWithEmptyExpectedResult() { - Optional sendRaw = api.proxy().txSendRaw("0x000000"); + Optional sendRaw = getApi().proxy().txSendRaw("0x000000"); assertFalse(sendRaw.isPresent()); } } diff --git a/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java b/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java index f92755e..3245b17 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java +++ b/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.statistic; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.model.Price; -import org.junit.Assert; import org.junit.Test; /** @@ -11,13 +10,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class StatisticPriceApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class StatisticPriceApiTest extends ApiRunner { @Test public void correct() { - Price price = api.stats().lastPrice(); + Price price = getApi().stats().lastPrice(); assertNotNull(price); assertNotNull(price.btcTimestamp()); assertNotNull(price.usdTimestamp()); diff --git a/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java b/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java index 4d1eecb..a705a31 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java +++ b/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.statistic; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.model.Supply; -import org.junit.Assert; import org.junit.Test; import java.math.BigInteger; @@ -13,13 +12,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class StatisticSupplyApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class StatisticSupplyApiTest extends ApiRunner { @Test public void correct() { - Supply supply = api.stats().supply(); + Supply supply = getApi().stats().supply(); assertNotNull(supply); assertNotNull(supply.getValue()); assertNotNull(supply.asGwei()); diff --git a/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java b/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java index 53aede7..0a84d01 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java +++ b/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.statistic; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; -import org.junit.Assert; import org.junit.Test; import java.math.BigInteger; @@ -13,25 +12,23 @@ * @author GoodforGod * @since 03.11.2018 */ -public class StatisticTokenSupplyApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class StatisticTokenSupplyApiTest extends ApiRunner { @Test public void correct() { - BigInteger supply = api.stats().supply("0x57d90b64a1a57749b0f932f1a3395792e12e7055"); + BigInteger supply = getApi().stats().supply("0x57d90b64a1a57749b0f932f1a3395792e12e7055"); assertNotNull(supply); - assertNotEquals(0, supply); + assertNotEquals(BigInteger.ZERO, supply); } @Test(expected = InvalidAddressException.class) public void invalidParamWithError() { - BigInteger supply = api.stats().supply("0x7d90b64a1a57749b0f932f1a3395792e12e7055"); + BigInteger supply = getApi().stats().supply("0x7d90b64a1a57749b0f932f1a3395792e12e7055"); } @Test public void correctParamWithEmptyExpectedResult() { - BigInteger supply = api.stats().supply("0x51d90b64a1a57749b0f932f1a3395792e12e7055"); + BigInteger supply = getApi().stats().supply("0x51d90b64a1a57749b0f932f1a3395792e12e7055"); assertNotNull(supply); assertEquals(0, supply.intValue()); } diff --git a/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java b/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java index 2f36d79..25320cc 100644 --- a/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java +++ b/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java @@ -1,9 +1,8 @@ package io.api.etherscan.transaction; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.Status; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -14,13 +13,11 @@ * @author GoodforGod * @since 03.11.2018 */ -public class TransactionExecApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class TransactionExecApiTest extends ApiRunner { @Test public void correct() { - Optional status = api.txs().execStatus("0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); + Optional status = getApi().txs().execStatus("0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); assertTrue(status.isPresent()); assertTrue(status.get().haveError()); assertNotNull(status.get().getErrDescription()); @@ -33,12 +30,12 @@ public void correct() { @Test(expected = InvalidTxHashException.class) public void invalidParamWithError() { - api.txs().execStatus("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b"); + getApi().txs().execStatus("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b"); } @Test public void correctParamWithEmptyExpectedResult() { - Optional status = api.txs().execStatus("0x55f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); + Optional status = getApi().txs().execStatus("0x55f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); assertTrue(status.isPresent()); assertFalse(status.get().haveError()); } diff --git a/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java b/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java index c716c8a..a459355 100644 --- a/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java +++ b/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java @@ -1,8 +1,7 @@ package io.api.etherscan.transaction; -import io.api.etherscan.core.impl.EtherScanApi; +import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; -import org.junit.Assert; import org.junit.Test; import java.util.Optional; @@ -13,25 +12,25 @@ * @author GoodforGod * @since 03.11.2018 */ -public class TransactionReceiptApiTest extends Assert { - - private final EtherScanApi api = new EtherScanApi(); +public class TransactionReceiptApiTest extends ApiRunner { @Test public void correct() { - Optional status = api.txs().receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); + Optional status = getApi().txs() + .receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); assertTrue(status.isPresent()); assertTrue(status.get()); } @Test(expected = InvalidTxHashException.class) public void invalidParamWithError() { - api.txs().receiptStatus("0x13c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); + getApi().txs().receiptStatus("0x13c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); } @Test public void correctParamWithEmptyExpectedResult() { - Optional status = api.txs().receiptStatus("0x113c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); + Optional status = getApi().txs() + .receiptStatus("0x113c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); assertFalse(status.isPresent()); } } diff --git a/src/test/java/io/api/manager/QueueManagerTest.java b/src/test/java/io/api/manager/QueueManagerTest.java index 19b2ea9..acc7b43 100644 --- a/src/test/java/io/api/manager/QueueManagerTest.java +++ b/src/test/java/io/api/manager/QueueManagerTest.java @@ -1,9 +1,9 @@ package io.api.manager; +import io.api.ApiRunner; import io.api.etherscan.manager.IQueueManager; import io.api.etherscan.manager.impl.FakeQueueManager; import io.api.etherscan.manager.impl.QueueManager; -import org.junit.Assert; import org.junit.Test; /** @@ -12,40 +12,40 @@ * @author GoodforGod * @since 03.11.2018 */ -public class QueueManagerTest extends Assert { +public class QueueManagerTest extends ApiRunner { @Test public void fakeManager() { IQueueManager fakeManager = new FakeQueueManager(); - assertTrue(fakeManager.takeTurn()); - assertTrue(fakeManager.takeTurn()); - assertTrue(fakeManager.takeTurn()); - assertTrue(fakeManager.takeTurn()); - assertTrue(fakeManager.takeTurn()); - assertTrue(fakeManager.takeTurn()); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); } @Test(timeout = 3500) public void queueManager() { IQueueManager queueManager = new QueueManager(1, 3); - assertTrue(queueManager.takeTurn()); + queueManager.takeTurn(); queueManager.takeTurn(); } @Test(timeout = 4500) public void queueManagerWithDelay() { IQueueManager queueManager = new QueueManager(1, 2, 2); - assertTrue(queueManager.takeTurn()); + queueManager.takeTurn(); queueManager.takeTurn(); } @Test public void queueManagerTimeout() { IQueueManager queueManager = new QueueManager(1, 3); - assertTrue(queueManager.takeTurn()); + queueManager.takeTurn(); long start = System.currentTimeMillis(); queueManager.takeTurn(); long end = System.currentTimeMillis(); - assertEquals(3, Math.round((double)(end - start)/1000)); + assertEquals(3, Math.round((double) (end - start) / 1000)); } } diff --git a/src/test/java/io/api/util/BasicUtilsTests.java b/src/test/java/io/api/util/BasicUtilsTests.java index 2c5ad71..c35bada 100644 --- a/src/test/java/io/api/util/BasicUtilsTests.java +++ b/src/test/java/io/api/util/BasicUtilsTests.java @@ -1,10 +1,10 @@ package io.api.util; import com.google.gson.Gson; +import io.api.ApiRunner; import io.api.etherscan.error.EtherScanException; import io.api.etherscan.error.ParseException; import io.api.etherscan.model.utility.StringResponseTO; -import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; @@ -18,7 +18,7 @@ * @author GoodforGod * @since 13.11.2018 */ -public class BasicUtilsTests extends Assert { +public class BasicUtilsTests extends ApiRunner { @Test(expected = EtherScanException.class) public void responseValidateEmpty() { From bb83e216e06214ef84a3ca6a2fdf30de4f4d666c Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 18 Oct 2020 15:54:19 +0300 Subject: [PATCH 13/16] [1.1.0-SNAPSHOT] Code style applied Experimental features marked --- src/main/java/io/api/etherscan/core/IEventsApi.java | 3 +++ src/main/java/io/api/etherscan/core/impl/EtherScanApi.java | 3 +-- .../java/io/api/etherscan/core/impl/EventsApiProvider.java | 3 +++ src/main/java/io/api/etherscan/manager/impl/QueueManager.java | 1 - 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/api/etherscan/core/IEventsApi.java b/src/main/java/io/api/etherscan/core/IEventsApi.java index 12c19db..8894b87 100644 --- a/src/main/java/io/api/etherscan/core/IEventsApi.java +++ b/src/main/java/io/api/etherscan/core/IEventsApi.java @@ -3,6 +3,7 @@ import io.api.etherscan.error.ApiException; import io.api.etherscan.model.event.IEvent; import io.api.etherscan.model.query.impl.LogQuery; +import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -10,6 +11,7 @@ /** * EtherScan - API Descriptions https://etherscan.io/apis#logs */ +@Experimental public interface IEventsApi { /** @@ -22,6 +24,7 @@ public interface IEventsApi { * * @see io.api.etherscan.model.query.impl.LogQueryBuilder */ + @Experimental @NotNull List events(LogQuery query) throws ApiException; } 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 0dd0f9e..8cd5d46 100644 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java @@ -68,8 +68,7 @@ public EtherScanApi(final String apiKey, this(apiKey, network, executorSupplier, DEFAULT_KEY.equals(apiKey) ? QueueManager.DEFAULT_KEY_QUEUE - : new FakeQueueManager() - ); + : new FakeQueueManager()); } public EtherScanApi(final String apiKey, diff --git a/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java b/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java index d6bfa25..4b42259 100644 --- a/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java @@ -8,6 +8,7 @@ import io.api.etherscan.model.query.impl.LogQuery; import io.api.etherscan.model.utility.LogResponseTO; import io.api.etherscan.util.BasicUtils; +import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.NotNull; import java.util.Collections; @@ -20,6 +21,7 @@ * @see IEventsApi * */ +@Experimental public class EventsApiProvider extends BasicProvider implements IEventsApi { private static final String ACT_LOGS_PARAM = ACT_PREFIX + "getLogs"; @@ -30,6 +32,7 @@ public class EventsApiProvider extends BasicProvider implements IEventsApi { super(queue, "logs", baseUrl, executor); } + @Experimental @NotNull @Override public List events(final LogQuery query) throws ApiException { 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 a9a32dd..8041734 100644 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java @@ -1,6 +1,5 @@ package io.api.etherscan.manager.impl; -import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.manager.IQueueManager; import java.util.concurrent.*; From e74930b66c0a35227f9ae220c571da29c18c4b03 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 18 Oct 2020 15:55:15 +0300 Subject: [PATCH 14/16] [1.1.0-SNAPSHOT] Experimental features removed as untested and badly designed --- .../io/api/etherscan/core/IEventsApi.java | 30 ---------- .../core/impl/EventsApiProvider.java | 55 ------------------- .../io/api/etherscan/model/event/IEvent.java | 33 ----------- .../model/event/impl/ApprovalEvent.java | 11 ---- .../model/event/impl/DepositEvent.java | 11 ---- .../api/etherscan/model/event/impl/Event.java | 38 ------------- .../etherscan/model/event/impl/MintEvent.java | 12 ---- .../etherscan/model/event/impl/SyncEvent.java | 11 ---- .../model/event/impl/TransferErc20Event.java | 32 ----------- .../model/event/impl/WithdrawEvent.java | 11 ---- 10 files changed, 244 deletions(-) delete mode 100644 src/main/java/io/api/etherscan/core/IEventsApi.java delete mode 100644 src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java delete mode 100644 src/main/java/io/api/etherscan/model/event/IEvent.java delete mode 100644 src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java delete mode 100644 src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java delete mode 100644 src/main/java/io/api/etherscan/model/event/impl/Event.java delete mode 100644 src/main/java/io/api/etherscan/model/event/impl/MintEvent.java delete mode 100644 src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java delete mode 100644 src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java delete mode 100644 src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java diff --git a/src/main/java/io/api/etherscan/core/IEventsApi.java b/src/main/java/io/api/etherscan/core/IEventsApi.java deleted file mode 100644 index 8894b87..0000000 --- a/src/main/java/io/api/etherscan/core/IEventsApi.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.api.etherscan.core; - -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.event.IEvent; -import io.api.etherscan.model.query.impl.LogQuery; -import org.jetbrains.annotations.ApiStatus.Experimental; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * EtherScan - API Descriptions https://etherscan.io/apis#logs - */ -@Experimental -public interface IEventsApi { - - /** - * This is a high-level alternative to the ILogsApi and an alternative to the - * native eth_getLogs Read at EtherScan API description for full info! - * - * @param query build log query - * @return logs according to query - * @throws ApiException parent exception class - * - * @see io.api.etherscan.model.query.impl.LogQueryBuilder - */ - @Experimental - @NotNull - List events(LogQuery query) throws ApiException; -} diff --git a/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java b/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java deleted file mode 100644 index 4b42259..0000000 --- a/src/main/java/io/api/etherscan/core/impl/EventsApiProvider.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.IEventsApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.event.IEvent; -import io.api.etherscan.model.query.impl.LogQuery; -import io.api.etherscan.model.utility.LogResponseTO; -import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.ApiStatus.Experimental; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Logs API Implementation - * - * @see IEventsApi - * - */ -@Experimental -public class EventsApiProvider extends BasicProvider implements IEventsApi { - - private static final String ACT_LOGS_PARAM = ACT_PREFIX + "getLogs"; - - EventsApiProvider(final IQueueManager queue, - final String baseUrl, - final IHttpExecutor executor) { - super(queue, "logs", baseUrl, executor); - } - - @Experimental - @NotNull - @Override - public List events(final LogQuery query) throws ApiException { - final String urlParams = ACT_LOGS_PARAM + query.getParams(); - final LogResponseTO response = getRequest(urlParams, LogResponseTO.class); - BasicUtils.validateTxResponse(response); - - if (BasicUtils.isEmpty(response.getResult())) { - return Collections.emptyList(); - } ; - return response - .getResult() - .stream() - .map((log) -> { - String eventTypeHash = log.getTopics().get(0); - return IEvent.createEvent(eventTypeHash, log); - }) - .collect(Collectors.toList()); - } -} diff --git a/src/main/java/io/api/etherscan/model/event/IEvent.java b/src/main/java/io/api/etherscan/model/event/IEvent.java deleted file mode 100644 index 47e2e2c..0000000 --- a/src/main/java/io/api/etherscan/model/event/IEvent.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.api.etherscan.model.event; - -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EventModelException; -import io.api.etherscan.model.Log; - -import java.util.HashMap; -import java.util.Map; - -public interface IEvent { - - static final Map> subTypes = new HashMap<>(); - - void setLog(Log log); - - static void registerEventType(String typeHash, Class clazz) { - subTypes.put(typeHash, clazz); - } - - static IEvent createEvent(String typeHash, Log log) { - if (null == typeHash) { - throw new EventModelException("Event type hash cannot be null"); - } - Class clazz = subTypes.get(typeHash); - try { - IEvent evt = (IEvent) clazz.newInstance(); - evt.setLog(log); - return evt; - } catch (InstantiationException | IllegalAccessException ex) { - throw new ApiException("Client-side error instantiating Event object", ex); - } - } -} diff --git a/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java b/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java deleted file mode 100644 index ba52faf..0000000 --- a/src/main/java/io/api/etherscan/model/event/impl/ApprovalEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.api.etherscan.model.event.impl; - -import io.api.etherscan.model.event.IEvent; - -public class ApprovalEvent extends Event { - - static final String eventTypeHash = "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"; - static { - IEvent.registerEventType(ApprovalEvent.eventTypeHash, ApprovalEvent.class); - } -} diff --git a/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java b/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java deleted file mode 100644 index fe3ad06..0000000 --- a/src/main/java/io/api/etherscan/model/event/impl/DepositEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.api.etherscan.model.event.impl; - -import io.api.etherscan.model.event.IEvent; - -public class DepositEvent extends Event { - - static final String eventTypeHash = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"; - static { - IEvent.registerEventType(DepositEvent.eventTypeHash, DepositEvent.class); - } -} diff --git a/src/main/java/io/api/etherscan/model/event/impl/Event.java b/src/main/java/io/api/etherscan/model/event/impl/Event.java deleted file mode 100644 index 6f30e25..0000000 --- a/src/main/java/io/api/etherscan/model/event/impl/Event.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.api.etherscan.model.event.impl; - -import io.api.etherscan.model.Log; -import io.api.etherscan.model.event.IEvent; - -/** - * Base class for a higher-level API on top of {@link Log}. Each Event class has - * an identifying hash - */ -public class Event implements IEvent { - - static String eventTypeHash; - - private Log log; - - String address; - - public static String getEventTypeHash() { - return eventTypeHash; - } - - public Log getLog() { - return log; - } - - public String getAddress() { - return address; - } - - public void setLog(Log log) { - this.log = log; - } - - public void setAddress(String address) { - this.address = address; - } - -} diff --git a/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java b/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java deleted file mode 100644 index cd3b6d5..0000000 --- a/src/main/java/io/api/etherscan/model/event/impl/MintEvent.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.model.event.impl; - -import io.api.etherscan.model.event.IEvent; - -public class MintEvent extends Event { - - static final String eventTypeHash = "0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f"; - static { - IEvent.registerEventType(MintEvent.eventTypeHash, MintEvent.class); - } - -} diff --git a/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java b/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java deleted file mode 100644 index 76b4931..0000000 --- a/src/main/java/io/api/etherscan/model/event/impl/SyncEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.api.etherscan.model.event.impl; - -import io.api.etherscan.model.event.IEvent; - -public class SyncEvent extends Event { - - static final String eventTypeHash = "0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"; - static { - IEvent.registerEventType(SyncEvent.eventTypeHash, SyncEvent.class); - } -} diff --git a/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java b/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java deleted file mode 100644 index ce16958..0000000 --- a/src/main/java/io/api/etherscan/model/event/impl/TransferErc20Event.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.api.etherscan.model.event.impl; - -import io.api.etherscan.model.event.IEvent; - -public class TransferErc20Event extends Event { - - static final String eventTypeHash = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; - static { - IEvent.registerEventType(TransferErc20Event.eventTypeHash, TransferErc20Event.class); - } - - String fromAddress; - - String toAddress; - - public String getFromAddress() { - return fromAddress; - } - - public void setFromAddress(String fromAddress) { - this.fromAddress = fromAddress; - } - - public String getToAddress() { - return toAddress; - } - - public void setToAddress(String toAddress) { - this.toAddress = toAddress; - } - -} diff --git a/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java b/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java deleted file mode 100644 index 23036bf..0000000 --- a/src/main/java/io/api/etherscan/model/event/impl/WithdrawEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.api.etherscan.model.event.impl; - -import io.api.etherscan.model.event.IEvent; - -public class WithdrawEvent extends Event { - - static final String eventTypeHash = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65"; - static { - IEvent.registerEventType(WithdrawEvent.eventTypeHash, WithdrawEvent.class); - } -} From 93e6b02e6adbcdf528650e055566d5cf8b2fe377 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 18 Oct 2020 16:02:55 +0300 Subject: [PATCH 15/16] [1.1.0-SNAPSHOT] gradlew permission added --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From ea512d1612ae1f5c9454c80e6082bb3a04b38e71 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Sun, 18 Oct 2020 16:10:17 +0300 Subject: [PATCH 16/16] [1.1.0-SNAPSHOT] FailFast true added [1.1.0-SNAPSHOT] Only java 11 for testing is left [1.1.0-SNAPSHOT] Tests on push disabled for RateLimit not exceed [1.1.0-SNAPSHOT] CI api key env setted [1.1.0-SNAPSHOT] QueueManager 7 sec instead of 6 set [1.1.0-SNAPSHOT] Test fixed with correct address for ABI Debug logging removed PersonalQueue optimized [1.1.0-SNAPSHOT] Key removed [1.1.0-SNAPSHOT] Weak queue set for tests [1.1.0-SNAPSHOT] Rest before timeout check for api not exceed rate --- .github/workflows/gradle.yml | 9 ++++----- build.gradle | 2 ++ .../etherscan/core/impl/BasicProvider.java | 10 ---------- .../etherscan/manager/impl/QueueManager.java | 16 ++++++++-------- src/test/java/io/api/ApiRunner.java | 19 ++++++++++++------- .../io/api/etherscan/EtherScanApiTest.java | 6 ++++-- .../etherscan/contract/ContractApiTest.java | 4 ++-- 7 files changed, 32 insertions(+), 34 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 655e4ad..6e68558 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,10 +1,6 @@ name: Java CI on: - push: - branches: - - master - - dev schedule: - cron: "0 12 1 * *" pull_request: @@ -17,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '1.8', '11' ] + java: [ '11' ] name: Java ${{ matrix.java }} setup steps: @@ -30,9 +26,12 @@ jobs: - name: Build with Gradle run: ./gradlew build jacocoTestReport + env: + API_KEY: ${{ secrets.API_KEY }} - name: Analyze with SonarQube run: ./gradlew sonarqube env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + API_KEY: ${{ secrets.API_KEY }} diff --git a/build.gradle b/build.gradle index a68159c..1d07a61 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,8 @@ dependencies { } test { + failFast = true + useJUnit() testLogging { events "passed", "skipped", "failed" diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index f2be0d2..cf16337 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -7,14 +7,10 @@ import io.api.etherscan.error.RateLimitException; import io.api.etherscan.executor.IHttpExecutor; import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.manager.impl.QueueManager; import io.api.etherscan.model.utility.StringResponseTO; import io.api.etherscan.util.BasicUtils; -import java.time.LocalTime; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Base provider for API Implementations @@ -25,8 +21,6 @@ */ abstract class BasicProvider { - private static final Logger logger = Logger.getLogger(QueueManager.class.getName()); - static final int MAX_END_BLOCK = Integer.MAX_VALUE; static final int MIN_START_BLOCK = 0; @@ -76,9 +70,7 @@ T convert(final String json, final Class tClass) { } String getRequest(final String urlParameters) { - logger.log(Level.SEVERE, "ASKED - " + LocalTime.now()); queue.takeTurn(); - logger.log(Level.SEVERE, "GRANTED - " + LocalTime.now()); final String url = baseUrl + module + urlParameters; final String result = executor.get(url); if (BasicUtils.isEmpty(result)) @@ -88,9 +80,7 @@ String getRequest(final String urlParameters) { } String postRequest(final String urlParameters, final String dataToPost) { - logger.log(Level.SEVERE, "ASKED - " + LocalTime.now()); queue.takeTurn(); - logger.log(Level.SEVERE, "GRANTED - " + LocalTime.now()); final String url = baseUrl + module + urlParameters; return executor.post(url, dataToPost); } 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 8041734..517883c 100644 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java @@ -5,7 +5,7 @@ import java.util.concurrent.*; /** - * Queue implementation With size and reset time as params + * Queue Semaphore implementation with size and reset time as params * * @see IQueueManager * @@ -14,19 +14,19 @@ */ public class QueueManager implements IQueueManager { - public static final QueueManager DEFAULT_KEY_QUEUE = new QueueManager(1, 6); - public static final QueueManager PERSONAL_KEY_QUEUE = new QueueManager(5, 1); + public static final QueueManager DEFAULT_KEY_QUEUE = new QueueManager(1, 7); + public static final QueueManager PERSONAL_KEY_QUEUE = new QueueManager(2, 1); private final Semaphore semaphore; - public QueueManager(int queueSize, int queueResetTimeInSec) { - this(queueSize, queueResetTimeInSec, queueResetTimeInSec); + public QueueManager(int size, int resetInSec) { + this(size, resetInSec, resetInSec); } - public QueueManager(int queueSize, int queueResetTimeInSec, int delayInSec) { - this.semaphore = new Semaphore(queueSize); + public QueueManager(int size, int queueResetTimeInSec, int delayInSec) { + this.semaphore = new Semaphore(size); Executors.newSingleThreadScheduledExecutor() - .scheduleAtFixedRate(releaseLocks(queueSize), delayInSec, queueResetTimeInSec, TimeUnit.SECONDS); + .scheduleAtFixedRate(releaseLocks(size), delayInSec, queueResetTimeInSec, TimeUnit.SECONDS); } @Override diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java index 75b3e4e..6f608d8 100644 --- a/src/test/java/io/api/ApiRunner.java +++ b/src/test/java/io/api/ApiRunner.java @@ -11,21 +11,26 @@ 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; static { final String apiKey = System.getenv("API_KEY"); - final String keyOrDefault = (apiKey == null || apiKey.isEmpty()) + key = (apiKey == null || apiKey.isEmpty()) ? EtherScanApi.DEFAULT_KEY : apiKey; - final QueueManager queue = keyOrDefault.equals(EtherScanApi.DEFAULT_KEY) + final QueueManager queue = key.equals(EtherScanApi.DEFAULT_KEY) ? QueueManager.DEFAULT_KEY_QUEUE - : QueueManager.PERSONAL_KEY_QUEUE; + : new QueueManager(1, 2); - api = new EtherScanApi(keyOrDefault, EthNetwork.MAINNET, queue); - apiRopsten = new EtherScanApi(keyOrDefault, EthNetwork.ROPSTEN, queue); - apiRinkeby = new EtherScanApi(keyOrDefault, EthNetwork.RINKEBY, queue); - apiKovan = new EtherScanApi(keyOrDefault, EthNetwork.KOVAN, queue); + 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); + } + + public static String getKey() { + return key; } 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 c478b59..5071a68 100644 --- a/src/test/java/io/api/etherscan/EtherScanApiTest.java +++ b/src/test/java/io/api/etherscan/EtherScanApiTest.java @@ -13,6 +13,7 @@ import org.junit.Test; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** @@ -75,9 +76,10 @@ public void noTimeoutUnlimitedAwait() { } @Test(expected = ApiTimeoutException.class) - public void timeout() { + public void timeout() throws InterruptedException { + TimeUnit.SECONDS.sleep(5); Supplier supplier = () -> new HttpExecutor(300, 300); - EtherScanApi api = new EtherScanApi(EthNetwork.KOVAN, supplier); + EtherScanApi api = new EtherScanApi(getKey(), EthNetwork.KOVAN, supplier); List blocks = api.account().minedBlocks("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D"); assertNotNull(blocks); } diff --git a/src/test/java/io/api/etherscan/contract/ContractApiTest.java b/src/test/java/io/api/etherscan/contract/ContractApiTest.java index 9d43c5f..6b4d7d8 100644 --- a/src/test/java/io/api/etherscan/contract/ContractApiTest.java +++ b/src/test/java/io/api/etherscan/contract/ContractApiTest.java @@ -34,8 +34,8 @@ public void invalidParamWithError() { @Test public void correctParamWithEmptyExpectedResult() { - Abi abi = getApi().contract().contractAbi("0xBB1bc244D798123fDe783fCc1C72d3Bb8C189413"); + Abi abi = getApi().contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); assertNotNull(abi); - assertFalse(abi.isVerified()); + assertTrue(abi.isVerified()); } }