From 22e3140ff0241b07cb9aabcbe3e6cfb03ef8efdd Mon Sep 17 00:00:00 2001 From: QuickMythril Date: Tue, 16 Mar 2021 03:00:55 -0400 Subject: [PATCH 1/3] add version on tooltip add Version Number on Qortal Core tooltip. https://i.imgur.com/eLnLnQ5.png --- src/main/java/org/qortal/controller/Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index b2c6c182..ce270ac1 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -775,7 +775,7 @@ public class Controller extends Thread { actionText = Translator.INSTANCE.translate("SysTray", "MINTING_DISABLED"); } - String tooltip = String.format("%s - %d %s - %s %d", actionText, numberOfPeers, connectionsText, heightText, height); + String tooltip = String.format("%s - %d %s - %s %d", actionText, numberOfPeers, connectionsText, heightText, height) + "\n" + String.format("Build version: %s", this.buildVersion); SysTray.getInstance().setToolTipText(tooltip); this.callbackExecutor.execute(() -> { From 4312ebfcc35803a5be5e8b2fea772e0fdbba458b Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 17 Apr 2021 20:44:57 +0100 Subject: [PATCH 2/3] Adapted the HSQLDBRepository.exportNodeLocalData() method It now has a new parameter - keepArchivedCopy - which when set to true will cause it to rename an existing TradeBotStates.script to TradeBotStates-archive-.script before creating a new backup. This should avoid keys being lost if a new backup is taken after replacing the db. In a future version we can improve this in such a way that it combines existing and new backups into a single file. This is just a "quick fix" to increase the chances of keys being recoverable after accidentally bootstrapping without a backup. --- .../qortal/api/resource/AdminResource.java | 2 +- .../org/qortal/repository/Repository.java | 2 +- .../repository/hsqldb/HSQLDBRepository.java | 53 +++++++++++++++---- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/AdminResource.java b/src/main/java/org/qortal/api/resource/AdminResource.java index 8069a0d5..c295b90b 100644 --- a/src/main/java/org/qortal/api/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/resource/AdminResource.java @@ -547,7 +547,7 @@ public class AdminResource { blockchainLock.lockInterruptibly(); try { - repository.exportNodeLocalData(); + repository.exportNodeLocalData(true); return "true"; } finally { blockchainLock.unlock(); diff --git a/src/main/java/org/qortal/repository/Repository.java b/src/main/java/org/qortal/repository/Repository.java index 656e6e1e..5438f1d9 100644 --- a/src/main/java/org/qortal/repository/Repository.java +++ b/src/main/java/org/qortal/repository/Repository.java @@ -49,7 +49,7 @@ public interface Repository extends AutoCloseable { public void performPeriodicMaintenance() throws DataException; - public void exportNodeLocalData() throws DataException; + public void exportNodeLocalData(boolean keepArchivedCopy) throws DataException; public void importDataFromFile(String filename) throws DataException; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java index 7c514d73..5557c13e 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java @@ -52,6 +52,7 @@ import org.qortal.repository.TransactionRepository; import org.qortal.repository.VotingRepository; import org.qortal.repository.hsqldb.transaction.HSQLDBTransactionRepository; import org.qortal.settings.Settings; +import org.qortal.utils.NTP; public class HSQLDBRepository implements Repository { @@ -459,10 +460,44 @@ public class HSQLDBRepository implements Repository { } @Override - public void exportNodeLocalData() throws DataException { + public void exportNodeLocalData(boolean keepArchivedCopy) throws DataException { + + // Create the qortal-backup folder if it doesn't exist + Path backupPath = Paths.get("qortal-backup"); + try { + Files.createDirectories(backupPath); + } catch (IOException e) { + LOGGER.info("Unable to create backup folder"); + throw new DataException("Unable to create backup folder"); + } + + // We need to rename or delete an existing TradeBotStates backup before creating a new one + File tradeBotStatesBackupFile = new File("qortal-backup/TradeBotStates.script"); + if (tradeBotStatesBackupFile.exists()) { + if (keepArchivedCopy) { + // Rename existing TradeBotStates backup, to make sure that we're not overwriting any keys + File archivedBackupFile = new File(String.format("qortal-backup/TradeBotStates-archive-%d.script", NTP.getTime())); + if (tradeBotStatesBackupFile.renameTo(archivedBackupFile)) + LOGGER.info(String.format("Moved existing TradeBotStates backup file to %s", archivedBackupFile.getPath())); + else + throw new DataException("Unable to rename existing TradeBotStates backup"); + } else { + // Delete existing copy + LOGGER.info("Deleting existing TradeBotStates backup because it is being replaced with a new one"); + tradeBotStatesBackupFile.delete(); + } + } + + // There's currently no need to take an archived copy of the MintingAccounts data - just delete the old one if it exists + File mintingAccountsBackupFile = new File("qortal-backup/MintingAccounts.script"); + if (mintingAccountsBackupFile.exists()) { + LOGGER.info("Deleting existing MintingAccounts backup because it is being replaced with a new one"); + mintingAccountsBackupFile.delete(); + } + try (Statement stmt = this.connection.createStatement()) { - stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE MintingAccounts DATA TO 'MintingAccounts.script'"); - stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE TradeBotStates DATA TO 'TradeBotStates.script'"); + stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE MintingAccounts DATA TO 'qortal-backup/MintingAccounts.script'"); + stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE TradeBotStates DATA TO 'qortal-backup/TradeBotStates.script'"); LOGGER.info("Exported sensitive/node-local data: minting keys and trade bot states"); } catch (SQLException e) { throw new DataException("Unable to export sensitive/node-local data from repository"); @@ -475,12 +510,12 @@ public class HSQLDBRepository implements Repository { LOGGER.info(() -> String.format("Importing data into repository from %s", filename)); String escapedFilename = stmt.enquoteLiteral(filename); - stmt.execute("PERFORM IMPORT SCRIPT DATA FROM " + escapedFilename + " STOP ON ERROR"); + stmt.execute("PERFORM IMPORT SCRIPT DATA FROM " + escapedFilename + " CONTINUE ON ERROR"); LOGGER.info(() -> String.format("Imported data into repository from %s", filename)); } catch (SQLException e) { LOGGER.info(() -> String.format("Failed to import data into repository from %s: %s", filename, e.getMessage())); - throw new DataException("Unable to export sensitive/node-local data from repository: " + e.getMessage()); + throw new DataException("Unable to import sensitive/node-local data to repository: " + e.getMessage()); } } @@ -681,7 +716,7 @@ public class HSQLDBRepository implements Repository { /** * Execute PreparedStatement and return changed row count. * - * @param preparedStatement + * @param sql * @param objects * @return number of changed rows * @throws SQLException @@ -693,8 +728,8 @@ public class HSQLDBRepository implements Repository { /** * Execute batched PreparedStatement * - * @param preparedStatement - * @param objects + * @param sql + * @param batchedObjects * @return number of changed rows * @throws SQLException */ @@ -818,7 +853,7 @@ public class HSQLDBRepository implements Repository { * * @param tableName * @param whereClause - * @param objects + * @param batchedObjects * @throws SQLException */ public int deleteBatch(String tableName, String whereClause, List batchedObjects) throws SQLException { From e9b4a3f6b357d1c367ecb03d5c473894931c1853 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 17 Apr 2021 20:45:35 +0100 Subject: [PATCH 3/3] Automatically backup trade bot data when starting a new trade (from either side). --- .../tradebot/LitecoinACCTv1TradeBot.java | 6 ++++++ .../qortal/controller/tradebot/TradeBot.java | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java index 15764199..286cbf74 100644 --- a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv1TradeBot.java @@ -211,6 +211,9 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot { TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Built AT %s. Waiting for deployment", atAddress)); + // Attempt to backup the trade bot data + TradeBot.backupTradeBotData(repository); + // Return to user for signing and broadcast as we don't have their Qortal private key try { return DeployAtTransactionTransformer.toBytes(deployAtTransactionData); @@ -283,6 +286,9 @@ public class LitecoinACCTv1TradeBot implements AcctTradeBot { tradeForeignPublicKey, tradeForeignPublicKeyHash, crossChainTradeData.expectedForeignAmount, xprv58, null, lockTimeA, receivingPublicKeyHash); + // Attempt to backup the trade bot data + TradeBot.backupTradeBotData(repository); + // Check we have enough funds via xprv58 to fund P2SH to cover expectedForeignAmount long p2shFee; try { diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java index 84e32125..94c7cefb 100644 --- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -267,6 +268,22 @@ public class TradeBot implements Listener { return secret; } + /*package*/ static void backupTradeBotData(Repository repository) { + // Attempt to backup the trade bot data. This an optional step and doesn't impact trading, so don't throw an exception on failure + try { + LOGGER.info("About to backup trade bot data..."); + ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock(); + blockchainLock.lockInterruptibly(); + try { + repository.exportNodeLocalData(true); + } finally { + blockchainLock.unlock(); + } + } catch (InterruptedException | DataException e) { + LOGGER.info(String.format("Failed to obtain blockchain lock when exporting trade bot data: %s", e.getMessage())); + } + } + /** Updates trade-bot entry to new state, with current timestamp, logs message and notifies state-change listeners. */ /*package*/ static void updateTradeBotState(Repository repository, TradeBotData tradeBotData, String newState, int newStateValue, Supplier logMessageSupplier) throws DataException {