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