Merge pull request #200 from jschulthess/reticulum

Reticulum Mesh Fixes, synchronized code with qortal-4.5.2-a02d1ce.
This commit is contained in:
crowetic 2024-08-20 11:13:46 -07:00 committed by GitHub
commit ce33abcade
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
90 changed files with 2237 additions and 413 deletions

View File

@ -7,12 +7,12 @@
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20240707083116</lastUpdated>
<lastUpdated>20240814140435</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>1.0-SNAPSHOT</value>
<updated>20240707083116</updated>
<updated>20240814140435</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>

View File

@ -6,6 +6,6 @@
<versions>
<version>1.0-SNAPSHOT</version>
</versions>
<lastUpdated>20240707083116</lastUpdated>
<lastUpdated>20240814140435</lastUpdated>
</versioning>
</metadata>

Binary file not shown.

View File

@ -0,0 +1,123 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ciyam</groupId>
<artifactId>AT</artifactId>
<version>1.4.2</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>false</skipTests>
<bouncycastle.version>1.70</bouncycastle.version>
<junit.version>4.13.2</junit.version>
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<maven-source-plugin.version>3.3.0</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.6.3</maven-javadoc-plugin.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.4.1</maven-jar-plugin.version>
</properties>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>${skipTests}</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<executions>
<execution>
<id>attach-javadoc</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -3,13 +3,14 @@
<groupId>org.ciyam</groupId>
<artifactId>AT</artifactId>
<versioning>
<release>1.4.1</release>
<release>1.4.2</release>
<versions>
<version>1.3.7</version>
<version>1.3.8</version>
<version>1.4.0</version>
<version>1.4.1</version>
<version>1.4.2</version>
</versions>
<lastUpdated>20231212092227</lastUpdated>
<lastUpdated>20240426084210</lastUpdated>
</versioning>
</metadata>

323
pom.xml
View File

