diff --git a/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/maven-metadata-local.xml b/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/maven-metadata-local.xml index ddc85eb7..0c5034d3 100644 --- a/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/maven-metadata-local.xml +++ b/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/maven-metadata-local.xml @@ -7,12 +7,12 @@ true - 20240707083116 + 20240814140435 jar 1.0-SNAPSHOT - 20240707083116 + 20240814140435 pom diff --git a/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/reticulum-network-stack-1.0-SNAPSHOT.jar b/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/reticulum-network-stack-1.0-SNAPSHOT.jar index a7473ddf..5695fd2a 100644 Binary files a/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/reticulum-network-stack-1.0-SNAPSHOT.jar and b/lib/io/reticulum/reticulum-network-stack/1.0-SNAPSHOT/reticulum-network-stack-1.0-SNAPSHOT.jar differ diff --git a/lib/io/reticulum/reticulum-network-stack/maven-metadata-local.xml b/lib/io/reticulum/reticulum-network-stack/maven-metadata-local.xml index 96f17d67..ef7609de 100644 --- a/lib/io/reticulum/reticulum-network-stack/maven-metadata-local.xml +++ b/lib/io/reticulum/reticulum-network-stack/maven-metadata-local.xml @@ -6,6 +6,6 @@ 1.0-SNAPSHOT - 20240707083116 + 20240814140435 diff --git a/lib/org/ciyam/AT/1.4.2/AT-1.4.2.jar b/lib/org/ciyam/AT/1.4.2/AT-1.4.2.jar new file mode 100644 index 00000000..24a1da57 Binary files /dev/null and b/lib/org/ciyam/AT/1.4.2/AT-1.4.2.jar differ diff --git a/lib/org/ciyam/AT/1.4.2/AT-1.4.2.pom b/lib/org/ciyam/AT/1.4.2/AT-1.4.2.pom new file mode 100644 index 00000000..49cf55e4 --- /dev/null +++ b/lib/org/ciyam/AT/1.4.2/AT-1.4.2.pom @@ -0,0 +1,123 @@ + + 4.0.0 + org.ciyam + AT + 1.4.2 + jar + + + UTF-8 + false + 1.70 + 4.13.2 + 3.13.0 + 3.3.0 + 3.6.3 + 3.2.5 + 3.4.1 + + + + src/main/java + src/test/java + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 11 + 11 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + ${skipTests} + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadoc + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + test-jar + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + test + + + junit + junit + ${junit.version} + test + + + diff --git a/lib/org/ciyam/AT/maven-metadata-local.xml b/lib/org/ciyam/AT/maven-metadata-local.xml index 355c973f..4ace7241 100644 --- a/lib/org/ciyam/AT/maven-metadata-local.xml +++ b/lib/org/ciyam/AT/maven-metadata-local.xml @@ -3,13 +3,14 @@ org.ciyam AT - 1.4.1 + 1.4.2 1.3.7 1.3.8 1.4.0 1.4.1 + 1.4.2 - 20231212092227 + 20240426084210 diff --git a/pom.xml b/pom.xml index 86369dd0..55fa4b36 100644 --- a/pom.xml +++ b/pom.xml @@ -3,62 +3,65 @@ 4.0.0 org.qortal qortal - 4.5.1 + 4.5.2 jar + UTF-8 true 7dc8c6f 0.15.10 1.70 ${maven.build.timestamp} - 1.4.1 + 1.4.2 3.8.0 - 1.11.0 - 2.15.1 - 1.26.1 + 1.12.0 + 2.16.1 + 1.26.2 3.14.0 1.2.2 0.12.3 4.9.10 - 1.62.2 - 33.0.0-jre + 1.65.0 + 33.2.1-jre 2.2 1.2.1 2.5.1 - 74.2 + 75.1 4.12 4.0.1 2.3.9 - 2.41 + 2.42 9.4.54.v20240208 1.1.1 20240303 1.17.2 - 5.10.0 + 5.11.0-M2 1.0.0 - 2.23.0 + 2.23.1 1.5.0-b01 - 3.5.0 - 3.12.1 + 3.6.0 + 3.13.0 3.6.1 - 3.3.0 + 3.4.2 1.1.0 - 2.16.2 + + 3.12.1 0.16 3.3.1 - 3.5.2 - 3.2.5 - UTF-8 + 3.6.0 + 3.3.0 3.25.3 1.5.3 1.17 - 1.7.36 2.0.10 - 5.11.8 + 5.17.14 1.2 1.9 1.18.30 - 2.14.3 + 2.16.1 + 2.0.12 + 4.3.0 + 5.9.2 src/main/java @@ -376,11 +379,6 @@ ${maven-surefire-plugin.version} ${skipTests} - - - --add-opens=java.base/java.lang=ALL-UNNAMED - --add-opens=java.base/java.util=ALL-UNNAMED - @@ -399,11 +397,6 @@ org.apache.maven.plugins maven-dependency-plugin ${maven-dependency-plugin.version} - unpack @@ -417,11 +410,6 @@ com.google.code.maven-replacer-plugin replacer ${replacer.version} - replace @@ -442,6 +430,10 @@ project.local project file:${project.basedir}/lib + + true + always + @@ -453,6 +445,26 @@ + @@ -578,7 +590,17 @@ guava ${guava.version} + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + org.apache.logging.log4j log4j-core @@ -589,24 +611,11 @@ log4j-api ${log4j.version} - - - org.apache.logging.log4j - log4j-slf4j-impl - ${log4j.version} - - - - org.apache.logging.log4j - log4j-jul - ${log4j.version} - - - - org.slf4j - slf4j-api - ${slf4j.version} - + + org.apache.logging.log4j + log4j-jul + ${log4j.version} + javax.servlet @@ -795,97 +804,129 @@ jaxb-runtime ${jaxb-runtime.version} + io.reticulum reticulum-network-stack 1.0-SNAPSHOT - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - org.projectlombok - lombok - ${lombok.version} - provided - - - commons-codec - commons-codec - 1.15 - - - org.apache.commons - commons-collections4 - 4.4 - - - org.msgpack - jackson-dataformat-msgpack - 0.9.3 - - - - - - io.netty - netty-all - 4.1.92.Final - - - org.bouncycastle - bcpkix-jdk15on - ${bouncycastle.version} - - - com.macasaet.fernet - fernet-java8 - 1.4.2 - - - - com.igormaznitsa - jbbp - 2.0.4 - - - com.github.seancfoley - ipaddress - 5.4.0 - - - org.msgpack - msgpack-core - 0.9.6 - - + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + commons-codec + commons-codec + 1.16.1 + + + + org.apache.commons + commons-collections4 + 4.4 + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + + + org.yaml + snakeyaml + 2.2 + + + org.msgpack + jackson-dataformat-msgpack + 0.9.8 + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.bouncycastle + bcpkix-jdk15on + 1.70 + + + com.macasaet.fernet + fernet-java8 + 1.5.0 + + + com.igormaznitsa + jbbp + 2.0.6 + + + io.netty + netty-all + + 5.0.0.Alpha2 + + + com.github.seancfoley + ipaddress + 5.4.2 + + + + org.dizitart + nitrite + ${nitrite.version} + + + org.dizitart + nitrite-mvstore-adapter + ${nitrite.version} + + + com.h2database + h2 + 2.3.230 + test + + + com.h2database + h2-mvstore + 2.3.230 + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.mockito + mockito-junit-jupiter + 5.10.0 + test + + diff --git a/src/main/java/cash/z/wallet/sdk/rpc/Service.java b/src/main/java/cash/z/wallet/sdk/rpc/Service.java index 3ffc0247..87c51509 100644 --- a/src/main/java/cash/z/wallet/sdk/rpc/Service.java +++ b/src/main/java/cash/z/wallet/sdk/rpc/Service.java @@ -5290,7 +5290,7 @@ public final class Service { if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(vendor_)) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, vendor_); } - if (taddrSupport_ != false) { + if (taddrSupport_) { output.writeBool(3, taddrSupport_); } if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(chainName_)) { @@ -5341,7 +5341,7 @@ public final class Service { if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(vendor_)) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, vendor_); } - if (taddrSupport_ != false) { + if (taddrSupport_) { size += com.google.protobuf.CodedOutputStream .computeBoolSize(3, taddrSupport_); } @@ -5729,7 +5729,7 @@ public final class Service { vendor_ = other.vendor_; onChanged(); } - if (other.getTaddrSupport() != false) { + if (other.getTaddrSupport()) { setTaddrSupport(other.getTaddrSupport()); } if (!other.getChainName().isEmpty()) { diff --git a/src/main/java/org/qortal/account/SelfSponsorshipAlgoV1.java b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV1.java index 65092602..de8663a9 100644 --- a/src/main/java/org/qortal/account/SelfSponsorshipAlgoV1.java +++ b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV1.java @@ -340,7 +340,7 @@ public class SelfSponsorshipAlgoV1 { return true; } transactionDataList.removeIf(t -> t.getTimestamp() >= this.snapshotTimestamp); - return transactionDataList.size() == 0; + return transactionDataList.isEmpty(); } private static List fetchTransactions(Repository repository, List txTypes, String address, boolean reverse) throws DataException { diff --git a/src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java index 80c1ba2f..3010b39d 100644 --- a/src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java +++ b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java @@ -344,7 +344,7 @@ public class SelfSponsorshipAlgoV3 { return true; } transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV3); - return transactionDataList.size() == 0; + return transactionDataList.isEmpty(); } private static List fetchTransactions(Repository repository, List txTypes, String address, boolean reverse) throws DataException { diff --git a/src/main/java/org/qortal/api/ApiRequest.java b/src/main/java/org/qortal/api/ApiRequest.java index 59bd791a..8036c8d9 100644 --- a/src/main/java/org/qortal/api/ApiRequest.java +++ b/src/main/java/org/qortal/api/ApiRequest.java @@ -141,7 +141,7 @@ public class ApiRequest { } String resultString = result.toString(); - return resultString.length() > 0 ? resultString.substring(0, resultString.length() - 1) : resultString; + return !resultString.isEmpty() ? resultString.substring(0, resultString.length() - 1) : resultString; } /** diff --git a/src/main/java/org/qortal/api/HTMLParser.java b/src/main/java/org/qortal/api/HTMLParser.java index f1794594..4887cf84 100644 --- a/src/main/java/org/qortal/api/HTMLParser.java +++ b/src/main/java/org/qortal/api/HTMLParser.java @@ -82,7 +82,7 @@ public class HTMLParser { } public static boolean isHtmlFile(String path) { - if (path.endsWith(".html") || path.endsWith(".htm") || path.equals("")) { + if (path.endsWith(".html") || path.endsWith(".htm") || path.isEmpty()) { return true; } return false; diff --git a/src/main/java/org/qortal/api/SearchMode.java b/src/main/java/org/qortal/api/SearchMode.java index 85c1c61a..e095b8b7 100644 --- a/src/main/java/org/qortal/api/SearchMode.java +++ b/src/main/java/org/qortal/api/SearchMode.java @@ -2,5 +2,5 @@ package org.qortal.api; public enum SearchMode { LATEST, - ALL; + ALL } diff --git a/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java b/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java index 7169fd4a..7f4b2f88 100644 --- a/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java +++ b/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java @@ -41,7 +41,7 @@ public class GatewayResource { private ArbitraryResourceStatus getStatus(Service service, String name, String identifier, Boolean build) { // If "build=true" has been specified in the query string, build the resource before returning its status - if (build != null && build == true) { + if (build != null && build) { try { ArbitraryDataReader reader = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, null); if (!reader.isBuilding()) { @@ -80,7 +80,7 @@ public class GatewayResource { private HttpServletResponse parsePath(String inPath, String qdnContext, String secret58, boolean includeResourceIdInPrefix, boolean async) { - if (inPath == null || inPath.equals("")) { + if (inPath == null || inPath.isEmpty()) { // Assume not a real file return ArbitraryDataRenderer.getResponse(response, 404, "Error 404: File Not Found"); } @@ -140,7 +140,7 @@ public class GatewayResource { } String prefix = StringUtils.join(prefixParts, "/"); - if (prefix != null && prefix.length() > 0) { + if (prefix != null && !prefix.isEmpty()) { prefix = "/" + prefix; } diff --git a/src/main/java/org/qortal/api/model/ConnectedPeer.java b/src/main/java/org/qortal/api/model/ConnectedPeer.java index c4198654..996b373f 100644 --- a/src/main/java/org/qortal/api/model/ConnectedPeer.java +++ b/src/main/java/org/qortal/api/model/ConnectedPeer.java @@ -17,7 +17,7 @@ public class ConnectedPeer { public enum Direction { INBOUND, - OUTBOUND; + OUTBOUND } public Direction direction; diff --git a/src/main/java/org/qortal/api/resource/AddressesResource.java b/src/main/java/org/qortal/api/resource/AddressesResource.java index 688b17fc..66d8412c 100644 --- a/src/main/java/org/qortal/api/resource/AddressesResource.java +++ b/src/main/java/org/qortal/api/resource/AddressesResource.java @@ -233,8 +233,7 @@ public class AddressesResource { } } catch (DataException e) { - continue; - } + } } // Sort by level diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 7752be8c..99fc0020 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -119,7 +119,7 @@ public class ArbitraryResource { // Ensure that "default" and "identifier" parameters cannot coexist boolean defaultRes = Boolean.TRUE.equals(defaultResource); - if (defaultRes == true && identifier != null) { + if (defaultRes && identifier != null) { throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "identifier cannot be specified when requesting a default resource"); } @@ -491,7 +491,7 @@ public class ArbitraryResource { List transactionDataList; - if (query == null || query.equals("")) { + if (query == null || query.isEmpty()) { transactionDataList = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository, limit, offset); } else { transactionDataList = ArbitraryDataStorageManager.getInstance().searchHostedTransactions(repository,query, limit, offset); @@ -1258,7 +1258,7 @@ public class ArbitraryResource { } // Finish here if user has requested a preview - if (preview != null && preview == true) { + if (preview != null && preview) { return this.preview(path, service); } diff --git a/src/main/java/org/qortal/api/resource/BlocksResource.java b/src/main/java/org/qortal/api/resource/BlocksResource.java index 22b40a1f..01d8d2ab 100644 --- a/src/main/java/org/qortal/api/resource/BlocksResource.java +++ b/src/main/java/org/qortal/api/resource/BlocksResource.java @@ -86,7 +86,7 @@ public class BlocksResource { // Check the database first BlockData blockData = repository.getBlockRepository().fromSignature(signature); if (blockData != null) { - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } return blockData; @@ -95,7 +95,7 @@ public class BlocksResource { // Not found, so try the block archive blockData = repository.getBlockArchiveRepository().fromSignature(signature); if (blockData != null) { - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } return blockData; @@ -304,7 +304,7 @@ public class BlocksResource { try (final Repository repository = RepositoryManager.getRepository()) { BlockData blockData = repository.getBlockRepository().getLastBlock(); - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } @@ -474,7 +474,7 @@ public class BlocksResource { // Firstly check the database BlockData blockData = repository.getBlockRepository().fromHeight(height); if (blockData != null) { - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } return blockData; @@ -483,7 +483,7 @@ public class BlocksResource { // Not found, so try the archive blockData = repository.getBlockArchiveRepository().fromHeight(height); if (blockData != null) { - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } return blockData; @@ -596,7 +596,7 @@ public class BlocksResource { if (height > 1) { // Found match in Blocks table blockData = repository.getBlockRepository().fromHeight(height); - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } return blockData; @@ -614,7 +614,7 @@ public class BlocksResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN); } - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } @@ -651,7 +651,7 @@ public class BlocksResource { @QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) { try (final Repository repository = RepositoryManager.getRepository()) { List blocks = new ArrayList<>(); - boolean shouldReverse = (reverse != null && reverse == true); + boolean shouldReverse = (reverse != null && reverse); int i = 0; while (i < count) { @@ -664,7 +664,7 @@ public class BlocksResource { break; } } - if (includeOnlineSignatures == null || includeOnlineSignatures == false) { + if (includeOnlineSignatures == null || !includeOnlineSignatures) { blockData.setOnlineAccountsSignatures(null); } diff --git a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java index ff4aee02..3720a0b5 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java @@ -8,7 +8,6 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.qortal.api.ApiError; import org.qortal.api.ApiErrors; @@ -18,7 +17,11 @@ import org.qortal.api.model.crosschain.AddressRequest; import org.qortal.api.model.crosschain.BitcoinSendRequest; import org.qortal.crosschain.AddressInfo; import org.qortal.crosschain.Bitcoin; +import org.qortal.crosschain.ChainableServer; +import org.qortal.crosschain.ElectrumX; import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.ServerConnectionInfo; +import org.qortal.crosschain.ServerInfo; import org.qortal.crosschain.SimpleTransaction; import org.qortal.crosschain.ServerConfigurationInfo; @@ -267,6 +270,181 @@ public class CrossChainBitcoinResource { return CrossChainUtils.buildServerConfigurationInfo(Bitcoin.getInstance()); } + @GET + @Path("/serverconnectionhistory") + @Operation( + summary = "Returns Bitcoin server connection history", + description = "Returns Bitcoin server connection history", + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) ) + ) + } + ) + public List getServerConnectionHistory() { + + return CrossChainUtils.buildServerConnectionHistory(Bitcoin.getInstance()); + } + + @POST + @Path("/addserver") + @Operation( + summary = "Add server to list of Bitcoin servers", + description = "Add server to list of Bitcoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if added, false if not added", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.addServer( Bitcoin.getInstance(), server )) { + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/removeserver") + @Operation( + summary = "Remove server from list of Bitcoin servers", + description = "Remove server from list of Bitcoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if removed, otherwise", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.removeServer( Bitcoin.getInstance(), server ) ) { + + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/setcurrentserver") + @Operation( + summary = "Set current Bitcoin server", + description = "Set current Bitcoin server", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "connection info", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerConnectionInfo.class + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + if( serverInfo.getConnectionType() == null || + serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + try { + return CrossChainUtils.setCurrentServer( Bitcoin.getInstance(), serverInfo ); + } + catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return new ServerConnectionInfo( + serverInfo, + CrossChainUtils.CORE_API_CALL, + true, + false, + System.currentTimeMillis(), + CrossChainUtils.getNotes(e)); + } + } + + @GET @Path("/feekb") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java index d78a4ed9..72b10096 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java @@ -16,8 +16,12 @@ import org.qortal.api.Security; import org.qortal.api.model.crosschain.AddressRequest; import org.qortal.api.model.crosschain.DigibyteSendRequest; import org.qortal.crosschain.AddressInfo; -import org.qortal.crosschain.Digibyte; +import org.qortal.crosschain.ChainableServer; +import org.qortal.crosschain.ElectrumX; import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.Digibyte; +import org.qortal.crosschain.ServerConnectionInfo; +import org.qortal.crosschain.ServerInfo; import org.qortal.crosschain.SimpleTransaction; import org.qortal.crosschain.ServerConfigurationInfo; @@ -266,6 +270,181 @@ public class CrossChainDigibyteResource { return CrossChainUtils.buildServerConfigurationInfo(Digibyte.getInstance()); } + @GET + @Path("/serverconnectionhistory") + @Operation( + summary = "Returns Digibyte server connection history", + description = "Returns Digibyte server connection history", + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) ) + ) + } + ) + public List getServerConnectionHistory() { + + return CrossChainUtils.buildServerConnectionHistory(Digibyte.getInstance()); + } + + @POST + @Path("/addserver") + @Operation( + summary = "Add server to list of Digibyte servers", + description = "Add server to list of Digibyte servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if added, false if not added", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.addServer( Digibyte.getInstance(), server )) { + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/removeserver") + @Operation( + summary = "Remove server from list of Digibyte servers", + description = "Remove server from list of Digibyte servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if removed, otherwise", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.removeServer( Digibyte.getInstance(), server ) ) { + + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/setcurrentserver") + @Operation( + summary = "Set current Digibyte server", + description = "Set current Digibyte server", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "connection info", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerConnectionInfo.class + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + if( serverInfo.getConnectionType() == null || + serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + try { + return CrossChainUtils.setCurrentServer( Digibyte.getInstance(), serverInfo ); + } + catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return new ServerConnectionInfo( + serverInfo, + CrossChainUtils.CORE_API_CALL, + true, + false, + System.currentTimeMillis(), + CrossChainUtils.getNotes(e)); + } + } + + @GET @Path("/feekb") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java index 8575a28d..4aa82e2b 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java @@ -16,8 +16,12 @@ import org.qortal.api.Security; import org.qortal.api.model.crosschain.AddressRequest; import org.qortal.api.model.crosschain.DogecoinSendRequest; import org.qortal.crosschain.AddressInfo; -import org.qortal.crosschain.Dogecoin; +import org.qortal.crosschain.ChainableServer; +import org.qortal.crosschain.ElectrumX; import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.Dogecoin; +import org.qortal.crosschain.ServerConnectionInfo; +import org.qortal.crosschain.ServerInfo; import org.qortal.crosschain.SimpleTransaction; import org.qortal.crosschain.ServerConfigurationInfo; @@ -266,6 +270,181 @@ public class CrossChainDogecoinResource { return CrossChainUtils.buildServerConfigurationInfo(Dogecoin.getInstance()); } + @GET + @Path("/serverconnectionhistory") + @Operation( + summary = "Returns Dogecoin server connection history", + description = "Returns Dogecoin server connection history", + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) ) + ) + } + ) + public List getServerConnectionHistory() { + + return CrossChainUtils.buildServerConnectionHistory(Dogecoin.getInstance()); + } + + @POST + @Path("/addserver") + @Operation( + summary = "Add server to list of Dogecoin servers", + description = "Add server to list of Dogecoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if added, false if not added", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.addServer( Dogecoin.getInstance(), server )) { + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/removeserver") + @Operation( + summary = "Remove server from list of Dogecoin servers", + description = "Remove server from list of Dogecoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if removed, otherwise", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.removeServer( Dogecoin.getInstance(), server ) ) { + + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/setcurrentserver") + @Operation( + summary = "Set current Dogecoin server", + description = "Set current Dogecoin server", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "connection info", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerConnectionInfo.class + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + if( serverInfo.getConnectionType() == null || + serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + try { + return CrossChainUtils.setCurrentServer( Dogecoin.getInstance(), serverInfo ); + } + catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return new ServerConnectionInfo( + serverInfo, + CrossChainUtils.CORE_API_CALL, + true, + false, + System.currentTimeMillis(), + CrossChainUtils.getNotes(e)); + } + } + + @GET @Path("/feekb") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java index 7667eea1..5b9e29d2 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java @@ -16,8 +16,12 @@ import org.qortal.api.Security; import org.qortal.api.model.crosschain.AddressRequest; import org.qortal.api.model.crosschain.LitecoinSendRequest; import org.qortal.crosschain.AddressInfo; +import org.qortal.crosschain.ChainableServer; +import org.qortal.crosschain.ElectrumX; import org.qortal.crosschain.ForeignBlockchainException; import org.qortal.crosschain.Litecoin; +import org.qortal.crosschain.ServerConnectionInfo; +import org.qortal.crosschain.ServerInfo; import org.qortal.crosschain.SimpleTransaction; import org.qortal.crosschain.ServerConfigurationInfo; @@ -266,6 +270,180 @@ public class CrossChainLitecoinResource { return CrossChainUtils.buildServerConfigurationInfo(Litecoin.getInstance()); } + @GET + @Path("/serverconnectionhistory") + @Operation( + summary = "Returns Litecoin server connection history", + description = "Returns Litecoin server connection history", + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) ) + ) + } + ) + public List getServerConnectionHistory() { + + return CrossChainUtils.buildServerConnectionHistory(Litecoin.getInstance()); + } + + @POST + @Path("/addserver") + @Operation( + summary = "Add server to list of Litecoin servers", + description = "Add server to list of Litecoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if added, false if not added", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.addServer( Litecoin.getInstance(), server )) { + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/removeserver") + @Operation( + summary = "Remove server from list of Litecoin servers", + description = "Remove server from list of Litecoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if removed, otherwise", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.removeServer( Litecoin.getInstance(), server ) ) { + + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/setcurrentserver") + @Operation( + summary = "Set current Litecoin server", + description = "Set current Litecoin server", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "connection info", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerConnectionInfo.class + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + if( serverInfo.getConnectionType() == null || + serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + try { + return CrossChainUtils.setCurrentServer( Litecoin.getInstance(), serverInfo ); + } + catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return new ServerConnectionInfo( + serverInfo, + CrossChainUtils.CORE_API_CALL, + true, + false, + System.currentTimeMillis(), + CrossChainUtils.getNotes(e)); + } + } + @POST @Path("/repair") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java index 03ff43b8..c6378f0b 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java @@ -13,8 +13,12 @@ import org.qortal.api.ApiErrors; import org.qortal.api.ApiExceptionFactory; import org.qortal.api.Security; import org.qortal.api.model.crosschain.PirateChainSendRequest; +import org.qortal.crosschain.ChainableServer; import org.qortal.crosschain.ForeignBlockchainException; import org.qortal.crosschain.PirateChain; +import org.qortal.crosschain.PirateLightClient; +import org.qortal.crosschain.ServerConnectionInfo; +import org.qortal.crosschain.ServerInfo; import org.qortal.crosschain.SimpleTransaction; import org.qortal.crosschain.ServerConfigurationInfo; @@ -352,6 +356,180 @@ public class CrossChainPirateChainResource { return CrossChainUtils.buildServerConfigurationInfo(PirateChain.getInstance()); } + @GET + @Path("/serverconnectionhistory") + @Operation( + summary = "Returns Pirate Chain server connection history", + description = "Returns Pirate Chain server connection history", + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) ) + ) + } + ) + public List getServerConnectionHistory() { + + return CrossChainUtils.buildServerConnectionHistory(PirateChain.getInstance()); + } + + @POST + @Path("/addserver") + @Operation( + summary = "Add server to list of Pirate Chain servers", + description = "Add server to list of Pirate Chain servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if added, false if not added", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String addServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + PirateLightClient.Server server = new PirateLightClient.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.addServer( PirateChain.getInstance(), server )) { + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/removeserver") + @Operation( + summary = "Remove server from list of Pirate Chain servers", + description = "Remove server from list of Pirate Chain servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if removed, otherwise", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String removeServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + PirateLightClient.Server server = new PirateLightClient.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.removeServer( PirateChain.getInstance(), server ) ) { + + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/setcurrentserver") + @Operation( + summary = "Set current Pirate Chain server", + description = "Set current Pirate Chain server", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "connection info", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerConnectionInfo.class + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public ServerConnectionInfo setCurrentServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + if( serverInfo.getConnectionType() == null || + serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + try { + return CrossChainUtils.setCurrentServer( PirateChain.getInstance(), serverInfo ); + } + catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return new ServerConnectionInfo( + serverInfo, + CrossChainUtils.CORE_API_CALL, + true, + false, + System.currentTimeMillis(), + CrossChainUtils.getNotes(e)); + } + } + @GET @Path("/feekb") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java index ce5cd668..c06122a9 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java @@ -16,8 +16,12 @@ import org.qortal.api.Security; import org.qortal.api.model.crosschain.AddressRequest; import org.qortal.api.model.crosschain.RavencoinSendRequest; import org.qortal.crosschain.AddressInfo; +import org.qortal.crosschain.ChainableServer; +import org.qortal.crosschain.ElectrumX; import org.qortal.crosschain.ForeignBlockchainException; import org.qortal.crosschain.Ravencoin; +import org.qortal.crosschain.ServerConnectionInfo; +import org.qortal.crosschain.ServerInfo; import org.qortal.crosschain.SimpleTransaction; import org.qortal.crosschain.ServerConfigurationInfo; @@ -266,6 +270,181 @@ public class CrossChainRavencoinResource { return CrossChainUtils.buildServerConfigurationInfo(Ravencoin.getInstance()); } + @GET + @Path("/serverconnectionhistory") + @Operation( + summary = "Returns Ravencoin server connection history", + description = "Returns Ravencoin server connection history", + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) ) + ) + } + ) + public List getServerConnectionHistory() { + + return CrossChainUtils.buildServerConnectionHistory(Ravencoin.getInstance()); + } + + @POST + @Path("/addserver") + @Operation( + summary = "Add server to list of Ravencoin servers", + description = "Add server to list of Ravencoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if added, false if not added", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.addServer( Ravencoin.getInstance(), server )) { + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/removeserver") + @Operation( + summary = "Remove server from list of Ravencoin servers", + description = "Remove server from list of Ravencoin servers", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "true if removed, otherwise", + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + try { + ElectrumX.Server server = new ElectrumX.Server( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + if( CrossChainUtils.removeServer( Ravencoin.getInstance(), server ) ) { + + return "true"; + } + else { + return "false"; + } + } + catch (IllegalArgumentException | NullPointerException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return "false"; + } + } + + @POST + @Path("/setcurrentserver") + @Operation( + summary = "Set current Ravencoin server", + description = "Set current Ravencoin server", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerInfo.class + ) + ) + ), + responses = { + @ApiResponse( + description = "connection info", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = ServerConnectionInfo.class + ) + ) + ) + } + + ) + @ApiErrors({ApiError.INVALID_DATA}) + @SecurityRequirement(name = "apiKey") + public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) { + Security.checkApiCallAllowed(request); + + if( serverInfo.getConnectionType() == null || + serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + try { + return CrossChainUtils.setCurrentServer( Ravencoin.getInstance(), serverInfo ); + } + catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + } + catch (Exception e) { + return new ServerConnectionInfo( + serverInfo, + CrossChainUtils.CORE_API_CALL, + true, + false, + System.currentTimeMillis(), + CrossChainUtils.getNotes(e)); + } + } + + @GET @Path("/feekb") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index d3919d9b..9e411127 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -376,7 +376,7 @@ public class CrossChainResource { int maximumCount = maxtrades != null ? maxtrades : 10; long minimumPeriod = 4 * 60 * 60 * 1000L; // ms Boolean isFinished = Boolean.TRUE; - boolean useInversePrice = (inverse != null && inverse == true); + boolean useInversePrice = (inverse != null && inverse); try (final Repository repository = RepositoryManager.getRepository()) { Map> acctsByCodeHash = SupportedBlockchain.getFilteredAcctMap(foreignBlockchain); diff --git a/src/main/java/org/qortal/api/resource/CrossChainUtils.java b/src/main/java/org/qortal/api/resource/CrossChainUtils.java index b07a9d6c..d1453bda 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainUtils.java +++ b/src/main/java/org/qortal/api/resource/CrossChainUtils.java @@ -9,32 +9,36 @@ import org.bitcoinj.script.ScriptBuilder; import org.qortal.crosschain.*; import org.qortal.data.at.ATData; -import org.qortal.data.crosschain.AtomicTransactionData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.crosschain.TradeBotData; -import org.qortal.data.crosschain.TransactionSummary; +import org.qortal.data.crosschain.*; import org.qortal.repository.DataException; import org.qortal.repository.Repository; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class CrossChainUtils { private static final Logger LOGGER = LogManager.getLogger(CrossChainUtils.class); + public static final String CORE_API_CALL = "Core API Call"; public static ServerConfigurationInfo buildServerConfigurationInfo(Bitcoiny blockchain) { BitcoinyBlockchainProvider blockchainProvider = blockchain.getBlockchainProvider(); + + // the only reason this is called is to ensure the current server is set on the blockchain provider, + // if there is an exception, then ignore it + try { + blockchainProvider.getCurrentHeight(); + } catch (ForeignBlockchainException e) { + LOGGER.warn("Problems getting block height before building server configuration infos"); + } + ChainableServer currentServer = blockchainProvider.getCurrentServer(); return new ServerConfigurationInfo( - buildInfos(blockchainProvider.getServers(), currentServer), + buildInfos(blockchainProvider.getServers(), currentServer).stream() + .sorted(Comparator.comparing(ServerInfo::isCurrent).reversed()) + .collect(Collectors.toList()), buildInfos(blockchainProvider.getRemainingServers(), currentServer), buildInfos(blockchainProvider.getUselessServers(), currentServer) ); @@ -178,6 +182,74 @@ public class CrossChainUtils { return summaries; } + /** + * Add Server + * + * Add foreign blockchain server to list of candidates. + * + * @param bitcoiny the foreign blockchain + * @param server the server + * + * @return true if the add was successful, otherwise false + */ + public static boolean addServer(Bitcoiny bitcoiny, ChainableServer server) { + + return bitcoiny.getBlockchainProvider().addServer(server); + } + + /** + * Remove Server + * + * Remove foreign blockchain server from list of candidates. + * + * @param bitcoiny the foreign blockchain + * @param server the server + * + * @return true if the removal was successful, otherwise false + */ + public static boolean removeServer(Bitcoiny bitcoiny, ChainableServer server){ + + return bitcoiny.getBlockchainProvider().removeServer(server); + } + + /** + * Set Current Server + * + * Set the server to use the intended foreign blockchain. + * + * @param bitcoiny the foreign blockchain + * @param serverInfo the server configuration information + * + * @return the server connection information + */ + public static ServerConnectionInfo setCurrentServer(Bitcoiny bitcoiny, ServerInfo serverInfo) throws ForeignBlockchainException { + + final BitcoinyBlockchainProvider blockchainProvider = bitcoiny.getBlockchainProvider(); + + ChainableServer server = blockchainProvider.getServer( + serverInfo.getHostName(), + ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()), + serverInfo.getPort() + ); + + ChainableServerConnection connection = blockchainProvider.setCurrentServer(server, CORE_API_CALL).get(); + + return new ServerConnectionInfo( + new ServerInfo( + 0, + serverInfo.getHostName(), + serverInfo.getPort(), + serverInfo.getConnectionType(), + connection.isSuccess() + ), + CORE_API_CALL, + true, + connection.isSuccess() , + System.currentTimeMillis(), + connection.getNotes() + ); + } + /** * Get P2Sh From Trade Bot * @@ -417,4 +489,60 @@ public class CrossChainUtils { } return totalInputOut; } + + /** + * Get Notes + * + * Build notes from an exception thrown. + * + * @param e the exception + * + * @return the exception message or the exception class name + */ + public static String getNotes(Exception e) { + return e.getMessage() + " (" + e.getClass().getSimpleName() + ")"; + } + + /** + * Build Server Connection History + * + * @param bitcoiny the foreign blockchain + * + * @return the history of connections from latest to first + */ + public static List buildServerConnectionHistory(Bitcoiny bitcoiny) { + + return bitcoiny.getBlockchainProvider().getServerConnections().stream() + .sorted(Comparator.comparing(ChainableServerConnection::getCurrentTimeMillis).reversed()) + .map( + connection -> new ServerConnectionInfo( + serverToServerInfo( connection.getServer()), + connection.getRequestedBy(), + connection.isOpen(), + connection.isSuccess(), + connection.getCurrentTimeMillis(), + connection.getNotes() + ) + ) + .collect(Collectors.toList()); + } + + /** + * Server To Server Info + * + * Make a server info object from a server object. + * + * @param server the server + * + * @return the server info + */ + private static ServerInfo serverToServerInfo(ChainableServer server) { + + return new ServerInfo( + 0, + server.getHostName(), + server.getPort(), + server.getConnectionType().toString(), + false); + } } \ No newline at end of file diff --git a/src/main/java/org/qortal/api/resource/TransactionsResource.java b/src/main/java/org/qortal/api/resource/TransactionsResource.java index ca2404ea..6e4db30c 100644 --- a/src/main/java/org/qortal/api/resource/TransactionsResource.java +++ b/src/main/java/org/qortal/api/resource/TransactionsResource.java @@ -330,8 +330,8 @@ public class TransactionsResource { public enum ConfirmationStatus { CONFIRMED, UNCONFIRMED, - BOTH; - } + BOTH + } @GET @Path("/search") diff --git a/src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java b/src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java index 4d65956e..db382779 100644 --- a/src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/AdminStatusWebSocket.java @@ -31,7 +31,7 @@ public class AdminStatusWebSocket extends ApiWebSocket implements Listener { return; } - EventBus.INSTANCE.addListener(this::listen); + EventBus.INSTANCE.addListener(this); } @Override diff --git a/src/main/java/org/qortal/api/websocket/BlocksWebSocket.java b/src/main/java/org/qortal/api/websocket/BlocksWebSocket.java index 01235755..a6cf77b1 100644 --- a/src/main/java/org/qortal/api/websocket/BlocksWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/BlocksWebSocket.java @@ -28,7 +28,7 @@ public class BlocksWebSocket extends ApiWebSocket implements Listener { public void configure(WebSocketServletFactory factory) { factory.register(BlocksWebSocket.class); - EventBus.INSTANCE.addListener(this::listen); + EventBus.INSTANCE.addListener(this); } @Override diff --git a/src/main/java/org/qortal/api/websocket/PresenceWebSocket.java b/src/main/java/org/qortal/api/websocket/PresenceWebSocket.java index 510b712f..f1083c66 100644 --- a/src/main/java/org/qortal/api/websocket/PresenceWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/PresenceWebSocket.java @@ -86,7 +86,7 @@ public class PresenceWebSocket extends ApiWebSocket implements Listener { return; } - EventBus.INSTANCE.addListener(this::listen); + EventBus.INSTANCE.addListener(this); } @Override diff --git a/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java b/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java index 7746f8f8..29713aff 100644 --- a/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/TradeBotWebSocket.java @@ -43,7 +43,7 @@ public class TradeBotWebSocket extends ApiWebSocket implements Listener { // No output this time } - EventBus.INSTANCE.addListener(this::listen); + EventBus.INSTANCE.addListener(this); } @Override diff --git a/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java b/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java index 3f61fb98..96257f4a 100644 --- a/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/TradeOffersWebSocket.java @@ -67,7 +67,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener { return; } - EventBus.INSTANCE.addListener(this::listen); + EventBus.INSTANCE.addListener(this); } @Override diff --git a/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java b/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java index ba9a8085..4477d761 100644 --- a/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/TradePresenceWebSocket.java @@ -29,7 +29,7 @@ public class TradePresenceWebSocket extends ApiWebSocket implements Listener { populateCurrentInfo(); - EventBus.INSTANCE.addListener(this::listen); + EventBus.INSTANCE.addListener(this); } @Override diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java index f438cef6..34130742 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java @@ -104,7 +104,7 @@ public class ArbitraryDataBuilder { if (latestPut.getMethod() != Method.PUT) { throw new DataException("Expected PUT but received PATCH"); } - if (transactionDataList.size() == 0) { + if (transactionDataList.isEmpty()) { throw new DataException(String.format("No transactions found for name %s, service %s, " + "identifier: %s, since %d", name, service, this.identifierString(), latestPut.getTimestamp())); } @@ -176,7 +176,7 @@ public class ArbitraryDataBuilder { } private void findLatestSignature() throws DataException { - if (this.transactions.size() == 0) { + if (this.transactions.isEmpty()) { throw new DataException("Unable to find latest signature from empty transaction list"); } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java index 71378461..6e1ca0b9 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java @@ -58,6 +58,9 @@ public class ArbitraryDataFile { public static int SHORT_DIGEST_LENGTH = 8; protected Path filePath; + protected byte[] fileContent; + private boolean useTemporaryFile; + protected String hash58; protected byte[] signature; private ArrayList chunks; @@ -90,8 +93,14 @@ public class ArbitraryDataFile { this.signature = signature; LOGGER.trace(String.format("File digest: %s, size: %d bytes", this.hash58, fileContent.length)); + this.fileContent = fileContent; + this.useTemporaryFile = useTemporaryFile; + } + + public void save() throws DataException { + Path outputFilePath; - if (useTemporaryFile) { + if (this.useTemporaryFile) { try { outputFilePath = Files.createTempFile("qortalRawData", null); outputFilePath.toFile().deleteOnExit(); @@ -149,6 +158,7 @@ public class ArbitraryDataFile { case RAW_DATA: arbitraryDataFile = ArbitraryDataFile.fromRawData(data, signature); + arbitraryDataFile.save(); break; } @@ -324,6 +334,7 @@ public class ArbitraryDataFile { out.flush(); ArbitraryDataFileChunk chunk = new ArbitraryDataFileChunk(out.toByteArray(), this.signature); + chunk.save(); ValidationResult validationResult = chunk.isValid(); if (validationResult == ValidationResult.OK) { this.chunks.add(chunk); @@ -343,7 +354,7 @@ public class ArbitraryDataFile { public boolean join() { // Ensure we have chunks - if (this.chunks != null && this.chunks.size() > 0) { + if (this.chunks != null && !this.chunks.isEmpty()) { // Create temporary path for joined file // Use the user-specified temp dir, as it is deterministic, and is more likely to be located on reusable storage hardware @@ -406,6 +417,10 @@ public class ArbitraryDataFile { } public boolean delete(int attempts) { + if (this.filePath == null) { + return false; + } + // Keep trying to delete the data until it is deleted, or we reach 10 attempts for (int i=0; i 0) { + if (this.chunks != null && !this.chunks.isEmpty()) { Iterator iterator = this.chunks.iterator(); while (iterator.hasNext()) { ArbitraryDataFileChunk chunk = (ArbitraryDataFileChunk) iterator.next(); @@ -467,6 +482,10 @@ public class ArbitraryDataFile { } public byte[] getBytes() { + if (this.fileContent != null) { + return this.fileContent; + } + try { return Files.readAllBytes(this.filePath); } catch (IOException e) { @@ -690,7 +709,7 @@ public class ArbitraryDataFile { } public byte[] chunkHashes() throws DataException { - if (this.chunks != null && this.chunks.size() > 0) { + if (this.chunks != null && !this.chunks.isEmpty()) { // Return null if we only have one chunk, with the same hash as the parent if (Arrays.equals(this.digest(), this.chunks.get(0).digest())) { return null; @@ -717,7 +736,7 @@ public class ArbitraryDataFile { public List chunkHashList() { List chunks = new ArrayList<>(); - if (this.chunks != null && this.chunks.size() > 0) { + if (this.chunks != null && !this.chunks.isEmpty()) { // Return null if we only have one chunk, with the same hash as the parent if (Arrays.equals(this.digest(), this.chunks.get(0).digest())) { return null; @@ -801,7 +820,7 @@ public class ArbitraryDataFile { String outputString = ""; if (this.chunkCount() > 0) { for (ArbitraryDataFileChunk chunk : this.chunks) { - if (outputString.length() > 0) { + if (!outputString.isEmpty()) { outputString = outputString.concat(","); } outputString = outputString.concat(chunk.digest58()); diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java index 09154a32..78a9ee86 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java @@ -73,7 +73,7 @@ public class ArbitraryDataReader { } // If identifier is a blank string, or reserved keyword "default", treat it as null - if (identifier == null || identifier.equals("") || identifier.equals("default")) { + if (identifier == null || identifier.isEmpty() || identifier.equals("default")) { identifier = null; } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java index 9d20df14..5200b5e2 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java @@ -199,7 +199,7 @@ public class ArbitraryDataRenderer { } private String getFilename(String directory, String userPath) { - if (userPath == null || userPath.endsWith("/") || userPath.equals("")) { + if (userPath == null || userPath.endsWith("/") || userPath.isEmpty()) { // Locate index file List indexFiles = ArbitraryDataRenderer.indexFiles(); for (String indexFile : indexFiles) { diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java index 16faf838..232d3416 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java @@ -52,7 +52,7 @@ public class ArbitraryDataResource { this.service = service; // If identifier is a blank string, or reserved keyword "default", treat it as null - if (identifier == null || identifier.equals("") || identifier.equals("default")) { + if (identifier == null || identifier.isEmpty() || identifier.equals("default")) { identifier = null; } this.identifier = identifier; diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java index 9cfc686a..a77442ec 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataTransactionBuilder.java @@ -81,7 +81,7 @@ public class ArbitraryDataTransactionBuilder { this.service = service; // If identifier is a blank string, or reserved keyword "default", treat it as null - if (identifier == null || identifier.equals("") || identifier.equals("default")) { + if (identifier == null || identifier.isEmpty() || identifier.equals("default")) { identifier = null; } this.identifier = identifier; diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java index 0098e4fa..e3dcbad7 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java @@ -78,7 +78,7 @@ public class ArbitraryDataWriter { this.compression = compression; // If identifier is a blank string, or reserved keyword "default", treat it as null - if (identifier == null || identifier.equals("") || identifier.equals("default")) { + if (identifier == null || identifier.isEmpty() || identifier.equals("default")) { identifier = null; } this.identifier = identifier; diff --git a/src/main/java/org/qortal/at/AT.java b/src/main/java/org/qortal/at/AT.java index e5cbf4e7..54356401 100644 --- a/src/main/java/org/qortal/at/AT.java +++ b/src/main/java/org/qortal/at/AT.java @@ -132,7 +132,7 @@ public class AT { // Nothing happened? if (state.getSteps() == 0 && Arrays.equals(stateHash, latestAtStateData.getStateHash())) // We currently want to execute frozen ATs, to maintain backwards support. - if (state.isFrozen() == false) + if (!state.isFrozen()) // this.atStateData will be null return Collections.emptyList(); diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index caaa0c76..bacff5b4 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1086,7 +1086,7 @@ public class Block { // Online accounts should only be included in designated blocks; all others must be empty if (!this.isOnlineAccountsBlock()) { - if (this.blockData.getOnlineAccountsCount() != 0 || accountIndexes.size() != 0) { + if (this.blockData.getOnlineAccountsCount() != 0 || !accountIndexes.isEmpty()) { return ValidationResult.ONLINE_ACCOUNTS_INVALID; } // Not a designated online accounts block and account count is 0. Everything is correct so no need to validate further. diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index 49831cba..91dd12bb 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -159,8 +159,7 @@ public class BlockMinter extends Thread { int level = mintingAccount.getEffectiveMintingLevel(); if (level < BlockChain.getInstance().getMinAccountLevelForBlockSubmissions()) { madi.remove(); - continue; - } + } } // Needs a mutable copy of the unmodifiableList @@ -172,7 +171,7 @@ public class BlockMinter extends Thread { // Disregard peers that don't have a recent block, but only if we're not in recovery mode. // In that mode, we want to allow minting on top of older blocks, to recover stalled networks. - if (Synchronizer.getInstance().getRecoveryMode() == false) + if (!Synchronizer.getInstance().getRecoveryMode()) peers.removeIf(Controller.hasNoRecentBlock); // Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from? @@ -197,7 +196,7 @@ public class BlockMinter extends Thread { // If our latest block isn't recent then we need to synchronize instead of minting, unless we're in recovery mode. if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp) - if (Synchronizer.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false) + if (!Synchronizer.getInstance().getRecoveryMode() && !recoverInvalidBlock) continue; // There are enough peers with a recent block and our latest block is recent diff --git a/src/main/java/org/qortal/controller/ChatNotifier.java b/src/main/java/org/qortal/controller/ChatNotifier.java index abfcee0d..52f5fd02 100644 --- a/src/main/java/org/qortal/controller/ChatNotifier.java +++ b/src/main/java/org/qortal/controller/ChatNotifier.java @@ -17,7 +17,7 @@ public class ChatNotifier { void notify(ChatTransactionData chatTransactionData); } - private Map listenersBySession = new HashMap<>(); + private final Map listenersBySession = new HashMap<>(); private ChatNotifier() { } diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index c1e0e279..5ebcb2cd 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -575,6 +575,34 @@ public class Controller extends Thread { // If GUI is enabled, we're no longer starting up but actually running now Gui.getInstance().notifyRunning(); + + // Check every 10 minutes to see if the block minter is running + Timer timer = new Timer(); + + timer.schedule(new TimerTask() { + @Override + public void run() { + if (blockMinter.isAlive()) { + LOGGER.debug("Block minter is running? {}", blockMinter.isAlive()); + } else if (!blockMinter.isAlive()) { + LOGGER.debug("Block minter is running? {}", blockMinter.isAlive()); + blockMinter.shutdown(); + + try { + // Wait 10 seconds before restart + TimeUnit.SECONDS.sleep(10); + + // Start new block minter thread + LOGGER.info("Restarting block minter"); + blockMinter.start(); + } catch (InterruptedException e) { + // Couldn't start new block minter thread + LOGGER.info("Starting block minter failed {}", e); + throw new RuntimeException(e); + } + } + } + }, 10*60*1000, 10*60*1000); } /** Called by AdvancedInstaller's launch EXE in single-instance mode, when an instance is already running. */ @@ -582,7 +610,6 @@ public class Controller extends Thread { // Return as we don't want to run more than one instance } - // Main thread @Override @@ -800,7 +827,7 @@ public class Controller extends Thread { public static final Predicate hasOldVersion = peer -> { final String minPeerVersion = Settings.getInstance().getMinPeerVersion(); - return peer.isAtLeastVersion(minPeerVersion) == false; + return !peer.isAtLeastVersion(minPeerVersion); }; public static final Predicate hasInvalidSigner = peer -> { @@ -1949,8 +1976,7 @@ public class Controller extends Thread { // Disregard peers that don't have a recent block if (peerChainTipData.getTimestamp() == null || peerChainTipData.getTimestamp() < minLatestBlockTimestamp) { iterator.remove(); - continue; - } + } } return peers; @@ -2030,5 +2056,4 @@ public class Controller extends Thread { public StatsSnapshot getStatsSnapshot() { return this.stats; } - } diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 9518b7f3..d37b2aef 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -538,7 +538,6 @@ public class OnlineAccountsManager { if (++i > 1 + 1) { iterator.remove(); - continue; } } } catch (DataException e) { diff --git a/src/main/java/org/qortal/controller/RestartNode.java b/src/main/java/org/qortal/controller/RestartNode.java index b8d81b85..e7a738aa 100644 --- a/src/main/java/org/qortal/controller/RestartNode.java +++ b/src/main/java/org/qortal/controller/RestartNode.java @@ -2,7 +2,7 @@ package org.qortal.controller; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.qortal.ApplyRestart;; +import org.qortal.ApplyRestart; import org.qortal.globalization.Translator; import org.qortal.gui.SysTray; import org.qortal.repository.RepositoryManager; diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 113b5846..306784f5 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -90,8 +90,8 @@ public class Synchronizer extends Thread { private static Synchronizer instance; public enum SynchronizationResult { - OK, NOTHING_TO_DO, GENESIS_ONLY, NO_COMMON_BLOCK, TOO_DIVERGENT, NO_REPLY, INFERIOR_CHAIN, INVALID_DATA, NO_BLOCKCHAIN_LOCK, REPOSITORY_ISSUE, SHUTTING_DOWN, CHAIN_TIP_TOO_OLD; - } + OK, NOTHING_TO_DO, GENESIS_ONLY, NO_COMMON_BLOCK, TOO_DIVERGENT, NO_REPLY, INFERIOR_CHAIN, INVALID_DATA, NO_BLOCKCHAIN_LOCK, REPOSITORY_ISSUE, SHUTTING_DOWN, CHAIN_TIP_TOO_OLD + } public static class NewChainTipEvent implements Event { private final BlockData priorChainTip; @@ -258,7 +258,7 @@ public class Synchronizer extends Thread { peers.removeIf(Controller.hasNoRecentBlock); final int peersRemoved = peersBeforeComparison - peers.size(); - if (peersRemoved > 0 && peers.size() > 0) + if (peersRemoved > 0 && !peers.isEmpty()) LOGGER.debug(String.format("Ignoring %d peers on inferior chains. Peers remaining: %d", peersRemoved, peers.size())); if (peers.isEmpty()) @@ -392,7 +392,7 @@ public class Synchronizer extends Thread { private boolean checkRecoveryModeForPeers(List qualifiedPeers) { List handshakedPeers = Network.getInstance().getImmutableHandshakedPeers(); - if (handshakedPeers.size() > 0) { + if (!handshakedPeers.isEmpty()) { // There is at least one handshaked peer if (qualifiedPeers.isEmpty()) { // There are no 'qualified' peers - i.e. peers that have a recent block we can sync to @@ -406,7 +406,7 @@ public class Synchronizer extends Thread { // If enough time has passed, enter recovery mode, which lifts some restrictions on who we can sync with and when we can mint long recoveryModeTimeout = Settings.getInstance().getRecoveryModeTimeout(); if (NTP.getTime() - timePeersLastAvailable > recoveryModeTimeout) { - if (recoveryMode == false) { + if (!recoveryMode) { LOGGER.info(String.format("Peers have been unavailable for %d minutes. Entering recovery mode...", recoveryModeTimeout/60/1000)); recoveryMode = true; } @@ -445,7 +445,7 @@ public class Synchronizer extends Thread { try (final Repository repository = RepositoryManager.getRepository()) { try { - if (peers.size() == 0) + if (peers.isEmpty()) return SynchronizationResult.NOTHING_TO_DO; // If our latest block is very old, it's best that we don't try and determine the best peers to sync to. @@ -663,7 +663,7 @@ public class Synchronizer extends Thread { } } - if (useCachedSummaries == false) { + if (!useCachedSummaries) { if (summariesRequired > 0) { LOGGER.trace(String.format("Requesting %d block summar%s from peer %s after common block %.8s. Peer height: %d", summariesRequired, (summariesRequired != 1 ? "ies" : "y"), peer, Base58.encode(commonBlockSummary.getSignature()), peerHeight)); @@ -701,7 +701,7 @@ public class Synchronizer extends Thread { // Reduce minChainLength if needed. If we don't have any blocks, this peer will be excluded from chain weight comparisons later in the process, so we shouldn't update minChainLength List peerBlockSummaries = peer.getCommonBlockData().getBlockSummariesAfterCommonBlock(); - if (peerBlockSummaries != null && peerBlockSummaries.size() > 0) + if (peerBlockSummaries != null && !peerBlockSummaries.isEmpty()) if (peerBlockSummaries.size() < minChainLength) minChainLength = peerBlockSummaries.size(); } @@ -728,7 +728,7 @@ public class Synchronizer extends Thread { // Calculate our chain weight BigInteger ourChainWeight = BigInteger.valueOf(0); - if (ourBlockSummaries.size() > 0) + if (!ourBlockSummaries.isEmpty()) ourChainWeight = Block.calcChainWeight(commonBlockSummary.getHeight(), commonBlockSummary.getSignature(), ourBlockSummaries, maxHeightForChainWeightComparisons); LOGGER.debug(String.format("Our chain weight based on %d blocks is %s", (usingSameLengthChainWeight ? minChainLength : ourBlockSummaries.size()), accurateFormatter.format(ourChainWeight))); @@ -780,7 +780,7 @@ public class Synchronizer extends Thread { } // Now that we have selected the best peers, compare them against each other and remove any with lower weights - if (superiorPeersForComparison.size() > 0) { + if (!superiorPeersForComparison.isEmpty()) { BigInteger bestChainWeight = null; for (Peer peer : superiorPeersForComparison) { // Increase bestChainWeight if needed @@ -1290,7 +1290,7 @@ public class Synchronizer extends Thread { cachedCommonBlockData.setBlockSummariesAfterCommonBlock(null); // If we have already received newer blocks from this peer that what we have already, go ahead and apply them - if (peerBlocks.size() > 0) { + if (!peerBlocks.isEmpty()) { final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock(); final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1); final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp(); @@ -1352,7 +1352,7 @@ public class Synchronizer extends Thread { if (retryCount >= maxRetries) { // If we have already received newer blocks from this peer that what we have already, go ahead and apply them - if (peerBlocks.size() > 0) { + if (!peerBlocks.isEmpty()) { final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock(); final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1); final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp(); diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java index 8611ab88..7b434acb 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java @@ -208,8 +208,7 @@ public class ArbitraryDataCleanupManager extends Thread { Base58.encode(arbitraryTransactionData.getSignature()))); ArbitraryTransactionUtils.convertFileToChunks(arbitraryTransactionData, now, STALE_FILE_TIMEOUT); - continue; - } + } } } catch (DataException e) { @@ -284,8 +283,7 @@ public class ArbitraryDataCleanupManager extends Thread { } } catch (DataException e) { - continue; - } + } } return pathList; diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java index 5ed8df21..1d5e4149 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileListManager.java @@ -605,7 +605,7 @@ public class ArbitraryDataFileListManager { } // Add the chunk hashes - if (arbitraryDataFile.getChunkHashes().size() > 0) { + if (!arbitraryDataFile.getChunkHashes().isEmpty()) { requestedHashes.addAll(arbitraryDataFile.getChunkHashes()); } // Add complete file if there are no hashes @@ -641,7 +641,7 @@ public class ArbitraryDataFileListManager { } // We should only respond if we have at least one hash - if (hashes.size() > 0) { + if (!hashes.isEmpty()) { // Firstly we should keep track of the requesting peer, to allow for potential direct connections later ArbitraryDataFileManager.getInstance().addRecentDataRequest(requestingPeer); diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java index 69a1150a..5836dcd8 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java @@ -43,7 +43,7 @@ public class ArbitraryDataFileManager extends Thread { /** * Map to keep track of hashes that we might need to relay */ - public List arbitraryRelayMap = Collections.synchronizedList(new ArrayList<>()); + public final List arbitraryRelayMap = Collections.synchronizedList(new ArrayList<>()); /** * List to keep track of any arbitrary data file hash responses @@ -53,7 +53,7 @@ public class ArbitraryDataFileManager extends Thread { /** * List to keep track of peers potentially available for direct connections, based on recent requests */ - private List directConnectionInfo = Collections.synchronizedList(new ArrayList<>()); + private final List directConnectionInfo = Collections.synchronizedList(new ArrayList<>()); /** * Map to keep track of peers requesting QDN data that we hold. @@ -242,13 +242,14 @@ public class ArbitraryDataFileManager extends Thread { boolean isRelayRequest = (requestingPeer != null); if (isRelayRequest) { if (!fileAlreadyExists) { - // File didn't exist locally before the request, and it's a forwarding request, so delete it - LOGGER.debug("Deleting file {} because it was needed for forwarding only", Base58.encode(hash)); - - // Keep trying to delete the data until it is deleted, or we reach 10 attempts + // File didn't exist locally before the request, and it's a forwarding request, so delete it if it exists. + // It shouldn't exist on the filesystem yet, but leaving this here just in case. arbitraryDataFile.delete(10); } } + else { + arbitraryDataFile.save(); + } // If this is a metadata file then we need to update the cache if (arbitraryTransactionData != null && arbitraryTransactionData.getMetadataHash() != null) { diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java index 398dcb5b..37801f50 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java @@ -230,8 +230,7 @@ public class ArbitraryDataManager extends Thread { // Remove transactions that we already have local data for if (hasLocalData(arbitraryTransaction)) { iterator.remove(); - continue; - } + } } if (signatures.isEmpty()) { @@ -313,8 +312,7 @@ public class ArbitraryDataManager extends Thread { // Remove transactions that we already have local data for if (hasLocalMetadata(arbitraryTransaction)) { iterator.remove(); - continue; - } + } } if (signatures.isEmpty()) { diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java index f6b2dc0a..91cb9965 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataStorageManager.java @@ -291,7 +291,6 @@ public class ArbitraryDataStorageManager extends Thread { arbitraryTransactionDataList.add(arbitraryTransactionData); } catch (DataException e) { - continue; } } @@ -345,7 +344,6 @@ public class ArbitraryDataStorageManager extends Thread { } } catch (Exception e) { - continue; } } diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java index 9133e86c..993a2b72 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryMetadataManager.java @@ -334,11 +334,17 @@ public class ArbitraryMetadataManager { } ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData; + // Check if the name is blocked + boolean isBlocked = (arbitraryTransactionData == null || ListUtils.isNameBlocked(arbitraryTransactionData.getName())); + + // Save if not blocked + ArbitraryDataFile arbitraryMetadataFile = arbitraryMetadataMessage.getArbitraryMetadataFile(); + if (!isBlocked && arbitraryMetadataFile != null) { + arbitraryMetadataFile.save(); + } + // Forwarding if (isRelayRequest && Settings.getInstance().isRelayModeEnabled()) { - - // Check if the name is blocked - boolean isBlocked = (arbitraryTransactionData == null || ListUtils.isNameBlocked(arbitraryTransactionData.getName())); if (!isBlocked) { Peer requestingPeer = request.getB(); if (requestingPeer != null) { diff --git a/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java b/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java index 698ad487..35981976 100644 --- a/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java +++ b/src/main/java/org/qortal/controller/repository/NamesDatabaseIntegrityCheck.java @@ -207,7 +207,7 @@ public class NamesDatabaseIntegrityCheck { // FUTURE: check database integrity for names that have been updated and then the original name re-registered else if (Objects.equals(updateNameTransactionData.getName(), registeredName)) { String newName = updateNameTransactionData.getNewName(); - if (newName == null || newName.length() == 0) { + if (newName == null || newName.isEmpty()) { // If new name is blank (or maybe null, just to be safe), it means that it stayed the same newName = registeredName; } diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java index 43330b07..3699bd2a 100644 --- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java @@ -724,8 +724,7 @@ public class TradeBot implements Listener { } catch (DataException e) { LOGGER.info("Unable to determine failed state of AT {}", crossChainTradeData.qortalAtAddress); - continue; - } + } } return updatedCrossChainTrades; diff --git a/src/main/java/org/qortal/crosschain/Bitcoiny.java b/src/main/java/org/qortal/crosschain/Bitcoiny.java index 1ae70fe9..7f624e20 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoiny.java +++ b/src/main/java/org/qortal/crosschain/Bitcoiny.java @@ -757,7 +757,7 @@ public abstract class Bitcoiny implements ForeignBlockchain { Address address = Address.fromKey(this.params, keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS), ScriptType.P2PKH); // if zero transactions, return address - if( 0 == getAddressTransactions(ScriptBuilder.createOutputScript(address).getProgram(), true).size() ) + if(getAddressTransactions(ScriptBuilder.createOutputScript(address).getProgram(), true).isEmpty()) return address.toString(); // else try the next receive funds address diff --git a/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java b/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java index 238eff38..1b14a474 100644 --- a/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java +++ b/src/main/java/org/qortal/crosschain/BitcoinyBlockchainProvider.java @@ -3,12 +3,14 @@ package org.qortal.crosschain; import cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock; import java.util.List; +import java.util.Optional; import java.util.Set; public abstract class BitcoinyBlockchainProvider { public static final boolean INCLUDE_UNCONFIRMED = true; public static final boolean EXCLUDE_UNCONFIRMED = false; + public static final String EMPTY = ""; /** Sets the blockchain using this provider instance */ public abstract void setBlockchain(Bitcoiny blockchain); @@ -67,4 +69,62 @@ public abstract class BitcoinyBlockchainProvider { public abstract Set getUselessServers(); public abstract ChainableServer getCurrentServer(); + + /** + * Add Server + * + * Add server to list of candidate servers. + * + * @param server the server + * + * @return true if added, otherwise false + */ + public abstract boolean addServer( ChainableServer server ); + + /** + * Remove Server + * + * Remove server from list of candidate servers. + * + * @param server the server + * + * @return true if removed, otherwise false + */ + public abstract boolean removeServer( ChainableServer server ); + + /** + * Set Current Server + * + * Set server to be used for this foreign blockchain. + * + * @param server the server + * @param requestedBy who requested this setting + * + * @return the connection that was made + * + * @throws ForeignBlockchainException + */ + public abstract Optional setCurrentServer(ChainableServer server, String requestedBy) throws ForeignBlockchainException; + + /** + * Get Server Connections + * + * Get the server connections made to this foreign blockchain, + * + * @return the server connections + */ + public abstract List getServerConnections(); + + /** + * Get Server + * + * Get a server for this foreign blockchain. + * + * @param hostName the host URL + * @param type the type of connection (TCP, SSL) + * @param port the port + * + * @return the server + */ + public abstract ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port); } diff --git a/src/main/java/org/qortal/crosschain/ChainableServerConnection.java b/src/main/java/org/qortal/crosschain/ChainableServerConnection.java new file mode 100644 index 00000000..e680061a --- /dev/null +++ b/src/main/java/org/qortal/crosschain/ChainableServerConnection.java @@ -0,0 +1,71 @@ +package org.qortal.crosschain; + +import java.util.Objects; + +public class ChainableServerConnection { + + private ChainableServer server; + private String requestedBy; + private boolean open; + private boolean success; + private long currentTimeMillis; + private String notes; + + public ChainableServerConnection(ChainableServer server, String requestedBy, boolean open, boolean success, long currentTimeMillis, String notes) { + this.server = server; + this.requestedBy = requestedBy; + this.open = open; + this.success = success; + this.currentTimeMillis = currentTimeMillis; + this.notes = notes; + } + + public ChainableServer getServer() { + return server; + } + + public String getRequestedBy() { + return requestedBy; + } + + public boolean isOpen() { + return open; + } + + public boolean isSuccess() { + return success; + } + + public long getCurrentTimeMillis() { + return currentTimeMillis; + } + + public String getNotes() { + return notes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ChainableServerConnection that = (ChainableServerConnection) o; + return currentTimeMillis == that.currentTimeMillis && Objects.equals(server, that.server); + } + + @Override + public int hashCode() { + return Objects.hash(server, currentTimeMillis); + } + + @Override + public String toString() { + return "ChainableServerConnection{" + + "server=" + server + + ", requestedBy='" + requestedBy + '\'' + + ", open=" + open + + ", success=" + success + + ", currentTimeMillis=" + currentTimeMillis + + ", notes='" + notes + '\'' + + '}'; + } +} diff --git a/src/main/java/org/qortal/crosschain/ChainableServerConnectionRecorder.java b/src/main/java/org/qortal/crosschain/ChainableServerConnectionRecorder.java new file mode 100644 index 00000000..aa26338f --- /dev/null +++ b/src/main/java/org/qortal/crosschain/ChainableServerConnectionRecorder.java @@ -0,0 +1,45 @@ +package org.qortal.crosschain; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class ChainableServerConnectionRecorder { + + private List connections; + private int limit; + + public ChainableServerConnectionRecorder(int limit) { + this.connections = new ArrayList<>(limit); + this.limit = limit; + } + + public ChainableServerConnection recordConnection( + ChainableServer server, String requestedBy, boolean open, boolean success, String notes) { + + ChainableServerConnection connection + = new ChainableServerConnection(server, requestedBy, open, success, System.currentTimeMillis(), notes); + + connections.add(connection); + + if( connections.size() > limit) { + ChainableServerConnection firstConnection + = connections.stream().sorted(Comparator.comparing(ChainableServerConnection::getCurrentTimeMillis)) + .findFirst().get(); + connections.remove(firstConnection); + } + return connection; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public List getConnections() { + return this.connections; + } +} diff --git a/src/main/java/org/qortal/crosschain/ElectrumX.java b/src/main/java/org/qortal/crosschain/ElectrumX.java index 27e140e2..0e70f787 100644 --- a/src/main/java/org/qortal/crosschain/ElectrumX.java +++ b/src/main/java/org/qortal/crosschain/ElectrumX.java @@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; +import org.qortal.api.resource.CrossChainUtils; import org.qortal.crypto.Crypto; import org.qortal.crypto.TrustlessSSLSocketFactory; import org.qortal.utils.BitTwiddling; @@ -26,6 +27,7 @@ import java.util.regex.Pattern; /** ElectrumX network support for querying Bitcoiny-related info like block headers, transaction outputs, etc. */ public class ElectrumX extends BitcoinyBlockchainProvider { + public static final String NULL_RESPONSE_FROM_ELECTRUM_X_SERVER = "Null response from ElectrumX server"; private static final Logger LOGGER = LogManager.getLogger(ElectrumX.class); private static final Random RANDOM = new Random(); @@ -44,6 +46,10 @@ public class ElectrumX extends BitcoinyBlockchainProvider { private static final int RESPONSE_TIME_READINGS = 5; private static final long MAX_AVG_RESPONSE_TIME = 2000L; // ms + public static final String MINIMUM_VERSION_ERROR = "MINIMUM VERSION ERROR"; + public static final String EXPECTED_GENESIS_ERROR = "EXPECTED GENESIS ERROR"; + + private ChainableServerConnectionRecorder recorder = new ChainableServerConnectionRecorder(100); public static class Server implements ChainableServer { String hostname; @@ -136,7 +142,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider { private int nextId = 1; private static final int TX_CACHE_SIZE = 1000; - @SuppressWarnings("serial") + private final Map transactionCache = Collections.synchronizedMap(new LinkedHashMap<>(TX_CACHE_SIZE + 1, 0.75F, true) { // This method is called just after a new entry has been added @Override @@ -216,10 +222,10 @@ public class ElectrumX extends BitcoinyBlockchainProvider { if (!(countObj instanceof Long) || !(hexObj instanceof String)) throw new ForeignBlockchainException.NetworkException("Missing/invalid 'count' or 'hex' entries in JSON from ElectrumX blockchain.block.headers RPC"); - Long returnedCount = (Long) countObj; + long returnedCount = (Long) countObj; String hex = (String) hexObj; - List rawBlockHeaders = new ArrayList<>(returnedCount.intValue()); + List rawBlockHeaders = new ArrayList<>((int) returnedCount); byte[] raw = HashCode.fromString(hex).asBytes(); @@ -421,7 +427,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider { Server uselessServer = (Server) e.getServer(); LOGGER.trace(() -> String.format("Server %s doesn't support verbose transactions - barring use of that server", uselessServer)); this.uselessServers.add(uselessServer); - this.closeServer(uselessServer); + this.closeServer(uselessServer, this.getClass().getSimpleName(), CrossChainUtils.getNotes(e)); continue; } @@ -495,12 +501,13 @@ public class ElectrumX extends BitcoinyBlockchainProvider { // Update: it turns out that they were just using a different key - "address" instead of "addresses" // The code below can remain in place, just in case a peer returns a missing address in the future if (addresses == null || addresses.isEmpty()) { + final String message = String.format("No output addresses returned for transaction %s", txHash); if (this.currentServer != null) { this.uselessServers.add(this.currentServer); - this.closeServer(this.currentServer); + this.closeServer(this.currentServer, this.getClass().getSimpleName(), message); } LOGGER.info("No output addresses returned for transaction {}", txHash); - throw new ForeignBlockchainException(String.format("No output addresses returned for transaction %s", txHash)); + throw new ForeignBlockchainException(message); } outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses)); @@ -585,7 +592,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider { Object peers = this.connectedRpc("server.peers.subscribe"); - for (Object rawPeer : (JSONArray) peers) { + for (Object rawPeer : (JSONArray) Objects.requireNonNull(peers)) { JSONArray peer = (JSONArray) rawPeer; if (peer.size() < 3) // We're expecting at least 3 fields for each peer entry: IP, hostname, features @@ -654,8 +661,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider { if (!this.remainingServers.isEmpty()) { long averageResponseTime = this.currentServer.averageResponseTime(); if (averageResponseTime > MAX_AVG_RESPONSE_TIME) { - LOGGER.info("Slow average response time {}ms from {} - trying another server...", averageResponseTime, this.currentServer.getHostName()); - this.closeServer(); + String message = String.format("Slow average response time %dms from %s - trying another server...", averageResponseTime, this.currentServer.getHostName()); + LOGGER.info(message); + this.closeServer(this.getClass().getSimpleName(), message); break; } } @@ -663,8 +671,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider { if (response != null) return response; + LOGGER.info(NULL_RESPONSE_FROM_ELECTRUM_X_SERVER); // Didn't work, try another server... - this.closeServer(); + this.closeServer(this.getClass().getSimpleName(), NULL_RESPONSE_FROM_ELECTRUM_X_SERVER); } // Failed to perform RPC - maybe lack of servers? @@ -680,56 +689,63 @@ public class ElectrumX extends BitcoinyBlockchainProvider { while (!this.remainingServers.isEmpty()) { ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size())); - LOGGER.trace(() -> String.format("Connecting to %s", server)); - - try { - SocketAddress endpoint = new InetSocketAddress(server.getHostName(), server.getPort()); - int timeout = 5000; // ms - - this.socket = new Socket(); - this.socket.connect(endpoint, timeout); - this.socket.setTcpNoDelay(true); - - if (server.getConnectionType() == Server.ConnectionType.SSL) { - SSLSocketFactory factory = TrustlessSSLSocketFactory.getSocketFactory(); - this.socket = factory.createSocket(this.socket, server.getHostName(), server.getPort(), true); - } - - this.scanner = new Scanner(this.socket.getInputStream()); - this.scanner.useDelimiter("\n"); - - // All connections need to start with a version negotiation - this.connectedRpc("server.version"); - - // Check connection is suitable by asking for server features, including genesis block hash - JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features"); - - if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION) - continue; - - if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash)) - continue; - - // Ask for more servers - Set moreServers = serverPeersSubscribe(); - // Discard duplicate servers we already know - moreServers.removeAll(this.servers); - // Add to both lists - this.remainingServers.addAll(moreServers); - this.servers.addAll(moreServers); - - LOGGER.debug(() -> String.format("Connected to %s", server)); - this.currentServer = server; - return true; - } catch (IOException | ForeignBlockchainException | ClassCastException | NullPointerException e) { - // Didn't work, try another server... - closeServer(); - } + Optional chainableServerConnection = makeConnection(server, this.getClass().getSimpleName()); + if(chainableServerConnection.isPresent() && chainableServerConnection.get().isSuccess() ) return true; } return false; } + private Optional makeConnection(ChainableServer server, String requestedBy) { + LOGGER.info(() -> String.format("Connecting to %s", server)); + + try { + SocketAddress endpoint = new InetSocketAddress(server.getHostName(), server.getPort()); + int timeout = 5000; // ms + + this.socket = new Socket(); + this.socket.connect(endpoint, timeout); + this.socket.setTcpNoDelay(true); + + if (server.getConnectionType() == Server.ConnectionType.SSL) { + SSLSocketFactory factory = TrustlessSSLSocketFactory.getSocketFactory(); + this.socket = factory.createSocket(this.socket, server.getHostName(), server.getPort(), true); + } + + this.scanner = new Scanner(this.socket.getInputStream()); + this.scanner.useDelimiter("\n"); + + // All connections need to start with a version negotiation + this.connectedRpc("server.version"); + + // Check connection is suitable by asking for server features, including genesis block hash + JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features"); + + if (featuresJson == null || Double.parseDouble((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION) + return Optional.of( recorder.recordConnection(server, requestedBy, true, false, MINIMUM_VERSION_ERROR) ); + + if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash)) + return Optional.of( recorder.recordConnection(server, requestedBy, true, false, EXPECTED_GENESIS_ERROR) ); + + // Ask for more servers + Set moreServers = serverPeersSubscribe(); + + // Discard duplicate servers we already know + moreServers.removeAll(this.servers); + + // Add all servers to both lists + this.remainingServers.addAll(moreServers); + this.servers.addAll(moreServers); + + LOGGER.info(() -> String.format("Connected to %s", server)); + this.currentServer = server; + return Optional.of( this.recorder.recordConnection( server, requestedBy, true, true, EMPTY) ); + } catch (IOException | ForeignBlockchainException | ClassCastException | NullPointerException e) { + // Didn't work, try another server... + return Optional.of( this.recorder.recordConnection( server, requestedBy, true, false, CrossChainUtils.getNotes(e))); + } + } + /** * Perform RPC using currently connected server. *

@@ -846,12 +862,19 @@ public class ElectrumX extends BitcoinyBlockchainProvider { /** * Closes connection to server if it is currently connected server. + * * @param server + * @param notes */ - private void closeServer(ChainableServer server) { + private Optional closeServer(ChainableServer server, String requestedBy, String notes) { + + ChainableServerConnection chainableServerConnection; + synchronized (this.serverLock) { if (this.currentServer == null || !this.currentServer.equals(server)) - return; + return Optional.empty(); + + chainableServerConnection = this.recorder.recordConnection(server, requestedBy, false, true, notes); if (this.socket != null && !this.socket.isClosed()) try { @@ -864,12 +887,14 @@ public class ElectrumX extends BitcoinyBlockchainProvider { this.scanner = null; this.currentServer = null; } + + return Optional.of( chainableServerConnection ); } /** Closes connection to currently connected server (if any). */ - private void closeServer() { + private Optional closeServer(String requestedBy, String notes) { synchronized (this.serverLock) { - this.closeServer(this.currentServer); + return this.closeServer(this.currentServer, requestedBy, notes); } } @@ -893,4 +918,32 @@ public class ElectrumX extends BitcoinyBlockchainProvider { public ChainableServer getCurrentServer() { return currentServer; } + + @Override + public boolean addServer(ChainableServer server) { + return this.servers.add(server); + } + + @Override + public boolean removeServer(ChainableServer server) { + boolean removedServer = this.servers.remove(server); + boolean removedRemaining = this.remainingServers.remove(server); + + return removedServer || removedRemaining; + } + + @Override + public Optional setCurrentServer(ChainableServer server, String requestedBy) { + return this.makeConnection(server, requestedBy); + } + + @Override + public List getServerConnections() { + return this.recorder.getConnections(); + } + + @Override + public ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port) { + return new ElectrumX.Server(hostName, type, port); + } } diff --git a/src/main/java/org/qortal/crosschain/PirateLightClient.java b/src/main/java/org/qortal/crosschain/PirateLightClient.java index ae7c3cc1..4bb94ecb 100644 --- a/src/main/java/org/qortal/crosschain/PirateLightClient.java +++ b/src/main/java/org/qortal/crosschain/PirateLightClient.java @@ -14,6 +14,7 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; +import org.qortal.api.resource.CrossChainUtils; import org.qortal.settings.Settings; import org.qortal.transform.TransformationException; @@ -127,6 +128,8 @@ public class PirateLightClient extends BitcoinyBlockchainProvider { } }); + private ChainableServerConnectionRecorder recorder = new ChainableServerConnectionRecorder(100); + // Constructors public PirateLightClient(String netId, String genesisHash, Collection initialServerList, Map defaultPorts) { @@ -443,12 +446,13 @@ public class PirateLightClient extends BitcoinyBlockchainProvider { // Update: it turns out that they were just using a different key - "address" instead of "addresses" // The code below can remain in place, just in case a peer returns a missing address in the future if (addresses == null || addresses.isEmpty()) { + final String message = String.format("No output addresses returned for transaction %s", txHash); if (this.currentServer != null) { this.uselessServers.add(this.currentServer); - this.closeServer(this.currentServer); + this.closeServer(this.currentServer, message, this.getClass().getSimpleName()); } - LOGGER.info("No output addresses returned for transaction {}", txHash); - throw new ForeignBlockchainException(String.format("No output addresses returned for transaction %s", txHash)); + LOGGER.info(message); + throw new ForeignBlockchainException(message); } outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses)); @@ -557,6 +561,42 @@ public class PirateLightClient extends BitcoinyBlockchainProvider { @Override public ChainableServer getCurrentServer() { return this.currentServer; } + @Override + public boolean addServer(ChainableServer server) { + return this.servers.add(server); + } + + @Override + public boolean removeServer(ChainableServer server) { + boolean removedServer = this.servers.remove(server); + boolean removedRemaining = this.remainingServers.remove(server); + + return removedServer || removedRemaining; + } + + @Override + public Optional setCurrentServer(ChainableServer server, String requestedBy) throws ForeignBlockchainException { + + closeServer( requestedBy, "Connecting to different server by request." ); + Optional connection = makeConnection(server, requestedBy); + + if( !connection.isPresent() || !connection.get().isSuccess() ) { + haveConnection(); + } + + return connection; + } + + @Override + public List getServerConnections() { + return this.recorder.getConnections(); + } + + @Override + public ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port) { + return new PirateLightClient.Server(hostName, type, port); + } + // Class-private utility methods @@ -576,8 +616,9 @@ public class PirateLightClient extends BitcoinyBlockchainProvider { if (!this.remainingServers.isEmpty()) { long averageResponseTime = this.currentServer.averageResponseTime(); if (averageResponseTime > MAX_AVG_RESPONSE_TIME) { - LOGGER.info("Slow average response time {}ms from {} - trying another server...", averageResponseTime, this.currentServer.getHostName()); - this.closeServer(); + String message = String.format("Slow average response time %dms from %s - trying another server...", averageResponseTime, this.currentServer.getHostName()); + LOGGER.info(message); + this.closeServer(this.getClass().getSimpleName(), message); continue; } } @@ -601,18 +642,27 @@ public class PirateLightClient extends BitcoinyBlockchainProvider { while (!this.remainingServers.isEmpty()) { ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size())); - LOGGER.trace(() -> String.format("Connecting to %s", server)); - try { - this.channel = ManagedChannelBuilder.forAddress(server.getHostName(), server.getPort()).build(); + Optional chainableServerConnection = makeConnection(server, this.getClass().getSimpleName()); + if( chainableServerConnection.isPresent() && chainableServerConnection.get().isSuccess() ) return true; + } - CompactTxStreamerGrpc.CompactTxStreamerBlockingStub stub = CompactTxStreamerGrpc.newBlockingStub(this.channel); - LightdInfo lightdInfo = stub.getLightdInfo(Empty.newBuilder().build()); + return false; + } - if (lightdInfo == null || lightdInfo.getBlockHeight() <= 0) - continue; + private Optional makeConnection(ChainableServer server, String requestedBy) { + LOGGER.info(() -> String.format("Connecting to %s", server)); - // TODO: find a way to verify that the server is using the expected chain + try { + this.channel = ManagedChannelBuilder.forAddress(server.getHostName(), server.getPort()).build(); + + CompactTxStreamerGrpc.CompactTxStreamerBlockingStub stub = CompactTxStreamerGrpc.newBlockingStub(this.channel); + LightdInfo lightdInfo = stub.getLightdInfo(Empty.newBuilder().build()); + + if (lightdInfo == null || lightdInfo.getBlockHeight() <= 0) + return Optional.of( this.recorder.recordConnection(server, requestedBy,true, false, "lightd info issues") ); + + // TODO: find a way to verify that the server is using the expected chain // if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION) // continue; @@ -620,28 +670,31 @@ public class PirateLightClient extends BitcoinyBlockchainProvider { // if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash)) // continue; - LOGGER.debug(() -> String.format("Connected to %s", server)); - this.currentServer = server; - return true; - } catch (Exception e) { - // Didn't work, try another server... - closeServer(); - } + LOGGER.info(() -> String.format("Connected to %s", server)); + this.currentServer = server; + return Optional.of( this.recorder.recordConnection(server, requestedBy,true, true, EMPTY) ); + } catch (Exception e) { + // Didn't work, try another server... + return Optional.of( this.recorder.recordConnection( server, requestedBy, true, false, CrossChainUtils.getNotes(e))); } - - return false; } - /** * Closes connection to server if it is currently connected server. + * * @param server + * @param requestedBy */ - private void closeServer(ChainableServer server) { + private Optional closeServer(ChainableServer server, String notes, String requestedBy) { + + final ChainableServerConnection connection; + synchronized (this.serverLock) { if (this.currentServer == null || !this.currentServer.equals(server) || this.channel == null) { - return; + return Optional.empty(); } + connection = this.recorder.recordConnection(server, requestedBy, false, true, notes); + // Close the gRPC managed-channel if not shut down already. if (!this.channel.isShutdown()) { try { @@ -669,12 +722,14 @@ public class PirateLightClient extends BitcoinyBlockchainProvider { this.channel = null; this.currentServer = null; } + + return Optional.of( connection ); } /** Closes connection to currently connected server (if any). */ - private void closeServer() { + private Optional closeServer(String requestedBy, String notes) { synchronized (this.serverLock) { - this.closeServer(this.currentServer); + return this.closeServer(this.currentServer, notes, requestedBy); } } diff --git a/src/main/java/org/qortal/crosschain/ServerConnectionInfo.java b/src/main/java/org/qortal/crosschain/ServerConnectionInfo.java new file mode 100644 index 00000000..c4829399 --- /dev/null +++ b/src/main/java/org/qortal/crosschain/ServerConnectionInfo.java @@ -0,0 +1,82 @@ +package org.qortal.crosschain; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import java.util.Objects; + +@XmlAccessorType(XmlAccessType.FIELD) +public class ServerConnectionInfo { + + private ServerInfo serverInfo; + + private String requestedBy; + + private boolean open; + + private boolean success; + + private long timeInMillis; + + private String notes; + + public ServerConnectionInfo() { + } + + public ServerConnectionInfo(ServerInfo serverInfo, String requestedBy, boolean open, boolean success, long timeInMillis, String notes) { + this.serverInfo = serverInfo; + this.requestedBy = requestedBy; + this.open = open; + this.success = success; + this.timeInMillis = timeInMillis; + this.notes = notes; + } + + public ServerInfo getServerInfo() { + return serverInfo; + } + + public String getRequestedBy() { + return requestedBy; + } + + public boolean isOpen() { + return open; + } + + public boolean isSuccess() { + return success; + } + + public long getTimeInMillis() { + return timeInMillis; + } + + public String getNotes() { + return notes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServerConnectionInfo that = (ServerConnectionInfo) o; + return timeInMillis == that.timeInMillis && Objects.equals(serverInfo, that.serverInfo); + } + + @Override + public int hashCode() { + return Objects.hash(serverInfo, timeInMillis); + } + + @Override + public String toString() { + return "ServerConnectionInfo{" + + "serverInfo=" + serverInfo + + ", requestedBy='" + requestedBy + '\'' + + ", open=" + open + + ", success=" + success + + ", timeInMillis=" + timeInMillis + + ", notes='" + notes + '\'' + + '}'; + } +} diff --git a/src/main/java/org/qortal/data/transaction/ArbitraryTransactionData.java b/src/main/java/org/qortal/data/transaction/ArbitraryTransactionData.java index 6e55e280..f3828de8 100644 --- a/src/main/java/org/qortal/data/transaction/ArbitraryTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/ArbitraryTransactionData.java @@ -25,8 +25,8 @@ public class ArbitraryTransactionData extends TransactionData { // "data" field types public enum DataType { RAW_DATA, - DATA_HASH; - } + DATA_HASH + } // Methods public enum Method { diff --git a/src/main/java/org/qortal/gui/SysTray.java b/src/main/java/org/qortal/gui/SysTray.java index 74a68618..67c68839 100644 --- a/src/main/java/org/qortal/gui/SysTray.java +++ b/src/main/java/org/qortal/gui/SysTray.java @@ -4,7 +4,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.controller.Controller; import org.qortal.globalization.Translator; -import org.qortal.settings.Settings; import org.qortal.utils.URLViewer; import javax.swing.*; @@ -17,14 +16,11 @@ import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; import java.io.IOException; import java.io.InputStream; -import java.net.InetSocketAddress; import java.net.URL; -import java.nio.channels.SocketChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/src/main/java/org/qortal/network/Handshake.java b/src/main/java/org/qortal/network/Handshake.java index 221e5e74..782ca2b7 100644 --- a/src/main/java/org/qortal/network/Handshake.java +++ b/src/main/java/org/qortal/network/Handshake.java @@ -70,15 +70,15 @@ public enum Handshake { peer.setPeersVersion(versionString, version); // Ensure the peer is running at least the version specified in MIN_PEER_VERSION - if (peer.isAtLeastVersion(MIN_PEER_VERSION) == false) { + if (!peer.isAtLeastVersion(MIN_PEER_VERSION)) { LOGGER.debug(String.format("Ignoring peer %s because it is on an old version (%s)", peer, versionString)); return null; } - if (Settings.getInstance().getAllowConnectionsWithOlderPeerVersions() == false) { + if (!Settings.getInstance().getAllowConnectionsWithOlderPeerVersions()) { // Ensure the peer is running at least the minimum version allowed for connections final String minPeerVersion = Settings.getInstance().getMinPeerVersion(); - if (peer.isAtLeastVersion(minPeerVersion) == false) { + if (!peer.isAtLeastVersion(minPeerVersion)) { LOGGER.debug(String.format("Ignoring peer %s because it is on an old version (%s)", peer, versionString)); return null; } diff --git a/src/main/java/org/qortal/network/Network.java b/src/main/java/org/qortal/network/Network.java index b42ab450..0e5885ad 100644 --- a/src/main/java/org/qortal/network/Network.java +++ b/src/main/java/org/qortal/network/Network.java @@ -810,7 +810,7 @@ public class Network { .filter(peer -> peer.hasReachedMaxConnectionAge()) .collect(Collectors.toList()); - if (peersToDisconnect != null && peersToDisconnect.size() > 0) { + if (peersToDisconnect != null && !peersToDisconnect.isEmpty()) { for (Peer peer : peersToDisconnect) { LOGGER.debug("Forcing disconnection of peer {} because connection age ({} ms) " + "has reached the maximum ({} ms)", peer, peer.getConnectionAge(), peer.getMaxConnectionAge()); diff --git a/src/main/java/org/qortal/network/Peer.java b/src/main/java/org/qortal/network/Peer.java index 821f368c..fc28ea87 100644 --- a/src/main/java/org/qortal/network/Peer.java +++ b/src/main/java/org/qortal/network/Peer.java @@ -859,7 +859,7 @@ public class Peer { } } - if (logStats && this.receivedMessageStats.size() > 0) { + if (logStats && !this.receivedMessageStats.isEmpty()) { StringBuilder statsBuilder = new StringBuilder(1024); statsBuilder.append("peer ").append(this).append(" message stats:\n=received="); appendMessageStats(statsBuilder, this.receivedMessageStats); diff --git a/src/main/java/org/qortal/network/RNSNetwork.java b/src/main/java/org/qortal/network/RNSNetwork.java index bac63f9f..35a6895c 100644 --- a/src/main/java/org/qortal/network/RNSNetwork.java +++ b/src/main/java/org/qortal/network/RNSNetwork.java @@ -27,7 +27,6 @@ import static io.reticulum.identity.IdentityKnownDestination.recall; import static io.reticulum.utils.IdentityUtils.concatArrays; //import static io.reticulum.constant.ReticulumConstant.TRUNCATED_HASHLENGTH; import static io.reticulum.constant.ReticulumConstant.CONFIG_FILE_NAME; -import lombok.extern.slf4j.Slf4j; import lombok.Data; //import lombok.Setter; //import lombok.Getter; @@ -61,6 +60,11 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.codec.binary.Hex; +// logging +import lombok.extern.slf4j.Slf4j; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; + @Data @Slf4j public class RNSNetwork { @@ -68,8 +72,8 @@ public class RNSNetwork { Reticulum reticulum; //private static final String APP_NAME = "qortal"; static final String APP_NAME = RNSCommon.APP_NAME; - //static final String defaultConfigPath = new String(".reticulum"); // if empty will look in Reticulums default paths - static final String defaultConfigPath = RNSCommon.defaultRNSConfigPath; + static final String defaultConfigPath = new String(".reticulum"); // if empty will look in Reticulums default paths + //static final String defaultConfigPath = RNSCommon.defaultRNSConfigPath; //private final String defaultConfigPath = Settings.getInstance().getDefaultRNSConfigPathForReticulum(); private static Integer MAX_PEERS = 12; //private final Integer MAX_PEERS = Settings.getInstance().getMaxReticulumPeers(); @@ -86,16 +90,27 @@ public class RNSNetwork { //private volatile boolean isShuttingDown = false; //private int totalThreadCount = 0; //// TODO: settings - MaxReticulumPeers, MaxRNSNetworkThreadPoolSize (if needed) + + //private static final Logger logger = LoggerFactory.getLogger(RNSNetwork.class); // Constructor private RNSNetwork () { + log.info("RNSNetwork constructor"); try { + //String configPath = new java.io.File(defaultConfigPath).getCanonicalPath(); + log.info("creating config from {}", defaultConfigPath); initConfig(defaultConfigPath); + //reticulum = new Reticulum(configPath); reticulum = new Reticulum(defaultConfigPath); - log.info("reticulum instance created: {}", reticulum.toString()); + var identitiesPath = reticulum.getStoragePath().resolve("identities"); + if (Files.notExists(identitiesPath)) { + Files.createDirectories(identitiesPath); + } } catch (IOException e) { log.error("unable to create Reticulum network", e); } + log.info("reticulum instance created"); + log.info("reticulum instance created: {}", reticulum); // Settings.getInstance().getMaxRNSNetworkThreadPoolSize(), // statically set to 5 below //ExecutorService RNSNetworkExecutor = new ThreadPoolExecutor(1, @@ -167,7 +182,20 @@ public class RNSNetwork { //rnsNetworkEPC.start(); } - //@Synchronized + private void initConfig(String configDir) throws IOException { + File configDir1 = new File(defaultConfigPath); + if (!configDir1.exists()) { + configDir1.mkdir(); + } + var configPath = Path.of(configDir1.getAbsolutePath()); + Path configFile = configPath.resolve(CONFIG_FILE_NAME); + + if (Files.notExists(configFile)) { + var defaultConfig = this.getClass().getClassLoader().getResourceAsStream("reticulum_default_config.yml"); + Files.copy(defaultConfig, configFile, StandardCopyOption.REPLACE_EXISTING); + } + } + public void shutdown() { isShuttingDown = true; log.info("shutting down Reticulum"); @@ -194,28 +222,21 @@ public class RNSNetwork { } // gracefully close links of peers that point to us for (Link l: incomingLinks) { - var data = concatArrays("close::".getBytes(UTF_8),l.getDestination().getHash()); - Packet closePacket = new Packet(l, data); - var packetReceipt = closePacket.send(); - packetReceipt.setTimeout(3L); - packetReceipt.setDeliveryCallback(this::closePacketDelivered); - packetReceipt.setTimeoutCallback(this::packetTimedOut); + sendCloseToRemote(l); } // Note: we still need to get the packet timeout callback to work... reticulum.exitHandler(); } - private void initConfig(String configDir) throws IOException { - File configDir1 = new File(defaultConfigPath); - if (!configDir1.exists()) { - configDir1.mkdir(); - } - var configPath = Path.of(configDir1.getAbsolutePath()); - Path configFile = configPath.resolve(CONFIG_FILE_NAME); - - if (Files.notExists(configFile)) { - var defaultConfig = this.getClass().getClassLoader().getResourceAsStream("reticulum_default_config.yml"); - Files.copy(defaultConfig, configFile, StandardCopyOption.REPLACE_EXISTING); + public void sendCloseToRemote(Link link) { + if (nonNull(link)) { + var data = concatArrays("close::".getBytes(UTF_8),link.getDestination().getHash()); + Packet closePacket = new Packet(link, data); + var packetReceipt = closePacket.send(); + packetReceipt.setDeliveryCallback(this::closePacketDelivered); + packetReceipt.setTimeoutCallback(this::packetTimedOut); + } else { + log.debug("can't send to null link"); } } @@ -236,10 +257,7 @@ public class RNSNetwork { } public void packetTimedOut(PacketReceipt receipt) { - log.info("packet timed out"); - if (receipt.getStatus() == PacketReceiptStatus.FAILED) { - log.info("packet timed out, receipt status: {}", PacketReceiptStatus.FAILED); - } + log.info("packet timed out, receipt status: {}", receipt.getStatus()); } public void clientConnected(Link link) { @@ -504,11 +522,28 @@ public class RNSNetwork { p.shutdown(); peerList.remove(p); } + } else { + peerList.remove(p); } } //removeExpiredPeers(this.linkedPeers); log.info("number of links (linkedPeers) after prunig: {}", peerList.size()); - log.info("we have {} non-initiator links, list: {}", incomingLinks.size(), incomingLinks); + //log.info("we have {} non-initiator links, list: {}", incomingLinks.size(), incomingLinks); + var activePeerCount = 0; + var lps = RNSNetwork.getInstance().getLinkedPeers(); + for (RNSPeer p: lps) { + pLink = p.getPeerLink(); + p.pingRemote(); + try { + TimeUnit.SECONDS.sleep(2); // allow for peers to disconnect gracefully + } catch (InterruptedException e) { + log.error("exception: {}", e); + } + if ((nonNull(pLink) && (pLink.getStatus() == ACTIVE))) { + activePeerCount = activePeerCount + 1; + } + } + log.info("we have {} active peers", activePeerCount); maybeAnnounce(getBaseDestination()); } diff --git a/src/main/java/org/qortal/network/RNSPeer.java b/src/main/java/org/qortal/network/RNSPeer.java index 399775db..618cf865 100644 --- a/src/main/java/org/qortal/network/RNSPeer.java +++ b/src/main/java/org/qortal/network/RNSPeer.java @@ -191,12 +191,10 @@ public class RNSPeer { } public void packetTimedOut(PacketReceipt receipt) { - log.info("packet timed out"); + log.info("packet timed out, receipt status: {}", receipt.getStatus()); if (receipt.getStatus() == PacketReceiptStatus.FAILED) { - log.info("packet timed out, receipt status: {}", PacketReceiptStatus.FAILED); this.peerTimedOut = true; - peerLink.teardown(); - //this.deleteMe = true; + this.peerLink.teardown(); } } @@ -230,14 +228,22 @@ public class RNSPeer { /** Utility methods */ public void pingRemote() { var link = this.peerLink; - log.info("pinging remote: {}", link); - var data = "ping".getBytes(UTF_8); - link.setPacketCallback(this::linkPacketReceived); - Packet pingPacket = new Packet(link, data); - PacketReceipt packetReceipt = pingPacket.send(); - //packetReceipt.setTimeout(3L); - packetReceipt.setTimeoutCallback(this::packetTimedOut); - packetReceipt.setDeliveryCallback(this::packetDelivered); + if (nonNull(link)) { + if (peerLink.getStatus() == ACTIVE) { + log.info("pinging remote: {}", link); + var data = "ping".getBytes(UTF_8); + link.setPacketCallback(this::linkPacketReceived); + Packet pingPacket = new Packet(link, data); + PacketReceipt packetReceipt = pingPacket.send(); + // Note: don't setTimeout, we want it to timeout with FAIL if not deliverable + //packetReceipt.setTimeout(5000L); + packetReceipt.setTimeoutCallback(this::packetTimedOut); + packetReceipt.setDeliveryCallback(this::packetDelivered); + } else { + log.info("can't send ping to a peer {} with (link) status: {}", + Hex.encodeHexString(peerLink.getDestination().getHash()), peerLink.getStatus()); + } + } } //public void shutdownLink(Link link) { diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java index eaa7be2a..c49074c5 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBArbitraryRepository.java @@ -1024,7 +1024,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository { String tag5 = null; if (tags != null) { - if (tags.size() > 0) tag1 = tags.get(0); + if (!tags.isEmpty()) tag1 = tags.get(0); if (tags.size() > 1) tag2 = tags.get(1); if (tags.size() > 2) tag3 = tags.get(2); if (tags.size() > 3) tag4 = tags.get(3); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 6bc4fa49..571a587d 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -69,10 +69,10 @@ public class HSQLDBChatRepository implements ChatRepository { bindParams.add(chatReferenceBytes); } - if (hasChatReference != null && hasChatReference == true) { + if (hasChatReference != null && hasChatReference) { whereClauses.add("chat_reference IS NOT NULL"); } - else if (hasChatReference != null && hasChatReference == false) { + else if (hasChatReference != null && !hasChatReference) { whereClauses.add("chat_reference IS NULL"); } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBImportExport.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBImportExport.java index 3e6dd534..4d62fed5 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBImportExport.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBImportExport.java @@ -137,7 +137,7 @@ public class HSQLDBImportExport { String existingTradePrivateKey = (String) existingTradeBotDataItem.get("tradePrivateKey"); // Check if we already have an entry for this trade boolean found = allTradeBotData.stream().anyMatch(tradeBotData -> Base58.encode(tradeBotData.getTradePrivateKey()).equals(existingTradePrivateKey)); - if (found == false) + if (!found) // Add the data from the backup file to our "allTradeBotDataJson" array as it's not currently in the db allTradeBotDataJson.put(existingTradeBotDataItem); } diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index df59c037..de0ce5ed 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -211,7 +211,7 @@ public class Settings { public long recoveryModeTimeout = 9999999999999L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "4.5.0"; + private String minPeerVersion = "4.5.1"; /** Whether to allow connections with peers below minPeerVersion * If true, we won't sync with them but they can still sync with us, and will show in the peers list * If false, sync will be blocked both ways, and they will not appear in the peers list */ diff --git a/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java b/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java index a9577bc5..f3511ded 100644 --- a/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java +++ b/src/main/java/org/qortal/transaction/CancelGroupBanTransaction.java @@ -70,6 +70,10 @@ public class CancelGroupBanTransaction extends Transaction { if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress())) return ValidationResult.NOT_GROUP_ADMIN; + // Can't unban if not group's current owner + if (!admin.getAddress().equals(groupData.getOwner())) + return ValidationResult.INVALID_GROUP_OWNER; + Account member = getMember(); // Check ban actually exists diff --git a/src/main/java/org/qortal/transaction/ChatTransaction.java b/src/main/java/org/qortal/transaction/ChatTransaction.java index 3004b5b9..a255e781 100644 --- a/src/main/java/org/qortal/transaction/ChatTransaction.java +++ b/src/main/java/org/qortal/transaction/ChatTransaction.java @@ -168,7 +168,7 @@ public class ChatTransaction extends Transaction { // Check for blocked author by registered name List names = this.repository.getNameRepository().getNamesByOwner(this.chatTransactionData.getSender()); - if (names != null && names.size() > 0) { + if (names != null && !names.isEmpty()) { for (NameData nameData : names) { if (nameData != null && nameData.getName() != null) { if (ListUtils.isNameBlocked(nameData.getName())) { diff --git a/src/main/java/org/qortal/transaction/GroupBanTransaction.java b/src/main/java/org/qortal/transaction/GroupBanTransaction.java index 27c00d5c..1716d206 100644 --- a/src/main/java/org/qortal/transaction/GroupBanTransaction.java +++ b/src/main/java/org/qortal/transaction/GroupBanTransaction.java @@ -70,6 +70,10 @@ public class GroupBanTransaction extends Transaction { if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress())) return ValidationResult.NOT_GROUP_ADMIN; + // Can't ban if not group's current owner + if (!admin.getAddress().equals(groupData.getOwner())) + return ValidationResult.INVALID_GROUP_OWNER; + Account offender = getOffender(); // Can't ban group owner diff --git a/src/main/java/org/qortal/transaction/GroupKickTransaction.java b/src/main/java/org/qortal/transaction/GroupKickTransaction.java index d67f3d15..3c426039 100644 --- a/src/main/java/org/qortal/transaction/GroupKickTransaction.java +++ b/src/main/java/org/qortal/transaction/GroupKickTransaction.java @@ -82,6 +82,10 @@ public class GroupKickTransaction extends Transaction { if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress())) return ValidationResult.INVALID_GROUP_OWNER; + // Can't kick if not group's current owner + if (!admin.getAddress().equals(groupData.getOwner())) + return ValidationResult.INVALID_GROUP_OWNER; + // Check creator has enough funds if (admin.getConfirmedBalance(Asset.QORT) < this.groupKickTransactionData.getFee()) return ValidationResult.NO_BALANCE; diff --git a/src/main/java/org/qortal/transaction/RewardShareTransaction.java b/src/main/java/org/qortal/transaction/RewardShareTransaction.java index 635c8c8a..b2261181 100644 --- a/src/main/java/org/qortal/transaction/RewardShareTransaction.java +++ b/src/main/java/org/qortal/transaction/RewardShareTransaction.java @@ -43,7 +43,7 @@ public class RewardShareTransaction extends Transaction { } private RewardShareData getExistingRewardShare() throws DataException { - if (this.haveCheckedForExistingRewardShare == false) { + if (!this.haveCheckedForExistingRewardShare) { this.haveCheckedForExistingRewardShare = true; // Look up any existing reward-share (using transaction's reward-share public key) diff --git a/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java b/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java index a2546c69..b61594bd 100644 --- a/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java +++ b/src/main/java/org/qortal/transaction/UpdateGroupTransaction.java @@ -83,6 +83,10 @@ public class UpdateGroupTransaction extends Transaction { Account owner = getOwner(); + // Check creator is group's current owner + if (!owner.getAddress().equals(groupData.getOwner())) + return ValidationResult.INVALID_GROUP_OWNER; + // Check creator has enough funds if (owner.getConfirmedBalance(Asset.QORT) < this.updateGroupTransactionData.getFee()) return ValidationResult.NO_BALANCE; diff --git a/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java b/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java index 2d3a2358..f641255f 100644 --- a/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java +++ b/src/main/java/org/qortal/utils/ArbitraryTransactionUtils.java @@ -398,7 +398,7 @@ public class ArbitraryTransactionUtils { public static ArbitraryResourceStatus getStatus(Service service, String name, String identifier, Boolean build, boolean updateCache) { // If "build" has been specified, build the resource before returning its status - if (build != null && build == true) { + if (build != null && build) { try { ArbitraryDataReader reader = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, identifier); if (!reader.isBuilding()) { diff --git a/src/main/java/org/qortal/utils/Base58.java b/src/main/java/org/qortal/utils/Base58.java index 8fa9ddad..f2ad611c 100644 --- a/src/main/java/org/qortal/utils/Base58.java +++ b/src/main/java/org/qortal/utils/Base58.java @@ -111,7 +111,7 @@ public class Base58 { // // Nothing to do if we have an empty string // - if (string.length() == 0) + if (string.isEmpty()) return null; // // Convert the input string to a byte sequence diff --git a/src/main/java/org/qortal/utils/BlockArchiveUtils.java b/src/main/java/org/qortal/utils/BlockArchiveUtils.java index 33482fc6..99670614 100644 --- a/src/main/java/org/qortal/utils/BlockArchiveUtils.java +++ b/src/main/java/org/qortal/utils/BlockArchiveUtils.java @@ -59,7 +59,7 @@ public class BlockArchiveUtils { if (firstBlock == null || firstBlock.getBlockData().getHeight() != startHeight) { throw new IllegalStateException("Non matching first block when importing from archive"); } - if (blockInfoList.size() > 0) { + if (!blockInfoList.isEmpty()) { BlockTransformation lastBlock = blockInfoList.get(blockInfoList.size() - 1); if (lastBlock == null || lastBlock.getBlockData().getHeight() != endHeight) { throw new IllegalStateException("Non matching last block when importing from archive"); diff --git a/src/main/java/org/qortal/utils/Unicode.java b/src/main/java/org/qortal/utils/Unicode.java index c3e282ce..9b837a07 100644 --- a/src/main/java/org/qortal/utils/Unicode.java +++ b/src/main/java/org/qortal/utils/Unicode.java @@ -141,7 +141,7 @@ public abstract class Unicode { while ((line = bufferedReader.readLine()) != null) { line = line.trim(); - if (line.startsWith("#") || line.length() == 0) + if (line.startsWith("#") || line.isEmpty()) continue; String[] charCodes = line.split(","); diff --git a/src/test/java/org/qortal/test/apps/SyncReport.java b/src/test/java/org/qortal/test/apps/SyncReport.java index 402ae118..803878c8 100644 --- a/src/test/java/org/qortal/test/apps/SyncReport.java +++ b/src/test/java/org/qortal/test/apps/SyncReport.java @@ -180,8 +180,7 @@ public class SyncReport { // Sync went bad syncEvent = null; - continue; - } + } } for (SyncEvent se : syncEvents) { diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataFileTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataFileTests.java index ac633717..7703c062 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataFileTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataFileTests.java @@ -21,6 +21,7 @@ public class ArbitraryDataFileTests extends Common { public void testSplitAndJoin() throws DataException { String dummyDataString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; ArbitraryDataFile arbitraryDataFile = new ArbitraryDataFile(dummyDataString.getBytes(), null, false); + arbitraryDataFile.save(); assertTrue(arbitraryDataFile.exists()); assertEquals(62, arbitraryDataFile.size()); assertEquals("3eyjYjturyVe61grRX42bprGr3Cvw6ehTy4iknVnosDj", arbitraryDataFile.digest58()); @@ -51,6 +52,7 @@ public class ArbitraryDataFileTests extends Common { new Random().nextBytes(randomData); // No need for SecureRandom here ArbitraryDataFile arbitraryDataFile = new ArbitraryDataFile(randomData, null, false); + arbitraryDataFile.save(); assertTrue(arbitraryDataFile.exists()); assertEquals(fileSize, arbitraryDataFile.size()); String originalFileDigest = arbitraryDataFile.digest58(); diff --git a/src/test/java/org/qortal/test/at/CrowdfundTests.java b/src/test/java/org/qortal/test/at/CrowdfundTests.java index 6a60c1ab..1964be64 100644 --- a/src/test/java/org/qortal/test/at/CrowdfundTests.java +++ b/src/test/java/org/qortal/test/at/CrowdfundTests.java @@ -7,7 +7,6 @@ import org.junit.Test; import org.qortal.account.Account; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; -import org.qortal.at.AT; import org.qortal.block.Block; import org.qortal.data.at.ATData; import org.qortal.data.at.ATStateData; diff --git a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java index 8c41d2d2..9de5e037 100644 --- a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java @@ -1,7 +1,5 @@ package org.qortal.test.crosschain; -import org.junit.Ignore; -import org.junit.Test; import org.qortal.crosschain.Bitcoin; import org.qortal.crosschain.Bitcoiny; diff --git a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java index 01b9449b..1e0bd876 100644 --- a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java +++ b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java @@ -11,7 +11,6 @@ import org.qortal.crypto.Crypto; import org.qortal.transform.TransformationException; import java.util.List; -import java.util.Set; import static org.junit.Assert.*; import static org.qortal.crosschain.BitcoinyHTLC.Status.*; diff --git a/start.sh b/start.sh index cc80dceb..cb738fa2 100755 --- a/start.sh +++ b/start.sh @@ -34,7 +34,7 @@ fi # Comment out for bigger systems, e.g. non-routers # or when API documentation is enabled # Uncomment (remove '#' sign) line below if your system has less than 12GB of RAM for optimal RAM defaults -JVM_MEMORY_ARGS="-Xss256m -XX:+UseSerialGC" +#JVM_MEMORY_ARGS="-Xss256m -XX:+UseSerialGC" # Although java.net.preferIPv4Stack is supposed to be false # by default in Java 11, on some platforms (e.g. FreeBSD 12), @@ -43,6 +43,9 @@ JVM_MEMORY_ARGS="-Xss256m -XX:+UseSerialGC" nohup nice -n 20 java \ -Djava.net.preferIPv4Stack=false \ ${JVM_MEMORY_ARGS} \ + --add-opens=java.base/java.lang=ALL-UNNAMED \ + --add-opens=java.base/java.net=ALL-UNNAMED \ + --illegal-access=warn \ -jar qortal.jar \ 1>run.log 2>&1 &