diff --git a/WindowsInstaller/Qortal.aip b/WindowsInstaller/Qortal.aip
index 16b86fe2..722d881e 100755
--- a/WindowsInstaller/Qortal.aip
+++ b/WindowsInstaller/Qortal.aip
@@ -17,10 +17,10 @@
-
+
-
+
@@ -212,7 +212,7 @@
-
+
diff --git a/pom.xml b/pom.xml
index 4054ce71..2b115f61 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.qortal
qortal
- 3.2.3
+ 3.2.5
jar
true
diff --git a/src/main/java/org/qortal/api/resource/AdminResource.java b/src/main/java/org/qortal/api/resource/AdminResource.java
index 0e16297d..bf7294ab 100644
--- a/src/main/java/org/qortal/api/resource/AdminResource.java
+++ b/src/main/java/org/qortal/api/resource/AdminResource.java
@@ -394,6 +394,10 @@ public class AdminResource {
) @QueryParam("limit") Integer limit, @Parameter(
ref = "offset"
) @QueryParam("offset") Integer offset, @Parameter(
+ name = "tail",
+ description = "Fetch most recent log lines",
+ schema = @Schema(type = "boolean")
+ ) @QueryParam("tail") Boolean tail, @Parameter(
ref = "reverse"
) @QueryParam("reverse") Boolean reverse) {
LoggerContext loggerContext = (LoggerContext) LogManager.getContext();
@@ -409,6 +413,13 @@ public class AdminResource {
if (reverse != null && reverse)
logLines = Lists.reverse(logLines);
+ // Tail mode - return the last X lines (where X = limit)
+ if (tail != null && tail) {
+ if (limit != null && limit > 0) {
+ offset = logLines.size() - limit;
+ }
+ }
+
// offset out of bounds?
if (offset != null && (offset < 0 || offset >= logLines.size()))
return "";
@@ -429,7 +440,7 @@ public class AdminResource {
limit = Math.min(limit, logLines.size());
- logLines.subList(limit - 1, logLines.size()).clear();
+ logLines.subList(limit, logLines.size()).clear();
return String.join("\n", logLines);
} catch (IOException e) {
diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java
index 14504f37..9be4f145 100644
--- a/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java
+++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java
@@ -93,10 +93,12 @@ public class ArbitraryDataFile {
File outputFile = outputFilePath.toFile();
try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
outputStream.write(fileContent);
+ outputStream.close();
this.filePath = outputFilePath;
// Verify hash
- if (!this.hash58.equals(this.digest58())) {
- LOGGER.error("Hash {} does not match file digest {} for signature: {}", this.hash58, this.digest58(), Base58.encode(signature));
+ String digest58 = this.digest58();
+ if (!this.hash58.equals(digest58)) {
+ LOGGER.error("Hash {} does not match file digest {} for signature: {}", this.hash58, digest58, Base58.encode(signature));
this.delete();
throw new DataException("Data file digest validation failed");
}
diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java
index fcae749f..44ad4a7f 100644
--- a/src/main/java/org/qortal/block/BlockChain.java
+++ b/src/main/java/org/qortal/block/BlockChain.java
@@ -430,9 +430,8 @@ public class BlockChain {
}
public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) {
- // Scan through for reward at our height
- for (int i = 0; i < nameRegistrationUnitFees.size(); ++i)
- if (ourTimestamp >= nameRegistrationUnitFees.get(i).timestamp)
+ for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
+ if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
return nameRegistrationUnitFees.get(i).fee;
// Default to system-wide unit fee
diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java
index e45e8f9a..9966d6a9 100644
--- a/src/main/java/org/qortal/controller/BlockMinter.java
+++ b/src/main/java/org/qortal/controller/BlockMinter.java
@@ -224,7 +224,7 @@ public class BlockMinter extends Thread {
// The last iteration found a higher weight block in the network, so sleep for a while
// to allow is to sync the higher weight chain. We are sleeping here rather than when
// detected as we don't want to hold the blockchain lock open.
- LOGGER.info("Sleeping for 10 seconds...");
+ LOGGER.debug("Sleeping for 10 seconds...");
Thread.sleep(10 * 1000L);
}
@@ -333,13 +333,13 @@ public class BlockMinter extends Thread {
// If less than 30 seconds has passed since first detection the higher weight chain,
// we should skip our block submission to give us the opportunity to sync to the better chain
if (NTP.getTime() - timeOfLastLowWeightBlock < 30*1000L) {
- LOGGER.info("Higher weight chain found in peers, so not signing a block this round");
- LOGGER.info("Time since detected: {}", NTP.getTime() - timeOfLastLowWeightBlock);
+ LOGGER.debug("Higher weight chain found in peers, so not signing a block this round");
+ LOGGER.debug("Time since detected: {}ms", NTP.getTime() - timeOfLastLowWeightBlock);
continue;
}
else {
// More than 30 seconds have passed, so we should submit our block candidate anyway.
- LOGGER.info("More than 30 seconds passed, so proceeding to submit block candidate...");
+ LOGGER.debug("More than 30 seconds passed, so proceeding to submit block candidate...");
}
}
else {
diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java
index 81067315..0668fd1f 100644
--- a/src/main/java/org/qortal/controller/Controller.java
+++ b/src/main/java/org/qortal/controller/Controller.java
@@ -628,15 +628,20 @@ public class Controller extends Thread {
MessageType.INFO);
LOGGER.info("Starting scheduled repository maintenance. This can take a while...");
- try (final Repository repository = RepositoryManager.getRepository()) {
+ int attempts = 0;
+ while (attempts <= 5) {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ attempts++;
- // Timeout if the database isn't ready for maintenance after 60 seconds
- long timeout = 60 * 1000L;
- repository.performPeriodicMaintenance(timeout);
+ // Timeout if the database isn't ready for maintenance after 60 seconds
+ long timeout = 60 * 1000L;
+ repository.performPeriodicMaintenance(timeout);
- LOGGER.info("Scheduled repository maintenance completed");
- } catch (DataException | TimeoutException e) {
- LOGGER.error("Scheduled repository maintenance failed", e);
+ LOGGER.info("Scheduled repository maintenance completed");
+ break;
+ } catch (DataException | TimeoutException e) {
+ LOGGER.info("Scheduled repository maintenance failed. Retrying up to 5 times...", e);
+ }
}
// Get a new random interval
@@ -710,29 +715,6 @@ public class Controller extends Thread {
return lastMisbehaved != null && lastMisbehaved > NTP.getTime() - MISBEHAVIOUR_COOLOFF;
};
- /** True if peer has unknown height, lower height or same height and same block signature (unless we don't have their block signature). */
- public static Predicate hasShorterBlockchain = peer -> {
- BlockData highestBlockData = getInstance().getChainTip();
- int ourHeight = highestBlockData.getHeight();
- final PeerChainTipData peerChainTipData = peer.getChainTipData();
-
- // Ensure we have chain tip data for this peer
- if (peerChainTipData == null)
- return true;
-
- // Remove if peer is at a lower height than us
- Integer peerHeight = peerChainTipData.getLastHeight();
- if (peerHeight == null || peerHeight < ourHeight)
- return true;
-
- // Don't remove if peer is on a greater height chain than us, or if we don't have their block signature
- if (peerHeight > ourHeight || peerChainTipData.getLastBlockSignature() == null)
- return false;
-
- // Remove if signatures match
- return Arrays.equals(peerChainTipData.getLastBlockSignature(), highestBlockData.getSignature());
- };
-
public static final Predicate hasNoRecentBlock = peer -> {
final Long minLatestBlockTimestamp = getMinimumLatestBlockTimestamp();
final PeerChainTipData peerChainTipData = peer.getChainTipData();
diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java
index 21ffd3fc..8f3a34bb 100644
--- a/src/main/java/org/qortal/controller/Synchronizer.java
+++ b/src/main/java/org/qortal/controller/Synchronizer.java
@@ -240,9 +240,6 @@ public class Synchronizer extends Thread {
// Disregard peers that are on the same block as last sync attempt and we didn't like their chain
peers.removeIf(Controller.hasInferiorChainTip);
- // Remove peers with unknown height, lower height or same height and same block signature (unless we don't have their block signature)
- peers.removeIf(Controller.hasShorterBlockchain);
-
final int peersBeforeComparison = peers.size();
// Request recent block summaries from the remaining peers, and locate our common block with each
diff --git a/src/main/java/org/qortal/gui/Gui.java b/src/main/java/org/qortal/gui/Gui.java
index 12191349..4944db52 100644
--- a/src/main/java/org/qortal/gui/Gui.java
+++ b/src/main/java/org/qortal/gui/Gui.java
@@ -47,12 +47,12 @@ public class Gui {
this.splashFrame = SplashFrame.getInstance();
}
- protected static BufferedImage loadImage(String resourceName) throws IOException {
+ protected static BufferedImage loadImage(String resourceName) {
try (InputStream in = Gui.class.getResourceAsStream("/images/" + resourceName)) {
return ImageIO.read(in);
} catch (IllegalArgumentException | IOException | ServiceConfigurationError e) {
LOGGER.warn(String.format("Couldn't locate image resource \"images/%s\"", resourceName));
- throw new IOException(String.format("Couldn't locate image resource \"images/%s\"", resourceName));
+ return null;
}
}
diff --git a/src/main/java/org/qortal/gui/SplashFrame.java b/src/main/java/org/qortal/gui/SplashFrame.java
index 3fd452fc..c4ea51d0 100644
--- a/src/main/java/org/qortal/gui/SplashFrame.java
+++ b/src/main/java/org/qortal/gui/SplashFrame.java
@@ -1,7 +1,6 @@
package org.qortal.gui;
import java.awt.*;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.awt.image.BufferedImage;
@@ -30,23 +29,18 @@ public class SplashFrame {
private JLabel statusLabel;
public SplashPanel() {
- try {
- image = Gui.loadImage(defaultSplash);
-
- // Add logo
- JLabel imageLabel = new JLabel(new ImageIcon(image));
- imageLabel.setSize(new Dimension(300, 300));
- add(imageLabel);
- }
- catch (IOException e) {
- LOGGER.warn("Unable to load splash panel image");
- }
+ image = Gui.loadImage(defaultSplash);
setOpaque(true);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBorder(new EmptyBorder(10, 10, 10, 10));
setBackground(Color.BLACK);
+ // Add logo
+ JLabel imageLabel = new JLabel(new ImageIcon(image));
+ imageLabel.setSize(new Dimension(300, 300));
+ add(imageLabel);
+
// Add spacing
add(Box.createRigidArea(new Dimension(0, 16)));
@@ -81,20 +75,15 @@ public class SplashFrame {
this.splashDialog = new JFrame();
- try {
- List icons = new ArrayList<>();
- icons.add(Gui.loadImage("icons/icon16.png"));
- icons.add(Gui.loadImage("icons/qortal_ui_tray_synced.png"));
- icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing_time-alt.png"));
- icons.add(Gui.loadImage("icons/qortal_ui_tray_minting.png"));
- icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing.png"));
- icons.add(Gui.loadImage("icons/icon64.png"));
- icons.add(Gui.loadImage("icons/Qlogo_128.png"));
- this.splashDialog.setIconImages(icons);
- }
- catch (IOException e) {
- LOGGER.warn("Unable to load splash frame icons");
- }
+ List icons = new ArrayList<>();
+ icons.add(Gui.loadImage("icons/icon16.png"));
+ icons.add(Gui.loadImage("icons/qortal_ui_tray_synced.png"));
+ icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing_time-alt.png"));
+ icons.add(Gui.loadImage("icons/qortal_ui_tray_minting.png"));
+ icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing.png"));
+ icons.add(Gui.loadImage("icons/icon64.png"));
+ icons.add(Gui.loadImage("icons/Qlogo_128.png"));
+ this.splashDialog.setIconImages(icons);
this.splashPanel = new SplashPanel();
this.splashDialog.getContentPane().add(this.splashPanel);
diff --git a/src/main/java/org/qortal/gui/SysTray.java b/src/main/java/org/qortal/gui/SysTray.java
index 861c9ab0..7a24f825 100644
--- a/src/main/java/org/qortal/gui/SysTray.java
+++ b/src/main/java/org/qortal/gui/SysTray.java
@@ -61,13 +61,7 @@ public class SysTray {
this.popupMenu = createJPopupMenu();
// Build TrayIcon without AWT PopupMenu (which doesn't support Unicode)...
- try {
- this.trayIcon = new TrayIcon(Gui.loadImage("icons/qortal_ui_tray_synced.png"), "qortal", null);
- }
- catch (IOException e) {
- LOGGER.warn("Unable to load system tray icon");
- return;
- }
+ this.trayIcon = new TrayIcon(Gui.loadImage("icons/qortal_ui_tray_synced.png"), "qortal", null);
// ...and attach mouse listener instead so we can use JPopupMenu (which does support Unicode)
this.trayIcon.addMouseListener(new MouseAdapter() {
@Override
diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json
index bba5d3b9..c8502d1b 100644
--- a/src/main/resources/blockchain.json
+++ b/src/main/resources/blockchain.json
@@ -5,7 +5,8 @@
"maxBytesPerUnitFee": 1024,
"unitFee": "0.001",
"nameRegistrationUnitFees": [
- { "timestamp": 1645372800000, "fee": "5" }
+ { "timestamp": 1645372800000, "fee": "5" },
+ { "timestamp": 1651420800000, "fee": "1.25" }
],
"useBrokenMD160ForAddresses": false,
"requireGroupForApproval": false,
diff --git a/src/test/java/org/qortal/test/naming/MiscTests.java b/src/test/java/org/qortal/test/naming/MiscTests.java
index 8252453c..2bcd098d 100644
--- a/src/test/java/org/qortal/test/naming/MiscTests.java
+++ b/src/test/java/org/qortal/test/naming/MiscTests.java
@@ -356,8 +356,15 @@ public class MiscTests extends Common {
UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp();
pastFeeIncrease.timestamp = now - 1000L; // 1 second ago
pastFeeIncrease.fee = new AmountTypeAdapter().unmarshal("3");
- FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease), true);
+
+ // Set another increase in the future
+ futureFeeIncrease = new UnitFeesByTimestamp();
+ futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future
+ futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10");
+
+ FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease, futureFeeIncrease), true);
assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
+ assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
// Register a different name
// First try with the default unit fee