@ -3,62 +3,65 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId>
<artifactId>qortal</artifactId>
<version>4.5.1</version>
<version>4.5.2</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>true</skipTests>
<altcoinj.version>7dc8c6f</altcoinj.version>
<bitcoinj.version>0.15.10</bitcoinj.version>
<bouncycastle.version>1.70</bouncycastle.version>
<build.timestamp>${maven.build.timestamp}</build.timestamp>
<ciyam-at.version>1.4.1</ciyam-at.version>
<ciyam-at.version>1.4.2</ciyam-at.version>
<commons-net.version>3.8.0</commons-net.version>
<commons-text.version>1.11.0</commons-text.version>
<commons-io.version>2.15.1</commons-io.version>
<commons-compress.version>1.26.1</commons-compress.version>
<commons-text.version>1.12.0</commons-text.version>
<commons-io.version>2.16.1</commons-io.version>
<commons-compress.version>1.26.2</commons-compress.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
<dagger.version>1.2.2</dagger.version>
<extendedset.version>0.12.3</extendedset.version>
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
<grpc.version>1.62.2</grpc.version>
<guava.version>33.0.0-jre</guava.version>
<grpc.version>1.65.0</grpc.version>
<guava.version>33.2.1-jre</guava.version>
<hamcrest-library.version>2.2</hamcrest-library.version>
<homoglyph.version>1.2.1</homoglyph.version>
<hsqldb.version>2.5.1</hsqldb.version>
<icu4j.version>74.2</icu4j.version>
<icu4j.version>75.1</icu4j.version>
<java-diff-utils.version>4.12</java-diff-utils.version>
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
<jaxb-runtime.version>2.3.9</jaxb-runtime.version>
<jersey.version>2.41</jersey.version>
<jersey.version>2.42</jersey.version>
<jetty.version>9.4.54.v20240208</jetty.version>
<json-simple.version>1.1.1</json-simple.version>
<json.version>20240303</json.version>
<jsoup.version>1.17.2</jsoup.version>
<junit-jupiter-engine.version>5.10.0</junit-jupiter-engine.version>
<junit-jupiter-engine.version>5.11.0-M2</junit-jupiter-engine.version>
<lifecycle-mapping.version>1.0.0</lifecycle-mapping.version>
<log4j.version>2.23.0</log4j.version>
<log4j.version>2.23.1</log4j.version>
<mail.version>1.5.0-b01</mail.version>
<maven-build-helper-plugin.version>3.5.0</maven-build-helper-plugin.version>
<maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
<maven-build-helper-plugin.version>3.6.0</maven-build-helper-plugin.version>
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<maven-package-info-plugin.version>1.1.0</maven-package-info-plugin.version>
<maven-plugin.version>2.16.2</maven-plugin.version>
<!--<maven-plugin.version>2.16.2</maven-plugin.version>-->
<maven-plugin.version>3.12.1</maven-plugin.version>
<maven-reproducible-build-plugin.version>0.16</maven-reproducible-build-plugin.version>
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
<maven-shade-plugin.version>3.5.2</maven-shade-plugin.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-shade-plugin.version>3.6.0</maven-shade-plugin.version>
<maven-surefire-plugin.version>3.3.0</maven-surefire-plugin.version>
<protobuf.version>3.25.3</protobuf.version>
<replacer.version>1.5.3</replacer.version>
<simplemagic.version>1.17</simplemagic.version>
<slf4j.version>1.7.36</slf4j.version>
<swagger-api.version>2.0.10</swagger-api.version>
<swagger-ui.version>5.11.8</swagger-ui.version>
<swagger-ui.version>5.17.14</swagger-ui.version>
<upnp.version>1.2</upnp.version>
<xz.version>1.9</xz.version>
<lombok.version>1.18.30</lombok.version>
<jackson.version>2.14.3</jackson.version>
<jackson.version>2.16.1</jackson.version>
<slf4j.version>2.0.12</slf4j.version>
<nitrite.version>4.3.0</nitrite.version>
<junit.version>5.9.2</junit.version>
</properties>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
@ -376,11 +379,6 @@
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>${skipTests}</skipTests>
<!-- for Java >=17.x unit testing ("higher restrictions for some internal modules") -->
<argLine>
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
@ -399,11 +397,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
<!--
<versionRange>
[3.6.0,)
</versionRange>
-->
<goals>
<goal>unpack</goal>
</goals>
@ -417,11 +410,6 @@
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>${replacer.version}</version>
<!--
<versionRange>
[1.5.3,)
</versionRange>
-->
<goals>
<goal>replace</goal>
</goals>
@ -442,6 +430,10 @@
<id>project.local</id>
<name>project</name>
<url>file:${project.basedir}/lib</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
<!-- jitpack for build-on-demand of altcoinj -->
<repository>
@ -453,6 +445,26 @@
</snapshots>
</repository>
</repositories>
<!--
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.dizitart</groupId>
<artifactId>nitrite-bom</artifactId>
<version>${nitrite.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
-->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.codehaus.mojo/build-helper-maven-plugin -->
<dependency>
@ -578,7 +590,17 @@
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Logging: log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
@ -589,24 +611,11 @@
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- redirect slf4j to log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- redirect java.utils.logging to log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jul</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- Logging: slf4j used by Jetty/Jersey -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jul</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- Servlet related -->
<dependency>
<groupId>javax.servlet</groupId>
@ -795,97 +804,129 @@
<artifactId>jaxb-runtime</artifactId>
<version>${jaxb-runtime.version}</version>
</dependency>
<!-- reticulum_network_stack -->
<dependency>
<groupId>io.reticulum</groupId>
<artifactId>reticulum-network-stack</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- reticulum_network_stack -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>jackson-dataformat-msgpack</artifactId>
<version>0.9.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
-->
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.92.Final</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>com.macasaet.fernet</groupId>
<artifactId>fernet-java8</artifactId>
<version>1.4.2</version>
</dependency>
<!--
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.25.0</version>
</dependency>
-->
<dependency>
<groupId>com.igormaznitsa</groupId>
<artifactId>jbbp</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack-core</artifactId>
<version>0.9.6</version>
</dependency>
<!--
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.1</version>
</dependency>
<!-- already declared earlier
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!-- already declared earlier
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.26.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
-->
<!-- note: covered ? -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>jackson-dataformat-msgpack</artifactId>
<version>0.9.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>com.macasaet.fernet</groupId>
<artifactId>fernet-java8</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.igormaznitsa</groupId>
<artifactId>jbbp</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<!--<version>4.1.106.Final</version>-->
<version>5.0.0.Alpha2</version>
</dependency>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>5.4.2</version>
</dependency>
<!-- Nitrite Modules -->
<dependency>
<groupId>org.dizitart</groupId>
<artifactId>nitrite</artifactId>
<version>${nitrite.version}</version>
</dependency>
<dependency>
<groupId>org.dizitart</groupId>
<artifactId>nitrite-mvstore-adapter</artifactId>
<version>${nitrite.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.230</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2-mvstore</artifactId>
<version>2.3.230</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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()) {

View File

@ -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<TransactionData> fetchTransactions(Repository repository, List<TransactionType> txTypes, String address, boolean reverse) throws DataException {

View File

@ -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<TransactionData> fetchTransactions(Repository repository, List<TransactionType> txTypes, String address, boolean reverse) throws DataException {

View File

@ -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;
}
/**

View File

@ -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;

View File

@ -2,5 +2,5 @@ package org.qortal.api;
public enum SearchMode {
LATEST,
ALL;
ALL
}

View File

@ -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;
}

View File

@ -17,7 +17,7 @@ public class ConnectedPeer {
public enum Direction {
INBOUND,
OUTBOUND;
OUTBOUND
}
public Direction direction;

View File

@ -233,8 +233,7 @@ public class AddressesResource {
}
} catch (DataException e) {
continue;
}
}
}
// Sort by level

View File

@ -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<ArbitraryTransactionData> 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);
}

View File

@ -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<BlockData> 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);
}

View File

@ -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<ServerConnectionInfo> 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(

View File

@ -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<ServerConnectionInfo> 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(

View File

@ -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<ServerConnectionInfo> 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(

View File

@ -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<ServerConnectionInfo> 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(

View File

@ -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<ServerConnectionInfo> 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(

View File

@ -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<ServerConnectionInfo> 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(

View File

@ -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<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getFilteredAcctMap(foreignBlockchain);

View File

@ -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<ServerConnectionInfo> 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);
}
}

View File

@ -330,8 +330,8 @@ public class TransactionsResource {
public enum ConfirmationStatus {
CONFIRMED,
UNCONFIRMED,
BOTH;
}
BOTH
}
@GET
@Path("/search")

View File

@ -31,7 +31,7 @@ public class AdminStatusWebSocket extends ApiWebSocket implements Listener {
return;
}
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@ -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

View File

@ -86,7 +86,7 @@ public class PresenceWebSocket extends ApiWebSocket implements Listener {
return;
}
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@ -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

View File

@ -67,7 +67,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
return;
}
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@ -29,7 +29,7 @@ public class TradePresenceWebSocket extends ApiWebSocket implements Listener {
populateCurrentInfo();
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@ -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");
}

View File

@ -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<ArbitraryDataFileChunk> 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<attempts; i++) {
if (this.delete()) {
@ -424,7 +439,7 @@ public class ArbitraryDataFile {
boolean success = false;
// Delete the individual chunks
if (this.chunks != null && this.chunks.size() > 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<byte[]> chunkHashList() {
List<byte[]> 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());

View File

@ -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;
}

View File

@ -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<String> indexFiles = ArbitraryDataRenderer.indexFiles();
for (String indexFile : indexFiles) {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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.

View File

@ -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

View File

@ -17,7 +17,7 @@ public class ChatNotifier {
void notify(ChatTransactionData chatTransactionData);
}
private Map<Session, Listener> listenersBySession = new HashMap<>();
private final Map<Session, Listener> listenersBySession = new HashMap<>();
private ChatNotifier() {
}

View File

@ -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<Peer> hasOldVersion = peer -> {
final String minPeerVersion = Settings.getInstance().getMinPeerVersion();
return peer.isAtLeastVersion(minPeerVersion) == false;
return !peer.isAtLeastVersion(minPeerVersion);
};
public static final Predicate<Peer> 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;
}
}

View File

@ -538,7 +538,6 @@ public class OnlineAccountsManager {
if (++i > 1 + 1) {
iterator.remove();
continue;
}
}
} catch (DataException e) {

View File

@ -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;

View File

@ -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<Peer> qualifiedPeers) {
List<Peer> 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 <BlockSummaryData> 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();

View File

@ -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;

View File

@ -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);

View File

@ -43,7 +43,7 @@ public class ArbitraryDataFileManager extends Thread {
/**
* Map to keep track of hashes that we might need to relay
*/
public List<ArbitraryRelayInfo> arbitraryRelayMap = Collections.synchronizedList(new ArrayList<>());
public final List<ArbitraryRelayInfo> 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<ArbitraryDirectConnectionInfo> directConnectionInfo = Collections.synchronizedList(new ArrayList<>());
private final List<ArbitraryDirectConnectionInfo> 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) {

View File

@ -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()) {

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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<ChainableServer> 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<ChainableServerConnection> 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<ChainableServerConnection> 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);
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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<ChainableServerConnection> 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<ChainableServerConnection> getConnections() {
return this.connections;
}
}

View File

@ -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<String, BitcoinyTransaction> 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<byte[]> rawBlockHeaders = new ArrayList<>(returnedCount.intValue());
List<byte[]> 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<Server> 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> chainableServerConnection = makeConnection(server, this.getClass().getSimpleName());
if(chainableServerConnection.isPresent() && chainableServerConnection.get().isSuccess() ) return true;
}
return false;
}
private Optional<ChainableServerConnection> 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<Server> 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.
* <p>
@ -846,12 +862,19 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
/**
* Closes connection to <tt>server</tt> if it is currently connected server.
*
* @param server
* @param notes
*/
private void closeServer(ChainableServer server) {
private Optional<ChainableServerConnection> 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<ChainableServerConnection> 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<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) {
return this.makeConnection(server, requestedBy);
}
@Override
public List<ChainableServerConnection> getServerConnections() {
return this.recorder.getConnections();
}
@Override
public ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port) {
return new ElectrumX.Server(hostName, type, port);
}
}

View File

@ -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<Server> initialServerList, Map<Server.ConnectionType, Integer> 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<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) throws ForeignBlockchainException {
closeServer( requestedBy, "Connecting to different server by request." );
Optional<ChainableServerConnection> connection = makeConnection(server, requestedBy);
if( !connection.isPresent() || !connection.get().isSuccess() ) {
haveConnection();
}
return connection;
}
@Override
public List<ChainableServerConnection> 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> 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<ChainableServerConnection> 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 <tt>server</tt> if it is currently connected server.
*
* @param server
* @param requestedBy
*/
private void closeServer(ChainableServer server) {
private Optional<ChainableServerConnection> 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<ChainableServerConnection> closeServer(String requestedBy, String notes) {
synchronized (this.serverLock) {
this.closeServer(this.currentServer);
return this.closeServer(this.currentServer, notes, requestedBy);
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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;
}

View File

@ -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());

View File

@ -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);

View File

@ -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());
}

View File

@ -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) {

View File

@ -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);

View File

@ -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");
}

View File

@ -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);
}

View File

@ -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 */

View File

@ -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

View File

@ -168,7 +168,7 @@ public class ChatTransaction extends Transaction {
// Check for blocked author by registered name
List<NameData> 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())) {

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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()) {

View File

@ -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

View File

@ -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");

View File

@ -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(",");

View File

@ -180,8 +180,7 @@ public class SyncReport {
// Sync went bad
syncEvent = null;
continue;
}
}
}
for (SyncEvent se : syncEvents) {

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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.*;

View File

@ -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 &